From 144504a752ec1e002684123035ed8aa02c99aa86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rikard=20Petr=C3=A9?= Date: Sat, 20 Jan 2024 15:53:19 +0100 Subject: [PATCH 001/171] odin/parser: Fix parsing of struct literal/call expression when closing brace/paren is on a new line without a comma after the last argument. --- core/odin/parser/parser.odin | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index f11d0eb73..cb4d2785b 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -416,24 +416,28 @@ end_of_line_pos :: proc(p: ^Parser, tok: tokenizer.Token) -> tokenizer.Pos { } expect_closing_brace_of_field_list :: proc(p: ^Parser) -> tokenizer.Token { + return expect_closing_token_of_field_list(p, .Close_Brace, "field list") +} + +expect_closing_token_of_field_list :: proc(p: ^Parser, closing_kind: tokenizer.Token_Kind, msg: string) -> tokenizer.Token { token := p.curr_tok - if allow_token(p, .Close_Brace) { + if allow_token(p, closing_kind) { return token } if allow_token(p, .Semicolon) && !tokenizer.is_newline(token) { str := tokenizer.token_to_string(token) error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", str) } - expect_brace := expect_token(p, .Close_Brace) + expect_closing := expect_token_after(p, closing_kind, msg) - if expect_brace.kind != .Close_Brace { - for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) { + if expect_closing.kind != closing_kind { + for p.curr_tok.kind != closing_kind && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) { advance_token(p) } return p.curr_tok } - return expect_brace + return expect_closing } is_non_inserted_semicolon :: proc(tok: tokenizer.Token) -> bool { @@ -2922,7 +2926,7 @@ parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { } p.expr_level -= 1 - close := expect_token_after(p, .Close_Brace, "compound literal") + close := expect_closing_brace_of_field_list(p); pos := type.pos if type != nil else open.pos lit := ast.new(ast.Comp_Lit, pos, end_pos(close)) @@ -2985,7 +2989,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { allow_token(p, .Comma) or_break } - close := expect_token_after(p, .Close_Paren, "argument list") + close := expect_closing_token_of_field_list(p, .Close_Paren, "argument list") p.expr_level -= 1 ce := ast.new(ast.Call_Expr, operand.pos, end_pos(close)) From 99825a28d7357b7e884ce0387f7e5fabd3180148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rikard=20Petr=C3=A9?= Date: Sat, 20 Jan 2024 16:00:41 +0100 Subject: [PATCH 002/171] odin/parser: Allow semicolon after return statement for the case: if x do return y; else do return z; --- core/odin/parser/parser.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index cb4d2785b..f9fef15a9 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1340,6 +1340,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { rs := ast.new(ast.Return_Stmt, tok.pos, end) rs.results = results[:] + expect_semicolon(p, rs) return rs case .Break, .Continue, .Fallthrough: From 239d4e10762a12e96280bd91003acbf2170cadf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rikard=20Petr=C3=A9?= Date: Sat, 20 Jan 2024 16:09:41 +0100 Subject: [PATCH 003/171] odin/tokenizer: Reset insert_semicolon to false in tokenizer.init to fix bug when tokenizing multiple files. --- core/odin/tokenizer/tokenizer.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 41de3ac8b..62170aa10 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -39,6 +39,7 @@ init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = defa t.read_offset = 0 t.line_offset = 0 t.line_count = len(src) > 0 ? 1 : 0 + t.insert_semicolon = false t.error_count = 0 t.path = path From 5533a327eb0f526cbebbe71124620fcbb0bc0649 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 22 Nov 2023 16:12:37 +0100 Subject: [PATCH 004/171] encoding/cbor: initial package implementation --- core/encoding/base64/base64.odin | 128 +-- core/encoding/cbor/cbor.odin | 680 +++++++++++++++ core/encoding/cbor/coding.odin | 825 ++++++++++++++++++ core/encoding/cbor/marshal.odin | 541 ++++++++++++ core/encoding/cbor/tags.odin | 361 ++++++++ core/encoding/cbor/unmarshal.odin | 832 +++++++++++++++++++ core/io/io.odin | 25 +- core/net/common.odin | 3 +- examples/all/all_main.odin | 2 + tests/core/Makefile | 1 + tests/core/build.bat | 1 + tests/core/encoding/cbor/test_core_cbor.odin | 719 ++++++++++++++++ 12 files changed, 4067 insertions(+), 51 deletions(-) create mode 100644 core/encoding/cbor/cbor.odin create mode 100644 core/encoding/cbor/coding.odin create mode 100644 core/encoding/cbor/marshal.odin create mode 100644 core/encoding/cbor/tags.odin create mode 100644 core/encoding/cbor/unmarshal.odin create mode 100644 tests/core/encoding/cbor/test_core_cbor.odin diff --git a/core/encoding/base64/base64.odin b/core/encoding/base64/base64.odin index cf2ea1c12..793f22c57 100644 --- a/core/encoding/base64/base64.odin +++ b/core/encoding/base64/base64.odin @@ -1,5 +1,9 @@ package base64 +import "core:io" +import "core:mem" +import "core:strings" + // @note(zh): Encoding utility for Base64 // A secondary param can be used to supply a custom alphabet to // @link(encode) and a matching decoding table to @link(decode). @@ -39,59 +43,85 @@ DEC_TABLE := [128]int { 49, 50, 51, -1, -1, -1, -1, -1, } -encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string #no_bounds_check { - length := len(data) - if length == 0 { - return "" - } +encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error { + out_length := encoded_length(data) + if out_length == 0 { + return + } - out_length := ((4 * length / 3) + 3) &~ 3 - out := make([]byte, out_length, allocator) + out: strings.Builder + strings.builder_init(&out, 0, out_length, allocator) or_return - c0, c1, c2, block: int + ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL) + assert(ioerr == nil) - for i, d := 0, 0; i < length; i, d = i + 3, d + 4 { - c0, c1, c2 = int(data[i]), -1, -1 - - if i + 1 < length { c1 = int(data[i + 1]) } - if i + 2 < length { c2 = int(data[i + 2]) } - - block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) - - out[d] = ENC_TBL[block >> 18 & 63] - out[d + 1] = ENC_TBL[block >> 12 & 63] - out[d + 2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] - out[d + 3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] - } - return string(out) + return strings.to_string(out), nil } -decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check { - length := len(data) - if length == 0 { - return nil - } +encoded_length :: #force_inline proc(data: []byte) -> int { + length := len(data) + if length == 0 { + return 0 + } - pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0 - out_length := ((length * 6) >> 3) - pad_count - out := make([]byte, out_length, allocator) - - c0, c1, c2, c3: int - b0, b1, b2: int - - for i, j := 0, 0; i < length; i, j = i + 4, j + 3 { - c0 = DEC_TBL[data[i]] - c1 = DEC_TBL[data[i + 1]] - c2 = DEC_TBL[data[i + 2]] - c3 = DEC_TBL[data[i + 3]] - - b0 = (c0 << 2) | (c1 >> 4) - b1 = (c1 << 4) | (c2 >> 2) - b2 = (c2 << 6) | c3 - - out[j] = byte(b0) - out[j + 1] = byte(b1) - out[j + 2] = byte(b2) - } - return out + return ((4 * length / 3) + 3) &~ 3 +} + +encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> (err: io.Error) #no_bounds_check { + length := len(data) + if length == 0 { + return + } + + c0, c1, c2, block: int + + for i, d := 0, 0; i < length; i, d = i + 3, d + 4 { + c0, c1, c2 = int(data[i]), -1, -1 + + if i + 1 < length { c1 = int(data[i + 1]) } + if i + 2 < length { c2 = int(data[i + 2]) } + + block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) + + out: [4]byte + out[0] = ENC_TBL[block >> 18 & 63] + out[1] = ENC_TBL[block >> 12 & 63] + out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] + out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] + + #bounds_check { io.write_full(w, out[:]) or_return } + } + return +} + +decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (out: []byte, err: mem.Allocator_Error) #optional_allocator_error { + #no_bounds_check { + length := len(data) + if length == 0 { + return + } + + pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0 + out_length := ((length * 6) >> 3) - pad_count + out = make([]byte, out_length, allocator) or_return + + c0, c1, c2, c3: int + b0, b1, b2: int + + for i, j := 0, 0; i < length; i, j = i + 4, j + 3 { + c0 = DEC_TBL[data[i]] + c1 = DEC_TBL[data[i + 1]] + c2 = DEC_TBL[data[i + 2]] + c3 = DEC_TBL[data[i + 3]] + + b0 = (c0 << 2) | (c1 >> 4) + b1 = (c1 << 4) | (c2 >> 2) + b2 = (c2 << 6) | c3 + + out[j] = byte(b0) + out[j + 1] = byte(b1) + out[j + 2] = byte(b2) + } + return + } } diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin new file mode 100644 index 000000000..e91c53f3c --- /dev/null +++ b/core/encoding/cbor/cbor.odin @@ -0,0 +1,680 @@ +package cbor + +import "core:encoding/json" +import "core:intrinsics" +import "core:io" +import "core:mem" +import "core:runtime" +import "core:strconv" +import "core:strings" + +// If we are decoding a stream of either a map or list, the initial capacity will be this value. +INITIAL_STREAMED_CONTAINER_CAPACITY :: 8 +// If we are decoding a stream of either text or bytes, the initial capacity will be this value. +INITIAL_STREAMED_BYTES_CAPACITY :: 16 + +// Known/common headers are defined, undefined headers can still be valid. +// Higher 3 bits is for the major type and lower 5 bits for the additional information. +Header :: enum u8 { + U8 = (u8(Major.Unsigned) << 5) | u8(Add.One_Byte), + U16 = (u8(Major.Unsigned) << 5) | u8(Add.Two_Bytes), + U32 = (u8(Major.Unsigned) << 5) | u8(Add.Four_Bytes), + U64 = (u8(Major.Unsigned) << 5) | u8(Add.Eight_Bytes), + + Neg_U8 = (u8(Major.Negative) << 5) | u8(Add.One_Byte), + Neg_U16 = (u8(Major.Negative) << 5) | u8(Add.Two_Bytes), + Neg_U32 = (u8(Major.Negative) << 5) | u8(Add.Four_Bytes), + Neg_U64 = (u8(Major.Negative) << 5) | u8(Add.Eight_Bytes), + + False = (u8(Major.Other) << 5) | u8(Add.False), + True = (u8(Major.Other) << 5) | u8(Add.True), + + Nil = (u8(Major.Other) << 5) | u8(Add.Nil), + Undefined = (u8(Major.Other) << 5) | u8(Add.Undefined), + + Simple = (u8(Major.Other) << 5) | u8(Add.One_Byte), + + F16 = (u8(Major.Other) << 5) | u8(Add.Two_Bytes), + F32 = (u8(Major.Other) << 5) | u8(Add.Four_Bytes), + F64 = (u8(Major.Other) << 5) | u8(Add.Eight_Bytes), + + Break = (u8(Major.Other) << 5) | u8(Add.Break), +} + +// The higher 3 bits of the header which denotes what type of value it is. +Major :: enum u8 { + Unsigned, + Negative, + Bytes, + Text, + Array, + Map, + Tag, + Other, +} + +// The lower 3 bits of the header which denotes additional information for the type of value. +Add :: enum u8 { + False = 20, + True = 21, + Nil = 22, + Undefined = 23, + + One_Byte = 24, + Two_Bytes = 25, + Four_Bytes = 26, + Eight_Bytes = 27, + + Length_Unknown = 31, + Break = Length_Unknown, +} + +Value :: union { + u8, + u16, + u32, + u64, + + Negative_U8, + Negative_U16, + Negative_U32, + Negative_U64, + + // Pointers so the size of the Value union stays small. + ^Bytes, + ^Text, + ^Array, + ^Map, + ^Tag, + + Simple, + f16, + f32, + f64, + bool, + Undefined, + Nil, +} + +Bytes :: []byte +Text :: string + +Array :: []Value + +Map :: []Map_Entry +Map_Entry :: struct { + key: Value, // Can be any unsigned, negative, float, Simple, bool, Text. + value: Value, +} + +Tag :: struct { + number: Tag_Number, + value: Value, // Value based on the number. +} + +Tag_Number :: u64 + +Nil :: distinct rawptr +Undefined :: distinct rawptr + +// A distinct atom-like number, range from `0..=19` and `32..=max(u8)`. +Simple :: distinct u8 +Atom :: Simple + +Unmarshal_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Decode_Data_Error, + Unmarshal_Data_Error, + Maybe(Unsupported_Type_Error), +} + +Marshal_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Encode_Data_Error, + Marshal_Data_Error, + Maybe(Unsupported_Type_Error), +} + +Decode_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Decode_Data_Error, +} + +Encode_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Encode_Data_Error, +} + +Decode_Data_Error :: enum { + None, + Bad_Major, // An invalid major type was encountered. + Bad_Argument, // A general unexpected value (most likely invalid additional info in header). + Bad_Tag_Value, // When the type of value for the given tag is not valid. + Nested_Indefinite_Length, // When an streamed/indefinite length container nests another, this is not allowed. + Nested_Tag, // When a tag's value is another tag, this is not allowed. + Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`. + Break, +} + +Encode_Data_Error :: enum { + None, + Invalid_Simple, // When a simple is being encoded that is out of the range `0..=19` and `32..=max(u8)`. + Int_Too_Big, // When an int is being encoded that is larger than `max(u64)` or smaller than `min(u64)`. + Bad_Tag_Value, // When the type of value is not supported by the tag implementation. +} + +Unmarshal_Data_Error :: enum { + None, + Invalid_Parameter, // When the given `any` can not be unmarshalled into. + Non_Pointer_Parameter, // When the given `any` is not a pointer. +} + +Marshal_Data_Error :: enum { + None, + Invalid_CBOR_Tag, // When the struct tag `cbor_tag:""` is not a registered name or number. +} + +// Error that is returned when a type couldn't be marshalled into or out of, as much information +// as possible/available is added. +Unsupported_Type_Error :: struct { + id: typeid, + hdr: Header, + add: Add, +} + +_unsupported :: proc(v: any, hdr: Header, add: Add = nil) -> Maybe(Unsupported_Type_Error) { + return Unsupported_Type_Error{ + id = v.id, + hdr = hdr, + add = add, + } +} + +// Actual value is `-1 - x` (be careful of overflows). + +Negative_U8 :: distinct u8 +Negative_U16 :: distinct u16 +Negative_U32 :: distinct u32 +Negative_U64 :: distinct u64 + +// Turns the CBOR negative unsigned int type into a signed integer type. +negative_to_int :: proc { + negative_u8_to_int, + negative_u16_to_int, + negative_u32_to_int, + negative_u64_to_int, +} + +negative_u8_to_int :: #force_inline proc(u: Negative_U8) -> i16 { + return -1 - i16(u) +} + +negative_u16_to_int :: #force_inline proc(u: Negative_U16) -> i32 { + return -1 - i32(u) +} + +negative_u32_to_int :: #force_inline proc(u: Negative_U32) -> i64 { + return -1 - i64(u) +} + +negative_u64_to_int :: #force_inline proc(u: Negative_U64) -> i128 { + return -1 - i128(u) +} + +// Utility for converting between the different errors when they are subsets of the other. +err_conv :: proc { + encode_to_marshal_err, + decode_to_unmarshal_err, + decode_to_unmarshal_err_p, + decode_to_unmarshal_err_p2, +} + +encode_to_marshal_err :: #force_inline proc(err: Encode_Error) -> Marshal_Error { + switch e in err { + case nil: return nil + case io.Error: return e + case mem.Allocator_Error: return e + case Encode_Data_Error: return e + case: return nil + } +} + +decode_to_unmarshal_err :: #force_inline proc(err: Decode_Error) -> Unmarshal_Error { + switch e in err { + case nil: return nil + case io.Error: return e + case mem.Allocator_Error: return e + case Decode_Data_Error: return e + case: return nil + } +} + +decode_to_unmarshal_err_p :: #force_inline proc(v: $T, err: Decode_Error) -> (T, Unmarshal_Error) { + return v, err_conv(err) +} + +decode_to_unmarshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Decode_Error) -> (T, T2, Unmarshal_Error) { + return v, v2, err_conv(err) +} + +// Recursively frees all memory allocated when decoding the passed value. +destroy :: proc(val: Value, allocator := context.allocator) { + context.allocator = allocator + #partial switch v in val { + case ^Map: + if v == nil { return } + for entry in v { + destroy(entry.key) + destroy(entry.value) + } + delete(v^) + free(v) + case ^Array: + if v == nil { return } + for entry in v { + destroy(entry) + } + delete(v^) + free(v) + case ^Text: + if v == nil { return } + delete(v^) + free(v) + case ^Bytes: + if v == nil { return } + delete(v^) + free(v) + case ^Tag: + if v == nil { return } + destroy(v.value) + free(v) + } +} + +/* +diagnose either writes or returns a human-readable representation of the value, +optionally formatted, defined as the diagnostic format in section 8 of RFC 8949. + +Incidentally, if the CBOR does not contain any of the additional types defined on top of JSON +this will also be valid JSON. +*/ +diagnose :: proc { + diagnostic_string, + diagnose_to_writer, +} + +// Turns the given CBOR value into a human-readable string. +// See docs on the proc group `diagnose` for more info. +diagnostic_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { + b := strings.builder_make(allocator) + w := strings.to_stream(&b) + err := diagnose_to_writer(w, val, padding) + if err == .EOF { + // The string builder stream only returns .EOF, and only if it can't write (out of memory). + return "", .Out_Of_Memory + } + assert(err == nil) + + return strings.to_string(b), nil +} + +// Writes the given CBOR value into the writer as human-readable text. +// See docs on the proc group `diagnose` for more info. +diagnose_to_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { + @(require_results) + indent :: proc(padding: int) -> int { + padding := padding + if padding != -1 { + padding += 1 + } + return padding + } + + @(require_results) + dedent :: proc(padding: int) -> int { + padding := padding + if padding != -1 { + padding -= 1 + } + return padding + } + + comma :: proc(w: io.Writer, padding: int) -> io.Error { + _ = io.write_string(w, ", " if padding == -1 else ",") or_return + return nil + } + + newline :: proc(w: io.Writer, padding: int) -> io.Error { + if padding != -1 { + io.write_string(w, "\n") or_return + for _ in 0.. (Value, mem.Allocator_Error) #optional_allocator_error { + internal :: proc(val: json.Value) -> (ret: Value, err: mem.Allocator_Error) { + switch v in val { + case json.Null: return Nil{}, nil + case json.Integer: + i, major := _int_to_uint(v) + #partial switch major { + case .Unsigned: return i, nil + case .Negative: return Negative_U64(i), nil + case: unreachable() + } + case json.Float: return v, nil + case json.Boolean: return v, nil + case json.String: + container := new(Text) or_return + + // We need the string to have a nil byte at the end so we clone to cstring. + container^ = string(strings.clone_to_cstring(v) or_return) + return container, nil + case json.Array: + arr := new(Array) or_return + arr^ = make([]Value, len(v)) or_return + for _, i in arr { + arr[i] = internal(v[i]) or_return + } + return arr, nil + case json.Object: + m := new(Map) or_return + dm := make([dynamic]Map_Entry, 0, len(v)) or_return + for mkey, mval in v { + append(&dm, Map_Entry{from_json(mkey) or_return, from_json(mval) or_return}) + } + m^ = dm[:] + return m, nil + } + return nil, nil + } + + context.allocator = allocator + return internal(val) +} + +/* +Converts from CBOR to JSON. + +NOTE: overflow on integers or floats is not handled. + +Everything is copied to the given allocator, the passed in CBOR value can be `destroy`'ed after. + +If a CBOR map with non-string keys is encountered it is turned into an array of tuples. +*/ +to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem.Allocator_Error) #optional_allocator_error { + internal :: proc(val: Value) -> (ret: json.Value, err: mem.Allocator_Error) { + switch v in val { + case Simple: return json.Integer(v), nil + + case u8: return json.Integer(v), nil + case u16: return json.Integer(v), nil + case u32: return json.Integer(v), nil + case u64: return json.Integer(v), nil + + case Negative_U8: return json.Integer(negative_to_int(v)), nil + case Negative_U16: return json.Integer(negative_to_int(v)), nil + case Negative_U32: return json.Integer(negative_to_int(v)), nil + case Negative_U64: return json.Integer(negative_to_int(v)), nil + + case f16: return json.Float(v), nil + case f32: return json.Float(v), nil + case f64: return json.Float(v), nil + + case bool: return json.Boolean(v), nil + + case Undefined: return json.Null{}, nil + case Nil: return json.Null{}, nil + + case ^Bytes: return json.String(strings.clone(string(v^)) or_return), nil + case ^Text: return json.String(strings.clone(v^) or_return), nil + + case ^Map: + keys_all_strings :: proc(m: ^Map) -> bool { + for entry in m { + #partial switch kv in entry.key { + case ^Bytes: + case ^Text: + case: return false + } + } + return false + } + + if keys_all_strings(v) { + obj := make(json.Object, len(v)) or_return + for entry in v { + k: string + #partial switch kv in entry.key { + case ^Bytes: k = string(kv^) + case ^Text: k = kv^ + case: unreachable() + } + + v := internal(entry.value) or_return + obj[k] = v + } + return obj, nil + } else { + // Resort to an array of tuples if keys aren't all strings. + arr := make(json.Array, 0, len(v)) or_return + for entry in v { + entry_arr := make(json.Array, 0, 2) or_return + append(&entry_arr, internal(entry.key) or_return) or_return + append(&entry_arr, internal(entry.value) or_return) or_return + append(&arr, entry_arr) or_return + } + return arr, nil + } + + case ^Array: + arr := make(json.Array, 0, len(v)) or_return + for entry in v { + append(&arr, internal(entry) or_return) or_return + } + return arr, nil + + case ^Tag: + obj := make(json.Object, 2) or_return + obj[strings.clone("number") or_return] = internal(v.number) or_return + obj[strings.clone("value") or_return] = internal(v.value) or_return + return obj, nil + + case: return json.Null{}, nil + } + } + + context.allocator = allocator + return internal(val) +} + +_int_to_uint :: proc { + _i8_to_uint, + _i16_to_uint, + _i32_to_uint, + _i64_to_uint, + _i128_to_uint, +} + +_u128_to_u64 :: #force_inline proc(v: u128) -> (u64, Encode_Data_Error) { + if v > u128(max(u64)) { + return 0, .Int_Too_Big + } + + return u64(v), nil +} + +_i8_to_uint :: #force_inline proc(v: i8) -> (u: u8, m: Major) { + if v < 0 { + return u8(abs(v)-1), .Negative + } + + return u8(v), .Unsigned +} + +_i16_to_uint :: #force_inline proc(v: i16) -> (u: u16, m: Major) { + if v < 0 { + return u16(abs(v)-1), .Negative + } + + return u16(v), .Unsigned +} + +_i32_to_uint :: #force_inline proc(v: i32) -> (u: u32, m: Major) { + if v < 0 { + return u32(abs(v)-1), .Negative + } + + return u32(v), .Unsigned +} + +_i64_to_uint :: #force_inline proc(v: i64) -> (u: u64, m: Major) { + if v < 0 { + return u64(abs(v)-1), .Negative + } + + return u64(v), .Unsigned +} + +_i128_to_uint :: proc(v: i128) -> (u: u64, m: Major, err: Encode_Data_Error) { + if v < 0 { + m = .Negative + u, err = _u128_to_u64(u128(abs(v) - 1)) + return + } + + m = .Unsigned + u, err = _u128_to_u64(u128(v)) + return +} + +@(private) +is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool { + if ti == nil { + return false + } + t := runtime.type_info_base(ti) + #partial switch info in t.variant { + case runtime.Type_Info_Integer: + switch info.endianness { + case .Platform: return false + case .Little: return ODIN_ENDIAN != .Little + case .Big: return ODIN_ENDIAN != .Big + } + } + return false +} + diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin new file mode 100644 index 000000000..5c14d8f87 --- /dev/null +++ b/core/encoding/cbor/coding.odin @@ -0,0 +1,825 @@ +package cbor + +import "core:bytes" +import "core:encoding/endian" +import "core:intrinsics" +import "core:io" +import "core:slice" +import "core:strings" + +Encoder_Flag :: enum { + // CBOR defines a tag header that also acts as a file/binary header, + // this way decoders can check the first header of the binary and see if it is CBOR. + Self_Described_CBOR, + + // Integers are stored in the smallest integer type it fits. + // This involves checking each int against the max of all its smaller types. + Deterministic_Int_Size, + + // Floats are stored in the smallest size float type without losing precision. + // This involves casting each float down to its smaller types and checking if it changed. + Deterministic_Float_Size, + + // Sort maps by their keys in bytewise lexicographic order of their deterministic encoding. + // NOTE: In order to do this, all keys of a map have to be pre-computed, sorted, and + // then written, this involves temporary allocations for the keys and a copy of the map itself. + Deterministic_Map_Sorting, + + // Internal flag to do initialization. + _In_Progress, +} + +Encoder_Flags :: bit_set[Encoder_Flag] + +// Flags for fully deterministic output (if you are not using streaming/indeterminate length). +ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size, .Deterministic_Map_Sorting} +// Flags for the smallest encoding output. +ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size} +// Flags for the fastest encoding output. +ENCODE_FAST :: Encoder_Flags{} + +Encoder :: struct { + flags: Encoder_Flags, + writer: io.Writer, +} + +/* +Decodes both deterministic and non-deterministic CBOR into a `Value` variant. + +`Text` and `Bytes` can safely be cast to cstrings because of an added 0 byte. + +Allocations are done using the given allocator, +*no* allocations are done on the `context.temp_allocator`. + +A value can be (fully and recursively) deallocated using the `destroy` proc in this package. +*/ +decode :: proc { + decode_string, + decode_reader, +} + +// Decodes the given string as CBOR. +// See docs on the proc group `decode` for more information. +decode_string :: proc(s: string, allocator := context.allocator) -> (v: Value, err: Decode_Error) { + context.allocator = allocator + + r: strings.Reader + strings.reader_init(&r, s) + return decode(strings.reader_to_stream(&r), allocator=allocator) +} + +// Reads a CBOR value from the given reader. +// See docs on the proc group `decode` for more information. +decode_reader :: proc(r: io.Reader, hdr: Header = Header(0), allocator := context.allocator) -> (v: Value, err: Decode_Error) { + context.allocator = allocator + + hdr := hdr + if hdr == Header(0) { hdr = _decode_header(r) or_return } + switch hdr { + case .U8: return _decode_u8 (r) + case .U16: return _decode_u16(r) + case .U32: return _decode_u32(r) + case .U64: return _decode_u64(r) + + case .Neg_U8: return Negative_U8 (_decode_u8 (r) or_return), nil + case .Neg_U16: return Negative_U16(_decode_u16(r) or_return), nil + case .Neg_U32: return Negative_U32(_decode_u32(r) or_return), nil + case .Neg_U64: return Negative_U64(_decode_u64(r) or_return), nil + + case .Simple: return _decode_simple(r) + + case .F16: return _decode_f16(r) + case .F32: return _decode_f32(r) + case .F64: return _decode_f64(r) + + case .True: return true, nil + case .False: return false, nil + + case .Nil: return Nil{}, nil + case .Undefined: return Undefined{}, nil + + case .Break: return nil, .Break + } + + maj, add := _header_split(hdr) + switch maj { + case .Unsigned: return _decode_tiny_u8(add) + case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil + case .Bytes: return _decode_bytes_ptr(r, add) + case .Text: return _decode_text_ptr(r, add) + case .Array: return _decode_array_ptr(r, add) + case .Map: return _decode_map_ptr(r, add) + case .Tag: return _decode_tag_ptr(r, add) + case .Other: return _decode_tiny_simple(add) + case: return nil, .Bad_Major + } +} + +/* +Encodes the CBOR value into a binary CBOR. + +Flags can be used to control the output (mainly determinism, which coincidently affects size). + +The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try +to put ints and floats into their smallest possible byte size without losing equality. + +Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know +the contents are CBOR from just reading the first byte. + +Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the +encoded key. This flag has a cost on performance and memory efficiency because all keys in a map +have to be precomputed, sorted and only then written to the output. + +Empty flags will do nothing extra to the value. + +The allocations for the `.Deterministic_Map_Sorting` flag are done using the `context.temp_allocator` +but are followed by the necessary `delete` and `free` calls if the allocator supports them. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary +allocations until the end. +*/ +encode_into :: proc { + encode_into_bytes, + encode_into_builder, + encode_into_writer, + encode_into_encoder, +} +encode :: encode_into + +// Encodes the CBOR value into binary CBOR allocated on the given allocator. +// See the docs on the proc group `encode_into` for more info. +encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator) -> (data: []byte, err: Encode_Error) { + b := strings.builder_make(allocator) or_return + encode_into_builder(&b, v, flags) or_return + return b.buf[:], nil +} + +// Encodes the CBOR value into binary CBOR written to the given builder. +// See the docs on the proc group `encode_into` for more info. +encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL) -> Encode_Error { + return encode_into_writer(strings.to_stream(b), v, flags) +} + +// Encodes the CBOR value into binary CBOR written to the given writer. +// See the docs on the proc group `encode_into` for more info. +encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL) -> Encode_Error { + return encode_into_encoder(Encoder{flags, w}, v) +} + +// Encodes the CBOR value into binary CBOR written to the given encoder. +// See the docs on the proc group `encode_into` for more info. +encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { + e := e + + outer: bool + defer if outer { + e.flags &~= {._In_Progress} + } + + if ._In_Progress not_in e.flags { + outer = true + e.flags |= {._In_Progress} + + if .Self_Described_CBOR in e.flags { + _encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return + } + } + + switch v_spec in v { + case u8: return _encode_u8(e.writer, v_spec, .Unsigned) + case u16: return _encode_u16(e, v_spec, .Unsigned) + case u32: return _encode_u32(e, v_spec, .Unsigned) + case u64: return _encode_u64(e, v_spec, .Unsigned) + case Negative_U8: return _encode_u8(e.writer, u8(v_spec), .Negative) + case Negative_U16: return _encode_u16(e, u16(v_spec), .Negative) + case Negative_U32: return _encode_u32(e, u32(v_spec), .Negative) + case Negative_U64: return _encode_u64(e, u64(v_spec), .Negative) + case ^Bytes: return _encode_bytes(e, v_spec^) + case ^Text: return _encode_text(e, v_spec^) + case ^Array: return _encode_array(e, v_spec^) + case ^Map: return _encode_map(e, v_spec^) + case ^Tag: return _encode_tag(e, v_spec^) + case Simple: return _encode_simple(e.writer, v_spec) + case f16: return _encode_f16(e.writer, v_spec) + case f32: return _encode_f32(e, v_spec) + case f64: return _encode_f64(e, v_spec) + case bool: return _encode_bool(e.writer, v_spec) + case Nil: return _encode_nil(e.writer) + case Undefined: return _encode_undefined(e.writer) + case: return nil + } +} + +_decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { + buf: [1]byte + io.read_full(r, buf[:]) or_return + return Header(buf[0]), nil +} + +_header_split :: proc(hdr: Header) -> (Major, Add) { + return Major(u8(hdr) >> 5), Add(u8(hdr) & 0x1f) +} + +_decode_u8 :: proc(r: io.Reader) -> (v: u8, err: io.Error) { + byte: [1]byte + io.read_full(r, byte[:]) or_return + return byte[0], nil +} + +_encode_uint :: proc { + _encode_u8, + _encode_u16, + _encode_u32, + _encode_u64, +} + +_encode_u8 :: proc(w: io.Writer, v: u8, major: Major = .Unsigned) -> (err: io.Error) { + header := u8(major) << 5 + if v < u8(Add.One_Byte) { + header |= v + _, err = io.write_full(w, {header}) + return + } + + header |= u8(Add.One_Byte) + _, err = io.write_full(w, {header, v}) + return +} + +_decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) { + if intrinsics.expect(additional < .One_Byte, true) { + return u8(additional), nil + } + + return 0, .Bad_Argument +} + +_decode_u16 :: proc(r: io.Reader) -> (v: u16, err: io.Error) { + bytes: [2]byte + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u16be(bytes[:]), nil +} + +_encode_u16 :: proc(e: Encoder, v: u16, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u16_exact(e.writer, v, major) +} + +_encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [3]byte + bytes[0] = (u8(major) << 5) | u8(Add.Two_Bytes) + endian.unchecked_put_u16be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_u32 :: proc(r: io.Reader) -> (v: u32, err: io.Error) { + bytes: [4]byte + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u32be(bytes[:]), nil +} + +_encode_u32 :: proc(e: Encoder, v: u32, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u32_exact(e.writer, v, major) +} + +_encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [5]byte + bytes[0] = (u8(major) << 5) | u8(Add.Four_Bytes) + endian.unchecked_put_u32be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_u64 :: proc(r: io.Reader) -> (v: u64, err: io.Error) { + bytes: [8]byte + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u64be(bytes[:]), nil +} + +_encode_u64 :: proc(e: Encoder, v: u64, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u64_exact(e.writer, v, major) +} + +_encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [9]byte + bytes[0] = (u8(major) << 5) | u8(Add.Eight_Bytes) + endian.unchecked_put_u64be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_bytes_ptr :: proc(r: io.Reader, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { + v = new(Bytes) or_return + defer if err != nil { free(v) } + + v^ = _decode_bytes(r, add, type) or_return + return +} + +_decode_bytes :: proc(r: io.Reader, add: Add, type: Major = .Bytes) -> (v: Bytes, err: Decode_Error) { + _n_items, length_is_unknown := _decode_container_length(r, add) or_return + + n_items := _n_items.? or_else INITIAL_STREAMED_BYTES_CAPACITY + + if length_is_unknown { + buf: strings.Builder + buf.buf = make([dynamic]byte, 0, n_items) or_return + defer if err != nil { strings.builder_destroy(&buf) } + + buf_stream := strings.to_stream(&buf) + + for { + header := _decode_header(r) or_return + maj, add := _header_split(header) + + #partial switch maj { + case type: + _n_items, length_is_unknown := _decode_container_length(r, add) or_return + if length_is_unknown { + return nil, .Nested_Indefinite_Length + } + n_items := i64(_n_items.?) + + copied := io.copy_n(buf_stream, r, n_items) or_return + assert(copied == n_items) + + case .Other: + if add != .Break { return nil, .Bad_Argument } + + v = buf.buf[:] + + // Write zero byte so this can be converted to cstring. + io.write_full(buf_stream, {0}) or_return + shrink(&buf.buf) // Ignoring error, this is not critical to succeed. + return + + case: + return nil, .Bad_Major + } + } + } else { + v = make([]byte, n_items + 1) or_return // Space for the bytes and a zero byte. + defer if err != nil { delete(v) } + + io.read_full(r, v[:n_items]) or_return + + v = v[:n_items] // Take off zero byte. + return + } +} + +_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) { + assert(len(val) >= 0) + _encode_u64(e, u64(len(val)), major) or_return + _, err = io.write_full(e.writer, val[:]) + return +} + +_decode_text_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Text, err: Decode_Error) { + v = new(Text) or_return + defer if err != nil { free(v) } + + v^ = _decode_text(r, add) or_return + return +} + +_decode_text :: proc(r: io.Reader, add: Add) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(r, add, .Text) or_return), nil +} + +_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { + return _encode_bytes(e, transmute([]byte)val, .Text) +} + +_decode_array_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Array, err: Decode_Error) { + v = new(Array) or_return + defer if err != nil { free(v) } + + v^ = _decode_array(r, add) or_return + return +} + +_decode_array :: proc(r: io.Reader, add: Add) -> (v: Array, err: Decode_Error) { + _n_items, length_is_unknown := _decode_container_length(r, add) or_return + n_items := _n_items.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + + array := make([dynamic]Value, 0, n_items) or_return + defer if err != nil { + for entry in array { destroy(entry) } + delete(array) + } + + for i := 0; length_is_unknown || i < n_items; i += 1 { + val, verr := decode(r) + if length_is_unknown && verr == .Break { + break + } else if verr != nil { + err = verr + return + } + + append(&array, val) or_return + } + + shrink(&array) + v = array[:] + return +} + +_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error { + assert(len(arr) >= 0) + _encode_u64(e, u64(len(arr)), .Array) + for val in arr { + encode(e, val) or_return + } + return nil +} + +_decode_map_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Map, err: Decode_Error) { + v = new(Map) or_return + defer if err != nil { free(v) } + + v^ = _decode_map(r, add) or_return + return +} + +_decode_map :: proc(r: io.Reader, add: Add) -> (v: Map, err: Decode_Error) { + _n_items, length_is_unknown := _decode_container_length(r, add) or_return + n_items := _n_items.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + + items := make([dynamic]Map_Entry, 0, n_items) or_return + defer if err != nil { + for entry in items { + destroy(entry.key) + destroy(entry.value) + } + delete(items) + } + + for i := 0; length_is_unknown || i < n_items; i += 1 { + key, kerr := decode(r) + if length_is_unknown && kerr == .Break { + break + } else if kerr != nil { + return nil, kerr + } + + value := decode(r) or_return + + append(&items, Map_Entry{ + key = key, + value = value, + }) or_return + } + + shrink(&items) + v = items[:] + return +} + +_encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { + assert(len(m) >= 0) + _encode_u64(e, u64(len(m)), .Map) or_return + + if .Deterministic_Map_Sorting not_in e.flags { + for entry in m { + encode(e, entry.key) or_return + encode(e, entry.value) or_return + } + return + } + + // Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the + // encoded key. + // + // This means we have to store and sort them before writing incurring extra (temporary) allocations. + + Map_Entry_With_Key :: struct { + encoded_key: []byte, + entry: Map_Entry, + } + + entries := make([]Map_Entry_With_Key, len(m), context.temp_allocator) or_return + defer delete(entries, context.temp_allocator) + + for &entry, i in entries { + entry.entry = m[i] + + buf := strings.builder_make(0, 8, context.temp_allocator) or_return + + ke := e + ke.writer = strings.to_stream(&buf) + + encode(ke, entry.entry.key) or_return + entry.encoded_key = buf.buf[:] + } + + // Sort lexicographic on the bytes of the key. + slice.sort_by_cmp(entries, proc(a, b: Map_Entry_With_Key) -> slice.Ordering { + return slice.Ordering(bytes.compare(a.encoded_key, b.encoded_key)) + }) + + for entry in entries { + io.write_full(e.writer, entry.encoded_key) or_return + delete(entry.encoded_key, context.temp_allocator) + + encode(e, entry.entry.value) or_return + } + + return nil +} + +_decode_tag_ptr :: proc(r: io.Reader, add: Add) -> (v: Value, err: Decode_Error) { + tag := _decode_tag(r, add) or_return + if t, ok := tag.?; ok { + defer if err != nil { destroy(t.value) } + tp := new(Tag) or_return + tp^ = t + return tp, nil + } + + // no error, no tag, this was the self described CBOR tag, skip it. + return decode(r) +} + +_decode_tag :: proc(r: io.Reader, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { + num := _decode_tag_nr(r, add) or_return + + // CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR. + // We can ignore it here. + if num == TAG_SELF_DESCRIBED_CBOR { + return + } + + t := Tag{ + number = num, + value = decode(r) or_return, + } + + if nested, ok := t.value.(^Tag); ok { + destroy(nested) + return nil, .Nested_Tag + } + + return t, nil +} + +_decode_tag_nr :: proc(r: io.Reader, add: Add) -> (nr: Tag_Number, err: Decode_Error) { + #partial switch add { + case .One_Byte: return u64(_decode_u8(r) or_return), nil + case .Two_Bytes: return u64(_decode_u16(r) or_return), nil + case .Four_Bytes: return u64(_decode_u32(r) or_return), nil + case .Eight_Bytes: return u64(_decode_u64(r) or_return), nil + case: return u64(_decode_tiny_u8(add) or_return), nil + } +} + +_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error { + _encode_u64(e, val.number, .Tag) or_return + return encode(e, val.value) +} + +_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) { + buf: [1]byte + io.read_full(r, buf[:]) or_return + return Simple(buf[0]), nil +} + +_encode_simple :: proc(w: io.Writer, v: Simple) -> (err: Encode_Error) { + header := u8(Major.Other) << 5 + + if v < Simple(Add.False) { + header |= u8(v) + _, err = io.write_full(w, {header}) + return + } else if v <= Simple(Add.Break) { + return .Invalid_Simple + } + + header |= u8(Add.One_Byte) + _, err = io.write_full(w, {header, u8(v)}) + return +} + +_decode_tiny_simple :: proc(add: Add) -> (Simple, Decode_Data_Error) { + if add < Add.False { + return Simple(add), nil + } + + return 0, .Bad_Argument +} + +_decode_f16 :: proc(r: io.Reader) -> (v: f16, err: io.Error) { + bytes: [2]byte + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u16be(bytes[:]) + return transmute(f16)n, nil +} + +_encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) { + bytes: [3]byte + bytes[0] = u8(Header.F16) + endian.unchecked_put_u16be(bytes[1:], transmute(u16)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_f32 :: proc(r: io.Reader) -> (v: f32, err: io.Error) { + bytes: [4]byte + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u32be(bytes[:]) + return transmute(f32)n, nil +} + +_encode_f32 :: proc(e: Encoder, v: f32) -> io.Error { + if .Deterministic_Float_Size in e.flags { + return _encode_deterministic_float(e.writer, v) + } + return _encode_f32_exact(e.writer, v) +} + +_encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) { + bytes: [5]byte + bytes[0] = u8(Header.F32) + endian.unchecked_put_u32be(bytes[1:], transmute(u32)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_f64 :: proc(r: io.Reader) -> (v: f64, err: io.Error) { + bytes: [8]byte + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u64be(bytes[:]) + return transmute(f64)n, nil +} + +_encode_f64 :: proc(e: Encoder, v: f64) -> io.Error { + if .Deterministic_Float_Size in e.flags { + return _encode_deterministic_float(e.writer, v) + } + return _encode_f64_exact(e.writer, v) +} + +_encode_f64_exact :: proc(w: io.Writer, v: f64) -> (err: io.Error) { + bytes: [9]byte + bytes[0] = u8(Header.F64) + endian.unchecked_put_u64be(bytes[1:], transmute(u64)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_encode_bool :: proc(w: io.Writer, v: bool) -> (err: io.Error) { + switch v { + case true: _, err = io.write_full(w, {u8(Header.True )}); return + case false: _, err = io.write_full(w, {u8(Header.False)}); return + case: unreachable() + } +} + +_encode_undefined :: proc(w: io.Writer) -> io.Error { + _, err := io.write_full(w, {u8(Header.Undefined)}) + return err +} + +_encode_nil :: proc(w: io.Writer) -> io.Error { + _, err := io.write_full(w, {u8(Header.Nil)}) + return err +} + +// Streaming + +encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) { + assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type") + + header := (u8(major) << 5) | u8(Add.Length_Unknown) + _, err = io.write_full(w, {header}) + return +} + +encode_stream_end :: proc(w: io.Writer) -> io.Error { + header := (u8(Major.Other) << 5) | u8(Add.Break) + _, err := io.write_full(w, {header}) + return err +} + +encode_stream_bytes :: _encode_bytes +encode_stream_text :: _encode_text +encode_stream_array_item :: encode + +encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error { + encode(e, key) or_return + return encode(e, val) +} + +// + +_decode_container_length :: proc(r: io.Reader, add: Add) -> (length: Maybe(int), is_unknown: bool, err: Decode_Error) { + if add == Add.Length_Unknown { return nil, true, nil } + #partial switch add { + case .One_Byte: length = int(_decode_u8(r) or_return) + case .Two_Bytes: length = int(_decode_u16(r) or_return) + case .Four_Bytes: + big_length := _decode_u32(r) or_return + if u64(big_length) > u64(max(int)) { + err = .Length_Too_Big + return + } + length = int(big_length) + case .Eight_Bytes: + big_length := _decode_u64(r) or_return + if big_length > u64(max(int)) { + err = .Length_Too_Big + return + } + length = int(big_length) + case: + length = int(_decode_tiny_u8(add) or_return) + } + return +} + +// Deterministic encoding is (among other things) encoding all values into their smallest +// possible representation. +// See section 4 of RFC 8949. + +_encode_deterministic_uint :: proc { + _encode_u8, + _encode_deterministic_u16, + _encode_deterministic_u32, + _encode_deterministic_u64, + _encode_deterministic_u128, +} + +_encode_deterministic_u16 :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u16(max(u8)): return _encode_u8(w, u8(v), major) + case: return _encode_u16_exact(w, v, major) + } +} + +_encode_deterministic_u32 :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u32(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u32(max(u16)): return _encode_u16_exact(w, u16(v), major) + case: return _encode_u32_exact(w, u32(v), major) + } +} + +_encode_deterministic_u64 :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u64(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u64(max(u16)): return _encode_u16_exact(w, u16(v), major) + case v <= u64(max(u32)): return _encode_u32_exact(w, u32(v), major) + case: return _encode_u64_exact(w, u64(v), major) + } +} + +_encode_deterministic_u128 :: proc(w: io.Writer, v: u128, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u128(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u128(max(u16)): return _encode_u16_exact(w, u16(v), major) + case v <= u128(max(u32)): return _encode_u32_exact(w, u32(v), major) + case v <= u128(max(u64)): return _encode_u64_exact(w, u64(v), major) + case: return .Int_Too_Big + } +} + +_encode_deterministic_negative :: #force_inline proc(w: io.Writer, v: $T) -> Encode_Error + where T == Negative_U8 || T == Negative_U16 || T == Negative_U32 || T == Negative_U64 { + return _encode_deterministic_uint(w, v, .Negative) +} + +// A Deterministic float is a float in the smallest type that stays the same after down casting. +_encode_deterministic_float :: proc { + _encode_f16, + _encode_deterministic_f32, + _encode_deterministic_f64, +} + +_encode_deterministic_f32 :: proc(w: io.Writer, v: f32) -> io.Error { + if (f32(f16(v)) == v) { + return _encode_f16(w, f16(v)) + } + + return _encode_f32_exact(w, v) +} + +_encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error { + if (f64(f16(v)) == v) { + return _encode_f16(w, f16(v)) + } + + if (f64(f32(v)) == v) { + return _encode_f32_exact(w, f32(v)) + } + + return _encode_f64_exact(w, v) +} diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin new file mode 100644 index 000000000..aab2defb2 --- /dev/null +++ b/core/encoding/cbor/marshal.odin @@ -0,0 +1,541 @@ +package cbor + +import "core:bytes" +import "core:intrinsics" +import "core:io" +import "core:mem" +import "core:reflect" +import "core:runtime" +import "core:slice" +import "core:strconv" +import "core:strings" +import "core:unicode/utf8" + +/* +Marshal a value into binary CBOR. + +Flags can be used to control the output (mainly determinism, which coincidently affects size). + +The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try +to put ints and floats into their smallest possible byte size without losing equality. + +Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know +the contents are CBOR from just reading the first byte. + +Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the +encoded key. This flag has a cost on performance and memory efficiency because all keys in a map +have to be precomputed, sorted and only then written to the output. + +Empty flags will do nothing extra to the value. + +The allocations for the `.Deterministic_Map_Sorting` flag are done using the `context.temp_allocator` +but are followed by the necessary `delete` and `free` calls if the allocator supports them. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary +allocations until the end. +*/ +marshal_into :: proc { + marshal_into_bytes, + marshal_into_builder, + marshal_into_writer, + marshal_into_encoder, +} + +marshal :: marshal_into + +// Marshals the given value into a CBOR byte stream (allocated using the given allocator). +// See docs on the `marshal_into` proc group for more info. +marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator) -> (bytes: []byte, err: Marshal_Error) { + b, alloc_err := strings.builder_make(allocator) + // The builder as a stream also returns .EOF if it ran out of memory so this is consistent. + if alloc_err != nil { + return nil, .EOF + } + + defer if err != nil { strings.builder_destroy(&b) } + + if err = marshal_into_builder(&b, v, flags); err != nil { + return + } + + return b.buf[:], nil +} + +// Marshals the given value into a CBOR byte stream written to the given builder. +// See docs on the `marshal_into` proc group for more info. +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags) +} + +// Marshals the given value into a CBOR byte stream written to the given writer. +// See docs on the `marshal_into` proc group for more info. +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL) -> Marshal_Error { + encoder := Encoder{flags, w} + return marshal_into_encoder(encoder, v) +} + +// Marshals the given value into a CBOR byte stream written to the given encoder. +// See docs on the `marshal_into` proc group for more info. +marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { + e := e + + init: bool + defer if init { + e.flags &~= {._In_Progress} + } + + // If not in progress we do initialization and set in progress. + if ._In_Progress not_in e.flags { + init = true + e.flags |= {._In_Progress} + + if .Self_Described_CBOR in e.flags { + err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return + } + } + + if v == nil { + return _encode_nil(e.writer) + } + + // Check if type has a tag implementation to use. + if impl, ok := _tag_implementations_type[v.id]; ok { + return impl->marshal(e, v) + } + + ti := runtime.type_info_base(type_info_of(v.id)) + a := any{v.data, ti.id} + + #partial switch info in ti.variant { + case runtime.Type_Info_Named: + unreachable() + + case runtime.Type_Info_Pointer: + switch vv in v { + case Undefined: return _encode_undefined(e.writer) + case Nil: return _encode_nil(e.writer) + } + + case runtime.Type_Info_Integer: + switch vv in v { + case Simple: return err_conv(_encode_simple(e.writer, vv)) + case Negative_U8: return _encode_u8(e.writer, u8(vv), .Negative) + case Negative_U16: return err_conv(_encode_u16(e, u16(vv), .Negative)) + case Negative_U32: return err_conv(_encode_u32(e, u32(vv), .Negative)) + case Negative_U64: return err_conv(_encode_u64(e, u64(vv), .Negative)) + } + + switch i in a { + case i8: return _encode_uint(e.writer, _int_to_uint(i)) + case i16: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i32: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i64: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i128: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + case int: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + + case u8: return _encode_uint(e.writer, i) + case u16: return err_conv(_encode_uint(e, i)) + case u32: return err_conv(_encode_uint(e, i)) + case u64: return err_conv(_encode_uint(e, i)) + case u128: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + case uint: return err_conv(_encode_uint(e, u64(i))) + case uintptr: return err_conv(_encode_uint(e, u64(i))) + + case i16le: return err_conv(_encode_uint(e, _int_to_uint(i16(i)))) + case i32le: return err_conv(_encode_uint(e, _int_to_uint(i32(i)))) + case i64le: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + case i128le: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + + case u16le: return err_conv(_encode_uint(e, u16(i))) + case u32le: return err_conv(_encode_uint(e, u32(i))) + case u64le: return err_conv(_encode_uint(e, u64(i))) + case u128le: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + + case i16be: return err_conv(_encode_uint(e, _int_to_uint(i16(i)))) + case i32be: return err_conv(_encode_uint(e, _int_to_uint(i32(i)))) + case i64be: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + case i128be: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + + case u16be: return err_conv(_encode_uint(e, u16(i))) + case u32be: return err_conv(_encode_uint(e, u32(i))) + case u64be: return err_conv(_encode_uint(e, u64(i))) + case u128be: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + } + + case runtime.Type_Info_Rune: + buf, w := utf8.encode_rune(a.(rune)) + return err_conv(_encode_text(e, string(buf[:w]))) + + case runtime.Type_Info_Float: + switch f in a { + case f16: return _encode_f16(e.writer, f) + case f32: return _encode_f32(e, f) + case f64: return _encode_f64(e, f) + + case f16le: return _encode_f16(e.writer, f16(f)) + case f32le: return _encode_f32(e, f32(f)) + case f64le: return _encode_f64(e, f64(f)) + + case f16be: return _encode_f16(e.writer, f16(f)) + case f32be: return _encode_f32(e, f32(f)) + case f64be: return _encode_f64(e, f64(f)) + } + + case runtime.Type_Info_Complex: + switch z in a { + case complex32: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + case complex64: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + case complex128: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + } + + case runtime.Type_Info_Quaternion: + switch q in a { + case quaternion64: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + case quaternion128: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + case quaternion256: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + } + + case runtime.Type_Info_String: + switch s in a { + case string: return err_conv(_encode_text(e, s)) + case cstring: return err_conv(_encode_text(e, string(s))) + } + + case runtime.Type_Info_Boolean: + val: bool + switch b in a { + case bool: return _encode_bool(e.writer, b) + case b8: return _encode_bool(e.writer, bool(b)) + case b16: return _encode_bool(e.writer, bool(b)) + case b32: return _encode_bool(e.writer, bool(b)) + case b64: return _encode_bool(e.writer, bool(b)) + } + + case runtime.Type_Info_Array: + if info.elem.id == byte { + raw := ([^]byte)(v.data) + return err_conv(_encode_bytes(e, raw[:info.count])) + } + + err_conv(_encode_u64(e, u64(info.count), .Array)) or_return + for i in 0.. (res: [10]byte) { + e := e + builder := strings.builder_from_slice(res[:]) + e.writer = strings.to_stream(&builder) + + assert(_encode_u64(e, u64(len(str)), .Text) == nil) + res[9] = u8(len(builder.buf)) + assert(res[9] < 10) + return + } + + Encoded_Entry_Fast :: struct($T: typeid) { + pre_key: [10]byte, + key: T, + val_idx: uintptr, + } + + Encoded_Entry :: struct { + key: ^[dynamic]byte, + val_idx: uintptr, + } + + switch info.key.id { + case string: + entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, context.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + a, b := a, b + pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]])) + if pre_cmp != .Equal { + return pre_cmp + } + + return slice.Ordering(bytes.compare(a.key^, b.key^)) + }) + + for &entry in entries { + io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return + io.write_full(e.writer, entry.key^) or_return + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + + case cstring: + entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, context.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + a, b := a, b + pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]])) + if pre_cmp != .Equal { + return pre_cmp + } + + ab := transmute([]byte)string(a.key^) + bb := transmute([]byte)string(b.key^) + return slice.Ordering(bytes.compare(ab, bb)) + }) + + for &entry in entries { + io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return + io.write_full(e.writer, transmute([]byte)string(entry.key^)) or_return + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + + case: + entries := make([dynamic]Encoded_Entry, 0, map_cap, context.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + return slice.Ordering(bytes.compare(a.key[:], b.key[:])) + }) + + for entry in entries { + io.write_full(e.writer, entry.key[:]) or_return + delete(entry.key^) + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + } + } + + case runtime.Type_Info_Struct: + switch vv in v { + case Tag: return err_conv(_encode_tag(e, vv)) + } + + err_conv(_encode_u16(e, u16(len(info.names)), .Map)) or_return + + marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, name: string, i: int) -> Marshal_Error { + err_conv(_encode_text(e, name)) or_return + + id := info.types[i].id + data := rawptr(uintptr(v.data) + info.offsets[i]) + field_any := any{data, id} + + if tag := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor_tag")); tag != "" { + if impl, ok := _tag_implementations_id[tag]; ok { + return impl->marshal(e, field_any) + } + + nr, ok := strconv.parse_u64_of_base(tag, 10) + if !ok { return .Invalid_CBOR_Tag } + + if impl, nok := _tag_implementations_nr[nr]; nok { + return impl->marshal(e, field_any) + } + + err_conv(_encode_u64(e, nr, .Tag)) or_return + } + + return marshal_into(e, field_any) + } + + field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string { + if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" { + return cbor_name + } else { + return info.names[i] + } + } + + if .Deterministic_Map_Sorting in e.flags { + Name :: struct { + name: string, + field: int, + } + entries := make([dynamic]Name, 0, len(info.names), context.temp_allocator) or_return + defer delete(entries) + + for name, i in info.names { + append(&entries, Name{field_name(info, i), i}) or_return + } + + // Sort lexicographic on the bytes of the key. + slice.sort_by_cmp(entries[:], proc(a, b: Name) -> slice.Ordering { + return slice.Ordering(bytes.compare(transmute([]byte)a.name, transmute([]byte)b.name)) + }) + + for entry in entries { + marshal_entry(e, info, v, entry.name, entry.field) or_return + } + } else { + for name, i in info.names { + marshal_entry(e, info, v, field_name(info, i), i) or_return + } + } + return + + case runtime.Type_Info_Union: + switch vv in v { + case Value: return err_conv(encode(e, vv)) + } + + tag := reflect.get_union_variant_raw_tag(v) + if v.data == nil || tag <= 0 { + return _encode_nil(e.writer) + } + id := info.variants[tag-1].id + return marshal_into(e, any{v.data, id}) + + case runtime.Type_Info_Enum: + return marshal_into(e, any{v.data, info.base.id}) + + case runtime.Type_Info_Bit_Set: + do_byte_swap := is_bit_set_different_endian_to_platform(info.underlying) + switch ti.size * 8 { + case 0: + return _encode_u8(e.writer, 0) + case 8: + x := (^u8)(v.data)^ + return _encode_u8(e.writer, x) + case 16: + x := (^u16)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u16(e, x)) + case 32: + x := (^u32)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u32(e, x)) + case 64: + x := (^u64)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u64(e, x)) + case: + panic("unknown bit_size size") + } + } + + return _unsupported(v.id, nil) +} diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin new file mode 100644 index 000000000..54bc7dd15 --- /dev/null +++ b/core/encoding/cbor/tags.odin @@ -0,0 +1,361 @@ +package cbor + +import "core:encoding/base64" +import "core:io" +import "core:math" +import "core:math/big" +import "core:mem" +import "core:reflect" +import "core:runtime" +import "core:strings" +import "core:time" + +// Tags defined in RFC 7049 that we provide implementations for. + +// UTC time in seconds, unmarshalled into a `core:time` `time.Time` or integer. +TAG_EPOCH_TIME_NR :: 1 +TAG_EPOCH_TIME_ID :: "epoch" + +// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. +TAG_UNSIGNED_BIG_NR :: 2 +// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. +TAG_NEGATIVE_BIG_NR :: 3 + +// TAG_DECIMAL_FRACTION :: 4 // NOTE: We could probably implement this with `math/fixed`. + +// Sometimes it is beneficial to carry an embedded CBOR data item that is not meant to be decoded +// immediately at the time the enclosing data item is being decoded. Tag number 24 (CBOR data item) +// can be used to tag the embedded byte string as a single data item encoded in CBOR format. +TAG_CBOR_NR :: 24 +TAG_CBOR_ID :: "cbor" + +// The contents of this tag are base64 encoded during marshal and decoded during unmarshal. +TAG_BASE64_NR :: 34 +TAG_BASE64_ID :: "base64" + +// A tag that is used to detect the contents of a binary buffer (like a file) are CBOR. +// This tag would wrap everything else, decoders can then check for this header and see if the +// given content is definitely CBOR. +TAG_SELF_DESCRIBED_CBOR :: 55799 + +// A tag implementation that handles marshals and unmarshals for the tag it is registered on. +Tag_Implementation :: struct { + data: rawptr, + unmarshal: Tag_Unmarshal_Proc, + marshal: Tag_Marshal_Proc, +} + +// Procedure responsible for umarshalling the tag out of the reader into the given `any`. +Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, r: io.Reader, tag_nr: Tag_Number, v: any) -> Unmarshal_Error + +// Procedure responsible for marshalling the tag in the given `any` into the given encoder. +Tag_Marshal_Proc :: #type proc(self: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error + +// When encountering a tag in the CBOR being unmarshalled, the implementation is used to unmarshal it. +// When encountering a struct tag like `cbor_tag:"Tag_Number"`, the implementation is used to marshal it. +_tag_implementations_nr: map[Tag_Number]Tag_Implementation + +// Same as the number implementations but friendlier to use as a struct tag. +// Instead of `cbor_tag:"34"` you can use `cbor_tag:"base64"`. +_tag_implementations_id: map[string]Tag_Implementation + +// Tag implementations that are always used by a type, if that type is encountered in marshal it +// will rely on the implementation to marshal it. +// +// This is good for types that don't make sense or can't marshal in its default form. +_tag_implementations_type: map[typeid]Tag_Implementation + +// Register a custom tag implementation to be used when marshalling that type and unmarshalling that tag number. +tag_register_type :: proc(impl: Tag_Implementation, nr: Tag_Number, type: typeid) { + _tag_implementations_nr[nr] = impl + _tag_implementations_type[type] = impl +} + +// Register a custom tag implementation to be used when marshalling that tag number or marshalling +// a field with the struct tag `cbor_tag:"nr"`. +tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string) { + _tag_implementations_nr[nr] = impl + _tag_implementations_id[id] = impl +} + +// Controls initialization of default tag implementations. +// JS and WASI default to a panic allocator so we don't want to do it on those. +INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, ODIN_OS != .JS && ODIN_OS != .WASI) + +@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) +tags_initialize_defaults :: proc() { + tags_register_defaults() +} + +// Registers tags that have implementations provided by this package. +// This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define. +tags_register_defaults :: proc() { + // NOTE: Not registering this the other way around, user can opt-in using the `cbor_tag:"1"` struct + // tag instead, it would lose precision and marshalling the `time.Time` struct normally is valid. + tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID) + + // Use the struct tag `cbor_tag:"34"` to have your field encoded in a base64. + tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID) + + // Use the struct tag `cbor_tag:"24"` to keep a non-decoded field of raw CBOR. + tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID) + + // These following tags are registered at the type level and don't require an opt-in struct tag. + // Encoding these types on its own make no sense or no data is lost to encode it. + + tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_UNSIGNED_BIG_NR, big.Int) + tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_NEGATIVE_BIG_NR, big.Int) +} + +// Tag number 1 contains a numerical value counting the number of seconds from 1970-01-01T00:00Z +// in UTC time to the represented point in civil time. +// +// See RFC 8949 section 3.4.2. +@(private) +tag_time_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(r) or_return + #partial switch hdr { + case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64: + switch &dst in v { + case time.Time: + i: i64 + _unmarshal_any_ptr(r, &i, hdr) or_return + dst = time.unix(i64(i), 0) + return + case: + return _unmarshal_value(r, v, hdr) + } + + case .F16, .F32, .F64: + switch &dst in v { + case time.Time: + f: f64 + _unmarshal_any_ptr(r, &f, hdr) or_return + whole, fract := math.modf(f) + dst = time.unix(i64(whole), i64(fract * 1e9)) + return + case: + return _unmarshal_value(r, v, hdr) + } + + case: + maj, add := _header_split(hdr) + if maj == .Other { + i := _decode_tiny_u8(add) or_return + + switch &dst in v { + case time.Time: + dst = time.unix(i64(i), 0) + case: + if _assign_int(v, i) { return } + } + } + + // Only numbers and floats are allowed in this tag. + return .Bad_Tag_Value + } + + return _unsupported(v, hdr) +} + +@(private) +tag_time_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + switch vv in v { + case time.Time: + // NOTE: we lose precision here, which is one of the reasons for this tag being opt-in. + i := time.time_to_unix(vv) + + _encode_u8(e.writer, TAG_EPOCH_TIME_NR, .Tag) or_return + return err_conv(_encode_uint(e, _int_to_uint(i))) + case: + unreachable() + } +} + +@(private) +tag_big_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(r) or_return + maj, add := _header_split(hdr) + if maj != .Bytes { + // Only bytes are supported in this tag. + return .Bad_Tag_Value + } + + switch &dst in v { + case big.Int: + bytes := err_conv(_decode_bytes(r, add)) or_return + defer delete(bytes) + + if err := big.int_from_bytes_big(&dst, bytes); err != nil { + return .Bad_Tag_Value + } + + if tnr == TAG_NEGATIVE_BIG_NR { + dst.sign = .Negative + } + + return + } + + return _unsupported(v, hdr) +} + +@(private) +tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + switch &vv in v { + case big.Int: + if !big.int_is_initialized(&vv) { + _encode_u8(e.writer, TAG_UNSIGNED_BIG_NR, .Tag) or_return + return _encode_u8(e.writer, 0, .Bytes) + } + + // NOTE: using the panic_allocator because all procedures should only allocate if the Int + // is uninitialized (which we checked). + + is_neg, err := big.is_negative(&vv, mem.panic_allocator()) + assert(err == nil, "only errors if not initialized, which has been checked") + + tnr: u8 = TAG_NEGATIVE_BIG_NR if is_neg else TAG_UNSIGNED_BIG_NR + _encode_u8(e.writer, tnr, .Tag) or_return + + size_in_bytes, berr := big.int_to_bytes_size(&vv, false, mem.panic_allocator()) + assert(berr == nil, "only errors if not initialized, which has been checked") + assert(size_in_bytes >= 0) + + err_conv(_encode_u64(e, u64(size_in_bytes), .Bytes)) or_return + + for offset := (size_in_bytes*8)-8; offset >= 0; offset -= 8 { + bits, derr := big.int_bitfield_extract(&vv, offset, 8, mem.panic_allocator()) + assert(derr == nil, "only errors if not initialized or invalid argument (offset and count), which won't happen") + + io.write_full(e.writer, {u8(bits & 255)}) or_return + } + return nil + + case: unreachable() + } +} + +@(private) +tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> Unmarshal_Error { + hdr := _decode_header(r) or_return + major, add := _header_split(hdr) + #partial switch major { + case .Bytes: + ti := reflect.type_info_base(type_info_of(v.id)) + return _unmarshal_bytes(r, v, ti, hdr, add) + + case: return .Bad_Tag_Value + } +} + +@(private) +tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + _encode_u8(e.writer, TAG_CBOR_NR, .Tag) or_return + ti := runtime.type_info_base(type_info_of(v.id)) + #partial switch t in ti.variant { + case runtime.Type_Info_String: + return marshal_into(e, v) + case runtime.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case runtime.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case runtime.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case: + return .Bad_Tag_Value + } +} + +// NOTE: this could probably be more efficient by decoding bytes from CBOR and then from base64 at the same time. +@(private) +tag_base64_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(r) or_return + major, add := _header_split(hdr) + #partial switch major { + case .Text: + ti := reflect.type_info_base(type_info_of(v.id)) + _unmarshal_bytes(r, v, ti, hdr, add) or_return + #partial switch t in ti.variant { + case runtime.Type_Info_String: + switch t.is_cstring { + case true: + str := string((^cstring)(v.data)^) + decoded := base64.decode(str) or_return + (^cstring)(v.data)^ = strings.clone_to_cstring(string(decoded)) or_return + delete(decoded) + delete(str) + case false: + str := (^string)(v.data)^ + decoded := base64.decode(str) or_return + (^string)(v.data)^ = string(decoded) + delete(str) + } + return + + case runtime.Type_Info_Array: + raw := ([^]byte)(v.data) + decoded := base64.decode(string(raw[:t.count])) or_return + copy(raw[:t.count], decoded) + delete(decoded) + return + + case runtime.Type_Info_Slice: + raw := (^[]byte)(v.data) + decoded := base64.decode(string(raw^)) or_return + delete(raw^) + raw^ = decoded + return + + case runtime.Type_Info_Dynamic_Array: + raw := (^mem.Raw_Dynamic_Array)(v.data) + str := string(((^[dynamic]byte)(v.data)^)[:]) + + decoded := base64.decode(str) or_return + delete(str) + + raw.data = raw_data(decoded) + raw.len = len(decoded) + raw.cap = len(decoded) + return + + case: unreachable() + } + + case: return .Bad_Tag_Value + } +} + +@(private) +tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + _encode_u8(e.writer, TAG_BASE64_NR, .Tag) or_return + + ti := runtime.type_info_base(type_info_of(v.id)) + a := any{v.data, ti.id} + + bytes: []byte + switch val in a { + case string: bytes = transmute([]byte)val + case cstring: bytes = transmute([]byte)string(val) + case []byte: bytes = val + case [dynamic]byte: bytes = val[:] + case: + #partial switch t in ti.variant { + case runtime.Type_Info_Array: + if t.elem.id != byte { return .Bad_Tag_Value } + bytes = ([^]byte)(v.data)[:t.count] + case: + return .Bad_Tag_Value + } + } + + out_len := base64.encoded_length(bytes) + err_conv(_encode_u64(e, u64(out_len), .Text)) or_return + return base64.encode_into(e.writer, bytes) +} diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin new file mode 100644 index 000000000..0da8e3f2a --- /dev/null +++ b/core/encoding/cbor/unmarshal.odin @@ -0,0 +1,832 @@ +package cbor + +import "core:intrinsics" +import "core:io" +import "core:mem" +import "core:reflect" +import "core:runtime" +import "core:strings" +import "core:unicode/utf8" + +// `strings` is only used in poly procs, but -vet thinks it is fully unused. +_ :: strings + +/* +Unmarshals the given CBOR into the given pointer using reflection. +Types that require allocation are allocated using the given allocator. + +Some temporary allocations are done on the `context.temp_allocator`, but, if you want to, +this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. +*/ +unmarshal :: proc { + unmarshal_from_reader, + unmarshal_from_string, +} + +// Unmarshals from a reader, see docs on the proc group `Unmarshal` for more info. +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, allocator := context.allocator) -> Unmarshal_Error { + return _unmarshal_any_ptr(r, ptr, allocator=allocator) +} + +// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. +unmarshal_from_string :: proc(s: string, ptr: ^$T, allocator := context.allocator) -> Unmarshal_Error { + sr: strings.Reader + r := strings.to_reader(&sr, s) + return _unmarshal_any_ptr(r, ptr, allocator=allocator) +} + +_unmarshal_any_ptr :: proc(r: io.Reader, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator) -> Unmarshal_Error { + context.allocator = allocator + v := v + + if v == nil || v.id == nil { + return .Invalid_Parameter + } + + v = reflect.any_base(v) + ti := type_info_of(v.id) + if !reflect.is_pointer(ti) || ti.id == rawptr { + return .Non_Pointer_Parameter + } + + data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} + return _unmarshal_value(r, data, hdr.? or_else (_decode_header(r) or_return)) +} + +_unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_Error) { + v := v + ti := reflect.type_info_base(type_info_of(v.id)) + + // If it's a union with only one variant, then treat it as that variant + if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 { + #partial switch hdr { + case .Nil, .Undefined, nil: // no-op. + case: + variant := u.variants[0] + v.id = variant.id + ti = reflect.type_info_base(variant) + if !reflect.is_pointer_internally(variant) { + tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} + assert(_assign_int(tag, 1)) + } + } + } + + // Allow generic unmarshal by doing it into a `Value`. + switch &dst in v { + case Value: + dst = err_conv(decode(r, hdr)) or_return + return + } + + switch hdr { + case .U8: + decoded := _decode_u8(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U16: + decoded := _decode_u16(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U32: + decoded := _decode_u32(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U64: + decoded := _decode_u64(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .Neg_U8: + decoded := Negative_U8(_decode_u8(r) or_return) + + switch &dst in v { + case Negative_U8: + dst = decoded + return + case Negative_U16: + dst = Negative_U16(decoded) + return + case Negative_U32: + dst = Negative_U32(decoded) + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U16: + decoded := Negative_U16(_decode_u16(r) or_return) + + switch &dst in v { + case Negative_U16: + dst = decoded + return + case Negative_U32: + dst = Negative_U32(decoded) + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U32: + decoded := Negative_U32(_decode_u32(r) or_return) + + switch &dst in v { + case Negative_U32: + dst = decoded + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U64: + decoded := Negative_U64(_decode_u64(r) or_return) + + switch &dst in v { + case Negative_U64: + dst = decoded + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Simple: + decoded := _decode_simple(r) or_return + + // NOTE: Because this is a special type and not to be treated as a general integer, + // We only put the value of it in fields that are explicitly of type `Simple`. + switch &dst in v { + case Simple: + dst = decoded + return + case: + return _unsupported(v, hdr) + } + + case .F16: + decoded := _decode_f16(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .F32: + decoded := _decode_f32(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .F64: + decoded := _decode_f64(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .True: + if !_assign_bool(v, true) { return _unsupported(v, hdr) } + return + + case .False: + if !_assign_bool(v, false) { return _unsupported(v, hdr) } + return + + case .Nil, .Undefined: + mem.zero(v.data, ti.size) + return + + case .Break: + return .Break + } + + maj, add := _header_split(hdr) + switch maj { + case .Unsigned: + decoded := _decode_tiny_u8(add) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr, add) } + return + + case .Negative: + decoded := Negative_U8(_decode_tiny_u8(add) or_return) + + switch &dst in v { + case Negative_U8: + dst = decoded + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr, add) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr, add) } + return + + case .Other: + decoded := _decode_tiny_simple(add) or_return + + // NOTE: Because this is a special type and not to be treated as a general integer, + // We only put the value of it in fields that are explicitly of type `Simple`. + switch &dst in v { + case Simple: + dst = decoded + return + case: + return _unsupported(v, hdr, add) + } + + case .Tag: + switch &dst in v { + case ^Tag: + tval := err_conv(_decode_tag_ptr(r, add)) or_return + if t, is_tag := tval.(^Tag); is_tag { + dst = t + return + } + + destroy(tval) + return .Bad_Tag_Value + case Tag: + t := err_conv(_decode_tag(r, add)) or_return + if t, is_tag := t.?; is_tag { + dst = t + return + } + + return .Bad_Tag_Value + } + + nr := err_conv(_decode_tag_nr(r, add)) or_return + + // Custom tag implementations. + if impl, ok := _tag_implementations_nr[nr]; ok { + return impl->unmarshal(r, nr, v) + } else { + // Discard the tag info and unmarshal as its value. + return _unmarshal_value(r, v, _decode_header(r) or_return) + } + + return _unsupported(v, hdr, add) + + case .Bytes: return _unmarshal_bytes(r, v, ti, hdr, add) + case .Text: return _unmarshal_string(r, v, ti, hdr, add) + case .Array: return _unmarshal_array(r, v, ti, hdr, add) + case .Map: return _unmarshal_map(r, v, ti, hdr, add) + + case: return .Bad_Major + } +} + +_unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { + #partial switch t in ti.variant { + case reflect.Type_Info_String: + bytes := err_conv(_decode_bytes(r, add)) or_return + + if t.is_cstring { + raw := (^cstring)(v.data) + assert_safe_for_cstring(string(bytes)) + raw^ = cstring(raw_data(bytes)) + } else { + // String has same memory layout as a slice, so we can directly use it as a slice. + raw := (^mem.Raw_String)(v.data) + raw^ = transmute(mem.Raw_String)bytes + } + + return + + case reflect.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + bytes := err_conv(_decode_bytes(r, add)) or_return + raw := (^mem.Raw_Slice)(v.data) + raw^ = transmute(mem.Raw_Slice)bytes + return + + case reflect.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + bytes := err_conv(_decode_bytes(r, add)) or_return + raw := (^mem.Raw_Dynamic_Array)(v.data) + raw.data = raw_data(bytes) + raw.len = len(bytes) + raw.cap = len(bytes) + raw.allocator = context.allocator + return + + case reflect.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + bytes: []byte; { + context.allocator = context.temp_allocator + bytes = err_conv(_decode_bytes(r, add)) or_return + } + defer delete(bytes, context.temp_allocator) + + if len(bytes) > t.count { return _unsupported(v, hdr) } + + // Copy into array type, delete original. + slice := ([^]byte)(v.data)[:len(bytes)] + n := copy(slice, bytes) + assert(n == len(bytes)) + return + } + + return _unsupported(v, hdr) +} + +_unmarshal_string :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { + #partial switch t in ti.variant { + case reflect.Type_Info_String: + text := err_conv(_decode_text(r, add)) or_return + + if t.is_cstring { + raw := (^cstring)(v.data) + + assert_safe_for_cstring(text) + raw^ = cstring(raw_data(text)) + } else { + raw := (^string)(v.data) + raw^ = text + } + return + + // Enum by its variant name. + case reflect.Type_Info_Enum: + context.allocator = context.temp_allocator + text := err_conv(_decode_text(r, add)) or_return + defer delete(text, context.temp_allocator) + + for name, i in t.names { + if name == text { + if !_assign_int(any{v.data, ti.id}, t.values[i]) { return _unsupported(v, hdr) } + return + } + } + + case reflect.Type_Info_Rune: + context.allocator = context.temp_allocator + text := err_conv(_decode_text(r, add)) or_return + defer delete(text, context.temp_allocator) + + r := (^rune)(v.data) + dr, n := utf8.decode_rune(text) + if dr == utf8.RUNE_ERROR || n < len(text) { + return _unsupported(v, hdr) + } + + r^ = dr + return + } + + return _unsupported(v, hdr) +} + +_unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { + + assign_array :: proc( + r: io.Reader, + da: ^mem.Raw_Dynamic_Array, + elemt: ^reflect.Type_Info, + _length: Maybe(int), + growable := true, + ) -> (out_of_space: bool, err: Unmarshal_Error) { + length, has_length := _length.? + for idx: uintptr = 0; !has_length || idx < uintptr(length); idx += 1 { + elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) + elem := any{elem_ptr, elemt.id} + + hdr := _decode_header(r) or_return + + // Double size if out of capacity. + if da.cap <= da.len { + // Not growable, error out. + if !growable { return true, .Out_Of_Memory } + + cap := 2 * da.cap + ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap) + + // NOTE: Might be lying here, but it is at least an allocator error. + if !ok { return false, .Out_Of_Memory } + } + + err = _unmarshal_value(r, elem, hdr) + if !has_length && err == .Break { break } + if err != nil { return } + + da.len += 1 + } + + return false, nil + } + + // Allow generically storing the values array. + switch &dst in v { + case ^Array: + dst = err_conv(_decode_array_ptr(r, add)) or_return + return + case Array: + dst = err_conv(_decode_array(r, add)) or_return + return + } + + #partial switch t in ti.variant { + case reflect.Type_Info_Slice: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + + data := mem.alloc_bytes_non_zeroed(t.elem.size * length, t.elem.align) or_return + defer if err != nil { mem.free_bytes(data) } + + da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } + + assign_array(r, &da, t.elem, _length) or_return + + if da.len < da.cap { + // Ignoring an error here, but this is not critical to succeed. + _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len) + } + + raw := (^mem.Raw_Slice)(v.data) + raw.data = da.data + raw.len = da.len + return + + case reflect.Type_Info_Dynamic_Array: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + + data := mem.alloc_bytes_non_zeroed(t.elem.size * length, t.elem.align) or_return + defer if err != nil { mem.free_bytes(data) } + + raw := (^mem.Raw_Dynamic_Array)(v.data) + raw.data = raw_data(data) + raw.len = 0 + raw.cap = length + raw.allocator = context.allocator + + _ = assign_array(r, raw, t.elem, _length) or_return + return + + case reflect.Type_Info_Array: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else t.count + + if !unknown && length > t.count { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + + out_of_space := assign_array(r, &da, t.elem, _length, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case reflect.Type_Info_Enumerated_Array: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else t.count + + if !unknown && length > t.count { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + + out_of_space := assign_array(r, &da, t.elem, _length, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case reflect.Type_Info_Complex: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else 2 + + if !unknown && length > 2 { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator } + + info: ^runtime.Type_Info + switch ti.id { + case complex32: info = type_info_of(f16) + case complex64: info = type_info_of(f32) + case complex128: info = type_info_of(f64) + case: unreachable() + } + + out_of_space := assign_array(r, &da, info, 2, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case reflect.Type_Info_Quaternion: + _length, unknown := err_conv(_decode_container_length(r, add)) or_return + length := _length.? or_else 4 + + if !unknown && length > 4 { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator } + + info: ^runtime.Type_Info + switch ti.id { + case quaternion64: info = type_info_of(f16) + case quaternion128: info = type_info_of(f32) + case quaternion256: info = type_info_of(f64) + case: unreachable() + } + + out_of_space := assign_array(r, &da, info, 4, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case: return _unsupported(v, hdr) + } +} + +_unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { + + decode_key :: proc(r: io.Reader, v: any) -> (k: string, err: Unmarshal_Error) { + entry_hdr := _decode_header(r) or_return + entry_maj, entry_add := _header_split(entry_hdr) + #partial switch entry_maj { + case .Text: + k = err_conv(_decode_text(r, entry_add)) or_return + return + case .Bytes: + bytes := err_conv(_decode_bytes(r, entry_add)) or_return + k = string(bytes) + return + case: + err = _unsupported(v, entry_hdr) + return + } + } + + // Allow generically storing the map array. + switch &dst in v { + case ^Map: + dst = err_conv(_decode_map_ptr(r, add)) or_return + return + case Map: + dst = err_conv(_decode_map(r, add)) or_return + return + } + + #partial switch t in ti.variant { + case reflect.Type_Info_Struct: + if t.is_raw_union { + return _unsupported(v, hdr) + } + + length, unknown := err_conv(_decode_container_length(r, add)) or_return + fields := reflect.struct_fields_zipped(ti.id) + + for idx := 0; unknown || idx < length.?; idx += 1 { + // Decode key, keys can only be strings. + key: string; { + context.allocator = context.temp_allocator + if keyv, kerr := decode_key(r, v); unknown && kerr == .Break { + break + } else if kerr != nil { + err = kerr + return + } else { + key = keyv + } + } + defer delete(key, context.temp_allocator) + + // Find matching field. + use_field_idx := -1 + { + for field, field_idx in fields { + tag_value := string(reflect.struct_tag_get(field.tag, "cbor")) + if key == tag_value { + use_field_idx = field_idx + break + } + + if key == field.name { + // No break because we want to still check remaining struct tags. + use_field_idx = field_idx + } + } + + // Skips unused map entries. + if use_field_idx < 0 { + continue + } + } + + field := fields[use_field_idx] + name := field.name + ptr := rawptr(uintptr(v.data) + field.offset) + fany := any{ptr, field.type.id} + _unmarshal_value(r, fany, _decode_header(r) or_return) or_return + } + return + + case reflect.Type_Info_Map: + if !reflect.is_string(t.key) { + return _unsupported(v, hdr) + } + + raw_map := (^mem.Raw_Map)(v.data) + if raw_map.allocator.procedure == nil { + raw_map.allocator = context.allocator + } + + defer if err != nil { + _ = runtime.map_free_dynamic(raw_map^, t.map_info) + } + + length, unknown := err_conv(_decode_container_length(r, add)) or_return + if !unknown { + // Reserve space before setting so we can return allocation errors and be efficient on big maps. + new_len := uintptr(runtime.map_len(raw_map^)+length.?) + runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return + } + + // Temporary memory to unmarshal keys into before inserting them into the map. + elem_backing := mem.alloc_bytes_non_zeroed(t.value.size, t.value.align, context.temp_allocator) or_return + defer delete(elem_backing, context.temp_allocator) + + map_backing_value := any{raw_data(elem_backing), t.value.id} + + for idx := 0; unknown || idx < length.?; idx += 1 { + // Decode key, keys can only be strings. + key: string + if keyv, kerr := decode_key(r, v); unknown && kerr == .Break { + break + } else if kerr != nil { + err = kerr + return + } else { + key = keyv + } + + if unknown { + // Reserve space for new element so we can return allocator errors. + new_len := uintptr(runtime.map_len(raw_map^)+1) + runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return + } + + mem.zero_slice(elem_backing) + _unmarshal_value(r, map_backing_value, _decode_header(r) or_return) or_return + + key_ptr := rawptr(&key) + key_cstr: cstring + if reflect.is_cstring(t.key) { + assert_safe_for_cstring(key) + key_cstr = cstring(raw_data(key)) + key_ptr = &key_cstr + } + + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) + // We already reserved space for it, so this shouldn't fail. + assert(set_ptr != nil) + } + return + + case: + return _unsupported(v, hdr) + } +} + +_assign_int :: proc(val: any, i: $T) -> bool { + v := reflect.any_core(val) + + // NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a + // less strict encoding?. + + switch &dst in v { + case i8: dst = i8 (i) + case i16: dst = i16 (i) + case i16le: dst = i16le (i) + case i16be: dst = i16be (i) + case i32: dst = i32 (i) + case i32le: dst = i32le (i) + case i32be: dst = i32be (i) + case i64: dst = i64 (i) + case i64le: dst = i64le (i) + case i64be: dst = i64be (i) + case i128: dst = i128 (i) + case i128le: dst = i128le (i) + case i128be: dst = i128be (i) + case u8: dst = u8 (i) + case u16: dst = u16 (i) + case u16le: dst = u16le (i) + case u16be: dst = u16be (i) + case u32: dst = u32 (i) + case u32le: dst = u32le (i) + case u32be: dst = u32be (i) + case u64: dst = u64 (i) + case u64le: dst = u64le (i) + case u64be: dst = u64be (i) + case u128: dst = u128 (i) + case u128le: dst = u128le (i) + case u128be: dst = u128be (i) + case int: dst = int (i) + case uint: dst = uint (i) + case uintptr: dst = uintptr(i) + case: + ti := type_info_of(v.id) + do_byte_swap := is_bit_set_different_endian_to_platform(ti) + #partial switch info in ti.variant { + case runtime.Type_Info_Bit_Set: + switch ti.size * 8 { + case 0: + case 8: + x := (^u8)(v.data) + x^ = u8(i) + case 16: + x := (^u16)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i) + case 32: + x := (^u32)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i) + case 64: + x := (^u64)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i) + case: + panic("unknown bit_size size") + } + case: + return false + } + } + return true +} + +_assign_float :: proc(val: any, f: $T) -> bool { + v := reflect.any_core(val) + + // NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a + // less strict encoding?. + + switch &dst in v { + case f16: dst = f16 (f) + case f16le: dst = f16le(f) + case f16be: dst = f16be(f) + case f32: dst = f32 (f) + case f32le: dst = f32le(f) + case f32be: dst = f32be(f) + case f64: dst = f64 (f) + case f64le: dst = f64le(f) + case f64be: dst = f64be(f) + + case complex32: dst = complex(f16(f), 0) + case complex64: dst = complex(f32(f), 0) + case complex128: dst = complex(f64(f), 0) + + case quaternion64: dst = quaternion(f16(f), 0, 0, 0) + case quaternion128: dst = quaternion(f32(f), 0, 0, 0) + case quaternion256: dst = quaternion(f64(f), 0, 0, 0) + + case: return false + } + return true +} + +_assign_bool :: proc(val: any, b: bool) -> bool { + v := reflect.any_core(val) + switch &dst in v { + case bool: dst = bool(b) + case b8: dst = b8 (b) + case b16: dst = b16 (b) + case b32: dst = b32 (b) + case b64: dst = b64 (b) + case: return false + } + return true +} + +// Sanity check that the decoder added a nil byte to the end. +@(private, disabled=ODIN_DISABLE_ASSERT) +assert_safe_for_cstring :: proc(s: string, loc := #caller_location) { + assert(([^]byte)(raw_data(s))[len(s)] == 0, loc = loc) +} diff --git a/core/io/io.odin b/core/io/io.odin index ea8e240b0..961dbe43e 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -29,7 +29,7 @@ Error :: enum i32 { // Invalid_Write means that a write returned an impossible count Invalid_Write, - // Short_Buffer means that a read required a longer buffer than was provided + // Short_Buffer means that a read/write required a longer buffer than was provided Short_Buffer, // No_Progress is returned by some implementations of `io.Reader` when many calls @@ -359,6 +359,29 @@ read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error) return } +// write_full writes until the entire contents of `buf` has been written or an error occurs. +write_full :: proc(w: Writer, buf: []byte) -> (n: int, err: Error) { + return write_at_least(w, buf, len(buf)) +} + +// write_at_least writes at least `buf[:min]` to the writer and returns the amount written. +// If an error occurs before writing everything it is returned. +write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, .Short_Buffer + } + for n < min && err == nil { + nn: int + nn, err = write(w, buf[n:]) + n += nn + } + + if err == nil && n < min { + err = .Short_Write + } + return +} + // copy copies from src to dst till either EOF is reached on src or an error occurs // It returns the number of bytes copied and the first error that occurred whilst copying, if any. copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) { diff --git a/core/net/common.odin b/core/net/common.odin index 2a6f44602..3cd1459a6 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -413,4 +413,5 @@ DNS_Record_Header :: struct #packed { DNS_Host_Entry :: struct { name: string, addr: Address, -} \ No newline at end of file +} + diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index fff344b22..22374f3b5 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -53,6 +53,7 @@ import json "core:encoding/json" import varint "core:encoding/varint" import xml "core:encoding/xml" import endian "core:encoding/endian" +import cbor "core:encoding/cbor" import fmt "core:fmt" import hash "core:hash" @@ -167,6 +168,7 @@ _ :: json _ :: varint _ :: xml _ :: endian +_ :: cbor _ :: fmt _ :: hash _ :: xxhash diff --git a/tests/core/Makefile b/tests/core/Makefile index 1207eeec5..1fca7bf97 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -55,6 +55,7 @@ encoding_test: $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml + $(ODIN) run encoding/cbor $(COMMON) -out:test_cbor math_test: $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math diff --git a/tests/core/build.bat b/tests/core/build.bat index d5f528f0c..5bf8e1ead 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -40,6 +40,7 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b echo --- echo Running core:math/noise tests diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin new file mode 100644 index 000000000..22359d830 --- /dev/null +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -0,0 +1,719 @@ +package test_encoding_cbor + +import "core:bytes" +import "core:encoding/cbor" +import "core:fmt" +import "core:intrinsics" +import "core:math/big" +import "core:mem" +import "core:reflect" +import "core:testing" +import "core:time" + +Foo :: struct { + str: string, + cstr: cstring, + value: cbor.Value, + neg: cbor.Negative_U16, + pos: u16, + iamint: int, + base64: string `cbor_tag:"base64"`, + renamed: f32 `cbor:"renamed :)"`, + now: time.Time `cbor_tag:"1"`, + nowie: time.Time, + child: struct{ + dyn: [dynamic]string, + mappy: map[string]int, + my_integers: [10]int, + }, + my_bytes: []byte, + ennie: FooBar, + ennieb: FooBars, + quat: quaternion64, + comp: complex128, + important: rune, + no: cbor.Nil, + nos: cbor.Undefined, + yes: b32, + biggie: u64, + smallie: cbor.Negative_U64, + onetwenty: i128, + small_onetwenty: i128, + biggest: big.Int, + smallest: big.Int, +} + +FooBar :: enum { + EFoo, + EBar, +} + +FooBars :: bit_set[FooBar; u16] + +@(test) +test_marshalling :: proc(t: ^testing.T) { + tracker: mem.Tracking_Allocator + mem.tracking_allocator_init(&tracker, context.allocator) + context.allocator = mem.tracking_allocator(&tracker) + context.temp_allocator = context.allocator + defer mem.tracking_allocator_destroy(&tracker) + + ev :: testing.expect_value + + { + nice := "16 is a nice number" + now := time.Time{_nsec = 1701117968 * 1e9} + f: Foo = { + str = "Hellope", + cstr = "Hellnope", + value = &cbor.Map{{u8(16), &nice}, {u8(32), u8(69)}}, + neg = 68, + pos = 1212, + iamint = -256, + base64 = nice, + renamed = 123123.125, + + now = now, + nowie = now, + + child = { + dyn = [dynamic]string{"one", "two", "three", "four"}, + mappy = map[string]int{"one" = 1, "two" = 2, "three" = 3, "four" = 4}, + my_integers = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + }, + + my_bytes = []byte{}, + + ennie = .EFoo, + ennieb = {.EBar}, + + quat = quaternion(16, 17, 18, 19), + comp = complex(32, 33), + + important = '!', + + no = cbor.Nil(uintptr(3)), + + yes = true, + + biggie = max(u64), + smallie = cbor.Negative_U64(max(u64)), + onetwenty = i128(12345), + small_onetwenty = -i128(max(u64)), + } + + big.atoi(&f.biggest, "1234567891011121314151617181920") + big.atoi(&f.smallest, "-1234567891011121314151617181920") + + defer { + delete(f.child.dyn) + delete(f.child.mappy) + big.destroy(&f.biggest) + big.destroy(&f.smallest) + } + + data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC) + ev(t, err, nil) + defer delete(data) + + decoded, derr := cbor.decode_string(string(data)) + ev(t, derr, nil) + defer cbor.destroy(decoded) + + diagnosis, eerr := cbor.diagnose(decoded) + ev(t, eerr, nil) + defer delete(diagnosis) + + ev(t, diagnosis, `{ + "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), + "biggest": 2(h'f951a9fd3c158afdff08ab8e0'), + "biggie": 18446744073709551615, + "child": { + "dyn": [ + "one", + "two", + "three", + "four" + ], + "mappy": { + "one": 1, + "two": 2, + "four": 4, + "three": 3 + }, + "my_integers": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + }, + "comp": [ + 32.0000, + 33.0000 + ], + "cstr": "Hellnope", + "ennie": 0, + "ennieb": 2, + "iamint": -256, + "important": "!", + "my_bytes": h'', + "neg": -69, + "no": nil, + "nos": undefined, + "now": 1(1701117968), + "nowie": { + "_nsec": 1701117968000000000 + }, + "onetwenty": 12345, + "pos": 1212, + "quat": [ + 17.0000, + 18.0000, + 19.0000, + 16.0000 + ], + "renamed :)": 123123.12500000, + "small_onetwenty": -18446744073709551615, + "smallest": 3(h'f951a9fd3c158afdff08ab8e0'), + "smallie": -18446744073709551616, + "str": "Hellope", + "value": { + 16: "16 is a nice number", + 32: 69 + }, + "yes": true +}`) + + backf: Foo + uerr := cbor.unmarshal(string(data), &backf) + ev(t, uerr, nil) + defer { + delete(backf.str) + delete(backf.cstr) + cbor.destroy(backf.value) + delete(backf.base64) + + for e in backf.child.dyn { delete(e) } + delete(backf.child.dyn) + + for k in backf.child.mappy { delete(k) } + delete(backf.child.mappy) + + delete(backf.my_bytes) + + big.destroy(&backf.biggest) + big.destroy(&backf.smallest) + } + + ev(t, backf.str, f.str) + ev(t, backf.cstr, f.cstr) + + #partial switch v in backf.value { + case ^cbor.Map: + for entry, i in v { + fm := f.value.(^cbor.Map) + ev(t, entry.key, fm[i].key) + + if str, is_str := entry.value.(^cbor.Text); is_str { + ev(t, str^, fm[i].value.(^cbor.Text)^) + } else { + ev(t, entry.value, fm[i].value) + } + } + + case: testing.error(t, v) + } + + ev(t, backf.neg, f.neg) + ev(t, backf.iamint, f.iamint) + ev(t, backf.base64, f.base64) + ev(t, backf.renamed, f.renamed) + ev(t, backf.now, f.now) + ev(t, backf.nowie, f.nowie) + for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) } + for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) } + ev(t, backf.child.my_integers, f.child.my_integers) + ev(t, len(backf.my_bytes), 0) + ev(t, len(backf.my_bytes), len(f.my_bytes)) + ev(t, backf.ennie, f.ennie) + ev(t, backf.ennieb, f.ennieb) + ev(t, backf.quat, f.quat) + ev(t, backf.comp, f.comp) + ev(t, backf.important, f.important) + ev(t, backf.no, nil) + ev(t, backf.nos, nil) + ev(t, backf.yes, f.yes) + ev(t, backf.biggie, f.biggie) + ev(t, backf.smallie, f.smallie) + ev(t, backf.onetwenty, f.onetwenty) + ev(t, backf.small_onetwenty, f.small_onetwenty) + + s_equals, s_err := big.equals(&backf.smallest, &f.smallest) + ev(t, s_err, nil) + if !s_equals { + testing.errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) + } + + b_equals, b_err := big.equals(&backf.biggest, &f.biggest) + ev(t, b_err, nil) + if !b_equals { + testing.errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) + } + } + + for _, leak in tracker.allocation_map { + testing.errorf(t, "%v leaked %m\n", leak.location, leak.size) + } + + for bad_free in tracker.bad_free_array { + testing.errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } +} + +@(test) +test_decode_unsigned :: proc(t: ^testing.T) { + expect_decoding(t, "\x00", "0", u8) + expect_decoding(t, "\x01", "1", u8) + expect_decoding(t, "\x0a", "10", u8) + expect_decoding(t, "\x17", "23", u8) + expect_decoding(t, "\x18\x18", "24", u8) + expect_decoding(t, "\x18\x19", "25", u8) + expect_decoding(t, "\x18\x64", "100", u8) + expect_decoding(t, "\x19\x03\xe8", "1000", u16) + expect_decoding(t, "\x1a\x00\x0f\x42\x40", "1000000", u32) // Million. + expect_decoding(t, "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00", "1000000000000", u64) // Trillion. + expect_decoding(t, "\x1b\xff\xff\xff\xff\xff\xff\xff\xff", "18446744073709551615", u64) // max(u64). +} + +@(test) +test_encode_unsigned :: proc(t: ^testing.T) { + expect_encoding(t, u8(0), "\x00") + expect_encoding(t, u8(1), "\x01") + expect_encoding(t, u8(10), "\x0a") + expect_encoding(t, u8(23), "\x17") + expect_encoding(t, u8(24), "\x18\x18") + expect_encoding(t, u8(25), "\x18\x19") + expect_encoding(t, u8(100), "\x18\x64") + expect_encoding(t, u16(1000), "\x19\x03\xe8") + expect_encoding(t, u32(1000000), "\x1a\x00\x0f\x42\x40") // Million. + expect_encoding(t, u64(1000000000000), "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00") // Trillion. + expect_encoding(t, u64(18446744073709551615), "\x1b\xff\xff\xff\xff\xff\xff\xff\xff") // max(u64). +} + +@(test) +test_decode_negative :: proc(t: ^testing.T) { + expect_decoding(t, "\x20", "-1", cbor.Negative_U8) + expect_decoding(t, "\x29", "-10", cbor.Negative_U8) + expect_decoding(t, "\x38\x63", "-100", cbor.Negative_U8) + expect_decoding(t, "\x39\x03\xe7", "-1000", cbor.Negative_U16) + + // Negative max(u64). + expect_decoding(t, "\x3b\xff\xff\xff\xff\xff\xff\xff\xff", "-18446744073709551616", cbor.Negative_U64) +} + +@(test) +test_encode_negative :: proc(t: ^testing.T) { + expect_encoding(t, cbor.Negative_U8(0), "\x20") + expect_encoding(t, cbor.Negative_U8(9), "\x29") + expect_encoding(t, cbor.Negative_U8(99), "\x38\x63") + expect_encoding(t, cbor.Negative_U16(999), "\x39\x03\xe7") + + // Negative max(u64). + expect_encoding(t, cbor.Negative_U64(18446744073709551615), "\x3b\xff\xff\xff\xff\xff\xff\xff\xff") +} + +@(test) +test_decode_simples :: proc(t: ^testing.T) { + expect_decoding(t, "\xf4", "false", bool) + expect_decoding(t, "\xf5", "true", bool) + expect_decoding(t, "\xf6", "nil", cbor.Nil) + expect_decoding(t, "\xf7", "undefined", cbor.Undefined) + + expect_decoding(t, "\xf0", "simple(16)", cbor.Simple) + expect_decoding(t, "\xf8\xff", "simple(255)", cbor.Atom) +} + +@(test) +test_encode_simples :: proc(t: ^testing.T) { + expect_encoding(t, bool(false), "\xf4") + expect_encoding(t, bool(true), "\xf5") + expect_encoding(t, cbor.Nil{}, "\xf6") // default value for a distinct rawptr, in this case Nil. + expect_encoding(t, cbor.Undefined{}, "\xf7") // default value for a distinct rawptr, in this case Undefined. + + expect_encoding(t, cbor.Simple(16), "\xf0") // simple(16) + expect_encoding(t, cbor.Simple(255), "\xf8\xff") // simple(255) +} + +@(test) +test_decode_floats :: proc(t: ^testing.T) { + expect_float(t, "\xf9\x00\x00", f16(0.0)) + expect_float(t, "\xf9\x80\x00", f16(-0.0)) + expect_float(t, "\xf9\x3c\x00", f16(1.0)) + expect_float(t, "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a", f64(1.1)) + expect_float(t, "\xf9\x3e\x00", f16(1.5)) + expect_float(t, "\xf9\x7b\xff", f16(65504.0)) + expect_float(t, "\xfa\x47\xc3\x50\x00", f32(100000.0)) + expect_float(t, "\xfa\x7f\x7f\xff\xff", f32(3.4028234663852886e+38)) + expect_float(t, "\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c", f64(1.0e+300)) + expect_float(t, "\xf9\x00\x01", f16(5.960464477539063e-8)) + expect_float(t, "\xf9\x04\x00", f16(0.00006103515625)) + expect_float(t, "\xf9\xc4\x00", f16(-4.0)) + expect_float(t, "\xfb\xc0\x10\x66\x66\x66\x66\x66\x66", f64(-4.1)) + expect_decoding(t, "\xf9\x7c\x00", "+Inf", f16) + expect_decoding(t, "\xf9\x7e\x00", "NaN", f16) + expect_decoding(t, "\xf9\xfc\x00", "-Inf", f16) + expect_decoding(t, "\xfa\x7f\x80\x00\x00", "+Inf", f32) + expect_decoding(t, "\xfa\x7f\xc0\x00\x00", "NaN", f32) + expect_decoding(t, "\xfa\xff\x80\x00\x00", "-Inf", f32) + expect_decoding(t, "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00", "+Inf", f64) + expect_decoding(t, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00", "NaN", f64) + expect_decoding(t, "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00", "-Inf", f64) +} + +@(test) +test_encode_floats :: proc(t: ^testing.T) { + expect_encoding(t, f16(0.0), "\xf9\x00\x00") + expect_encoding(t, f16(-0.0), "\xf9\x80\x00") + expect_encoding(t, f16(1.0), "\xf9\x3c\x00") + expect_encoding(t, f64(1.1), "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a") + expect_encoding(t, f16(1.5), "\xf9\x3e\x00") + expect_encoding(t, f16(65504.0), "\xf9\x7b\xff") + expect_encoding(t, f32(100000.0), "\xfa\x47\xc3\x50\x00") + expect_encoding(t, f32(3.4028234663852886e+38), "\xfa\x7f\x7f\xff\xff") + expect_encoding(t, f64(1.0e+300), "\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c") + expect_encoding(t, f16(5.960464477539063e-8), "\xf9\x00\x01") + expect_encoding(t, f16(0.00006103515625), "\xf9\x04\x00") + expect_encoding(t, f16(-4.0), "\xf9\xc4\x00") + expect_encoding(t, f64(-4.1), "\xfb\xc0\x10\x66\x66\x66\x66\x66\x66") +} + +@(test) +test_decode_bytes :: proc(t: ^testing.T) { + expect_decoding(t, "\x40", "h''", ^cbor.Bytes) + expect_decoding(t, "\x44\x01\x02\x03\x04", "h'1234'", ^cbor.Bytes) + + // Indefinite lengths + + expect_decoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", "h'12345'", ^cbor.Bytes) +} + +@(test) +test_encode_bytes :: proc(t: ^testing.T) { + expect_encoding(t, &cbor.Bytes{}, "\x40") + expect_encoding(t, &cbor.Bytes{1, 2, 3, 4}, "\x44\x01\x02\x03\x04") + + // Indefinite lengths + + expect_streamed_encoding(t, "\x5f\x42\x01\x02\x43\x03\x04\x05\xff", &cbor.Bytes{1, 2}, &cbor.Bytes{3, 4, 5}) +} + +@(test) +test_decode_strings :: proc(t: ^testing.T) { + expect_decoding(t, "\x60", `""`, ^cbor.Text) + expect_decoding(t, "\x61\x61", `"a"`, ^cbor.Text) + expect_decoding(t, "\x64\x49\x45\x54\x46", `"IETF"`, ^cbor.Text) + expect_decoding(t, "\x62\x22\x5c", `""\"`, ^cbor.Text) + expect_decoding(t, "\x62\xc3\xbc", `"ü"`, ^cbor.Text) + expect_decoding(t, "\x63\xe6\xb0\xb4", `"水"`, ^cbor.Text) + expect_decoding(t, "\x64\xf0\x90\x85\x91", `"𐅑"`, ^cbor.Text) + + // Indefinite lengths + + expect_decoding(t, "\x7f\x65\x73\x74\x72\x65\x61\x64\x6d\x69\x6e\x67\xff", `"streaming"`, ^cbor.Text) +} + +@(test) +test_encode_strings :: proc(t: ^testing.T) { + expect_encoding(t, &cbor.Text{}, "\x60") + + a := "a" + expect_encoding(t, &a, "\x61\x61") + + b := "IETF" + expect_encoding(t, &b, "\x64\x49\x45\x54\x46") + + c := "\"\\" + expect_encoding(t, &c, "\x62\x22\x5c") + + d := "ü" + expect_encoding(t, &d, "\x62\xc3\xbc") + + e := "水" + expect_encoding(t, &e, "\x63\xe6\xb0\xb4") + + f := "𐅑" + expect_encoding(t, &f, "\x64\xf0\x90\x85\x91") + + // Indefinite lengths + + sa := "strea" + sb := "ming" + expect_streamed_encoding(t, "\x7f\x65\x73\x74\x72\x65\x61\x64\x6d\x69\x6e\x67\xff", &sa, &sb) +} + +@(test) +test_decode_lists :: proc(t: ^testing.T) { + expect_decoding(t, "\x80", "[]", ^cbor.Array) + expect_decoding(t, "\x83\x01\x02\x03", "[1, 2, 3]", ^cbor.Array) + expect_decoding(t, "\x83\x01\x82\x02\x03\x82\x04\x05", "[1, [2, 3], [4, 5]]", ^cbor.Array) + expect_decoding(t, "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19", "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", ^cbor.Array) + expect_decoding(t, "\x82\x61\x61\xa1\x61\x62\x61\x63", `["a", {"b": "c"}]`, ^cbor.Array) + + // Indefinite lengths + + expect_decoding(t, "\x9f\xff", "[]", ^cbor.Array) + expect_decoding(t, "\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array) + expect_decoding(t, "\x9f\x01\x82\x02\x03\x82\x04\x05\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array) + expect_decoding(t, "\x83\x01\x82\x02\x03\x9f\x04\x05\xff", "[1, [2, 3], [4, 5]]", ^cbor.Array) + expect_decoding(t, "\x83\x01\x9f\x02\x03\xff\x82\x04\x05", "[1, [2, 3], [4, 5]]", ^cbor.Array) + expect_decoding(t, "\x9f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19\xff", "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", ^cbor.Array) + expect_decoding(t, "\x82\x61\x61\xbf\x61\x62\x61\x63\xff", `["a", {"b": "c"}]`, ^cbor.Array) +} + +@(test) +test_encode_lists :: proc(t: ^testing.T) { + expect_encoding(t, &cbor.Array{}, "\x80") + expect_encoding(t, &cbor.Array{u8(1), u8(2), u8(3)}, "\x83\x01\x02\x03") + expect_encoding(t, &cbor.Array{u8(1), &cbor.Array{u8(2), u8(3)}, &cbor.Array{u8(4), u8(5)}}, "\x83\x01\x82\x02\x03\x82\x04\x05") + expect_encoding(t, &cbor.Array{u8(1), u8(2), u8(3), u8(4), u8(5), u8(6), u8(7), u8(8), u8(9), u8(10), u8(11), u8(12), u8(13), u8(14), u8(15), u8(16), u8(17), u8(18), u8(19), u8(20), u8(21), u8(22), u8(23), u8(24), u8(25)}, "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19") + + { + a := "a" + b := "b" + c := "c" + expect_encoding(t, &cbor.Array{&a, &cbor.Map{{&b, &c}}}, "\x82\x61\x61\xa1\x61\x62\x61\x63") + } + + // Indefinite lengths + + expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{}) + + { + bytes.buffer_reset(&buf) + + err: cbor.Encode_Error + err = cbor.encode_stream_begin(stream, .Array) + testing.expect_value(t, err, nil) + + { + err = cbor.encode_stream_array_item(encoder, u8(1)) + testing.expect_value(t, err, nil) + + err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)}) + testing.expect_value(t, err, nil) + + err = cbor.encode_stream_begin(stream, .Array) + testing.expect_value(t, err, nil) + + { + err = cbor.encode_stream_array_item(encoder, u8(4)) + testing.expect_value(t, err, nil) + + err = cbor.encode_stream_array_item(encoder, u8(5)) + testing.expect_value(t, err, nil) + } + + err = cbor.encode_stream_end(stream) + testing.expect_value(t, err, nil) + } + + err = cbor.encode_stream_end(stream) + testing.expect_value(t, err, nil) + + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) + } + + { + bytes.buffer_reset(&buf) + + err: cbor.Encode_Error + err = cbor._encode_u8(stream, 2, .Array) + testing.expect_value(t, err, nil) + + a := "a" + err = cbor.encode(encoder, &a) + testing.expect_value(t, err, nil) + + { + err = cbor.encode_stream_begin(stream, .Map) + testing.expect_value(t, err, nil) + + b := "b" + c := "c" + err = cbor.encode_stream_map_entry(encoder, &b, &c) + testing.expect_value(t, err, nil) + + err = cbor.encode_stream_end(stream) + testing.expect_value(t, err, nil) + } + + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) + } +} + +@(test) +test_decode_maps :: proc(t: ^testing.T) { + expect_decoding(t, "\xa0", "{}", ^cbor.Map) + expect_decoding(t, "\xa2\x01\x02\x03\x04", "{1: 2, 3: 4}", ^cbor.Map) + expect_decoding(t, "\xa2\x61\x61\x01\x61\x62\x82\x02\x03", `{"a": 1, "b": [2, 3]}`, ^cbor.Map) + expect_decoding(t, "\xa5\x61\x61\x61\x41\x61\x62\x61\x42\x61\x63\x61\x43\x61\x64\x61\x44\x61\x65\x61\x45", `{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E"}`, ^cbor.Map) + + // Indefinite lengths + + expect_decoding(t, "\xbf\x61\x61\x01\x61\x62\x9f\x02\x03\xff\xff", `{"a": 1, "b": [2, 3]}`, ^cbor.Map) + expect_decoding(t, "\xbf\x63\x46\x75\x6e\xf5\x63\x41\x6d\x74\x21\xff", `{"Fun": true, "Amt": -2}`, ^cbor.Map) +} + +@(test) +test_encode_maps :: proc(t: ^testing.T) { + expect_encoding(t, &cbor.Map{}, "\xa0") + expect_encoding(t, &cbor.Map{{u8(1), u8(2)}, {u8(3), u8(4)}}, "\xa2\x01\x02\x03\x04") + + a := "a" + b := "b" + // NOTE: also tests the deterministic nature because it has to swap/sort the entries. + expect_encoding(t, &cbor.Map{{&b, &cbor.Array{u8(2), u8(3)}}, {&a, u8(1)}}, "\xa2\x61\x61\x01\x61\x62\x82\x02\x03") + + fun := "Fun" + amt := "Amt" + expect_streamed_encoding(t, "\xbf\x63\x46\x75\x6e\xf5\x63\x41\x6d\x74\x21\xff", &cbor.Map{{&fun, true}, {&amt, cbor.Negative_U8(1)}}) +} + +@(test) +test_decode_tags :: proc(t: ^testing.T) { + // Tag number 2 (unsigned bignumber), value bytes, max(u64) + 1. + expect_tag(t, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_UNSIGNED_BIG_NR, "2(h'100000000')") + + // Tag number 3 (negative bignumber), value bytes, negative max(u64) - 1. + expect_tag(t, "\xc3\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", cbor.TAG_NEGATIVE_BIG_NR, "3(h'100000000')") + + expect_tag(t, "\xc1\x1a\x51\x4b\x67\xb0", cbor.TAG_EPOCH_TIME_NR, "1(1363896240)") + expect_tag(t, "\xc1\xfb\x41\xd4\x52\xd9\xec\x20\x00\x00", cbor.TAG_EPOCH_TIME_NR, "1(1363896240.5000000000000000)") + expect_tag(t, "\xd8\x18\x45\x64\x49\x45\x54\x46", cbor.TAG_CBOR_NR, "24(h'6449455446')") +} + +@(test) +test_encode_tags :: proc(t: ^testing.T) { + expect_encoding(t, &cbor.Tag{cbor.TAG_UNSIGNED_BIG_NR, &cbor.Bytes{1, 0, 0, 0, 0, 0, 0, 0, 0}}, "\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00") + expect_encoding(t, &cbor.Tag{cbor.TAG_EPOCH_TIME_NR, u32(1363896240)}, "\xc1\x1a\x51\x4b\x67\xb0") + expect_encoding(t, &cbor.Tag{cbor.TAG_EPOCH_TIME_NR, f64(1363896240.500)}, "\xc1\xfb\x41\xd4\x52\xd9\xec\x20\x00\x00") +} + +// Helpers + +buf: bytes.Buffer +stream := bytes.buffer_to_stream(&buf) +encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream} + +expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: typeid, loc := #caller_location) { + bytes.buffer_reset(&buf) + bytes.buffer_write_string(&buf, encoded) + + res, err := cbor.decode(stream) + defer cbor.destroy(res) + + testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) + testing.expect_value(t, err, nil, loc) + + str := cbor.diagnose(res, padding=-1) + defer delete(str) + + testing.expect_value(t, str, decoded, loc) +} + +expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { + bytes.buffer_reset(&buf) + bytes.buffer_write_string(&buf, encoded) + + res, err := cbor.decode(stream) + defer cbor.destroy(res) + + testing.expect_value(t, err, nil, loc) + + if tag, is_tag := res.(^cbor.Tag); is_tag { + testing.expect_value(t, tag.number, nr, loc) + + str := cbor.diagnose(tag, padding=-1) + defer delete(str) + + testing.expect_value(t, str, value_decoded, loc) + } else { + testing.errorf(t, "Value %#v is not a tag", res, loc) + } +} + +expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #caller_location) where intrinsics.type_is_float(T) { + bytes.buffer_reset(&buf) + bytes.buffer_write_string(&buf, encoded) + + res, err := cbor.decode(stream) + defer cbor.destroy(res) + + testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) + testing.expect_value(t, err, nil, loc) + + #partial switch r in res { + case f16: + when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + case f32: + when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + case f64: + when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + case: + unreachable() + } +} + +expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { + bytes.buffer_reset(&buf) + + err := cbor.encode(encoder, val) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) +} + +expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) { + bytes.buffer_reset(&buf) + + for value, i in values { + err: cbor.Encode_Error + err2: cbor.Encode_Error + #partial switch v in value { + case ^cbor.Bytes: + if i == 0 { err = cbor.encode_stream_begin(stream, .Bytes) } + err2 = cbor._encode_bytes(encoder, v^) + case ^cbor.Text: + if i == 0 { err = cbor.encode_stream_begin(stream, .Text) } + err2 = cbor._encode_text(encoder, v^) + case ^cbor.Array: + if i == 0 { err = cbor.encode_stream_begin(stream, .Array) } + for item in v { + err2 = cbor.encode_stream_array_item(encoder, item) + if err2 != nil { break } + } + case ^cbor.Map: + err = cbor.encode_stream_begin(stream, .Map) + for item in v { + err2 = cbor.encode_stream_map_entry(encoder, item.key, item.value) + if err2 != nil { break } + } + case: + testing.errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) + } + + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err2, nil, loc) + } + + err := cbor.encode_stream_end(stream) + testing.expect_value(t, err, nil, loc) + + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) +} From b6c47e796390924faabd236204bc620ea35c1d13 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 16 Dec 2023 21:40:41 +0100 Subject: [PATCH 005/171] encoding/base64: add decode_into, add tests --- core/encoding/base64/base64.odin | 139 +++++++++++++++++-------- tests/core/Makefile | 3 + tests/core/build.bat | 2 + tests/core/encoding/base64/base64.odin | 60 +++++++++++ 4 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 tests/core/encoding/base64/base64.odin diff --git a/core/encoding/base64/base64.odin b/core/encoding/base64/base64.odin index 793f22c57..535d457d5 100644 --- a/core/encoding/base64/base64.odin +++ b/core/encoding/base64/base64.odin @@ -44,21 +44,48 @@ DEC_TABLE := [128]int { } encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error { - out_length := encoded_length(data) + out_length := encoded_len(data) if out_length == 0 { return } - out: strings.Builder - strings.builder_init(&out, 0, out_length, allocator) or_return - + out := strings.builder_make(0, out_length, allocator) or_return ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL) - assert(ioerr == nil) + + assert(ioerr == nil, "string builder should not IO error") + assert(strings.builder_cap(out) == out_length, "buffer resized, `encoded_len` was wrong") return strings.to_string(out), nil } -encoded_length :: #force_inline proc(data: []byte) -> int { +encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Error { + length := len(data) + if length == 0 { + return nil + } + + c0, c1, c2, block: int + out: [4]byte + for i := 0; i < length; i += 3 { + #no_bounds_check { + c0, c1, c2 = int(data[i]), -1, -1 + + if i + 1 < length { c1 = int(data[i + 1]) } + if i + 2 < length { c2 = int(data[i + 2]) } + + block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) + + out[0] = ENC_TBL[block >> 18 & 63] + out[1] = ENC_TBL[block >> 12 & 63] + out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] + out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] + } + io.write_full(w, out[:]) or_return + } + return nil +} + +encoded_len :: proc(data: []byte) -> int { length := len(data) if length == 0 { return 0 @@ -67,48 +94,30 @@ encoded_length :: #force_inline proc(data: []byte) -> int { return ((4 * length / 3) + 3) &~ 3 } -encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> (err: io.Error) #no_bounds_check { - length := len(data) - if length == 0 { - return - } +decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (decoded: []byte, err: mem.Allocator_Error) #optional_allocator_error { + out_length := decoded_len(data) - c0, c1, c2, block: int + out := strings.builder_make(0, out_length, allocator) or_return + ioerr := decode_into(strings.to_stream(&out), data, DEC_TBL) - for i, d := 0, 0; i < length; i, d = i + 3, d + 4 { - c0, c1, c2 = int(data[i]), -1, -1 + assert(ioerr == nil, "string builder should not IO error") + assert(strings.builder_cap(out) == out_length, "buffer resized, `decoded_len` was wrong") - if i + 1 < length { c1 = int(data[i + 1]) } - if i + 2 < length { c2 = int(data[i + 2]) } - - block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) - - out: [4]byte - out[0] = ENC_TBL[block >> 18 & 63] - out[1] = ENC_TBL[block >> 12 & 63] - out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] - out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] - - #bounds_check { io.write_full(w, out[:]) or_return } - } - return + return out.buf[:], nil } -decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (out: []byte, err: mem.Allocator_Error) #optional_allocator_error { - #no_bounds_check { - length := len(data) - if length == 0 { - return - } +decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Error { + length := decoded_len(data) + if length == 0 { + return nil + } - pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0 - out_length := ((length * 6) >> 3) - pad_count - out = make([]byte, out_length, allocator) or_return - - c0, c1, c2, c3: int - b0, b1, b2: int - - for i, j := 0, 0; i < length; i, j = i + 4, j + 3 { + c0, c1, c2, c3: int + b0, b1, b2: int + buf: [3]byte + i, j: int + for ; j + 3 <= length; i, j = i + 4, j + 3 { + #no_bounds_check { c0 = DEC_TBL[data[i]] c1 = DEC_TBL[data[i + 1]] c2 = DEC_TBL[data[i + 2]] @@ -118,10 +127,48 @@ decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocato b1 = (c1 << 4) | (c2 >> 2) b2 = (c2 << 6) | c3 - out[j] = byte(b0) - out[j + 1] = byte(b1) - out[j + 2] = byte(b2) + buf[0] = byte(b0) + buf[1] = byte(b1) + buf[2] = byte(b2) } - return + + io.write_full(w, buf[:]) or_return } + + rest := length - j + if rest > 0 { + #no_bounds_check { + c0 = DEC_TBL[data[i]] + c1 = DEC_TBL[data[i + 1]] + c2 = DEC_TBL[data[i + 2]] + + b0 = (c0 << 2) | (c1 >> 4) + b1 = (c1 << 4) | (c2 >> 2) + } + + switch rest { + case 1: io.write_byte(w, byte(b0)) or_return + case 2: io.write_full(w, {byte(b0), byte(b1)}) or_return + } + } + + return nil +} + +decoded_len :: proc(data: string) -> int { + length := len(data) + if length == 0 { + return 0 + } + + padding: int + if data[length - 1] == PADDING { + if length > 1 && data[length - 2] == PADDING { + padding = 2 + } else { + padding = 1 + } + } + + return ((length * 6) >> 3) - padding } diff --git a/tests/core/Makefile b/tests/core/Makefile index 1fca7bf97..3fa38cd34 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -51,11 +51,14 @@ noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise encoding_test: +<<<<<<< HEAD $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml $(ODIN) run encoding/cbor $(COMMON) -out:test_cbor + $(ODIN) run encoding/hex $(COMMON) -out:test_hex + $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 math_test: $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math diff --git a/tests/core/build.bat b/tests/core/build.bat index 5bf8e1ead..b9fc4e828 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -41,6 +41,8 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b %PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b +%PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b +%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b echo --- echo Running core:math/noise tests diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin new file mode 100644 index 000000000..41dbba683 --- /dev/null +++ b/tests/core/encoding/base64/base64.odin @@ -0,0 +1,60 @@ +package test_encoding_base64 + +import "core:encoding/base64" +import "core:fmt" +import "core:intrinsics" +import "core:os" +import "core:reflect" +import "core:testing" + +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect_value :: testing.expect_value + +} else { + expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { + TEST_count += 1 + ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) + if !ok { + TEST_fail += 1 + fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) + } + return ok + } +} + +main :: proc() { + t := testing.T{} + + test_encoding(&t) + test_decoding(&t) + + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + if TEST_fail > 0 { + os.exit(1) + } +} + +@(test) +test_encoding :: proc(t: ^testing.T) { + expect_value(t, base64.encode(transmute([]byte)string("")), "") + expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==") + expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=") + expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v") + expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==") + expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=") + expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy") +} + +@(test) +test_decoding :: proc(t: ^testing.T) { + expect_value(t, string(base64.decode("")), "") + expect_value(t, string(base64.decode("Zg==")), "f") + expect_value(t, string(base64.decode("Zm8=")), "fo") + expect_value(t, string(base64.decode("Zm9v")), "foo") + expect_value(t, string(base64.decode("Zm9vYg==")), "foob") + expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba") + expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar") +} From 363769d4d3de601a64e7e4bd1e6b0e744c75671c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 16 Dec 2023 21:42:06 +0100 Subject: [PATCH 006/171] encoding/cbor: cleanup base64 tag --- core/encoding/cbor/tags.odin | 112 +++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 54bc7dd15..ef3ef45f2 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -213,20 +213,20 @@ tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_E // is uninitialized (which we checked). is_neg, err := big.is_negative(&vv, mem.panic_allocator()) - assert(err == nil, "only errors if not initialized, which has been checked") + assert(err == nil, "should only error if not initialized, which has been checked") tnr: u8 = TAG_NEGATIVE_BIG_NR if is_neg else TAG_UNSIGNED_BIG_NR _encode_u8(e.writer, tnr, .Tag) or_return size_in_bytes, berr := big.int_to_bytes_size(&vv, false, mem.panic_allocator()) - assert(berr == nil, "only errors if not initialized, which has been checked") + assert(berr == nil, "should only error if not initialized, which has been checked") assert(size_in_bytes >= 0) err_conv(_encode_u64(e, u64(size_in_bytes), .Bytes)) or_return for offset := (size_in_bytes*8)-8; offset >= 0; offset -= 8 { bits, derr := big.int_bitfield_extract(&vv, offset, 8, mem.panic_allocator()) - assert(derr == nil, "only errors if not initialized or invalid argument (offset and count), which won't happen") + assert(derr == nil, "should only error if not initialized or invalid argument (offset and count), which won't happen") io.write_full(e.writer, {u8(bits & 255)}) or_return } @@ -273,63 +273,75 @@ tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_ } } -// NOTE: this could probably be more efficient by decoding bytes from CBOR and then from base64 at the same time. @(private) tag_base64_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { - hdr := _decode_header(r) or_return + hdr := _decode_header(r) or_return major, add := _header_split(hdr) - #partial switch major { - case .Text: - ti := reflect.type_info_base(type_info_of(v.id)) - _unmarshal_bytes(r, v, ti, hdr, add) or_return - #partial switch t in ti.variant { - case runtime.Type_Info_String: - switch t.is_cstring { - case true: - str := string((^cstring)(v.data)^) - decoded := base64.decode(str) or_return - (^cstring)(v.data)^ = strings.clone_to_cstring(string(decoded)) or_return - delete(decoded) - delete(str) - case false: - str := (^string)(v.data)^ - decoded := base64.decode(str) or_return - (^string)(v.data)^ = string(decoded) - delete(str) - } - return + ti := reflect.type_info_base(type_info_of(v.id)) - case runtime.Type_Info_Array: - raw := ([^]byte)(v.data) - decoded := base64.decode(string(raw[:t.count])) or_return - copy(raw[:t.count], decoded) - delete(decoded) - return + if major != .Text && major != .Bytes { + return .Bad_Tag_Value + } - case runtime.Type_Info_Slice: - raw := (^[]byte)(v.data) - decoded := base64.decode(string(raw^)) or_return - delete(raw^) - raw^ = decoded - return + bytes: string; { + context.allocator = context.temp_allocator + bytes = string(err_conv(_decode_bytes(r, add)) or_return) + } + defer delete(bytes, context.temp_allocator) - case runtime.Type_Info_Dynamic_Array: - raw := (^mem.Raw_Dynamic_Array)(v.data) - str := string(((^[dynamic]byte)(v.data)^)[:]) + #partial switch t in ti.variant { + case reflect.Type_Info_String: - decoded := base64.decode(str) or_return - delete(str) + if t.is_cstring { + length := base64.decoded_len(bytes) + builder := strings.builder_make(0, length+1) + base64.decode_into(strings.to_stream(&builder), bytes) or_return - raw.data = raw_data(decoded) - raw.len = len(decoded) - raw.cap = len(decoded) - return - - case: unreachable() + raw := (^cstring)(v.data) + raw^ = cstring(raw_data(builder.buf)) + } else { + raw := (^string)(v.data) + raw^ = string(base64.decode(bytes) or_return) } - case: return .Bad_Tag_Value + return + + case reflect.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + raw := (^[]byte)(v.data) + raw^ = base64.decode(bytes) or_return + return + + case reflect.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + decoded := base64.decode(bytes) or_return + + raw := (^mem.Raw_Dynamic_Array)(v.data) + raw.data = raw_data(decoded) + raw.len = len(decoded) + raw.cap = len(decoded) + raw.allocator = context.allocator + return + + case reflect.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + if base64.decoded_len(bytes) > t.count { return _unsupported(v, hdr) } + + slice := ([^]byte)(v.data)[:len(bytes)] + copy(slice, base64.decode(bytes) or_return) + return } + + return _unsupported(v, hdr) } @(private) @@ -355,7 +367,7 @@ tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marsha } } - out_len := base64.encoded_length(bytes) + out_len := base64.encoded_len(bytes) err_conv(_encode_u64(e, u64(out_len), .Text)) or_return return base64.encode_into(e.writer, bytes) } From d77ae9ababb539e7b48258c94c3b55fc46e62919 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 16 Dec 2023 21:42:33 +0100 Subject: [PATCH 007/171] encoding/cbor: fully support marshal/unmarshal of unions --- core/encoding/cbor/marshal.odin | 26 +- core/encoding/cbor/tags.odin | 9 + core/encoding/cbor/unmarshal.odin | 76 +++++- tests/core/encoding/cbor/test_core_cbor.odin | 260 ++++++++++++++++--- 4 files changed, 325 insertions(+), 46 deletions(-) diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index aab2defb2..a5d5efb3e 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -506,8 +506,32 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { if v.data == nil || tag <= 0 { return _encode_nil(e.writer) } + id := info.variants[tag-1].id - return marshal_into(e, any{v.data, id}) + if len(info.variants) == 1 { + id := info.variants[tag-1].id + return marshal_into(e, any{v.data, id}) + } + + // Encode a non-nil multi-variant union as the `TAG_OBJECT_TYPE`. + // Which is a tag of an array, where the first element is the textual id/type of the object + // that follows it. + + err_conv(_encode_u16(e, TAG_OBJECT_TYPE, .Tag)) or_return + _encode_u8(e.writer, 2, .Array) or_return + + vti := reflect.union_variant_type_info(v) + #partial switch vt in vti.variant { + case reflect.Type_Info_Named: + err_conv(_encode_text(e, vt.name)) or_return + case: + builder := strings.builder_make(context.temp_allocator) or_return + defer strings.builder_destroy(&builder) + reflect.write_type(&builder, vti) + err_conv(_encode_text(e, strings.to_string(builder))) or_return + } + + return marshal_into(e, any{v.data, vti.id}) case runtime.Type_Info_Enum: return marshal_into(e, any{v.data, info.base.id}) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index ef3ef45f2..509896d22 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -38,6 +38,15 @@ TAG_BASE64_ID :: "base64" // given content is definitely CBOR. TAG_SELF_DESCRIBED_CBOR :: 55799 +// A tag that is used to assign a textual type to the object following it. +// The tag's value must be an array of 2 items, where the first is text (describing the following type) +// and the second is any valid CBOR value. +// +// See the registration: https://datatracker.ietf.org/doc/draft-rundgren-cotx/05/ +// +// We use this in Odin to marshal and unmarshal unions. +TAG_OBJECT_TYPE :: 1010 + // A tag implementation that handles marshals and unmarshals for the tag it is registered on. Tag_Implementation :: struct { data: rawptr, diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 0da8e3f2a..c3ab6f908 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -8,9 +8,6 @@ import "core:runtime" import "core:strings" import "core:unicode/utf8" -// `strings` is only used in poly procs, but -vet thinks it is fully unused. -_ :: strings - /* Unmarshals the given CBOR into the given pointer using reflection. Types that require allocation are allocated using the given allocator. @@ -79,7 +76,7 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E dst = err_conv(decode(r, hdr)) or_return return } - + switch hdr { case .U8: decoded := _decode_u8(r) or_return @@ -275,10 +272,12 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E } nr := err_conv(_decode_tag_nr(r, add)) or_return - + // Custom tag implementations. if impl, ok := _tag_implementations_nr[nr]; ok { return impl->unmarshal(r, nr, v) + } else if nr == TAG_OBJECT_TYPE { + return _unmarshal_union(r, v, ti, hdr) } else { // Discard the tag info and unmarshal as its value. return _unmarshal_value(r, v, _decode_header(r) or_return) @@ -717,6 +716,73 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header } } +// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which +// contains an array of exactly two elements, the first is a textual representation of the following +// CBOR value's type. +_unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { + #partial switch t in ti.variant { + case reflect.Type_Info_Union: + idhdr: Header + target_name: string + { + vhdr := _decode_header(r) or_return + vmaj, vadd := _header_split(vhdr) + if vmaj != .Array { + return .Bad_Tag_Value + } + + n_items, unknown := err_conv(_decode_container_length(r, vadd)) or_return + if unknown || n_items != 2 { + return .Bad_Tag_Value + } + + idhdr = _decode_header(r) or_return + idmaj, idadd := _header_split(idhdr) + if idmaj != .Text { + return .Bad_Tag_Value + } + + context.allocator = context.temp_allocator + target_name = err_conv(_decode_text(r, idadd)) or_return + } + defer delete(target_name, context.temp_allocator) + + for variant, i in t.variants { + tag := i64(i) + if !t.no_nil { + tag += 1 + } + + #partial switch vti in variant.variant { + case reflect.Type_Info_Named: + if vti.name == target_name { + reflect.set_union_variant_raw_tag(v, tag) + return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return) + } + + case: + builder := strings.builder_make(context.temp_allocator) + defer strings.builder_destroy(&builder) + + reflect.write_type(&builder, variant) + variant_name := strings.to_string(builder) + + if variant_name == target_name { + reflect.set_union_variant_raw_tag(v, tag) + return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return) + } + } + } + + // No variant matched. + return _unsupported(v, idhdr) + + case: + // Not a union. + return _unsupported(v, hdr) + } +} + _assign_int :: proc(val: any, i: $T) -> bool { v := reflect.any_core(val) diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 22359d830..06b96c915 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -6,10 +6,96 @@ import "core:fmt" import "core:intrinsics" import "core:math/big" import "core:mem" +import "core:os" import "core:reflect" import "core:testing" import "core:time" +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect :: testing.expect + expect_value :: testing.expect_value + errorf :: testing.errorf + log :: testing.log + +} else { + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.printf("[%v] %v\n", loc, message) + return + } + } + + expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { + TEST_count += 1 + ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) + if !ok { + TEST_fail += 1 + fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) + } + return ok + } + + errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) { + TEST_fail += 1 + fmt.printf("[%v] ERROR: ", loc) + fmt.printf(fmts, ..args) + fmt.println() + } + + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +main :: proc() { + t := testing.T{} + + test_marshalling(&t) + + test_marshalling_maybe(&t) + test_marshalling_nil_maybe(&t) + + test_cbor_marshalling_union(&t) + + test_decode_unsigned(&t) + test_encode_unsigned(&t) + + test_decode_negative(&t) + test_encode_negative(&t) + + test_decode_simples(&t) + test_encode_simples(&t) + + test_decode_floats(&t) + test_encode_floats(&t) + + test_decode_bytes(&t) + test_encode_bytes(&t) + + test_decode_strings(&t) + test_encode_strings(&t) + + test_decode_lists(&t) + test_encode_lists(&t) + + test_decode_maps(&t) + test_encode_maps(&t) + + test_decode_tags(&t) + test_encode_tags(&t) + + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + if TEST_fail > 0 { + os.exit(1) + } +} + Foo :: struct { str: string, cstr: cstring, @@ -58,7 +144,7 @@ test_marshalling :: proc(t: ^testing.T) { context.temp_allocator = context.allocator defer mem.tracking_allocator_destroy(&tracker) - ev :: testing.expect_value + ev :: expect_value { nice := "16 is a nice number" @@ -228,7 +314,7 @@ test_marshalling :: proc(t: ^testing.T) { } } - case: testing.error(t, v) + case: errorf(t, "wrong type %v", v) } ev(t, backf.neg, f.neg) @@ -258,22 +344,116 @@ test_marshalling :: proc(t: ^testing.T) { s_equals, s_err := big.equals(&backf.smallest, &f.smallest) ev(t, s_err, nil) if !s_equals { - testing.errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) + errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) } b_equals, b_err := big.equals(&backf.biggest, &f.biggest) ev(t, b_err, nil) if !b_equals { - testing.errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) + errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) } } for _, leak in tracker.allocation_map { - testing.errorf(t, "%v leaked %m\n", leak.location, leak.size) + errorf(t, "%v leaked %m\n", leak.location, leak.size) } for bad_free in tracker.bad_free_array { - testing.errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } +} + +@(test) +test_marshalling_maybe :: proc(t: ^testing.T) { + maybe_test: Maybe(int) = 1 + data, err := cbor.marshal(maybe_test) + expect_value(t, err, nil) + + val, derr := cbor.decode(string(data)) + expect_value(t, derr, nil) + + expect_value(t, cbor.diagnose(val), "1") + + maybe_dest: Maybe(int) + uerr := cbor.unmarshal(string(data), &maybe_dest) + expect_value(t, uerr, nil) + expect_value(t, maybe_dest, 1) +} + +@(test) +test_marshalling_nil_maybe :: proc(t: ^testing.T) { + maybe_test: Maybe(int) + data, err := cbor.marshal(maybe_test) + expect_value(t, err, nil) + + val, derr := cbor.decode(string(data)) + expect_value(t, derr, nil) + + expect_value(t, cbor.diagnose(val), "nil") + + maybe_dest: Maybe(int) + uerr := cbor.unmarshal(string(data), &maybe_dest) + expect_value(t, uerr, nil) + expect_value(t, maybe_dest, nil) +} + +@(test) +test_cbor_marshalling_union :: proc(t: ^testing.T) { + My_Distinct :: distinct string + + My_Enum :: enum { + One, + Two, + } + + My_Struct :: struct { + my_enum: My_Enum, + } + + My_Union :: union { + string, + My_Distinct, + My_Struct, + int, + } + + { + test: My_Union = My_Distinct("Hello, World!") + data, err := cbor.marshal(test) + expect_value(t, err, nil) + + val, derr := cbor.decode(string(data)) + expect_value(t, derr, nil) + + expect_value(t, cbor.diagnose(val, -1), `1010(["My_Distinct", "Hello, World!"])`) + + dest: My_Union + uerr := cbor.unmarshal(string(data), &dest) + expect_value(t, uerr, nil) + expect_value(t, dest, My_Distinct("Hello, World!")) + } + + My_Union_No_Nil :: union #no_nil { + string, + My_Distinct, + My_Struct, + int, + } + + { + test: My_Union_No_Nil = My_Struct{.Two} + data, err := cbor.marshal(test) + expect_value(t, err, nil) + + val, derr := cbor.decode(string(data)) + expect_value(t, derr, nil) + + expect_value(t, cbor.diagnose(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) + + dest: My_Union_No_Nil + uerr := cbor.unmarshal(string(data), &dest) + expect_value(t, uerr, nil) + expect_value(t, dest, My_Struct{.Two}) } } @@ -500,34 +680,34 @@ test_encode_lists :: proc(t: ^testing.T) { err: cbor.Encode_Error err = cbor.encode_stream_begin(stream, .Array) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(1)) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)}) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) err = cbor.encode_stream_begin(stream, .Array) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(4)) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, u8(5)) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) - testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) + expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) } { @@ -535,26 +715,26 @@ test_encode_lists :: proc(t: ^testing.T) { err: cbor.Encode_Error err = cbor._encode_u8(stream, 2, .Array) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) a := "a" err = cbor.encode(encoder, &a) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) { err = cbor.encode_stream_begin(stream, .Map) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) b := "b" c := "c" err = cbor.encode_stream_map_entry(encoder, &b, &c) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) err = cbor.encode_stream_end(stream) - testing.expect_value(t, err, nil) + expect_value(t, err, nil) } - testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) + expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) } } @@ -619,13 +799,13 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t res, err := cbor.decode(stream) defer cbor.destroy(res) - testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) - testing.expect_value(t, err, nil, loc) + expect_value(t, reflect.union_variant_typeid(res), type, loc) + expect_value(t, err, nil, loc) str := cbor.diagnose(res, padding=-1) defer delete(str) - testing.expect_value(t, str, decoded, loc) + expect_value(t, str, decoded, loc) } expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { @@ -635,17 +815,17 @@ expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_de res, err := cbor.decode(stream) defer cbor.destroy(res) - testing.expect_value(t, err, nil, loc) + expect_value(t, err, nil, loc) if tag, is_tag := res.(^cbor.Tag); is_tag { - testing.expect_value(t, tag.number, nr, loc) + expect_value(t, tag.number, nr, loc) str := cbor.diagnose(tag, padding=-1) defer delete(str) - testing.expect_value(t, str, value_decoded, loc) + expect_value(t, str, value_decoded, loc) } else { - testing.errorf(t, "Value %#v is not a tag", res, loc) + errorf(t, "Value %#v is not a tag", res, loc) } } @@ -656,16 +836,16 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle res, err := cbor.decode(stream) defer cbor.destroy(res) - testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) - testing.expect_value(t, err, nil, loc) + expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) + expect_value(t, err, nil, loc) #partial switch r in res { case f16: - when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() } case f32: - when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() } case f64: - when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() } + when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() } case: unreachable() } @@ -675,8 +855,8 @@ expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := bytes.buffer_reset(&buf) err := cbor.encode(encoder, val) - testing.expect_value(t, err, nil, loc) - testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + expect_value(t, err, nil, loc) + expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) { @@ -705,15 +885,15 @@ expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor. if err2 != nil { break } } case: - testing.errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) + errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) } - testing.expect_value(t, err, nil, loc) - testing.expect_value(t, err2, nil, loc) + expect_value(t, err, nil, loc) + expect_value(t, err2, nil, loc) } err := cbor.encode_stream_end(stream) - testing.expect_value(t, err, nil, loc) + expect_value(t, err, nil, loc) - testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } From 21e6e28a3a5609bc4db19dd2b1bc00ff7b1ac5e5 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 16 Dec 2023 23:02:30 +0100 Subject: [PATCH 008/171] encoding/cbor: add decoder flags and protect from malicious untrusted input --- core/encoding/cbor/cbor.odin | 8 +- core/encoding/cbor/coding.odin | 279 ++++++++++++------- core/encoding/cbor/tags.odin | 32 +-- core/encoding/cbor/unmarshal.odin | 246 +++++++++------- tests/core/encoding/cbor/test_core_cbor.odin | 17 +- 5 files changed, 351 insertions(+), 231 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index e91c53f3c..9c4bb0e4e 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -10,8 +10,13 @@ import "core:strings" // If we are decoding a stream of either a map or list, the initial capacity will be this value. INITIAL_STREAMED_CONTAINER_CAPACITY :: 8 + // If we are decoding a stream of either text or bytes, the initial capacity will be this value. -INITIAL_STREAMED_BYTES_CAPACITY :: 16 +INITIAL_STREAMED_BYTES_CAPACITY :: 16 + +// The default maximum amount of bytes to allocate on a buffer/container at once to prevent +// malicious input from causing massive allocations. +DEFAULT_MAX_PRE_ALLOC :: mem.Kilobyte // Known/common headers are defined, undefined headers can still be valid. // Higher 3 bits is for the major type and lower 5 bits for the additional information. @@ -157,6 +162,7 @@ Decode_Data_Error :: enum { Nested_Indefinite_Length, // When an streamed/indefinite length container nests another, this is not allowed. Nested_Tag, // When a tag's value is another tag, this is not allowed. Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`. + Disallowed_Streaming, // When the `.Disallow_Streaming` flag is set and a streaming header is encountered. Break, } diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 5c14d8f87..e39519e01 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -33,16 +33,40 @@ Encoder_Flags :: bit_set[Encoder_Flag] // Flags for fully deterministic output (if you are not using streaming/indeterminate length). ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size, .Deterministic_Map_Sorting} + // Flags for the smallest encoding output. -ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size} -// Flags for the fastest encoding output. -ENCODE_FAST :: Encoder_Flags{} +ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size} Encoder :: struct { flags: Encoder_Flags, writer: io.Writer, } +Decoder_Flag :: enum { + // Rejects (with an error `.Disallowed_Streaming`) when a streaming CBOR header is encountered. + Disallow_Streaming, + + // Pre-allocates buffers and containers with the size that was set in the CBOR header. + // This should only be enabled when you control both ends of the encoding, if you don't, + // attackers can craft input that causes massive (`max(u64)`) byte allocations for a few bytes of + // CBOR. + Trusted_Input, + + // Makes the decoder shrink of excess capacity from allocated buffers/containers before returning. + Shrink_Excess, +} + +Decoder_Flags :: bit_set[Decoder_Flag] + +Decoder :: struct { + // The max amount of bytes allowed to pre-allocate when `.Trusted_Input` is not set on the + // flags. + max_pre_alloc: int, + + flags: Decoder_Flags, + reader: io.Reader, +} + /* Decodes both deterministic and non-deterministic CBOR into a `Value` variant. @@ -52,28 +76,60 @@ Allocations are done using the given allocator, *no* allocations are done on the `context.temp_allocator`. A value can be (fully and recursively) deallocated using the `destroy` proc in this package. + +Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag. + +Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag. + +Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature +of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only +do this when you own both sides of the encoding and are sure there can't be malicious bytes used as +an input. */ -decode :: proc { - decode_string, - decode_reader, +decode_from :: proc { + decode_from_string, + decode_from_reader, + decode_from_decoder, } +decode :: decode_from // Decodes the given string as CBOR. // See docs on the proc group `decode` for more information. -decode_string :: proc(s: string, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { context.allocator = allocator - r: strings.Reader strings.reader_init(&r, s) - return decode(strings.reader_to_stream(&r), allocator=allocator) + return decode_from_reader(strings.reader_to_stream(&r), flags) } // Reads a CBOR value from the given reader. // See docs on the proc group `decode` for more information. -decode_reader :: proc(r: io.Reader, hdr: Header = Header(0), allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { + return decode_from_decoder( + Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, + allocator=allocator, + ) +} + +// Reads a CBOR value from the given decoder. +// See docs on the proc group `decode` for more information. +decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) { context.allocator = allocator + d := d + if d.max_pre_alloc <= 0 { + d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC + } + + v, err = _decode_from_decoder(d) + // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. + if err == .EOF { err = .Unexpected_EOF } + return +} + +_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) { hdr := hdr + r := d.reader if hdr == Header(0) { hdr = _decode_header(r) or_return } switch hdr { case .U8: return _decode_u8 (r) @@ -105,11 +161,11 @@ decode_reader :: proc(r: io.Reader, hdr: Header = Header(0), allocator := contex switch maj { case .Unsigned: return _decode_tiny_u8(add) case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil - case .Bytes: return _decode_bytes_ptr(r, add) - case .Text: return _decode_text_ptr(r, add) - case .Array: return _decode_array_ptr(r, add) - case .Map: return _decode_map_ptr(r, add) - case .Tag: return _decode_tag_ptr(r, add) + case .Bytes: return _decode_bytes_ptr(d, add) + case .Text: return _decode_text_ptr(d, add) + case .Array: return _decode_array_ptr(d, add) + case .Map: return _decode_map_ptr(d, add) + case .Tag: return _decode_tag_ptr(d, add) case .Other: return _decode_tiny_simple(add) case: return nil, .Bad_Major } @@ -246,7 +302,7 @@ _encode_u8 :: proc(w: io.Writer, v: u8, major: Major = .Unsigned) -> (err: io.Er } _decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) { - if intrinsics.expect(additional < .One_Byte, true) { + if additional < .One_Byte { return u8(additional), nil } @@ -316,64 +372,53 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er return } -_decode_bytes_ptr :: proc(r: io.Reader, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { +_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { v = new(Bytes) or_return defer if err != nil { free(v) } - v^ = _decode_bytes(r, add, type) or_return + v^ = _decode_bytes(d, add, type) or_return return } -_decode_bytes :: proc(r: io.Reader, add: Add, type: Major = .Bytes) -> (v: Bytes, err: Decode_Error) { - _n_items, length_is_unknown := _decode_container_length(r, add) or_return +_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: Bytes, err: Decode_Error) { + n, scap := _decode_len_str(d, add) or_return + + buf := strings.builder_make(0, scap) or_return + defer if err != nil { strings.builder_destroy(&buf) } + buf_stream := strings.to_stream(&buf) - n_items := _n_items.? or_else INITIAL_STREAMED_BYTES_CAPACITY - - if length_is_unknown { - buf: strings.Builder - buf.buf = make([dynamic]byte, 0, n_items) or_return - defer if err != nil { strings.builder_destroy(&buf) } - - buf_stream := strings.to_stream(&buf) - - for { - header := _decode_header(r) or_return + if n == -1 { + indefinite_loop: for { + header := _decode_header(d.reader) or_return maj, add := _header_split(header) - #partial switch maj { case type: - _n_items, length_is_unknown := _decode_container_length(r, add) or_return - if length_is_unknown { + iter_n, iter_cap := _decode_len_str(d, add) or_return + if iter_n == -1 { return nil, .Nested_Indefinite_Length } - n_items := i64(_n_items.?) + reserve(&buf.buf, len(buf.buf) + iter_cap) or_return + io.copy_n(buf_stream, d.reader, i64(iter_n)) or_return - copied := io.copy_n(buf_stream, r, n_items) or_return - assert(copied == n_items) - case .Other: if add != .Break { return nil, .Bad_Argument } - - v = buf.buf[:] - - // Write zero byte so this can be converted to cstring. - io.write_full(buf_stream, {0}) or_return - shrink(&buf.buf) // Ignoring error, this is not critical to succeed. - return + break indefinite_loop case: return nil, .Bad_Major } } } else { - v = make([]byte, n_items + 1) or_return // Space for the bytes and a zero byte. - defer if err != nil { delete(v) } - - io.read_full(r, v[:n_items]) or_return - - v = v[:n_items] // Take off zero byte. - return + io.copy_n(buf_stream, d.reader, i64(n)) or_return } + + v = buf.buf[:] + + // Write zero byte so this can be converted to cstring. + strings.write_byte(&buf, 0) + + if .Shrink_Excess in d.flags { shrink(&buf.buf) } + return } _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) { @@ -383,43 +428,41 @@ _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: En return } -_decode_text_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Text, err: Decode_Error) { +_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) { v = new(Text) or_return defer if err != nil { free(v) } - v^ = _decode_text(r, add) or_return + v^ = _decode_text(d, add) or_return return } -_decode_text :: proc(r: io.Reader, add: Add) -> (v: Text, err: Decode_Error) { - return (Text)(_decode_bytes(r, add, .Text) or_return), nil +_decode_text :: proc(d: Decoder, add: Add) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(d, add, .Text) or_return), nil } _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { return _encode_bytes(e, transmute([]byte)val, .Text) } -_decode_array_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Array, err: Decode_Error) { +_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) { v = new(Array) or_return defer if err != nil { free(v) } - v^ = _decode_array(r, add) or_return + v^ = _decode_array(d, add) or_return return } -_decode_array :: proc(r: io.Reader, add: Add) -> (v: Array, err: Decode_Error) { - _n_items, length_is_unknown := _decode_container_length(r, add) or_return - n_items := _n_items.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY - - array := make([dynamic]Value, 0, n_items) or_return +_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) { + n, scap := _decode_len_container(d, add) or_return + array := make([dynamic]Value, 0, scap) or_return defer if err != nil { for entry in array { destroy(entry) } delete(array) } - for i := 0; length_is_unknown || i < n_items; i += 1 { - val, verr := decode(r) - if length_is_unknown && verr == .Break { + for i := 0; n == -1 || i < n; i += 1 { + val, verr := _decode_from_decoder(d) + if n == -1 && verr == .Break { break } else if verr != nil { err = verr @@ -428,8 +471,9 @@ _decode_array :: proc(r: io.Reader, add: Add) -> (v: Array, err: Decode_Error) { append(&array, val) or_return } + + if .Shrink_Excess in d.flags { shrink(&array) } - shrink(&array) v = array[:] return } @@ -443,19 +487,17 @@ _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error { return nil } -_decode_map_ptr :: proc(r: io.Reader, add: Add) -> (v: ^Map, err: Decode_Error) { +_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) { v = new(Map) or_return defer if err != nil { free(v) } - v^ = _decode_map(r, add) or_return + v^ = _decode_map(d, add) or_return return } -_decode_map :: proc(r: io.Reader, add: Add) -> (v: Map, err: Decode_Error) { - _n_items, length_is_unknown := _decode_container_length(r, add) or_return - n_items := _n_items.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY - - items := make([dynamic]Map_Entry, 0, n_items) or_return +_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) { + n, scap := _decode_len_container(d, add) or_return + items := make([dynamic]Map_Entry, 0, scap) or_return defer if err != nil { for entry in items { destroy(entry.key) @@ -464,23 +506,24 @@ _decode_map :: proc(r: io.Reader, add: Add) -> (v: Map, err: Decode_Error) { delete(items) } - for i := 0; length_is_unknown || i < n_items; i += 1 { - key, kerr := decode(r) - if length_is_unknown && kerr == .Break { + for i := 0; n == -1 || i < n; i += 1 { + key, kerr := _decode_from_decoder(d) + if n == -1 && kerr == .Break { break } else if kerr != nil { return nil, kerr } - value := decode(r) or_return + value := decode_from_decoder(d) or_return append(&items, Map_Entry{ key = key, value = value, }) or_return } + + if .Shrink_Excess in d.flags { shrink(&items) } - shrink(&items) v = items[:] return } @@ -537,8 +580,8 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { return nil } -_decode_tag_ptr :: proc(r: io.Reader, add: Add) -> (v: Value, err: Decode_Error) { - tag := _decode_tag(r, add) or_return +_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) { + tag := _decode_tag(d, add) or_return if t, ok := tag.?; ok { defer if err != nil { destroy(t.value) } tp := new(Tag) or_return @@ -547,11 +590,11 @@ _decode_tag_ptr :: proc(r: io.Reader, add: Add) -> (v: Value, err: Decode_Error) } // no error, no tag, this was the self described CBOR tag, skip it. - return decode(r) + return _decode_from_decoder(d) } -_decode_tag :: proc(r: io.Reader, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { - num := _decode_tag_nr(r, add) or_return +_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { + num := _decode_uint_as_u64(d.reader, add) or_return // CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR. // We can ignore it here. @@ -561,7 +604,7 @@ _decode_tag :: proc(r: io.Reader, add: Add) -> (v: Maybe(Tag), err: Decode_Error t := Tag{ number = num, - value = decode(r) or_return, + value = _decode_from_decoder(d) or_return, } if nested, ok := t.value.(^Tag); ok { @@ -572,7 +615,7 @@ _decode_tag :: proc(r: io.Reader, add: Add) -> (v: Maybe(Tag), err: Decode_Error return t, nil } -_decode_tag_nr :: proc(r: io.Reader, add: Add) -> (nr: Tag_Number, err: Decode_Error) { +_decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Error) { #partial switch add { case .One_Byte: return u64(_decode_u8(r) or_return), nil case .Two_Bytes: return u64(_decode_u16(r) or_return), nil @@ -719,30 +762,50 @@ encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Er return encode(e, val) } -// - -_decode_container_length :: proc(r: io.Reader, add: Add) -> (length: Maybe(int), is_unknown: bool, err: Decode_Error) { - if add == Add.Length_Unknown { return nil, true, nil } - #partial switch add { - case .One_Byte: length = int(_decode_u8(r) or_return) - case .Two_Bytes: length = int(_decode_u16(r) or_return) - case .Four_Bytes: - big_length := _decode_u32(r) or_return - if u64(big_length) > u64(max(int)) { - err = .Length_Too_Big - return +// For `Bytes` and `Text` strings: Decodes the number of items the header says follows. +// If the number is not specified -1 is returned and streaming should be initiated. +// A suitable starting capacity is also returned for a buffer that is allocated up the stack. +_decode_len_str :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) { + if add == .Length_Unknown { + if .Disallow_Streaming in d.flags { + return -1, -1, .Disallowed_Streaming } - length = int(big_length) - case .Eight_Bytes: - big_length := _decode_u64(r) or_return - if big_length > u64(max(int)) { - err = .Length_Too_Big - return - } - length = int(big_length) - case: - length = int(_decode_tiny_u8(add) or_return) + return -1, INITIAL_STREAMED_BYTES_CAPACITY, nil } + + _n := _decode_uint_as_u64(d.reader, add) or_return + if _n > u64(max(int)) { return -1, -1, .Length_Too_Big } + n = int(_n) + + scap = n + 1 // Space for zero byte. + if .Trusted_Input not_in d.flags { + scap = min(d.max_pre_alloc, scap) + } + + return +} + +// For `Array` and `Map` types: Decodes the number of items the header says follows. +// If the number is not specified -1 is returned and streaming should be initiated. +// A suitable starting capacity is also returned for a buffer that is allocated up the stack. +_decode_len_container :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) { + if add == .Length_Unknown { + if .Disallow_Streaming in d.flags { + return -1, -1, .Disallowed_Streaming + } + return -1, INITIAL_STREAMED_CONTAINER_CAPACITY, nil + } + + _n := _decode_uint_as_u64(d.reader, add) or_return + if _n > u64(max(int)) { return -1, -1, .Length_Too_Big } + n = int(_n) + + scap = n + if .Trusted_Input not_in d.flags { + // NOTE: if this is a map it will be twice this. + scap = min(d.max_pre_alloc / size_of(Value), scap) + } + return } diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 509896d22..d2867e7be 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -55,7 +55,7 @@ Tag_Implementation :: struct { } // Procedure responsible for umarshalling the tag out of the reader into the given `any`. -Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, r: io.Reader, tag_nr: Tag_Number, v: any) -> Unmarshal_Error +Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, d: Decoder, tag_nr: Tag_Number, v: any) -> Unmarshal_Error // Procedure responsible for marshalling the tag in the given `any` into the given encoder. Tag_Marshal_Proc :: #type proc(self: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error @@ -121,30 +121,30 @@ tags_register_defaults :: proc() { // // See RFC 8949 section 3.4.2. @(private) -tag_time_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { - hdr := _decode_header(r) or_return +tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return #partial switch hdr { case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64: switch &dst in v { case time.Time: i: i64 - _unmarshal_any_ptr(r, &i, hdr) or_return + _unmarshal_any_ptr(d, &i, hdr) or_return dst = time.unix(i64(i), 0) return case: - return _unmarshal_value(r, v, hdr) + return _unmarshal_value(d, v, hdr) } case .F16, .F32, .F64: switch &dst in v { case time.Time: f: f64 - _unmarshal_any_ptr(r, &f, hdr) or_return + _unmarshal_any_ptr(d, &f, hdr) or_return whole, fract := math.modf(f) dst = time.unix(i64(whole), i64(fract * 1e9)) return case: - return _unmarshal_value(r, v, hdr) + return _unmarshal_value(d, v, hdr) } case: @@ -182,8 +182,8 @@ tag_time_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_ } @(private) -tag_big_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) { - hdr := _decode_header(r) or_return +tag_big_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return maj, add := _header_split(hdr) if maj != .Bytes { // Only bytes are supported in this tag. @@ -192,7 +192,7 @@ tag_big_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, tnr: Tag_Number, switch &dst in v { case big.Int: - bytes := err_conv(_decode_bytes(r, add)) or_return + bytes := err_conv(_decode_bytes(d, add)) or_return defer delete(bytes) if err := big.int_from_bytes_big(&dst, bytes); err != nil { @@ -246,13 +246,13 @@ tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_E } @(private) -tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> Unmarshal_Error { - hdr := _decode_header(r) or_return +tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> Unmarshal_Error { + hdr := _decode_header(d.reader) or_return major, add := _header_split(hdr) #partial switch major { case .Bytes: ti := reflect.type_info_base(type_info_of(v.id)) - return _unmarshal_bytes(r, v, ti, hdr, add) + return _unmarshal_bytes(d, v, ti, hdr, add) case: return .Bad_Tag_Value } @@ -283,8 +283,8 @@ tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_ } @(private) -tag_base64_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { - hdr := _decode_header(r) or_return +tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return major, add := _header_split(hdr) ti := reflect.type_info_base(type_info_of(v.id)) @@ -294,7 +294,7 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, r: io.Reader, _: Tag_Number bytes: string; { context.allocator = context.temp_allocator - bytes = string(err_conv(_decode_bytes(r, add)) or_return) + bytes = string(err_conv(_decode_bytes(d, add)) or_return) } defer delete(bytes, context.temp_allocator) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index c3ab6f908..2df99ca71 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -15,25 +15,56 @@ Types that require allocation are allocated using the given allocator. Some temporary allocations are done on the `context.temp_allocator`, but, if you want to, this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made. This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. + +Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag. + +Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag. + +Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature +of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only +do this when you own both sides of the encoding and are sure there can't be malicious bytes used as +an input. */ unmarshal :: proc { unmarshal_from_reader, unmarshal_from_string, } -// Unmarshals from a reader, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, allocator := context.allocator) -> Unmarshal_Error { - return _unmarshal_any_ptr(r, ptr, allocator=allocator) +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator) -> (err: Unmarshal_Error) { + err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator=allocator) + + // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. + if err == .EOF { err = .Unexpected_EOF } + return } // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_string :: proc(s: string, ptr: ^$T, allocator := context.allocator) -> Unmarshal_Error { +unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator) -> (err: Unmarshal_Error) { sr: strings.Reader r := strings.to_reader(&sr, s) - return _unmarshal_any_ptr(r, ptr, allocator=allocator) + + err = unmarshal_from_reader(r, ptr, flags, allocator) + + // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. + if err == .EOF { err = .Unexpected_EOF } + return } -_unmarshal_any_ptr :: proc(r: io.Reader, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator) -> Unmarshal_Error { +unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator) -> (err: Unmarshal_Error) { + d := d + if d.max_pre_alloc <= 0 { + d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC + } + + err = _unmarshal_any_ptr(d, ptr, allocator=allocator) + + // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. + if err == .EOF { err = .Unexpected_EOF } + return + +} + +_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator) -> Unmarshal_Error { context.allocator = allocator v := v @@ -48,12 +79,13 @@ _unmarshal_any_ptr :: proc(r: io.Reader, v: any, hdr: Maybe(Header) = nil, alloc } data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} - return _unmarshal_value(r, data, hdr.? or_else (_decode_header(r) or_return)) + return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return)) } -_unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) { v := v ti := reflect.type_info_base(type_info_of(v.id)) + r := d.reader // If it's a union with only one variant, then treat it as that variant if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 { @@ -73,7 +105,7 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E // Allow generic unmarshal by doing it into a `Value`. switch &dst in v { case Value: - dst = err_conv(decode(r, hdr)) or_return + dst = err_conv(_decode_from_decoder(d, hdr)) or_return return } @@ -253,7 +285,7 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E case .Tag: switch &dst in v { case ^Tag: - tval := err_conv(_decode_tag_ptr(r, add)) or_return + tval := err_conv(_decode_tag_ptr(d, add)) or_return if t, is_tag := tval.(^Tag); is_tag { dst = t return @@ -262,7 +294,7 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E destroy(tval) return .Bad_Tag_Value case Tag: - t := err_conv(_decode_tag(r, add)) or_return + t := err_conv(_decode_tag(d, add)) or_return if t, is_tag := t.?; is_tag { dst = t return @@ -271,33 +303,33 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E return .Bad_Tag_Value } - nr := err_conv(_decode_tag_nr(r, add)) or_return + nr := err_conv(_decode_uint_as_u64(r, add)) or_return // Custom tag implementations. if impl, ok := _tag_implementations_nr[nr]; ok { - return impl->unmarshal(r, nr, v) + return impl->unmarshal(d, nr, v) } else if nr == TAG_OBJECT_TYPE { - return _unmarshal_union(r, v, ti, hdr) + return _unmarshal_union(d, v, ti, hdr) } else { // Discard the tag info and unmarshal as its value. - return _unmarshal_value(r, v, _decode_header(r) or_return) + return _unmarshal_value(d, v, _decode_header(r) or_return) } return _unsupported(v, hdr, add) - case .Bytes: return _unmarshal_bytes(r, v, ti, hdr, add) - case .Text: return _unmarshal_string(r, v, ti, hdr, add) - case .Array: return _unmarshal_array(r, v, ti, hdr, add) - case .Map: return _unmarshal_map(r, v, ti, hdr, add) + case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add) + case .Text: return _unmarshal_string(d, v, ti, hdr, add) + case .Array: return _unmarshal_array(d, v, ti, hdr, add) + case .Map: return _unmarshal_map(d, v, ti, hdr, add) case: return .Bad_Major } } -_unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - bytes := err_conv(_decode_bytes(r, add)) or_return + bytes := err_conv(_decode_bytes(d, add)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -316,7 +348,7 @@ _unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(r, add)) or_return + bytes := err_conv(_decode_bytes(d, add)) or_return raw := (^mem.Raw_Slice)(v.data) raw^ = transmute(mem.Raw_Slice)bytes return @@ -326,7 +358,7 @@ _unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(r, add)) or_return + bytes := err_conv(_decode_bytes(d, add)) or_return raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(bytes) raw.len = len(bytes) @@ -339,11 +371,9 @@ _unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head if elem_base.id != byte { return _unsupported(v, hdr) } - bytes: []byte; { - context.allocator = context.temp_allocator - bytes = err_conv(_decode_bytes(r, add)) or_return - } - defer delete(bytes, context.temp_allocator) + context.allocator = context.temp_allocator + bytes := err_conv(_decode_bytes(d, add)) or_return + defer delete(bytes) if len(bytes) > t.count { return _unsupported(v, hdr) } @@ -357,10 +387,10 @@ _unmarshal_bytes :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head return _unsupported(v, hdr) } -_unmarshal_string :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - text := err_conv(_decode_text(r, add)) or_return + text := err_conv(_decode_text(d, add)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -376,8 +406,8 @@ _unmarshal_string :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Hea // Enum by its variant name. case reflect.Type_Info_Enum: context.allocator = context.temp_allocator - text := err_conv(_decode_text(r, add)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add)) or_return + defer delete(text) for name, i in t.names { if name == text { @@ -388,8 +418,8 @@ _unmarshal_string :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Hea case reflect.Type_Info_Rune: context.allocator = context.temp_allocator - text := err_conv(_decode_text(r, add)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add)) or_return + defer delete(text) r := (^rune)(v.data) dr, n := utf8.decode_rune(text) @@ -404,21 +434,19 @@ _unmarshal_string :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Hea return _unsupported(v, hdr) } -_unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { - +_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { assign_array :: proc( - r: io.Reader, + d: Decoder, da: ^mem.Raw_Dynamic_Array, elemt: ^reflect.Type_Info, - _length: Maybe(int), + length: int, growable := true, ) -> (out_of_space: bool, err: Unmarshal_Error) { - length, has_length := _length.? - for idx: uintptr = 0; !has_length || idx < uintptr(length); idx += 1 { + for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 { elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) elem := any{elem_ptr, elemt.id} - hdr := _decode_header(r) or_return + hdr := _decode_header(d.reader) or_return // Double size if out of capacity. if da.cap <= da.len { @@ -432,8 +460,8 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head if !ok { return false, .Out_Of_Memory } } - err = _unmarshal_value(r, elem, hdr) - if !has_length && err == .Break { break } + err = _unmarshal_value(d, elem, hdr) + if length == -1 && err == .Break { break } if err != nil { return } da.len += 1 @@ -445,26 +473,25 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head // Allow generically storing the values array. switch &dst in v { case ^Array: - dst = err_conv(_decode_array_ptr(r, add)) or_return + dst = err_conv(_decode_array_ptr(d, add)) or_return return case Array: - dst = err_conv(_decode_array(r, add)) or_return + dst = err_conv(_decode_array(d, add)) or_return return } #partial switch t in ti.variant { case reflect.Type_Info_Slice: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * length, t.elem.align) or_return + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return defer if err != nil { mem.free_bytes(data) } da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } - assign_array(r, &da, t.elem, _length) or_return + assign_array(d, &da, t.elem, length) or_return - if da.len < da.cap { + if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len) } @@ -475,54 +502,58 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head return case reflect.Type_Info_Dynamic_Array: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else INITIAL_STREAMED_CONTAINER_CAPACITY + length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * length, t.elem.align) or_return + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return defer if err != nil { mem.free_bytes(data) } - raw := (^mem.Raw_Dynamic_Array)(v.data) - raw.data = raw_data(data) - raw.len = 0 - raw.cap = length - raw.allocator = context.allocator + raw := (^mem.Raw_Dynamic_Array)(v.data) + raw.data = raw_data(data) + raw.len = 0 + raw.cap = length + raw.allocator = context.allocator - _ = assign_array(r, raw, t.elem, _length) or_return + _ = assign_array(d, raw, t.elem, length) or_return + + if .Shrink_Excess in d.flags { + // Ignoring an error here, but this is not critical to succeed. + _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len) + } return case reflect.Type_Info_Array: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else t.count + _length, scap := err_conv(_decode_len_container(d, add)) or_return + length := min(scap, t.count) - if !unknown && length > t.count { + if length > t.count { return _unsupported(v, hdr) } da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } - out_of_space := assign_array(r, &da, t.elem, _length, growable=false) or_return + out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return case reflect.Type_Info_Enumerated_Array: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else t.count + _length, scap := err_conv(_decode_len_container(d, add)) or_return + length := min(scap, t.count) - if !unknown && length > t.count { + if length > t.count { return _unsupported(v, hdr) } da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } - out_of_space := assign_array(r, &da, t.elem, _length, growable=false) or_return + out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return case reflect.Type_Info_Complex: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else 2 + _length, scap := err_conv(_decode_len_container(d, add)) or_return + length := min(scap, 2) - if !unknown && length > 2 { + if length > 2 { return _unsupported(v, hdr) } @@ -536,15 +567,15 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head case: unreachable() } - out_of_space := assign_array(r, &da, info, 2, growable=false) or_return + out_of_space := assign_array(d, &da, info, 2, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return case reflect.Type_Info_Quaternion: - _length, unknown := err_conv(_decode_container_length(r, add)) or_return - length := _length.? or_else 4 + _length, scap := err_conv(_decode_len_container(d, add)) or_return + length := min(scap, 4) - if !unknown && length > 4 { + if length > 4 { return _unsupported(v, hdr) } @@ -558,7 +589,7 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head case: unreachable() } - out_of_space := assign_array(r, &da, info, 4, growable=false) or_return + out_of_space := assign_array(d, &da, info, 4, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } return @@ -566,17 +597,17 @@ _unmarshal_array :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head } } -_unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { - - decode_key :: proc(r: io.Reader, v: any) -> (k: string, err: Unmarshal_Error) { - entry_hdr := _decode_header(r) or_return +_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { + r := d.reader + decode_key :: proc(d: Decoder, v: any) -> (k: string, err: Unmarshal_Error) { + entry_hdr := _decode_header(d.reader) or_return entry_maj, entry_add := _header_split(entry_hdr) #partial switch entry_maj { case .Text: - k = err_conv(_decode_text(r, entry_add)) or_return + k = err_conv(_decode_text(d, entry_add)) or_return return case .Bytes: - bytes := err_conv(_decode_bytes(r, entry_add)) or_return + bytes := err_conv(_decode_bytes(d, entry_add)) or_return k = string(bytes) return case: @@ -588,10 +619,10 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header // Allow generically storing the map array. switch &dst in v { case ^Map: - dst = err_conv(_decode_map_ptr(r, add)) or_return + dst = err_conv(_decode_map_ptr(d, add)) or_return return case Map: - dst = err_conv(_decode_map(r, add)) or_return + dst = err_conv(_decode_map(d, add)) or_return return } @@ -601,14 +632,15 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - length, unknown := err_conv(_decode_container_length(r, add)) or_return + length, scap := err_conv(_decode_len_container(d, add)) or_return + unknown := length == -1 fields := reflect.struct_fields_zipped(ti.id) - for idx := 0; unknown || idx < length.?; idx += 1 { + for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 { // Decode key, keys can only be strings. key: string; { context.allocator = context.temp_allocator - if keyv, kerr := decode_key(r, v); unknown && kerr == .Break { + if keyv, kerr := decode_key(d, v); unknown && kerr == .Break { break } else if kerr != nil { err = kerr @@ -641,11 +673,11 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header } } - field := fields[use_field_idx] - name := field.name - ptr := rawptr(uintptr(v.data) + field.offset) - fany := any{ptr, field.type.id} - _unmarshal_value(r, fany, _decode_header(r) or_return) or_return + field := fields[use_field_idx] + name := field.name + ptr := rawptr(uintptr(v.data) + field.offset) + fany := any{ptr, field.type.id} + _unmarshal_value(d, fany, _decode_header(r) or_return) or_return } return @@ -654,6 +686,8 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } + // TODO: shrink excess. + raw_map := (^mem.Raw_Map)(v.data) if raw_map.allocator.procedure == nil { raw_map.allocator = context.allocator @@ -663,10 +697,11 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header _ = runtime.map_free_dynamic(raw_map^, t.map_info) } - length, unknown := err_conv(_decode_container_length(r, add)) or_return + length, scap := err_conv(_decode_len_container(d, add)) or_return + unknown := length == -1 if !unknown { // Reserve space before setting so we can return allocation errors and be efficient on big maps. - new_len := uintptr(runtime.map_len(raw_map^)+length.?) + new_len := uintptr(min(scap, runtime.map_len(raw_map^)+length)) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } @@ -676,10 +711,10 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header map_backing_value := any{raw_data(elem_backing), t.value.id} - for idx := 0; unknown || idx < length.?; idx += 1 { + for idx := 0; unknown || idx < length; idx += 1 { // Decode key, keys can only be strings. key: string - if keyv, kerr := decode_key(r, v); unknown && kerr == .Break { + if keyv, kerr := decode_key(d, v); unknown && kerr == .Break { break } else if kerr != nil { err = kerr @@ -688,14 +723,14 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header key = keyv } - if unknown { + if unknown || idx > scap { // Reserve space for new element so we can return allocator errors. new_len := uintptr(runtime.map_len(raw_map^)+1) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } mem.zero_slice(elem_backing) - _unmarshal_value(r, map_backing_value, _decode_header(r) or_return) or_return + _unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return key_ptr := rawptr(&key) key_cstr: cstring @@ -709,6 +744,10 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header // We already reserved space for it, so this shouldn't fail. assert(set_ptr != nil) } + + if .Shrink_Excess in d.flags { + _, _ = runtime.map_shrink_dynamic(raw_map, t.map_info) + } return case: @@ -719,7 +758,8 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which // contains an array of exactly two elements, the first is a textual representation of the following // CBOR value's type. -_unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { + r := d.reader #partial switch t in ti.variant { case reflect.Type_Info_Union: idhdr: Header @@ -731,8 +771,8 @@ _unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head return .Bad_Tag_Value } - n_items, unknown := err_conv(_decode_container_length(r, vadd)) or_return - if unknown || n_items != 2 { + n_items, _ := err_conv(_decode_len_container(d, vadd)) or_return + if n_items != 2 { return .Bad_Tag_Value } @@ -743,7 +783,7 @@ _unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head } context.allocator = context.temp_allocator - target_name = err_conv(_decode_text(r, idadd)) or_return + target_name = err_conv(_decode_text(d, idadd)) or_return } defer delete(target_name, context.temp_allocator) @@ -757,7 +797,7 @@ _unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head case reflect.Type_Info_Named: if vti.name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) } case: @@ -769,7 +809,7 @@ _unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Head if variant_name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) } } } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 06b96c915..23bfbd3d8 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -4,6 +4,7 @@ import "core:bytes" import "core:encoding/cbor" import "core:fmt" import "core:intrinsics" +import "core:io" import "core:math/big" import "core:mem" import "core:os" @@ -61,7 +62,9 @@ main :: proc() { test_marshalling_maybe(&t) test_marshalling_nil_maybe(&t) - test_cbor_marshalling_union(&t) + test_marshalling_union(&t) + + test_lying_length_array(&t) test_decode_unsigned(&t) test_encode_unsigned(&t) @@ -202,7 +205,7 @@ test_marshalling :: proc(t: ^testing.T) { ev(t, err, nil) defer delete(data) - decoded, derr := cbor.decode_string(string(data)) + decoded, derr := cbor.decode(string(data)) ev(t, derr, nil) defer cbor.destroy(decoded) @@ -398,7 +401,7 @@ test_marshalling_nil_maybe :: proc(t: ^testing.T) { } @(test) -test_cbor_marshalling_union :: proc(t: ^testing.T) { +test_marshalling_union :: proc(t: ^testing.T) { My_Distinct :: distinct string My_Enum :: enum { @@ -457,6 +460,14 @@ test_cbor_marshalling_union :: proc(t: ^testing.T) { } } +@(test) +test_lying_length_array :: proc(t: ^testing.T) { + // Input says this is an array of length max(u64), this should not allocate that amount. + input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42} + _, err := cbor.decode(string(input)) + expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. +} + @(test) test_decode_unsigned :: proc(t: ^testing.T) { expect_decoding(t, "\x00", "0", u8) From 7283b5e75ccecf7dbf28072456a137b29ff983af Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 16 Dec 2023 23:44:40 +0100 Subject: [PATCH 009/171] encoding/cbor: minor things --- core/encoding/cbor/cbor.odin | 6 +++++- core/encoding/cbor/tags.odin | 2 +- core/encoding/cbor/unmarshal.odin | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 9c4bb0e4e..ddbd53c8d 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -1,3 +1,7 @@ +// Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary. +// Also provided are conversion to and from JSON and the CBOR diagnostic format. +// +// You can additionally provide custom CBOR tag implementations for your use cases. package cbor import "core:encoding/json" @@ -163,7 +167,7 @@ Decode_Data_Error :: enum { Nested_Tag, // When a tag's value is another tag, this is not allowed. Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`. Disallowed_Streaming, // When the `.Disallow_Streaming` flag is set and a streaming header is encountered. - Break, + Break, // When the `break` header was found without any stream to break off. } Encode_Data_Error :: enum { diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index d2867e7be..cdb7227ef 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -89,7 +89,7 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string // Controls initialization of default tag implementations. // JS and WASI default to a panic allocator so we don't want to do it on those. -INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, ODIN_OS != .JS && ODIN_OS != .WASI) +INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_NIL_ALLOCATOR && ODIN_OS != .JS && ODIN_OS != .WASI) @(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) tags_initialize_defaults :: proc() { diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 2df99ca71..dea4b749c 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -686,8 +686,6 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, return _unsupported(v, hdr) } - // TODO: shrink excess. - raw_map := (^mem.Raw_Map)(v.data) if raw_map.allocator.procedure == nil { raw_map.allocator = context.allocator From 46b58ad48d2e326c9592654e96efdf2e927dc876 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 20 Dec 2023 14:29:34 +0100 Subject: [PATCH 010/171] encoding/cbor: don't zero bytes we are going to write/read to/from anyway --- core/encoding/cbor/coding.odin | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index e39519e01..5d99aa6d2 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -266,7 +266,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { } _decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { - buf: [1]byte + buf: [1]byte = --- io.read_full(r, buf[:]) or_return return Header(buf[0]), nil } @@ -276,7 +276,7 @@ _header_split :: proc(hdr: Header) -> (Major, Add) { } _decode_u8 :: proc(r: io.Reader) -> (v: u8, err: io.Error) { - byte: [1]byte + byte: [1]byte = --- io.read_full(r, byte[:]) or_return return byte[0], nil } @@ -310,7 +310,7 @@ _decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) { } _decode_u16 :: proc(r: io.Reader) -> (v: u16, err: io.Error) { - bytes: [2]byte + bytes: [2]byte = --- io.read_full(r, bytes[:]) or_return return endian.unchecked_get_u16be(bytes[:]), nil } @@ -323,7 +323,7 @@ _encode_u16 :: proc(e: Encoder, v: u16, major: Major = .Unsigned) -> Encode_Erro } _encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (err: io.Error) { - bytes: [3]byte + bytes: [3]byte = --- bytes[0] = (u8(major) << 5) | u8(Add.Two_Bytes) endian.unchecked_put_u16be(bytes[1:], v) _, err = io.write_full(w, bytes[:]) @@ -331,7 +331,7 @@ _encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (er } _decode_u32 :: proc(r: io.Reader) -> (v: u32, err: io.Error) { - bytes: [4]byte + bytes: [4]byte = --- io.read_full(r, bytes[:]) or_return return endian.unchecked_get_u32be(bytes[:]), nil } @@ -344,7 +344,7 @@ _encode_u32 :: proc(e: Encoder, v: u32, major: Major = .Unsigned) -> Encode_Erro } _encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (err: io.Error) { - bytes: [5]byte + bytes: [5]byte = --- bytes[0] = (u8(major) << 5) | u8(Add.Four_Bytes) endian.unchecked_put_u32be(bytes[1:], v) _, err = io.write_full(w, bytes[:]) @@ -352,7 +352,7 @@ _encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (er } _decode_u64 :: proc(r: io.Reader) -> (v: u64, err: io.Error) { - bytes: [8]byte + bytes: [8]byte = --- io.read_full(r, bytes[:]) or_return return endian.unchecked_get_u64be(bytes[:]), nil } @@ -365,7 +365,7 @@ _encode_u64 :: proc(e: Encoder, v: u64, major: Major = .Unsigned) -> Encode_Erro } _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (err: io.Error) { - bytes: [9]byte + bytes: [9]byte = --- bytes[0] = (u8(major) << 5) | u8(Add.Eight_Bytes) endian.unchecked_put_u64be(bytes[1:], v) _, err = io.write_full(w, bytes[:]) @@ -556,7 +556,7 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { for &entry, i in entries { entry.entry = m[i] - buf := strings.builder_make(0, 8, context.temp_allocator) or_return + buf := strings.builder_make(context.temp_allocator) or_return ke := e ke.writer = strings.to_stream(&buf) @@ -631,7 +631,7 @@ _encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error { } _decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) { - buf: [1]byte + buf: [1]byte = --- io.read_full(r, buf[:]) or_return return Simple(buf[0]), nil } @@ -661,14 +661,14 @@ _decode_tiny_simple :: proc(add: Add) -> (Simple, Decode_Data_Error) { } _decode_f16 :: proc(r: io.Reader) -> (v: f16, err: io.Error) { - bytes: [2]byte + bytes: [2]byte = --- io.read_full(r, bytes[:]) or_return n := endian.unchecked_get_u16be(bytes[:]) return transmute(f16)n, nil } _encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) { - bytes: [3]byte + bytes: [3]byte = --- bytes[0] = u8(Header.F16) endian.unchecked_put_u16be(bytes[1:], transmute(u16)v) _, err = io.write_full(w, bytes[:]) @@ -676,7 +676,7 @@ _encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) { } _decode_f32 :: proc(r: io.Reader) -> (v: f32, err: io.Error) { - bytes: [4]byte + bytes: [4]byte = --- io.read_full(r, bytes[:]) or_return n := endian.unchecked_get_u32be(bytes[:]) return transmute(f32)n, nil @@ -690,7 +690,7 @@ _encode_f32 :: proc(e: Encoder, v: f32) -> io.Error { } _encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) { - bytes: [5]byte + bytes: [5]byte = --- bytes[0] = u8(Header.F32) endian.unchecked_put_u32be(bytes[1:], transmute(u32)v) _, err = io.write_full(w, bytes[:]) @@ -698,7 +698,7 @@ _encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) { } _decode_f64 :: proc(r: io.Reader) -> (v: f64, err: io.Error) { - bytes: [8]byte + bytes: [8]byte = --- io.read_full(r, bytes[:]) or_return n := endian.unchecked_get_u64be(bytes[:]) return transmute(f64)n, nil @@ -712,7 +712,7 @@ _encode_f64 :: proc(e: Encoder, v: f64) -> io.Error { } _encode_f64_exact :: proc(w: io.Writer, v: f64) -> (err: io.Error) { - bytes: [9]byte + bytes: [9]byte = --- bytes[0] = u8(Header.F64) endian.unchecked_put_u64be(bytes[1:], transmute(u64)v) _, err = io.write_full(w, bytes[:]) From cb8bb8bfd8df311f13d40bfc19018f70e105a1cf Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 20 Dec 2023 15:29:55 +0100 Subject: [PATCH 011/171] encoding/cbor: cleanup default temp allocator --- core/encoding/cbor/cbor.odin | 5 ++ core/encoding/cbor/coding.odin | 87 ++++++++++++++++++++++++------- core/encoding/cbor/marshal.odin | 15 +----- core/encoding/cbor/unmarshal.odin | 5 +- 4 files changed, 77 insertions(+), 35 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index ddbd53c8d..9df4dfa51 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -238,6 +238,7 @@ negative_u64_to_int :: #force_inline proc(u: Negative_U64) -> i128 { // Utility for converting between the different errors when they are subsets of the other. err_conv :: proc { encode_to_marshal_err, + encode_to_marshal_err_p2, decode_to_unmarshal_err, decode_to_unmarshal_err_p, decode_to_unmarshal_err_p2, @@ -253,6 +254,10 @@ encode_to_marshal_err :: #force_inline proc(err: Encode_Error) -> Marshal_Error } } +encode_to_marshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Encode_Error) -> (T, T2, Marshal_Error) { + return v, v2, err_conv(err) +} + decode_to_unmarshal_err :: #force_inline proc(err: Decode_Error) -> Unmarshal_Error { switch e in err { case nil: return nil diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 5d99aa6d2..1e77a35c8 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -4,6 +4,7 @@ import "core:bytes" import "core:encoding/endian" import "core:intrinsics" import "core:io" +import "core:runtime" import "core:slice" import "core:strings" @@ -54,6 +55,9 @@ Decoder_Flag :: enum { // Makes the decoder shrink of excess capacity from allocated buffers/containers before returning. Shrink_Excess, + + // Internal flag to do initialization. + _In_Progress, } Decoder_Flags :: bit_set[Decoder_Flag] @@ -117,9 +121,8 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V context.allocator = allocator d := d - if d.max_pre_alloc <= 0 { - d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC - } + + DECODE_PROGRESS_GUARD(&d) v, err = _decode_from_decoder(d) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. @@ -225,21 +228,9 @@ encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL) -> Enc // See the docs on the proc group `encode_into` for more info. encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { e := e + + ENCODE_PROGRESS_GUARD(&e) or_return - outer: bool - defer if outer { - e.flags &~= {._In_Progress} - } - - if ._In_Progress not_in e.flags { - outer = true - e.flags |= {._In_Progress} - - if .Self_Described_CBOR in e.flags { - _encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return - } - } - switch v_spec in v { case u8: return _encode_u8(e.writer, v_spec, .Unsigned) case u16: return _encode_u16(e, v_spec, .Unsigned) @@ -265,6 +256,66 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { } } +@(deferred_in_out=_decode_progress_end) +DECODE_PROGRESS_GUARD :: proc(d: ^Decoder) -> (is_begin: bool, tmp: runtime.Arena_Temp) { + if ._In_Progress in d.flags { + return + } + is_begin = true + + incl_elem(&d.flags, Decoder_Flag._In_Progress) + + if context.allocator != context.temp_allocator { + tmp = runtime.default_temp_allocator_temp_begin() + } + + if d.max_pre_alloc <= 0 { + d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC + } + + return +} + +_decode_progress_end :: proc(d: ^Decoder, is_begin: bool, tmp: runtime.Arena_Temp) { + if !is_begin { + return + } + + excl_elem(&d.flags, Decoder_Flag._In_Progress) + + runtime.default_temp_allocator_temp_end(tmp) +} + +@(deferred_in_out=_encode_progress_end) +ENCODE_PROGRESS_GUARD :: proc(e: ^Encoder) -> (is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { + if ._In_Progress in e.flags { + return + } + is_begin = true + + incl_elem(&e.flags, Encoder_Flag._In_Progress) + + if context.allocator != context.temp_allocator { + tmp = runtime.default_temp_allocator_temp_begin() + } + + if .Self_Described_CBOR in e.flags { + _encode_u64(e^, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return + } + + return +} + +_encode_progress_end :: proc(e: ^Encoder, is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { + if !is_begin || err != nil { + return + } + + excl_elem(&e.flags, Encoder_Flag._In_Progress) + + runtime.default_temp_allocator_temp_end(tmp) +} + _decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { buf: [1]byte = --- io.read_full(r, buf[:]) or_return @@ -514,7 +565,7 @@ _decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) { return nil, kerr } - value := decode_from_decoder(d) or_return + value := _decode_from_decoder(d) or_return append(&items, Map_Entry{ key = key, diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index a5d5efb3e..898371adf 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -77,21 +77,8 @@ marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL) -> Mars // See docs on the `marshal_into` proc group for more info. marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { e := e - - init: bool - defer if init { - e.flags &~= {._In_Progress} - } - - // If not in progress we do initialization and set in progress. - if ._In_Progress not_in e.flags { - init = true - e.flags |= {._In_Progress} - if .Self_Described_CBOR in e.flags { - err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return - } - } + err_conv(ENCODE_PROGRESS_GUARD(&e)) or_return if v == nil { return _encode_nil(e.writer) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index dea4b749c..c7de2d87a 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -52,9 +52,8 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator) -> (err: Unmarshal_Error) { d := d - if d.max_pre_alloc <= 0 { - d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC - } + + DECODE_PROGRESS_GUARD(&d) err = _unmarshal_any_ptr(d, ptr, allocator=allocator) From 85f1a60cf301abab292e1dab65e19c61c5612e8e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 20 Dec 2023 16:08:27 +0100 Subject: [PATCH 012/171] encoding/cbor: cleanup comments about tags --- core/encoding/cbor/tags.odin | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index cdb7227ef..38649f634 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -13,29 +13,35 @@ import "core:time" // Tags defined in RFC 7049 that we provide implementations for. // UTC time in seconds, unmarshalled into a `core:time` `time.Time` or integer. +// Use the struct tag `cbor_tag:"1"` or `cbor_tag:"epoch"` to have your `time.Time` field en/decoded as epoch time. TAG_EPOCH_TIME_NR :: 1 TAG_EPOCH_TIME_ID :: "epoch" // Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. -TAG_UNSIGNED_BIG_NR :: 2 +// These fields use this tag by default, no struct tag required. +TAG_UNSIGNED_BIG_NR :: 2 // Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. -TAG_NEGATIVE_BIG_NR :: 3 +// These fields use this tag by default, no struct tag required. +TAG_NEGATIVE_BIG_NR :: 3 // TAG_DECIMAL_FRACTION :: 4 // NOTE: We could probably implement this with `math/fixed`. // Sometimes it is beneficial to carry an embedded CBOR data item that is not meant to be decoded // immediately at the time the enclosing data item is being decoded. Tag number 24 (CBOR data item) // can be used to tag the embedded byte string as a single data item encoded in CBOR format. +// Use the struct tag `cbor_tag:"24"` or `cbor_tag:"cbor"` to keep a non-decoded field (string or bytes) of raw CBOR. TAG_CBOR_NR :: 24 TAG_CBOR_ID :: "cbor" // The contents of this tag are base64 encoded during marshal and decoded during unmarshal. +// Use the struct tag `cbor_tag:"34"` or `cbor_tag:"base64"` to have your field string or bytes field en/decoded as base64. TAG_BASE64_NR :: 34 TAG_BASE64_ID :: "base64" // A tag that is used to detect the contents of a binary buffer (like a file) are CBOR. // This tag would wrap everything else, decoders can then check for this header and see if the // given content is definitely CBOR. +// Added by the encoder if it has the flag `.Self_Described_CBOR`, decoded by default. TAG_SELF_DESCRIBED_CBOR :: 55799 // A tag that is used to assign a textual type to the object following it. @@ -99,19 +105,14 @@ tags_initialize_defaults :: proc() { // Registers tags that have implementations provided by this package. // This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define. tags_register_defaults :: proc() { - // NOTE: Not registering this the other way around, user can opt-in using the `cbor_tag:"1"` struct - // tag instead, it would lose precision and marshalling the `time.Time` struct normally is valid. - tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID) - - // Use the struct tag `cbor_tag:"34"` to have your field encoded in a base64. - tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID) - - // Use the struct tag `cbor_tag:"24"` to keep a non-decoded field of raw CBOR. - tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID) + tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID) + tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID) + tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID) // These following tags are registered at the type level and don't require an opt-in struct tag. // Encoding these types on its own make no sense or no data is lost to encode it. - + + // En/Decoding of `big.Int` fields by default. tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_UNSIGNED_BIG_NR, big.Int) tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_NEGATIVE_BIG_NR, big.Int) } From 3fccc77829d6479b972026c5fee7ef0f34ac589e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 20 Dec 2023 16:20:33 +0100 Subject: [PATCH 013/171] encoding/cbor: clean and fixup some allocations --- core/encoding/cbor/coding.odin | 8 +++--- core/encoding/cbor/tags.odin | 5 +--- core/encoding/cbor/unmarshal.odin | 44 +++++++++++++------------------ 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 1e77a35c8..32ecf52bc 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -431,7 +431,9 @@ _decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^By return } -_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: Bytes, err: Decode_Error) { +_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) { + context.allocator = allocator + n, scap := _decode_len_str(d, add) or_return buf := strings.builder_make(0, scap) or_return @@ -487,8 +489,8 @@ _decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) return } -_decode_text :: proc(d: Decoder, add: Add) -> (v: Text, err: Decode_Error) { - return (Text)(_decode_bytes(d, add, .Text) or_return), nil +_decode_text :: proc(d: Decoder, add: Add, allocator := context.temp_allocator) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil } _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 38649f634..efe724f8c 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -293,10 +293,7 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, return .Bad_Tag_Value } - bytes: string; { - context.allocator = context.temp_allocator - bytes = string(err_conv(_decode_bytes(d, add)) or_return) - } + bytes := string(err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return) defer delete(bytes, context.temp_allocator) #partial switch t in ti.variant { diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index c7de2d87a..ae7f97c98 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -370,9 +370,8 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - context.allocator = context.temp_allocator - bytes := err_conv(_decode_bytes(d, add)) or_return - defer delete(bytes) + bytes := err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return + defer delete(bytes, context.temp_allocator) if len(bytes) > t.count { return _unsupported(v, hdr) } @@ -404,9 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade // Enum by its variant name. case reflect.Type_Info_Enum: - context.allocator = context.temp_allocator - text := err_conv(_decode_text(d, add)) or_return - defer delete(text) + text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return + defer delete(text, context.temp_allocator) for name, i in t.names { if name == text { @@ -416,9 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade } case reflect.Type_Info_Rune: - context.allocator = context.temp_allocator - text := err_conv(_decode_text(d, add)) or_return - defer delete(text) + text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return + defer delete(text, context.temp_allocator) r := (^rune)(v.data) dr, n := utf8.decode_rune(text) @@ -585,7 +582,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case quaternion64: info = type_info_of(f16) case quaternion128: info = type_info_of(f32) case quaternion256: info = type_info_of(f64) - case: unreachable() + case: unreachable() } out_of_space := assign_array(d, &da, info, 4, growable=false) or_return @@ -598,15 +595,15 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { r := d.reader - decode_key :: proc(d: Decoder, v: any) -> (k: string, err: Unmarshal_Error) { + decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) { entry_hdr := _decode_header(d.reader) or_return entry_maj, entry_add := _header_split(entry_hdr) #partial switch entry_maj { case .Text: - k = err_conv(_decode_text(d, entry_add)) or_return + k = err_conv(_decode_text(d, entry_add, allocator)) or_return return case .Bytes: - bytes := err_conv(_decode_bytes(d, entry_add)) or_return + bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return k = string(bytes) return case: @@ -637,16 +634,14 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 { // Decode key, keys can only be strings. - key: string; { - context.allocator = context.temp_allocator - if keyv, kerr := decode_key(d, v); unknown && kerr == .Break { - break - } else if kerr != nil { - err = kerr - return - } else { - key = keyv - } + key: string + if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break { + break + } else if kerr != nil { + err = kerr + return + } else { + key = keyv } defer delete(key, context.temp_allocator) @@ -779,8 +774,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return .Bad_Tag_Value } - context.allocator = context.temp_allocator - target_name = err_conv(_decode_text(d, idadd)) or_return + target_name = err_conv(_decode_text(d, idadd, context.temp_allocator)) or_return } defer delete(target_name, context.temp_allocator) From 154e0d41c6f77feb8a11ff8a6cb4449c11dd767e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 18:11:52 +0100 Subject: [PATCH 014/171] encoding/cbor: fix wrong allocator bug --- core/encoding/cbor/coding.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 32ecf52bc..ee928f68e 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -100,10 +100,9 @@ decode :: decode_from // Decodes the given string as CBOR. // See docs on the proc group `decode` for more information. decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { - context.allocator = allocator r: strings.Reader strings.reader_init(&r, s) - return decode_from_reader(strings.reader_to_stream(&r), flags) + return decode_from_reader(strings.reader_to_stream(&r), flags, allocator) } // Reads a CBOR value from the given reader. @@ -489,7 +488,7 @@ _decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) return } -_decode_text :: proc(d: Decoder, add: Add, allocator := context.temp_allocator) -> (v: Text, err: Decode_Error) { +_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) { return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil } From 72d5b87b52fd4a1fb92819121e7f17b9118dac99 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 18:12:13 +0100 Subject: [PATCH 015/171] encoding/cbor: clean --- core/encoding/cbor/coding.odin | 5 ++--- tests/core/encoding/cbor/test_core_cbor.odin | 23 ++++++-------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index ee928f68e..9dd6d2639 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -316,9 +316,8 @@ _encode_progress_end :: proc(e: ^Encoder, is_begin: bool, tmp: runtime.Arena_Tem } _decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { - buf: [1]byte = --- - io.read_full(r, buf[:]) or_return - return Header(buf[0]), nil + hdr = Header(_decode_u8(r) or_return) + return } _header_split :: proc(hdr: Header) -> (Major, Add) { diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 23bfbd3d8..0fb8b521f 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -799,15 +799,8 @@ test_encode_tags :: proc(t: ^testing.T) { // Helpers -buf: bytes.Buffer -stream := bytes.buffer_to_stream(&buf) -encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream} - expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: typeid, loc := #caller_location) { - bytes.buffer_reset(&buf) - bytes.buffer_write_string(&buf, encoded) - - res, err := cbor.decode(stream) + res, err := cbor.decode(encoded) defer cbor.destroy(res) expect_value(t, reflect.union_variant_typeid(res), type, loc) @@ -820,10 +813,7 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t } expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { - bytes.buffer_reset(&buf) - bytes.buffer_write_string(&buf, encoded) - - res, err := cbor.decode(stream) + res, err := cbor.decode(encoded) defer cbor.destroy(res) expect_value(t, err, nil, loc) @@ -841,10 +831,7 @@ expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_de } expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #caller_location) where intrinsics.type_is_float(T) { - bytes.buffer_reset(&buf) - bytes.buffer_write_string(&buf, encoded) - - res, err := cbor.decode(stream) + res, err := cbor.decode(encoded) defer cbor.destroy(res) expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) @@ -862,6 +849,10 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle } } +buf: bytes.Buffer +stream := bytes.buffer_to_stream(&buf) +encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream} + expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { bytes.buffer_reset(&buf) From 7854aa22d99b2c0340f4352f133ce06fd1b80df6 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 18:40:27 +0100 Subject: [PATCH 016/171] encoding/cbor: fix unused import --- tests/core/encoding/hex/test_core_hex.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index a5daa206e..d928cd28e 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -4,7 +4,6 @@ import "core:encoding/hex" import "core:testing" import "core:fmt" import "core:os" -import "core:bytes" TEST_count := 0 TEST_fail := 0 From 759d095548e7135bbfeb68ac6b0a21857af49527 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 18:52:53 +0100 Subject: [PATCH 017/171] encoding/cbor: ignore struct fields with `cbor:"-"` --- core/encoding/cbor/marshal.odin | 37 ++++++++++++++------ core/encoding/cbor/unmarshal.odin | 4 +++ tests/core/encoding/cbor/test_core_cbor.odin | 3 ++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 898371adf..deb7ba020 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -422,7 +422,13 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { case Tag: return err_conv(_encode_tag(e, vv)) } - err_conv(_encode_u16(e, u16(len(info.names)), .Map)) or_return + field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string { + if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" { + return cbor_name + } else { + return info.names[i] + } + } marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, name: string, i: int) -> Marshal_Error { err_conv(_encode_text(e, name)) or_return @@ -448,13 +454,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { return marshal_into(e, field_any) } - - field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string { - if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" { - return cbor_name - } else { - return info.names[i] + + n: u64; { + for _, i in info.names { + if field_name(info, i) != "-" { + n += 1 + } } + err_conv(_encode_u64(e, n, .Map)) or_return } if .Deterministic_Map_Sorting in e.flags { @@ -462,11 +469,16 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { name: string, field: int, } - entries := make([dynamic]Name, 0, len(info.names), context.temp_allocator) or_return + entries := make([dynamic]Name, 0, n, context.temp_allocator) or_return defer delete(entries) for name, i in info.names { - append(&entries, Name{field_name(info, i), i}) or_return + fname := field_name(info, i) + if fname == "-" { + continue + } + + append(&entries, Name{fname, i}) or_return } // Sort lexicographic on the bytes of the key. @@ -479,7 +491,12 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { } } else { for name, i in info.names { - marshal_entry(e, info, v, field_name(info, i), i) or_return + fname := field_name(info, i) + if fname == "-" { + continue + } + + marshal_entry(e, info, v, fname, i) or_return } } return diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index ae7f97c98..9ad25a38d 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -650,6 +650,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, { for field, field_idx in fields { tag_value := string(reflect.struct_tag_get(field.tag, "cbor")) + if tag_value == "-" { + continue + } + if key == tag_value { use_field_idx = field_idx break diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 0fb8b521f..daf31c277 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -130,6 +130,7 @@ Foo :: struct { small_onetwenty: i128, biggest: big.Int, smallest: big.Int, + ignore_this: ^Foo `cbor:"-"`, } FooBar :: enum { @@ -189,6 +190,7 @@ test_marshalling :: proc(t: ^testing.T) { smallie = cbor.Negative_U64(max(u64)), onetwenty = i128(12345), small_onetwenty = -i128(max(u64)), + ignore_this = &Foo{}, } big.atoi(&f.biggest, "1234567891011121314151617181920") @@ -343,6 +345,7 @@ test_marshalling :: proc(t: ^testing.T) { ev(t, backf.smallie, f.smallie) ev(t, backf.onetwenty, f.onetwenty) ev(t, backf.small_onetwenty, f.small_onetwenty) + ev(t, backf.ignore_this, nil) s_equals, s_err := big.equals(&backf.smallest, &f.smallest) ev(t, s_err, nil) From 317931a3c5179e10db941157a994c8e89b7080c2 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 19:22:33 +0100 Subject: [PATCH 018/171] encoding/cbor: deterministically store bit sets as big endian --- core/encoding/cbor/cbor.odin | 18 ----------------- core/encoding/cbor/marshal.odin | 3 ++- core/encoding/cbor/unmarshal.odin | 13 ++++++------ core/reflect/reflect.odin | 21 ++++++++++++++++++++ tests/core/encoding/cbor/test_core_cbor.odin | 2 +- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 9df4dfa51..3ab493b4b 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -675,21 +675,3 @@ _i128_to_uint :: proc(v: i128) -> (u: u64, m: Major, err: Encode_Data_Error) { u, err = _u128_to_u64(u128(v)) return } - -@(private) -is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool { - if ti == nil { - return false - } - t := runtime.type_info_base(ti) - #partial switch info in t.variant { - case runtime.Type_Info_Integer: - switch info.endianness { - case .Platform: return false - case .Little: return ODIN_ENDIAN != .Little - case .Big: return ODIN_ENDIAN != .Big - } - } - return false -} - diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index deb7ba020..b7c47f252 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -541,7 +541,8 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { return marshal_into(e, any{v.data, info.base.id}) case runtime.Type_Info_Bit_Set: - do_byte_swap := is_bit_set_different_endian_to_platform(info.underlying) + // Store bit_set as big endian just like the protocol. + do_byte_swap := !reflect.bit_set_is_big_endian(v) switch ti.size * 8 { case 0: return _encode_u8(e.writer, 0) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 9ad25a38d..98ef06635 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -856,12 +856,11 @@ _assign_int :: proc(val: any, i: $T) -> bool { case uintptr: dst = uintptr(i) case: ti := type_info_of(v.id) - do_byte_swap := is_bit_set_different_endian_to_platform(ti) - #partial switch info in ti.variant { - case runtime.Type_Info_Bit_Set: + if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := !reflect.bit_set_is_big_endian(v) switch ti.size * 8 { - case 0: - case 8: + case 0: // no-op. + case 8: x := (^u8)(v.data) x^ = u8(i) case 16: @@ -876,9 +875,9 @@ _assign_int :: proc(val: any, i: $T) -> bool { case: panic("unknown bit_size size") } - case: - return false + return true } + return false } return true } diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index de5dec2e3..de7379ecc 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -934,6 +934,27 @@ set_union_value :: proc(dst: any, value: any) -> bool { panic("expected a union to reflect.set_union_variant_typeid") } +@(require_results) +bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool { + if value == nil { return ODIN_ENDIAN == .Big } + + ti := runtime.type_info_base(type_info_of(value.id)) + if info, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + if info.underlying == nil { return ODIN_ENDIAN == .Big } + + underlying_ti := runtime.type_info_base(info.underlying) + if underlying_info, uok := underlying_ti.variant.(runtime.Type_Info_Integer); uok { + switch underlying_info.endianness { + case .Platform: return ODIN_ENDIAN == .Big + case .Little: return false + case .Big: return true + } + } + + return ODIN_ENDIAN == .Big + } + panic("expected a bit_set to reflect.bit_set_is_big_endian", loc) +} @(require_results) diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index daf31c277..691a0a5ec 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -251,7 +251,7 @@ test_marshalling :: proc(t: ^testing.T) { ], "cstr": "Hellnope", "ennie": 0, - "ennieb": 2, + "ennieb": 512, "iamint": -256, "important": "!", "my_bytes": h'', From c1cf6c1a95bb489525e329280be735d7a5ce966b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 23 Dec 2023 20:02:04 +0100 Subject: [PATCH 019/171] encoding/cbor: add general docs and example --- core/encoding/cbor/cbor.odin | 5 -- core/encoding/cbor/coding.odin | 8 +- core/encoding/cbor/doc.odin | 143 ++++++++++++++++++++++++++++++ core/encoding/cbor/marshal.odin | 2 +- core/encoding/cbor/unmarshal.odin | 2 +- 5 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 core/encoding/cbor/doc.odin diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 3ab493b4b..7e0f4ea1a 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -1,14 +1,9 @@ -// Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary. -// Also provided are conversion to and from JSON and the CBOR diagnostic format. -// -// You can additionally provide custom CBOR tag implementations for your use cases. package cbor import "core:encoding/json" import "core:intrinsics" import "core:io" import "core:mem" -import "core:runtime" import "core:strconv" import "core:strings" diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 9dd6d2639..a9bb6e408 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -121,7 +121,7 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V d := d - DECODE_PROGRESS_GUARD(&d) + _DECODE_PROGRESS_GUARD(&d) v, err = _decode_from_decoder(d) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. @@ -228,7 +228,7 @@ encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL) -> Enc encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { e := e - ENCODE_PROGRESS_GUARD(&e) or_return + _ENCODE_PROGRESS_GUARD(&e) or_return switch v_spec in v { case u8: return _encode_u8(e.writer, v_spec, .Unsigned) @@ -256,7 +256,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { } @(deferred_in_out=_decode_progress_end) -DECODE_PROGRESS_GUARD :: proc(d: ^Decoder) -> (is_begin: bool, tmp: runtime.Arena_Temp) { +_DECODE_PROGRESS_GUARD :: proc(d: ^Decoder) -> (is_begin: bool, tmp: runtime.Arena_Temp) { if ._In_Progress in d.flags { return } @@ -286,7 +286,7 @@ _decode_progress_end :: proc(d: ^Decoder, is_begin: bool, tmp: runtime.Arena_Tem } @(deferred_in_out=_encode_progress_end) -ENCODE_PROGRESS_GUARD :: proc(e: ^Encoder) -> (is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { +_ENCODE_PROGRESS_GUARD :: proc(e: ^Encoder) -> (is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { if ._In_Progress in e.flags { return } diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin new file mode 100644 index 000000000..efcad5c9e --- /dev/null +++ b/core/encoding/cbor/doc.odin @@ -0,0 +1,143 @@ +/* +Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary. +Also provided are conversion to and from JSON and the CBOR diagnostic format. + +**Allocations:** + +In general, when in the following table it says allocations are done on the `context.temp_allocator`, these allocations +are still attempted to be deallocated. +This allows you to use an allocator with freeing implemented as the `context.temp_allocator` which is handy with big CBOR. + +If you use the default `context.temp_allocator` it will be returned back to its state when the process (en/decoding, (un)marshal) started. + +- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on `context.temp_allocator` + some space for the keys of maps in order to sort them and then write them. + Other than that there are no allocations (only for the final bytes if you use `cbor.encode_into_bytes`. + +- *Decoding*: Allocates everything on the given allocator and input given can be deleted after decoding. + *No* allocations are done on the `context.temp_allocator`. + +- *Marshal*: Same allocation strategy as encoding. + +- *Unmarshal*: Allocates everything on the given allocator and input given can be deleted after unmarshalling. + Some temporary allocations are done on the `context.temp_allocator`. + +**Determinism:** + +CBOR defines a deterministic en/decoder, which among other things uses the smallest type possible for integers and floats, +and sorts map keys by their (encoded) lexical bytewise order. + +You can enable this behaviour using a combination of flags, also available as the `cbor.ENCODE_FULLY_DETERMINISTIC` constant. +If you just want the small size that comes with this, but not the map sorting (which has a performance cost) you can use the +`cbor.ENCODE_SMALL` constant for the flags. + +A deterministic float is a float in the smallest type (f16, f32, f64) that hasn't changed after conversion. +A deterministic integer is an integer in the smallest representation (u8, u16, u32, u64) it fits in. + +**Untrusted Input:** + +By default input is treated as untrusted, this means the sizes that are encoded in the CBOR are not blindly trusted. +If you were to trust these sizes, and allocate space for them an attacker would be able to cause massive allocations with small payloads. + +The decoder has a `max_pre_alloc` field that specifies the maximum amount of bytes (roughly) to pre allocate, a KiB by default. + +This does mean reallocations are more common though, you can, if you know the input is trusted, add the `.Trusted_Input` flag to the decoder. + +**Tags:** + +CBOR describes tags that you can wrap values with to assign a number to describe what type of data will follow. + +More information and a list of default tags can be found here: [[RFC 8949 Section 3.4;https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items]]. + +A list of registered extension types can be found here: [[IANA CBOR assignments;https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml]]. + +Tags can either be assigned to a distinct Odin type (used by default), +or be used with struct tags (`cbor_tag:"base64"`, or `cbor_tag:"1"` for example). + +By default, the following tags are supported/provided by this implementation: + +- *1/epoch*: Assign this tag to `time.Time` or integer fields to use the defined seconds since epoch format. + +- *24/cbor*: Assign this tag to string or byte fields to store encoded CBOR (not decoding it). + +- *34/base64*: Assign this tag to string or byte fields to store and decode the contents in base64. + +- *2 & 3*: Used automatically by the implementation to encode and decode big numbers into/from `core:math/big`. + +- *55799*: Self described CBOR, used when `.Self_Described_CBOR` flag is used to wrap the entire binary. + This shows other implementations that we are dealing with CBOR by just looking at the first byte of input. + +- *1010*: An extension tag that defines a string type followed by its value, this is used by this implementation to support Odin's unions. + +Users can provide their own tag implementations using the `cbor.tag_register_type(...)` to register a tag for a distinct Odin type +used automatically when it is encountered during marshal and unmarshal. +Or with `cbor.tag_register_number(...)` to register a tag number along with an identifier for convenience that can be used with struct tags, +e.g. `cbor_tag:"69"` or `cbor_tag:"my_tag"`. + +You can look at the default tags provided for pointers on how these implementations work. + +Example: + package main + + import "core:encoding/cbor" + import "core:fmt" + import "core:time" + + Possibilities :: union { + string, + int, + } + + Data :: struct { + str: string, + neg: cbor.Negative_U16, // Store a CBOR value directly. + now: time.Time `cbor_tag:"epoch"`, // Wrapped in the epoch tag. + ignore_this: ^Data `cbor:"-"`, // Ignored by implementation. + renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded. + my_union: Possibilities, // Union support. + } + + main :: proc() { + now := time.Time{_nsec = 1701117968 * 1e9} + + data := Data{ + str = "Hello, World!", + neg = 300, + now = now, + ignore_this = &Data{}, + renamed = 123123.125, + my_union = 3, + } + + // Marshal the struct into binary CBOR. + binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC) + assert(err == nil) + defer delete(binary) + + // Decode the binary data into a `cbor.Value`. + decoded, derr := cbor.decode(string(binary)) + assert(derr == nil) + defer cbor.destroy(decoded) + + // Turn the CBOR into a human readable representation. + diagnosis, eerr := cbor.diagnose(decoded) + assert(eerr == nil) + defer delete(diagnosis) + + fmt.println(diagnosis) + } + +Output: + { + "my_union": 1010([ + "int", + 3 + ]), + "neg": -301, + "now": 1(1701117968), + "renamed :)": 123123.12500000, + "str": "Hello, World!" + } +*/ +package cbor + diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index b7c47f252..4a0619c04 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -78,7 +78,7 @@ marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL) -> Mars marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { e := e - err_conv(ENCODE_PROGRESS_GUARD(&e)) or_return + err_conv(_ENCODE_PROGRESS_GUARD(&e)) or_return if v == nil { return _encode_nil(e.writer) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 98ef06635..0acb48083 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -53,7 +53,7 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator) -> (err: Unmarshal_Error) { d := d - DECODE_PROGRESS_GUARD(&d) + _DECODE_PROGRESS_GUARD(&d) err = _unmarshal_any_ptr(d, ptr, allocator=allocator) From c4e45d509a25ad1d341a5519606ddff59bfeb64e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 17 Jan 2024 00:03:35 +0100 Subject: [PATCH 020/171] encoding/cbor: adhere to new quaternion rules of master --- core/encoding/cbor/unmarshal.odin | 6 +++--- tests/core/encoding/cbor/test_core_cbor.odin | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 0acb48083..eec999c12 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -903,9 +903,9 @@ _assign_float :: proc(val: any, f: $T) -> bool { case complex64: dst = complex(f32(f), 0) case complex128: dst = complex(f64(f), 0) - case quaternion64: dst = quaternion(f16(f), 0, 0, 0) - case quaternion128: dst = quaternion(f32(f), 0, 0, 0) - case quaternion256: dst = quaternion(f64(f), 0, 0, 0) + case quaternion64: dst = quaternion(w=f16(f), x=0, y=0, z=0) + case quaternion128: dst = quaternion(w=f32(f), x=0, y=0, z=0) + case quaternion256: dst = quaternion(w=f64(f), x=0, y=0, z=0) case: return false } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 691a0a5ec..e7a3ef419 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -177,7 +177,7 @@ test_marshalling :: proc(t: ^testing.T) { ennie = .EFoo, ennieb = {.EBar}, - quat = quaternion(16, 17, 18, 19), + quat = quaternion(w=16, x=17, y=18, z=19), comp = complex(32, 33), important = '!', From a664d9804f64f7f9d6cb4a8bbe2e618297663c60 Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 6 Feb 2024 19:17:07 +0100 Subject: [PATCH 021/171] encoding/cbor: remove usage of incl_elem and excl_elem --- core/encoding/cbor/coding.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index a9bb6e408..a5f21af1f 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -262,7 +262,7 @@ _DECODE_PROGRESS_GUARD :: proc(d: ^Decoder) -> (is_begin: bool, tmp: runtime.Are } is_begin = true - incl_elem(&d.flags, Decoder_Flag._In_Progress) + d.flags |= { ._In_Progress } if context.allocator != context.temp_allocator { tmp = runtime.default_temp_allocator_temp_begin() @@ -280,7 +280,7 @@ _decode_progress_end :: proc(d: ^Decoder, is_begin: bool, tmp: runtime.Arena_Tem return } - excl_elem(&d.flags, Decoder_Flag._In_Progress) + d.flags &~= { ._In_Progress } runtime.default_temp_allocator_temp_end(tmp) } @@ -292,7 +292,7 @@ _ENCODE_PROGRESS_GUARD :: proc(e: ^Encoder) -> (is_begin: bool, tmp: runtime.Are } is_begin = true - incl_elem(&e.flags, Encoder_Flag._In_Progress) + e.flags |= { ._In_Progress } if context.allocator != context.temp_allocator { tmp = runtime.default_temp_allocator_temp_begin() @@ -310,7 +310,7 @@ _encode_progress_end :: proc(e: ^Encoder, is_begin: bool, tmp: runtime.Arena_Tem return } - excl_elem(&e.flags, Encoder_Flag._In_Progress) + e.flags &~= { ._In_Progress } runtime.default_temp_allocator_temp_end(tmp) } From 0076c07076783e5256a501e9dc37a803757ea577 Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 6 Feb 2024 19:20:18 +0100 Subject: [PATCH 022/171] encoding/cbor: core -> base --- core/encoding/cbor/cbor.odin | 3 ++- core/encoding/cbor/coding.odin | 5 +++-- core/encoding/cbor/marshal.odin | 5 +++-- core/encoding/cbor/tags.odin | 3 ++- core/encoding/cbor/unmarshal.odin | 5 +++-- tests/core/encoding/base64/base64.odin | 3 ++- tests/core/encoding/cbor/test_core_cbor.odin | 3 ++- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 7e0f4ea1a..f879a11aa 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -1,7 +1,8 @@ package cbor +import "base:intrinsics" + import "core:encoding/json" -import "core:intrinsics" import "core:io" import "core:mem" import "core:strconv" diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index a5f21af1f..5719078c7 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -1,10 +1,11 @@ package cbor +import "base:intrinsics" +import "base:runtime" + import "core:bytes" import "core:encoding/endian" -import "core:intrinsics" import "core:io" -import "core:runtime" import "core:slice" import "core:strings" diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 4a0619c04..7d93088cb 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -1,11 +1,12 @@ package cbor +import "base:intrinsics" +import "base:runtime" + import "core:bytes" -import "core:intrinsics" import "core:io" import "core:mem" import "core:reflect" -import "core:runtime" import "core:slice" import "core:strconv" import "core:strings" diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index efe724f8c..c9ddaed56 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -1,12 +1,13 @@ package cbor +import "base:runtime" + import "core:encoding/base64" import "core:io" import "core:math" import "core:math/big" import "core:mem" import "core:reflect" -import "core:runtime" import "core:strings" import "core:time" diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index eec999c12..eef5d3d99 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -1,10 +1,11 @@ package cbor -import "core:intrinsics" +import "base:intrinsics" +import "base:runtime" + import "core:io" import "core:mem" import "core:reflect" -import "core:runtime" import "core:strings" import "core:unicode/utf8" diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin index 41dbba683..e48eea020 100644 --- a/tests/core/encoding/base64/base64.odin +++ b/tests/core/encoding/base64/base64.odin @@ -1,8 +1,9 @@ package test_encoding_base64 +import "base:intrinsics" + import "core:encoding/base64" import "core:fmt" -import "core:intrinsics" import "core:os" import "core:reflect" import "core:testing" diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index e7a3ef419..8262e5da4 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -1,9 +1,10 @@ package test_encoding_cbor +import "base:intrinsics" + import "core:bytes" import "core:encoding/cbor" import "core:fmt" -import "core:intrinsics" import "core:io" import "core:math/big" import "core:mem" From b11d839fb6dab106a557cf65257e31644a84725d Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 6 Feb 2024 20:13:30 +0100 Subject: [PATCH 023/171] encoding/cbor: make temp allocations more explicit --- core/encoding/cbor/coding.odin | 108 +++++-------------- core/encoding/cbor/doc.odin | 12 +-- core/encoding/cbor/marshal.odin | 37 ++++--- core/encoding/cbor/unmarshal.odin | 19 ++-- tests/core/encoding/cbor/test_core_cbor.odin | 2 +- 5 files changed, 63 insertions(+), 115 deletions(-) diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 5719078c7..abb832ccf 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -26,9 +26,6 @@ Encoder_Flag :: enum { // NOTE: In order to do this, all keys of a map have to be pre-computed, sorted, and // then written, this involves temporary allocations for the keys and a copy of the map itself. Deterministic_Map_Sorting, - - // Internal flag to do initialization. - _In_Progress, } Encoder_Flags :: bit_set[Encoder_Flag] @@ -40,8 +37,9 @@ ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Determinis ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size} Encoder :: struct { - flags: Encoder_Flags, - writer: io.Writer, + flags: Encoder_Flags, + writer: io.Writer, + temp_allocator: runtime.Allocator, } Decoder_Flag :: enum { @@ -56,9 +54,6 @@ Decoder_Flag :: enum { // Makes the decoder shrink of excess capacity from allocated buffers/containers before returning. Shrink_Excess, - - // Internal flag to do initialization. - _In_Progress, } Decoder_Flags :: bit_set[Decoder_Flag] @@ -122,7 +117,9 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V d := d - _DECODE_PROGRESS_GUARD(&d) + if d.max_pre_alloc <= 0 { + d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC + } v, err = _decode_from_decoder(d) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. @@ -191,7 +188,7 @@ have to be precomputed, sorted and only then written to the output. Empty flags will do nothing extra to the value. -The allocations for the `.Deterministic_Map_Sorting` flag are done using the `context.temp_allocator` +The allocations for the `.Deterministic_Map_Sorting` flag are done using the given temp_allocator. but are followed by the necessary `delete` and `free` calls if the allocator supports them. This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. @@ -206,22 +203,22 @@ encode :: encode_into // Encodes the CBOR value into binary CBOR allocated on the given allocator. // See the docs on the proc group `encode_into` for more info. -encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator) -> (data: []byte, err: Encode_Error) { +encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) { b := strings.builder_make(allocator) or_return - encode_into_builder(&b, v, flags) or_return + encode_into_builder(&b, v, flags, temp_allocator) or_return return b.buf[:], nil } // Encodes the CBOR value into binary CBOR written to the given builder. // See the docs on the proc group `encode_into` for more info. -encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL) -> Encode_Error { - return encode_into_writer(strings.to_stream(b), v, flags) +encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { + return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator) } // Encodes the CBOR value into binary CBOR written to the given writer. // See the docs on the proc group `encode_into` for more info. -encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL) -> Encode_Error { - return encode_into_encoder(Encoder{flags, w}, v) +encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { + return encode_into_encoder(Encoder{flags, w, temp_allocator}, v) } // Encodes the CBOR value into binary CBOR written to the given encoder. @@ -229,8 +226,15 @@ encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL) -> Enc encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { e := e - _ENCODE_PROGRESS_GUARD(&e) or_return - + if e.temp_allocator.procedure == nil { + e.temp_allocator = context.temp_allocator + } + + if .Self_Described_CBOR in e.flags { + _encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return + e.flags &~= { .Self_Described_CBOR } + } + switch v_spec in v { case u8: return _encode_u8(e.writer, v_spec, .Unsigned) case u16: return _encode_u16(e, v_spec, .Unsigned) @@ -256,66 +260,6 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { } } -@(deferred_in_out=_decode_progress_end) -_DECODE_PROGRESS_GUARD :: proc(d: ^Decoder) -> (is_begin: bool, tmp: runtime.Arena_Temp) { - if ._In_Progress in d.flags { - return - } - is_begin = true - - d.flags |= { ._In_Progress } - - if context.allocator != context.temp_allocator { - tmp = runtime.default_temp_allocator_temp_begin() - } - - if d.max_pre_alloc <= 0 { - d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC - } - - return -} - -_decode_progress_end :: proc(d: ^Decoder, is_begin: bool, tmp: runtime.Arena_Temp) { - if !is_begin { - return - } - - d.flags &~= { ._In_Progress } - - runtime.default_temp_allocator_temp_end(tmp) -} - -@(deferred_in_out=_encode_progress_end) -_ENCODE_PROGRESS_GUARD :: proc(e: ^Encoder) -> (is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { - if ._In_Progress in e.flags { - return - } - is_begin = true - - e.flags |= { ._In_Progress } - - if context.allocator != context.temp_allocator { - tmp = runtime.default_temp_allocator_temp_begin() - } - - if .Self_Described_CBOR in e.flags { - _encode_u64(e^, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return - } - - return -} - -_encode_progress_end :: proc(e: ^Encoder, is_begin: bool, tmp: runtime.Arena_Temp, err: Encode_Error) { - if !is_begin || err != nil { - return - } - - e.flags &~= { ._In_Progress } - - runtime.default_temp_allocator_temp_end(tmp) -} - _decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { hdr = Header(_decode_u8(r) or_return) return @@ -602,13 +546,13 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { entry: Map_Entry, } - entries := make([]Map_Entry_With_Key, len(m), context.temp_allocator) or_return - defer delete(entries, context.temp_allocator) + entries := make([]Map_Entry_With_Key, len(m), e.temp_allocator) or_return + defer delete(entries, e.temp_allocator) for &entry, i in entries { entry.entry = m[i] - buf := strings.builder_make(context.temp_allocator) or_return + buf := strings.builder_make(e.temp_allocator) or_return ke := e ke.writer = strings.to_stream(&buf) @@ -624,7 +568,7 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { for entry in entries { io.write_full(e.writer, entry.encoded_key) or_return - delete(entry.encoded_key, context.temp_allocator) + delete(entry.encoded_key, e.temp_allocator) encode(e, entry.entry.value) or_return } diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin index efcad5c9e..ee8ba23a0 100644 --- a/core/encoding/cbor/doc.odin +++ b/core/encoding/cbor/doc.odin @@ -4,23 +4,21 @@ Also provided are conversion to and from JSON and the CBOR diagnostic format. **Allocations:** -In general, when in the following table it says allocations are done on the `context.temp_allocator`, these allocations +In general, when in the following table it says allocations are done on the `temp_allocator`, these allocations are still attempted to be deallocated. -This allows you to use an allocator with freeing implemented as the `context.temp_allocator` which is handy with big CBOR. +This allows you to use an allocator with freeing implemented as the `temp_allocator` which is handy with big CBOR. -If you use the default `context.temp_allocator` it will be returned back to its state when the process (en/decoding, (un)marshal) started. - -- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on `context.temp_allocator` +- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on the given `temp_allocator` some space for the keys of maps in order to sort them and then write them. Other than that there are no allocations (only for the final bytes if you use `cbor.encode_into_bytes`. - *Decoding*: Allocates everything on the given allocator and input given can be deleted after decoding. - *No* allocations are done on the `context.temp_allocator`. + *No* temporary allocations are done. - *Marshal*: Same allocation strategy as encoding. - *Unmarshal*: Allocates everything on the given allocator and input given can be deleted after unmarshalling. - Some temporary allocations are done on the `context.temp_allocator`. + Some temporary allocations are done on the given `temp_allocator`. **Determinism:** diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 7d93088cb..2ffb6b5b4 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -29,7 +29,7 @@ have to be precomputed, sorted and only then written to the output. Empty flags will do nothing extra to the value. -The allocations for the `.Deterministic_Map_Sorting` flag are done using the `context.temp_allocator` +The allocations for the `.Deterministic_Map_Sorting` flag are done using the given `temp_allocator`. but are followed by the necessary `delete` and `free` calls if the allocator supports them. This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. @@ -45,7 +45,7 @@ marshal :: marshal_into // Marshals the given value into a CBOR byte stream (allocated using the given allocator). // See docs on the `marshal_into` proc group for more info. -marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator) -> (bytes: []byte, err: Marshal_Error) { +marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) { b, alloc_err := strings.builder_make(allocator) // The builder as a stream also returns .EOF if it ran out of memory so this is consistent. if alloc_err != nil { @@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a defer if err != nil { strings.builder_destroy(&b) } - if err = marshal_into_builder(&b, v, flags); err != nil { + if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil { return } @@ -63,14 +63,14 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a // Marshals the given value into a CBOR byte stream written to the given builder. // See docs on the `marshal_into` proc group for more info. -marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL) -> Marshal_Error { - return marshal_into_writer(strings.to_writer(b), v, flags) +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator) } // Marshals the given value into a CBOR byte stream written to the given writer. // See docs on the `marshal_into` proc group for more info. -marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL) -> Marshal_Error { - encoder := Encoder{flags, w} +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { + encoder := Encoder{flags, w, temp_allocator} return marshal_into_encoder(encoder, v) } @@ -79,7 +79,14 @@ marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL) -> Mars marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { e := e - err_conv(_ENCODE_PROGRESS_GUARD(&e)) or_return + if e.temp_allocator.procedure == nil { + e.temp_allocator = context.temp_allocator + } + + if .Self_Described_CBOR in e.flags { + err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return + e.flags &~= { .Self_Described_CBOR } + } if v == nil { return _encode_nil(e.writer) @@ -321,7 +328,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { switch info.key.id { case string: - entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, context.temp_allocator) or_return + entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, e.temp_allocator) or_return defer delete(entries) for bucket_index in 0.. (err: Marshal_Error) { return case cstring: - entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, context.temp_allocator) or_return + entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, e.temp_allocator) or_return defer delete(entries) for bucket_index in 0.. (err: Marshal_Error) { return case: - entries := make([dynamic]Encoded_Entry, 0, map_cap, context.temp_allocator) or_return + entries := make([dynamic]Encoded_Entry, 0, map_cap, e.temp_allocator) or_return defer delete(entries) for bucket_index in 0.. (err: Marshal_Error) { name: string, field: int, } - entries := make([dynamic]Name, 0, n, context.temp_allocator) or_return + entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return defer delete(entries) for name, i in info.names { @@ -530,7 +537,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { case reflect.Type_Info_Named: err_conv(_encode_text(e, vt.name)) or_return case: - builder := strings.builder_make(context.temp_allocator) or_return + builder := strings.builder_make(e.temp_allocator) or_return defer strings.builder_destroy(&builder) reflect.write_type(&builder, vti) err_conv(_encode_text(e, strings.to_string(builder))) or_return diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index eef5d3d99..6e7f3c0bb 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -13,7 +13,7 @@ import "core:unicode/utf8" Unmarshals the given CBOR into the given pointer using reflection. Types that require allocation are allocated using the given allocator. -Some temporary allocations are done on the `context.temp_allocator`, but, if you want to, +Some temporary allocations are done on the given `temp_allocator`, but, if you want to, this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made. This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. @@ -31,8 +31,8 @@ unmarshal :: proc { unmarshal_from_string, } -unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator) -> (err: Unmarshal_Error) { - err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator=allocator) +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { + err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -40,23 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, } // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator) -> (err: Unmarshal_Error) { +unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { sr: strings.Reader r := strings.to_reader(&sr, s) - err = unmarshal_from_reader(r, ptr, flags, allocator) + err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator) -> (err: Unmarshal_Error) { +unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { d := d - _DECODE_PROGRESS_GUARD(&d) - - err = _unmarshal_any_ptr(d, ptr, allocator=allocator) + err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -64,8 +62,9 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca } -_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator) -> Unmarshal_Error { +_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error { context.allocator = allocator + context.temp_allocator = temp_allocator v := v if v == nil || v.id == nil { diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 8262e5da4..60c122a69 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -855,7 +855,7 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle buf: bytes.Buffer stream := bytes.buffer_to_stream(&buf) -encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream} +encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { bytes.buffer_reset(&buf) From 2a39c60fe4988339a910828ba6dcb022e3086d7a Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 6 Feb 2024 20:37:19 +0100 Subject: [PATCH 024/171] encoding/cbor: respect default to panic allocator --- core/encoding/cbor/tags.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index c9ddaed56..040ce2458 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -96,7 +96,7 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string // Controls initialization of default tag implementations. // JS and WASI default to a panic allocator so we don't want to do it on those. -INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_NIL_ALLOCATOR && ODIN_OS != .JS && ODIN_OS != .WASI) +INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR) @(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) tags_initialize_defaults :: proc() { From 9fc8587e2c2bff33a063531ef9045d48dc4a587e Mon Sep 17 00:00:00 2001 From: Laytan Date: Tue, 6 Feb 2024 20:41:53 +0100 Subject: [PATCH 025/171] encoding/cbor: untouch net/common.odin --- core/net/common.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 3cd1459a6..2a6f44602 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -413,5 +413,4 @@ DNS_Record_Header :: struct #packed { DNS_Host_Entry :: struct { name: string, addr: Address, -} - +} \ No newline at end of file From 04bd3cc525e5ef366043ace552bd0f3aa7cdd4b8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 12 Feb 2024 20:17:24 +0100 Subject: [PATCH 026/171] encoding/cbor: rename `diagnose` to `to_diagnostic_format` to be clearer --- core/encoding/cbor/cbor.odin | 24 ++++++++++---------- core/encoding/cbor/doc.odin | 4 ++-- tests/core/encoding/cbor/test_core_cbor.odin | 14 ++++++------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index f879a11aa..defae4163 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -307,23 +307,23 @@ destroy :: proc(val: Value, allocator := context.allocator) { } /* -diagnose either writes or returns a human-readable representation of the value, -optionally formatted, defined as the diagnostic format in section 8 of RFC 8949. +to_diagnostic_format either writes or returns a human-readable representation of the value, +optionally formatted, defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]]. Incidentally, if the CBOR does not contain any of the additional types defined on top of JSON this will also be valid JSON. */ -diagnose :: proc { - diagnostic_string, - diagnose_to_writer, +to_diagnostic_format :: proc { + to_diagnostic_format_string, + to_diagnostic_format_writer, } // Turns the given CBOR value into a human-readable string. // See docs on the proc group `diagnose` for more info. -diagnostic_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { +to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { b := strings.builder_make(allocator) w := strings.to_stream(&b) - err := diagnose_to_writer(w, val, padding) + err := to_diagnostic_format_writer(w, val, padding) if err == .EOF { // The string builder stream only returns .EOF, and only if it can't write (out of memory). return "", .Out_Of_Memory @@ -335,7 +335,7 @@ diagnostic_string :: proc(val: Value, padding := 0, allocator := context.allocat // Writes the given CBOR value into the writer as human-readable text. // See docs on the proc group `diagnose` for more info. -diagnose_to_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { +to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { @(require_results) indent :: proc(padding: int) -> int { padding := padding @@ -421,7 +421,7 @@ diagnose_to_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { newline(w, padding) or_return for entry, i in v { - diagnose(w, entry, padding) or_return + to_diagnostic_format(w, entry, padding) or_return if i != len(v)-1 { comma(w, padding) or_return newline(w, padding) or_return @@ -444,9 +444,9 @@ diagnose_to_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { newline(w, padding) or_return for entry, i in v { - diagnose(w, entry.key, padding) or_return + to_diagnostic_format(w, entry.key, padding) or_return io.write_string(w, ": ") or_return - diagnose(w, entry.value, padding) or_return + to_diagnostic_format(w, entry.value, padding) or_return if i != len(v)-1 { comma(w, padding) or_return newline(w, padding) or_return @@ -460,7 +460,7 @@ diagnose_to_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { case ^Tag: io.write_u64(w, v.number) or_return io.write_string(w, "(") or_return - diagnose(w, v.value, padding) or_return + to_diagnostic_format(w, v.value, padding) or_return io.write_string(w, ")") or_return case Simple: io.write_string(w, "simple(") or_return diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin index ee8ba23a0..77eac51cb 100644 --- a/core/encoding/cbor/doc.odin +++ b/core/encoding/cbor/doc.odin @@ -117,8 +117,8 @@ Example: assert(derr == nil) defer cbor.destroy(decoded) - // Turn the CBOR into a human readable representation. - diagnosis, eerr := cbor.diagnose(decoded) + // Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]]. + diagnosis, eerr := cbor.to_diagnostic_format(decoded) assert(eerr == nil) defer delete(diagnosis) diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 60c122a69..72244e1d3 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -212,7 +212,7 @@ test_marshalling :: proc(t: ^testing.T) { ev(t, derr, nil) defer cbor.destroy(decoded) - diagnosis, eerr := cbor.diagnose(decoded) + diagnosis, eerr := cbor.to_diagnostic_format(decoded) ev(t, eerr, nil) defer delete(diagnosis) @@ -379,7 +379,7 @@ test_marshalling_maybe :: proc(t: ^testing.T) { val, derr := cbor.decode(string(data)) expect_value(t, derr, nil) - expect_value(t, cbor.diagnose(val), "1") + expect_value(t, cbor.to_diagnostic_format(val), "1") maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) @@ -396,7 +396,7 @@ test_marshalling_nil_maybe :: proc(t: ^testing.T) { val, derr := cbor.decode(string(data)) expect_value(t, derr, nil) - expect_value(t, cbor.diagnose(val), "nil") + expect_value(t, cbor.to_diagnostic_format(val), "nil") maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) @@ -432,7 +432,7 @@ test_marshalling_union :: proc(t: ^testing.T) { val, derr := cbor.decode(string(data)) expect_value(t, derr, nil) - expect_value(t, cbor.diagnose(val, -1), `1010(["My_Distinct", "Hello, World!"])`) + expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`) dest: My_Union uerr := cbor.unmarshal(string(data), &dest) @@ -455,7 +455,7 @@ test_marshalling_union :: proc(t: ^testing.T) { val, derr := cbor.decode(string(data)) expect_value(t, derr, nil) - expect_value(t, cbor.diagnose(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) + expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) dest: My_Union_No_Nil uerr := cbor.unmarshal(string(data), &dest) @@ -810,7 +810,7 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t expect_value(t, reflect.union_variant_typeid(res), type, loc) expect_value(t, err, nil, loc) - str := cbor.diagnose(res, padding=-1) + str := cbor.to_diagnostic_format(res, padding=-1) defer delete(str) expect_value(t, str, decoded, loc) @@ -825,7 +825,7 @@ expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_de if tag, is_tag := res.(^cbor.Tag); is_tag { expect_value(t, tag.number, nr, loc) - str := cbor.diagnose(tag, padding=-1) + str := cbor.to_diagnostic_format(tag, padding=-1) defer delete(str) expect_value(t, str, value_decoded, loc) From 9a5f3fed8c89bccededf80308c7c6213ae760792 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 4 Mar 2024 17:26:49 +0100 Subject: [PATCH 027/171] encoding/cbor: fix conflict --- tests/core/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 3fa38cd34..6c5df7f66 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -51,7 +51,6 @@ noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise encoding_test: -<<<<<<< HEAD $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint From 9045c9ed0c91563bf1dc806e3da8ed0d123f4c12 Mon Sep 17 00:00:00 2001 From: olesya-wo <29059032+olesya-wo@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:53:31 +0300 Subject: [PATCH 028/171] Improved statistics for core/mem/Tracking_Allocator --- core/mem/tracking_allocator.odin | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index d6d189731..bdf6aa5e2 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -22,6 +22,12 @@ Tracking_Allocator :: struct { bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, mutex: sync.Mutex, clear_on_free_all: bool, + allocated: int, + alloc_count: int, + freed: int, + free_count: int, + peak: int, + current: int, } tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { @@ -44,6 +50,7 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) clear(&t.bad_free_array) + t.current = 0 sync.mutex_unlock(&t.mutex) } @@ -56,6 +63,19 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } +track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.allocated += entry.size + data.alloc_count += 1 + data.current += entry.size + if data.current > data.peak do data.peak = data.current +} + +track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.freed += entry.size + data.free_count += 1 + data.current -= entry.size +} + tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { @@ -100,13 +120,21 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, err = err, location = loc, } + track_alloc(data, &data.allocation_map[result_ptr]) case .Free: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } delete_key(&data.allocation_map, old_memory) case .Free_All: if data.clear_on_free_all { clear_map(&data.allocation_map) + data.current = 0 } case .Resize, .Resize_Non_Zeroed: + if old_memory != nil && old_memory in data.allocation_map { + track_free(data, &data.allocation_map[old_memory]) + } if old_memory != result_ptr { delete_key(&data.allocation_map, old_memory) } @@ -118,6 +146,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, err = err, location = loc, } + track_alloc(data, &data.allocation_map[result_ptr]) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) From d979129a50ae4b22ffb1196686d2cef760ce5141 Mon Sep 17 00:00:00 2001 From: olesya-wo <29059032+olesya-wo@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:32:17 +0300 Subject: [PATCH 029/171] Naming and type changes --- core/mem/tracking_allocator.odin | 45 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index bdf6aa5e2..9ed49a41b 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -22,12 +22,13 @@ Tracking_Allocator :: struct { bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, mutex: sync.Mutex, clear_on_free_all: bool, - allocated: int, - alloc_count: int, - freed: int, - free_count: int, - peak: int, - current: int, + + total_memory_allocated: i64, + total_allocation_count: i64, + total_memory_freed: i64, + total_free_count: i64, + peak_memory_allocated: i64, + current_memory_allocated: i64, } tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { @@ -50,7 +51,7 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) clear(&t.bad_free_array) - t.current = 0 + t.current_memory_allocated = 0 sync.mutex_unlock(&t.mutex) } @@ -63,22 +64,24 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } -track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { - data.allocated += entry.size - data.alloc_count += 1 - data.current += entry.size - if data.current > data.peak do data.peak = data.current -} - -track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { - data.freed += entry.size - data.free_count += 1 - data.current -= entry.size -} - tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { + track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_allocated += entry.size + data.total_allocation_count += 1 + data.current_memory_allocated += entry.size + if data.current_memory_allocated > data.peak_memory_allocated { + data.peak_memory_allocated = data.current_memory_allocated + } + } + + track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { + data.total_memory_freed += entry.size + data.total_free_count += 1 + data.current_memory_allocated -= entry.size + } + data := (^Tracking_Allocator)(allocator_data) sync.mutex_guard(&data.mutex) @@ -129,7 +132,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Free_All: if data.clear_on_free_all { clear_map(&data.allocation_map) - data.current = 0 + data.current_memory_allocated = 0 } case .Resize, .Resize_Non_Zeroed: if old_memory != nil && old_memory in data.allocation_map { From 51a4d97f03980838139b4883b1d62debe55105e8 Mon Sep 17 00:00:00 2001 From: olesya-wo <29059032+olesya-wo@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:25:54 +0300 Subject: [PATCH 030/171] type conversion fix --- core/mem/tracking_allocator.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index 9ed49a41b..bc624617d 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -68,18 +68,18 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { - data.total_memory_allocated += entry.size + data.total_memory_allocated += i64(entry.size) data.total_allocation_count += 1 - data.current_memory_allocated += entry.size + data.current_memory_allocated += i64(entry.size) if data.current_memory_allocated > data.peak_memory_allocated { data.peak_memory_allocated = data.current_memory_allocated } } track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { - data.total_memory_freed += entry.size + data.total_memory_freed += i64(entry.size) data.total_free_count += 1 - data.current_memory_allocated -= entry.size + data.current_memory_allocated -= i64(entry.size) } data := (^Tracking_Allocator)(allocator_data) From 730f992bff01543082332dc5e4921ac44da087ca Mon Sep 17 00:00:00 2001 From: RilleP Date: Wed, 10 Apr 2024 19:16:38 +0200 Subject: [PATCH 031/171] fix indentation --- core/odin/parser/parser.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 415ec949d..24a44f60e 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2995,7 +2995,7 @@ parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { } p.expr_level -= 1 - skip_possible_newline(p) + skip_possible_newline(p) close := expect_closing_brace_of_field_list(p); pos := type.pos if type != nil else open.pos From 330c1616255e2f22b8c4dfde57d6a3b17d038a15 Mon Sep 17 00:00:00 2001 From: RilleP Date: Thu, 11 Apr 2024 09:36:28 +0200 Subject: [PATCH 032/171] remove semicolon --- core/odin/parser/parser.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 24a44f60e..b2ffd3888 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2996,7 +2996,7 @@ parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { p.expr_level -= 1 skip_possible_newline(p) - close := expect_closing_brace_of_field_list(p); + close := expect_closing_brace_of_field_list(p) pos := type.pos if type != nil else open.pos lit := ast.new(ast.Comp_Lit, pos, end_pos(close)) From 0abbf3ba0a9ea53aa9276dfb43bbf95d949c2d91 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 13:57:37 +0100 Subject: [PATCH 033/171] Fix #3412 --- src/checker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checker.cpp b/src/checker.cpp index 244e7efbd..3556c4647 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -703,7 +703,7 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { array_add(&vetted_entities, ve_unused); } else if (is_shadowed) { array_add(&vetted_entities, ve_shadowed); - } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using)) == 0) { + } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using)) == 0 && !e->Variable.is_global) { i64 sz = type_size_of(e->type); // TODO(bill): When is a good size warn? // Is 128 KiB good enough? From 6c38ae36580156171c453579942991b431f6b27b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:24:08 +0100 Subject: [PATCH 034/171] Remove `#optional_ok` from docs --- base/intrinsics/intrinsics.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 458596adf..dca33bfd9 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -38,9 +38,9 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- -overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- From b6d2ac11b8a3fa34542333966f3958602b6ba2c8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:24:36 +0100 Subject: [PATCH 035/171] Add `-vet-unused-variables` and ``-vet-unused-imports` (`-vet-unused` is both) --- src/build_settings.cpp | 21 ++++++++++++++------- src/checker.cpp | 5 ++++- src/main.cpp | 31 +++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 30d6f0b3c..ad1b3e884 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -272,13 +272,16 @@ enum BuildPath : u8 { }; enum VetFlags : u64 { - VetFlag_NONE = 0, - VetFlag_Unused = 1u<<0, // 1 - VetFlag_Shadowing = 1u<<1, // 2 - VetFlag_UsingStmt = 1u<<2, // 4 - VetFlag_UsingParam = 1u<<3, // 8 - VetFlag_Style = 1u<<4, // 16 - VetFlag_Semicolon = 1u<<5, // 32 + VetFlag_NONE = 0, + VetFlag_Shadowing = 1u<<0, + VetFlag_UsingStmt = 1u<<1, + VetFlag_UsingParam = 1u<<2, + VetFlag_Style = 1u<<3, + VetFlag_Semicolon = 1u<<4, + VetFlag_UnusedVariables = 1u<<5, + VetFlag_UnusedImports = 1u<<6, + + VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt, @@ -288,6 +291,10 @@ enum VetFlags : u64 { u64 get_vet_flag_from_name(String const &name) { if (name == "unused") { return VetFlag_Unused; + } else if (name == "unused-variables") { + return VetFlag_UnusedVariables; + } else if (name == "unused-imports") { + return VetFlag_UnusedImports; } else if (name == "shadowing") { return VetFlag_Shadowing; } else if (name == "using-stmt") { diff --git a/src/checker.cpp b/src/checker.cpp index 3556c4647..35554cf44 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -728,7 +728,10 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { } else if (vet_flags) { switch (ve.kind) { case VettedEntity_Unused: - if (vet_flags & VetFlag_Unused) { + if (e->kind == Entity_Variable && (vet_flags & VetFlag_UnusedVariables) != 0) { + error(e->token, "'%.*s' declared but not used", LIT(name)); + } + if ((e->kind == Entity_ImportName || e->kind == Entity_LibraryName) && (vet_flags & VetFlag_UnusedImports) != 0) { error(e->token, "'%.*s' declared but not used", LIT(name)); } break; diff --git a/src/main.cpp b/src/main.cpp index 2dbb72ca2..36a99ec32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,6 +253,8 @@ enum BuildFlagKind { BuildFlag_Vet, BuildFlag_VetShadowing, BuildFlag_VetUnused, + BuildFlag_VetUnusedImports, + BuildFlag_VetUnusedVariables, BuildFlag_VetUsingStmt, BuildFlag_VetUsingParam, BuildFlag_VetStyle, @@ -444,6 +446,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUnused, str_lit("vet-unused"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetUnusedVariables, str_lit("vet-unused-variables"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetUnusedImports, str_lit("vet-unused-imports"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetShadowing, str_lit("vet-shadowing"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUsingStmt, str_lit("vet-using-stmt"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUsingParam, str_lit("vet-using-param"), BuildFlagParam_None, Command__does_check); @@ -1026,10 +1030,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_UseSeparateModules: build_context.use_separate_modules = true; break; - case BuildFlag_NoThreadedChecker: { + case BuildFlag_NoThreadedChecker: build_context.no_threaded_checker = true; break; - } case BuildFlag_ShowDebugMessages: build_context.show_debug_messages = true; break; @@ -1037,12 +1040,14 @@ gb_internal bool parse_build_flags(Array args) { build_context.vet_flags |= VetFlag_All; break; - case BuildFlag_VetUnused: build_context.vet_flags |= VetFlag_Unused; break; - case BuildFlag_VetShadowing: build_context.vet_flags |= VetFlag_Shadowing; break; - case BuildFlag_VetUsingStmt: build_context.vet_flags |= VetFlag_UsingStmt; break; - case BuildFlag_VetUsingParam: build_context.vet_flags |= VetFlag_UsingParam; break; - case BuildFlag_VetStyle: build_context.vet_flags |= VetFlag_Style; break; - case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; + case BuildFlag_VetUnusedVariables: build_context.vet_flags |= VetFlag_UnusedVariables; break; + case BuildFlag_VetUnusedImports: build_context.vet_flags |= VetFlag_UnusedImports; break; + case BuildFlag_VetUnused: build_context.vet_flags |= VetFlag_Unused; break; + case BuildFlag_VetShadowing: build_context.vet_flags |= VetFlag_Shadowing; break; + case BuildFlag_VetUsingStmt: build_context.vet_flags |= VetFlag_UsingStmt; break; + case BuildFlag_VetUsingParam: build_context.vet_flags |= VetFlag_UsingParam; break; + case BuildFlag_VetStyle: build_context.vet_flags |= VetFlag_Style; break; + case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; case BuildFlag_IgnoreUnknownAttributes: build_context.ignore_unknown_attributes = true; @@ -1875,6 +1880,8 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Does extra checks on the code."); print_usage_line(2, "Extra checks include:"); print_usage_line(3, "-vet-unused"); + print_usage_line(3, "-vet-unused-variables"); + print_usage_line(3, "-vet-unused-imports"); print_usage_line(3, "-vet-shadowing"); print_usage_line(3, "-vet-using-stmt"); print_usage_line(0, ""); @@ -1883,6 +1890,14 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Checks for unused declarations."); print_usage_line(0, ""); + print_usage_line(1, "-vet-unused-variables"); + print_usage_line(2, "Checks for unused variable declarations."); + print_usage_line(0, ""); + + print_usage_line(1, "-vet-unused-imports"); + print_usage_line(2, "Checks for unused import declarations."); + print_usage_line(0, ""); + print_usage_line(1, "-vet-shadowing"); print_usage_line(2, "Checks for variable shadowing within procedures."); print_usage_line(0, ""); From 45d7a670ce689b5be046e023102871566cac9b7b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:34:34 +0100 Subject: [PATCH 036/171] Fix `@(static)` error message bug --- src/entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 6cea0930f..a12e1d0a6 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -496,7 +496,7 @@ gb_internal bool is_entity_local_variable(Entity *e) { if (e->scope == nullptr) { return true; } - if (e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue)) { + if (e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue|EntityFlag_Static)) { return false; } From f36fb6d1ef6ad1f4c5dc56b9b761e843195546b6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:41:01 +0100 Subject: [PATCH 037/171] Add `nil` checks --- src/check_stmt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index fc3b9aa43..a6def5997 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2355,14 +2355,14 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { unsafe_return_error(o, "the address of a compound literal"); } else if (x->kind == Ast_IndexExpr) { Entity *f = entity_of_node(x->IndexExpr.expr); - if (is_type_array_like(f->type) || is_type_matrix(f->type)) { + if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) { if (is_entity_local_variable(f)) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } } else if (x->kind == Ast_MatrixIndexExpr) { Entity *f = entity_of_node(x->MatrixIndexExpr.expr); - if (is_type_matrix(f->type) && is_entity_local_variable(f)) { + if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } From b2e887be36a23dbfc52e50eb054ccceeeea31692 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:55:01 +0100 Subject: [PATCH 038/171] Change stack overflow check to >256 KiB --- src/checker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 35554cf44..900a4f243 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -706,8 +706,8 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using)) == 0 && !e->Variable.is_global) { i64 sz = type_size_of(e->type); // TODO(bill): When is a good size warn? - // Is 128 KiB good enough? - if (sz >= 1ll<<17) { + // Is >256 KiB good enough? + if (sz > 1ll<<18) { gbString type_str = type_to_string(e->type); warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); gb_string_free(type_str); From 503964c7699125e2388762780f7121a704e6e25a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:57:13 +0100 Subject: [PATCH 039/171] Add @(static) check --- src/checker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 900a4f243..0d72c1e76 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -703,13 +703,13 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { array_add(&vetted_entities, ve_unused); } else if (is_shadowed) { array_add(&vetted_entities, ve_shadowed); - } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using)) == 0 && !e->Variable.is_global) { + } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using|EntityFlag_Static)) == 0 && !e->Variable.is_global) { i64 sz = type_size_of(e->type); // TODO(bill): When is a good size warn? // Is >256 KiB good enough? if (sz > 1ll<<18) { gbString type_str = type_to_string(e->type); - warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); + warning(e->token, "Declaration of '%.*s' may cause a stack overflow? due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); gb_string_free(type_str); } } From aab122ede8b04a9877e22c9013c0b020186bc9b4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 15:59:22 +0100 Subject: [PATCH 040/171] Remove `?` --- src/checker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checker.cpp b/src/checker.cpp index 0d72c1e76..2b3ca0e9f 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -709,7 +709,7 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { // Is >256 KiB good enough? if (sz > 1ll<<18) { gbString type_str = type_to_string(e->type); - warning(e->token, "Declaration of '%.*s' may cause a stack overflow? due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); + warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); gb_string_free(type_str); } } From cb0a57eaa99499a6f427fef0f9476085df476464 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 11 Apr 2024 17:18:51 +0100 Subject: [PATCH 041/171] Add `-target:freestanding_amd64_win64` --- src/build_settings.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ad1b3e884..106ae8a28 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -631,6 +631,15 @@ gb_global TargetMetrics target_freestanding_amd64_sysv = { TargetABI_SysV, }; +gb_global TargetMetrics target_freestanding_amd64_win64 = { + TargetOs_freestanding, + TargetArch_amd64, + 8, 8, 8, 16, + str_lit("x86_64-pc-none-msvc"), + str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), + TargetABI_Win64, +}; + gb_global TargetMetrics target_freestanding_arm64 = { TargetOs_freestanding, TargetArch_arm64, @@ -672,7 +681,9 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("js_wasm64p32"), &target_js_wasm64p32 }, { str_lit("wasi_wasm64p32"), &target_wasi_wasm64p32 }, - { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, + { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, + { str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 }, + { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, }; From 4cdadeedc3b46f91dd9d863b9a8f94d9ac820067 Mon Sep 17 00:00:00 2001 From: alec hodgkinson Date: Thu, 11 Apr 2024 09:55:43 -0700 Subject: [PATCH 042/171] Added docs for trig function procedure groups --- core/math/math.odin | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/math/math.odin b/core/math/math.odin index 570c2d255..8d85c2381 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -60,6 +60,7 @@ sqrt :: proc{ @(require_results) sin_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(sin_f32(f32(θ))) } @(require_results) sin_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(sin_f64(f64(θ))) } @(require_results) sin_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(sin_f64(f64(θ))) } +// Return the sine of θ in radians. sin :: proc{ sin_f16, sin_f16le, sin_f16be, sin_f32, sin_f32le, sin_f32be, @@ -72,6 +73,7 @@ sin :: proc{ @(require_results) cos_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(cos_f32(f32(θ))) } @(require_results) cos_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(cos_f64(f64(θ))) } @(require_results) cos_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(cos_f64(f64(θ))) } +// Return the cosine of θ in radians. cos :: proc{ cos_f16, cos_f16le, cos_f16be, cos_f32, cos_f32le, cos_f32be, @@ -378,6 +380,7 @@ log10 :: proc{ @(require_results) tan_f64 :: proc "contextless" (θ: f64) -> f64 { return sin(θ)/cos(θ) } @(require_results) tan_f64le :: proc "contextless" (θ: f64le) -> f64le { return f64le(tan_f64(f64(θ))) } @(require_results) tan_f64be :: proc "contextless" (θ: f64be) -> f64be { return f64be(tan_f64(f64(θ))) } +// Return the tangent of θ in radians. tan :: proc{ tan_f16, tan_f16le, tan_f16be, tan_f32, tan_f32le, tan_f32be, @@ -1752,7 +1755,28 @@ atan2_f64be :: proc "contextless" (y, x: f64be) -> f64be { // TODO(bill): Better atan2_f32 return f64be(atan2_f64(f64(y), f64(x))) } +/* + Return the arc tangent of y/x in radians. Defined on the domain [-∞, ∞] for x and y with a range of [-π, π] + Special cases: + atan2(y, NaN) = NaN + atan2(NaN, x) = NaN + atan2(+0, x>=0) = + 0 + atan2(-0, x>=0) = - 0 + atan2(+0, x<=-0) = + π + atan2(-0, x<=-0) = - π + atan2(y>0, 0) = + π/2 + atan2(y<0, 0) = - π/2 + atan2(+∞, +∞) = + π/4 + atan2(-∞, +∞) = - π/4 + atan2(+∞, -∞) = 3π/4 + atan2(-∞, -∞) = - 3π/4 + atan2(y, +∞) = 0 + atan2(y>0, -∞) = + π + atan2(y<0, -∞) = - π + atan2(+∞, x) = + π/2 + atan2(-∞, x) = - π/2 +*/ atan2 :: proc{ atan2_f64, atan2_f32, atan2_f16, atan2_f64le, atan2_f64be, @@ -1760,6 +1784,7 @@ atan2 :: proc{ atan2_f16le, atan2_f16be, } +// Return the arc tangent of x, in radians. Defined on the domain of [-∞, ∞] with a range of [-π/2, π/2] @(require_results) atan :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) { return atan2(x, 1) @@ -1871,6 +1896,7 @@ asin_f16le :: proc "contextless" (x: f16le) -> f16le { asin_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(asin_f64(f64(x))) } +// Return the arc sine of x, in radians. Defined on the domain of [-1, 1] with a range of [-π/2, π/2] asin :: proc{ asin_f64, asin_f32, asin_f16, asin_f64le, asin_f64be, @@ -1985,6 +2011,7 @@ acos_f16le :: proc "contextless" (x: f16le) -> f16le { acos_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(acos_f64(f64(x))) } +// Return the arc cosine of x, in radians. Defined on the domain of [-1, 1] with a range of [0, π]. acos :: proc{ acos_f64, acos_f32, acos_f16, acos_f64le, acos_f64be, From efc3f9916ec4217cd511f561166cb5f4348295c5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 12:30:16 +0100 Subject: [PATCH 043/171] Fix #3414 --- src/check_type.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index f1d991acb..f4e5d7c96 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3233,6 +3233,11 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T Type *elem = t_invalid; Operand o = {}; + if (unparen_expr(pt->type) == nullptr) { + error(e, "Invalid pointer type"); + return false; + } + check_expr_or_type(&c, &o, pt->type); if (o.mode != Addressing_Invalid && o.mode != Addressing_Type) { if (o.mode == Addressing_Variable) { From 3426af2d6cb5944d373470d10ca91f827a8f39ca Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 12:33:25 +0100 Subject: [PATCH 044/171] Fix #3415 --- src/common_memory.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index c6ee88f03..60e570eee 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -163,6 +163,10 @@ gb_internal void platform_virtual_memory_protect(void *memory, isize size); GB_ASSERT(is_protected); } #else + #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) + #define MAP_ANONYMOUS MAP_ANON + #endif + gb_internal void platform_virtual_memory_init(void) { global_platform_memory_block_sentinel.prev = &global_platform_memory_block_sentinel; global_platform_memory_block_sentinel.next = &global_platform_memory_block_sentinel; From 46b9bd8c0e3987080f94ae42921b513a79708ef9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 13:35:14 +0100 Subject: [PATCH 045/171] Improve error messages for `switch` and `for` r-values with a suggestion --- src/check_stmt.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/error.cpp | 21 ++++++++++++++++----- src/tokenizer.cpp | 1 + 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index a6def5997..f2b7f8661 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -474,16 +474,59 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } Entity *e = entity_of_node(lhs->expr); + Entity *original_e = e; + + Ast *name = unparen_expr(lhs->expr); + while (name->kind == Ast_SelectorExpr) { + name = name->SelectorExpr.expr; + e = entity_of_node(name); + } + if (e == nullptr) { + e = original_e; + } gbString str = expr_to_string(lhs->expr); if (e != nullptr && e->flags & EntityFlag_Param) { + ERROR_BLOCK(); if (e->flags & EntityFlag_Using) { error(lhs->expr, "Cannot assign to '%s' which is from a 'using' procedure parameter", str); } else { error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str); } + error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string)); + show_error_on_line(e->token.pos, token_pos_end(e->token)); } else { + ERROR_BLOCK(); error(lhs->expr, "Cannot assign to '%s'", str); + + if (e) if (e->flags & EntityFlag_ForValue) { + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + if (offset < 0) { + if (is_type_map(e->type)) { + error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); + } + } else { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + + } else if (e->flags & EntityFlag_SwitchValue) { + isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); + if (offset < 0) { + error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + } } gb_string_free(str); diff --git a/src/error.cpp b/src/error.cpp index eb167d4c3..8647f60b9 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -84,6 +84,7 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_file_path_strings.count) { array_resize(&global_file_path_strings, index+1); @@ -94,6 +95,7 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { ok = true; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return ok; } @@ -102,6 +104,7 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_files.count) { array_resize(&global_files, index+1); @@ -111,7 +114,7 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { global_files[index] = file; ok = true; } - + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return ok; } @@ -119,12 +122,14 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); String path = {}; if (index < global_file_path_strings.count) { path = global_file_path_strings[index]; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return path; } @@ -132,12 +137,14 @@ gb_internal String get_file_path_string(i32 index) { gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); AstFile *file = nullptr; if (index < global_files.count) { file = global_files[index]; } + mutex_unlock(&global_files_mutex); mutex_unlock(&global_error_collector.path_mutex); return file; } @@ -247,10 +254,10 @@ gb_internal void terminal_reset_colours(void) { } -gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { +gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char const *prefix=nullptr) { get_error_value()->end = end; if (!show_error_line()) { - return false; + return -1; } i32 offset = 0; @@ -270,6 +277,10 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { MAX_LINE_LENGTH_PADDED = MAX_LINE_LENGTH-MAX_TAB_WIDTH-ELLIPSIS_PADDING, }; + if (prefix) { + error_out("\t%s\n\n", prefix); + } + error_out("\t"); terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); @@ -328,9 +339,9 @@ gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { terminal_reset_colours(); error_out("\n"); - return true; + return offset; } - return false; + return -1; } gb_internal void error_out_empty(void) { diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 3d5348074..fdff9224a 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -193,6 +193,7 @@ gb_internal void init_keyword_hash_table(void) { gb_global Array global_file_path_strings; // index is file id gb_global Array global_files; // index is file id +gb_global BlockingMutex global_files_mutex; gb_internal String get_file_path_string(i32 index); gb_internal struct AstFile *thread_safe_get_ast_file_from_id(i32 index); From caa344c88d7b2f736da58a8b9c88b510caafb68c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 14:05:36 +0100 Subject: [PATCH 046/171] Simplify scalar -> array conversions in LLVM to use a loop after a certain size --- src/llvm_backend_expr.cpp | 13 +++++++++++-- src/llvm_backend_general.cpp | 10 ---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index fcec59968..c97e63c26 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2143,9 +2143,18 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lbAddr v = lb_add_local_generated(p, t, false); isize index_count = cast(isize)get_array_type_count(dst); - for (isize i = 0; i < index_count; i++) { - lbValue elem = lb_emit_array_epi(p, v.addr, i); + if (type_size_of(dst) > build_context.max_simd_align) { + auto loop_data = lb_loop_start(p, index_count, t_int); + + lbValue elem = lb_emit_array_ep(p, v.addr, loop_data.idx); lb_emit_store(p, elem, e); + + lb_loop_end(p, loop_data); + } else { + for (isize i = 0; i < index_count; i++) { + lbValue elem = lb_emit_array_epi(p, v.addr, i); + lb_emit_store(p, elem, e); + } } return lb_addr_load(p, v); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 0d8d9258a..73e4a00e6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -954,16 +954,6 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { GB_ASSERT(value.value != nullptr); value = lb_emit_conv(p, value, lb_addr_type(addr)); - // if (lb_is_const_or_global(value)) { - // // NOTE(bill): Just bypass the actual storage and set the initializer - // if (LLVMGetValueKind(addr.addr.value) == LLVMGlobalVariableValueKind) { - // LLVMValueRef dst = addr.addr.value; - // LLVMValueRef src = value.value; - // LLVMSetInitializer(dst, src); - // return; - // } - // } - lb_emit_store(p, addr.addr, value); } From 4240e0025e1db18821148210e7c5260faef7d830 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 14:20:46 +0100 Subject: [PATCH 047/171] Improve scalar -> array assignment when the scalar is constant in LLVM --- src/llvm_backend_expr.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c97e63c26..ad28f2e5e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2138,12 +2138,34 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_array_like(dst)) { Type *elem = base_array_type(dst); - lbValue e = lb_emit_conv(p, value, elem); - // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops - lbAddr v = lb_add_local_generated(p, t, false); isize index_count = cast(isize)get_array_type_count(dst); - if (type_size_of(dst) > build_context.max_simd_align) { + isize inlineable = type_size_of(dst) <= build_context.max_simd_align; + lbValue e = lb_emit_conv(p, value, elem); + if (inlineable && lb_is_const(e)) { + lbAddr v = {}; + if (e.value) { + TEMPORARY_ALLOCATOR_GUARD(); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, index_count); + for (isize i = 0; i < index_count; i++) { + values[i] = e.value; + } + lbValue array_const_value = {}; + array_const_value.type = t; + array_const_value.value = LLVMConstArray(lb_type(m, elem), values, cast(unsigned)index_count); + v = lb_add_global_generated(m, t, array_const_value); + } else { + v = lb_add_global_generated(m, t); + } + + lb_make_global_private_const(v); + return lb_addr_load(p, v); + } + + // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops + lbAddr v = lb_add_local_generated(p, t, false); + + if (!inlineable) { auto loop_data = lb_loop_start(p, index_count, t_int); lbValue elem = lb_emit_array_ep(p, v.addr, loop_data.idx); From 5726b7d9541d62f39cb5b04412f283a02e13c077 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Apr 2024 14:51:22 +0100 Subject: [PATCH 048/171] Remove warning on clang --- src/check_stmt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f2b7f8661..971841165 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -499,7 +499,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O ERROR_BLOCK(); error(lhs->expr, "Cannot assign to '%s'", str); - if (e) if (e->flags & EntityFlag_ForValue) { + if (e && e->flags & EntityFlag_ForValue) { isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); if (offset < 0) { if (is_type_map(e->type)) { @@ -515,7 +515,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); } - } else if (e->flags & EntityFlag_SwitchValue) { + } else if (e && e->flags & EntityFlag_SwitchValue) { isize offset = show_error_on_line(e->token.pos, token_pos_end(e->token), "Suggestion:"); if (offset < 0) { error_line("\tSuggestion: Did you mean? 'switch &%.*s in ...'\n", LIT(e->token.string)); From c753711d86fda7392c2556741de0fd9b70d7c2e1 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:39:32 +1000 Subject: [PATCH 049/171] Added support for URL fragments Added support for a URL's fragment/anchor to `split_url` & `join_url` in `core:net` plus 4 new tests to cover it. --- core/net/url.odin | 17 +++++- tests/core/net/test_core_net.odin | 86 +++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 7ad88bd1f..5257b757c 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -21,7 +21,7 @@ import "core:strconv" import "core:unicode/utf8" import "core:encoding/hex" -split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) { +split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string, fragment: string) { s := url i := strings.index(s, "://") @@ -30,6 +30,12 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, s = s[i+3:] } + i = strings.index_byte(s, '#') + if i != -1 { + fragment = s[i+1:] + s = s[:i] + } + i = strings.index(s, "?") if i != -1 { query_str := s[i+1:] @@ -62,7 +68,7 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, return } -join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string { +join_url :: proc(scheme, host, path: string, queries: map[string]string, fragment: string, allocator := context.allocator) -> string { b := strings.builder_make(allocator) strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path)) @@ -95,6 +101,13 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, allocat i += 1 } + if fragment != "" { + if fragment[0] != '#' { + strings.write_byte(&b, '#') + } + strings.write_string(&b, strings.trim_space(fragment)) + } + return strings.to_string(b) } diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 579298904..2a581c66b 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -473,6 +473,7 @@ client_sends_server_data :: proc(t: ^testing.T) { URL_Test :: struct { scheme, host, path: string, queries: map[string]string, + fragment: string, url: []string, } @@ -481,58 +482,78 @@ split_url_test :: proc(t: ^testing.T) { test_cases := []URL_Test{ { "http", "example.com", "/", - {}, + {}, "", {"http://example.com"}, }, { "https", "odin-lang.org", "/", - {}, + {}, "", {"https://odin-lang.org"}, }, { "https", "odin-lang.org", "/docs/", - {}, + {}, "", {"https://odin-lang.org/docs/"}, }, { "https", "odin-lang.org", "/docs/overview", - {}, + {}, "", {"https://odin-lang.org/docs/overview"}, }, { "http", "example.com", "/", - {"a" = "b"}, + {"a" = "b"}, "", {"http://example.com?a=b"}, }, { "http", "example.com", "/", - {"a" = ""}, + {"a" = ""}, "", {"http://example.com?a"}, }, { "http", "example.com", "/", - {"a" = "b", "c" = "d"}, + {"a" = "b", "c" = "d"}, "", {"http://example.com?a=b&c=d"}, }, { "http", "example.com", "/", - {"a" = "", "c" = "d"}, + {"a" = "", "c" = "d"}, "", {"http://example.com?a&c=d"}, }, { "http", "example.com", "/example", - {"a" = "", "b" = ""}, + {"a" = "", "b" = ""}, "", {"http://example.com/example?a&b"}, }, { "https", "example.com", "/callback", - {"redirect" = "https://other.com/login"}, + {"redirect" = "https://other.com/login"}, "", {"https://example.com/callback?redirect=https://other.com/login"}, }, + { + "http", "odin-lang.org", "/", + {}, "Hellope", + {"http://odin-lang.org#Hellope"} + }, + { + "https", "odin-lang.org", "/", + {"a" = ""}, "Hellope", + {"https://odin-lang.org?a#Hellope"} + }, + { + "http", "example.com", "/", + {"a" = "b"}, "Hellope", + {"http://example.com?a=b#Hellope"} + }, + { + "https", "example.com", "/example", + {}, "Hellope", + {"https://example.com/example#Hellope"} + }, } for test in test_cases { - scheme, host, path, queries := net.split_url(test.url[0]) + scheme, host, path, queries, fragment := net.split_url(test.url[0]) defer { delete(queries) delete(test.queries) @@ -551,6 +572,9 @@ split_url_test :: proc(t: ^testing.T) { msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v) expect(t, v == expected, msg) } + msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment) + expect(t, fragment == test.fragment, msg) + } } @@ -560,53 +584,73 @@ join_url_test :: proc(t: ^testing.T) { test_cases := []URL_Test{ { "http", "example.com", "/", - {}, + {}, "", {"http://example.com/"}, }, { "https", "odin-lang.org", "/", - {}, + {}, "", {"https://odin-lang.org/"}, }, { "https", "odin-lang.org", "/docs/", - {}, + {}, "", {"https://odin-lang.org/docs/"}, }, { "https", "odin-lang.org", "/docs/overview", - {}, + {}, "", {"https://odin-lang.org/docs/overview"}, }, { "http", "example.com", "/", - {"a" = "b"}, + {"a" = "b"}, "", {"http://example.com/?a=b"}, }, { "http", "example.com", "/", - {"a" = ""}, + {"a" = ""}, "", {"http://example.com/?a"}, }, { "http", "example.com", "/", - {"a" = "b", "c" = "d"}, + {"a" = "b", "c" = "d"}, "", {"http://example.com/?a=b&c=d", "http://example.com/?c=d&a=b"}, }, { "http", "example.com", "/", - {"a" = "", "c" = "d"}, + {"a" = "", "c" = "d"}, "", {"http://example.com/?a&c=d", "http://example.com/?c=d&a"}, }, { "http", "example.com", "/example", - {"a" = "", "b" = ""}, + {"a" = "", "b" = ""}, "", {"http://example.com/example?a&b", "http://example.com/example?b&a"}, }, + { + "http", "odin-lang.org", "", + {}, "Hellope", + {"http://odin-lang.org#Hellope"} + }, + { + "https", "odin-lang.org", "", + {"a" = ""}, "Hellope", + {"https://odin-lang.org?a#Hellope"} + }, + { + "http", "example.com", "", + {"a" = "b"}, "Hellope", + {"http://example.com?a=b#Hellope"} + }, + { + "https", "example.com", "/example", + {}, "Hellope", + {"https://example.com/example#Hellope"} + }, } for test in test_cases { - url := net.join_url(test.scheme, test.host, test.path, test.queries) + url := net.join_url(test.scheme, test.host, test.path, test.queries, test.fragment) defer { delete(url) delete(test.queries) From 2d1260bec9874907fd6545bfbabc0829c697b361 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:47:49 +1000 Subject: [PATCH 050/171] uniformity change small change to check things uniform --- core/net/url.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 5257b757c..16aa57ec5 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -30,7 +30,7 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, s = s[i+3:] } - i = strings.index_byte(s, '#') + i = strings.index(s, "#") if i != -1 { fragment = s[i+1:] s = s[:i] @@ -103,7 +103,7 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, fragmen if fragment != "" { if fragment[0] != '#' { - strings.write_byte(&b, '#') + strings.write_string(&b, "#") } strings.write_string(&b, strings.trim_space(fragment)) } From 6348b56c8bfced2fc202517a322bbf9505725384 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:57:36 +1000 Subject: [PATCH 051/171] Move rounded tests --- tests/core/net/test_core_net.odin | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 2a581c66b..52d22a9f0 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -531,9 +531,9 @@ split_url_test :: proc(t: ^testing.T) { {"https://example.com/callback?redirect=https://other.com/login"}, }, { - "http", "odin-lang.org", "/", + "http", "example.com", "/", {}, "Hellope", - {"http://odin-lang.org#Hellope"} + {"http://example.com#Hellope"} }, { "https", "odin-lang.org", "/", @@ -542,13 +542,13 @@ split_url_test :: proc(t: ^testing.T) { }, { "http", "example.com", "/", - {"a" = "b"}, "Hellope", - {"http://example.com?a=b#Hellope"} + {"a" = "b"}, "BeesKnees", + {"http://example.com?a=b#BeesKnees"} }, { - "https", "example.com", "/example", - {}, "Hellope", - {"https://example.com/example#Hellope"} + "https", "odin-lang.org", "/docs/overview/", + {}, "hellope", + {"https://odin-lang.org/docs/overview/#hellope"} }, } @@ -628,24 +628,24 @@ join_url_test :: proc(t: ^testing.T) { {"http://example.com/example?a&b", "http://example.com/example?b&a"}, }, { - "http", "odin-lang.org", "", + "http", "example.com", "/", {}, "Hellope", - {"http://odin-lang.org#Hellope"} + {"http://example.com/#Hellope"} }, { - "https", "odin-lang.org", "", + "https", "odin-lang.org", "/", {"a" = ""}, "Hellope", - {"https://odin-lang.org?a#Hellope"} + {"https://odin-lang.org/?a#Hellope"} }, { - "http", "example.com", "", - {"a" = "b"}, "Hellope", - {"http://example.com?a=b#Hellope"} + "http", "example.com", "/", + {"a" = "b"}, "BeesKnees", + {"http://example.com/?a=b#BeesKnees"} }, { - "https", "example.com", "/example", - {}, "Hellope", - {"https://example.com/example#Hellope"} + "https", "odin-lang.org", "/docs/overview/", + {}, "hellope", + {"https://odin-lang.org/docs/overview/#hellope"} }, } From a4d16e97a1c48481ff6cda5c7541a552f1e6d9e8 Mon Sep 17 00:00:00 2001 From: blob1807 <12388588+blob1807@users.noreply.github.com> Date: Sat, 13 Apr 2024 01:14:55 +1000 Subject: [PATCH 052/171] Fix CI's parser --- tests/core/net/test_core_net.odin | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 52d22a9f0..9df03414c 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -533,22 +533,22 @@ split_url_test :: proc(t: ^testing.T) { { "http", "example.com", "/", {}, "Hellope", - {"http://example.com#Hellope"} + {"http://example.com#Hellope"}, }, { "https", "odin-lang.org", "/", {"a" = ""}, "Hellope", - {"https://odin-lang.org?a#Hellope"} + {"https://odin-lang.org?a#Hellope"}, }, { "http", "example.com", "/", {"a" = "b"}, "BeesKnees", - {"http://example.com?a=b#BeesKnees"} + {"http://example.com?a=b#BeesKnees"}, }, { "https", "odin-lang.org", "/docs/overview/", {}, "hellope", - {"https://odin-lang.org/docs/overview/#hellope"} + {"https://odin-lang.org/docs/overview/#hellope"}, }, } @@ -630,22 +630,22 @@ join_url_test :: proc(t: ^testing.T) { { "http", "example.com", "/", {}, "Hellope", - {"http://example.com/#Hellope"} + {"http://example.com/#Hellope"}, }, { "https", "odin-lang.org", "/", {"a" = ""}, "Hellope", - {"https://odin-lang.org/?a#Hellope"} + {"https://odin-lang.org/?a#Hellope"}, }, { "http", "example.com", "/", {"a" = "b"}, "BeesKnees", - {"http://example.com/?a=b#BeesKnees"} + {"http://example.com/?a=b#BeesKnees"}, }, { "https", "odin-lang.org", "/docs/overview/", {}, "hellope", - {"https://odin-lang.org/docs/overview/#hellope"} + {"https://odin-lang.org/docs/overview/#hellope"}, }, } From d5bb67e9e65caa14c962e796e57cb81478ef35f6 Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 12 Apr 2024 20:47:02 +0200 Subject: [PATCH 053/171] Fixup vendor/d3d12 calling convention mistakes --- vendor/directx/d3d12/d3d12.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vendor/directx/d3d12/d3d12.odin b/vendor/directx/d3d12/d3d12.odin index c39c2c9c9..7c4065d8b 100644 --- a/vendor/directx/d3d12/d3d12.odin +++ b/vendor/directx/d3d12/d3d12.odin @@ -2283,7 +2283,7 @@ IHeap :: struct #raw_union { } IHeap_VTable :: struct { using id3d12devicechild_vtable: IDeviceChild_VTable, - GetDesc: proc "system" (this: ^IHeap) -> HEAP_DESC, + GetDesc: proc "system" (this: ^IHeap, pRetValue: ^HEAP_DESC) -> ^HEAP_DESC, } @@ -2297,7 +2297,7 @@ IResource_VTable :: struct { using id3d12devicechild_vtable: IDeviceChild_VTable, Map: proc "system" (this: ^IResource, Subresource: u32, pReadRange: ^RANGE, ppData: ^rawptr) -> HRESULT, Unmap: proc "system" (this: ^IResource, Subresource: u32, pWrittenRange: ^RANGE), - GetDesc: proc "system" (this: ^IResource) -> RESOURCE_DESC, + GetDesc: proc "system" (this: ^IResource, pRetValue: ^RESOURCE_DESC) -> ^RESOURCE_DESC, GetGPUVirtualAddress: proc "system" (this: ^IResource) -> GPU_VIRTUAL_ADDRESS, WriteToSubresource: proc "system" (this: ^IResource, DstSubresource: u32, pDstBox: ^BOX, pSrcData: rawptr, SrcRowPitch: u32, SrcDepthPitch: u32) -> HRESULT, ReadFromSubresource: proc "system" (this: ^IResource, pDstData: rawptr, DstRowPitch: u32, DstDepthPitch: u32, SrcSubresource: u32, pSrcBox: ^BOX) -> HRESULT, @@ -2514,7 +2514,7 @@ ICommandQueue_VTable :: struct { Wait: proc "system" (this: ^ICommandQueue, pFence: ^IFence, Value: u64) -> HRESULT, GetTimestampFrequency: proc "system" (this: ^ICommandQueue, pFrequency: ^u64) -> HRESULT, GetClockCalibration: proc "system" (this: ^ICommandQueue, pGpuTimestamp: ^u64, pCpuTimestamp: ^u64) -> HRESULT, - GetDesc: proc "system" (this: ^ICommandQueue) -> COMMAND_QUEUE_DESC, + GetDesc: proc "system" (this: ^ICommandQueue, pRetVal: ^COMMAND_QUEUE_DESC) -> ^COMMAND_QUEUE_DESC, } @@ -2712,7 +2712,7 @@ IProtectedResourceSession :: struct #raw_union { } IProtectedResourceSession_VTable :: struct { using id3d12protectedsession_vtable: IProtectedSession_VTable, - GetDesc: proc "system" (this: ^IProtectedResourceSession) -> PROTECTED_RESOURCE_SESSION_DESC, + GetDesc: proc "system" (this: ^IProtectedResourceSession, pRetVal: ^PROTECTED_RESOURCE_SESSION_DESC) -> ^PROTECTED_RESOURCE_SESSION_DESC, } @@ -3492,7 +3492,7 @@ IProtectedResourceSession1 :: struct #raw_union { } IProtectedResourceSession1_VTable :: struct { using id3d12protectedresourcesession_vtable: IProtectedResourceSession_VTable, - GetDesc1: proc "system" (this: ^IProtectedResourceSession1) -> PROTECTED_RESOURCE_SESSION_DESC1, + GetDesc1: proc "system" (this: ^IProtectedResourceSession1, pRetVal: ^PROTECTED_RESOURCE_SESSION_DESC1) -> ^PROTECTED_RESOURCE_SESSION_DESC1, } @@ -3545,7 +3545,7 @@ IResource2 :: struct #raw_union { } IResource2_VTable :: struct { using id3d12resource1_vtable: IResource1_VTable, - GetDesc1: proc "system" (this: ^IResource2) -> RESOURCE_DESC1, + GetDesc1: proc "system" (this: ^IResource2, pRetVal: ^RESOURCE_DESC1) -> ^RESOURCE_DESC1, } From befb0f786891415358c33ff53de6b265335c45f5 Mon Sep 17 00:00:00 2001 From: Vitalii Kravchenko Date: Tue, 2 Apr 2024 22:23:28 +0100 Subject: [PATCH 054/171] Core Foundation and Security vendor libraries. --- core/crypto/rand_bsd.odin | 2 +- core/crypto/rand_darwin.odin | 14 +- core/crypto/rand_generic.odin | 2 +- core/crypto/rand_js.odin | 2 +- core/crypto/rand_linux.odin | 2 +- core/crypto/rand_windows.odin | 2 +- core/os/stat.odin | 4 +- core/sys/darwin/CoreFoundation/CFBase.odin | 34 ++ core/sys/darwin/CoreFoundation/CFString.odin | 203 ++++++++++ core/sys/darwin/Foundation/NSString.odin | 15 +- core/sys/darwin/Security/SecBase.odin | 386 +++++++++++++++++++ core/sys/darwin/Security/SecRandom.odin | 19 + core/sys/darwin/core_foundation.odin | 98 ----- core/sys/darwin/security.odin | 26 -- examples/demo/demo.odin | 2 +- 15 files changed, 664 insertions(+), 147 deletions(-) create mode 100644 core/sys/darwin/CoreFoundation/CFBase.odin create mode 100644 core/sys/darwin/CoreFoundation/CFString.odin create mode 100644 core/sys/darwin/Security/SecBase.odin create mode 100644 core/sys/darwin/Security/SecRandom.odin delete mode 100644 core/sys/darwin/core_foundation.odin delete mode 100644 core/sys/darwin/security.odin diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index 61eaf652f..7a0c42683 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -11,6 +11,6 @@ _rand_bytes :: proc(dst: []byte) { arc4random_buf(raw_data(dst), len(dst)) } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return true } diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin index 2864b46dd..c1a3d1dbc 100644 --- a/core/crypto/rand_darwin.odin +++ b/core/crypto/rand_darwin.odin @@ -1,16 +1,18 @@ package crypto import "core:fmt" -import "core:sys/darwin" + +import CF "core:sys/darwin/CoreFoundation" +import Sec "core:sys/darwin/Security" _rand_bytes :: proc(dst: []byte) { - res := darwin.SecRandomCopyBytes(count=len(dst), bytes=raw_data(dst)) - if res != .Success { - msg := darwin.CFStringCopyToOdinString(darwin.SecCopyErrorMessageString(res)) - panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", res, msg)) + err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst)) + if err != .Success { + msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) + panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)) } } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return true } diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index 006ca51fe..cba49f700 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -10,6 +10,6 @@ _rand_bytes :: proc(dst: []byte) { unimplemented("crypto: rand_bytes not supported on this OS") } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return false } diff --git a/core/crypto/rand_js.odin b/core/crypto/rand_js.odin index cb2711404..90f60b99b 100644 --- a/core/crypto/rand_js.odin +++ b/core/crypto/rand_js.odin @@ -19,6 +19,6 @@ _rand_bytes :: proc(dst: []byte) { } } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return true } diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin index 05c05597d..a9dc37415 100644 --- a/core/crypto/rand_linux.odin +++ b/core/crypto/rand_linux.odin @@ -35,6 +35,6 @@ _rand_bytes :: proc (dst: []byte) { } } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return true } diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index e1d9f6118..5cafe7fb5 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -22,6 +22,6 @@ _rand_bytes :: proc(dst: []byte) { } } -_has_rand_bytes :: proc () -> bool { +_has_rand_bytes :: proc() -> bool { return true } diff --git a/core/os/stat.odin b/core/os/stat.odin index 1b64ad33b..21a4961d1 100644 --- a/core/os/stat.odin +++ b/core/os/stat.odin @@ -3,8 +3,8 @@ package os import "core:time" File_Info :: struct { - fullpath: string, - name: string, + fullpath: string, // allocated + name: string, // uses `fullpath` as underlying data size: i64, mode: File_Mode, is_dir: bool, diff --git a/core/sys/darwin/CoreFoundation/CFBase.odin b/core/sys/darwin/CoreFoundation/CFBase.odin new file mode 100644 index 000000000..7335f087b --- /dev/null +++ b/core/sys/darwin/CoreFoundation/CFBase.odin @@ -0,0 +1,34 @@ +package CoreFoundation + +foreign import CoreFoundation "system:CoreFoundation.framework" + +TypeID :: distinct uint +OptionFlags :: distinct uint +HashCode :: distinct uint +Index :: distinct int +TypeRef :: distinct rawptr + +Range :: struct { + location: Index, + length: Index, +} + +foreign CoreFoundation { + // Releases a Core Foundation object. + CFRelease :: proc(cf: TypeRef) --- +} + +// Releases a Core Foundation object. +Release :: proc { + ReleaseObject, + ReleaseString, +} + +ReleaseObject :: #force_inline proc(cf: TypeRef) { + CFRelease(cf) +} + +// Releases a Core Foundation string. +ReleaseString :: #force_inline proc(theString: String) { + CFRelease(TypeRef(theString)) +} diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin new file mode 100644 index 000000000..4a167c604 --- /dev/null +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -0,0 +1,203 @@ +package CoreFoundation + +import "base:runtime" + +foreign import CoreFoundation "system:CoreFoundation.framework" + +String :: distinct TypeRef // same as CFStringRef + +StringEncoding :: distinct u32 + +StringBuiltInEncodings :: enum StringEncoding { + MacRoman = 0, + WindowsLatin1 = 0x0500, + ISOLatin1 = 0x0201, + NextStepLatin = 0x0B01, + ASCII = 0x0600, + Unicode = 0x0100, + UTF8 = 0x08000100, + NonLossyASCII = 0x0BFF, + + UTF16 = 0x0100, + UTF16BE = 0x10000100, + UTF16LE = 0x14000100, + + UTF32 = 0x0c000100, + UTF32BE = 0x18000100, + UTF32LE = 0x1c000100, +} + +StringEncodings :: enum Index { + MacJapanese = 1, + MacChineseTrad = 2, + MacKorean = 3, + MacArabic = 4, + MacHebrew = 5, + MacGreek = 6, + MacCyrillic = 7, + MacDevanagari = 9, + MacGurmukhi = 10, + MacGujarati = 11, + MacOriya = 12, + MacBengali = 13, + MacTamil = 14, + MacTelugu = 15, + MacKannada = 16, + MacMalayalam = 17, + MacSinhalese = 18, + MacBurmese = 19, + MacKhmer = 20, + MacThai = 21, + MacLaotian = 22, + MacGeorgian = 23, + MacArmenian = 24, + MacChineseSimp = 25, + MacTibetan = 26, + MacMongolian = 27, + MacEthiopic = 28, + MacCentralEurRoman = 29, + MacVietnamese = 30, + MacExtArabic = 31, + MacSymbol = 33, + MacDingbats = 34, + MacTurkish = 35, + MacCroatian = 36, + MacIcelandic = 37, + MacRomanian = 38, + MacCeltic = 39, + MacGaelic = 40, + MacFarsi = 0x8C, + MacUkrainian = 0x98, + MacInuit = 0xEC, + MacVT100 = 0xFC, + MacHFS = 0xFF, + ISOLatin2 = 0x0202, + ISOLatin3 = 0x0203, + ISOLatin4 = 0x0204, + ISOLatinCyrillic = 0x0205, + ISOLatinArabic = 0x0206, + ISOLatinGreek = 0x0207, + ISOLatinHebrew = 0x0208, + ISOLatin5 = 0x0209, + ISOLatin6 = 0x020A, + ISOLatinThai = 0x020B, + ISOLatin7 = 0x020D, + ISOLatin8 = 0x020E, + ISOLatin9 = 0x020F, + ISOLatin10 = 0x0210, + DOSLatinUS = 0x0400, + DOSGreek = 0x0405, + DOSBalticRim = 0x0406, + DOSLatin1 = 0x0410, + DOSGreek1 = 0x0411, + DOSLatin2 = 0x0412, + DOSCyrillic = 0x0413, + DOSTurkish = 0x0414, + DOSPortuguese = 0x0415, + DOSIcelandic = 0x0416, + DOSHebrew = 0x0417, + DOSCanadianFrench = 0x0418, + DOSArabic = 0x0419, + DOSNordic = 0x041A, + DOSRussian = 0x041B, + DOSGreek2 = 0x041C, + DOSThai = 0x041D, + DOSJapanese = 0x0420, + DOSChineseSimplif = 0x0421, + DOSKorean = 0x0422, + DOSChineseTrad = 0x0423, + WindowsLatin2 = 0x0501, + WindowsCyrillic = 0x0502, + WindowsGreek = 0x0503, + WindowsLatin5 = 0x0504, + WindowsHebrew = 0x0505, + WindowsArabic = 0x0506, + WindowsBalticRim = 0x0507, + WindowsVietnamese = 0x0508, + WindowsKoreanJohab = 0x0510, + ANSEL = 0x0601, + JIS_X0201_76 = 0x0620, + JIS_X0208_83 = 0x0621, + JIS_X0208_90 = 0x0622, + JIS_X0212_90 = 0x0623, + JIS_C6226_78 = 0x0624, + ShiftJIS_X0213 = 0x0628, + ShiftJIS_X0213_MenKuTen = 0x0629, + GB_2312_80 = 0x0630, + GBK_95 = 0x0631, + GB_18030_2000 = 0x0632, + KSC_5601_87 = 0x0640, + KSC_5601_92_Johab = 0x0641, + CNS_11643_92_P1 = 0x0651, + CNS_11643_92_P2 = 0x0652, + CNS_11643_92_P3 = 0x0653, + ISO_2022_JP = 0x0820, + ISO_2022_JP_2 = 0x0821, + ISO_2022_JP_1 = 0x0822, + ISO_2022_JP_3 = 0x0823, + ISO_2022_CN = 0x0830, + ISO_2022_CN_EXT = 0x0831, + ISO_2022_KR = 0x0840, + EUC_JP = 0x0920, + EUC_CN = 0x0930, + EUC_TW = 0x0931, + EUC_KR = 0x0940, + ShiftJIS = 0x0A01, + KOI8_R = 0x0A02, + Big5 = 0x0A03, + MacRomanLatin1 = 0x0A04, + HZ_GB_2312 = 0x0A05, + Big5_HKSCS_1999 = 0x0A06, + VISCII = 0x0A07, + KOI8_U = 0x0A08, + Big5_E = 0x0A09, + NextStepJapanese = 0x0B02, + EBCDIC_US = 0x0C01, + EBCDIC_CP037 = 0x0C02, + UTF7 = 0x04000100, + UTF7_IMAP = 0x0A10, + ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. +} + +@(link_prefix = "CF", default_calling_convention = "c") +foreign CoreFoundation { + // Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding. + StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 --- + + // Returns the number (in terms of UTF-16 code pairs) of Unicode characters in a string. + StringGetLength :: proc(theString: String) -> Index --- + + // Returns the maximum number of bytes a string of a specified length (in Unicode characters) will take up if encoded in a specified encoding. + StringGetMaximumSizeForEncoding :: proc(length: Index, encoding: StringEncoding) -> Index --- + + // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. + StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index --- + + StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool --- + + @(link_name = "__CFStringMakeConstantString") + StringMakeConstantString :: proc "c" (#const c: cstring) -> String --- +} + +STR :: StringMakeConstantString + +StringCopyToOdinString :: proc( + theString: String, + allocator := context.allocator, +) -> ( + str: string, + ok: bool, +) #optional_ok { + length := StringGetLength(theString) + max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8)) + + buf, err := make([]byte, max, allocator) + if err != nil do return + + raw_str := runtime.Raw_String { + data = raw_data(buf), + } + StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), max, (^Index)(&raw_str.len)) + + return transmute(string)raw_str, true +} diff --git a/core/sys/darwin/Foundation/NSString.odin b/core/sys/darwin/Foundation/NSString.odin index d3c6c454d..b4918b3fb 100644 --- a/core/sys/darwin/Foundation/NSString.odin +++ b/core/sys/darwin/Foundation/NSString.odin @@ -23,12 +23,9 @@ StringEncoding :: enum UInteger { WindowsCP1250 = 15, ISO2022JP = 21, MacOSRoman = 30, - UTF16 = Unicode, - UTF16BigEndian = 0x90000100, UTF16LittleEndian = 0x94000100, - UTF32 = 0x8c000100, UTF32BigEndian = 0x98000100, UTF32LittleEndian = 0x9c000100, @@ -49,12 +46,9 @@ StringCompareOption :: enum UInteger { unichar :: distinct u16 -@(link_prefix="NS", default_calling_convention="c") -foreign Foundation { - StringFromClass :: proc(cls: Class) -> ^String --- -} - AT :: MakeConstantString + +// CFString is 'toll-free bridged' with its Cocoa Foundation counterpart, NSString. MakeConstantString :: proc "c" (#const c: cstring) -> ^String { foreign Foundation { __CFStringMakeConstantString :: proc "c" (c: cstring) -> ^String --- @@ -62,6 +56,10 @@ MakeConstantString :: proc "c" (#const c: cstring) -> ^String { return __CFStringMakeConstantString(c) } +@(link_prefix="NS", default_calling_convention="c") +foreign Foundation { + StringFromClass :: proc(cls: Class) -> ^String --- +} @(objc_type=String, objc_name="alloc", objc_is_class_method=true) String_alloc :: proc "c" () -> ^String { @@ -73,7 +71,6 @@ String_init :: proc "c" (self: ^String) -> ^String { return msgSend(^String, self, "init") } - @(objc_type=String, objc_name="initWithString") String_initWithString :: proc "c" (self: ^String, other: ^String) -> ^String { return msgSend(^String, self, "initWithString:", other) diff --git a/core/sys/darwin/Security/SecBase.odin b/core/sys/darwin/Security/SecBase.odin new file mode 100644 index 000000000..9cc82d6f5 --- /dev/null +++ b/core/sys/darwin/Security/SecBase.odin @@ -0,0 +1,386 @@ +package Security + +OSStatus :: distinct i32 + +errSec :: enum OSStatus { + Success = 0, // No error. + Unimplemented = -4, // Function or operation not implemented. + DiskFull = -34, // The disk is full. + IO = -36, // I/O error. + OpWr = -49, // File already open with with write permission. + Param = -50, // One or more parameters passed to a function were not valid. + WrPerm = -61, // Write permissions error. + Allocate = -108, // Failed to allocate memory. + UserCanceled = -128, // User canceled the operation. + BadReq = -909, // Bad parameter or invalid state for operation. + InternalComponent = -2070, + CoreFoundationUnknown = -4960, + MissingEntitlement, // A required entitlement isn't present. + RestrictedAPI, // Client is restricted and is not permitted to perform this operation. + NotAvailable = -25291, // No keychain is available. You may need to restart your computer. + ReadOnly = -25292, // This keychain cannot be modified. + AuthFailed = -25293, // The user name or passphrase you entered is not correct. + NoSuchKeychain = -25294, // The specified keychain could not be found. + InvalidKeychain = -25295, // The specified keychain is not a valid keychain file. + DuplicateKeychain = -25296, // A keychain with the same name already exists. + DuplicateCallback = -25297, // The specified callback function is already installed. + InvalidCallback = -25298, // The specified callback function is not valid. + DuplicateItem = -25299, // The specified item already exists in the keychain. + ItemNotFound = -25300, // The specified item could not be found in the keychain. + BufferTooSmall = -25301, // There is not enough memory available to use the specified item. + DataTooLarge = -25302, // This item contains information which is too large or in a format that cannot be displayed. + NoSuchAttr = -25303, // The specified attribute does not exist. + InvalidItemRef = -25304, // The specified item is no longer valid. It may have been deleted from the keychain. + InvalidSearchRef = -25305, // Unable to search the current keychain. + NoSuchClass = -25306, // The specified item does not appear to be a valid keychain item. + NoDefaultKeychain = -25307, // A default keychain could not be found. + InteractionNotAllowed = -25308, // User interaction is not allowed. + ReadOnlyAttr = -25309, // The specified attribute could not be modified. + WrongSecVersion = -25310, // This keychain was created by a different version of the system software and cannot be opened. + KeySizeNotAllowed = -25311, // This item specifies a key size which is too large or too small. + NoStorageModule = -25312, // A required component (data storage module) could not be loaded. You may need to restart your computer. + NoCertificateModule = -25313, // A required component (certificate module) could not be loaded. You may need to restart your computer. + NoPolicyModule = -25314, // A required component (policy module) could not be loaded. You may need to restart your computer. + InteractionRequired = -25315, // User interaction is required, but is currently not allowed. + DataNotAvailable = -25316, // The contents of this item cannot be retrieved. + DataNotModifiable = -25317, // The contents of this item cannot be modified. + CreateChainFailed = -25318, // One or more certificates required to validate this certificate cannot be found. + InvalidPrefsDomain = -25319, // The specified preferences domain is not valid. + InDarkWake = -25320, // In dark wake, no UI possible + ACLNotSimple = -25240, // The specified access control list is not in standard (simple) form. + PolicyNotFound = -25241, // The specified policy cannot be found. + InvalidTrustSetting = -25242, // The specified trust setting is invalid. + NoAccessForItem = -25243, // The specified item has no access control. + InvalidOwnerEdit = -25244, // Invalid attempt to change the owner of this item. + TrustNotAvailable = -25245, // No trust results are available. + UnsupportedFormat = -25256, // Import/Export format unsupported. + UnknownFormat = -25257, // Unknown format in import. + KeyIsSensitive = -25258, // Key material must be wrapped for export. + MultiplePrivKeys = -25259, // An attempt was made to import multiple private keys. + PassphraseRequired = -25260, // Passphrase is required for import/export. + InvalidPasswordRef = -25261, // The password reference was invalid. + InvalidTrustSettings = -25262, // The Trust Settings Record was corrupted. + NoTrustSettings = -25263, // No Trust Settings were found. + Pkcs12VerifyFailure = -25264, // MAC verification failed during PKCS12 import (wrong password?) + NotSigner = -26267, // A certificate was not signed by its proposed parent. + Decode = -26275, // Unable to decode the provided data. + ServiceNotAvailable = -67585, // The required service is not available. + InsufficientClientID = -67586, // The client ID is not correct. + DeviceReset = -67587, // A device reset has occurred. + DeviceFailed = -67588, // A device failure has occurred. + AppleAddAppACLSubject = -67589, // Adding an application ACL subject failed. + ApplePublicKeyIncomplete = -67590, // The public key is incomplete. + AppleSignatureMismatch = -67591, // A signature mismatch has occurred. + AppleInvalidKeyStartDate = -67592, // The specified key has an invalid start date. + AppleInvalidKeyEndDate = -67593, // The specified key has an invalid end date. + ConversionError = -67594, // A conversion error has occurred. + AppleSSLv2Rollback = -67595, // A SSLv2 rollback error has occurred. + QuotaExceeded = -67596, // The quota was exceeded. + FileTooBig = -67597, // The file is too big. + InvalidDatabaseBlob = -67598, // The specified database has an invalid blob. + InvalidKeyBlob = -67599, // The specified database has an invalid key blob. + IncompatibleDatabaseBlob = -67600, // The specified database has an incompatible blob. + IncompatibleKeyBlob = -67601, // The specified database has an incompatible key blob. + HostNameMismatch = -67602, // A host name mismatch has occurred. + UnknownCriticalExtensionFlag = -67603, // There is an unknown critical extension flag. + NoBasicConstraints = -67604, // No basic constraints were found. + NoBasicConstraintsCA = -67605, // No basic CA constraints were found. + InvalidAuthorityKeyID = -67606, // The authority key ID is not valid. + InvalidSubjectKeyID = -67607, // The subject key ID is not valid. + InvalidKeyUsageForPolicy = -67608, // The key usage is not valid for the specified policy. + InvalidExtendedKeyUsage = -67609, // The extended key usage is not valid. + InvalidIDLinkage = -67610, // The ID linkage is not valid. + PathLengthConstraintExceeded = -67611, // The path length constraint was exceeded. + InvalidRoot = -67612, // The root or anchor certificate is not valid. + CRLExpired = -67613, // The CRL has expired. + CRLNotValidYet = -67614, // The CRL is not yet valid. + CRLNotFound = -67615, // The CRL was not found. + CRLServerDown = -67616, // The CRL server is down. + CRLBadURI = -67617, // The CRL has a bad Uniform Resource Identifier. + UnknownCertExtension = -67618, // An unknown certificate extension was encountered. + UnknownCRLExtension = -67619, // An unknown CRL extension was encountered. + CRLNotTrusted = -67620, // The CRL is not trusted. + CRLPolicyFailed = -67621, // The CRL policy failed. + IDPFailure = -67622, // The issuing distribution point was not valid. + SMIMEEmailAddressesNotFound = -67623, // An email address mismatch was encountered. + SMIMEBadExtendedKeyUsage = -67624, // The appropriate extended key usage for SMIME was not found. + SMIMEBadKeyUsage = -67625, // The key usage is not compatible with SMIME. + SMIMEKeyUsageNotCritical = -67626, // The key usage extension is not marked as critical. + SMIMENoEmailAddress = -67627, // No email address was found in the certificate. + SMIMESubjAltNameNotCritical = -67628, // The subject alternative name extension is not marked as critical. + SSLBadExtendedKeyUsage = -67629, // The appropriate extended key usage for SSL was not found. + OCSPBadResponse = -67630, // The OCSP response was incorrect or could not be parsed. + OCSPBadRequest = -67631, // The OCSP request was incorrect or could not be parsed. + OCSPUnavailable = -67632, // OCSP service is unavailable. + OCSPStatusUnrecognized = -67633, // The OCSP server did not recognize this certificate. + EndOfData = -67634, // An end-of-data was detected. + IncompleteCertRevocationCheck = -67635, // An incomplete certificate revocation check occurred. + NetworkFailure = -67636, // A network failure occurred. + OCSPNotTrustedToAnchor = -67637, // The OCSP response was not trusted to a root or anchor certificate. + RecordModified = -67638, // The record was modified. + OCSPSignatureError = -67639, // The OCSP response had an invalid signature. + OCSPNoSigner = -67640, // The OCSP response had no signer. + OCSPResponderMalformedReq = -67641, // The OCSP responder was given a malformed request. + OCSPResponderInternalError = -67642, // The OCSP responder encountered an internal error. + OCSPResponderTryLater = -67643, // The OCSP responder is busy, try again later. + OCSPResponderSignatureRequired = -67644, // The OCSP responder requires a signature. + OCSPResponderUnauthorized = -67645, // The OCSP responder rejected this request as unauthorized. + OCSPResponseNonceMismatch = -67646, // The OCSP response nonce did not match the request. + CodeSigningBadCertChainLength = -67647, // Code signing encountered an incorrect certificate chain length. + CodeSigningNoBasicConstraints = -67648, // Code signing found no basic constraints. + CodeSigningBadPathLengthConstraint = -67649, // Code signing encountered an incorrect path length constraint. + CodeSigningNoExtendedKeyUsage = -67650, // Code signing found no extended key usage. + CodeSigningDevelopment = -67651, // Code signing indicated use of a development-only certificate. + ResourceSignBadCertChainLength = -67652, // Resource signing has encountered an incorrect certificate chain length. + ResourceSignBadExtKeyUsage = -67653, // Resource signing has encountered an error in the extended key usage. + TrustSettingDeny = -67654, // The trust setting for this policy was set to Deny. + InvalidSubjectName = -67655, // An invalid certificate subject name was encountered. + UnknownQualifiedCertStatement = -67656, // An unknown qualified certificate statement was encountered. + MobileMeRequestQueued = -67657, + MobileMeRequestRedirected = -67658, + MobileMeServerError = -67659, + MobileMeServerNotAvailable = -67660, + MobileMeServerAlreadyExists = -67661, + MobileMeServerServiceErr = -67662, + MobileMeRequestAlreadyPending = -67663, + MobileMeNoRequestPending = -67664, + MobileMeCSRVerifyFailure = -67665, + MobileMeFailedConsistencyCheck = -67666, + NotInitialized = -67667, // A function was called without initializing CSSM. + InvalidHandleUsage = -67668, // The CSSM handle does not match with the service type. + PVCReferentNotFound = -67669, // A reference to the calling module was not found in the list of authorized callers. + FunctionIntegrityFail = -67670, // A function address was not within the verified module. + InternalError = -67671, // An internal error has occurred. + MemoryError = -67672, // A memory error has occurred. + InvalidData = -67673, // Invalid data was encountered. + MDSError = -67674, // A Module Directory Service error has occurred. + InvalidPointer = -67675, // An invalid pointer was encountered. + SelfCheckFailed = -67676, // Self-check has failed. + FunctionFailed = -67677, // A function has failed. + ModuleManifestVerifyFailed = -67678, // A module manifest verification failure has occurred. + InvalidGUID = -67679, // An invalid GUID was encountered. + InvalidHandle = -67680, // An invalid handle was encountered. + InvalidDBList = -67681, // An invalid DB list was encountered. + InvalidPassthroughID = -67682, // An invalid passthrough ID was encountered. + InvalidNetworkAddress = -67683, // An invalid network address was encountered. + CRLAlreadySigned = -67684, // The certificate revocation list is already signed. + InvalidNumberOfFields = -67685, // An invalid number of fields were encountered. + VerificationFailure = -67686, // A verification failure occurred. + UnknownTag = -67687, // An unknown tag was encountered. + InvalidSignature = -67688, // An invalid signature was encountered. + InvalidName = -67689, // An invalid name was encountered. + InvalidCertificateRef = -67690, // An invalid certificate reference was encountered. + InvalidCertificateGroup = -67691, // An invalid certificate group was encountered. + TagNotFound = -67692, // The specified tag was not found. + InvalidQuery = -67693, // The specified query was not valid. + InvalidValue = -67694, // An invalid value was detected. + CallbackFailed = -67695, // A callback has failed. + ACLDeleteFailed = -67696, // An ACL delete operation has failed. + ACLReplaceFailed = -67697, // An ACL replace operation has failed. + ACLAddFailed = -67698, // An ACL add operation has failed. + ACLChangeFailed = -67699, // An ACL change operation has failed. + InvalidAccessCredentials = -67700, // Invalid access credentials were encountered. + InvalidRecord = -67701, // An invalid record was encountered. + InvalidACL = -67702, // An invalid ACL was encountered. + InvalidSampleValue = -67703, // An invalid sample value was encountered. + IncompatibleVersion = -67704, // An incompatible version was encountered. + PrivilegeNotGranted = -67705, // The privilege was not granted. + InvalidScope = -67706, // An invalid scope was encountered. + PVCAlreadyConfigured = -67707, // The PVC is already configured. + InvalidPVC = -67708, // An invalid PVC was encountered. + EMMLoadFailed = -67709, // The EMM load has failed. + EMMUnloadFailed = -67710, // The EMM unload has failed. + AddinLoadFailed = -67711, // The add-in load operation has failed. + InvalidKeyRef = -67712, // An invalid key was encountered. + InvalidKeyHierarchy = -67713, // An invalid key hierarchy was encountered. + AddinUnloadFailed = -67714, // The add-in unload operation has failed. + LibraryReferenceNotFound = -67715, // A library reference was not found. + InvalidAddinFunctionTable = -67716, // An invalid add-in function table was encountered. + InvalidServiceMask = -67717, // An invalid service mask was encountered. + ModuleNotLoaded = -67718, // A module was not loaded. + InvalidSubServiceID = -67719, // An invalid subservice ID was encountered. + AttributeNotInContext = -67720, // An attribute was not in the context. + ModuleManagerInitializeFailed = -67721, // A module failed to initialize. + ModuleManagerNotFound = -67722, // A module was not found. + EventNotificationCallbackNotFound = -67723, // An event notification callback was not found. + InputLengthError = -67724, // An input length error was encountered. + OutputLengthError = -67725, // An output length error was encountered. + PrivilegeNotSupported = -67726, // The privilege is not supported. + DeviceError = -67727, // A device error was encountered. + AttachHandleBusy = -67728, // The CSP handle was busy. + NotLoggedIn = -67729, // You are not logged in. + AlgorithmMismatch = -67730, // An algorithm mismatch was encountered. + KeyUsageIncorrect = -67731, // The key usage is incorrect. + KeyBlobTypeIncorrect = -67732, // The key blob type is incorrect. + KeyHeaderInconsistent = -67733, // The key header is inconsistent. + UnsupportedKeyFormat = -67734, // The key header format is not supported. + UnsupportedKeySize = -67735, // The key size is not supported. + InvalidKeyUsageMask = -67736, // The key usage mask is not valid. + UnsupportedKeyUsageMask = -67737, // The key usage mask is not supported. + InvalidKeyAttributeMask = -67738, // The key attribute mask is not valid. + UnsupportedKeyAttributeMask = -67739, // The key attribute mask is not supported. + InvalidKeyLabel = -67740, // The key label is not valid. + UnsupportedKeyLabel = -67741, // The key label is not supported. + InvalidKeyFormat = -67742, // The key format is not valid. + UnsupportedVectorOfBuffers = -67743, // The vector of buffers is not supported. + InvalidInputVector = -67744, // The input vector is not valid. + InvalidOutputVector = -67745, // The output vector is not valid. + InvalidContext = -67746, // An invalid context was encountered. + InvalidAlgorithm = -67747, // An invalid algorithm was encountered. + InvalidAttributeKey = -67748, // A key attribute was not valid. + MissingAttributeKey = -67749, // A key attribute was missing. + InvalidAttributeInitVector = -67750, // An init vector attribute was not valid. + MissingAttributeInitVector = -67751, // An init vector attribute was missing. + InvalidAttributeSalt = -67752, // A salt attribute was not valid. + MissingAttributeSalt = -67753, // A salt attribute was missing. + InvalidAttributePadding = -67754, // A padding attribute was not valid. + MissingAttributePadding = -67755, // A padding attribute was missing. + InvalidAttributeRandom = -67756, // A random number attribute was not valid. + MissingAttributeRandom = -67757, // A random number attribute was missing. + InvalidAttributeSeed = -67758, // A seed attribute was not valid. + MissingAttributeSeed = -67759, // A seed attribute was missing. + InvalidAttributePassphrase = -67760, // A passphrase attribute was not valid. + MissingAttributePassphrase = -67761, // A passphrase attribute was missing. + InvalidAttributeKeyLength = -67762, // A key length attribute was not valid. + MissingAttributeKeyLength = -67763, // A key length attribute was missing. + InvalidAttributeBlockSize = -67764, // A block size attribute was not valid. + MissingAttributeBlockSize = -67765, // A block size attribute was missing. + InvalidAttributeOutputSize = -67766, // An output size attribute was not valid. + MissingAttributeOutputSize = -67767, // An output size attribute was missing. + InvalidAttributeRounds = -67768, // The number of rounds attribute was not valid. + MissingAttributeRounds = -67769, // The number of rounds attribute was missing. + InvalidAlgorithmParms = -67770, // An algorithm parameters attribute was not valid. + MissingAlgorithmParms = -67771, // An algorithm parameters attribute was missing. + InvalidAttributeLabel = -67772, // A label attribute was not valid. + MissingAttributeLabel = -67773, // A label attribute was missing. + InvalidAttributeKeyType = -67774, // A key type attribute was not valid. + MissingAttributeKeyType = -67775, // A key type attribute was missing. + InvalidAttributeMode = -67776, // A mode attribute was not valid. + MissingAttributeMode = -67777, // A mode attribute was missing. + InvalidAttributeEffectiveBits = -67778, // An effective bits attribute was not valid. + MissingAttributeEffectiveBits = -67779, // An effective bits attribute was missing. + InvalidAttributeStartDate = -67780, // A start date attribute was not valid. + MissingAttributeStartDate = -67781, // A start date attribute was missing. + InvalidAttributeEndDate = -67782, // An end date attribute was not valid. + MissingAttributeEndDate = -67783, // An end date attribute was missing. + InvalidAttributeVersion = -67784, // A version attribute was not valid. + MissingAttributeVersion = -67785, // A version attribute was missing. + InvalidAttributePrime = -67786, // A prime attribute was not valid. + MissingAttributePrime = -67787, // A prime attribute was missing. + InvalidAttributeBase = -67788, // A base attribute was not valid. + MissingAttributeBase = -67789, // A base attribute was missing. + InvalidAttributeSubprime = -67790, // A subprime attribute was not valid. + MissingAttributeSubprime = -67791, // A subprime attribute was missing. + InvalidAttributeIterationCount = -67792, // An iteration count attribute was not valid. + MissingAttributeIterationCount = -67793, // An iteration count attribute was missing. + InvalidAttributeDLDBHandle = -67794, // A database handle attribute was not valid. + MissingAttributeDLDBHandle = -67795, // A database handle attribute was missing. + InvalidAttributeAccessCredentials = -67796, // An access credentials attribute was not valid. + MissingAttributeAccessCredentials = -67797, // An access credentials attribute was missing. + InvalidAttributePublicKeyFormat = -67798, // A public key format attribute was not valid. + MissingAttributePublicKeyFormat = -67799, // A public key format attribute was missing. + InvalidAttributePrivateKeyFormat = -67800, // A private key format attribute was not valid. + MissingAttributePrivateKeyFormat = -67801, // A private key format attribute was missing. + InvalidAttributeSymmetricKeyFormat = -67802, // A symmetric key format attribute was not valid. + MissingAttributeSymmetricKeyFormat = -67803, // A symmetric key format attribute was missing. + InvalidAttributeWrappedKeyFormat = -67804, // A wrapped key format attribute was not valid. + MissingAttributeWrappedKeyFormat = -67805, // A wrapped key format attribute was missing. + StagedOperationInProgress = -67806, // A staged operation is in progress. + StagedOperationNotStarted = -67807, // A staged operation was not started. + VerifyFailed = -67808, // A cryptographic verification failure has occurred. + QuerySizeUnknown = -67809, // The query size is unknown. + BlockSizeMismatch = -67810, // A block size mismatch occurred. + PublicKeyInconsistent = -67811, // The public key was inconsistent. + DeviceVerifyFailed = -67812, // A device verification failure has occurred. + InvalidLoginName = -67813, // An invalid login name was detected. + AlreadyLoggedIn = -67814, // The user is already logged in. + InvalidDigestAlgorithm = -67815, // An invalid digest algorithm was detected. + InvalidCRLGroup = -67816, // An invalid CRL group was detected. + CertificateCannotOperate = -67817, // The certificate cannot operate. + CertificateExpired = -67818, // An expired certificate was detected. + CertificateNotValidYet = -67819, // The certificate is not yet valid. + CertificateRevoked = -67820, // The certificate was revoked. + CertificateSuspended = -67821, // The certificate was suspended. + InsufficientCredentials = -67822, // Insufficient credentials were detected. + InvalidAction = -67823, // The action was not valid. + InvalidAuthority = -67824, // The authority was not valid. + VerifyActionFailed = -67825, // A verify action has failed. + InvalidCertAuthority = -67826, // The certificate authority was not valid. + InvalidCRLAuthority = -67827, // The CRL authority was not valid. + InvalidCRLEncoding = -67828, // The CRL encoding was not valid. + InvalidCRLType = -67829, // The CRL type was not valid. + InvalidCRL = -67830, // The CRL was not valid. + InvalidFormType = -67831, // The form type was not valid. + InvalidID = -67832, // The ID was not valid. + InvalidIdentifier = -67833, // The identifier was not valid. + InvalidIndex = -67834, // The index was not valid. + InvalidPolicyIdentifiers = -67835, // The policy identifiers are not valid. + InvalidTimeString = -67836, // The time specified was not valid. + InvalidReason = -67837, // The trust policy reason was not valid. + InvalidRequestInputs = -67838, // The request inputs are not valid. + InvalidResponseVector = -67839, // The response vector was not valid. + InvalidStopOnPolicy = -67840, // The stop-on policy was not valid. + InvalidTuple = -67841, // The tuple was not valid. + MultipleValuesUnsupported = -67842, // Multiple values are not supported. + NotTrusted = -67843, // The certificate was not trusted. + NoDefaultAuthority = -67844, // No default authority was detected. + RejectedForm = -67845, // The trust policy had a rejected form. + RequestLost = -67846, // The request was lost. + RequestRejected = -67847, // The request was rejected. + UnsupportedAddressType = -67848, // The address type is not supported. + UnsupportedService = -67849, // The service is not supported. + InvalidTupleGroup = -67850, // The tuple group was not valid. + InvalidBaseACLs = -67851, // The base ACLs are not valid. + InvalidTupleCredentials = -67852, // The tuple credentials are not valid. + InvalidEncoding = -67853, // The encoding was not valid. + InvalidValidityPeriod = -67854, // The validity period was not valid. + InvalidRequestor = -67855, // The requestor was not valid. + RequestDescriptor = -67856, // The request descriptor was not valid. + InvalidBundleInfo = -67857, // The bundle information was not valid. + InvalidCRLIndex = -67858, // The CRL index was not valid. + NoFieldValues = -67859, // No field values were detected. + UnsupportedFieldFormat = -67860, // The field format is not supported. + UnsupportedIndexInfo = -67861, // The index information is not supported. + UnsupportedLocality = -67862, // The locality is not supported. + UnsupportedNumAttributes = -67863, // The number of attributes is not supported. + UnsupportedNumIndexes = -67864, // The number of indexes is not supported. + UnsupportedNumRecordTypes = -67865, // The number of record types is not supported. + FieldSpecifiedMultiple = -67866, // Too many fields were specified. + IncompatibleFieldFormat = -67867, // The field format was incompatible. + InvalidParsingModule = -67868, // The parsing module was not valid. + DatabaseLocked = -67869, // The database is locked. + DatastoreIsOpen = -67870, // The data store is open. + MissingValue = -67871, // A missing value was detected. + UnsupportedQueryLimits = -67872, // The query limits are not supported. + UnsupportedNumSelectionPreds = -67873, // The number of selection predicates is not supported. + UnsupportedOperator = -67874, // The operator is not supported. + InvalidDBLocation = -67875, // The database location is not valid. + InvalidAccessRequest = -67876, // The access request is not valid. + InvalidIndexInfo = -67877, // The index information is not valid. + InvalidNewOwner = -67878, // The new owner is not valid. + InvalidModifyMode = -67879, // The modify mode is not valid. + MissingRequiredExtension = -67880, // A required certificate extension is missing. + ExtendedKeyUsageNotCritical = -67881, // The extended key usage extension was not marked critical. + TimestampMissing = -67882, // A timestamp was expected but was not found. + TimestampInvalid = -67883, // The timestamp was not valid. + TimestampNotTrusted = -67884, // The timestamp was not trusted. + TimestampServiceNotAvailable = -67885, // The timestamp service is not available. + TimestampBadAlg = -67886, // An unrecognized or unsupported Algorithm Identifier in timestamp. + TimestampBadRequest = -67887, // The timestamp transaction is not permitted or supported. + TimestampBadDataFormat = -67888, // The timestamp data submitted has the wrong format. + TimestampTimeNotAvailable = -67889, // The time source for the Timestamp Authority is not available. + TimestampUnacceptedPolicy = -67890, // The requested policy is not supported by the Timestamp Authority. + TimestampUnacceptedExtension = -67891, // The requested extension is not supported by the Timestamp Authority. + TimestampAddInfoNotAvailable = -67892, // The additional information requested is not available. + TimestampSystemFailure = -67893, // The timestamp request cannot be handled due to system failure. + SigningTimeMissing = -67894, // A signing time was expected but was not found. + TimestampRejection = -67895, // A timestamp transaction was rejected. + TimestampWaiting = -67896, // A timestamp transaction is waiting. + TimestampRevocationWarning = -67897, // A timestamp authority revocation warning was issued. + TimestampRevocationNotification = -67898, // A timestamp authority revocation notification was issued. + CertificatePolicyNotAllowed = -67899, // The requested policy is not allowed for this certificate. + CertificateNameNotAllowed = -67900, // The requested name is not allowed for this certificate. + CertificateValidityPeriodTooLong = -67901, // The validity period in the certificate exceeds the maximum allowed. + CertificateIsCA = -67902, // The verified certificate is a CA rather than an end-entity. + CertificateDuplicateExtension = -67903, // The certificate contains multiple extensions with the same extension ID. +} diff --git a/core/sys/darwin/Security/SecRandom.odin b/core/sys/darwin/Security/SecRandom.odin new file mode 100644 index 000000000..0527baca1 --- /dev/null +++ b/core/sys/darwin/Security/SecRandom.odin @@ -0,0 +1,19 @@ +package Security + +import CF "core:sys/darwin/CoreFoundation" + +foreign import Security "system:Security.framework" + +// A reference to a random number generator. +RandomRef :: distinct rawptr + +@(link_prefix="Sec", default_calling_convention="c") +foreign Security { + // Default random ref for /dev/random. Synonym for nil. + @(link_name="kSecRandomDefault") kSecRandomDefault: RandomRef + + // Generates an array of cryptographically secure random bytes. + RandomCopyBytes :: proc(rnd: RandomRef = kSecRandomDefault, count: uint, bytes: [^]byte) -> errSec --- + + CopyErrorMessageString :: proc(status: errSec, reserved: rawptr = nil) -> CF.String --- +} \ No newline at end of file diff --git a/core/sys/darwin/core_foundation.odin b/core/sys/darwin/core_foundation.odin deleted file mode 100644 index 325122216..000000000 --- a/core/sys/darwin/core_foundation.odin +++ /dev/null @@ -1,98 +0,0 @@ -//+build darwin -package darwin - -import "base:runtime" - -foreign import core_foundation "system:CoreFoundation.framework" - -CFTypeRef :: distinct rawptr - -CFStringRef :: distinct CFTypeRef - -CFIndex :: int - -CFRange :: struct { - location: CFIndex, - length: CFIndex, -} - -CFStringEncoding :: enum u32 { - ASCII = 1, - NEXTSTEP = 2, - JapaneseEUC = 3, - UTF8 = 4, - ISOLatin1 = 5, - Symbol = 6, - NonLossyASCII = 7, - ShiftJIS = 8, - ISOLatin2 = 9, - Unicode = 10, - WindowsCP1251 = 11, - WindowsCP1252 = 12, - WindowsCP1253 = 13, - WindowsCP1254 = 14, - WindowsCP1250 = 15, - ISO2022JP = 21, - MacOSRoman = 30, - - UTF16 = Unicode, - - UTF16BigEndian = 0x90000100, - UTF16LittleEndian = 0x94000100, - - UTF32 = 0x8c000100, - UTF32BigEndian = 0x98000100, - UTF32LittleEndian = 0x9c000100, -} - -foreign core_foundation { - // Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding. - CFStringGetCString :: proc(theString: CFStringRef, buffer: [^]byte, bufferSize: CFIndex, encoding: CFStringEncoding) -> Bool --- - - // Returns the number (in terms of UTF-16 code pairs) of Unicode characters in a string. - CFStringGetLength :: proc(theString: CFStringRef) -> CFIndex --- - - // Returns the maximum number of bytes a string of a specified length (in Unicode characters) will take up if encoded in a specified encoding. - CFStringGetMaximumSizeForEncoding :: proc(length: CFIndex, encoding: CFStringEncoding) -> CFIndex --- - - // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. - CFStringGetBytes :: proc( - thestring: CFStringRef, - range: CFRange, - encoding: CFStringEncoding, - lossByte: u8, - isExternalRepresentation: Bool, - buffer: [^]byte, - maxBufLen: CFIndex, - usedBufLen: ^CFIndex, - ) -> CFIndex --- - - // Releases a Core Foundation object. - @(link_name="CFRelease") - _CFRelease :: proc(cf: CFTypeRef) --- -} - -// Releases a Core Foundation object. -CFRelease :: proc { - CFReleaseString, -} - -// Releases a Core Foundation string. -CFReleaseString :: #force_inline proc(theString: CFStringRef) { - _CFRelease(CFTypeRef(theString)) -} - -CFStringCopyToOdinString :: proc(theString: CFStringRef, allocator := context.allocator) -> (str: string, ok: bool) #optional_ok { - length := CFStringGetLength(theString) - max := CFStringGetMaximumSizeForEncoding(length, .UTF8) - - buf, err := make([]byte, max, allocator) - if err != nil { return } - - raw_str := runtime.Raw_String{ - data = raw_data(buf), - } - CFStringGetBytes(theString, {0, length}, .UTF8, 0, false, raw_data(buf), max, &raw_str.len) - - return transmute(string)raw_str, true -} diff --git a/core/sys/darwin/security.odin b/core/sys/darwin/security.odin deleted file mode 100644 index 0c58260e7..000000000 --- a/core/sys/darwin/security.odin +++ /dev/null @@ -1,26 +0,0 @@ -//+build darwin -package darwin - -foreign import security "system:Security.framework" - -// A reference to a random number generator. -SecRandomRef :: distinct rawptr - -OSStatus :: distinct i32 - -errSec :: enum OSStatus { - Success = 0, // No error. - Unimplemented = -4, // Function or operation not implemented. - - // Many more... -} - -foreign security { - // Synonym for nil, uses a cryptographically secure random number generator. - kSecRandomDefault: SecRandomRef - - // Generates an array of cryptographically secure random bytes. - SecRandomCopyBytes :: proc(rnd: SecRandomRef = kSecRandomDefault, count: uint, bytes: [^]byte) -> errSec --- - - SecCopyErrorMessageString :: proc(status: errSec, reserved: rawptr = nil) -> CFStringRef --- -} diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index b2736ffcd..1f6d337e8 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -7,7 +7,7 @@ import "core:os" import "core:thread" import "core:time" import "core:reflect" -import "core:runtime" +import "base:runtime" import "core:intrinsics" import "core:math/big" From d2ca91b830b8c57396e65c3ada846fee188a83c7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 13 Apr 2024 19:32:22 +0200 Subject: [PATCH 055/171] fix wasm runtime.js storeString to support Unicode --- vendor/wasm/js/runtime.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 2e69a28c7..320d74d68 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -104,9 +104,12 @@ class WasmMemoryInterface { storeInt(addr, value) { this.mem.setInt32 (addr, value, true); } storeUint(addr, value) { this.mem.setUint32 (addr, value, true); } + // Returned length might not be the same as `value.length` if non-ascii strings are given. storeString(addr, value) { - const bytes = this.loadBytes(addr, value.length); - new TextEncoder().encodeInto(value, bytes); + const src = new TextEncoder().encode(value); + const dst = new Uint8Array(this.memory.buffer, addr, src.length); + dst.set(src); + return src.length; } }; From 9f97056c14990c4e8645c8aaa5a7432e9fdf5102 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 13 Apr 2024 20:08:10 +0200 Subject: [PATCH 056/171] add 'odin root' command --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 36a99ec32..063b6c8b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -212,6 +212,7 @@ gb_internal void usage(String argv0) { print_usage_line(1, "doc Generates documentation on a directory of .odin files."); print_usage_line(1, "version Prints version."); print_usage_line(1, "report Prints information useful to reporting a bug."); + print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections."); print_usage_line(0, ""); print_usage_line(0, "For further details on a command, invoke command help:"); print_usage_line(1, "e.g. `odin build -help` or `odin help build`"); @@ -2572,6 +2573,9 @@ int main(int arg_count, char const **arg_ptr) { print_show_help(args[0], args[2]); return 0; } + } else if (command == "root") { + gb_printf("%.*s", LIT(odin_root_dir())); + return 0; } else { usage(args[0]); return 1; From f252084b1f04028cf94c77662fbfefa4cf0987f1 Mon Sep 17 00:00:00 2001 From: wrapperup Date: Sat, 13 Apr 2024 15:05:51 -0400 Subject: [PATCH 057/171] add dxc to all_vendor.odin --- examples/all/all_vendor.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index 9093e0f3e..1ab1debea 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -73,10 +73,12 @@ _ :: MTK _ :: CA +import DXC "vendor:directx/dxc" import D3D11 "vendor:directx/d3d11" import D3D12 "vendor:directx/d3d12" import DXGI "vendor:directx/dxgi" +_ :: DXC _ :: D3D11 _ :: D3D12 _ :: DXGI From b22e43c335bd48df1f4d4016c0c8d83db08794ca Mon Sep 17 00:00:00 2001 From: wrapperup Date: Sat, 13 Apr 2024 15:27:02 -0400 Subject: [PATCH 058/171] add freebsd, openbsd to dxcdef_unix.odin --- vendor/directx/dxc/dxcdef_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/directx/dxc/dxcdef_unix.odin b/vendor/directx/dxc/dxcdef_unix.odin index 649bb8a13..12a682310 100644 --- a/vendor/directx/dxc/dxcdef_unix.odin +++ b/vendor/directx/dxc/dxcdef_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin +//+build linux, darwin, freebsd, openbsd package directx_dxc import "core:c" From 9591eb2ed582205c4ed0f47ad7f45eab82f5fb75 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 14 Apr 2024 16:41:10 +0200 Subject: [PATCH 059/171] Tell Git to always treat Makefile and build_odin.sh as Nix EOL --- .gitattributes | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 0178b8a8f..e58996311 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,6 @@ *.odin linguist-language=Odin -* text=auto \ No newline at end of file +* text=auto + +# These files must always have *nix line-endings +Makefile text eol=lf +*.sh text eol=lf \ No newline at end of file From a0cff82320840e4be6d40cee8a2432645773a53e Mon Sep 17 00:00:00 2001 From: "Maurizio M. Gavioli" Date: Sun, 14 Apr 2024 17:18:08 +0200 Subject: [PATCH 060/171] Fix the format of some `doc.odin` files of the `core` library which did not made into the documentation. `c/frontend/tokenizer`: add proper "Example:" header to demo example code, removed empty lines. `container/bit_array`: moved comment before package; aligned narrative lines to left margin; converted case lines into bulleted lines ("- "); converted individual examples to single-tab-indented preformatted text. `dynlib`: removed "//+build ignore" line; added newline at EOF. `image/netpmb`: converted indented lines of "Reading", "Wrting" and "Some syntax..." into bulleted lists; "Formats" indented lines kept as they are as the preformatted text seems relevant to keep the alignments; doubly indented lines kept as single-indented to keep them different (as the format does not allow for two-level bulleted lists); removed empy lines. `os/os2`: WIP, not modified `sys/info`: removed "//+build ignore" line; converted tab-indented initial description into regular left-margin comment; moved uncommented sample code within the doc comment as an "Example:"; moved simple- and double-tabbed separate comments with sample Windows and macOS outputs within the doc comment as bulleted headlines with preformatted output listings; removed now empty comments and blank lines after the package line. `text/i18n`: removed "//+build ignore" line; moved the pacakge line at the end; de-indented the tab-indented introductory narrative; moved sample code comments into the doc comment as tab-indented code with a proper "Example:" heading; removed "```" MD attempts at code formatting. `text/table`: unindented the comment lines of a descriptive kind; headlines of major subdivisions are marked as bold; kept code samples as tab-indented preformatted text (as there are several of them, the standard "Example:" and "Output:" headings cannot be used) removing the "```" MD attempts at code formatting; removed in-between blank lines. --- core/c/frontend/tokenizer/doc.odin | 45 ++++---- core/container/bit_array/doc.odin | 81 +++++++------- core/dynlib/doc.odin | 5 +- core/image/netpbm/doc.odin | 39 ++++--- core/sys/info/doc.odin | 128 ++++++++++----------- core/text/i18n/doc.odin | 171 ++++++++++++++--------------- core/text/table/doc.odin | 31 ++---- 7 files changed, 240 insertions(+), 260 deletions(-) diff --git a/core/c/frontend/tokenizer/doc.odin b/core/c/frontend/tokenizer/doc.odin index 9b1734fc4..43747dfe8 100644 --- a/core/c/frontend/tokenizer/doc.odin +++ b/core/c/frontend/tokenizer/doc.odin @@ -1,34 +1,31 @@ /* -package demo +Example: + package demo -import tokenizer "core:c/frontend/tokenizer" -import preprocessor "core:c/frontend/preprocessor" -import "core:fmt" + import tokenizer "core:c/frontend/tokenizer" + import preprocessor "core:c/frontend/preprocessor" + import "core:fmt" -main :: proc() { - t := &tokenizer.Tokenizer{}; - tokenizer.init_defaults(t); + main :: proc() { + t := &tokenizer.Tokenizer{}; + tokenizer.init_defaults(t); - cpp := &preprocessor.Preprocessor{}; - cpp.warn, cpp.err = t.warn, t.err; - preprocessor.init_lookup_tables(cpp); - preprocessor.init_default_macros(cpp); - cpp.include_paths = {"my/path/to/include"}; + cpp := &preprocessor.Preprocessor{}; + cpp.warn, cpp.err = t.warn, t.err; + preprocessor.init_lookup_tables(cpp); + preprocessor.init_default_macros(cpp); + cpp.include_paths = {"my/path/to/include"}; - tok := tokenizer.tokenize_file(t, "the/source/file.c", 1); + tok := tokenizer.tokenize_file(t, "the/source/file.c", 1); - tok = preprocessor.preprocess(cpp, tok); - if tok != nil { - for t := tok; t.kind != .EOF; t = t.next { - fmt.println(t.lit); + tok = preprocessor.preprocess(cpp, tok); + if tok != nil { + for t := tok; t.kind != .EOF; t = t.next { + fmt.println(t.lit); + } } + + fmt.println("[Done]"); } - - fmt.println("[Done]"); -} */ - - package c_frontend_tokenizer - - diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index 52e252d8a..371f63f0e 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -1,53 +1,52 @@ -package dynamic_bit_array - /* - The Bit Array can be used in several ways: +The Bit Array can be used in several ways: - -- By default you don't need to instantiate a Bit Array: +- By default you don't need to instantiate a Bit Array: - package test + package test - import "core:fmt" - import "core:container/bit_array" + import "core:fmt" + import "core:container/bit_array" - main :: proc() { - using bit_array + main :: proc() { + using bit_array - bits: Bit_Array + bits: Bit_Array - // returns `true` - fmt.println(set(&bits, 42)) + // returns `true` + fmt.println(set(&bits, 42)) - // returns `false`, `false`, because this Bit Array wasn't created to allow negative indices. - was_set, was_retrieved := get(&bits, -1) - fmt.println(was_set, was_retrieved) - destroy(&bits) + // returns `false`, `false`, because this Bit Array wasn't created to allow negative indices. + was_set, was_retrieved := get(&bits, -1) + fmt.println(was_set, was_retrieved) + destroy(&bits) + } + +- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation: + + package test + + import "core:fmt" + import "core:container/bit_array" + + main :: proc() { + Foo :: enum int { + Negative_Test = -42, + Bar = 420, + Leaves = 69105, } - -- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation: + using bit_array - package test + bits := create(int(max(Foo)), int(min(Foo))) + defer destroy(bits) - import "core:fmt" - import "core:container/bit_array" - - main :: proc() { - Foo :: enum int { - Negative_Test = -42, - Bar = 420, - Leaves = 69105, - } - - using bit_array - - bits := create(int(max(Foo)), int(min(Foo))) - defer destroy(bits) - - fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar)) - fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar)) - fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test)) - fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves)) - fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test)) - fmt.printf("Freed.\n") - } -*/ \ No newline at end of file + fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar)) + fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar)) + fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test)) + fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves)) + fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test)) + fmt.printf("Freed.\n") + } +*/ +package dynamic_bit_array diff --git a/core/dynlib/doc.odin b/core/dynlib/doc.odin index 849e03a71..f5c91c54e 100644 --- a/core/dynlib/doc.odin +++ b/core/dynlib/doc.odin @@ -1,6 +1,5 @@ -//+build ignore /* -Package core:dynlib implements loading of shared libraries/DLLs and their symbols. +Package `core:dynlib` implements loading of shared libraries/DLLs and their symbols. The behaviour of dynamically loaded libraries is specific to the target platform of the program. For in depth detail on the underlying behaviour please refer to your target platform's documentation. @@ -8,4 +7,4 @@ For in depth detail on the underlying behaviour please refer to your target plat See `example` directory for an example library exporting 3 symbols and a host program loading them automatically by defining a symbol table struct. */ -package dynlib \ No newline at end of file +package dynlib diff --git a/core/image/netpbm/doc.odin b/core/image/netpbm/doc.odin index 1b5b46856..7106e023e 100644 --- a/core/image/netpbm/doc.odin +++ b/core/image/netpbm/doc.odin @@ -1,5 +1,6 @@ /* Formats: + PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel) PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value) PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value) @@ -7,27 +8,29 @@ Formats: PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel) Reading: - All formats fill out header fields `format`, `width`, `height`, `channels`, `depth` - Specific formats use more fields - PGM, PPM, and PAM set `maxval` (maximum of 65535) - PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3) - PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats) - Currently doesn't support reading multiple images from one binary-format file + +- All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`. +- Specific formats use more fields: + PGM, PPM, and PAM set `maxval` (maximum of 65535) + PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3) + PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats) +- Currently doesn't support reading multiple images from one binary-format file. Writing: - You can use your own `Netpbm_Info` struct to control how images are written - All formats require the header field `format` to be specified - Additional header fields are required for specific formats - PGM, PPM, and PAM require `maxval` (maximum of 65535) - PAM also uses `tupltype`, though it may be left as default (empty or nil string) - PFM requires `scale`, and optionally `little_endian` + +- You can use your own `Netpbm_Info` struct to control how images are written. +- All formats require the header field `format` to be specified. +- Additional header fields are required for specific formats: + PGM, PPM, and PAM require `maxval` (maximum of 65535) + PAM also uses `tupltype`, though it may be left as default (empty or nil string) + PFM requires `scale`, and optionally `little_endian` Some syntax differences from the specifications: - `channels` stores the number of values per pixel, what the PAM specification calls `depth` - `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise) - `scale` and `little_endian` are separated, so the `header` will always store a positive `scale` - `little_endian` will only be true for a negative `scale` PFM, every other format will be false - `little_endian` only describes the netpbm data being read/written, the image buffer will be native -*/ +- `channels` stores the number of values per pixel, what the PAM specification calls `depth` +- `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise) +- `scale` and `little_endian` are separated, so the `header` will always store a positive `scale` +- `little_endian` will only be true for a negative `scale` PFM, every other format will be false +- `little_endian` only describes the netpbm data being read/written, the image buffer will be native +*/ package netpbm diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 81c3fb342..15af0d4b3 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -1,78 +1,78 @@ /* - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. +Copyright 2022 Jeroen van Rijn . +Made available under Odin's BSD-3 license. - Package `core:sys/info` gathers system information on: - Windows, Linux, macOS, FreeBSD & OpenBSD. +Package `core:sys/info` gathers system information on: +Windows, Linux, macOS, FreeBSD & OpenBSD. - Simply import the package and you'll have access to the OS version, RAM amount - and CPU information. +Simply import the package and you'll have access to the OS version, RAM amount +and CPU information. - On Windows, GPUs will also be enumerated using the registry. +On Windows, GPUs will also be enumerated using the registry. - CPU feature flags can be tested against `cpu_features`, where applicable, e.g. - `if .aes in si.aes { ... }` -*/ -//+build ignore -package sysinfo +CPU feature flags can be tested against `cpu_features`, where applicable, e.g. +`if .aes in si.aes { ... }` -import "core:fmt" -import si "core:sys/info" +Example: -main :: proc() { - fmt.printf("Odin: %v\n", ODIN_VERSION) - fmt.printf("OS: %v\n", si.os_version.as_string) - fmt.printf("OS: %#v\n", si.os_version) - fmt.printf("CPU: %v\n", si.cpu_name) - fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024) + import "core:fmt" + import si "core:sys/info" - fmt.println() - for gpu, i in si.gpus { - fmt.printf("GPU #%v:\n", i) - fmt.printf("\tVendor: %v\n", gpu.vendor_name) - fmt.printf("\tModel: %v\n", gpu.model_name) - fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024) + main :: proc() { + fmt.printf("Odin: %v\n", ODIN_VERSION) + fmt.printf("OS: %v\n", si.os_version.as_string) + fmt.printf("OS: %#v\n", si.os_version) + fmt.printf("CPU: %v\n", si.cpu_name) + fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024) + + fmt.println() + for gpu, i in si.gpus { + fmt.printf("GPU #%v:\n", i) + fmt.printf("\tVendor: %v\n", gpu.vendor_name) + fmt.printf("\tModel: %v\n", gpu.model_name) + fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024) + } } -} -/* - Example Windows output: - Odin: dev-2022-09 - OS: Windows 10 Professional (version: 20H2), build: 19042.1466 - OS: OS_Version{ - platform = "Windows", - major = 10, - minor = 0, +- Example Windows output: + + Odin: dev-2022-09 + OS: Windows 10 Professional (version: 20H2), build: 19042.1466 + OS: OS_Version{ + platform = "Windows", + major = 10, + minor = 0, + patch = 0, + build = [ + 19042, + 1466, + ], + version = "20H2", + as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", + } + CPU: AMD Ryzen 7 1800X Eight-Core Processor + RAM: 65469 MiB + GPU #0: + Vendor: Advanced Micro Devices, Inc. + Model: Radeon RX Vega + VRAM: 8176 MiB + +- Example macOS output: + + ODIN: dev-2022-09 + OS: OS_Version{ + platform = "MacOS", + major = 21, + minor = 5, patch = 0, build = [ - 19042, - 1466, + 0, + 0, ], - version = "20H2", - as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", - } - CPU: AMD Ryzen 7 1800X Eight-Core Processor - RAM: 65469 MiB - - GPU #0: - Vendor: Advanced Micro Devices, Inc. - Model: Radeon RX Vega - VRAM: 8176 MiB - - Example macOS output: - ODIN: dev-2022-09 - OS: OS_Version{ - platform = "MacOS", - major = 21, - minor = 5, - patch = 0, - build = [ - 0, - 0, - ], - version = "21F79", - as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)", - } - CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz - RAM: 8192 MiB + version = "21F79", + as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)", + } + CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz + RAM: 8192 MiB */ +package sysinfo diff --git a/core/text/i18n/doc.odin b/core/text/i18n/doc.odin index ef619451e..54bf8b80f 100644 --- a/core/text/i18n/doc.odin +++ b/core/text/i18n/doc.odin @@ -1,111 +1,106 @@ -//+build ignore -package i18n /* - The i18n package is flexible and easy to use. +The `i18n` package is flexible and easy to use. - It has one call to get a translation: `get`, which the user can alias into something like `T`. +It has one call to get a translation: `get`, which the user can alias into something like `T`. - `get`, referred to as `T` here, has a few different signatures. - All of them will return the key if the entry can't be found in the active translation catalog. +`get`, referred to as `T` here, has a few different signatures. +All of them will return the key if the entry can't be found in the active translation catalog. - - `T(key)` returns the translation of `key`. - - `T(key, n)` returns a pluralized translation of `key` according to value `n`. +- `T(key)` returns the translation of `key`. +- `T(key, n)` returns a pluralized translation of `key` according to value `n`. - - `T(section, key)` returns the translation of `key` in `section`. - - `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`. +- `T(section, key)` returns the translation of `key` in `section`. +- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`. - By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use. - If you want to override which translation to use, for example in a language preview dialog, you can use the following: +By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use. +If you want to override which translation to use, for example in a language preview dialog, you can use the following: - - `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog. - - `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog. +- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog. +- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog. - If a catalog has translation contexts or sections, then ommitting it in the above calls looks up in section "". +If a catalog has translation contexts or sections, then omitting it in the above calls looks up in section "". - The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form. - Passing n != 1 returns plural form 1. +The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form. +Passing n != 1 returns plural form 1. - Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser. - This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used. +Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser. +This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used. - You can also assign it to a loaded catalog after parsing, of course. +You can also assign it to a loaded catalog after parsing, of course. - Some code examples follow. -*/ +Example: -/* -```cpp -import "core:fmt" -import "core:text/i18n" + import "core:fmt" + import "core:text/i18n" -T :: i18n.get + T :: i18n.get -mo :: proc() { - using fmt + mo :: proc() { + using fmt - err: i18n.Error + err: i18n.Error - /* - Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter. - */ - i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo")) - defer i18n.destroy() + /* + Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter. + */ + i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo")) + defer i18n.destroy() - if err != .None { return } + if err != .None { return } - /* - These are in the .MO catalog. - */ - println("-----") - println(T("")) - println("-----") - println(T("There are 69,105 leaves here.")) - println("-----") - println(T("Hellope, World!")) - println("-----") - // We pass 1 into `T` to get the singular format string, then 1 again into printf. - printf(T("There is %d leaf.\n", 1), 1) - // We pass 42 into `T` to get the plural format string, then 42 again into printf. - printf(T("There is %d leaf.\n", 42), 42) + /* + These are in the .MO catalog. + */ + println("-----") + println(T("")) + println("-----") + println(T("There are 69,105 leaves here.")) + println("-----") + println(T("Hellope, World!")) + println("-----") + // We pass 1 into `T` to get the singular format string, then 1 again into printf. + printf(T("There is %d leaf.\n", 1), 1) + // We pass 42 into `T` to get the plural format string, then 42 again into printf. + printf(T("There is %d leaf.\n", 42), 42) - /* - This isn't in the translation catalog, so the key is passed back untranslated. - */ - println("-----") - println(T("Come visit us on Discord!")) -} - -qt :: proc() { - using fmt - - err: i18n.Error - - /* - Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter. - */ - i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts")) - defer i18n.destroy() - - if err != .None { - return + /* + This isn't in the translation catalog, so the key is passed back untranslated. + */ + println("-----") + println(T("Come visit us on Discord!")) } - /* - These are in the .TS catalog. As you can see they have sections. - */ - println("--- Page section ---") - println("Page:Text for translation =", T("Page", "Text for translation")) - println("-----") - println("Page:Also text to translate =", T("Page", "Also text to translate")) - println("-----") - println("--- installscript section ---") - println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall")) - println("-----") - println("--- apple_count section ---") - println("apple_count:%d apple(s) =") - println("\t 1 =", T("apple_count", "%d apple(s)", 1)) - println("\t 42 =", T("apple_count", "%d apple(s)", 42)) -} -``` -*/ \ No newline at end of file + qt :: proc() { + using fmt + + err: i18n.Error + + /* + Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter. + */ + i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts")) + defer i18n.destroy() + + if err != .None { + return + } + + /* + These are in the .TS catalog. As you can see they have sections. + */ + println("--- Page section ---") + println("Page:Text for translation =", T("Page", "Text for translation")) + println("-----") + println("Page:Also text to translate =", T("Page", "Also text to translate")) + println("-----") + println("--- installscript section ---") + println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall")) + println("-----") + println("--- apple_count section ---") + println("apple_count:%d apple(s) =") + println("\t 1 =", T("apple_count", "%d apple(s)", 1)) + println("\t 42 =", T("apple_count", "%d apple(s)", 42)) + } +*/ +package i18n diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 9b5c1f932..76886bdea 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -1,11 +1,8 @@ /* - package table implements ascii/markdown/html/custom rendering of tables. +The package `table` implements ASCII/markdown/HTML/custom rendering of tables. - --- +**Custom rendering example:** - Custom rendering example: - - ```odin tbl := init(&Table{}) padding(tbl, 0, 1) row(tbl, "A_LONG_ENUM", "= 54,", "// A comment about A_LONG_ENUM") @@ -17,19 +14,14 @@ } io.write_byte(stdio_writer(), '\n') } - ``` - This outputs: - ``` +This outputs: + A_LONG_ENUM = 54, // A comment about A_LONG_ENUM AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM - ``` - --- +**ASCII rendering example:** - ASCII rendering example: - - ```odin tbl := init(&Table{}) defer destroy(tbl) @@ -69,10 +61,9 @@ write_ascii_table(stdio_writer(), tbl) write_markdown_table(stdio_writer(), tbl) - ``` - This outputs: - ``` +This outputs: + +-----------------------------------------------+ | This is a table caption and it is very long | +------------------+-----------------+----------+ @@ -82,19 +73,15 @@ | 000000005 | 6.283185 | | | a | bbb | c | +------------------+-----------------+----------+ - ``` - and +and - ``` | AAAAAAAAA | B | C | |:-----------------|:---------------:|---------:| | 123 | foo | | | 000000005 | 6.283185 | | | a | bbb | c | - ``` - respectively. +respectively. */ - package text_table From a0e25be1967315d1ab2cf6b937549f3ff74c71f6 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Mon, 15 Apr 2024 11:16:52 +1100 Subject: [PATCH 061/171] [sys/linux]: Fix signature on wait4 syscall --- core/sys/linux/sys.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 869ce88e3..54a34fbea 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -787,8 +787,8 @@ exit :: proc "contextless" (code: i32) -> ! { Wait for the process to change state. Available since Linux 1.0. */ -wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options) -> (Pid, Errno) { - ret := syscall(SYS_wait4, pid, status, transmute(u32) options) +wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options, rusage: ^RUsage) -> (Pid, Errno) { + ret := syscall(SYS_wait4, pid, status, transmute(u32) options, rusage) return errno_unwrap(ret, Pid) } From 2e29687ceeb004bfa820dd8c475786d2fed78a6d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 10:28:14 +0100 Subject: [PATCH 062/171] Fix #3425 --- src/error.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index 8647f60b9..2e6641e3b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -292,10 +292,11 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con if (line_len > MAX_LINE_LENGTH_PADDED) { i32 left = MAX_TAB_WIDTH; - if (offset > 0) { - line_text += offset-left; - line_len -= offset-left; - offset = left+MAX_TAB_WIDTH/2; + i32 diff = gb_max(offset-left, 0); + if (diff > 0) { + line_text += diff; + line_len -= diff; + offset = left + ELLIPSIS_PADDING/2; } if (line_len > MAX_LINE_LENGTH_PADDED) { line_len = MAX_LINE_LENGTH_PADDED; @@ -304,7 +305,7 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end, char con squiggle_extra = 1; } } - if (offset > 0) { + if (diff > 0) { error_out("... %.*s ...", cast(i32)line_len, line_text); } else { error_out("%.*s ...", cast(i32)line_len, line_text); From a294f067a96bdc0fb696af785940ac4f8bb18f22 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:28:22 -0400 Subject: [PATCH 063/171] Fix `big.internal_random_prime` with `Second_MSB_On` --- core/math/big/prime.odin | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index b02b7cb4e..5e7c02f37 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -1247,6 +1247,20 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := a.digit[0] |= 3 } if .Second_MSB_On in flags { + /* + Ensure there's enough space for the bit to be set. + */ + if a.used * _DIGIT_BITS < size_in_bits - 1 { + new_size := (size_in_bits - 1) / _DIGIT_BITS + + if new_size % _DIGIT_BITS > 0 { + new_size += 1 + } + + internal_grow(a, new_size) or_return + a.used = new_size + } + internal_int_bitfield_set_single(a, size_in_bits - 2) or_return } From 4bfa1ea76c93e6cb6ba234a39f8628f881a09c12 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Mon, 15 Apr 2024 22:16:03 +1100 Subject: [PATCH 064/171] [sys/linux]: Fix syscall calls for open and fstat --- core/sys/linux/sys.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 869ce88e3..e94d3015b 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -40,10 +40,10 @@ write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { */ open :: proc "contextless" (name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode) + ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } else { - ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) mode) + ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } } @@ -91,10 +91,10 @@ stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { */ fstat :: proc "contextless" (fd: Fd, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { - ret := syscall(SYS_fstat, stat) + ret := syscall(SYS_fstat, cast(i32) fd, stat) return Errno(-ret) } else { - ret := syscall(SYS_fstat64, stat) + ret := syscall(SYS_fstat64, cast(i32) fd, stat) return Errno(-ret) } } From 7e582dd671addfd2119b0a9016635cdaddc6b22f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 12:43:27 +0100 Subject: [PATCH 065/171] Add basic suggestion to missing `package` name --- src/parser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index 01a3069ff..f4d3dc48d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6114,7 +6114,13 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { CommentGroup *docs = f->lead_comment; if (f->curr_token.kind != Token_package) { + ERROR_BLOCK(); syntax_error(f->curr_token, "Expected a package declaration at the beginning of the file"); + // IMPORTANT NOTE(bill): this is technically a race condition with the suggestion, but it's ony a suggession + // so in practice is should be "fine" + if (f->pkg && f->pkg->name != "") { + error_line("\tSuggestion: Add 'package %.*s' to the top of the file\n", LIT(f->pkg->name)); + } return false; } From 36644a3c09630cc75e7826ec443bb760bdbbd4af Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 12:43:45 +0100 Subject: [PATCH 066/171] Add template specialization for compared against `""` with `String` internally --- src/string.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/string.cpp b/src/string.cpp index 7bfa52f33..4adec7a90 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -171,6 +171,9 @@ template gb_internal bool operator > (String const &a, char const (&b template gb_internal bool operator <= (String const &a, char const (&b)[N]) { return str_le(a, make_string(cast(u8 *)b, N-1)); } template gb_internal bool operator >= (String const &a, char const (&b)[N]) { return str_ge(a, make_string(cast(u8 *)b, N-1)); } +template <> bool operator == (String const &a, char const (&b)[1]) { return a.len == 0; } +template <> bool operator != (String const &a, char const (&b)[1]) { return a.len != 0; } + gb_internal gb_inline bool string_starts_with(String const &s, String const &prefix) { if (prefix.len > s.len) { return false; From 38c1fd58241ca3da4f539958b4cc10574b641138 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 14:35:51 +0100 Subject: [PATCH 067/171] Keep -vet happy --- core/encoding/cbor/cbor.odin | 4 ++-- core/encoding/cbor/coding.odin | 6 ++++-- core/encoding/cbor/marshal.odin | 13 +++++-------- core/encoding/cbor/unmarshal.odin | 12 ++++++------ 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index defae4163..550cf87fd 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -428,7 +428,7 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i } } - padding := dedent(padding) + padding = dedent(padding) newline(w, padding) or_return io.write_string(w, "]") or_return @@ -453,7 +453,7 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i } } - padding := dedent(padding) + padding = dedent(padding) newline(w, padding) or_return io.write_string(w, "}") or_return diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index abb832ccf..11db994da 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -377,6 +377,7 @@ _decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^By _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) { context.allocator = allocator + add := add n, scap := _decode_len_str(d, add) or_return buf := strings.builder_make(0, scap) or_return @@ -385,8 +386,9 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c if n == -1 { indefinite_loop: for { - header := _decode_header(d.reader) or_return - maj, add := _header_split(header) + header := _decode_header(d.reader) or_return + maj: Major + maj, add = _header_split(header) #partial switch maj { case type: iter_n, iter_cap := _decode_len_str(d, add) or_return diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 2ffb6b5b4..87e91bbd8 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -208,7 +208,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { } case runtime.Type_Info_Boolean: - val: bool switch b in a { case bool: return _encode_bool(e.writer, b) case b8: return _encode_bool(e.writer, bool(b)) @@ -231,7 +230,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { return case runtime.Type_Info_Enumerated_Array: - index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum) + // index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum) err_conv(_encode_u64(e, u64(info.count), .Array)) or_return for i in 0.. (err: Marshal_Error) { entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return defer delete(entries) - for name, i in info.names { + for _, i in info.names { fname := field_name(info, i) if fname == "-" { continue @@ -498,7 +497,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { marshal_entry(e, info, v, entry.name, entry.field) or_return } } else { - for name, i in info.names { + for _, i in info.names { fname := field_name(info, i) if fname == "-" { continue @@ -514,14 +513,12 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { case Value: return err_conv(encode(e, vv)) } - tag := reflect.get_union_variant_raw_tag(v) - if v.data == nil || tag <= 0 { + id := reflect.union_variant_typeid(v) + if v.data == nil || id == nil { return _encode_nil(e.writer) } - id := info.variants[tag-1].id if len(info.variants) == 1 { - id := info.variants[tag-1].id return marshal_into(e, any{v.data, id}) } diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 6e7f3c0bb..5480b9125 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -518,7 +518,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Array: - _length, scap := err_conv(_decode_len_container(d, add)) or_return + _, scap := err_conv(_decode_len_container(d, add)) or_return length := min(scap, t.count) if length > t.count { @@ -532,7 +532,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Enumerated_Array: - _length, scap := err_conv(_decode_len_container(d, add)) or_return + _, scap := err_conv(_decode_len_container(d, add)) or_return length := min(scap, t.count) if length > t.count { @@ -546,7 +546,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Complex: - _length, scap := err_conv(_decode_len_container(d, add)) or_return + _, scap := err_conv(_decode_len_container(d, add)) or_return length := min(scap, 2) if length > 2 { @@ -568,7 +568,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return case reflect.Type_Info_Quaternion: - _length, scap := err_conv(_decode_len_container(d, add)) or_return + _, scap := err_conv(_decode_len_container(d, add)) or_return length := min(scap, 4) if length > 4 { @@ -628,7 +628,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, return _unsupported(v, hdr) } - length, scap := err_conv(_decode_len_container(d, add)) or_return + length, _ := err_conv(_decode_len_container(d, add)) or_return unknown := length == -1 fields := reflect.struct_fields_zipped(ti.id) @@ -672,7 +672,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, } field := fields[use_field_idx] - name := field.name + // name := field.name ptr := rawptr(uintptr(v.data) + field.offset) fany := any{ptr, field.type.id} _unmarshal_value(d, fany, _decode_header(r) or_return) or_return From 69db9c6390a7ad5e59b478877b745643b25fec1c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 15:40:32 +0100 Subject: [PATCH 068/171] Add loads of `nil` checks when doing `s.builder` --- core/text/edit/text_edit.odin | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index caccb6be8..08fce0444 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -92,7 +92,7 @@ begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { end(s) } s.id = id - s.selection = {len(builder.buf), 0} + s.selection = {len(builder.buf, 0} s.builder = builder update_time(s) undo_clear(s, &s.undo) @@ -137,6 +137,9 @@ clear_all :: proc(s: ^State) -> (cleared: bool) { // push current text state to the wanted undo|redo stack undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error { + if s.builder != nil { + return nil + } text := string(s.builder.buf[:]) item := (^Undo_State)(mem.alloc(size_of(Undo_State) + len(text), align_of(Undo_State), s.undo_text_allocator) or_return) item.selection = s.selection @@ -154,7 +157,7 @@ undo :: proc(s: ^State, undo, redo: ^[dynamic]^Undo_State) { undo_state_push(s, redo) item := pop(undo) s.selection = item.selection - #no_bounds_check { + #no_bounds_check if s.builder != nil { strings.builder_reset(s.builder) strings.write_string(s.builder, string(item.text[:item.len])) } @@ -224,13 +227,17 @@ input_rune :: proc(s: ^State, r: rune) { // insert a single rune into the edit state - deletes the current selection insert :: proc(s: ^State, at: int, text: string) { undo_check(s) - inject_at(&s.builder.buf, at, text) + if s.builder != nil { + inject_at(&s.builder.buf, at, text) + } } // remove the wanted range withing, usually the selection within byte indices remove :: proc(s: ^State, lo, hi: int) { undo_check(s) - remove_range(&s.builder.buf, lo, hi) + if s.builder != nil { + remove_range(&s.builder.buf, lo, hi) + } } // true if selection head and tail dont match and form a selection of multiple characters @@ -244,8 +251,8 @@ has_selection :: proc(s: ^State) -> bool { sorted_selection :: proc(s: ^State) -> (lo, hi: int) { lo = min(s.selection[0], s.selection[1]) hi = max(s.selection[0], s.selection[1]) - lo = clamp(lo, 0, len(s.builder.buf)) - hi = clamp(hi, 0, len(s.builder.buf)) + lo = clamp(lo, 0, len(s.builder.buf) if s.builder != nil else 0) + hi = clamp(hi, 0, len(s.builder.buf) if s.builder != nil else 0) return } @@ -265,7 +272,10 @@ translate_position :: proc(s: ^State, t: Translation) -> int { return b == ' ' || b == '\t' || b == '\n' } - buf := s.builder.buf[:] + buf: []byte + if s.builder != nil { + buf = s.builder.buf[:] + } pos := clamp(s.selection[0], 0, len(buf)) switch t { @@ -352,7 +362,10 @@ delete_to :: proc(s: ^State, t: Translation) { // return the currently selected text current_selected_text :: proc(s: ^State) -> string { lo, hi := sorted_selection(s) - return string(s.builder.buf[lo:hi]) + if s.builder != nil { + return string(s.builder.buf[lo:hi]) + } + return "" } // copy & delete the current selection when copy() succeeds @@ -431,7 +444,7 @@ perform_command :: proc(s: ^State, cmd: Command) { case .Cut: cut(s) case .Copy: copy(s) case .Paste: paste(s) - case .Select_All: s.selection = {len(s.builder.buf), 0} + case .Select_All: s.selection = {len(s.builder.buf) if s.builder != nil else 0, 0} case .Backspace: delete_to(s, .Left) case .Delete: delete_to(s, .Right) case .Delete_Word_Left: delete_to(s, .Word_Left) From 76229cabfaf1f3fd973842b8e11e16c2e4e64896 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 15:44:03 +0100 Subject: [PATCH 069/171] Fix typo --- core/text/edit/text_edit.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 08fce0444..6f21c9860 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -92,7 +92,7 @@ begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { end(s) } s.id = id - s.selection = {len(builder.buf, 0} + s.selection = {len(builder.buf), 0} s.builder = builder update_time(s) undo_clear(s, &s.undo) From 0729f2b4fb1d03beaae468ab7da8b736b060da3e Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 15 Apr 2024 21:26:30 +0200 Subject: [PATCH 070/171] Fix for dynlib:initialize_symbols not passing -vet-unused --- core/dynlib/lib.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 30d55edae..3d41cbe2e 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -135,7 +135,7 @@ initialize_symbols :: proc( prefixed_symbol_buf: [2048]u8 = --- count = 0 - for field, i in reflect.struct_fields_zipped(T) { + for field in reflect.struct_fields_zipped(T) { // Calculate address of struct member field_ptr := rawptr(uintptr(symbol_table) + field.offset) From 3e449e93dd60d80c89657d42efa85cb3cc4e8cc3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:07:05 -0400 Subject: [PATCH 071/171] Implement Fisher-Yates shuffle --- core/math/rand/rand.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 560dc8379..d6a20bd1e 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -789,8 +789,8 @@ shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) { return } - for i := i64(0); i < n; i += 1 { - j := int63_max(n, r) + for i := i64(n - 1); i > 0; i -= 1 { + j := int63_max(i + 1, r) array[i], array[j] = array[j], array[i] } } From 8a0f9ae108a75d9ca86b8a91fca2f2423e0a58df Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Apr 2024 13:15:23 +0100 Subject: [PATCH 072/171] Print to string buffer before printing errors --- src/error.cpp | 142 ++++++++++++++++++++++++++++--------------------- src/string.cpp | 37 +++++++++++++ 2 files changed, 119 insertions(+), 60 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index 2e6641e3b..8c9fb265b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -7,7 +7,8 @@ struct ErrorValue { ErrorValueKind kind; TokenPos pos; TokenPos end; - Array msgs; + Array msg; + bool seen_newline; }; struct ErrorCollector { @@ -30,19 +31,21 @@ gb_global ErrorCollector global_error_collector; gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue"); ErrorValue ev = {kind, pos}; - ev.msgs.allocator = heap_allocator(); + ev.msg.allocator = heap_allocator(); global_error_collector.curr_error_value = ev; global_error_collector.curr_error_value_set.store(true); } gb_internal void pop_error_value(void) { + mutex_lock(&global_error_collector.mutex); if (global_error_collector.curr_error_value_set.load()) { array_add(&global_error_collector.error_values, global_error_collector.curr_error_value); global_error_collector.curr_error_value = {}; global_error_collector.curr_error_value_set.store(false); } + mutex_unlock(&global_error_collector.mutex); } @@ -180,9 +183,18 @@ gb_internal ERROR_OUT_PROC(default_error_out_va) { isize n = len-1; if (n > 0) { - String msg = copy_string(permanent_allocator(), {(u8 *)buf, n}); ErrorValue *ev = get_error_value(); - array_add(&ev->msgs, msg); + if (terse_errors()) { + for (isize i = 0; i < n && !ev->seen_newline; i++) { + u8 c = cast(u8)buf[i]; + if (c == '\n') { + ev->seen_newline = true; + } + array_add(&ev->msg, c); + } + } else { + array_add_elems(&ev->msg, (u8 *)buf, n); + } } } @@ -645,109 +657,119 @@ gb_internal int error_value_cmp(void const *a, void const *b) { } gb_internal void print_all_errors(void) { - auto const &escape_char = [](gbFile *f, u8 c) { + auto const &escape_char = [](gbString res, u8 c) -> gbString { switch (c) { - case '\n': gb_file_write(f, "\\n", 2); break; - case '"': gb_file_write(f, "\\\"", 2); break; - case '\\': gb_file_write(f, "\\\\", 2); break; - case '\b': gb_file_write(f, "\\b", 2); break; - case '\f': gb_file_write(f, "\\f", 2); break; - case '\r': gb_file_write(f, "\\r", 2); break; - case '\t': gb_file_write(f, "\\t", 2); break; + case '\n': res = gb_string_append_length(res, "\\n", 2); break; + case '"': res = gb_string_append_length(res, "\\\"", 2); break; + case '\\': res = gb_string_append_length(res, "\\\\", 2); break; + case '\b': res = gb_string_append_length(res, "\\b", 2); break; + case '\f': res = gb_string_append_length(res, "\\f", 2); break; + case '\r': res = gb_string_append_length(res, "\\r", 2); break; + case '\t': res = gb_string_append_length(res, "\\t", 2); break; default: if ('\x00' <= c && c <= '\x1f') { - gb_fprintf(f, "\\u%04x", c); + res = gb_string_append_fmt(res, "\\u%04x", c); } else { - gb_file_write(f, &c, 1); + res = gb_string_append_length(res, &c, 1); } break; } + return res; }; GB_ASSERT(any_errors() || any_warnings()); - gbFile *f = gb_file_get_standard(gbFileStandard_Error); + array_sort(global_error_collector.error_values, error_value_cmp); + gbString res = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(res)); if (json_errors()) { - gb_fprintf(f, "{\n"); - gb_fprintf(f, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); - gb_fprintf(f, "\t\"errors\": [\n"); + res = gb_string_append_fmt(res, "{\n"); + res = gb_string_append_fmt(res, "\t\"error_count\": %td,\n", global_error_collector.error_values.count); + res = gb_string_append_fmt(res, "\t\"errors\": [\n"); for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; - gb_fprintf(f, "\t\t{\n"); + res = gb_string_append_fmt(res, "\t\t{\n"); - gb_fprintf(f, "\t\t\t\"type\": \""); + res = gb_string_append_fmt(res, "\t\t\t\"type\": \""); if (ev.kind == ErrorValue_Warning) { - gb_fprintf(f, "warning"); + res = gb_string_append_fmt(res, "warning"); } else { - gb_fprintf(f, "error"); + res = gb_string_append_fmt(res, "error"); } - gb_fprintf(f, "\",\n"); + res = gb_string_append_fmt(res, "\",\n"); - gb_fprintf(f, "\t\t\t\"pos\": {\n"); + res = gb_string_append_fmt(res, "\t\t\t\"pos\": {\n"); if (ev.pos.file_id) { - gb_fprintf(f, "\t\t\t\t\"file\": \""); + res = gb_string_append_fmt(res, "\t\t\t\t\"file\": \""); String file = get_file_path_string(ev.pos.file_id); for (isize k = 0; k < file.len; k++) { - escape_char(f, file.text[k]); + res = escape_char(res, file.text[k]); } - gb_fprintf(f, "\",\n"); - gb_fprintf(f, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); - gb_fprintf(f, "\t\t\t\t\"line\": %d,\n", ev.pos.line); - gb_fprintf(f, "\t\t\t\t\"column\": %d,\n", ev.pos.column); + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); + res = gb_string_append_fmt(res, "\t\t\t\t\"line\": %d,\n", ev.pos.line); + res = gb_string_append_fmt(res, "\t\t\t\t\"column\": %d,\n", ev.pos.column); i32 end_column = gb_max(ev.end.column, ev.pos.column); - gb_fprintf(f, "\t\t\t\t\"end_column\": %d\n", end_column); - gb_fprintf(f, "\t\t\t},\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\"end_column\": %d\n", end_column); + res = gb_string_append_fmt(res, "\t\t\t},\n"); } - gb_fprintf(f, "\t\t\t\"msgs\": [\n"); + res = gb_string_append_fmt(res, "\t\t\t\"msgs\": [\n"); - if (ev.msgs.count > 1) { - gb_fprintf(f, "\t\t\t\t\""); + auto lines = split_lines_from_array(ev.msg, heap_allocator()); + defer (array_free(&lines)); - for (isize j = 1; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - for (isize k = 0; k < msg.len; k++) { - u8 c = msg.text[k]; - if (c == '\n') { - if (k+1 == msg.len && j+1 == ev.msgs.count) { - // don't do the last one - } else { - gb_fprintf(f, "\",\n"); - gb_fprintf(f, "\t\t\t\t\""); - } - } else { - escape_char(f, c); - } + if (lines.count > 0) { + res = gb_string_append_fmt(res, "\t\t\t\t\""); + + for (isize j = 0; j < lines.count; j++) { + String line = lines[j]; + for (isize k = 0; k < line.len; k++) { + u8 c = line.text[k]; + res = escape_char(res, c); + } + if (j+1 < lines.count) { + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\""); } } - gb_fprintf(f, "\"\n"); + res = gb_string_append_fmt(res, "\"\n"); } - gb_fprintf(f, "\t\t\t]\n"); - gb_fprintf(f, "\t\t}"); + res = gb_string_append_fmt(res, "\t\t\t]\n"); + res = gb_string_append_fmt(res, "\t\t}"); if (i+1 != global_error_collector.error_values.count) { - gb_fprintf(f, ","); + res = gb_string_append_fmt(res, ","); } - gb_fprintf(f, "\n"); + res = gb_string_append_fmt(res, "\n"); } - gb_fprintf(f, "\t]\n"); - gb_fprintf(f, "}\n"); + res = gb_string_append_fmt(res, "\t]\n"); + res = gb_string_append_fmt(res, "}\n"); } else { for_array(i, global_error_collector.error_values) { ErrorValue ev = global_error_collector.error_values[i]; - for (isize j = 0; j < ev.msgs.count; j++) { - String msg = ev.msgs[j]; - gb_file_write(f, msg.text, msg.len); - if (terse_errors() && string_contains_char(msg, '\n')) { + String_Iterator it = {{ev.msg.data, ev.msg.count}, 0}; + + for (isize line_idx = 0; /**/; line_idx++) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + line = string_trim_trailing_whitespace(line); + res = gb_string_append_length(res, line.text, line.len); + res = gb_string_append_length(res, " \n", 2); + if (line_idx == 0 && terse_errors()) { break; } } } } + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + gb_file_write(f, res, gb_string_length(res)); } \ No newline at end of file diff --git a/src/string.cpp b/src/string.cpp index 4adec7a90..3747f4564 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -276,6 +276,43 @@ gb_internal String string_trim_whitespace(String str) { return str; } +gb_internal String string_trim_trailing_whitespace(String str) { + while (str.len > 0) { + u8 c = str[str.len-1]; + if (rune_is_whitespace(c) || c == 0) { + str.len -= 1; + } else { + break; + } + } + return str; +} + +gb_internal String split_lines_first_line_from_array(Array const &array, gbAllocator allocator) { + String_Iterator it = {{array.data, array.count}, 0}; + + String line = string_split_iterator(&it, '\n'); + line = string_trim_trailing_whitespace(line); + return line; +} + +gb_internal Array split_lines_from_array(Array const &array, gbAllocator allocator) { + Array lines = {}; + lines.allocator = allocator; + + String_Iterator it = {{array.data, array.count}, 0}; + + for (;;) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + line = string_trim_trailing_whitespace(line); + array_add(&lines, line); + } + + return lines; +} gb_internal bool string_contains_char(String const &s, u8 c) { isize i; From a61ae7c861fa301684ee1582507061317b11426b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Apr 2024 13:31:49 +0100 Subject: [PATCH 073/171] Fix #3427 --- src/check_type.cpp | 20 ++++++++++++++------ src/checker.hpp | 5 ++++- src/llvm_backend_general.cpp | 2 +- src/llvm_backend_type.cpp | 2 +- src/llvm_backend_utility.cpp | 4 ++-- src/types.cpp | 1 - 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index f4e5d7c96..3bb1a4fd1 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2495,18 +2495,16 @@ gb_internal Type *get_map_cell_type(Type *type) { return s; } -gb_internal void init_map_internal_types(Type *type) { +gb_internal void init_map_internal_debug_types(Type *type) { GB_ASSERT(type->kind == Type_Map); GB_ASSERT(t_allocator != nullptr); - if (type->Map.lookup_result_type != nullptr) return; + if (type->Map.debug_metadata_type != nullptr) return; Type *key = type->Map.key; Type *value = type->Map.value; GB_ASSERT(key != nullptr); GB_ASSERT(value != nullptr); - - Type *key_cell = get_map_cell_type(key); Type *value_cell = get_map_cell_type(value); @@ -2541,6 +2539,18 @@ gb_internal void init_map_internal_types(Type *type) { gb_unused(type_size_of(debug_type)); type->Map.debug_metadata_type = debug_type; +} + + +gb_internal void init_map_internal_types(Type *type) { + GB_ASSERT(type->kind == Type_Map); + GB_ASSERT(t_allocator != nullptr); + if (type->Map.lookup_result_type != nullptr) return; + + Type *key = type->Map.key; + Type *value = type->Map.value; + GB_ASSERT(key != nullptr); + GB_ASSERT(value != nullptr); type->Map.lookup_result_type = make_optional_ok_type(value); } @@ -2613,8 +2623,6 @@ gb_internal void check_map_type(CheckerContext *ctx, Type *type, Ast *node) { init_core_map_type(ctx->checker); init_map_internal_types(type); - - // error(node, "'map' types are not yet implemented"); } gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) { diff --git a/src/checker.hpp b/src/checker.hpp index 1701da58d..2ade9312e 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -563,4 +563,7 @@ gb_internal void init_mem_allocator(Checker *c); gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped); -gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); \ No newline at end of file +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); + + +gb_internal void init_map_internal_types(Type *type); \ No newline at end of file diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 73e4a00e6..7a5ed5635 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2070,7 +2070,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { break; case Type_Map: - init_map_internal_types(type); + init_map_internal_debug_types(type); GB_ASSERT(t_raw_map != nullptr); return lb_type_internal(m, t_raw_map); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 93e2874a5..0bac2f732 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -903,7 +903,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ case Type_Map: { tag_type = t_type_info_map; - init_map_internal_types(t); + init_map_internal_debug_types(t); LLVMValueRef vals[3] = { get_type_info_ptr(m, t->Map.key), diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 865c3f1ec..0d1db2cbf 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1125,7 +1125,7 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { case 3: result_type = t_allocator; break; } } else if (is_type_map(t)) { - init_map_internal_types(t); + init_map_internal_debug_types(t); Type *itp = alloc_type_pointer(t_raw_map); s = lb_emit_transmute(p, s, itp); @@ -1264,7 +1264,7 @@ gb_internal lbValue lb_emit_struct_ev(lbProcedure *p, lbValue s, i32 index) { case Type_Map: { - init_map_internal_types(t); + init_map_internal_debug_types(t); switch (index) { case 0: result_type = get_struct_field_type(t_raw_map, 0); break; case 1: result_type = get_struct_field_type(t_raw_map, 1); break; diff --git a/src/types.cpp b/src/types.cpp index 97512d29b..18cb12ea1 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -769,7 +769,6 @@ gb_internal gbString type_to_string (Type *type, bool shorthand=true); gb_internal gbString type_to_string (Type *type, gbAllocator allocator, bool shorthand=true); gb_internal i64 type_size_of_internal(Type *t, TypePath *path); gb_internal i64 type_align_of_internal(Type *t, TypePath *path); -gb_internal void init_map_internal_types(Type *type); gb_internal Type * bit_set_to_int(Type *t); gb_internal bool are_types_identical(Type *x, Type *y); From 6dcf38b85bc7ab7659671f7ac1d0dfdc5d942163 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 16 Apr 2024 21:36:54 +0100 Subject: [PATCH 074/171] Correct `copy_from_string` docs --- base/runtime/core_builtin.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index c5c419de3..00c30d3fd 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -40,7 +40,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { } return n } -// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`. +// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). // @@ -53,7 +53,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int } return n } -// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`. +// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). @builtin From 7cd2bc26f42237f825274198c5bc68f7633b73b1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 17 Apr 2024 13:31:32 +0100 Subject: [PATCH 075/171] Clear error message on collisions with `using` on struct fields --- src/check_type.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index 3bb1a4fd1..a6dbb8dfc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -29,10 +29,11 @@ gb_internal void populate_using_array_index(CheckerContext *ctx, Ast *node, AstF } } -gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t) { +gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t, isize level) { if (t == nullptr) { return; } + Type *original_type = t; t = base_type(type_deref(t)); gbString str = nullptr; defer (gb_string_free(str)); @@ -46,16 +47,18 @@ gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, Ast String name = f->token.string; Entity *e = scope_lookup_current(ctx->scope, name); if (e != nullptr && name != "_") { + gbString ot = type_to_string(original_type); // TODO(bill): Better type error if (str != nullptr) { - error(e->token, "'%.*s' is already declared in '%s'", LIT(name), str); + error(e->token, "'%.*s' is already declared in '%s', through 'using' from '%s'", LIT(name), str, ot); } else { - error(e->token, "'%.*s' is already declared", LIT(name)); + error(e->token, "'%.*s' is already declared, through 'using' from '%s'", LIT(name), ot); } + gb_string_free(ot); } else { add_entity(ctx, ctx->scope, nullptr, f); if (f->flags & EntityFlag_Using) { - populate_using_entity_scope(ctx, node, field, f->type); + populate_using_entity_scope(ctx, node, field, f->type, level+1); } } } @@ -200,7 +203,7 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slicenames.count > 0) { From e296b050eeca33ef44eb71e00532a8f2f971cb01 Mon Sep 17 00:00:00 2001 From: Matias Fernandez Date: Wed, 17 Apr 2024 22:54:30 -0400 Subject: [PATCH 076/171] fix #soa '%#v' formatting --- core/fmt/fmt.odin | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index d3b9d7d69..7f432a6be 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1900,7 +1900,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St // fi.hash = false; fi.indent += 1 - if hash { + if !is_soa && hash { io.write_byte(fi.writer, '\n', &fi.n) } defer { @@ -1934,6 +1934,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) } + if hash && n > 0 { + io.write_byte(fi.writer, '\n', &fi.n) + } for index in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } @@ -1942,9 +1945,23 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St if !hash && field_count > 0 { io.write_string(fi.writer, ", ", &fi.n) } + if hash { + fi.indent -= 1 + fmt_write_indent(fi) + fi.indent += 1 + } io.write_string(fi.writer, base_type_name, &fi.n) io.write_byte(fi.writer, '{', &fi.n) - defer io.write_byte(fi.writer, '}', &fi.n) + if hash { io.write_byte(fi.writer, '\n', &fi.n) } + defer { + if hash { + fi.indent -= 1 + fmt_write_indent(fi) + fi.indent += 1 + } + io.write_byte(fi.writer, '}', &fi.n) + if hash { io.write_string(fi.writer, ",\n", &fi.n) } + } fi.record_level += 1 defer fi.record_level -= 1 From 6127339c56e15eeb4282ea385333c98d690091ee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 10:59:02 +0100 Subject: [PATCH 077/171] Add `#force_no_inline` to many of the runtime print procedures --- base/runtime/print.odin | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/base/runtime/print.odin b/base/runtime/print.odin index c93c2ab49..e8da84f14 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" _INTEGER_DIGITS_VAR := _INTEGER_DIGITS when !ODIN_NO_RTTI { - print_any_single :: proc "contextless" (arg: any) { + print_any_single :: #force_no_inline proc "contextless" (arg: any) { x := arg if x.data == nil { print_string("nil") @@ -72,7 +72,7 @@ when !ODIN_NO_RTTI { print_string("") } } - println_any :: proc "contextless" (args: ..any) { + println_any :: #force_no_inline proc "contextless" (args: ..any) { context = default_context() loop: for arg, i in args { assert(arg.id != nil) @@ -122,12 +122,12 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { return buf, 4 } -print_string :: proc "contextless" (str: string) -> (n: int) { +print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) { n, _ = stderr_write(transmute([]byte)str) return } -print_strings :: proc "contextless" (args: ..string) -> (n: int) { +print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) { for str in args { m, err := stderr_write(transmute([]byte)str) n += m @@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) { return } -print_byte :: proc "contextless" (b: byte) -> (n: int) { +print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) { n, _ = stderr_write([]byte{b}) return } -print_encoded_rune :: proc "contextless" (r: rune) { +print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) { print_byte('\'') switch r { @@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) { print_byte('\'') } -print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { +print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check { RUNE_SELF :: 0x80 if r < RUNE_SELF { @@ -183,7 +183,7 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { } -print_u64 :: proc "contextless" (x: u64) #no_bounds_check { +print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check { a: [129]byte i := len(a) b := u64(10) @@ -198,7 +198,7 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check { } -print_i64 :: proc "contextless" (x: i64) #no_bounds_check { +print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check { b :: i64(10) u := x @@ -223,7 +223,7 @@ print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) } print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) } print_int :: proc "contextless" (x: int) { print_i64(i64(x)) } -print_caller_location :: proc "contextless" (loc: Source_Code_Location) { +print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) { print_string(loc.file_path) when ODIN_ERROR_POS_STYLE == .Default { print_byte('(') @@ -241,7 +241,7 @@ print_caller_location :: proc "contextless" (loc: Source_Code_Location) { #panic("unhandled ODIN_ERROR_POS_STYLE") } } -print_typeid :: proc "contextless" (id: typeid) { +print_typeid :: #force_no_inline proc "contextless" (id: typeid) { when ODIN_NO_RTTI { if id == nil { print_string("nil") @@ -257,7 +257,7 @@ print_typeid :: proc "contextless" (id: typeid) { } } } -print_type :: proc "contextless" (ti: ^Type_Info) { +print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { if ti == nil { print_string("nil") return From 889cd5461ca3601d5359269345594903bd9ed5ca Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 11:00:47 +0100 Subject: [PATCH 078/171] Add `@(optimization_mode="size")` to `runtime.print_type` --- base/runtime/print.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/runtime/print.odin b/base/runtime/print.odin index e8da84f14..4e2ffaf80 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -257,6 +257,8 @@ print_typeid :: #force_no_inline proc "contextless" (id: typeid) { } } } + +@(optimization_mode="size") print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { if ti == nil { print_string("nil") From 5c52f3cf2fee63f860914d062555ed87c1dba9d8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 11:13:44 +0100 Subject: [PATCH 079/171] Add `ODIN_NO_BOUNDS_CHECK` --- base/runtime/error_checks.odin | 14 +++++++++++++- src/checker.cpp | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/base/runtime/error_checks.odin b/base/runtime/error_checks.odin index ea6333c29..742e06a71 100644 --- a/base/runtime/error_checks.odin +++ b/base/runtime/error_checks.odin @@ -19,6 +19,7 @@ type_assertion_trap :: proc "contextless" () -> ! { } +@(disabled=ODIN_NO_BOUNDS_CHECK) bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { if uint(index) < uint(count) { return @@ -61,6 +62,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu } +@(disabled=ODIN_NO_BOUNDS_CHECK) multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { if lo <= hi { return @@ -68,6 +70,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column multi_pointer_slice_handle_error(file, line, column, lo, hi) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { if 0 <= hi && hi <= len { return @@ -75,6 +78,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: slice_handle_error(file, line, column, 0, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) { if 0 <= lo && lo <= len && lo <= hi && hi <= len { return @@ -82,6 +86,7 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l slice_handle_error(file, line, column, lo, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { if 0 <= low && low <= high && high <= max { return @@ -102,6 +107,7 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, } +@(disabled=ODIN_NO_BOUNDS_CHECK) matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) { if uint(row_index) < uint(row_count) && uint(column_index) < uint(column_count) { @@ -224,6 +230,7 @@ when ODIN_NO_RTTI { } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { if 0 <= len { return @@ -239,6 +246,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio handle_error(loc, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { if 0 <= len && len <= cap { return @@ -256,6 +264,7 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller handle_error(loc, len, cap) } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { if 0 <= cap { return @@ -274,19 +283,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca - +@(disabled=ODIN_NO_BOUNDS_CHECK) bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) { bounds_check_error(loc.file_path, loc.line, loc.column, index, count) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) { slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) { slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) { dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max) } diff --git a/src/checker.cpp b/src/checker.cpp index 2b3ca0e9f..e82836b2a 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1111,6 +1111,7 @@ gb_internal void init_universal(void) { add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); + add_global_bool_constant("ODIN_NO_BOUNDS_CHECK", build_context.no_bounds_check); add_global_bool_constant("ODIN_DEFAULT_TO_PANIC_ALLOCATOR", bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR); add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals); add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); From ece78d22d2b549116a0884d3578972b8f389f983 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 11:22:31 +0100 Subject: [PATCH 080/171] Add `-no-type-assert` and `ODIN_NO_TYPE_ASSERT` --- src/build_settings.cpp | 1 + src/checker.cpp | 1 + src/llvm_backend_expr.cpp | 4 +- src/llvm_backend_utility.cpp | 80 +++++++++++++++++++----------------- src/main.cpp | 9 ++++ 5 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 106ae8a28..b806adcd6 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -382,6 +382,7 @@ struct BuildContext { bool keep_temp_files; bool ignore_unknown_attributes; bool no_bounds_check; + bool no_type_assert; bool no_dynamic_literals; bool no_output_files; bool no_crt; diff --git a/src/checker.cpp b/src/checker.cpp index e82836b2a..b7fe2b903 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1112,6 +1112,7 @@ gb_internal void init_universal(void) { add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); add_global_bool_constant("ODIN_NO_BOUNDS_CHECK", build_context.no_bounds_check); + add_global_bool_constant("ODIN_NO_TYPE_ASSERT", build_context.no_type_assert); add_global_bool_constant("ODIN_DEFAULT_TO_PANIC_ALLOCATOR", bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR); add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals); add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index ad28f2e5e..edd5daeca 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -3116,7 +3116,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { Type *dst_type = type; - if ((p->state_flags & StateFlag_no_type_assert) == 0) { + if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) { lbValue src_tag = {}; lbValue dst_tag = {}; if (is_type_union_maybe_pointer(src_type)) { @@ -3156,7 +3156,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { v = lb_emit_load(p, v); } lbValue data_ptr = lb_emit_struct_ev(p, v, 0); - if ((p->state_flags & StateFlag_no_type_assert) == 0) { + if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) { GB_ASSERT(!build_context.no_rtti); lbValue any_id = lb_emit_struct_ev(p, v, 1); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 0d1db2cbf..2dd7fbc38 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -728,30 +728,32 @@ gb_internal lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type lb_start_block(p, end_block); if (!is_tuple) { - GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0); - // NOTE(bill): Panic on invalid conversion - Type *dst_type = tuple->Tuple.variables[0]->type; + if (!build_context.no_type_assert) { + GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0); + // NOTE(bill): Panic on invalid conversion + Type *dst_type = tuple->Tuple.variables[0]->type; - isize arg_count = 7; - if (build_context.no_rtti) { - arg_count = 4; + isize arg_count = 7; + if (build_context.no_rtti) { + arg_count = 4; + } + + lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); + auto args = array_make(permanent_allocator(), arg_count); + args[0] = ok; + + args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(m, t_i32, pos.line); + args[3] = lb_const_int(m, t_i32, pos.column); + + if (!build_context.no_rtti) { + args[4] = lb_typeid(m, src_type); + args[5] = lb_typeid(m, dst_type); + args[6] = lb_emit_conv(p, value_, t_rawptr); + } + lb_emit_runtime_call(p, "type_assertion_check2", args); } - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - auto args = array_make(permanent_allocator(), arg_count); - args[0] = ok; - - args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(m, t_i32, pos.line); - args[3] = lb_const_int(m, t_i32, pos.column); - - if (!build_context.no_rtti) { - args[4] = lb_typeid(m, src_type); - args[5] = lb_typeid(m, dst_type); - args[6] = lb_emit_conv(p, value_, t_rawptr); - } - lb_emit_runtime_call(p, "type_assertion_check2", args); - return lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 0)); } return lb_addr_load(p, v); @@ -806,25 +808,27 @@ gb_internal lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *ty if (!is_tuple) { // NOTE(bill): Panic on invalid conversion - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); + if (!build_context.no_type_assert) { + lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - isize arg_count = 7; - if (build_context.no_rtti) { - arg_count = 4; + isize arg_count = 7; + if (build_context.no_rtti) { + arg_count = 4; + } + auto args = array_make(permanent_allocator(), arg_count); + args[0] = ok; + + args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(m, t_i32, pos.line); + args[3] = lb_const_int(m, t_i32, pos.column); + + if (!build_context.no_rtti) { + args[4] = any_typeid; + args[5] = dst_typeid; + args[6] = lb_emit_struct_ev(p, value, 0); + } + lb_emit_runtime_call(p, "type_assertion_check2", args); } - auto args = array_make(permanent_allocator(), arg_count); - args[0] = ok; - - args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(m, t_i32, pos.line); - args[3] = lb_const_int(m, t_i32, pos.column); - - if (!build_context.no_rtti) { - args[4] = any_typeid; - args[5] = dst_typeid; - args[6] = lb_emit_struct_ev(p, value, 0); - } - lb_emit_runtime_call(p, "type_assertion_check2", args); return lb_addr(lb_emit_struct_ep(p, v.addr, 0)); } diff --git a/src/main.cpp b/src/main.cpp index 063b6c8b3..53103ce3a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -243,6 +243,7 @@ enum BuildFlagKind { BuildFlag_Debug, BuildFlag_DisableAssert, BuildFlag_NoBoundsCheck, + BuildFlag_NoTypeAssert, BuildFlag_NoDynamicLiterals, BuildFlag_NoCRT, BuildFlag_NoEntryPoint, @@ -436,6 +437,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoTypeAssert, str_lit("no-type-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); @@ -1013,6 +1015,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_NoBoundsCheck: build_context.no_bounds_check = true; break; + case BuildFlag_NoTypeAssert: + build_context.no_type_assert = true; + break; case BuildFlag_NoDynamicLiterals: build_context.no_dynamic_literals = true; break; @@ -1850,6 +1855,10 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Disables bounds checking program wide."); print_usage_line(0, ""); + print_usage_line(1, "-no-type-assert"); + print_usage_line(2, "Disables type assertion checking program wide."); + print_usage_line(0, ""); + print_usage_line(1, "-no-crt"); print_usage_line(2, "Disables automatic linking with the C Run Time."); print_usage_line(0, ""); From aad41fc76210814f4bf708927a10d17de0e2621d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 11:27:42 +0100 Subject: [PATCH 081/171] Fix #3445 --- src/llvm_backend_debug.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 511ff0475..b430cf894 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -739,6 +739,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { } case Type_Map: { + init_map_internal_debug_types(bt); Type *bt = base_type(type->Map.debug_metadata_type); GB_ASSERT(bt->kind == Type_Struct); @@ -945,6 +946,7 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) { } case Type_Map: { + init_map_internal_debug_types(bt); bt = base_type(bt->Map.debug_metadata_type); GB_ASSERT(bt->kind == Type_Struct); return lb_debug_struct(m, type, bt, name, scope, file, line); From f84a09297795bd5c6d00305e0ad32dd696fe36a7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 11:28:46 +0100 Subject: [PATCH 082/171] Fix typo. --- src/llvm_backend_debug.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index b430cf894..853941496 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -739,7 +739,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { } case Type_Map: { - init_map_internal_debug_types(bt); + init_map_internal_debug_types(type); Type *bt = base_type(type->Map.debug_metadata_type); GB_ASSERT(bt->kind == Type_Struct); From 334e08c750a1eac3042b867c0e460b7bc516743e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 12:17:39 +0100 Subject: [PATCH 083/171] Update numerous package declaration names --- core/compress/gzip/example.odin | 2 +- core/compress/gzip/gzip.odin | 2 +- core/compress/shoco/model.odin | 2 +- core/compress/shoco/shoco.odin | 2 +- core/compress/zlib/example.odin | 2 +- core/compress/zlib/zlib.odin | 2 +- core/container/bit_array/bit_array.odin | 2 +- core/container/bit_array/doc.odin | 2 +- core/encoding/base32/base32.odin | 2 +- core/encoding/base64/base64.odin | 2 +- core/encoding/cbor/cbor.odin | 2 +- core/encoding/cbor/coding.odin | 2 +- core/encoding/cbor/doc.odin | 2 +- core/encoding/cbor/marshal.odin | 2 +- core/encoding/cbor/tags.odin | 2 +- core/encoding/cbor/unmarshal.odin | 2 +- core/encoding/csv/reader.odin | 2 +- core/encoding/csv/writer.odin | 2 +- core/encoding/entity/entity.odin | 2 +- core/encoding/entity/generated.odin | 2 +- core/encoding/hex/hex.odin | 2 +- core/encoding/json/marshal.odin | 2 +- core/encoding/json/parser.odin | 2 +- core/encoding/json/tokenizer.odin | 2 +- core/encoding/json/types.odin | 2 +- core/encoding/json/unmarshal.odin | 2 +- core/encoding/json/validator.odin | 2 +- core/encoding/varint/doc.odin | 2 +- core/encoding/varint/leb128.odin | 2 +- core/encoding/xml/debug_print.odin | 2 +- core/encoding/xml/helpers.odin | 2 +- core/encoding/xml/tokenizer.odin | 2 +- core/encoding/xml/xml_reader.odin | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index 635134e40..09540aafc 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -1,5 +1,5 @@ //+build ignore -package gzip +package compress_gzip /* Copyright 2021 Jeroen van Rijn . diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 50945fc77..57ed3c3c5 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -1,4 +1,4 @@ -package gzip +package compress_gzip /* Copyright 2021 Jeroen van Rijn . diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin index bbc38903d..f62236c00 100644 --- a/core/compress/shoco/model.odin +++ b/core/compress/shoco/model.odin @@ -5,7 +5,7 @@ */ // package shoco is an implementation of the shoco short string compressor -package shoco +package compress_shoco DEFAULT_MODEL :: Shoco_Model { min_char = 39, diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index e65acb0bc..3c1f412ba 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -9,7 +9,7 @@ */ // package shoco is an implementation of the shoco short string compressor -package shoco +package compress_shoco import "base:intrinsics" import "core:compress" diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin index 19017b279..fedd6671d 100644 --- a/core/compress/zlib/example.odin +++ b/core/compress/zlib/example.odin @@ -1,5 +1,5 @@ //+build ignore -package zlib +package compress_zlib /* Copyright 2021 Jeroen van Rijn . diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index d4dc6e3d7..b7f381f2b 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -1,5 +1,5 @@ //+vet !using-param -package zlib +package compress_zlib /* Copyright 2021 Jeroen van Rijn . diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index dbd2e0d3a..a8720715c 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -1,4 +1,4 @@ -package dynamic_bit_array +package container_dynamic_bit_array import "base:intrinsics" import "core:mem" diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index 371f63f0e..77e1904a8 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -49,4 +49,4 @@ The Bit Array can be used in several ways: fmt.printf("Freed.\n") } */ -package dynamic_bit_array +package container_dynamic_bit_array diff --git a/core/encoding/base32/base32.odin b/core/encoding/base32/base32.odin index 7ab35afd0..962a3ead4 100644 --- a/core/encoding/base32/base32.odin +++ b/core/encoding/base32/base32.odin @@ -1,4 +1,4 @@ -package base32 +package encoding_base32 // @note(zh): Encoding utility for Base32 // A secondary param can be used to supply a custom alphabet to diff --git a/core/encoding/base64/base64.odin b/core/encoding/base64/base64.odin index 535d457d5..1013a7d0b 100644 --- a/core/encoding/base64/base64.odin +++ b/core/encoding/base64/base64.odin @@ -1,4 +1,4 @@ -package base64 +package encoding_base64 import "core:io" import "core:mem" diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 550cf87fd..d0e406ab1 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -1,4 +1,4 @@ -package cbor +package encoding_cbor import "base:intrinsics" diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 11db994da..0d276a7a1 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -1,4 +1,4 @@ -package cbor +package encoding_cbor import "base:intrinsics" import "base:runtime" diff --git a/core/encoding/cbor/doc.odin b/core/encoding/cbor/doc.odin index 77eac51cb..937b1b61b 100644 --- a/core/encoding/cbor/doc.odin +++ b/core/encoding/cbor/doc.odin @@ -137,5 +137,5 @@ Output: "str": "Hello, World!" } */ -package cbor +package encoding_cbor diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 87e91bbd8..37c9dd180 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -1,4 +1,4 @@ -package cbor +package encoding_cbor import "base:intrinsics" import "base:runtime" diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 040ce2458..3dc79a5dd 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -1,4 +1,4 @@ -package cbor +package encoding_cbor import "base:runtime" diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 5480b9125..a1524d9f4 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -1,4 +1,4 @@ -package cbor +package encoding_cbor import "base:intrinsics" import "base:runtime" diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index 44a9fdcc4..22eea9568 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -1,6 +1,6 @@ // package csv reads and writes comma-separated values (CSV) files. // This package supports the format described in RFC 4180 -package csv +package encoding_csv import "core:bufio" import "core:bytes" diff --git a/core/encoding/csv/writer.odin b/core/encoding/csv/writer.odin index d519104f2..46145ecc1 100644 --- a/core/encoding/csv/writer.odin +++ b/core/encoding/csv/writer.odin @@ -1,4 +1,4 @@ -package csv +package encoding_csv import "core:io" import "core:strings" diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index ec640c69f..cee6230ef 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -1,4 +1,4 @@ -package unicode_entity +package encoding_unicode_entity /* A unicode entity encoder/decoder diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index 3d1c02513..d2acde20d 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -1,4 +1,4 @@ -package unicode_entity +package encoding_unicode_entity /* ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index ef0bab1d0..dbffe216b 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -1,4 +1,4 @@ -package hex +package encoding_hex import "core:strings" diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 3d57316b3..04ef6d434 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" import "core:math/bits" diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index 8bcef1339..3973725dc 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" import "core:unicode/utf8" diff --git a/core/encoding/json/tokenizer.odin b/core/encoding/json/tokenizer.odin index a406a73a5..5c20a2cc3 100644 --- a/core/encoding/json/tokenizer.odin +++ b/core/encoding/json/tokenizer.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:unicode/utf8" diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 20c806236..73e183615 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:strings" diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index b2052e43c..691303521 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" import "core:math" diff --git a/core/encoding/json/validator.odin b/core/encoding/json/validator.odin index 961c2dc23..a6873319d 100644 --- a/core/encoding/json/validator.odin +++ b/core/encoding/json/validator.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" diff --git a/core/encoding/varint/doc.odin b/core/encoding/varint/doc.odin index 5e4708a59..c0a09873c 100644 --- a/core/encoding/varint/doc.odin +++ b/core/encoding/varint/doc.odin @@ -25,4 +25,4 @@ ``` */ -package varint \ No newline at end of file +package encoding_varint \ No newline at end of file diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index 1cdbb81b0..ca6513f04 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -8,7 +8,7 @@ // package varint implements variable length integer encoding and decoding using // the LEB128 format as used by DWARF debug info, Android .dex and other file formats. -package varint +package encoding_varint // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. // Instead we'll set limits on the values we'll encode/decode diff --git a/core/encoding/xml/debug_print.odin b/core/encoding/xml/debug_print.odin index 2607bec23..be958baaa 100644 --- a/core/encoding/xml/debug_print.odin +++ b/core/encoding/xml/debug_print.odin @@ -1,4 +1,4 @@ -package xml +package encoding_xml /* An XML 1.0 / 1.1 parser diff --git a/core/encoding/xml/helpers.odin b/core/encoding/xml/helpers.odin index 42a5258b3..a9d4ad493 100644 --- a/core/encoding/xml/helpers.odin +++ b/core/encoding/xml/helpers.odin @@ -1,4 +1,4 @@ -package xml +package encoding_xml /* An XML 1.0 / 1.1 parser diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index a223a75d6..0f87c366b 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -1,4 +1,4 @@ -package xml +package encoding_xml /* An XML 1.0 / 1.1 parser diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index bf8646bc3..5b4b12948 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -24,7 +24,7 @@ MAYBE: List of contributors: - Jeroen van Rijn: Initial implementation. */ -package xml +package encoding_xml // An XML 1.0 / 1.1 parser import "core:bytes" From 689982a38dae7d6991eeca47585cca06d562d6c4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 12:22:41 +0100 Subject: [PATCH 084/171] Force runtime type table to be in rodata/rdata section --- src/llvm_backend_type.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 0bac2f732..588768b1a 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1103,6 +1103,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ LLVMValueRef giant_const = LLVMConstArray(lb_type(m, t_type_info_ptr), giant_const_values, cast(unsigned)global_type_info_data_entity_count); LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value; LLVMSetInitializer(giant_array, giant_const); + LLVMSetGlobalConstant(giant_array, true); } @@ -1132,4 +1133,7 @@ gb_internal void lb_setup_type_info_data(lbModule *m) { // NOTE(bill): Setup typ LLVMValueRef slice = llvm_const_slice_internal(m, data, len); LLVMSetInitializer(global_type_table.value, slice); + + // force it to be constant + LLVMSetGlobalConstant(global_type_table.value, true); } From b72d49ceb5e3b0010d933a7cd370b8d7e1502561 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 12:41:05 +0100 Subject: [PATCH 085/171] Set linkage to private for `__$type_info_data` --- src/llvm_backend_type.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 588768b1a..78e6af852 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1104,6 +1104,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value; LLVMSetInitializer(giant_array, giant_const); LLVMSetGlobalConstant(giant_array, true); + LLVMSetLinkage(giant_array, LLVMLinkerPrivateLinkage); } From 5200e3fe7a0eff6ecc76838e20ad33762ba08d5d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 12:45:20 +0100 Subject: [PATCH 086/171] Set `__$ti-` stuff to be private linkage --- src/llvm_backend_general.cpp | 2 +- src/llvm_backend_type.cpp | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 7a5ed5635..da69f94d7 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -211,7 +211,7 @@ gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) { gb_internal void lb_make_global_private_const(LLVMValueRef global_data) { - LLVMSetLinkage(global_data, LLVMPrivateLinkage); + LLVMSetLinkage(global_data, LLVMLinkerPrivateLinkage); LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(global_data, true); } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 78e6af852..e202a59ba 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -249,9 +249,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ char name[64] = {}; gb_snprintf(name, 63, "__$ti-%lld", cast(long long)index); LLVMValueRef g = LLVMAddGlobal(m->mod, type, name); - LLVMSetLinkage(g, LLVMInternalLinkage); - LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); - LLVMSetGlobalConstant(g, true); + lb_make_global_private_const(g); return g; }; @@ -1103,8 +1101,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ LLVMValueRef giant_const = LLVMConstArray(lb_type(m, t_type_info_ptr), giant_const_values, cast(unsigned)global_type_info_data_entity_count); LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value; LLVMSetInitializer(giant_array, giant_const); - LLVMSetGlobalConstant(giant_array, true); - LLVMSetLinkage(giant_array, LLVMLinkerPrivateLinkage); + lb_make_global_private_const(giant_array); } From 2416380f34f26bb2ccf45f5ca075293a3e07af19 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Apr 2024 12:56:18 +0100 Subject: [PATCH 087/171] Enforce as global constant --- src/llvm_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 645a091b0..4b94cf020 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2659,7 +2659,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetInitializer(g, LLVMConstNull(internal_llvm_type)); LLVMSetLinkage(g, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); - LLVMSetGlobalConstant(g, /*true*/false); + LLVMSetGlobalConstant(g, true); lbValue value = {}; value.value = g; From 3812d5e002fd2a2f4762b7732c72e49c1c6ee767 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Apr 2024 00:19:02 +0100 Subject: [PATCH 088/171] Only override the comma value on `*_init` if it is "invalid" --- core/encoding/csv/reader.odin | 5 ++++- core/encoding/csv/writer.odin | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index 22eea9568..5d3626b9f 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -91,7 +91,10 @@ DEFAULT_RECORD_BUFFER_CAPACITY :: 256 // reader_init initializes a new Reader from r reader_init :: proc(reader: ^Reader, r: io.Reader, buffer_allocator := context.allocator) { - reader.comma = ',' + switch reader.comma { + case '\x00', '\n', '\r', 0xfffd: + reader.comma = ',' + } context.allocator = buffer_allocator reserve(&reader.record_buffer, DEFAULT_RECORD_BUFFER_CAPACITY) diff --git a/core/encoding/csv/writer.odin b/core/encoding/csv/writer.odin index 46145ecc1..132fa0a51 100644 --- a/core/encoding/csv/writer.odin +++ b/core/encoding/csv/writer.odin @@ -17,7 +17,10 @@ Writer :: struct { // writer_init initializes a Writer that writes to w writer_init :: proc(writer: ^Writer, w: io.Writer) { - writer.comma = ',' + switch writer.comma { + case '\x00', '\n', '\r', 0xfffd: + writer.comma = ',' + } writer.w = w } From 20223345a4376c6490736ca952427b919c178985 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Apr 2024 00:33:31 +0100 Subject: [PATCH 089/171] Return partial reads --- core/encoding/csv/reader.odin | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index 5d3626b9f..f8c72c423 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -124,6 +124,7 @@ reader_destroy :: proc(r: ^Reader) { // read reads a single record (a slice of fields) from r // // All \r\n sequences are normalized to \n, including multi-line field +@(require_results) read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, err: Error) { if r.reuse_record { record, err = _read_record(r, &r.last_record, allocator) @@ -136,6 +137,7 @@ read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, e } // is_io_error checks where an Error is a specific io.Error kind +@(require_results) is_io_error :: proc(err: Error, io_err: io.Error) -> bool { if v, ok := err.(io.Error); ok { return v == io_err @@ -143,10 +145,10 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool { return false } - // read_all reads all the remaining records from r. // Each record is a slice of fields. // read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error +@(require_results) read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) { context.allocator = allocator records: [dynamic][]string @@ -156,13 +158,18 @@ read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Err return records[:], nil } if rerr != nil { - return nil, rerr + // allow for a partial read + if record != nil { + append(&records, record) + } + return records[:], rerr } append(&records, record) } } // read reads a single record (a slice of fields) from the provided input. +@(require_results) read_from_string :: proc(input: string, record_allocator := context.allocator, buffer_allocator := context.allocator) -> (record: []string, n: int, err: Error) { ir: strings.Reader strings.reader_init(&ir, input) @@ -178,6 +185,7 @@ read_from_string :: proc(input: string, record_allocator := context.allocator, b // read_all reads all the remaining records from the provided input. +@(require_results) read_all_from_string :: proc(input: string, records_allocator := context.allocator, buffer_allocator := context.allocator) -> ([][]string, Error) { ir: strings.Reader strings.reader_init(&ir, input) @@ -189,7 +197,7 @@ read_all_from_string :: proc(input: string, records_allocator := context.allocat return read_all(&r, records_allocator) } -@private +@(private, require_results) is_valid_delim :: proc(r: rune) -> bool { switch r { case 0, '"', '\r', '\n', utf8.RUNE_ERROR: @@ -198,8 +206,9 @@ is_valid_delim :: proc(r: rune) -> bool { return utf8.valid_rune(r) } -@private +@(private, require_results) _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) { + @(require_results) read_line :: proc(r: ^Reader) -> ([]byte, io.Error) { if !r.multiline_fields { line, err := bufio.reader_read_slice(&r.r, '\n') @@ -269,6 +278,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all unreachable() } + @(require_results) length_newline :: proc(b: []byte) -> int { if len(b) > 0 && b[len(b)-1] == '\n' { return 1 @@ -276,6 +286,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all return 0 } + @(require_results) next_rune :: proc(b: []byte) -> rune { r, _ := utf8.decode_rune(b) return r From ec7e75a57fc6376242089c7747203ac630c9bc1a Mon Sep 17 00:00:00 2001 From: "Maurizio M. Gavioli" Date: Fri, 19 Apr 2024 08:23:28 +0200 Subject: [PATCH 090/171] Fix #3451 - `core:text/i18n` default `number` value in `get_*_section` proc. In the to procs `get_single_section()` and `get_by_section()` the `number` parameter should have a default of `1` rather than `0`. See the issue for more details. --- core/text/i18n/i18n.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin index 151f9e129..64593c4e8 100644 --- a/core/text/i18n/i18n.odin +++ b/core/text/i18n/i18n.odin @@ -90,7 +90,7 @@ DEFAULT_PARSE_OPTIONS :: Parse_Options{ - get(key, number), which returns the appropriate plural from the active catalog, or - get(key, number, catalog) to grab text from a specific one. */ -get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) { +get_single_section :: proc(key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) { /* A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. */ @@ -108,7 +108,7 @@ get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACT - get(section, key, number), which returns the appropriate plural from the active catalog, or - get(section, key, number, catalog) to grab text from a specific one. */ -get_by_section :: proc(section, key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) { +get_by_section :: proc(section, key: string, number := 1, catalog: ^Translation = ACTIVE) -> (value: string) { /* A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. */ From c44f618b7dec82cf80609fd613c93ef91cf6a7ae Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Fri, 19 Apr 2024 15:17:21 +0300 Subject: [PATCH 091/171] fix(net): add `NOSIGNAL` to `send` options This is a better default than not having it, since it turns errors that would be signals into error values instead. We could take these as options but given that we currently don't I think this at the very least improves on the status quo. --- core/net/errors_linux.odin | 1 + core/net/socket_linux.odin | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 2370dd0d8..5e2c52aea 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -136,6 +136,7 @@ TCP_Send_Error :: enum c.int { Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. + Broken_Pipe = c.int(linux.Errno.EPIPE), // The peer has disconnected when we are trying to send to it } // TODO diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index ba48959fb..9c4342592 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -258,7 +258,7 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { for total_written < len(buf) { limit := min(int(max(i32)), len(buf) - total_written) remaining := buf[total_written:][:limit] - res, errno := linux.send(linux.Fd(tcp_sock), remaining, {}) + res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) if errno != .NONE { return total_written, TCP_Send_Error(errno) } From 7b95562827290258c49e27c7ee8d7be53b7239fe Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Fri, 19 Apr 2024 15:29:28 +0300 Subject: [PATCH 092/171] feat(net): turn `EPIPE` into `Connection_Closed` --- core/net/errors_linux.odin | 1 - core/net/socket_linux.odin | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 5e2c52aea..2370dd0d8 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -136,7 +136,6 @@ TCP_Send_Error :: enum c.int { Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. - Broken_Pipe = c.int(linux.Errno.EPIPE), // The peer has disconnected when we are trying to send to it } // TODO diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 9c4342592..d9b29fb3a 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -259,7 +259,9 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { limit := min(int(max(i32)), len(buf) - total_written) remaining := buf[total_written:][:limit] res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) - if errno != .NONE { + if errno == .EPIPE { + return total_written, TCP_Send_Error.Connection_Closed + } else if errno != .NONE { return total_written, TCP_Send_Error(errno) } total_written += int(res) From 059175de3bfab925085808989aadd909932b5c1d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Apr 2024 13:32:55 +0100 Subject: [PATCH 093/171] Do not print column of a runtime.Source_Code_Location if the `column == 0` --- base/runtime/print.odin | 12 ++++++++---- core/fmt/fmt.odin | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 4e2ffaf80..ed5893e15 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -228,14 +228,18 @@ print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_L when ODIN_ERROR_POS_STYLE == .Default { print_byte('(') print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) + if loc.column != 0 { + print_byte(':') + print_u64(u64(loc.column)) + } print_byte(')') } else when ODIN_ERROR_POS_STYLE == .Unix { print_byte(':') print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) + if loc.column != 0 { + print_byte(':') + print_u64(u64(loc.column)) + } print_byte(':') } else { #panic("unhandled ODIN_ERROR_POS_STYLE") diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index d3b9d7d69..547d59ce0 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2156,14 +2156,18 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named) when ODIN_ERROR_POS_STYLE == .Default { io.write_byte(fi.writer, '(', &fi.n) io.write_int(fi.writer, int(a.line), 10, &fi.n) - io.write_byte(fi.writer, ':', &fi.n) - io.write_int(fi.writer, int(a.column), 10, &fi.n) + if a.column != 0 { + io.write_byte(fi.writer, ':', &fi.n) + io.write_int(fi.writer, int(a.column), 10, &fi.n) + } io.write_byte(fi.writer, ')', &fi.n) } else when ODIN_ERROR_POS_STYLE == .Unix { io.write_byte(fi.writer, ':', &fi.n) io.write_int(fi.writer, int(a.line), 10, &fi.n) - io.write_byte(fi.writer, ':', &fi.n) - io.write_int(fi.writer, int(a.column), 10, &fi.n) + if a.column != 0 { + io.write_byte(fi.writer, ':', &fi.n) + io.write_int(fi.writer, int(a.column), 10, &fi.n) + } io.write_byte(fi.writer, ':', &fi.n) } else { #panic("Unhandled ODIN_ERROR_POS_STYLE") From efc84cd390e6773ee71e35bc851ce4f55f39c34a Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Fri, 19 Apr 2024 15:37:20 +0300 Subject: [PATCH 094/171] docs(net): add comment about `EPIPE` -> `Connection_Closed` --- core/net/socket_linux.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index d9b29fb3a..ee3a41927 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -260,7 +260,9 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { remaining := buf[total_written:][:limit] res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) if errno == .EPIPE { - return total_written, TCP_Send_Error.Connection_Closed + // If the peer is disconnected when we are trying to send we will get an `EPIPE` error, + // so we turn that into a clearer error + return total_written, .Connection_Closed } else if errno != .NONE { return total_written, TCP_Send_Error(errno) } From 68f663ea8585f0de6ca7d34ecf93031603f22cb6 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Fri, 19 Apr 2024 15:39:04 +0300 Subject: [PATCH 095/171] fix(net): fix return type for `send_tcp` Was `.Connection_Closed` but this is only inferrable if our return type is not a sub-union of another. --- core/net/socket_linux.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index ee3a41927..a4d75b92b 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -262,7 +262,7 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { if errno == .EPIPE { // If the peer is disconnected when we are trying to send we will get an `EPIPE` error, // so we turn that into a clearer error - return total_written, .Connection_Closed + return total_written, TCP_Send_Error.Connection_Closed } else if errno != .NONE { return total_written, TCP_Send_Error(errno) } From 0a16f7a6f1e3e40dfed7cb93725d325787bc948b Mon Sep 17 00:00:00 2001 From: Thomas la Cour Date: Tue, 26 Mar 2024 12:22:18 +0100 Subject: [PATCH 096/171] normalize_path --- src/build_settings.cpp | 6 ++---- src/string.cpp | 34 ++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b806adcd6..03a95a19b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -840,13 +840,11 @@ gb_internal String odin_root_dir(void) { char const *found = gb_get_env("ODIN_ROOT", a); if (found) { String path = path_to_full_path(a, make_string_c(found)); - if (path[path.len-1] != '/' && path[path.len-1] != '\\') { #if defined(GB_SYSTEM_WINDOWS) - path = concatenate_strings(a, path, WIN32_SEPARATOR_STRING); + path = normalize_path(a, path, WIN32_SEPARATOR_STRING); #else - path = concatenate_strings(a, path, NIX_SEPARATOR_STRING); + path = normalize_path(a, path, NIX_SEPARATOR_STRING); #endif - } global_module_path = path; global_module_path_set = true; diff --git a/src/string.cpp b/src/string.cpp index 3747f4564..b92dd589e 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -237,11 +237,16 @@ gb_internal String string_split_iterator(String_Iterator *it, const char sep) { return substring(it->str, start, end); } +gb_internal gb_inline bool is_separator(u8 const &ch) { + return (ch == '/' || ch == '\\'); +} + + gb_internal gb_inline isize string_extension_position(String const &str) { isize dot_pos = -1; isize i = str.len; while (i --> 0) { - if (str[i] == '\\' || str[i] == '/') + if (is_separator(str[i])) break; if (str[i] == '.') { dot_pos = i; @@ -332,8 +337,7 @@ gb_internal String filename_from_path(String s) { if (i > 0) { isize j = 0; for (j = s.len-1; j >= 0; j--) { - if (s[j] == '/' || - s[j] == '\\') { + if (is_separator(s[j])) { break; } } @@ -346,8 +350,7 @@ gb_internal String filename_from_path(String s) { gb_internal String filename_without_directory(String s) { isize j = 0; for (j = s.len-1; j >= 0; j--) { - if (s[j] == '/' || - s[j] == '\\') { + if (is_separator(s[j])) { break; } } @@ -410,7 +413,26 @@ gb_internal String copy_string(gbAllocator a, String const &s) { return make_string(data, s.len); } - +gb_internal String normalize_path(gbAllocator a, String const &path, String const &sep) { + String s; + if (sep.len < 1) { + return path; + } + if (path.len < 1) { + s = STR_LIT(""); + } else if (is_separator(path[path.len-1])) { + s = copy_string(a, path); + } else { + s = concatenate_strings(a, path, sep); + } + isize i; + for (i = 0; i < s.len; i++) { + if (is_separator(s.text[i])) { + s.text[i] = sep.text[0]; + } + } + return s; +} #if defined(GB_SYSTEM_WINDOWS) From ebb1a07dd081bb9210e093ebac89f692cb8200d6 Mon Sep 17 00:00:00 2001 From: Thomas la Cour Date: Tue, 26 Mar 2024 12:22:41 +0100 Subject: [PATCH 097/171] spelling --- src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 53103ce3a..4e8f64e05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -337,12 +337,12 @@ struct BuildFlag { String name; BuildFlagParamKind param_kind; u32 command_support; - bool allow_mulitple; + bool allow_multiple; }; -gb_internal void add_flag(Array *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_mulitple=false) { - BuildFlag flag = {kind, name, param_kind, command_support, allow_mulitple}; +gb_internal void add_flag(Array *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_multiple=false) { + BuildFlag flag = {kind, name, param_kind, command_support, allow_multiple}; array_add(build_flags, flag); } @@ -1358,7 +1358,7 @@ gb_internal bool parse_build_flags(Array args) { } } - if (!bf.allow_mulitple) { + if (!bf.allow_multiple) { set_flags[bf.kind] = ok; } } From 902e877467bb4bb1705652e36e5f43a2776adeb7 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 21 Apr 2024 21:21:46 +0900 Subject: [PATCH 098/171] repo: Add more test binaries to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 228f006a3..f6c3927a2 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ tests/documentation/all.odin-doc tests/internal/test_map tests/internal/test_pow tests/internal/test_rtti +tests/core/test_base64 +tests/core/test_cbor tests/core/test_core_compress tests/core/test_core_container tests/core/test_core_filepath @@ -40,8 +42,10 @@ tests/core/test_core_net tests/core/test_core_os_exit tests/core/test_core_reflect tests/core/test_core_strings +tests/core/test_core_time tests/core/test_crypto tests/core/test_hash +tests/core/test_hex tests/core/test_hxa tests/core/test_json tests/core/test_linalg_glsl_math From 2a70faca146b752a5009b7a7bb68c488461e40bb Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 21 Apr 2024 22:37:04 +0200 Subject: [PATCH 099/171] Add printfln and eprintfln functions to fmt_js.odin --- core/fmt/fmt_js.odin | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index c70b7c1c0..a0a890a9a 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -37,6 +37,8 @@ print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } // printf formats according to the specififed format string and writes to stdout printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } +// printfln formats according to the specified format string and writes to stdout, followed by a newline. +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to stderr eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } @@ -44,3 +46,5 @@ eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint( eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } // eprintf formats according to the specififed format string and writes to stderr eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } +// eprintfln formats according to the specified format string and writes to stderr, followed by a newline. +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } From 90369b669b5d48f1912f5a3667fcea57a0c4cef2 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 22 Apr 2024 19:05:50 +0200 Subject: [PATCH 100/171] fix direct proc args debug info --- src/llvm_backend_debug.cpp | 12 ++---------- src/llvm_backend_proc.cpp | 13 ++----------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 853941496..2654a1d28 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1027,7 +1027,7 @@ gb_internal void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, T LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block); } -gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block, lbArgKind arg_kind) { +gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block) { if (p->debug_info == nullptr) { return; } @@ -1088,15 +1088,7 @@ gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, T // NOTE(bill, 2022-02-01): For parameter values, you must insert them at the end of the decl block // The reason is that if the parameter is at index 0 and a pointer, there is not such things as an // instruction "before" it. - switch (arg_kind) { - case lbArg_Direct: - LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); - break; - case lbArg_Indirect: - LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); - break; - } - + LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index bb4aed3f1..f73698d34 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -597,16 +597,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lbValue ptr = lb_address_from_load_or_generate_local(p, param); GB_ASSERT(LLVMIsAAllocaInst(ptr.value)); lb_add_entity(p->module, e, ptr); - - lbBlock *block = p->decl_block; - if (original_value != value) { - block = p->curr_block; - } - LLVMValueRef debug_storage_value = value; - if (original_value != value && LLVMIsALoadInst(value)) { - debug_storage_value = LLVMGetOperand(value, 0); - } - lb_add_debug_param_variable(p, debug_storage_value, e->type, e->token, param_index+1, block, arg_type->kind); + lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->curr_block); } } else if (arg_type->kind == lbArg_Indirect) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { @@ -614,7 +605,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { ptr.value = LLVMGetParam(p->value, param_offset+param_index); ptr.type = alloc_type_pointer(e->type); lb_add_entity(p->module, e, ptr); - lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block, arg_type->kind); + lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block); } } } From c6a446fe87b818ef7d34131babe074080cdc4575 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Apr 2024 18:41:48 +0100 Subject: [PATCH 101/171] Add check for `build.` and `run.` typos --- src/main.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 53103ce3a..8a1f4852e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -198,7 +198,12 @@ gb_internal void print_usage_line(i32 indent, char const *fmt, ...) { gb_printf("\n"); } -gb_internal void usage(String argv0) { +gb_internal void usage(String argv0, String argv1 = {}) { + if (argv1 == "run.") { + print_usage_line(0, "Did you mean 'odin run .'?"); + } else if (argv1 == "build.") { + print_usage_line(0, "Did you mean 'odin build .'?"); + } print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(argv0)); print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s command [arguments]", LIT(argv0)); @@ -2586,7 +2591,11 @@ int main(int arg_count, char const **arg_ptr) { gb_printf("%.*s", LIT(odin_root_dir())); return 0; } else { - usage(args[0]); + String argv1 = {}; + if (args.count > 1) { + argv1 = args[1]; + } + usage(args[0], argv1); return 1; } From d1a1e8f646c5b959be638aa955856f686b11a4f3 Mon Sep 17 00:00:00 2001 From: Laytan Date: Mon, 22 Apr 2024 20:33:19 +0200 Subject: [PATCH 102/171] fix linking with clang-18 Because we currently just use the clang from the user's path linking suddenly breaks when the user updates their system clang to 18 with an error about an unknown option -arch. I had already fixed it for my LLVM 18 PR but it seems like a good idea to get this in already to avoid that breakage (had a few people come to the Discord with it and an issue). This fixes #3461 --- src/build_settings.cpp | 102 ++++++++++++----------------------------- 1 file changed, 29 insertions(+), 73 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 03a95a19b..3bd362996 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1465,26 +1465,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } bc->metrics = *metrics; - if (metrics->os == TargetOs_darwin) { - if (!bc->minimum_os_version_string_given) { - bc->minimum_os_version_string = str_lit("11.0.0"); - } - - switch (subtarget) { - case Subtarget_Default: - bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); - break; - case Subtarget_iOS: - if (metrics->arch == TargetArch_arm64) { - bc->metrics.target_triplet = str_lit("arm64-apple-ios"); - } else if (metrics->arch == TargetArch_amd64) { - bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); - } else { - GB_PANIC("Unknown architecture for darwin"); - } - break; - } - } bc->ODIN_OS = target_os_names[metrics->os]; bc->ODIN_ARCH = target_arch_names[metrics->arch]; @@ -1520,62 +1500,26 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[Windows_Subsystem_CONSOLE]; } - // NOTE(zangent): The linker flags to set the build architecture are different - // across OSs. It doesn't make sense to allocate extra data on the heap - // here, so I just #defined the linker flags to keep things concise. - if (bc->metrics.arch == TargetArch_amd64) { - switch (bc->metrics.os) { - case TargetOs_windows: - bc->link_flags = str_lit("/machine:x64 "); + if (metrics->os == TargetOs_darwin && subtarget == Subtarget_iOS) { + switch (metrics->arch) { + case TargetArch_arm64: + bc->metrics.target_triplet = str_lit("arm64-apple-ios"); break; - case TargetOs_darwin: - bc->link_flags = str_lit("-arch x86_64 "); - break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_freebsd: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_openbsd: - bc->link_flags = str_lit("-arch x86-64 "); - break; - case TargetOs_haiku: - bc->link_flags = str_lit("-arch x86-64 "); - break; - } - } else if (bc->metrics.arch == TargetArch_i386) { - switch (bc->metrics.os) { - case TargetOs_windows: - bc->link_flags = str_lit("/machine:x86 "); - break; - case TargetOs_darwin: - gb_printf_err("Unsupported architecture\n"); - gb_exit(1); - break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch x86 "); - break; - case TargetOs_freebsd: - bc->link_flags = str_lit("-arch x86 "); - break; - } - } else if (bc->metrics.arch == TargetArch_arm32) { - switch (bc->metrics.os) { - case TargetOs_linux: - bc->link_flags = str_lit("-arch arm "); + case TargetArch_amd64: + bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); break; default: - gb_printf_err("Compiler Error: Unsupported architecture\n"); - gb_exit(1); + GB_PANIC("Unknown architecture for darwin"); } - } else if (bc->metrics.arch == TargetArch_arm64) { - switch (bc->metrics.os) { - case TargetOs_darwin: - bc->link_flags = str_lit("-arch arm64 "); + } + + if (bc->metrics.os == TargetOs_windows) { + switch (bc->metrics.arch) { + case TargetArch_amd64: + bc->link_flags = str_lit("/machine:x64 "); break; - case TargetOs_linux: - bc->link_flags = str_lit("-arch aarch64 "); + case TargetArch_i386: + bc->link_flags = str_lit("/machine:x86 "); break; } } else if (is_arch_wasm()) { @@ -1595,8 +1539,20 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // Disallow on wasm bc->use_separate_modules = false; } else { - gb_printf_err("Compiler Error: Unsupported architecture\n"); - gb_exit(1); + bc->link_flags = concatenate3_strings(permanent_allocator(), + str_lit("-target "), bc->metrics.target_triplet, str_lit(" ")); + } + + // NOTE: needs to be done after adding the -target flag to the linker flags so the linker + // does not annoy the user with version warnings. + if (metrics->os == TargetOs_darwin) { + if (!bc->minimum_os_version_string_given) { + bc->minimum_os_version_string = str_lit("11.0.0"); + } + + if (subtarget == Subtarget_Default) { + bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string); + } } if (bc->ODIN_DEBUG && !bc->custom_optimization_level) { From a6eb64df6cd136639d1234e5a157ad280a1a32a8 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 21 Apr 2024 21:06:21 +0900 Subject: [PATCH 103/171] core/crypto: Add a `HAS_RAND_BYTES` constant --- core/crypto/crypto.odin | 9 +++------ core/crypto/rand_bsd.odin | 7 +++---- core/crypto/rand_darwin.odin | 7 +++---- core/crypto/rand_generic.odin | 7 +++---- core/crypto/rand_js.odin | 8 ++++---- core/crypto/rand_linux.odin | 8 ++++---- core/crypto/rand_windows.odin | 7 +++---- tests/core/crypto/test_core_crypto.odin | 2 +- 8 files changed, 24 insertions(+), 31 deletions(-) diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index 05f25111a..f0874cc6d 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -49,15 +49,12 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i // the system entropy source. This routine will block if the system entropy // source is not ready yet. All system entropy source failures are treated // as catastrophic, resulting in a panic. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. rand_bytes :: proc (dst: []byte) { // zero-fill the buffer first mem.zero_explicit(raw_data(dst), len(dst)) _rand_bytes(dst) } - -// has_rand_bytes returns true iff the target has support for accessing the -// system entropty source. -has_rand_bytes :: proc () -> bool { - return _has_rand_bytes() -} diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index 7a0c42683..a31e4f2b2 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -3,14 +3,13 @@ package crypto foreign import libc "system:c" +HAS_RAND_BYTES :: true + foreign libc { arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- } +@(private) _rand_bytes :: proc(dst: []byte) { arc4random_buf(raw_data(dst), len(dst)) } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin index c1a3d1dbc..5355f31c5 100644 --- a/core/crypto/rand_darwin.odin +++ b/core/crypto/rand_darwin.odin @@ -5,6 +5,9 @@ import "core:fmt" import CF "core:sys/darwin/CoreFoundation" import Sec "core:sys/darwin/Security" +HAS_RAND_BYTES :: true + +@(private) _rand_bytes :: proc(dst: []byte) { err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst)) if err != .Success { @@ -12,7 +15,3 @@ _rand_bytes :: proc(dst: []byte) { panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)) } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index cba49f700..4ea61ec91 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -6,10 +6,9 @@ //+build !js package crypto +HAS_RAND_BYTES :: false + +@(private) _rand_bytes :: proc(dst: []byte) { unimplemented("crypto: rand_bytes not supported on this OS") } - -_has_rand_bytes :: proc() -> bool { - return false -} diff --git a/core/crypto/rand_js.odin b/core/crypto/rand_js.odin index 90f60b99b..72093810e 100644 --- a/core/crypto/rand_js.odin +++ b/core/crypto/rand_js.odin @@ -6,8 +6,12 @@ foreign odin_env { env_rand_bytes :: proc "contextless" (buf: []byte) --- } +HAS_RAND_BYTES :: true + +@(private) _MAX_PER_CALL_BYTES :: 65536 // 64kiB +@(private) _rand_bytes :: proc(dst: []byte) { dst := dst @@ -18,7 +22,3 @@ _rand_bytes :: proc(dst: []byte) { dst = dst[to_read:] } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin index a9dc37415..43b3b3075 100644 --- a/core/crypto/rand_linux.odin +++ b/core/crypto/rand_linux.odin @@ -4,8 +4,12 @@ import "core:fmt" import "core:sys/linux" +HAS_RAND_BYTES :: true + +@(private) _MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1 +@(private) _rand_bytes :: proc (dst: []byte) { dst := dst l := len(dst) @@ -34,7 +38,3 @@ _rand_bytes :: proc (dst: []byte) { dst = dst[n_read:] } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index 5cafe7fb5..a92d376cb 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -4,6 +4,9 @@ import win32 "core:sys/windows" import "core:os" import "core:fmt" +HAS_RAND_BYTES :: true + +@(private) _rand_bytes :: proc(dst: []byte) { ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) if ret != os.ERROR_NONE { @@ -21,7 +24,3 @@ _rand_bytes :: proc(dst: []byte) { } } } - -_has_rand_bytes :: proc() -> bool { - return true -} diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 72d8e7c78..95db3f292 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -277,7 +277,7 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { test_rand_bytes :: proc(t: ^testing.T) { tc.log(t, "Testing rand_bytes") - if !crypto.has_rand_bytes() { + if !crypto.HAS_RAND_BYTES { tc.log(t, "rand_bytes not supported - skipping") return } From e2fa9be7e2c02ad950e4f3205f5e67c9ebd3a70c Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 21 Apr 2024 21:16:50 +0900 Subject: [PATCH 104/171] core/math/rand: Use `core:crypto` for the system RNG This removes some code duplication and expands support for the system RNG to all targets that `core:crypto` supports. --- core/math/rand/rand.odin | 39 ++++++++++++++++-------------- core/math/rand/system_darwin.odin | 22 ----------------- core/math/rand/system_js.odin | 14 ----------- core/math/rand/system_linux.odin | 29 ---------------------- core/math/rand/system_windows.odin | 13 ---------- 5 files changed, 21 insertions(+), 96 deletions(-) delete mode 100644 core/math/rand/system_darwin.odin delete mode 100644 core/math/rand/system_js.odin delete mode 100644 core/math/rand/system_linux.odin delete mode 100644 core/math/rand/system_windows.odin diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index d6a20bd1e..664d6abc9 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -5,6 +5,7 @@ Package core:math/rand implements various random number generators package rand import "base:intrinsics" +import "core:crypto" import "core:math" import "core:mem" @@ -104,27 +105,30 @@ init :: proc(r: ^Rand, seed: u64) { } /* -Initialises a random number generator to use the system random number generator. -The system random number generator is platform specific. -On `linux` refer to the `getrandom` syscall. -On `darwin` refer to `getentropy`. -On `windows` refer to `BCryptGenRandom`. - -All other platforms are not supported +Initialises a random number generator to use the system random number generator. +The system random number generator is platform specific, and not supported +on all targets. Inputs: - r: The random number generator to use the system random number generator -WARNING: Panics if the system is not either `windows`, `darwin` or `linux` +WARNING: Panics if the system random number generator is not supported. +Support can be determined via the `core:crypto.HAS_RAND_BYTES` constant. Example: + import "core:crypto" import "core:math/rand" import "core:fmt" init_as_system_example :: proc() { my_rand: rand.Rand - rand.init_as_system(&my_rand) - fmt.println(rand.uint64(&my_rand)) + switch crypto.HAS_RAND_BYTES { + case true: + rand.init_as_system(&my_rand) + fmt.println(rand.uint64(&my_rand)) + case false: + fmt.println("system random not supported!") + } } Possible Output: @@ -133,7 +137,7 @@ Possible Output: */ init_as_system :: proc(r: ^Rand) { - if !#defined(_system_random) { + if !crypto.HAS_RAND_BYTES { panic(#procedure + " is not supported on this platform yet") } r.state = 0 @@ -144,15 +148,14 @@ init_as_system :: proc(r: ^Rand) { @(private) _random_u64 :: proc(r: ^Rand) -> u64 { r := r - if r == nil { + switch { + case r == nil: r = &global_rand + case r.is_system: + value: u64 + crypto.rand_bytes((cast([^]u8)&value)[:size_of(u64)]) + return value } - when #defined(_system_random) { - if r.is_system { - return _system_random() - } - } - old_state := r.state r.state = old_state * 6364136223846793005 + (r.inc|1) diff --git a/core/math/rand/system_darwin.odin b/core/math/rand/system_darwin.odin deleted file mode 100644 index 756f7fcae..000000000 --- a/core/math/rand/system_darwin.odin +++ /dev/null @@ -1,22 +0,0 @@ -package rand - -import "core:sys/darwin" - -@(require_results) -_system_random :: proc() -> u64 { - for { - value: u64 - ret := darwin.syscall_getentropy(([^]u8)(&value), size_of(value)) - if ret < 0 { - switch ret { - case -4: // EINTR - continue - case -78: // ENOSYS - panic("getentropy not available in kernel") - case: - panic("getentropy failed") - } - } - return value - } -} \ No newline at end of file diff --git a/core/math/rand/system_js.odin b/core/math/rand/system_js.odin deleted file mode 100644 index b9b71c4a6..000000000 --- a/core/math/rand/system_js.odin +++ /dev/null @@ -1,14 +0,0 @@ -package rand - -foreign import "odin_env" -foreign odin_env { - @(link_name = "rand_bytes") - env_rand_bytes :: proc "contextless" (buf: []byte) --- -} - -@(require_results) -_system_random :: proc() -> u64 { - buf: [8]u8 - env_rand_bytes(buf[:]) - return transmute(u64)buf -} diff --git a/core/math/rand/system_linux.odin b/core/math/rand/system_linux.odin deleted file mode 100644 index 42c9f86fa..000000000 --- a/core/math/rand/system_linux.odin +++ /dev/null @@ -1,29 +0,0 @@ -package rand - -import "core:sys/linux" - -@(require_results) -_system_random :: proc() -> u64 { - for { - value: u64 - value_buf := (cast([^]u8)&value)[:size_of(u64)] - _, errno := linux.getrandom(value_buf, {}) - #partial switch errno { - case .NONE: - // Do nothing - case .EINTR: - // Call interupted by a signal handler, just retry the request. - continue - case .ENOSYS: - // The kernel is apparently prehistoric (< 3.17 circa 2014) - // and does not support getrandom. - panic("getrandom not available in kernel") - case: - // All other failures are things that should NEVER happen - // unless the kernel interface changes (ie: the Linux - // developers break userland). - panic("getrandom failed") - } - return value - } -} \ No newline at end of file diff --git a/core/math/rand/system_windows.odin b/core/math/rand/system_windows.odin deleted file mode 100644 index c6d68816d..000000000 --- a/core/math/rand/system_windows.odin +++ /dev/null @@ -1,13 +0,0 @@ -package rand - -import win32 "core:sys/windows" - -@(require_results) -_system_random :: proc() -> u64 { - value: u64 - status := win32.BCryptGenRandom(nil, ([^]u8)(&value), size_of(value), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG) - if status < 0 { - panic("BCryptGenRandom failed") - } - return value -} \ No newline at end of file From ec5a84a5379236a2413b8f3115509629879f5b53 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 13:10:58 +0100 Subject: [PATCH 105/171] Improve code generation for loading `bit_field` fields --- src/check_type.cpp | 15 ++++--- src/llvm_backend_general.cpp | 80 +++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index a6dbb8dfc..77ac91c38 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -955,13 +955,18 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, GB_ASSERT(is_type_bit_field(bit_field_type)); Type *backing_type = check_type(ctx, bf->backing_type); - if (backing_type == nullptr || !is_valid_bit_field_backing_type(backing_type)) { - error(node, "Backing type for a bit_field must be an integer or an array of an integer"); + + bit_field_type->BitField.backing_type = backing_type ? backing_type : t_u8; + bit_field_type->BitField.scope = ctx->scope; + + if (backing_type == nullptr) { + error(bf->backing_type, "Backing type for a bit_field must be an integer or an array of an integer"); + return; + } + if (!is_valid_bit_field_backing_type(backing_type)) { + error(bf->backing_type, "Backing type for a bit_field must be an integer or an array of an integer"); return; } - - bit_field_type->BitField.backing_type = backing_type; - bit_field_type->BitField.scope = ctx->scope; auto fields = array_make(permanent_allocator(), 0, bf->fields.count); auto bit_sizes = array_make (permanent_allocator(), 0, bf->fields.count); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index da69f94d7..b8fbd231e 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -774,13 +774,23 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.kind == lbAddr_BitField) { lbValue dst = addr.addr; + lbValue src = lb_address_from_load_or_generate_local(p, value); - auto args = array_make(temporary_allocator(), 4); - args[0] = dst; - args[1] = lb_address_from_load_or_generate_local(p, value); - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__write_bits", args); + if ((addr.bitfield.bit_offset & 7) == 0 && + (addr.bitfield.bit_size & 7) == 0) { + lbValue byte_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset/8); + lbValue byte_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size/8); + lbValue dst_offset = lb_emit_conv(p, dst, t_u8_ptr); + dst_offset = lb_emit_ptr_offset(p, dst_offset, byte_offset); + lb_mem_copy_non_overlapping(p, dst_offset, src, byte_size); + } else { + auto args = array_make(temporary_allocator(), 4); + args[0] = dst; + args[1] = src; + args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lb_emit_runtime_call(p, "__write_bits", args); + } return; } else if (addr.kind == lbAddr_RelativePointer) { Type *rel_ptr = base_type(lb_addr_type(addr)); @@ -1088,23 +1098,63 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { GB_ASSERT(addr.addr.value != nullptr); if (addr.kind == lbAddr_BitField) { - lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, true); + Type *ct = core_type(addr.bitfield.type); + bool do_mask = false; + if (is_type_unsigned(ct) || is_type_boolean(ct)) { + // Mask + if (addr.bitfield.bit_size != 8*type_size_of(ct)) { + do_mask = true; + } + } + + i64 total_bitfield_bit_size = 8*type_size_of(lb_addr_type(addr)); + i64 dst_byte_size = type_size_of(addr.bitfield.type); + lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, false); lbValue src = addr.addr; - auto args = array_make(temporary_allocator(), 4); - args[0] = dst.addr; - args[1] = src; - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__read_bits", args); + lbValue bit_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + lbValue bit_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lbValue byte_offset = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_offset+7)/8); + lbValue byte_size = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_size+7)/8); + + GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); + + if ((addr.bitfield.bit_offset & 7) == 0) { + lbValue copy_size = byte_size; + lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); + src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); + if (addr.bitfield.bit_offset + dst_byte_size <= total_bitfield_bit_size) { + do_mask = true; + copy_size = lb_const_int(p->module, t_uintptr, dst_byte_size); + } + lb_mem_copy_non_overlapping(p, dst.addr, src_offset, copy_size, false); + } else { + auto args = array_make(temporary_allocator(), 4); + args[0] = dst.addr; + args[1] = src; + args[2] = bit_offset; + args[3] = bit_size; + lb_emit_runtime_call(p, "__read_bits", args); + } lbValue r = lb_addr_load(p, dst); + Type *t = addr.bitfield.type; - if (!is_type_unsigned(core_type(addr.bitfield.type))) { + if (do_mask) { + GB_ASSERT(addr.bitfield.bit_size < 8*type_size_of(ct)); + + LLVMTypeRef lt = lb_type(p->module, t); + LLVMValueRef mask = LLVMConstInt(lt, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(lt, addr.bitfield.bit_size, false)); + mask = LLVMConstSub(mask, LLVMConstInt(lt, 1, false)); + lbValue m = {mask, t}; + r = lb_emit_arith(p, Token_And, r, m, t); + } + + if (!is_type_unsigned(ct) && !is_type_boolean(ct)) { // Sign extension // m := 1<<(bit_size-1) // r = (r XOR m) - m - Type *t = addr.bitfield.type; lbValue m = lb_const_int(p->module, t, 1ull<<(addr.bitfield.bit_size-1)); r = lb_emit_arith(p, Token_Xor, r, m, t); r = lb_emit_arith(p, Token_Sub, r, m, t); From c330e5b5c1b512e1b0ca7181941057e5f2e085e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 14:46:34 +0100 Subject: [PATCH 106/171] Improve codegen for `bit_field` compound literals with an integer backing --- src/llvm_backend.hpp | 1 - src/llvm_backend_expr.cpp | 113 ++++++++++++++++++++++++++++++----- src/llvm_backend_general.cpp | 3 +- src/llvm_backend_utility.cpp | 9 +-- 4 files changed, 102 insertions(+), 24 deletions(-) diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index c4bf2691d..7dc5f6b63 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -122,7 +122,6 @@ struct lbAddr { } swizzle_large; struct { Type *type; - i64 index; i64 bit_offset; i64 bit_size; } bitfield; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index edd5daeca..4209ba1ea 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4296,7 +4296,19 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { switch (bt->kind) { default: GB_PANIC("Unknown CompoundLit type: %s", type_to_string(type)); break; - case Type_BitField: + case Type_BitField: { + TEMPORARY_ALLOCATOR_GUARD(); + + // Type *backing_type = core_type(bt->BitField.backing_type); + + struct FieldData { + Type *field_type; + u64 bit_offset; + u64 bit_size; + }; + auto values = array_make(temporary_allocator(), 0, cl->elems.count); + auto fields = array_make(temporary_allocator(), 0, cl->elems.count); + for (Ast *elem : cl->elems) { ast_node(fv, FieldValue, elem); String name = fv->field->Ident.token.string; @@ -4307,26 +4319,97 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { GB_ASSERT(sel.entity != nullptr); i64 index = sel.index[0]; - i64 bit_offset = 0; - i64 bit_size = -1; - for_array(i, bt->BitField.fields) { - Entity *f = bt->BitField.fields[i]; - if (f == sel.entity) { - bit_offset = bt->BitField.bit_offsets[i]; - bit_size = bt->BitField.bit_sizes[i]; - break; - } - } + Entity *f = bt->BitField.fields[index]; + GB_ASSERT(f == sel.entity); + i64 bit_offset = bt->BitField.bit_offsets[index]; + i64 bit_size = bt->BitField.bit_sizes[index]; GB_ASSERT(bit_size > 0); Type *field_type = sel.entity->type; lbValue field_expr = lb_build_expr(p, fv->value); field_expr = lb_emit_conv(p, field_expr, field_type); - - lbAddr field_addr = lb_addr_bit_field(v.addr, field_type, index, bit_offset, bit_size); - lb_addr_store(p, field_addr, field_expr); + array_add(&values, field_expr); + array_add(&fields, FieldData{field_type, cast(u64)bit_offset, cast(u64)bit_size}); } + + // NOTE(bill): inline insertion sort should be good enough, right? + for (isize i = 1; i < values.count; i++) { + for (isize j = i; + j > 0 && fields[i].bit_offset < fields[j].bit_offset; + j--) { + auto vtmp = values[j]; + values[j] = values[j-1]; + values[j-1] = vtmp; + + auto ftmp = fields[j]; + fields[j] = fields[j-1]; + fields[j-1] = ftmp; + } + } + + if (fields.count == bt->BitField.fields.count) { + Type *backing_type = core_type(bt->BitField.backing_type); + GB_ASSERT(is_type_integer(backing_type) || + (is_type_array(backing_type) && is_type_integer(backing_type->Array.elem))); + + // NOTE(bill): all fields are present + // this means no masking is necessary since on write, the bits will be overridden + + lbValue dst_byte_ptr = lb_emit_conv(p, v.addr, t_u8_ptr); + u64 total_bit_size = cast(u64)(8*type_size_of(bt)); + + if (is_type_integer(backing_type)) { + LLVMTypeRef lbt = lb_type(p->module, backing_type); + + LLVMValueRef res = LLVMConstInt(lbt, 0, false); + + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef mask = LLVMConstInt(lbt, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(lbt, f.bit_size, false)); + mask = LLVMConstSub(mask, LLVMConstInt(lbt, 1, false)); + + LLVMValueRef elem = values[i].value; + elem = LLVMBuildZExt(p->builder, elem, lbt, ""); + elem = LLVMBuildAnd(p->builder, elem, mask, ""); + + elem = LLVMBuildShl(p->builder, elem, LLVMConstInt(lbt, f.bit_offset, false), ""); + + res = LLVMBuildOr(p->builder, res, elem, ""); + } + LLVMBuildStore(p->builder, res, v.addr.value); + } else { + for_array(i, fields) { + auto const &f = fields[i]; + + if ((f.bit_offset & 7) == 0) { + u64 unpacked_bit_size = cast(u64)(8*type_size_of(f.field_type)); + u64 byte_size = (f.bit_size+7)/8; + + if (f.bit_offset + unpacked_bit_size <= total_bit_size) { + byte_size = unpacked_bit_size/8; + } + lbValue dst = lb_emit_ptr_offset(p, dst_byte_ptr, lb_const_int(p->module, t_int, f.bit_offset/8)); + lbValue src = lb_address_from_load_or_generate_local(p, values[i]); + lb_mem_copy_non_overlapping(p, dst, src, lb_const_int(p->module, t_uintptr, byte_size)); + } else { + lbAddr dst = lb_addr_bit_field(v.addr, f.field_type, f.bit_offset, f.bit_size); + lb_addr_store(p, dst, values[i]); + } + } + } + } else { + // individual storing + for_array(i, values) { + auto const &f = fields[i]; + lbAddr dst = lb_addr_bit_field(v.addr, f.field_type, f.bit_offset, f.bit_size); + lb_addr_store(p, dst, values[i]); + } + } + return v; + } case Type_Struct: { // TODO(bill): "constant" '#raw_union's are not initialized constantly at the moment. @@ -4771,7 +4854,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { u8 bit_size = bf_type->BitField.bit_sizes[index]; i64 bit_offset = bf_type->BitField.bit_offsets[index]; - return lb_addr_bit_field(ptr, f->type, index, bit_offset, bit_size); + return lb_addr_bit_field(ptr, f->type, bit_offset, bit_size); } { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index b8fbd231e..bf23417c6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -450,14 +450,13 @@ gb_internal lbAddr lb_addr_swizzle_large(lbValue addr, Type *array_type, Slicemodule, type); LLVMTypeKind kind = LLVMGetTypeKind(llvm_type); - + i64 sz = type_size_of(type); switch (kind) { case LLVMStructTypeKind: case LLVMArrayTypeKind: - { - // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too - i32 sz = cast(i32)type_size_of(type); - lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); - } + // NOTE(bill): Enforce zeroing through memset to make sure padding is zeroed too + lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, sz).value, alignment, false); break; default: LLVMBuildStore(p->builder, LLVMConstNull(lb_type(p->module, type)), ptr); From 214537b4209211f9ceb9932b8d9980ea6503e8ea Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 17:01:09 +0100 Subject: [PATCH 107/171] Improve codegen for `bit_field [N]T` compound literals --- base/runtime/internal.odin | 10 ++-- src/llvm_backend_expr.cpp | 91 ++++++++++++++++++++++++++++++++---- src/llvm_backend_general.cpp | 8 ++-- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 6ca61c721..6f0445787 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1042,19 +1042,17 @@ fixdfti :: proc(a: u64) -> i128 { __write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { for i in 0..>3]) & (1<<(i&7)) != 0) b := the_bit<<(j&7) - dst[j/8] &~= b - dst[j/8] |= b + dst[j>>3] = (dst[j>>3] &~ b) | b } } __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { for j in 0..>3]) & (1<<(i&7)) != 0) b := the_bit<<(j&7) - dst[j/8] &~= b - dst[j/8] |= b + dst[j>>3] = (dst[j>>3] &~ b) | b } } \ No newline at end of file diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 4209ba1ea..ee1a384ae 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4347,7 +4347,19 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { } } - if (fields.count == bt->BitField.fields.count) { + bool any_fields_different_endian = false; + for (auto const &f : fields) { + if (is_type_different_to_arch_endianness(f.field_type)) { + // NOTE(bill): Just be slow for this, to be correct + any_fields_different_endian = true; + break; + } + } + + if (!any_fields_different_endian && + fields.count == bt->BitField.fields.count) { + // SINGLE INTEGER BACKING ONLY + Type *backing_type = core_type(bt->BitField.backing_type); GB_ASSERT(is_type_integer(backing_type) || (is_type_array(backing_type) && is_type_integer(backing_type->Array.elem))); @@ -4359,27 +4371,90 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { u64 total_bit_size = cast(u64)(8*type_size_of(bt)); if (is_type_integer(backing_type)) { - LLVMTypeRef lbt = lb_type(p->module, backing_type); + LLVMTypeRef lit = lb_type(p->module, backing_type); - LLVMValueRef res = LLVMConstInt(lbt, 0, false); + LLVMValueRef res = LLVMConstInt(lit, 0, false); for (isize i = 0; i < fields.count; i++) { auto const &f = fields[i]; - LLVMValueRef mask = LLVMConstInt(lbt, 1, false); - mask = LLVMConstShl(mask, LLVMConstInt(lbt, f.bit_size, false)); - mask = LLVMConstSub(mask, LLVMConstInt(lbt, 1, false)); + LLVMValueRef mask = LLVMConstInt(lit, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(lit, f.bit_size, false)); + mask = LLVMConstSub(mask, LLVMConstInt(lit, 1, false)); LLVMValueRef elem = values[i].value; - elem = LLVMBuildZExt(p->builder, elem, lbt, ""); + elem = LLVMBuildZExt(p->builder, elem, lit, ""); elem = LLVMBuildAnd(p->builder, elem, mask, ""); - elem = LLVMBuildShl(p->builder, elem, LLVMConstInt(lbt, f.bit_offset, false), ""); + elem = LLVMBuildShl(p->builder, elem, LLVMConstInt(lit, f.bit_offset, false), ""); res = LLVMBuildOr(p->builder, res, elem, ""); } + LLVMBuildStore(p->builder, res, v.addr.value); + } else if (is_type_array(backing_type)) { + // ARRAY OF INTEGER BACKING + + i64 array_count = backing_type->Array.count; + LLVMTypeRef lit = lb_type(p->module, core_type(backing_type->Array.elem)); + gb_unused(array_count); + gb_unused(lit); + + LLVMValueRef *elems = gb_alloc_array(temporary_allocator(), LLVMValueRef, array_count); + for (i64 i = 0; i < array_count; i++) { + elems[i] = LLVMConstInt(lit, 0, false); + } + + u64 elem_bit_size = cast(u64)(8*type_size_of(backing_type->Array.elem)); + u64 curr_bit_offset = 0; + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef val = values[i].value; + LLVMTypeRef vt = lb_type(p->module, values[i].type); + for (u64 bits_to_set = f.bit_size; + bits_to_set > 0; + /**/) { + i64 elem_idx = curr_bit_offset/elem_bit_size; + u64 elem_bit_offset = curr_bit_offset%elem_bit_size; + + u64 mask_width = gb_min(bits_to_set, elem_bit_size-elem_bit_offset); + GB_ASSERT(mask_width > 0); + bits_to_set -= mask_width; + + LLVMValueRef mask = LLVMConstInt(vt, 1, false); + mask = LLVMConstShl(mask, LLVMConstInt(vt, mask_width, false)); + mask = LLVMConstSub(mask, LLVMConstInt(vt, 1, false)); + + LLVMValueRef to_set = LLVMBuildAnd(p->builder, val, mask, ""); + + if (elem_bit_offset != 0) { + to_set = LLVMBuildShl(p->builder, to_set, LLVMConstInt(vt, elem_bit_offset, false), ""); + } + to_set = LLVMBuildTrunc(p->builder, to_set, lit, ""); + + if (LLVMIsNull(elems[elem_idx])) { + elems[elem_idx] = to_set; // don't even bother doing `0 | to_set` + } else { + elems[elem_idx] = LLVMBuildOr(p->builder, elems[elem_idx], to_set, ""); + } + + if (mask_width != 0) { + val = LLVMBuildLShr(p->builder, val, LLVMConstInt(vt, mask_width, false), ""); + } + curr_bit_offset += mask_width; + } + + GB_ASSERT(curr_bit_offset == f.bit_offset + f.bit_size); + } + + for (i64 i = 0; i < array_count; i++) { + LLVMValueRef elem_ptr = LLVMBuildStructGEP2(p->builder, lb_type(p->module, backing_type), v.addr.value, cast(unsigned)i, ""); + LLVMBuildStore(p->builder, elems[i], elem_ptr); + } } else { + // SLOW STORAGE + for_array(i, fields) { auto const &f = fields[i]; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index bf23417c6..494af9056 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -775,8 +775,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { lbValue dst = addr.addr; lbValue src = lb_address_from_load_or_generate_local(p, value); - if ((addr.bitfield.bit_offset & 7) == 0 && - (addr.bitfield.bit_size & 7) == 0) { + if ((addr.bitfield.bit_offset % 8) == 0 && + (addr.bitfield.bit_size % 8) == 0) { lbValue byte_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset/8); lbValue byte_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size/8); lbValue dst_offset = lb_emit_conv(p, dst, t_u8_ptr); @@ -1108,7 +1108,7 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { i64 total_bitfield_bit_size = 8*type_size_of(lb_addr_type(addr)); i64 dst_byte_size = type_size_of(addr.bitfield.type); - lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, false); + lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, true); lbValue src = addr.addr; lbValue bit_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); @@ -1118,7 +1118,7 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); - if ((addr.bitfield.bit_offset & 7) == 0) { + if ((addr.bitfield.bit_offset % 8) == 0) { lbValue copy_size = byte_size; lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); From 04278cd6548ccb79f01ffbf25141fdf21969d3e6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 17:13:53 +0100 Subject: [PATCH 108/171] Remove line info in message with `-json-errors` --- src/error.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index 8c9fb265b..7fb62c966 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -391,7 +391,11 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -424,7 +428,11 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); @@ -457,7 +465,11 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li error_out_va(fmt, va); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -482,7 +494,11 @@ gb_internal void syntax_error_va(TokenPos const &pos, TokenPos end, char const * // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); @@ -516,7 +532,11 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out("\n"); } else if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } if (has_ansi_terminal_colours()) { error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); } @@ -545,7 +565,11 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != pos) { global_error_collector.prev = pos; - error_out_pos(pos); + if (json_errors()) { + error_out_empty(); + } else { + error_out_pos(pos); + } error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); From 448827c0e459bc5e865c14a2516aa1406685f7e7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 24 Apr 2024 18:26:03 +0200 Subject: [PATCH 109/171] ci: fix macOS versions --- .github/workflows/ci.yml | 2 +- .github/workflows/nightly.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cbe8ad23..e40372af3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: timeout-minutes: 10 build_macOS: name: MacOS Build, Check, and Test - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v1 - name: Download LLVM, and setup PATH diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ff90ab57e..c9a2c821b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -77,7 +77,7 @@ jobs: build_macos: name: MacOS Build if: github.repository == 'odin-lang/Odin' - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v1 - name: Download LLVM and setup PATH From 94d35d9918a8ce8b3686dba52bab3e468b46729d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 17:28:00 +0100 Subject: [PATCH 110/171] Disallow mixing endian types within a `bit_field` --- src/check_type.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index 77ac91c38..ab8c0b057 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1101,6 +1101,45 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, gb_string_free(s); } + enum EndianKind { + Endian_Unknown, + Endian_Native, + Endian_Little, + Endian_Big, + }; + auto const &determine_endian_kind = [](Type *type) -> EndianKind { + if (is_type_boolean(type)) { + // NOTE(bill): it doesn't matter, and when it does, + // that api is absolutely stupid + return Endian_Unknown; + } else if (is_type_endian_specific(type)) { + if (is_type_endian_little(type)) { + return Endian_Little; + } else { + return Endian_Big; + } + } + return Endian_Native; + }; + + EndianKind backing_type_endian_kind = determine_endian_kind(core_array_type(backing_type)); + EndianKind endian_kind = Endian_Unknown; + for (Entity *f : fields) { + EndianKind field_kind = determine_endian_kind(f->type); + + if (field_kind && backing_type_endian_kind != field_kind) { + error(f->token, "All 'bit_field' field types must match the same endian kind as the backing type, i.e. all native, all little, or all big"); + } + + if (endian_kind == Endian_Unknown) { + endian_kind = field_kind; + } else if (field_kind && endian_kind != field_kind) { + error(f->token, "All 'bit_field' field types must be of the same endian variety, i.e. all native, all little, or all big"); + } + } + + + if (bit_sizes.count > 0 && is_type_integer(backing_type)) { bool all_booleans = is_type_boolean(fields[0]->type); bool all_ones = bit_sizes[0] == 1; From e8c5bb46296f8eb65fe8f343407381e88ba9bcca Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 24 Apr 2024 18:38:46 +0200 Subject: [PATCH 111/171] compiler: support returning 0 sized types in arm64 abi --- src/llvm_abi.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 88bb58c55..85a16d321 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1131,8 +1131,9 @@ namespace lbAbiArm64 { if (size <= 16) { LLVMTypeRef cast_type = nullptr; - GB_ASSERT(size > 0); - if (size <= 8) { + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); + } else if (size <= 8) { cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); From a4cec2e8b823ee922d73c15ba5e2fbf8461b89a4 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 24 Apr 2024 18:37:03 +0200 Subject: [PATCH 112/171] sys/darwin/foundation: fix Application->sendEvent signature --- core/sys/darwin/Foundation/NSApplication.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index d332345f9..34221aed6 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -132,7 +132,7 @@ Application_nextEventMatchingMask :: proc "c" (self: ^Application, mask: EventMa @(objc_type=Application, objc_name="sendEvent") Application_sendEvent :: proc "c" (self: ^Application, event: ^Event) { - msgSend(Event, self, "sendEvent:", event) + msgSend(nil, self, "sendEvent:", event) } @(objc_type=Application, objc_name="updateWindows") Application_updateWindows :: proc "c" (self: ^Application) { From 3b53c99576de5cb1e294f316bf5ce7c95d1cd51a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Apr 2024 20:55:18 +0100 Subject: [PATCH 113/171] Improve support for big-endian `bit_field`s --- src/llvm_backend_general.cpp | 52 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 494af9056..02afa628c 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -773,16 +773,33 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.kind == lbAddr_BitField) { lbValue dst = addr.addr; - lbValue src = lb_address_from_load_or_generate_local(p, value); + if (is_type_endian_big(addr.bitfield.type)) { + i64 shift_amount = 8*type_size_of(value.type) - addr.bitfield.bit_size; + lbValue shifted_value = value; + shifted_value.value = LLVMBuildLShr(p->builder, + shifted_value.value, + LLVMConstInt(LLVMTypeOf(shifted_value.value), shift_amount, false), ""); + + lbValue src = lb_address_from_load_or_generate_local(p, shifted_value); + + auto args = array_make(temporary_allocator(), 4); + args[0] = dst; + args[1] = src; + args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); + args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); + lb_emit_runtime_call(p, "__write_bits", args); + } else if ((addr.bitfield.bit_offset % 8) == 0 && + (addr.bitfield.bit_size % 8) == 0) { + lbValue src = lb_address_from_load_or_generate_local(p, value); - if ((addr.bitfield.bit_offset % 8) == 0 && - (addr.bitfield.bit_size % 8) == 0) { lbValue byte_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset/8); lbValue byte_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size/8); lbValue dst_offset = lb_emit_conv(p, dst, t_u8_ptr); dst_offset = lb_emit_ptr_offset(p, dst_offset, byte_offset); lb_mem_copy_non_overlapping(p, dst_offset, src, byte_size); } else { + lbValue src = lb_address_from_load_or_generate_local(p, value); + auto args = array_make(temporary_allocator(), 4); args[0] = dst; args[1] = src; @@ -1118,7 +1135,23 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); - if ((addr.bitfield.bit_offset % 8) == 0) { + lbValue r = {}; + if (is_type_endian_big(addr.bitfield.type)) { + auto args = array_make(temporary_allocator(), 4); + args[0] = dst.addr; + args[1] = src; + args[2] = bit_offset; + args[3] = bit_size; + lb_emit_runtime_call(p, "__read_bits", args); + + LLVMValueRef shift_amount = LLVMConstInt( + lb_type(p->module, lb_addr_type(dst)), + 8*dst_byte_size - addr.bitfield.bit_size, + false + ); + r = lb_addr_load(p, dst); + r.value = LLVMBuildShl(p->builder, r.value, shift_amount, ""); + } else if ((addr.bitfield.bit_offset % 8) == 0) { lbValue copy_size = byte_size; lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); @@ -1127,6 +1160,7 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { copy_size = lb_const_int(p->module, t_uintptr, dst_byte_size); } lb_mem_copy_non_overlapping(p, dst.addr, src_offset, copy_size, false); + r = lb_addr_load(p, dst); } else { auto args = array_make(temporary_allocator(), 4); args[0] = dst.addr; @@ -1134,20 +1168,16 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { args[2] = bit_offset; args[3] = bit_size; lb_emit_runtime_call(p, "__read_bits", args); + r = lb_addr_load(p, dst); } - lbValue r = lb_addr_load(p, dst); Type *t = addr.bitfield.type; if (do_mask) { GB_ASSERT(addr.bitfield.bit_size < 8*type_size_of(ct)); - LLVMTypeRef lt = lb_type(p->module, t); - LLVMValueRef mask = LLVMConstInt(lt, 1, false); - mask = LLVMConstShl(mask, LLVMConstInt(lt, addr.bitfield.bit_size, false)); - mask = LLVMConstSub(mask, LLVMConstInt(lt, 1, false)); - lbValue m = {mask, t}; - r = lb_emit_arith(p, Token_And, r, m, t); + lbValue mask = lb_const_int(p->module, t, (1ull< Date: Thu, 25 Apr 2024 19:08:48 +0200 Subject: [PATCH 114/171] improve some Negative_Read/Negative_Write logic Returns the actual error if one is set, instead of swallowing it for the less descriptive negative error. Also fixes a out-of-bounds slice error in `bufio.writer_write` because it wasn't checking the returned `m`. --- core/bufio/reader.odin | 8 ++++---- core/bufio/writer.odin | 4 ++++ core/bytes/buffer.odin | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index e5307f105..8ec736a66 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error { for i := b.max_consecutive_empty_reads; i > 0; i -= 1 { n, err := io.read(b.rd, b.buf[b.w:]) if n < 0 { - return .Negative_Read + return err if err != nil else .Negative_Read } b.w += n if err != nil { @@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) { if len(p) >= len(b.buf) { n, b.err = io.read(b.rd, p) if n < 0 { - return 0, .Negative_Read + return 0, b.err if b.err != nil else .Negative_Read } if n > 0 { @@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) { b.r, b.w = 0, 0 n, b.err = io.read(b.rd, b.buf) if n < 0 { - return 0, .Negative_Read + return 0, b.err if b.err != nil else .Negative_Read } if n == 0 { return 0, _reader_consume_err(b) @@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) { n, err := io.write(w, b.buf[b.r:b.w]) if n < 0 { - return 0, .Negative_Write + return 0, err if err != nil else .Negative_Write } b.r += n return i64(n), err diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index 3c7fd30c5..5edd3dd6b 100644 --- a/core/bufio/writer.odin +++ b/core/bufio/writer.odin @@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) { m: int if writer_buffered(b) == 0 { m, b.err = io.write(b.wr, p) + if m < 0 && b.err == nil { + b.err = .Negative_Write + break + } } else { m = copy(b.buf[b.n:], p) b.n += m diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index abfee6f2f..cb2ef9c62 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -359,7 +359,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n resize(&b.buf, i) m, e := io.read(r, b.buf[i:cap(b.buf)]) if m < 0 { - err = .Negative_Read + err = e if e != nil else .Negative_Read return } From 3000508c027c9d30c168266d0ae276cc14de3982 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Thu, 25 Apr 2024 21:50:34 +0200 Subject: [PATCH 115/171] Switched to native futex on NetBSD --- core/sync/futex_netbsd.odin | 189 ++++++++--------------------- core/sys/unix/signal_netbsd.odin | 30 ----- core/sys/unix/syscalls_netbsd.odin | 3 + src/threading.cpp | 13 +- 4 files changed, 61 insertions(+), 174 deletions(-) delete mode 100644 core/sys/unix/signal_netbsd.odin create mode 100644 core/sys/unix/syscalls_netbsd.odin diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin index 4f52e952f..06ff00a98 100644 --- a/core/sync/futex_netbsd.odin +++ b/core/sync/futex_netbsd.odin @@ -1,165 +1,74 @@ //+private package sync -import "core:c" +import "base:intrinsics" import "core:time" +import "core:c" import "core:sys/unix" -@(private="file") -Wait_Node :: struct { - thread: unix.pthread_t, - futex: ^Futex, - prev, next: ^Wait_Node, +foreign import libc "system:c" + +FUTEX_PRIVATE_FLAG :: 128 + +FUTEX_WAIT_PRIVATE :: 0 | FUTEX_PRIVATE_FLAG +FUTEX_WAKE_PRIVATE :: 1 | FUTEX_PRIVATE_FLAG + +EINTR :: 4 /* Interrupted system call */ +EAGAIN :: 35 /* Resource temporarily unavailable */ +ETIMEDOUT :: 60 /* Operation timed out */ + +Time_Spec :: struct { + time_sec: uint, + time_nsec: uint, } -@(private="file") -atomic_flag :: distinct bool -@(private="file") -Wait_Queue :: struct { - lock: atomic_flag, - list: Wait_Node, -} -@(private="file") -waitq_lock :: proc "contextless" (waitq: ^Wait_Queue) { - for cast(bool)atomic_exchange_explicit(&waitq.lock, atomic_flag(true), .Acquire) { - cpu_relax() // spin... + +get_last_error :: proc "contextless" () -> int { + foreign libc { + __errno :: proc() -> ^c.int --- } -} -@(private="file") -waitq_unlock :: proc "contextless" (waitq: ^Wait_Queue) { - atomic_store_explicit(&waitq.lock, atomic_flag(false), .Release) + return int(__errno()^) } -// FIXME: This approach may scale badly in the future, -// possible solution - hash map (leads to deadlocks now). -@(private="file") -g_waitq: Wait_Queue - -@(init, private="file") -g_waitq_init :: proc() { - g_waitq = { - list = { - prev = &g_waitq.list, - next = &g_waitq.list, - }, +_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case: + _panic("futex_wait failure") + } } + return true } -@(private="file") -get_waitq :: #force_inline proc "contextless" (f: ^Futex) -> ^Wait_Queue { - _ = f - return &g_waitq -} - -_futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - waiter := Wait_Node{ - thread = unix.pthread_self(), - futex = f, - prev = head, - next = head.next, - } - - waiter.prev.next = &waiter - waiter.next.prev = &waiter - - old_mask, mask: unix.sigset_t - unix.sigemptyset(&mask) - unix.sigaddset(&mask, unix.SIGCONT) - unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) - - if u32(atomic_load_explicit(f, .Acquire)) == expect { - waitq_unlock(waitq) - defer waitq_lock(waitq) - - sig: c.int - unix.sigwait(&mask, &sig) - errno := unix.errno() - ok = errno == unix.ERROR_NONE - } - - waiter.prev.next = waiter.next - waiter.next.prev = waiter.prev - - unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) - - // FIXME: Add error handling! - return -} - -_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) { +_futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, duration: time.Duration) -> bool { if duration <= 0 { return false } - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - waiter := Wait_Node{ - thread = unix.pthread_self(), - futex = f, - prev = head, - next = head.next, - } - - waiter.prev.next = &waiter - waiter.next.prev = &waiter - - old_mask, mask: unix.sigset_t - unix.sigemptyset(&mask) - unix.sigaddset(&mask, unix.SIGCONT) - unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) - - if u32(atomic_load_explicit(f, .Acquire)) == expect { - waitq_unlock(waitq) - defer waitq_lock(waitq) - - info: unix.siginfo_t - ts := unix.timespec{ - tv_sec = i64(duration / 1e9), - tv_nsec = i64(duration % 1e9), + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{ + time_sec = cast(uint)(duration / 1e9), + time_nsec = cast(uint)(duration % 1e9), + }) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case ETIMEDOUT: + return false + case: + _panic("futex_wait_with_timeout failure") } - unix.sigtimedwait(&mask, &info, &ts) - errno := unix.errno() - ok = errno == unix.EAGAIN || errno == unix.ERROR_NONE } - - waiter.prev.next = waiter.next - waiter.next.prev = waiter.prev - - unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) - - // FIXME: Add error handling! - return + return true } -_futex_signal :: proc "contextless" (f: ^Futex) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - for waiter := head.next; waiter != head; waiter = waiter.next { - if waiter.futex == f { - unix.pthread_kill(waiter.thread, unix.SIGCONT) - break - } +_futex_signal :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1) == -1 { + _panic("futex_wake_single failure") } } -_futex_broadcast :: proc "contextless" (f: ^Futex) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - for waiter := head.next; waiter != head; waiter = waiter.next { - if waiter.futex == f { - unix.pthread_kill(waiter.thread, unix.SIGCONT) - } +_futex_broadcast :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32))) == -1 { + _panic("_futex_wake_all failure") } } diff --git a/core/sys/unix/signal_netbsd.odin b/core/sys/unix/signal_netbsd.odin deleted file mode 100644 index c32f0bfbe..000000000 --- a/core/sys/unix/signal_netbsd.odin +++ /dev/null @@ -1,30 +0,0 @@ -package unix - -import "core:c" - -foreign import libc "system:c" - -ERROR_NONE :: 0 -EAGAIN :: 35 - -SIGCONT :: 19 - -SIG_BLOCK :: 1 -SIG_UNBLOCK :: 2 -SIG_SETMASK :: 3 - -siginfo_t :: struct { _: [128]u8 } -sigset_t :: struct { _: [4]u32 } - -foreign libc { - @(link_name="__sigemptyset14") sigemptyset :: proc(set: ^sigset_t) -> c.int --- - @(link_name="__sigaddset14") sigaddset :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- - @(link_name="__sigtimedwait50") sigtimedwait :: proc(set: ^sigset_t, info: ^siginfo_t, timeout: ^timespec) -> c.int --- - @(link_name="sigwait") sigwait :: proc(set: ^sigset_t, _signal: ^c.int) -> c.int --- - - @(private="file", link_name="__errno") get_error_location :: proc() -> ^c.int --- -} - -errno :: #force_inline proc "contextless" () -> int { - return int(get_error_location()^) -} diff --git a/core/sys/unix/syscalls_netbsd.odin b/core/sys/unix/syscalls_netbsd.odin new file mode 100644 index 000000000..e92dc6d92 --- /dev/null +++ b/core/sys/unix/syscalls_netbsd.odin @@ -0,0 +1,3 @@ +package unix + +SYS___futex : uintptr : 166 diff --git a/src/threading.cpp b/src/threading.cpp index 79ed8e8a4..b2cfa6d8e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -634,9 +634,15 @@ gb_internal void thread_set_name(Thread *t, char const *name) { #endif } -#if defined(GB_SYSTEM_LINUX) -#include +#if defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_NETBSD) + #include +#ifdef GB_SYSTEM_LINUX + #include +#else + #include + #define SYS_futex SYS___futex +#endif gb_internal void futex_signal(Futex *addr) { int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); @@ -903,11 +909,10 @@ gb_internal void futex_wait(Futex *f, Footex val) { } while (f->load() == val); } -#elif defined(GB_SYSTEM_HAIKU) || defined(GB_SYSTEM_NETBSD) +#elif defined(GB_SYSTEM_HAIKU) // Futex implementation taken from https://tavianator.com/2023/futex.html -#include #include #include From 22fa420c4f655ab9380b5384350be2e2281b39a6 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Thu, 25 Apr 2024 22:22:59 +0200 Subject: [PATCH 116/171] Should pass 0 as the rest of futex arguments --- core/sync/futex_netbsd.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin index 06ff00a98..08f7cd6c9 100644 --- a/core/sync/futex_netbsd.odin +++ b/core/sync/futex_netbsd.odin @@ -30,7 +30,7 @@ get_last_error :: proc "contextless" () -> int { } _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { - if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0) == -1 { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0) == -1 { switch get_last_error() { case EINTR, EAGAIN: return true @@ -48,7 +48,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{ time_sec = cast(uint)(duration / 1e9), time_nsec = cast(uint)(duration % 1e9), - }) == -1 { + }, 0, 0) == -1 { switch get_last_error() { case EINTR, EAGAIN: return true @@ -62,13 +62,13 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du } _futex_signal :: proc "contextless" (futex: ^Futex) { - if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1) == -1 { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0) == -1 { _panic("futex_wake_single failure") } } _futex_broadcast :: proc "contextless" (futex: ^Futex) { - if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32))) == -1 { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0) == -1 { _panic("_futex_wake_all failure") } } From 6bbdbb4447b0a2b5b485ae4351016b05ae79758f Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Fri, 26 Apr 2024 11:04:27 +0200 Subject: [PATCH 117/171] Added missing core:sys/info package for NetBSD --- ...latform_openbsd.odin => platform_bsd.odin} | 12 +++-- core/sys/unix/sysctl_netbsd.odin | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) rename core/sys/info/{platform_openbsd.odin => platform_bsd.odin} (89%) create mode 100644 core/sys/unix/sysctl_netbsd.odin diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_bsd.odin similarity index 89% rename from core/sys/info/platform_openbsd.odin rename to core/sys/info/platform_bsd.odin index 772531ceb..3826129d1 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,4 +1,4 @@ -// +build openbsd +//+build openbsd, netbsd package sysinfo import sys "core:sys/unix" @@ -11,12 +11,16 @@ version_string_buf: [1024]u8 @(init, private) init_os_version :: proc () { - os_version.platform = .OpenBSD + when ODIN_OS == .NetBSD { + os_version.platform = .NetBSD + } else { + os_version.platform = .OpenBSD + } kernel_version_buf: [1024]u8 b := strings.builder_from_bytes(version_string_buf[:]) - // Retrieve kernel info using `sysctl`, e.g. OpenBSD + // Retrieve kernel info using `sysctl`, e.g. OpenBSD and NetBSD mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE} if !sys.sysctl(mib, &kernel_version_buf) { return @@ -69,4 +73,4 @@ init_ram :: proc() { if sys.sysctl(mib, &mem_size) { ram.total_ram = int(mem_size) } -} \ No newline at end of file +} diff --git a/core/sys/unix/sysctl_netbsd.odin b/core/sys/unix/sysctl_netbsd.odin new file mode 100644 index 000000000..ad89b9ad4 --- /dev/null +++ b/core/sys/unix/sysctl_netbsd.odin @@ -0,0 +1,44 @@ +package unix + +import "core:c" +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 --- +} + +sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { + mib := mib + result_size := c.size_t(size_of(T)) + res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0) + return res == 0 +} + +// See /usr/include/sys/sysctl.h for details +CTL_KERN :: 1 + KERN_OSTYPE :: 1 + KERN_OSRELEASE :: 2 + KERN_OSREV :: 3 + KERN_VERSION :: 4 +CTL_VM :: 2 +CTL_FS :: 3 +CTL_NET :: 4 +CTL_DEBUG :: 5 +CTL_HW :: 6 + HW_MACHINE :: 1 + HW_MODEL :: 2 + HW_NCPU :: 3 + HW_BYTEORDER :: 4 + HW_PHYSMEM :: 5 + HW_USERMEM :: 6 + HW_PAGESIZE :: 7 + HW_DISKNAMES :: 8 + HW_IOSTATS :: 9 + HW_MACHINE_ARCH :: 10 + HW_ALIGNBYTES :: 11 + HW_CNMAGIC :: 12 + HW_PHYSMEM64 :: 13 + HW_USERMEM64 :: 14 + HW_IOSTATNAMES :: 15 + HW_NCPUONLINE :: 16 From f95bb77f722ac076963f072432c508a32d338340 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 26 Apr 2024 05:19:52 -0400 Subject: [PATCH 118/171] Fix memory leak in `sync/chan` --- core/sync/chan/chan.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index cbcfdf3bf..f4774e4f8 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -75,6 +75,7 @@ create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: ptr := mem.alloc(size, align, allocator) or_return c = (^Raw_Chan)(ptr) + c.allocator = allocator c.allocation_size = size c.unbuffered_data = ([^]byte)(ptr)[offset:] c.msg_size = u16(msg_size) @@ -99,6 +100,7 @@ create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: ptr := mem.alloc(size, align, allocator) or_return c = (^Raw_Chan)(ptr) + c.allocator = allocator c.allocation_size = size bptr := ([^]byte)(ptr) From 94e0707456bd2dc697cff055810e36d68e0f5a47 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 13:12:15 +0100 Subject: [PATCH 119/171] Fix minor bug --- src/linker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker.cpp b/src/linker.cpp index 498a96c5f..e694fd999 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -167,7 +167,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (has_asm_extension(lib)) { if (!string_set_update(&asm_files, lib)) { - String asm_file = asm_files.entries[i].value; + String asm_file = lib; String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); String obj_format = str_lit("win64"); #if defined(GB_ARCH_32_BIT) From 7305478261700fc95f6748ba3091978a3fe7b1f3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 13:12:23 +0100 Subject: [PATCH 120/171] Minor changes --- src/checker.cpp | 2 ++ src/docs_writer.cpp | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index b7fe2b903..116f275bc 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4486,6 +4486,8 @@ gb_internal void check_all_global_entities(Checker *c) { (void)type_align_of(e->type); } } + + gb_printf_err("Global Entity Count: %td\n", c->info.entities.count); } diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 9ced78d33..ba71eae4d 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -987,9 +987,8 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { auto entities = array_make(heap_allocator(), 0, w->entity_cache.count); defer (array_free(&entities)); - for (u32 i = 0; i < w->entity_cache.count; i++) { - Entity *e = w->entity_cache.entries[i].key; - array_add(&entities, e); + for (auto const &entry : w->entity_cache) { + array_add(&entities, entry.key); } for (Entity *e : entities) { GB_ASSERT(e != nullptr); @@ -998,9 +997,9 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { } } - for (u32 i = 0; i < w->entity_cache.count; i++) { - Entity *e = w->entity_cache.entries[i].key; - OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value; + for (auto const &entry : w->entity_cache) { + Entity *e = entry.key; + OdinDocEntityIndex entity_index = entry.value; OdinDocTypeIndex type_index = odin_doc_type(w, e->type); OdinDocEntityIndex foreign_library = 0; From a3e77dcc3bc4cf7d0548afa73d38914354761aa0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 13:25:08 +0100 Subject: [PATCH 121/171] Minor clean up --- src/checker.cpp | 16 ++++++++++------ src/string_map.cpp | 7 ++++--- src/string_set.cpp | 4 +++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 116f275bc..f3e14eeba 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4309,17 +4309,21 @@ gb_internal bool correct_single_type_alias(CheckerContext *c, Entity *e) { gb_internal bool correct_type_alias_in_scope_backwards(CheckerContext *c, Scope *s) { bool correction = false; - u32 n = s->elements.count; - for (u32 i = n-1; i < n; i--) { - correction |= correct_single_type_alias(c, s->elements.entries[i].value); + for (u32 n = s->elements.count, i = n-1; i < n; i--) { + Entity *e = s->elements.entries[i].value; + if (e != nullptr) { + correction |= correct_single_type_alias(c, e); + } } return correction; } gb_internal bool correct_type_alias_in_scope_forwards(CheckerContext *c, Scope *s) { bool correction = false; - u32 n = s->elements.count; - for (isize i = 0; i < n; i++) { - correction |= correct_single_type_alias(c, s->elements.entries[i].value); + for (auto const &entry : s->elements) { + Entity *e = entry.value; + if (e != nullptr) { + correction |= correct_single_type_alias(c, entry.value); + } } return correction; } diff --git a/src/string_map.cpp b/src/string_map.cpp index f8b86a950..894579a03 100644 --- a/src/string_map.cpp +++ b/src/string_map.cpp @@ -2,8 +2,8 @@ GB_STATIC_ASSERT(sizeof(MapIndex) == sizeof(u32)); struct StringHashKey { - u32 hash; String string; + u32 hash; operator String() const noexcept { return this->string; @@ -329,11 +329,12 @@ gb_internal StringMapEntry const *begin(StringMap const &m) noexcept { template -gb_internal StringMapEntry *end(StringMap &m) { +gb_internal StringMapEntry *end(StringMap &m) noexcept { return m.entries + m.count; } template gb_internal StringMapEntry const *end(StringMap const &m) noexcept { return m.entries + m.count; -} \ No newline at end of file +} + diff --git a/src/string_set.cpp b/src/string_set.cpp index fb4640c20..a37d8ba80 100644 --- a/src/string_set.cpp +++ b/src/string_set.cpp @@ -208,7 +208,9 @@ gb_internal void string_set__erase(StringSet *s, MapFindResult fr) { } auto *entry = &s->entries[fr.entry_index]; *entry = s->entries[s->entries.count-1]; - StringHashKey key = {entry->hash, entry->value}; + StringHashKey key; + key.hash = entry->hash; + key.string = entry->value; last = string_set__find(s, key); if (last.entry_prev != MAP_SENTINEL) { s->entries[last.entry_prev].next = fr.entry_index; From c685b404ea9cc9807b6d8cfc6f2a119924a79b4f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 14:15:22 +0100 Subject: [PATCH 122/171] Implement dumb `StringMap` --- src/checker.cpp | 7 +- src/string_map.cpp | 305 ++++++++++++++++++++------------------------- 2 files changed, 139 insertions(+), 173 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index f3e14eeba..3b51cc6e0 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4310,8 +4310,9 @@ gb_internal bool correct_single_type_alias(CheckerContext *c, Entity *e) { gb_internal bool correct_type_alias_in_scope_backwards(CheckerContext *c, Scope *s) { bool correction = false; for (u32 n = s->elements.count, i = n-1; i < n; i--) { - Entity *e = s->elements.entries[i].value; - if (e != nullptr) { + auto const &entry = s->elements.entries[i]; + Entity *e = entry.value; + if (entry.hash && e != nullptr) { correction |= correct_single_type_alias(c, e); } } @@ -4490,8 +4491,6 @@ gb_internal void check_all_global_entities(Checker *c) { (void)type_align_of(e->type); } } - - gb_printf_err("Global Entity Count: %td\n", c->info.entities.count); } diff --git a/src/string_map.cpp b/src/string_map.cpp index 894579a03..4de88bbf9 100644 --- a/src/string_map.cpp +++ b/src/string_map.cpp @@ -13,7 +13,8 @@ struct StringHashKey { } }; gb_internal gb_inline u32 string_hash(String const &s) { - return fnv32a(s.text, s.len) & 0x7fffffff; + u32 res = fnv32a(s.text, s.len) & 0x7fffffff; + return res | (res == 0); } gb_internal gb_inline StringHashKey string_hash_string(String const &s) { @@ -25,19 +26,16 @@ gb_internal gb_inline StringHashKey string_hash_string(String const &s) { template struct StringMapEntry { - String key; - u32 hash; - MapIndex next; - T value; + String key; + u32 hash; + T value; }; template struct StringMap { - MapIndex * hashes; - usize hashes_count; StringMapEntry *entries; u32 count; - u32 entries_capacity; + u32 capacity; }; @@ -73,127 +71,91 @@ gb_internal gb_inline void string_map_init(StringMap *h, usize capacity) { template gb_internal gb_inline void string_map_destroy(StringMap *h) { - gb_free(string_map_allocator(), h->hashes); gb_free(string_map_allocator(), h->entries); } template -gb_internal void string_map__resize_hashes(StringMap *h, usize count) { - h->hashes_count = cast(u32)resize_array_raw(&h->hashes, string_map_allocator(), h->hashes_count, count, MAP_CACHE_LINE_SIZE); -} - - -template -gb_internal void string_map__reserve_entries(StringMap *h, usize capacity) { - h->entries_capacity = cast(u32)resize_array_raw(&h->entries, string_map_allocator(), h->entries_capacity, capacity, MAP_CACHE_LINE_SIZE); -} - - -template -gb_internal MapIndex string_map__add_entry(StringMap *h, u32 hash, String const &key) { - StringMapEntry e = {}; - e.key = key; - e.hash = hash; - e.next = MAP_SENTINEL; - if (h->count+1 >= h->entries_capacity) { - string_map__reserve_entries(h, gb_max(h->entries_capacity*2, 4)); +gb_internal void string_map__insert(StringMap *h, u32 hash, String const &key, T const &value) { + if (h->count+1 >= h->capacity) { + string_map_grow(h); } - h->entries[h->count++] = e; - return cast(MapIndex)(h->count-1); -} + GB_ASSERT(h->count+1 < h->capacity); -template -gb_internal MapFindResult string_map__find(StringMap *h, u32 hash, String const &key) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count != 0) { - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->hash == hash && entry->key == key) { - return fr; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; + u32 mask = h->capacity-1; + MapIndex index = hash & mask; + MapIndex original_index = index; + do { + auto *entry = h->entries+index; + if (entry->hash == 0) { + entry->key = key; + entry->hash = hash; + entry->value = value; + + h->count += 1; + return; } - } - return fr; -} + index = (index+1)&mask; + } while (index != original_index); -template -gb_internal MapFindResult string_map__find_from_entry(StringMap *h, StringMapEntry *e) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count != 0) { - fr.hash_index = cast(MapIndex)(e->hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry == e) { - return fr; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; - } - } - return fr; + GB_PANIC("Full map"); } template gb_internal b32 string_map__full(StringMap *h) { - return 0.75f * h->hashes_count <= h->count; + return 0.75f * h->count <= h->capacity; } template gb_inline void string_map_grow(StringMap *h) { - isize new_count = gb_max(h->hashes_count<<1, 16); - string_map_reserve(h, new_count); + isize new_capacity = gb_max(h->capacity<<1, 16); + string_map_reserve(h, new_capacity); } -template -gb_internal void string_map_reset_entries(StringMap *h) { - for (u32 i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; - } - for (isize i = 0; i < h->count; i++) { - MapFindResult fr; - StringMapEntry *e = &h->entries[i]; - e->next = MAP_SENTINEL; - fr = string_map__find_from_entry(h, e); - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = cast(MapIndex)i; - } else { - h->entries[fr.entry_prev].next = cast(MapIndex)i; - } - } -} - template gb_internal void string_map_reserve(StringMap *h, usize cap) { - if (h->count*2 < h->hashes_count) { + if (cap < h->capacity) { return; } - string_map__reserve_entries(h, cap); - string_map__resize_hashes(h, cap*2); - string_map_reset_entries(h); + cap = next_pow2_isize(cap); + + StringMap new_h = {}; + new_h.count = 0; + new_h.capacity = cast(u32)cap; + new_h.entries = gb_alloc_array(string_map_allocator(), StringMapEntry, new_h.capacity); + + if (h->count) { + for (u32 i = 0; i < h->capacity; i++) { + auto *entry = h->entries+i; + if (entry->hash) { + string_map__insert(&new_h, entry->hash, entry->key, entry->value); + } + } + } + string_map_destroy(h); + *h = new_h; } template gb_internal T *string_map_get(StringMap *h, u32 hash, String const &key) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count != 0) { - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->hash == hash && entry->key == key) { - return &entry->value; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; - } + if (h->count == 0) { + return nullptr; } + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + u32 curr_hash = entry->hash; + if (curr_hash == 0) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (curr_hash == hash && entry->key == key) { + return &entry->value; + } + index = (index+1) & mask; + } while (original_index != index); return nullptr; } @@ -216,9 +178,9 @@ gb_internal gb_inline T *string_map_get(StringMap *h, char const *key) { template gb_internal T &string_map_must_get(StringMap *h, u32 hash, String const &key) { - isize index = string_map__find(h, hash, key).entry_index; - GB_ASSERT(index != MAP_SENTINEL); - return h->entries[index].value; + T *found = string_map_get(h, hash, key); + GB_ASSERT(found != nullptr); + return *found; } template @@ -239,27 +201,15 @@ gb_internal gb_inline T &string_map_must_get(StringMap *h, char const *key) { template gb_internal void string_map_set(StringMap *h, u32 hash, String const &key, T const &value) { - MapIndex index; - MapFindResult fr; - if (h->hashes_count == 0) { + if (h->count == 0) { string_map_grow(h); } - fr = string_map__find(h, hash, key); - if (fr.entry_index != MAP_SENTINEL) { - index = fr.entry_index; - } else { - index = string_map__add_entry(h, hash, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; - } else { - h->hashes[fr.hash_index] = index; - } - } - h->entries[index].value = value; - - if (string_map__full(h)) { - string_map_grow(h); + auto *found = string_map_get(h, hash, key); + if (found) { + *found = value; + return; } + string_map__insert(h, hash, key, value); } template @@ -278,63 +228,80 @@ gb_internal gb_inline void string_map_set(StringMap *h, StringHashKey const & } - -// template -// gb_internal void string_map__erase(StringMap *h, MapFindResult const &fr) { -// MapFindResult last; -// if (fr.entry_prev == MAP_SENTINEL) { -// h->hashes[fr.hash_index] = h->entries[fr.entry_index].next; -// } else { -// h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next; -// } -// if (fr.entry_index == h->count-1) { -// array_pop(&h->entries); -// return; -// } -// h->entries[fr.entry_index] = h->entries[h->count-1]; -// last = string_map__find(h, h->entries[fr.entry_index].key); -// if (last.entry_prev != MAP_SENTINEL) { -// h->entries[last.entry_prev].next = fr.entry_index; -// } else { -// h->hashes[last.hash_index] = fr.entry_index; -// } -// } - -// template -// gb_internal void string_map_remove(StringMap *h, StringHashKey const &key) { -// MapFindResult fr = string_map__find(h, key); -// if (fr.entry_index != MAP_SENTINEL) { -// string_map__erase(h, fr); -// } -// } - template gb_internal gb_inline void string_map_clear(StringMap *h) { h->count = 0; - for (u32 i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; + gb_zero_array(h->entries, h->capacity); +} + + +template +struct StringMapIterator { + StringMap *map; + MapIndex index; + + StringMapIterator &operator++() noexcept { + for (;;) { + ++index; + if (map->capacity == index) { + return *this; + } + StringMapEntry *entry = map->entries+index; + if (entry->hash != 0) { + return *this; + } + } } + + bool operator==(StringMapIterator const &other) const noexcept { + return this->map == other->map && this->index == other->index; + } + + operator StringMapEntry *() const { + return map->entries+index; + } +}; + + +template +gb_internal StringMapIterator end(StringMap &m) noexcept { + return StringMapIterator{&m, m.capacity}; +} + +template +gb_internal StringMapIterator const end(StringMap const &m) noexcept { + return StringMapIterator{&m, m.capacity}; } template -gb_internal StringMapEntry *begin(StringMap &m) noexcept { - return m.entries; +gb_internal StringMapIterator begin(StringMap &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + if (m.entries[index].hash) { + break; + } + index++; + } + return StringMapIterator{&m, index}; } template -gb_internal StringMapEntry const *begin(StringMap const &m) noexcept { - return m.entries; +gb_internal StringMapIterator const begin(StringMap const &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + if (m.entries[index].hash) { + break; + } + index++; + } + return StringMapIterator{&m, index}; } - - -template -gb_internal StringMapEntry *end(StringMap &m) noexcept { - return m.entries + m.count; -} - -template -gb_internal StringMapEntry const *end(StringMap const &m) noexcept { - return m.entries + m.count; -} - From 2b26384b89ed2b29f9d7db13f73c52029a2a2341 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 15:04:46 +0100 Subject: [PATCH 123/171] Implement dumb `PtrMap` --- src/check_expr.cpp | 10 +- src/checker.cpp | 5 +- src/exact_value.cpp | 37 ++-- src/ptr_map.cpp | 455 +++++++++++++++++++++----------------------- 4 files changed, 245 insertions(+), 262 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b893b3a00..06d0a8b12 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8079,11 +8079,10 @@ gb_internal void add_constant_switch_case(CheckerContext *ctx, SeenMap *seen, Op } uintptr key = hash_exact_value(operand.value); - TypeAndToken *found = map_get(seen, key); - if (found != nullptr) { + GB_ASSERT(key != 0); + isize count = multi_map_count(seen, key); + if (count) { TEMPORARY_ALLOCATOR_GUARD(); - - isize count = multi_map_count(seen, key); TypeAndToken *taps = gb_alloc_array(temporary_allocator(), TypeAndToken, count); multi_map_get_all(seen, key, taps); @@ -9406,7 +9405,8 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * continue; } ExactValue v = f->Constant.value; - auto found = map_get(&seen, hash_exact_value(v)); + uintptr hash = hash_exact_value(v); + auto found = map_get(&seen, hash); if (!found) { array_add(&unhandled, f); } diff --git a/src/checker.cpp b/src/checker.cpp index 3b51cc6e0..64fca0312 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1897,8 +1897,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_dependency(c->info, c->decl, t); MUTEX_GUARD_BLOCK(&c->info->type_info_mutex) { - MapFindResult fr; - auto found = map_try_get(&c->info->type_info_map, t, &fr); + auto found = map_get(&c->info->type_info_map, t); if (found != nullptr) { // Types have already been added return; @@ -1922,7 +1921,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { ti_index = c->info->type_info_types.count; array_add(&c->info->type_info_types, t); } - map_set_internal_from_try_get(&c->checker->info.type_info_map, t, ti_index, fr); + map_set(&c->checker->info.type_info_map, t, ti_index); if (prev) { // NOTE(bill): If a previous one exists already, no need to continue diff --git a/src/exact_value.cpp b/src/exact_value.cpp index b744d2db0..83af82f55 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -54,37 +54,50 @@ gb_global ExactValue const empty_exact_value = {}; gb_internal uintptr hash_exact_value(ExactValue v) { mutex_lock(&hash_exact_value_mutex); defer (mutex_unlock(&hash_exact_value_mutex)); + + uintptr res = 0; switch (v.kind) { case ExactValue_Invalid: return 0; case ExactValue_Bool: - return gb_fnv32a(&v.value_bool, gb_size_of(v.value_bool)); + res = gb_fnv32a(&v.value_bool, gb_size_of(v.value_bool)); + break; case ExactValue_String: - return gb_fnv32a(v.value_string.text, v.value_string.len); + res = gb_fnv32a(v.value_string.text, v.value_string.len); + break; case ExactValue_Integer: { u32 key = gb_fnv32a(v.value_integer.dp, gb_size_of(*v.value_integer.dp) * v.value_integer.used); u8 last = (u8)v.value_integer.sign; - return (key ^ last) * 0x01000193; + res = (key ^ last) * 0x01000193; + break; } case ExactValue_Float: - return gb_fnv32a(&v.value_float, gb_size_of(v.value_float)); + res = gb_fnv32a(&v.value_float, gb_size_of(v.value_float)); + break; case ExactValue_Pointer: - return ptr_map_hash_key(v.value_pointer); + res = ptr_map_hash_key(v.value_pointer); + break; case ExactValue_Complex: - return gb_fnv32a(v.value_complex, gb_size_of(Complex128)); + res = gb_fnv32a(v.value_complex, gb_size_of(Complex128)); + break; case ExactValue_Quaternion: - return gb_fnv32a(v.value_quaternion, gb_size_of(Quaternion256)); + res = gb_fnv32a(v.value_quaternion, gb_size_of(Quaternion256)); + break; case ExactValue_Compound: - return ptr_map_hash_key(v.value_compound); + res = ptr_map_hash_key(v.value_compound); + break; case ExactValue_Procedure: - return ptr_map_hash_key(v.value_procedure); + res = ptr_map_hash_key(v.value_procedure); + break; case ExactValue_Typeid: - return ptr_map_hash_key(v.value_typeid); + res = ptr_map_hash_key(v.value_typeid); + break; + default: + res = gb_fnv32a(&v, gb_size_of(ExactValue)); } - return gb_fnv32a(&v, gb_size_of(ExactValue)); - + return res & 0x7fffffff; } diff --git a/src/ptr_map.cpp b/src/ptr_map.cpp index 23278014f..8fd627768 100644 --- a/src/ptr_map.cpp +++ b/src/ptr_map.cpp @@ -16,23 +16,21 @@ struct MapFindResult { }; enum : MapIndex { MAP_SENTINEL = ~(MapIndex)0 }; +static void *const MAP_TOMBSTONE = (void *)~(uintptr)0; template struct PtrMapEntry { static_assert(sizeof(K) == sizeof(void *), "Key size must be pointer size"); - K key; - V value; - MapIndex next; + K key; + V value; }; template struct PtrMap { - MapIndex * hashes; - usize hashes_count; PtrMapEntry *entries; u32 count; - u32 entries_capacity; + u32 capacity; }; @@ -69,7 +67,6 @@ template gb_internal void map_grow (PtrMap< template gb_internal void map_rehash (PtrMap *h, isize new_count); template gb_internal void map_reserve (PtrMap *h, isize cap); -#if PTR_MAP_ENABLE_MULTI_MAP // Mutlivalued map procedure template gb_internal PtrMapEntry * multi_map_find_first(PtrMap *h, K key); template gb_internal PtrMapEntry * multi_map_find_next (PtrMap *h, PtrMapEntry *e); @@ -79,7 +76,6 @@ template gb_internal void multi_map_get_all (PtrMap< template gb_internal void multi_map_insert (PtrMap *h, K key, V const &value); template gb_internal void multi_map_remove (PtrMap *h, K key, PtrMapEntry *e); template gb_internal void multi_map_remove_all(PtrMap *h, K key); -#endif gb_internal gbAllocator map_allocator(void) { return heap_allocator(); @@ -94,170 +90,141 @@ gb_internal gb_inline void map_init(PtrMap *h, isize capacity) { template gb_internal gb_inline void map_destroy(PtrMap *h) { gbAllocator a = map_allocator(); - gb_free(a, h->hashes); gb_free(a, h->entries); } -template -gb_internal void map__resize_hashes(PtrMap *h, usize count) { - h->hashes_count = cast(u32)resize_array_raw(&h->hashes, map_allocator(), h->hashes_count, count, MAP_CACHE_LINE_SIZE); -} template -gb_internal void map__reserve_entries(PtrMap *h, usize capacity) { - h->entries_capacity = cast(u32)resize_array_raw(&h->entries, map_allocator(), h->entries_capacity, capacity, MAP_CACHE_LINE_SIZE); -} - - -template -gb_internal MapIndex map__add_entry(PtrMap *h, K key) { - PtrMapEntry e = {}; - e.key = key; - e.next = MAP_SENTINEL; - if (h->count+1 >= h->entries_capacity) { - map__reserve_entries(h, gb_max(h->entries_capacity*2, 4)); - } - h->entries[h->count++] = e; - return cast(MapIndex)(h->count-1); -} - -template -gb_internal MapFindResult map__find(PtrMap *h, K key) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count == 0) { - return fr; +gb_internal void map__insert(PtrMap *h, K key, V const &value) { + if (h->count+1 >= h->capacity) { + map_grow(h); } u32 hash = ptr_map_hash_key(key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->key == key) { - return fr; + u32 mask = h->capacity-1; + MapIndex index = hash & mask; + MapIndex original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key || entry->key == cast(K)MAP_TOMBSTONE) { + entry->key = key; + entry->value = value; + h->count += 1; + return; } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; - } - return fr; -} + index = (index+1)&mask; + } while (index != original_index); -template -gb_internal MapFindResult map__find_from_entry(PtrMap *h, PtrMapEntry *e) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count == 0) { - return fr; - } - u32 hash = ptr_map_hash_key(e->key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - if (&h->entries[fr.entry_index] == e) { - return fr; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = h->entries[fr.entry_index].next; - } - return fr; + GB_PANIC("FAILED TO INSERT"); } template gb_internal b32 map__full(PtrMap *h) { - return 0.75f * h->hashes_count <= h->count; + return 0.75f * h->capacity <= h->count; } template gb_internal gb_inline void map_grow(PtrMap *h) { - isize new_count = gb_max(h->hashes_count<<1, 16); - map_rehash(h, new_count); + isize new_capacity = gb_max(h->capacity<<1, 16); + map_reserve(h, new_capacity); } template -gb_internal void map_reset_entries(PtrMap *h) { - for (usize i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; - } - for (usize i = 0; i < h->count; i++) { - MapFindResult fr; - PtrMapEntry *e = &h->entries[i]; - e->next = MAP_SENTINEL; - fr = map__find_from_entry(h, e); - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = cast(MapIndex)i; - } else { - h->entries[fr.entry_prev].next = cast(MapIndex)i; - } +gb_internal void try_map_grow(PtrMap *h) { + if (h->capacity == 0 || map__full(h)) { + map_grow(h); } } + template gb_internal void map_reserve(PtrMap *h, isize cap) { - if (h->count*2 < h->hashes_count) { + if (cap < h->capacity) { return; } - map__reserve_entries(h, cap); - map__resize_hashes(h, cap*2); - map_reset_entries(h); -} + cap = next_pow2_isize(cap); + typedef PtrMapEntry EntryType; + PtrMap new_h = {}; + new_h.count = 0; + new_h.capacity = cast(u32)cap; + new_h.entries = gb_alloc_array(string_map_allocator(), EntryType, new_h.capacity); -template -gb_internal void map_rehash(PtrMap *h, isize new_count) { - map_reserve(h, new_count); + if (h->count) { + for (u32 i = 0; i < h->capacity; i++) { + auto *entry = h->entries+i; + if (entry->key && + entry->key != cast(K)MAP_TOMBSTONE) { + map__insert(&new_h, entry->key, entry->value); + } + } + } + map_destroy(h); + *h = new_h; } template gb_internal V *map_get(PtrMap *h, K key) { - MapIndex hash_index = MAP_SENTINEL; - MapIndex entry_prev = MAP_SENTINEL; - MapIndex entry_index = MAP_SENTINEL; - if (h->hashes_count != 0) { - u32 hash = ptr_map_hash_key(key); - hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - entry_index = h->hashes[hash_index]; - while (entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[entry_index]; - if (entry->key == key) { - return &entry->value; - } - entry_prev = entry_index; - entry_index = entry->next; - } + if (h->count == 0) { + return nullptr; } + if (key == 0) { + GB_PANIC("0 key"); + } + + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + return &entry->value; + } + index = (index+1) & mask; + } while (original_index != index); return nullptr; } template -gb_internal V *map_try_get(PtrMap *h, K key, MapFindResult *fr_) { - MapFindResult fr = {MAP_SENTINEL, MAP_SENTINEL, MAP_SENTINEL}; - if (h->hashes_count != 0) { - u32 hash = ptr_map_hash_key(key); - fr.hash_index = cast(MapIndex)(hash & (h->hashes_count-1)); - fr.entry_index = h->hashes[fr.hash_index]; - while (fr.entry_index != MAP_SENTINEL) { - auto *entry = &h->entries[fr.entry_index]; - if (entry->key == key) { - return &entry->value; - } - fr.entry_prev = fr.entry_index; - fr.entry_index = entry->next; +gb_internal V *map_try_get(PtrMap *h, K key, MapIndex *found_index_) { + if (found_index_) *found_index_ = ~(MapIndex)0; + + if (h->count == 0) { + return nullptr; + } + if (key == 0) { + GB_PANIC("0 key"); + } + + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + if (found_index_) *found_index_ = index; + return &entry->value; } - } - if (h->hashes_count == 0 || map__full(h)) { - map_grow(h); - } - if (fr_) *fr_ = fr; + index = (index+1) & mask; + } while (original_index != index); return nullptr; } template -gb_internal void map_set_internal_from_try_get(PtrMap *h, K key, V const &value, MapFindResult const &fr) { - MapIndex index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; +gb_internal void map_set_internal_from_try_get(PtrMap *h, K key, V const &value, MapIndex found_index) { + if (found_index != MAP_SENTINEL) { + GB_ASSERT(h->entries[found_index].key == key); + h->entries[found_index].value = value; } else { - h->hashes[fr.hash_index] = index; + map_set(h, key, value); } - h->entries[index].value = value; } template @@ -269,116 +236,83 @@ gb_internal V &map_must_get(PtrMap *h, K key) { template gb_internal void map_set(PtrMap *h, K key, V const &value) { - MapIndex index; - MapFindResult fr; - if (h->hashes_count == 0) { - map_grow(h); - } - fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { - index = fr.entry_index; - } else { - index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; - } else { - h->hashes[fr.hash_index] = index; - } - } - h->entries[index].value = value; - - if (map__full(h)) { - map_grow(h); + GB_ASSERT(key != 0); + try_map_grow(h); + auto *found = map_get(h, key); + if (found) { + *found = value; + return; } + map__insert(h, key, value); } // returns true if it previously existed template gb_internal bool map_set_if_not_previously_exists(PtrMap *h, K key, V const &value) { - MapIndex index; - MapFindResult fr; - if (h->hashes_count == 0) { - map_grow(h); - } - fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { + try_map_grow(h); + auto *found = map_get(h, key); + if (found) { return true; - } else { - index = map__add_entry(h, key); - if (fr.entry_prev != MAP_SENTINEL) { - h->entries[fr.entry_prev].next = index; - } else { - h->hashes[fr.hash_index] = index; - } - } - h->entries[index].value = value; - - if (map__full(h)) { - map_grow(h); } + map__insert(h, key, value); return false; } -template -gb_internal void map__erase(PtrMap *h, MapFindResult const &fr) { - MapFindResult last; - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = h->entries[fr.entry_index].next; - } else { - h->entries[fr.entry_prev].next = h->entries[fr.entry_index].next; - } - if (fr.entry_index == h->count-1) { - h->count--; - return; - } - h->entries[fr.entry_index] = h->entries[h->count-1]; - h->count--; - - last = map__find(h, h->entries[fr.entry_index].key); - if (last.entry_prev != MAP_SENTINEL) { - h->entries[last.entry_prev].next = fr.entry_index; - } else { - h->hashes[last.hash_index] = fr.entry_index; - } -} - template gb_internal void map_remove(PtrMap *h, K key) { - MapFindResult fr = map__find(h, key); - if (fr.entry_index != MAP_SENTINEL) { - map__erase(h, fr); + MapIndex found_index = 0; + if (map_try_get(h, key, &found_index)) { + h->entries[found_index].key = cast(K)MAP_TOMBSTONE; + h->count -= 1; } } template gb_internal gb_inline void map_clear(PtrMap *h) { h->count = 0; - for (usize i = 0; i < h->hashes_count; i++) { - h->hashes[i] = MAP_SENTINEL; - } + gb_zero_array(h->entries, h->capacity); } #if PTR_MAP_ENABLE_MULTI_MAP template gb_internal PtrMapEntry *multi_map_find_first(PtrMap *h, K key) { - MapIndex i = map__find(h, key).entry_index; - if (i == MAP_SENTINEL) { + if (h->count == 0) { return nullptr; } - return &h->entries[i]; + u32 hash = ptr_map_hash_key(key); + u32 mask = (h->capacity-1); + u32 index = hash & mask; + u32 original_index = index; + do { + auto *entry = h->entries+index; + if (!entry->key) { + // NOTE(bill): no found, but there isn't any key removal for this hash map + return nullptr; + } else if (entry->key == key) { + return entry; + } + index = (index+1) & mask; + } while (original_index != index); + return nullptr; } template gb_internal PtrMapEntry *multi_map_find_next(PtrMap *h, PtrMapEntry *e) { - MapIndex i = e->next; - while (i != MAP_SENTINEL) { - if (h->entries[i].key == e->key) { - return &h->entries[i]; + u32 mask = h->capacity-1; + MapIndex index = cast(MapIndex)(e - h->entries); + MapIndex original_index = index; + do { + index = (index+1)&mask; + auto *entry = h->entries+index; + if (!entry->key) { + return nullptr; } - i = h->entries[i].next; - } + if (entry->key == e->key) { + return entry; + } + } while (original_index != index); return nullptr; } @@ -405,34 +339,16 @@ gb_internal void multi_map_get_all(PtrMap *h, K key, V *items) { template gb_internal void multi_map_insert(PtrMap *h, K key, V const &value) { - MapFindResult fr; - MapIndex i; - if (h->hashes_count == 0) { - map_grow(h); - } - // Make - fr = map__find(h, key); - i = map__add_entry(h, key); - if (fr.entry_prev == MAP_SENTINEL) { - h->hashes[fr.hash_index] = i; - } else { - h->entries[fr.entry_prev].next = i; - } - h->entries[i].next = fr.entry_index; - h->entries[i].value = value; - // Grow if needed - if (map__full(h)) { - map_grow(h); - } + try_map_grow(h); + map__insert(h, key, value); } -template -gb_internal void multi_map_remove(PtrMap *h, K key, PtrMapEntry *e) { - MapFindResult fr = map__find_from_entry(h, e); - if (fr.entry_index != MAP_SENTINEL) { - map__erase(h, fr); - } -} +// template +// gb_internal void multi_map_remove(PtrMap *h, K key, PtrMapEntry *e) { +// if (fr.entry_index != MAP_SENTINEL) { +// map__erase(h, fr); +// } +// } template gb_internal void multi_map_remove_all(PtrMap *h, K key) { @@ -443,22 +359,77 @@ gb_internal void multi_map_remove_all(PtrMap *h, K key) { #endif -template -gb_internal PtrMapEntry *begin(PtrMap &m) { - return m.entries; -} -template -gb_internal PtrMapEntry const *begin(PtrMap const &m) { - return m.entries; -} template -gb_internal PtrMapEntry *end(PtrMap &m) { - return m.entries + m.count; +struct PtrMapIterator { + PtrMap *map; + MapIndex index; + + PtrMapIterator &operator++() noexcept { + for (;;) { + ++index; + if (map->capacity == index) { + return *this; + } + PtrMapEntry *entry = map->entries+index; + if (entry->key && entry->key != cast(K)MAP_TOMBSTONE) { + return *this; + } + } + } + + bool operator==(PtrMapIterator const &other) const noexcept { + return this->map == other->map && this->index == other->index; + } + + operator PtrMapEntry *() const { + return map->entries+index; + } +}; + + +template +gb_internal PtrMapIterator end(PtrMap &m) noexcept { + return PtrMapIterator{&m, m.capacity}; } template -gb_internal PtrMapEntry const *end(PtrMap const &m) { - return m.entries + m.count; +gb_internal PtrMapIterator const end(PtrMap const &m) noexcept { + return PtrMapIterator{&m, m.capacity}; +} + + + +template +gb_internal PtrMapIterator begin(PtrMap &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + auto key = m.entries[index].key; + if (key && key != cast(K)MAP_TOMBSTONE) { + break; + } + index++; + } + return PtrMapIterator{&m, index}; +} +template +gb_internal PtrMapIterator const begin(PtrMap const &m) noexcept { + if (m.count == 0) { + return end(m); + } + + MapIndex index = 0; + while (index < m.capacity) { + auto key = m.entries[index].key; + if (key && key != cast(K)MAP_TOMBSTONE) { + break; + } + index++; + } + return PtrMapIterator{&m, index}; } From 4bea5dbac1aea38d33b21e2c13297b0fc96c1be9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Apr 2024 15:09:08 +0100 Subject: [PATCH 124/171] Correct map usage --- src/checker.cpp | 2 +- src/llvm_backend_general.cpp | 2 +- src/llvm_backend_type.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 64fca0312..ee0ee5713 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2192,7 +2192,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { // IMPORTANT NOTE(bill): this must be copied as `map_set` takes a const ref // and effectively assigns the `+1` of the value isize const count = set->count; - if (map_set_if_not_previously_exists(set, ti_index, count)) { + if (map_set_if_not_previously_exists(set, ti_index+1, count)) { // Type already exists; return; } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 02afa628c..15cbb7c71 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -140,7 +140,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { } gen->default_module.gen = gen; - map_set(&gen->modules, cast(void *)nullptr, &gen->default_module); + map_set(&gen->modules, cast(void *)1, &gen->default_module); lb_init_module(&gen->default_module, c); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index e202a59ba..2c4abbb4d 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -2,7 +2,7 @@ gb_internal isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_ auto *set = &info->minimum_dependency_type_info_set; isize index = type_info_index(info, type, err_on_not_found); if (index >= 0) { - auto *found = map_get(set, index); + auto *found = map_get(set, index+1); if (found) { GB_ASSERT(*found >= 0); return *found + 1; From 92402a75f63fd75118fd364ab6e59e9ab438e75f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:40:59 -0400 Subject: [PATCH 125/171] Fix wrong llvm-config in build script for FreeBSD --- build_odin.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build_odin.sh b/build_odin.sh index c53766290..aca98a36d 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -32,11 +32,11 @@ if [ -z "$LLVM_CONFIG" ]; then elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12" elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11" # freebsd - elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config-17" - elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config-14" - elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config-13" - elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config-12" - elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config-11" + elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17" + elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14" + elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config13" + elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config12" + elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config11" # fallback elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config" else From 652079476489b2539c77824ec2719847f28c352d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 08:50:05 +0100 Subject: [PATCH 126/171] Fix wrong allocator usage --- src/ptr_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ptr_map.cpp b/src/ptr_map.cpp index 8fd627768..362e412ba 100644 --- a/src/ptr_map.cpp +++ b/src/ptr_map.cpp @@ -147,7 +147,7 @@ gb_internal void map_reserve(PtrMap *h, isize cap) { PtrMap new_h = {}; new_h.count = 0; new_h.capacity = cast(u32)cap; - new_h.entries = gb_alloc_array(string_map_allocator(), EntryType, new_h.capacity); + new_h.entries = gb_alloc_array(map_allocator(), EntryType, new_h.capacity); if (h->count) { for (u32 i = 0; i < h->capacity; i++) { From c752d0b541f5cc92fe44f510a4db1e7e02469fd6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 09:16:18 +0100 Subject: [PATCH 127/171] Fix printing of big endian integers in a `bit_field` --- core/fmt/fmt.odin | 5 +++- core/reflect/types.odin | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index ba749d102..018c66bd3 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2526,8 +2526,11 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit bit_offset := info.bit_offsets[i] bit_size := info.bit_sizes[i] - value := read_bits(([^]byte)(v.data), bit_offset, bit_size) type := info.types[i] + value := read_bits(([^]byte)(v.data), bit_offset, bit_size) + if reflect.is_endian_big(type) { + value <<= u64(8*type.size) - u64(bit_size) + } if !reflect.is_unsigned(runtime.type_info_core(type)) { // Sign Extension diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 9cff46a00..f242dfd5c 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -408,7 +408,68 @@ is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool { } +@(require_results) +is_endian_platform :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + return v.endianness == .Platform + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return true + case Type_Info_Pointer: + return true + } + return false +} +@(require_results) +is_endian_little :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Little + } + return v.endianness == .Little + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return ODIN_ENDIAN == .Little + case Type_Info_Pointer: + return ODIN_ENDIAN == .Little + } + return ODIN_ENDIAN == .Little +} + +@(require_results) +is_endian_big :: proc(info: ^Type_Info) -> bool { + if info == nil { return false} + info := info + info = type_info_core(info) + #partial switch v in info.variant { + case Type_Info_Integer: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Big + } + return v.endianness == .Big + case Type_Info_Bit_Set: + if v.underlying != nil { + return is_endian_platform(v.underlying) + } + return ODIN_ENDIAN == .Big + case Type_Info_Pointer: + return ODIN_ENDIAN == .Big + } + return ODIN_ENDIAN == .Big +} From 44548492521b3f03d39f46ccadec78160c014b2b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 09:16:36 +0100 Subject: [PATCH 128/171] Add attributes to procedures in text/scanner --- core/text/scanner/scanner.odin | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 7c17a0ec0..6d2465461 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -8,6 +8,7 @@ // A Scanner may be customized to recognize only a subset of those literals and to recognize different identifiers and white space characters. package text_scanner +import "base:runtime" import "core:fmt" import "core:strings" import "core:unicode" @@ -23,10 +24,12 @@ Position :: struct { } // position_is_valid reports where the position is valid +@(require_results) position_is_valid :: proc(pos: Position) -> bool { return pos.line > 0 } +@(require_results) position_to_string :: proc(pos: Position, allocator := context.temp_allocator) -> string { s := pos.filename if s == "" { @@ -140,7 +143,7 @@ init :: proc(s: ^Scanner, src: string, filename := "") -> ^Scanner { } -@(private) +@(private, require_results) advance :: proc(s: ^Scanner) -> rune { if s.src_pos >= len(s.src) { s.prev_char_len = 0 @@ -190,6 +193,7 @@ next :: proc(s: ^Scanner) -> rune { // peek returns the next Unicode character in the source without advancing the scanner // It returns EOF if the scanner's position is at least the last character of the source // if n > 0, it call next n times and return the nth Unicode character and then restore the Scanner's state +@(require_results) peek :: proc(s: ^Scanner, n := 0) -> (ch: rune) { if s.ch == -2 { s.ch = advance(s) @@ -211,6 +215,7 @@ peek :: proc(s: ^Scanner, n := 0) -> (ch: rune) { // peek returns the next token in the source // It returns EOF if the scanner's position is at least the last character of the source // if n > 0, it call next n times and return the nth token and then restore the Scanner's state +@(require_results) peek_token :: proc(s: ^Scanner, n := 0) -> (tok: rune) { assert(n >= 0) prev_s := s^ @@ -249,7 +254,7 @@ errorf :: proc(s: ^Scanner, format: string, args: ..any) { error(s, fmt.tprintf(format, ..args)) } -@(private) +@(private, require_results) is_ident_rune :: proc(s: ^Scanner, ch: rune, i: int) -> bool { if s.is_ident_rune != nil { return s.is_ident_rune(ch, i) @@ -257,7 +262,7 @@ is_ident_rune :: proc(s: ^Scanner, ch: rune, i: int) -> bool { return ch == '_' || unicode.is_letter(ch) || unicode.is_digit(ch) && i > 0 } -@(private) +@(private, require_results) scan_identifier :: proc(s: ^Scanner) -> rune { ch := advance(s) for i := 1; is_ident_rune(s, ch, i); i += 1 { @@ -266,13 +271,13 @@ scan_identifier :: proc(s: ^Scanner) -> rune { return ch } -@(private) lower :: proc(ch: rune) -> rune { return ('a' - 'A') | ch } -@(private) is_decimal :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' } -@(private) is_hex :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } +@(private, require_results) lower :: proc(ch: rune) -> rune { return ('a' - 'A') | ch } +@(private, require_results) is_decimal :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' } +@(private, require_results) is_hex :: proc(ch: rune) -> bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } -@(private) +@(private, require_results) scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { lit_name :: proc(prefix: rune) -> string { switch prefix { @@ -417,7 +422,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { return tok, ch } -@(private) +@(private, require_results) scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) { digit_val :: proc(ch: rune) -> int { switch v := lower(ch); v { @@ -484,7 +489,7 @@ scan_char :: proc(s: ^Scanner) { } } -@(private) +@(private, require_results) scan_comment :: proc(s: ^Scanner, ch: rune) -> rune { ch := ch if ch == '/' { // line comment @@ -611,6 +616,7 @@ scan :: proc(s: ^Scanner) -> (tok: rune) { // position returns the position of the character immediately after the character or token returns by the previous call to next or scan // Use the Scanner's position field for the most recently scanned token position +@(require_results) position :: proc(s: ^Scanner) -> Position { pos: Position pos.filename = s.pos.filename @@ -630,6 +636,7 @@ position :: proc(s: ^Scanner) -> Position { } // token_text returns the string of the most recently scanned token +@(require_results) token_text :: proc(s: ^Scanner) -> string { if s.tok_pos < 0 { return "" @@ -639,7 +646,8 @@ token_text :: proc(s: ^Scanner) -> string { // token_string returns a printable string for a token or Unicode character // By default, it uses the context.temp_allocator to produce the string -token_string :: proc(tok: rune, allocator := context.temp_allocator) -> string { +@(require_results) +token_string :: proc(tok: rune, allocator: runtime.Allocator) -> string { context.allocator = allocator switch tok { case EOF: return strings.clone("EOF") From efae99971bd93875ed50aeb1854701959b8814bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 09:19:50 +0100 Subject: [PATCH 129/171] Fix missing `_ =` --- core/text/scanner/scanner.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 6d2465461..d27c66f24 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -568,13 +568,13 @@ scan :: proc(s: ^Scanner) -> (tok: rune) { break case '"': if .Scan_Strings in s.flags { - scan_string(s, '"') + _ = scan_string(s, '"') tok = String } ch = advance(s) case '\'': if .Scan_Chars in s.flags { - scan_string(s, '\'') + _ = scan_string(s, '\'') tok = Char } ch = advance(s) From 393e4a9db6d649387dd5c831134e75d98b780156 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 09:53:02 +0100 Subject: [PATCH 130/171] Generalize Odin call-based "iterators" to work with more than 2-values: `for x, y, z, w in iterate(&it)` It has an artificial limitation of 100 values because if you need for than that, you're doing something wrong. --- src/check_stmt.cpp | 35 +++++++++++++++----------- src/llvm_backend_stmt.cpp | 53 ++++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 971841165..c31056b33 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1669,12 +1669,20 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Tuple: { isize count = t->Tuple.variables.count; - if (count < 1 || count > 3) { + if (count < 1) { ERROR_BLOCK(); check_not_tuple(ctx, &operand); - error_line("\tMultiple return valued parameters in a range statement are limited to a maximum of 2 usable values with a trailing boolean for the conditional\n"); + error_line("\tMultiple return valued parameters in a range statement are limited to a minimum of 1 usable values with a trailing boolean for the conditional, got %td\n", count); break; } + enum : isize {MAXIMUM_COUNT = 100}; + if (count > MAXIMUM_COUNT) { + ERROR_BLOCK(); + check_not_tuple(ctx, &operand); + error_line("\tMultiple return valued parameters in a range statement are limited to a maximum of %td usable values with a trailing boolean for the conditional, got %td\n", MAXIMUM_COUNT, count); + break; + } + Type *cond_type = t->Tuple.variables[count-1]->type; if (!is_type_boolean(cond_type)) { gbString s = type_to_string(cond_type); @@ -1683,24 +1691,23 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; } + max_val_count = count; + for (Entity *e : t->Tuple.variables) { array_add(&vals, e->type); } is_possibly_addressable = false; - if (rs->vals.count > 1 && rs->vals[1] != nullptr && count < 3) { - gbString s = type_to_string(t); - error(operand.expr, "Expected a 3-valued expression on the rhs, got (%s)", s); - gb_string_free(s); - break; - } - - if (rs->vals.count > 0 && rs->vals[0] != nullptr && count < 2) { - gbString s = type_to_string(t); - error(operand.expr, "Expected at least a 2-valued expression on the rhs, got (%s)", s); - gb_string_free(s); - break; + bool do_break = false; + for (isize i = rs->vals.count-1; i >= 0; i--) { + if (rs->vals[i] != nullptr && count < i+2) { + gbString s = type_to_string(t); + error(operand.expr, "Expected a %td-valued expression on the rhs, got (%s)", i+2, s); + gb_string_free(s); + do_break = true; + break; + } } if (is_reverse) { diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 24dd321f6..851433415 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -802,8 +802,19 @@ gb_internal void lb_build_range_enum(lbProcedure *p, Type *enum_type, Type *val_ if (done_) *done_ = done; } -gb_internal void lb_build_range_tuple(lbProcedure *p, Ast *expr, Type *val0_type, Type *val1_type, - lbValue *val0_, lbValue *val1_, lbBlock **loop_, lbBlock **done_) { +gb_internal void lb_build_range_tuple(lbProcedure *p, AstRangeStmt *rs, Scope *scope) { + Ast *expr = unparen_expr(rs->expr); + + Type *expr_type = type_of_expr(expr); + Type *et = base_type(type_deref(expr_type)); + GB_ASSERT(et->kind == Type_Tuple); + + i32 value_count = cast(i32)et->Tuple.variables.count; + + lbValue *values = gb_alloc_array(permanent_allocator(), lbValue, value_count); + + lb_open_scope(p, scope); + lbBlock *loop = lb_create_block(p, "for.tuple.loop"); lb_emit_jump(p, loop); lb_start_block(p, loop); @@ -821,11 +832,26 @@ gb_internal void lb_build_range_tuple(lbProcedure *p, Ast *expr, Type *val0_type lb_emit_if(p, cond, body, done); lb_start_block(p, body); + for (i32 i = 0; i < value_count; i++) { + values[i] = lb_emit_tuple_ev(p, tuple_value, i); + } - if (val0_) *val0_ = lb_emit_tuple_ev(p, tuple_value, 0); - if (val1_) *val1_ = lb_emit_tuple_ev(p, tuple_value, 1); - if (loop_) *loop_ = loop; - if (done_) *done_ = done; + GB_ASSERT(rs->vals.count <= value_count); + for (isize i = 0; i < rs->vals.count; i++) { + Ast *val = rs->vals[i]; + if (val != nullptr) { + lb_store_range_stmt_val(p, val, values[i]); + } + } + + lb_push_target_list(p, rs->label, done, loop, nullptr); + + lb_build_stmt(p, rs->body); + + lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_pop_target_list(p); + lb_emit_jump(p, loop); + lb_start_block(p, done); } gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs, Scope *scope) { @@ -968,6 +994,17 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc } } + TypeAndValue tav = type_and_value_of_expr(expr); + if (tav.mode != Addressing_Type) { + Type *expr_type = type_of_expr(expr); + Type *et = base_type(type_deref(expr_type)); + if (et->kind == Type_Tuple) { + lb_build_range_tuple(p, rs, scope); + return; + } + } + + lb_open_scope(p, scope); Ast *val0 = rs->vals.count > 0 ? lb_strip_and_prefix(rs->vals[0]) : nullptr; @@ -986,7 +1023,6 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lbBlock *loop = nullptr; lbBlock *done = nullptr; bool is_map = false; - TypeAndValue tav = type_and_value_of_expr(expr); if (tav.mode == Addressing_Type) { lb_build_range_enum(p, type_deref(tav.type), val0_type, &val, &key, &loop, &done); @@ -1062,8 +1098,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc break; } case Type_Tuple: - lb_build_range_tuple(p, expr, val0_type, val1_type, &val, &key, &loop, &done); - break; + GB_PANIC("Should be handled already"); case Type_BitSet: { lbModule *m = p->module; From 309a770cbf49ef950a9c9e02ccfaced57f59e73a Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 27 Apr 2024 05:24:59 -0400 Subject: [PATCH 131/171] Fix `omitempty` in `json.marshal` --- core/encoding/json/marshal.odin | 2 +- core/encoding/json/unmarshal.odin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 04ef6d434..f45cdb1f1 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -420,7 +420,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: data := rawptr(uintptr(v.data) + info.offsets[i]) the_value := any{data, id} - if is_omitempty(the_value) { + if omitempty && is_omitempty(the_value) { continue } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 691303521..eb59e7838 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -348,7 +348,7 @@ json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name = value if comma_index := strings.index_byte(json_name, ','); comma_index >= 0 { json_name = json_name[:comma_index] - extra = json_name[comma_index:] + extra = value[1 + comma_index:] } return } From 5e1b376e22854ee4dd1d455d9db372d24f445ae4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Apr 2024 10:34:17 +0100 Subject: [PATCH 132/171] Disallow `for x in bitset_or_map` if `x` is a variable that matches the "key" --- src/check_stmt.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index c31056b33..cccbab4f6 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1629,6 +1629,17 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (build_context.no_rtti && is_type_enum(t->BitSet.elem)) { error(node, "Iteration over a bit_set of an enum is not allowed runtime type information (RTTI) has been disallowed"); } + if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { + String name = rs->vals[0]->Ident.token.string; + Entity *found = scope_lookup(ctx->scope, name); + if (found && are_types_identical(found->type, t->BitSet.elem)) { + ERROR_BLOCK(); + gbString s = expr_to_string(expr); + error(rs->vals[0], "'%.*s' shadows a previous declaration which might be ambiguous with 'for (%.*s in %s)'", LIT(name), LIT(name), s); + error_line("\tSuggestion: Use a different identifier if iteration is wanted, or surround in parentheses if a normal for loop is wanted\n"); + gb_string_free(s); + } + } break; case Type_EnumeratedArray: @@ -1664,6 +1675,17 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (is_reverse) { error(node, "#reverse for is not supported for map types, as maps are unordered"); } + if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { + String name = rs->vals[0]->Ident.token.string; + Entity *found = scope_lookup(ctx->scope, name); + if (found && are_types_identical(found->type, t->Map.key)) { + ERROR_BLOCK(); + gbString s = expr_to_string(expr); + error(rs->vals[0], "'%.*s' shadows a previous declaration which might be ambiguous with 'for (%.*s in %s)'", LIT(name), LIT(name), s); + error_line("\tSuggestion: Use a different identifier if iteration is wanted, or surround in parentheses if a normal for loop is wanted\n"); + gb_string_free(s); + } + } break; case Type_Tuple: From 1deb53cddb20b6383878975ffb23579bbdddb1b9 Mon Sep 17 00:00:00 2001 From: Yunky <126269099+DreepyYunky@users.noreply.github.com> Date: Sat, 27 Apr 2024 18:58:46 +0100 Subject: [PATCH 133/171] Add SetMenu --- core/sys/windows/user32.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index a589c3ec9..cb4091058 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -126,6 +126,7 @@ foreign user32 { CreatePopupMenu :: proc() -> HMENU --- DestroyMenu :: proc(hMenu: HMENU) -> BOOL --- AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- + SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL --- TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- From 00b1a41540306a0dc292e179ad780130c02f9f44 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 11:31:50 +0100 Subject: [PATCH 134/171] Add stack frame procedures for `core:sys/windows` --- core/sys/windows/dbghelp.odin | 38 ++++++++++++++++++++++++++++++++++ core/sys/windows/kernel32.odin | 2 ++ core/sys/windows/types.odin | 4 ++++ 3 files changed, 44 insertions(+) diff --git a/core/sys/windows/dbghelp.odin b/core/sys/windows/dbghelp.odin index c2e506748..cb5458248 100644 --- a/core/sys/windows/dbghelp.odin +++ b/core/sys/windows/dbghelp.odin @@ -228,6 +228,38 @@ MINIDUMP_TYPE :: enum u32 { ValidTypeFlags = 0x01ffffff, } + +SYMBOL_INFOW :: struct { + SizeOfStruct: ULONG, + TypeIndex: ULONG, + Reserved: [2]ULONG64, + Index: ULONG, + Size: ULONG, + ModBase: ULONG64, + Flags: ULONG, + Value: ULONG64, + Address: ULONG64, + Register: ULONG, + Scope: ULONG, + Tag: ULONG, + NameLen: ULONG, + MaxNameLen: ULONG, + Name: [1]WCHAR, +} + +IMAGEHLP_LINE64 :: struct { + SizeOfStruct: DWORD, + Key: PVOID, + LineNumber: DWORD, + FileName: PWSTR, + Address: DWORD64, +} + +PSYMBOL_INFOW :: ^SYMBOL_INFOW +PIMAGEHLP_LINEW64 :: ^IMAGEHLP_LINE64 + +SYMOPT_LOAD_LINES :: 0x00000010 + @(default_calling_convention = "system") foreign Dbghelp { MiniDumpWriteDump :: proc( @@ -247,4 +279,10 @@ foreign Dbghelp { StreamPointer: ^PVOID, StreamSize: ^ULONG, ) -> BOOL --- + + SymInitialize :: proc(hProcess: HANDLE, UserSearchPath: PCSTR, fInvadeProcess: BOOL) -> BOOL --- + SymCleanup :: proc(hProcess: HANDLE) -> BOOL --- + SymSetOptions :: proc(SymOptions: DWORD) -> DWORD --- + SymFromAddrW :: proc(hProcess: HANDLE, Address: DWORD64, Displacement: PDWORD64, Symbol: PSYMBOL_INFOW) -> BOOL --- + SymGetLineFromAddrW64 :: proc(hProcess: HANDLE, dwAddr: DWORD64, pdwDisplacement: PDWORD, Line: PIMAGEHLP_LINEW64) -> BOOL --- } diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 10cc80041..5409c58f5 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -460,6 +460,8 @@ foreign kernel32 { PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: c_ulong, lpOverlapped: ^OVERLAPPED) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation) GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- + + RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT ---; } DEBUG_PROCESS :: 0x00000001 diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 4b54f0ed1..11d2774d6 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -93,10 +93,14 @@ LONG32 :: i32 ULONG64 :: u64 LONG64 :: i64 +DWORD64 :: u64 +PDWORD64 :: ^DWORD64 + PDWORD_PTR :: ^DWORD_PTR ATOM :: distinct WORD wstring :: [^]WCHAR +PWSTR :: [^]WCHAR PBYTE :: ^BYTE LPBYTE :: ^BYTE From e71cf96bbc809c1c1fb7172cd9e3c7c259520625 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 11:35:51 +0100 Subject: [PATCH 135/171] Keep -vet happy --- core/sys/windows/kernel32.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 5409c58f5..f998f33da 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -461,7 +461,7 @@ foreign kernel32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation) GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- - RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT ---; + RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT --- } DEBUG_PROCESS :: 0x00000001 From ebfbe4d26031bde4e91ba489102d60fe6959173f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 06:38:32 -0400 Subject: [PATCH 136/171] Clear unused `global_error_collector.curr_error` This should cleanly prevent acknowledging duplicate errors on the same position as seems to be the intent based on the prior `else if` condition. --- src/error.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/error.cpp b/src/error.cpp index 7fb62c966..bbbb98053 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -403,6 +403,8 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out("\n"); show_error_on_line(pos, end); } else { + global_error_collector.curr_error_value = {}; + global_error_collector.curr_error_value_set.store(false); global_error_collector.count.fetch_sub(1); } try_pop_error_value(); From 362aa82f59557743c81d9eb2853e972541ba7690 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 11:58:16 +0100 Subject: [PATCH 137/171] Begin work on `core:debug/trace` --- core/debug/trace/trace.odin | 44 ++++++++++++++++++ core/debug/trace/trace_windows.odin | 70 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 core/debug/trace/trace.odin create mode 100644 core/debug/trace/trace_windows.odin diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin new file mode 100644 index 000000000..6ca9ed2c3 --- /dev/null +++ b/core/debug/trace/trace.odin @@ -0,0 +1,44 @@ +package debug_trace + +import "base:intrinsics" +import "base:runtime" + +Frame :: distinct uintptr +MAX_FRAMES :: 64 + +Frame_Location :: runtime.Source_Code_Location + +delete_frame_location :: proc(loc: Frame_Location, allocator: runtime.Allocator) -> runtime.Allocator_Error { + delete(loc.procedure, allocator) or_return + delete(loc.file_path, allocator) or_return + return nil +} + +Context :: struct { + in_resolve: bool, // atomic + impl: _Context, +} + +init :: proc(ctx: ^Context) -> bool { + return _init(ctx) +} + +destroy :: proc(ctx: ^Context) -> bool { + return _destroy(ctx) +} + +@(require_results) +frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame { + return _frames(ctx, skip, allocator) +} + +@(require_results) +resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) { + return _resolve(ctx, frame, allocator) +} + + +@(require_results) +in_resolve :: proc "contextless" (ctx: ^Context) -> bool { + return intrinsics.atomic_load(&ctx.in_resolve) +} \ No newline at end of file diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin new file mode 100644 index 000000000..3ad5d7ed0 --- /dev/null +++ b/core/debug/trace/trace_windows.odin @@ -0,0 +1,70 @@ +//+private +//+build windows +package debug_trace + +import "base:intrinsics" +import "base:runtime" + +import win32 "core:sys/windows" +import "core:fmt" + +_Context :: struct { + hProcess: win32.HANDLE, + lock: win32.SRWLOCK, +} + +_init :: proc "contextless" (ctx: ^Context) -> (ok: bool) { + defer if !ok { _destroy(ctx) } + ctx.impl.hProcess = win32.GetCurrentProcess() + win32.SymInitialize(ctx.impl.hProcess, nil, true) or_return + win32.SymSetOptions(win32.SYMOPT_LOAD_LINES) + return true +} + +_destroy :: proc "contextless" (ctx: ^Context) -> bool { + if ctx != nil { + win32.SymCleanup(ctx.impl.hProcess) + } + return true +} + +_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame { + buffer: [MAX_FRAMES]rawptr + frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, len(buffer), &buffer[0], nil) + frames := make([]Frame, frame_count, allocator) + for &f, i in frames { + // NOTE: Return address is one after the call instruction so subtract a byte to + // end up back inside the call instruction which is needed for SymFromAddr. + f = Frame(buffer[i]) - 1 + } + return frames +} + + +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) { + intrinsics.atomic_store(&ctx.in_resolve, true) + defer intrinsics.atomic_store(&ctx.in_resolve, false) + + // NOTE(bill): Dbghelp is not thread-safe + win32.AcquireSRWLockExclusive(&ctx.impl.lock) + defer win32.ReleaseSRWLockExclusive(&ctx.impl.lock) + + data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte + symbol := (^win32.SYMBOL_INFOW)(&data[0]) + symbol.SizeOfStruct = size_of(symbol) + symbol.MaxNameLen = 255 + if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) { + result.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) + } else { + result.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) + } + + line: win32.IMAGEHLP_LINE64 + line.SizeOfStruct = size_of(line) + if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) { + result.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator) + result.line = i32(line.LineNumber) + } + + return result +} \ No newline at end of file From 29987c20c058fe269ea0961205d12f51fc3e8326 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 06:59:55 -0400 Subject: [PATCH 138/171] Fix invalid rune literal reported twice The tokenizer and the parser were reporting it in different positions. This way, they'll report in the same spot. --- src/tokenizer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index fdff9224a..f7751d840 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -767,9 +767,8 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { } } - // TODO(bill): Better Error Handling if (valid && n != 1) { - tokenizer_err(t, "Invalid rune literal"); + tokenizer_err(t, token->pos, "Invalid rune literal"); } token->string.len = t->curr - token->string.text; goto semicolon_check; From 44c9b988bbc54203307d50418c79188aee53c08c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:05:15 +0100 Subject: [PATCH 139/171] Add default debug/trace to do nothing --- core/debug/trace/trace_nil.odin | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 core/debug/trace/trace_nil.odin diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin new file mode 100644 index 000000000..ab08aee04 --- /dev/null +++ b/core/debug/trace/trace_nil.odin @@ -0,0 +1,18 @@ +//+build !windows +package debug_trace + +_Context :: struct { +} + +_init :: proc(ctx: ^Context) -> (ok: bool) { + return true +} +_destroy :: proc(ctx: ^Context) -> bool { + return true +} +_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame { + return nil +} +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) { + return +} From 6c185a5dca739b4aab034449f0627925e0137173 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:43:27 +0100 Subject: [PATCH 140/171] Add `core:debug/trace` for Linux --- core/debug/trace/trace_linux.odin | 191 ++++++++++++++++++++++++++++++ core/debug/trace/trace_nil.odin | 2 +- 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 core/debug/trace/trace_linux.odin diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_linux.odin new file mode 100644 index 000000000..529a83470 --- /dev/null +++ b/core/debug/trace/trace_linux.odin @@ -0,0 +1,191 @@ +//+private file +//+build linux +package debug_trace + +import "base:runtime" +import "core:strings" +import "core:fmt" +import "core:c" + +// NOTE: Relies on C++23 which adds and becomes ABI and that can be used +foreign import stdcpplibbacktrace "system:stdc++_libbacktrace" + +foreign import libdl "system:dl" + +backtrace_state :: struct {} +backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int) +backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int +backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int +backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr) + +@(default_calling_convention="c", link_prefix="__glibcxx_") +foreign stdcpplibbacktrace { + backtrace_create_state :: proc( + filename: cstring, + threaded: c.int, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> ^backtrace_state --- + backtrace_simple :: proc( + state: ^backtrace_state, + skip: c.int, + callback: backtrace_simple_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + backtrace_pcinfo :: proc( + state: ^backtrace_state, + pc: uintptr, + callback: backtrace_full_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + backtrace_syminfo :: proc( + state: ^backtrace_state, + addr: uintptr, + callback: backtrace_syminfo_callback, + error_callback: backtrace_error_callback, + data: rawptr, + ) -> c.int --- + + // NOTE(bill): this is technically an internal procedure, but it is exposed + backtrace_free :: proc( + state: ^backtrace_state, + p: rawptr, + size: c.size_t, // unused + error_callback: backtrace_error_callback, // unused + data: rawptr, // unused + ) --- +} + +Dl_info :: struct { + dli_fname: cstring, + dli_fbase: rawptr, + dli_sname: cstring, + dli_saddr: rawptr, +} + +@(default_calling_convention="c") +foreign libdl { + dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int --- +} + +@(private="package") +_Context :: struct { + state: ^backtrace_state, +} + +@(private="package") +_init :: proc(ctx: ^Context) -> (ok: bool) { + defer if !ok do destroy(ctx) + + ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx) + return ctx.impl.state != nil +} + +@(private="package") +_destroy :: proc(ctx: ^Context) -> bool { + if ctx != nil { + backtrace_free(ctx.impl.state, nil, 0, nil, nil) + } + return true +} + +@(private="package") +_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (frames: []Frame) { + Backtrace_Context :: struct { + ctx: ^Context, + rt_ctx: runtime.Context, + frames: [MAX_FRAMES]Frame, + frame_count: int, + } + + btc := &Backtrace_Context{ + ctx = ctx, + rt_ctx = context, + } + backtrace_simple( + ctx.impl.state, + c.int(skip + 2), + proc "c" (user: rawptr, address: uintptr) -> c.int { + btc := (^Backtrace_Context)(user) + address := Frame(address) + if address == 0 { + return 1 + } + btc.frames[btc.frame_count] = address + btc.frame_count += 1 + return 0 + }, + nil, + btc, + ) + + res := btc.frames[:btc.frame_count] + if len(res) > 0 { + frames = make([]Frame, btc.frame_count, allocator) + copy(frames, res) + } + return +} + +@(private="package") +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location { + Backtrace_Context :: struct { + rt_ctx: runtime.Context, + allocator: runtime.Allocator, + frame: Frame_Location, + } + + btc := &Backtrace_Context{ + rt_ctx = context, + allocator = allocator, + } + done := backtrace_pcinfo( + ctx.impl.state, + uintptr(frame), + proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int { + btc := (^Backtrace_Context)(data) + context = btc.rt_ctx + + frame := &btc.frame + + if file != nil { + frame.file_path = strings.clone_from_cstring(file, btc.allocator) + } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" { + frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator) + } + if symbol != nil { + frame.procedure = strings.clone_from_cstring(symbol, btc.allocator) + } else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" { + frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator) + } else { + frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator) + } + frame.line = i32(line) + return 0 + }, + nil, + btc, + ) + if done != 0 { + return btc.frame + } + + // NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least + backtrace_syminfo( + ctx.impl.state, + uintptr(frame), + proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) { + if symbol != nil { + btc := (^Backtrace_Context)(data) + context = btc.rt_ctx + btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator) + } + }, + nil, + btc, + ) + + return btc.frame +} \ No newline at end of file diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin index ab08aee04..926bd18cc 100644 --- a/core/debug/trace/trace_nil.odin +++ b/core/debug/trace/trace_nil.odin @@ -1,4 +1,4 @@ -//+build !windows +//+build !windows !linux package debug_trace _Context :: struct { From 0fa269811a6001fb21c8c9f074005f7c82584d82 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:49:17 +0100 Subject: [PATCH 141/171] Change layout of `Frame_Location` --- core/debug/trace/trace.odin | 12 ++++++++---- core/debug/trace/trace_windows.odin | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin index 6ca9ed2c3..eef27499d 100644 --- a/core/debug/trace/trace.odin +++ b/core/debug/trace/trace.odin @@ -6,11 +6,15 @@ import "base:runtime" Frame :: distinct uintptr MAX_FRAMES :: 64 -Frame_Location :: runtime.Source_Code_Location +Frame_Location :: struct { + using loc: runtime.Source_Code_Location, + allocator: runtime.Allocator, +} -delete_frame_location :: proc(loc: Frame_Location, allocator: runtime.Allocator) -> runtime.Allocator_Error { - delete(loc.procedure, allocator) or_return - delete(loc.file_path, allocator) or_return +delete_frame_location :: proc(fl: Frame_Location) -> runtime.Allocator_Error { + allocator := fl.allocator + delete(fl.loc.procedure, allocator) or_return + delete(fl.loc.file_path, allocator) or_return return nil } diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index 3ad5d7ed0..c7b4eeaa1 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -41,7 +41,7 @@ _frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Fr } -_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) { +_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (fl: Frame_Location) { intrinsics.atomic_store(&ctx.in_resolve, true) defer intrinsics.atomic_store(&ctx.in_resolve, false) @@ -54,17 +54,17 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> ( symbol.SizeOfStruct = size_of(symbol) symbol.MaxNameLen = 255 if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) { - result.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) + fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) } else { - result.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) + fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) } line: win32.IMAGEHLP_LINE64 line.SizeOfStruct = size_of(line) if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) { - result.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator) - result.line = i32(line.LineNumber) + fl.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator) + fl.line = i32(line.LineNumber) } - return result + return } \ No newline at end of file From 2eea06fc73ea7abc1432a2ad368d0fe032f5ba6f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:51:10 +0100 Subject: [PATCH 142/171] Set `in_resolve` for linux --- core/debug/trace/trace_linux.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_linux.odin index 529a83470..c65a165f9 100644 --- a/core/debug/trace/trace_linux.odin +++ b/core/debug/trace/trace_linux.odin @@ -131,6 +131,9 @@ _frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (fra @(private="package") _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location { + intrinsics.atomic_store(&ctx.in_resolve, true) + defer intrinsics.atomic_store(&ctx.in_resolve, false) + Backtrace_Context :: struct { rt_ctx: runtime.Context, allocator: runtime.Allocator, From 5ac8e8f9fd6d2e749edd7bf37c28d7439ed4e840 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:52:02 +0100 Subject: [PATCH 143/171] Add doc.odin --- core/debug/trace/doc.odin | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 core/debug/trace/doc.odin diff --git a/core/debug/trace/doc.odin b/core/debug/trace/doc.odin new file mode 100644 index 000000000..8c0778558 --- /dev/null +++ b/core/debug/trace/doc.odin @@ -0,0 +1,50 @@ +/* +A debug stack trace library. Only works when debug symbols are enabled `-debug`. + +Example: + import "base:runtime" + import "core:debug/trace" + + import "core:fmt" + + global_trace_ctx: trace.Context + + debug_trace_assertion_failure_proc :: proc(prefix, message: string, loc := #caller_location) -> ! { + runtime.print_caller_location(loc) + runtime.print_string(" ") + runtime.print_string(prefix) + if len(message) > 0 { + runtime.print_string(": ") + runtime.print_string(message) + } + runtime.print_byte('\n') + + ctx := &global_trace_ctx + if !trace.in_resolve(ctx) { + runtime.print_string("Debug Trace:\n") + frames := trace.frames(ctx, skip=1, allocator=context.temp_allocator) + for f, i in frames { + fl := trace.resolve(ctx, f, context.temp_allocator) + if fl.loc.file_path == "" && fl.loc.line == 0 { + continue + } + runtime.print_caller_location(fl.loc) + runtime.print_string(" - frame ") + runtime.print_int(i) + runtime.print_byte('\n') + } + } + runtime.trap() + } + + main :: proc() { + trace.init(&global_trace_ctx) + defer trace.destroy(&global_trace_ctx) + + context.assertion_failure_proc = debug_trace_assertion_failure_proc + + ... + } + +*/ +package debug_trace \ No newline at end of file From be09584ea5a61ac83c8ddc96eba1c7bccbe51472 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 12:56:53 +0100 Subject: [PATCH 144/171] Increase `MAX_FRAMES` --- core/debug/trace/trace.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin index eef27499d..243232ddc 100644 --- a/core/debug/trace/trace.odin +++ b/core/debug/trace/trace.odin @@ -4,7 +4,7 @@ import "base:intrinsics" import "base:runtime" Frame :: distinct uintptr -MAX_FRAMES :: 64 +MAX_FRAMES :: 512 Frame_Location :: struct { using loc: runtime.Source_Code_Location, From c0b7dd7da60c4b7557e494049937af630bc25c33 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 13:05:19 +0100 Subject: [PATCH 145/171] Remove need for allocator and MAX_FRAMES in `trace.frames` --- core/debug/trace/trace.odin | 5 ++--- core/debug/trace/trace_linux.odin | 17 +++++++++-------- core/debug/trace/trace_windows.odin | 12 +++++------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/core/debug/trace/trace.odin b/core/debug/trace/trace.odin index 243232ddc..134609b05 100644 --- a/core/debug/trace/trace.odin +++ b/core/debug/trace/trace.odin @@ -4,7 +4,6 @@ import "base:intrinsics" import "base:runtime" Frame :: distinct uintptr -MAX_FRAMES :: 512 Frame_Location :: struct { using loc: runtime.Source_Code_Location, @@ -32,8 +31,8 @@ destroy :: proc(ctx: ^Context) -> bool { } @(require_results) -frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame { - return _frames(ctx, skip, allocator) +frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { + return _frames(ctx, skip, frames_buffer) } @(require_results) diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_linux.odin index c65a165f9..211c379ca 100644 --- a/core/debug/trace/trace_linux.odin +++ b/core/debug/trace/trace_linux.odin @@ -2,6 +2,7 @@ //+build linux package debug_trace +import "base:intrinsics" import "base:runtime" import "core:strings" import "core:fmt" @@ -92,17 +93,16 @@ _destroy :: proc(ctx: ^Context) -> bool { } @(private="package") -_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (frames: []Frame) { +_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) { Backtrace_Context :: struct { ctx: ^Context, - rt_ctx: runtime.Context, - frames: [MAX_FRAMES]Frame, + frames: []Frame, frame_count: int, } btc := &Backtrace_Context{ ctx = ctx, - rt_ctx = context, + frames = frames_buffer, } backtrace_simple( ctx.impl.state, @@ -113,6 +113,9 @@ _frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (fra if address == 0 { return 1 } + if btc.frame_count == len(btc.frames) { + return 1 + } btc.frames[btc.frame_count] = address btc.frame_count += 1 return 0 @@ -121,10 +124,8 @@ _frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> (fra btc, ) - res := btc.frames[:btc.frame_count] - if len(res) > 0 { - frames = make([]Frame, btc.frame_count, allocator) - copy(frames, res) + if btc.frame_count > 0 { + frames = btc.frames[:btc.frame_count] } return } diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index c7b4eeaa1..5535c09f5 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -28,16 +28,14 @@ _destroy :: proc "contextless" (ctx: ^Context) -> bool { return true } -_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame { - buffer: [MAX_FRAMES]rawptr - frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, len(buffer), &buffer[0], nil) - frames := make([]Frame, frame_count, allocator) - for &f, i in frames { +_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { + frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, len(frames_buffer), &frames_buffer[0], nil) + for i in 0.. Date: Sun, 28 Apr 2024 13:51:10 +0100 Subject: [PATCH 146/171] Update doc.odin --- core/debug/trace/doc.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/debug/trace/doc.odin b/core/debug/trace/doc.odin index 8c0778558..e65548769 100644 --- a/core/debug/trace/doc.odin +++ b/core/debug/trace/doc.odin @@ -19,10 +19,11 @@ Example: } runtime.print_byte('\n') - ctx := &global_trace_ctx + ctx := &trace_ctx if !trace.in_resolve(ctx) { + buf: [64]trace.Frame runtime.print_string("Debug Trace:\n") - frames := trace.frames(ctx, skip=1, allocator=context.temp_allocator) + frames := trace.frames(ctx, 1, buf[:]) for f, i in frames { fl := trace.resolve(ctx, f, context.temp_allocator) if fl.loc.file_path == "" && fl.loc.line == 0 { From 74d75fb7fb9092e77eabb36cb71771c2c60ebb88 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 13:51:46 +0100 Subject: [PATCH 147/171] Correct types on windows --- core/debug/trace/trace_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index 5535c09f5..de1461e96 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -29,7 +29,7 @@ _destroy :: proc "contextless" (ctx: ^Context) -> bool { } _frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame { - frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, len(frames_buffer), &frames_buffer[0], nil) + frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, u32(len(frames_buffer)), ([^]rawptr)(&frames_buffer[0]), nil) for i in 0.. Date: Sun, 28 Apr 2024 13:52:52 +0100 Subject: [PATCH 148/171] Rename `trace_linux.odin` to `trace_cpp.odin` --- core/debug/trace/{trace_linux.odin => trace_cpp.odin} | 2 +- core/debug/trace/trace_nil.odin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename core/debug/trace/{trace_linux.odin => trace_cpp.odin} (99%) diff --git a/core/debug/trace/trace_linux.odin b/core/debug/trace/trace_cpp.odin similarity index 99% rename from core/debug/trace/trace_linux.odin rename to core/debug/trace/trace_cpp.odin index 211c379ca..894046c45 100644 --- a/core/debug/trace/trace_linux.odin +++ b/core/debug/trace/trace_cpp.odin @@ -1,5 +1,5 @@ //+private file -//+build linux +//+build linux, darwin package debug_trace import "base:intrinsics" diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin index 926bd18cc..40478898b 100644 --- a/core/debug/trace/trace_nil.odin +++ b/core/debug/trace/trace_nil.odin @@ -1,4 +1,4 @@ -//+build !windows !linux +//+build !windows !linux !darwin package debug_trace _Context :: struct { From 30cfdd73b0cf6df7bf04f0f17a91fd6450ba7f73 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Apr 2024 14:45:59 +0100 Subject: [PATCH 149/171] Add extra asserts --- src/checker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/checker.cpp b/src/checker.cpp index ee0ee5713..7e2d88982 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2925,6 +2925,8 @@ gb_internal void init_core_type_info(Checker *c) { return; } Entity *type_info_entity = find_core_entity(c, str_lit("Type_Info")); + GB_ASSERT(type_info_entity != nullptr); + GB_ASSERT(type_info_entity->type != nullptr); t_type_info = type_info_entity->type; t_type_info_ptr = alloc_type_pointer(t_type_info); From 4fea5720a52b726671a1e6f8e9acfe0ee2569972 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 28 Apr 2024 16:05:41 +0200 Subject: [PATCH 150/171] wasm: allow `-default-to-nil-allocator` --- src/build_settings.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3bd362996..8509394ff 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1583,8 +1583,10 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta if (bc->metrics.os == TargetOs_js || bc->metrics.os == TargetOs_wasi) { // TODO(bill): Should these even have a default "heap-like" allocator? } - bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; - bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; + + if (!bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR && !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; + } } } From 0530f86a48a191381e343e1b51a88b0ef2f0e1c9 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 28 Apr 2024 16:09:03 +0200 Subject: [PATCH 151/171] fix: buddy allocator wrong query info pointer --- core/mem/allocators.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index eba79eacf..1d79e09c1 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -1124,7 +1124,7 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Query_Info: info := (^Allocator_Query_Info)(old_memory) if info != nil && info.pointer != nil { - ptr := old_memory + ptr := info.pointer if !(b.head <= ptr && ptr <= b.tail) { return nil, .Invalid_Pointer } From cc5faecceda60ee6ccb1fd574c4fde8890245be0 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 28 Apr 2024 16:10:04 +0200 Subject: [PATCH 152/171] wasm: add the `fprint` procedures to `fmt` This makes the `log` package work on wasm --- core/fmt/fmt_js.odin | 51 ++++++++++++++++++++++++++++++++++++++++++++ core/os/os_js.odin | 9 ++------ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index a0a890a9a..acf218eb5 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,7 +1,9 @@ //+build js package fmt +import "core:bufio" import "core:io" +import "core:os" foreign import "odin_env" @@ -31,6 +33,55 @@ stderr := io.Writer{ data = rawptr(uintptr(2)), } +@(private="file") +fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { + switch fd { + case 1: return stdout + case 2: return stderr + case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc) + } +} + +// fprint formats using the default print settings and writes to fd +fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + w := bufio.writer_to_writer(&b) + return wprint(w, ..args, sep=sep, flush=flush) +} + +// fprintln formats using the default print settings and writes to fd +fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + + w := bufio.writer_to_writer(&b) + return wprintln(w, ..args, sep=sep, flush=flush) +} + +// fprintf formats according to the specified format string and writes to fd +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { + buf: [1024]byte + b: bufio.Writer + defer bufio.writer_flush(&b) + + bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + + w := bufio.writer_to_writer(&b) + return wprintf(w, fmt, ..args, flush=flush, newline=newline) +} + +// fprintfln formats according to the specified format string and writes to fd, followed by a newline. +fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { + return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc) +} + // print formats using the default print settings and writes to stdout print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } // println formats using the default print settings and writes to stdout diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 910cb8155..56b830e36 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -64,13 +64,8 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) unimplemented("core:os procedure not supported on JS target") } - - -// NOTE(bill): Uses startup to initialize it -//stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) -//stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) -//stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) - +stdout: Handle = 1 +stderr: Handle = 2 get_std_handle :: proc "contextless" (h: uint) -> Handle { context = runtime.default_context() From f1c13d6bd8ad7390ba29d5d6de79596b9b53bf24 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:03:11 -0400 Subject: [PATCH 153/171] Fix race condition in `error_va` If the error count exceeded `MAX_ERROR_COLLECTOR_COUNT`, multiple threads could print and exit simultaneously, causing a segfault. This change moves the mutex lock back before the conditional. --- src/error.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.cpp b/src/error.cpp index bbbb98053..1b091f88e 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -376,11 +376,11 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { print_all_errors(); gb_exit(1); } - mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Error); // NOTE(bill): Duplicate error, skip it From a573161abde810bcad561f60079f69d938497ce3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:42:04 -0400 Subject: [PATCH 154/171] Allow `@(init)` procs to be `@(disabled)` --- src/checker.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/checker.cpp b/src/checker.cpp index 7e2d88982..70ca4fc47 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2535,6 +2535,11 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st is_init = false; } + if ((e->flags & EntityFlag_Disabled) != 0) { + warning(e->token, "This @(init) procedure is disabled; you must call it manually"); + is_init = false; + } + if (is_init) { add_dependency_to_set(c, e); array_add(&c->info.init_procedures, e); From 1f5f41711609ade4df5383ae18cdb6723639e54f Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Sun, 28 Apr 2024 21:44:34 +0200 Subject: [PATCH 155/171] fix(linalg/glsl): incorrect quat by vector3 multiplication --- core/math/linalg/glsl/linalg_glsl.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index bda1f1723..363a95887 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -1724,7 +1724,7 @@ quatFromMat4 :: proc "c" (m: mat4) -> (q: quat) { @(require_results) quatMulVec3 :: proc "c" (q: quat, v: vec3) -> vec3 { xyz := vec3{q.x, q.y, q.z} - t := cross(xyz, v) + t := cross(2.0 * xyz, v) return v + q.w*t + cross(xyz, t) } @@ -1832,7 +1832,7 @@ dquatFromDmat4 :: proc "c" (m: dmat4) -> (q: dquat) { @(require_results) dquatMulDvec3 :: proc "c" (q: dquat, v: dvec3) -> dvec3 { xyz := dvec3{q.x, q.y, q.z} - t := cross(xyz, v) + t := cross(2.0 * xyz, v) return v + q.w*t + cross(xyz, t) } From c712de0cd07a6e7c5d34570228a2612326cc2acf Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 17:17:01 -0400 Subject: [PATCH 156/171] Require results for non-buffered `print` procs --- core/fmt/fmt.odin | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 018c66bd3..2209af6e2 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -120,6 +120,7 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist // // Returns: A formatted string. // +@(require_results) aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) @@ -136,6 +137,7 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin // // Returns: A formatted string with a newline character at the end. // +@(require_results) aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) @@ -153,6 +155,7 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str // // Returns: A formatted string. The returned string must be freed accordingly. // +@(require_results) aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) @@ -169,6 +172,7 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newlin // // Returns: A formatted string. The returned string must be freed accordingly. // +@(require_results) aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { return aprintf(fmt, ..args, allocator=allocator, newline=true) } @@ -182,6 +186,7 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s // // Returns: A formatted string. // +@(require_results) tprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) @@ -198,6 +203,7 @@ tprint :: proc(args: ..any, sep := " ") -> string { // // Returns: A formatted string with a newline character at the end. // +@(require_results) tprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) @@ -215,6 +221,7 @@ tprintln :: proc(args: ..any, sep := " ") -> string { // // Returns: A formatted string. // +@(require_results) tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) @@ -231,6 +238,7 @@ tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { // // Returns: A formatted string. // +@(require_results) tprintfln :: proc(fmt: string, args: ..any) -> string { return tprintf(fmt, ..args, newline=true) } @@ -339,6 +347,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // // Returns: A formatted C string // +@(require_results) caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str) @@ -357,6 +366,7 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // // Returns: A formatted C string // +@(require_results) caprintfln :: proc(format: string, args: ..any) -> cstring { return caprintf(format, ..args, newline=true) } @@ -371,6 +381,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // // Returns: A formatted C string // +@(require_results) ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) @@ -389,6 +400,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // // Returns: A formatted C string // +@(require_results) ctprintfln :: proc(format: string, args: ..any) -> cstring { return ctprintf(format, ..args, newline=true) } From 700f9c94bd44dfcf77d7bef3bf7d0222c39e798e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 17:18:46 -0400 Subject: [PATCH 157/171] Combine adjacent `sbprint*`/`to_string` calls The `sbprint*` procs already return a string conversion. --- core/fmt/fmt.odin | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 2209af6e2..867257491 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -124,8 +124,7 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprint(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprint(&str, ..args, sep=sep) } // Creates a formatted string with a newline character at the end // @@ -141,8 +140,7 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprintln(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprintln(&str, ..args, sep=sep) } // Creates a formatted string using a format string and arguments // @@ -159,8 +157,7 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) - sbprintf(&str, fmt, ..args, newline=newline) - return strings.to_string(str) + return sbprintf(&str, fmt, ..args, newline=newline) } // Creates a formatted string using a format string and arguments, followed by a newline. // @@ -190,8 +187,7 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s tprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprint(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprint(&str, ..args, sep=sep) } // Creates a formatted string with a newline character at the end // @@ -207,8 +203,7 @@ tprint :: proc(args: ..any, sep := " ") -> string { tprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprintln(&str, ..args, sep=sep) - return strings.to_string(str) + return sbprintln(&str, ..args, sep=sep) } // Creates a formatted string using a format string and arguments // @@ -225,8 +220,7 @@ tprintln :: proc(args: ..any, sep := " ") -> string { tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, fmt, ..args, newline=newline) - return strings.to_string(str) + return sbprintf(&str, fmt, ..args, newline=newline) } // Creates a formatted string using a format string and arguments, followed by a newline. // From bbebb4ad60996cea544507cf551f7b2a4fa86520 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 28 Apr 2024 17:20:52 -0400 Subject: [PATCH 158/171] Fix unseen `print` call in demo --- examples/demo/demo.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 1f6d337e8..a62c11310 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -2385,7 +2385,7 @@ matrix_type :: proc() { c := a * b #assert(type_of(c) == matrix[2, 2]f32) - fmt.tprintln("c = a * b", c) + fmt.println("c = a * b", c) } { // Matrices support multiplication between matrices and arrays From ae322739b58cdd48224b7a332753ab393cc40405 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 29 Apr 2024 16:59:52 +0200 Subject: [PATCH 159/171] Remove instrinsics and utf16 imports from os/os_js --- core/os/os_js.odin | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 56b830e36..8b61cb7ed 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,9 +1,7 @@ //+build js package os -import "base:intrinsics" import "base:runtime" -import "core:unicode/utf16" is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' From e896efdaebc3d527732bdfa76ff93496f855df11 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:32:46 +0200 Subject: [PATCH 160/171] sys/info: add easy way of getting the MacOS version --- core/sys/info/platform_darwin.odin | 2 ++ core/sys/info/sysinfo.odin | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index b95a48bd0..5c69d5cc0 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -76,6 +76,8 @@ init_os_version :: proc () { os_version.minor = rel.darwin.y os_version.patch = rel.darwin.z + macos_version = transmute(Version)rel.release.version + strings.write_string(&b, rel.os_name) if match == .Exact || match == .Nearest { strings.write_rune(&b, ' ') diff --git a/core/sys/info/sysinfo.odin b/core/sys/info/sysinfo.odin index 69f9f1584..f0262f317 100644 --- a/core/sys/info/sysinfo.odin +++ b/core/sys/info/sysinfo.odin @@ -8,6 +8,9 @@ os_version: OS_Version ram: RAM gpus: []GPU +// Only on MacOS, contains the actual MacOS version, while the `os_version` contains the kernel version. +macos_version: Version + OS_Version_Platform :: enum { Unknown, Windows, @@ -19,12 +22,14 @@ OS_Version_Platform :: enum { NetBSD, } +Version :: struct { + major, minor, patch: int, +} + OS_Version :: struct { platform: OS_Version_Platform, - major: int, - minor: int, - patch: int, + using _: Version, build: [2]int, version: string, @@ -42,4 +47,4 @@ GPU :: struct { vendor_name: string, model_name: string, total_ram: int, -} \ No newline at end of file +} From c58da765625786ac9caa49a7f1335d9251766c02 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:35:28 +0200 Subject: [PATCH 161/171] sys/darwin: fix sysctl and sysctlbyname syscalls --- core/sys/darwin/xnu_system_call_wrappers.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index b69877cc9..7100da4f1 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -337,7 +337,7 @@ syscall_ftruncate :: #force_inline proc "contextless" (fd: c.int, length: off_t) return cast(c.int)intrinsics.syscall(unix_offset_syscall(.ftruncate), uintptr(fd), uintptr(length)) } -syscall_sysctl :: #force_inline proc "contextless" (name: ^c.int, namelen: c.uint, oldp: rawptr, oldlenp: ^i64, newp: ^i8, newlen: i64) -> c.int { +syscall_sysctl :: #force_inline proc "contextless" (name: [^]c.int, namelen: c.size_t, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int { return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctl), uintptr(name), uintptr(namelen), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) } @@ -390,8 +390,8 @@ syscall_adjtime :: #force_inline proc "contextless" (delta: ^timeval, old_delta: return cast(c.int)intrinsics.syscall(unix_offset_syscall(.adjtime), uintptr(delta), uintptr(old_delta)) } -syscall_sysctlbyname :: #force_inline proc "contextless" (name: cstring, oldp: rawptr, oldlenp: ^i64, newp: rawptr, newlen: i64) -> c.int { - return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), transmute(uintptr)name, uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) +syscall_sysctlbyname :: #force_inline proc "contextless" (name: string, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.sysctlbyname), uintptr(raw_data(name)), uintptr(len(name)), uintptr(oldp), uintptr(oldlenp), uintptr(newp), uintptr(newlen)) } syscall_proc_info :: #force_inline proc "contextless" (num: c.int, pid: u32, flavor: c.int, arg: u64, buffer: rawptr, buffer_size: c.int) -> c.int { From cebe6bd982597f49cae5b4cd745e99aff1454ff7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:36:13 +0200 Subject: [PATCH 162/171] sys/unix: add sysctlbyname for darwin --- core/sys/unix/sysctl_darwin.odin | 27 ++++++++++++++++++--------- core/time/tsc_darwin.odin | 21 +++++---------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 76c72f478..6417961e5 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -1,20 +1,29 @@ //+build darwin package unix -import "core:sys/darwin" import "base:intrinsics" +import "core:c" +import "core:sys/darwin" + _ :: darwin -sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { - mib := mib - result_size := i64(size_of(T)) +sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { + result_size := c.size_t(size_of(T)) + res := darwin.syscall_sysctl( + raw_data(mib), len(mib), + val, &result_size, + nil, 0, + ) + return res == 0 +} - res := intrinsics.syscall( - darwin.unix_offset_syscall(.sysctl), - uintptr(raw_data(mib)), uintptr(len(mib)), - uintptr(val), uintptr(&result_size), - uintptr(0), uintptr(0), +sysctlbyname :: proc "contextless" (name: string, val: ^$T) -> (ok: bool) { + result_size := c.size_t(size_of(T)) + res := darwin.syscall_sysctlbyname( + name, + val, &result_size, + nil, 0, ) return res == 0 } diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 6688ae7d8..841c0b692 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,21 +1,10 @@ //+private -//+build darwin package time -import "core:c" +import "core:sys/unix" -foreign import libc "system:System.framework" -foreign libc { - @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- -} - -_get_tsc_frequency :: proc "contextless" () -> (u64, bool) { - tmp_freq : u64 = 0 - tmp_size : i64 = size_of(tmp_freq) - ret := _sysctlbyname("machdep.tsc.frequency", &tmp_freq, &tmp_size, nil, 0) - if ret < 0 { - return 0, false - } - - return tmp_freq, true +_get_tsc_frequency :: proc "contextless" () -> (freq: u64, ok: bool) { + unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return + ok = true + return } From 9e94e9dac1335d48f976d6f540bbac0849757566 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:38:26 +0200 Subject: [PATCH 163/171] sys/info: remove unneccesary build tags --- core/sys/info/platform_darwin.odin | 1 - core/sys/info/platform_freebsd.odin | 1 - core/sys/info/platform_linux.odin | 1 - core/sys/info/platform_openbsd.odin | 1 - core/sys/info/platform_windows.odin | 1 - 5 files changed, 5 deletions(-) diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 5c69d5cc0..b5f6dd68d 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -1,4 +1,3 @@ -// +build darwin package sysinfo import sys "core:sys/unix" diff --git a/core/sys/info/platform_freebsd.odin b/core/sys/info/platform_freebsd.odin index 26b4be7e9..5f953e713 100644 --- a/core/sys/info/platform_freebsd.odin +++ b/core/sys/info/platform_freebsd.odin @@ -1,4 +1,3 @@ -// +build freebsd package sysinfo import sys "core:sys/unix" diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index 89b1204a7..e7588f9ec 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -1,4 +1,3 @@ -// +build linux package sysinfo import "base:intrinsics" diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_openbsd.odin index 772531ceb..fba85aa61 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_openbsd.odin @@ -1,4 +1,3 @@ -// +build openbsd package sysinfo import sys "core:sys/unix" diff --git a/core/sys/info/platform_windows.odin b/core/sys/info/platform_windows.odin index 250f938b1..3f0c055dc 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -1,4 +1,3 @@ -// +build windows package sysinfo import sys "core:sys/windows" From 485afb011c4dabaebacb20b0e15544a1893a4e12 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:39:14 +0200 Subject: [PATCH 164/171] sys/info: improve platform_linux 1. fix the `linux.open` call, passing `{ .RDONLY }` becomes `0x00000001` while `RDONLY` is supposed to be `0x00000000` 2. fix the case where `/etc/os-release` starts with `PRETTY_NAME` `strings.index` was used but was checking `> 0` while `0` is valid 3. remove unneccesary temporary allocations 4. simplify the logic --- core/sys/info/platform_linux.odin | 102 +++++++++++++++++------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index e7588f9ec..3933d9f5c 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -1,10 +1,9 @@ package sysinfo import "base:intrinsics" -import "base:runtime" -import "core:strings" -import "core:strconv" +import "core:strconv" +import "core:strings" import "core:sys/linux" @(private) @@ -13,32 +12,37 @@ version_string_buf: [1024]u8 @(init, private) init_os_version :: proc () { os_version.platform = .Linux - // Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` - fd, errno := linux.open("/etc/os-release", {.RDONLY}, {}) - assert(errno == .NONE, "Failed to read /etc/os-release") - defer { - cerrno := linux.close(fd) - assert(cerrno == .NONE, "Failed to close the file descriptor") - } - os_release_buf: [2048]u8 - n, read_errno := linux.read(fd, os_release_buf[:]) - assert(read_errno == .NONE, "Failed to read data from /etc/os-release") - release := string(os_release_buf[:n]) - // Search the line in the file until we find "PRETTY_NAME=" - NEEDLE :: "PRETTY_NAME=\"" - pretty_start := strings.index(release, NEEDLE) + b := strings.builder_from_bytes(version_string_buf[:]) - if pretty_start > 0 { - for r, i in release[pretty_start + len(NEEDLE):] { - if r == '"' { - strings.write_string(&b, release[pretty_start + len(NEEDLE):][:i]) - break - } else if r == '\r' || r == '\n' { - strings.write_string(&b, "Unknown Linux Distro") - break + + // Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` + { + fd, errno := linux.open("/etc/os-release", {}) + assert(errno == .NONE, "Failed to read /etc/os-release") + defer { + cerrno := linux.close(fd) + assert(cerrno == .NONE, "Failed to close the file descriptor") + } + + os_release_buf: [2048]u8 + n, read_errno := linux.read(fd, os_release_buf[:]) + assert(read_errno == .NONE, "Failed to read data from /etc/os-release") + release := string(os_release_buf[:n]) + + // Search the line in the file until we find "PRETTY_NAME=" + NEEDLE :: "PRETTY_NAME=\"" + _, _, post := strings.partition(release, NEEDLE) + if len(post) > 0 { + end := strings.index_any(post, "\"\n") + if end > -1 && post[end] == '"' { + strings.write_string(&b, post[:end]) } } + if strings.builder_len(b) == 0 { + strings.write_string(&b, "Unknown Linux Distro") + } } + // Grab kernel info using `uname()` syscall, https://linux.die.net/man/2/uname uts: linux.UTS_Name uname_errno := linux.uname(&uts) @@ -47,28 +51,36 @@ init_os_version :: proc () { strings.write_string(&b, ", ") strings.write_string(&b, string(cstring(&uts.sysname[0]))) strings.write_rune(&b, ' ') - l := strings.builder_len(b) + + release_i := strings.builder_len(b) strings.write_string(&b, string(cstring(&uts.release[0]))) - // Parse kernel version, as substrings of the version info in `version_string_buf` - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - version_bits := strings.split_n(strings.to_string(b)[l:], "-", 2, context.temp_allocator) - if len(version_bits) > 1 { - os_version.version = version_bits[1] - } - // Parse major, minor, patch from release info - triplet := strings.split(version_bits[0], ".", context.temp_allocator) - if len(triplet) == 3 { - major, major_ok := strconv.parse_int(triplet[0]) - minor, minor_ok := strconv.parse_int(triplet[1]) - patch, patch_ok := strconv.parse_int(triplet[2]) - if major_ok && minor_ok && patch_ok { - os_version.major = major - os_version.minor = minor - os_version.patch = patch + release_str := string(b.buf[release_i:]) + + os_version.as_string = strings.to_string(b) + + // Parse the Linux version out of the release string + { + version_num, _, version_suffix := strings.partition(release_str, "-") + os_version.version = version_suffix + + i: int + for part in strings.split_iterator(&version_num, ".") { + defer i += 1 + + dst: ^int + switch i { + case 0: dst = &os_version.major + case 1: dst = &os_version.minor + case 2: dst = &os_version.patch + case: break + } + + num, ok := strconv.parse_int(part) + if !ok { break } + + dst^ = num } } - // Finish the string - os_version.as_string = strings.to_string(b) } @(init) @@ -83,4 +95,4 @@ init_ram :: proc() { total_swap = int(sys_info.totalswap) * int(sys_info.mem_unit), free_swap = int(sys_info.freeswap) * int(sys_info.mem_unit), } -} \ No newline at end of file +} From 8660718ebe1b53af6c6ef13f1dae5689234454ed Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:43:46 +0200 Subject: [PATCH 165/171] sys/info: add feature detection for Darwin and Linux ARM --- core/sys/info/cpu_arm.odin | 57 ++++++++++++----- core/sys/info/cpu_darwin_arm64.odin | 98 +++++++++++++++++++++++++++++ core/sys/info/cpu_linux_arm.odin | 65 +++++++++++++++++++ 3 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 core/sys/info/cpu_darwin_arm64.odin create mode 100644 core/sys/info/cpu_linux_arm.odin diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index f66f0e780..10c37ceef 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,26 +1,55 @@ //+build arm32, arm64 package sysinfo -// TODO: Set up an enum with the ARM equivalent of the above. -CPU_Feature :: enum u64 {} +CPU_Feature :: enum u64 { + // Advanced SIMD & floating-point capabilities: + asimd, // General support for Advanced SIMD instructions/neon. + floatingpoint, // General support for floating-point instructions. + asimdhp, // Advanced SIMD half-precision conversion instructions. + bf16, // Storage and arithmetic instructions of the Brain Floating Point (BFloat16) data type. + fcma, // Floating-point complex number instructions. + fhm, // Floating-point half-precision multiplication instructions. + fp16, // General half-precision floating-point data processing instructions. + frint, // Floating-point to integral valued floating-point number rounding instructions. + i8mm, // Advanced SIMD int8 matrix multiplication instructions. + jscvt, // JavaScript conversion instruction. + rdm, // Advanced SIMD rounding double multiply accumulate instructions. -cpu_features: Maybe(CPU_Feature) -cpu_name: Maybe(string) + flagm, // Condition flag manipulation instructions. + flagm2, // Enhancements to condition flag manipulation instructions. + crc32, // CRC32 instructions. -@(init, private) -init_cpu_features :: proc "c" () { + lse, // Atomic instructions to support large systems. + lse2, // Changes to single-copy atomicity and alignment requirements for loads and stores for large systems. + lrcpc, // Load-acquire Release Consistency processor consistent (RCpc) instructions. + lrcpc2, // Load-acquire Release Consistency processor consistent (RCpc) instructions version 2. + + aes, + pmull, + sha1, + sha256, + sha512, + sha3, + + sb, // Barrier instruction to control speculation. + ssbs, // Instructions to control speculation of loads and stores. } +CPU_Features :: distinct bit_set[CPU_Feature; u64] + +cpu_features: Maybe(CPU_Features) +cpu_name: Maybe(string) + @(private) -_cpu_name_buf: [72]u8 +cpu_name_buf: [128]byte @(init, private) -init_cpu_name :: proc "c" () { - when ODIN_ARCH == .arm32 { - copy(_cpu_name_buf[:], "ARM") - cpu_name = string(_cpu_name_buf[:3]) +init_cpu_name :: proc "contextless" () { + when ODIN_ARCH == .arm64 { + copy(cpu_name_buf[:], "ARM64") + cpu_name = string(cpu_name_buf[:len("ARM64")]) } else { - copy(_cpu_name_buf[:], "ARM64") - cpu_name = string(_cpu_name_buf[:5]) + copy(cpu_name_buf[:], "ARM") + cpu_name = string(cpu_name_buf[:len("ARM")]) } -} \ No newline at end of file +} diff --git a/core/sys/info/cpu_darwin_arm64.odin b/core/sys/info/cpu_darwin_arm64.odin new file mode 100644 index 000000000..336334bc0 --- /dev/null +++ b/core/sys/info/cpu_darwin_arm64.odin @@ -0,0 +1,98 @@ +package sysinfo + +import "core:sys/unix" + +@(init, private) +init_cpu_features :: proc "contextless" () { + @(static) features: CPU_Features + defer cpu_features = features + + try_set :: proc "contextless" (name: string, feature: CPU_Feature) -> (ok: bool) { + support: b32 + if ok = unix.sysctlbyname(name, &support); ok && support { + features += { feature } + } + return + } + + // Docs from Apple: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics + // Features from there that do not have (or I didn't find) an equivalent on Linux are commented out below. + + // Advanced SIMD & floating-point capabilities: + { + if !try_set("hw.optional.AdvSIMD", .asimd) { + try_set("hw.optional.neon", .asimd) + } + + try_set("hw.optional.floatingpoint", .floatingpoint) + + if !try_set("hw.optional.AdvSIMD_HPFPCvt", .asimdhp) { + try_set("hw.optional.neon_hpfp", .asimdhp) + } + + try_set("hw.optional.arm.FEAT_BF16", .bf16) + // try_set("hw.optional.arm.FEAT_DotProd", .dotprod) + + if !try_set("hw.optional.arm.FEAT_FCMA", .fcma) { + try_set("hw.optional.armv8_3_compnum", .fcma) + } + + if !try_set("hw.optional.arm.FEAT_FHM", .fhm) { + try_set("hw.optional.armv8_2_fhm", .fhm) + } + + if !try_set("hw.optional.arm.FEAT_FP16", .fp16) { + try_set("hw.optional.neon_fp16", .fp16) + } + + try_set("hw.optional.arm.FEAT_FRINTTS", .frint) + try_set("hw.optional.arm.FEAT_I8MM", .i8mm) + try_set("hw.optional.arm.FEAT_JSCVT", .jscvt) + try_set("hw.optional.arm.FEAT_RDM", .rdm) + } + + // Integer capabilities: + { + try_set("hw.optional.arm.FEAT_FlagM", .flagm) + try_set("hw.optional.arm.FEAT_FlagM2", .flagm2) + try_set("hw.optional.armv8_crc32", .crc32) + } + + // Atomic and memory ordering instruction capabilities: + { + try_set("hw.optional.arm.FEAT_LRCPC", .lrcpc) + try_set("hw.optional.arm.FEAT_LRCPC2", .lrcpc2) + + if !try_set("hw.optional.arm.FEAT_LSE", .lse) { + try_set("hw.optional.armv8_1_atomics", .lse) + } + + // try_set("hw.optional.arm.FEAT_LSE2", .lse2) + } + + // Encryption capabilities: + { + try_set("hw.optional.arm.FEAT_AES", .aes) + try_set("hw.optional.arm.FEAT_PMULL", .pmull) + try_set("hw.optional.arm.FEAT_SHA1", .sha1) + try_set("hw.optional.arm.FEAT_SHA256", .sha256) + + if !try_set("hw.optional.arm.FEAT_SHA512", .sha512) { + try_set("hw.optional.armv8_2_sha512", .sha512) + } + + if !try_set("hw.optional.arm.FEAT_SHA3", .sha3) { + try_set("hw.optional.armv8_2_sha3", .sha3) + } + } + + // General capabilities: + { + // try_set("hw.optional.arm.FEAT_BTI", .bti) + // try_set("hw.optional.arm.FEAT_DPB", .dpb) + // try_set("hw.optional.arm.FEAT_DPB2", .dpb2) + // try_set("hw.optional.arm.FEAT_ECV", .ecv) + try_set("hw.optional.arm.FEAT_SB", .sb) + try_set("hw.optional.arm.FEAT_SSBS", .ssbs) + } +} diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin new file mode 100644 index 000000000..dcc252971 --- /dev/null +++ b/core/sys/info/cpu_linux_arm.odin @@ -0,0 +1,65 @@ +//+build arm32, arm64 +//+build linux +package sysinfo + +import "core:sys/linux" +import "core:strings" + +@(init, private) +init_cpu_features :: proc() { + fd, err := linux.open("/proc/cpuinfo", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + features: CPU_Features + defer cpu_features = features + + str := string(buf[:n]) + for line in strings.split_lines_iterator(&str) { + key, _, value := strings.partition(line, ":") + key = strings.trim_space(key) + value = strings.trim_space(value) + + if key != "Features" { continue } + + for feature in strings.split_by_byte_iterator(&value, ' ') { + switch feature { + case "asimd", "neon": features += { .asimd } + case "fp": features += { .floatingpoint } + case "asimdhp": features += { .asimdhp } + case "asimdbf16": features += { .bf16 } + case "fcma": features += { .fcma } + case "asimdfhm": features += { .fhm } + case "fphp", "half": features += { .fp16 } + case "frint": features += { .frint } + case "i8mm": features += { .i8mm } + case "jscvt": features += { .jscvt } + case "asimdrdm": features += { .rdm } + + case "flagm": features += { .flagm } + case "flagm2": features += { .flagm2 } + case "crc32": features += { .crc32 } + + case "atomics": features += { .lse } + case "lrcpc": features += { .lrcpc } + case "ilrcpc": features += { .lrcpc2 } + + case "aes": features += { .aes } + case "pmull": features += { .pmull } + case "sha1": features += { .sha1 } + case "sha2": features += { .sha256 } + case "sha3": features += { .sha3 } + case "sha512": features += { .sha512 } + + case "sb": features += { .sb } + case "ssbs": features += { .ssbs } + } + } + break + } +} From d40c207fdee3d975f708a146d9b06d3715445c67 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 29 Apr 2024 23:44:48 +0200 Subject: [PATCH 166/171] sys/info: retrieve better CPU description on Darwin Previously either `ARM` or `ARM64`, now you get something like `Apple M1` --- core/sys/info/cpu_arm.odin | 27 +++++++++++++++++++++------ src/bug_report.cpp | 27 ++++++++++++++++++++------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index 10c37ceef..aa4bb368a 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,6 +1,10 @@ //+build arm32, arm64 package sysinfo +import "core:sys/unix" + +_ :: unix + CPU_Feature :: enum u64 { // Advanced SIMD & floating-point capabilities: asimd, // General support for Advanced SIMD instructions/neon. @@ -45,11 +49,22 @@ cpu_name_buf: [128]byte @(init, private) init_cpu_name :: proc "contextless" () { - when ODIN_ARCH == .arm64 { - copy(cpu_name_buf[:], "ARM64") - cpu_name = string(cpu_name_buf[:len("ARM64")]) - } else { - copy(cpu_name_buf[:], "ARM") - cpu_name = string(cpu_name_buf[:len("ARM")]) + generic := true + + when ODIN_OS == .Darwin { + if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) { + cpu_name = string(cstring(rawptr(&cpu_name_buf))) + generic = false + } + } + + if generic { + when ODIN_ARCH == .arm64 { + copy(cpu_name_buf[:], "ARM64") + cpu_name = string(cpu_name_buf[:len("ARM64")]) + } else { + copy(cpu_name_buf[:], "ARM") + cpu_name = string(cpu_name_buf[:len("ARM")]) + } } } diff --git a/src/bug_report.cpp b/src/bug_report.cpp index e919ba67b..88ab9492c 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -204,14 +204,27 @@ gb_internal void report_cpu_info() { } #elif defined(GB_CPU_ARM) - /* - TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`. - */ - #if defined(GB_ARCH_64_BIT) - gb_printf("ARM64\n"); - #else - gb_printf("ARM\n"); + bool generic = true; + + #if defined(GB_SYSTEM_OSX) + char cpu_name[128] = {}; + size_t cpu_name_size = 128; + if (sysctlbyname("machdep.cpu.brand_string", &cpu_name, &cpu_name_size, nullptr, 0) == 0) { + generic = false; + gb_printf("%s\n", (char *)&cpu_name[0]); + } #endif + + if (generic) { + /* + TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`. + */ + #if defined(GB_ARCH_64_BIT) + gb_printf("ARM64\n"); + #else + gb_printf("ARM\n"); + #endif + } #else gb_printf("Unknown\n"); #endif From b41395e3b48c7953f271bfc552d210be8e8aeddf Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 30 Apr 2024 00:13:36 +0200 Subject: [PATCH 167/171] sys/info: update doc.odin --- core/sys/info/doc.odin | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 15af0d4b3..802cd9c60 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -19,18 +19,21 @@ Example: import si "core:sys/info" main :: proc() { - fmt.printf("Odin: %v\n", ODIN_VERSION) - fmt.printf("OS: %v\n", si.os_version.as_string) - fmt.printf("OS: %#v\n", si.os_version) - fmt.printf("CPU: %v\n", si.cpu_name) - fmt.printf("RAM: %v MiB\n", si.ram.total_ram / 1024 / 1024) + fmt.printfln("Odin: %v", ODIN_VERSION) + fmt.printfln("OS: %v", si.os_version.as_string) + fmt.printfln("OS: %#v", si.os_version) + fmt.printfln("CPU: %v", si.cpu_name) + fmt.printfln("RAM: %#.1M", si.ram.total_ram) + + // fmt.printfln("Features: %v", si.cpu_features) + // fmt.printfln("MacOS version: %v", si.macos_version) fmt.println() for gpu, i in si.gpus { - fmt.printf("GPU #%v:\n", i) - fmt.printf("\tVendor: %v\n", gpu.vendor_name) - fmt.printf("\tModel: %v\n", gpu.model_name) - fmt.printf("\tVRAM: %v MiB\n", gpu.total_ram / 1024 / 1024) + fmt.printfln("GPU #%v:", i) + fmt.printfln("\tVendor: %v", gpu.vendor_name) + fmt.printfln("\tModel: %v", gpu.model_name) + fmt.printfln("\tVRAM: %#.1M", gpu.total_ram) } } @@ -51,11 +54,11 @@ Example: as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", } CPU: AMD Ryzen 7 1800X Eight-Core Processor - RAM: 65469 MiB + RAM: 64.0 GiB GPU #0: Vendor: Advanced Micro Devices, Inc. Model: Radeon RX Vega - VRAM: 8176 MiB + VRAM: 8.0 GiB - Example macOS output: @@ -73,6 +76,6 @@ Example: as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)", } CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz - RAM: 8192 MiB + RAM: 8.0 GiB */ package sysinfo From c0ca26ac1776e197d6909ed0826ff5e6bf8ca87d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 30 Apr 2024 00:22:23 +0200 Subject: [PATCH 168/171] sys/info: add missing `@(private)`'s --- core/sys/info/platform_darwin.odin | 2 +- core/sys/info/platform_freebsd.odin | 2 +- core/sys/info/platform_linux.odin | 2 +- core/sys/info/platform_openbsd.odin | 2 +- core/sys/info/platform_windows.odin | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index b5f6dd68d..122dd42ee 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -114,7 +114,7 @@ init_os_version :: proc () { os_version.as_string = strings.to_string(b) } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` diff --git a/core/sys/info/platform_freebsd.odin b/core/sys/info/platform_freebsd.odin index 5f953e713..c1429c4b2 100644 --- a/core/sys/info/platform_freebsd.odin +++ b/core/sys/info/platform_freebsd.odin @@ -67,7 +67,7 @@ init_os_version :: proc () { } } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM} diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index 3933d9f5c..45efc3329 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -83,7 +83,7 @@ init_os_version :: proc () { } } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysinfo` sys_info: linux.Sys_Info diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_openbsd.odin index fba85aa61..3a7c07570 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_openbsd.odin @@ -60,7 +60,7 @@ init_os_version :: proc () { os_version.as_string = strings.to_string(b) } -@(init) +@(init, private) init_ram :: proc() { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64} diff --git a/core/sys/info/platform_windows.odin b/core/sys/info/platform_windows.odin index 3f0c055dc..4c00ddadf 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -258,7 +258,7 @@ init_os_version :: proc () { } } -@(init) +@(init, private) init_ram :: proc() { state: sys.MEMORYSTATUSEX From 5c1201fa422639f0c70bb772b29f3cfb4c0e3d04 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Apr 2024 09:10:00 +0100 Subject: [PATCH 169/171] Fix #3459 --- base/runtime/internal.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 6f0445787..3e9c524bd 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1043,8 +1043,8 @@ __write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: ui for i in 0..>3]) & (1<<(i&7)) != 0) - b := the_bit<<(j&7) - dst[j>>3] = (dst[j>>3] &~ b) | b + dst[j>>3] &~= 1<<(j&7) + dst[j>>3] |= the_bit<<(j&7) } } @@ -1052,7 +1052,7 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin for j in 0..>3]) & (1<<(i&7)) != 0) - b := the_bit<<(j&7) - dst[j>>3] = (dst[j>>3] &~ b) | b + dst[j>>3] &~= 1<<(j&7) + dst[j>>3] |= the_bit<<(j&7) } } \ No newline at end of file From 9ffa4a4eb157c12340c1269e703683fd313551fa Mon Sep 17 00:00:00 2001 From: flysand7 Date: Wed, 1 May 2024 05:24:02 +1100 Subject: [PATCH 170/171] [sys/linux]: Fix bit numbers for open flags --- bug | Bin 0 -> 380104 bytes core/sys/linux/bits.odin | 39 +++++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 bug diff --git a/bug b/bug new file mode 100644 index 0000000000000000000000000000000000000000..f84127c550766cdfdd0807030de32a404c78ca8f GIT binary patch literal 380104 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

i3;IRUV+}vV@qSWHjoDxuc6lEr+WF|8t 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 mLEO&>JKz;60kap%1>f@r^E*roM87-?(GR Date: Tue, 30 Apr 2024 20:58:36 +0200 Subject: [PATCH 171/171] Remove stray binary. --- bug | Bin 380104 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bug diff --git a/bug b/bug deleted file mode 100644 index f84127c550766cdfdd0807030de32a404c78ca8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 380104 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

i3;IRUV+}vV@qSWHjoDxuc6lEr+WF|8t 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 mLEO&>JKz;60kap%1>f@r^E*roM87-?(GR