From aff8f06e3cb1bdb9c3ffee98a68675c843df78fd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 7 Jan 2024 19:56:00 +0000 Subject: Add frontend stuff instrumentation tooling //+no-instrumentation @(no_instrumentation) @(instrumentation_enter) @(instrumentation_exit) --- src/parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index c0498b425..2671054df 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5919,7 +5919,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { f->vet_flags = parse_vet_tag(tok, lc); f->vet_flags_set = true; } else if (string_starts_with(lc, str_lit("+ignore"))) { - return false; + return false; } else if (string_starts_with(lc, str_lit("+private"))) { f->flags |= AstFile_IsPrivatePkg; String command = string_trim_starts_with(lc, str_lit("+private ")); @@ -5941,6 +5941,8 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } else { f->flags |= AstFile_IsLazy; } + } else if (lc == "+no-instrumentation") { + f->flags |= AstFile_NoInstrumentation; } else { warning(tok, "Ignoring unknown tag '%.*s'", LIT(lc)); } -- cgit v1.2.3 From 68df35b378d59f9813f5af81e61080c5f1b20e23 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 17:33:29 +0000 Subject: Add `#field_align(N)` It sets the minimum alignment for the fields within a struct. This cannot be used with `#packed`, but can be used with `#align(N)`. If `#align(N)` is less than `#field_align(N)`, then a warning will be printed. --- core/odin/ast/ast.odin | 1 + core/odin/parser/parser.odin | 7 +++++++ src/check_type.cpp | 40 +++++++++++++++++++++++++--------------- src/parser.cpp | 25 ++++++++++++++++++++----- src/parser.hpp | 1 + src/types.cpp | 14 ++++++++++---- 6 files changed, 64 insertions(+), 24 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 67a26d6f2..f6bcbab4e 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -768,6 +768,7 @@ Struct_Type :: struct { tok_pos: tokenizer.Pos, poly_params: ^Field_List, align: ^Expr, + field_align: ^Expr, where_token: tokenizer.Token, where_clauses: []^Expr, is_packed: bool, diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 3383f3514..fc7a2c792 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2547,6 +2547,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { poly_params: ^ast.Field_List align: ^ast.Expr + field_align: ^ast.Expr is_packed: bool is_raw_union: bool is_no_copy: bool @@ -2578,6 +2579,11 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) } align = parse_expr(p, true) + case "field_align": + if field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + field_align = parse_expr(p, true) case "raw_union": if is_raw_union { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) @@ -2620,6 +2626,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { st := ast.new(ast.Struct_Type, tok.pos, end_pos(close)) st.poly_params = poly_params st.align = align + st.field_align = field_align st.is_packed = is_packed st.is_raw_union = is_raw_union st.is_no_copy = is_no_copy diff --git a/src/check_type.cpp b/src/check_type.cpp index a95026711..5cb1eb9cc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -219,13 +219,13 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slice 1) { gbAllocator a = heap_allocator(); String str = big_int_to_string(a, &v); - error(node, "#align too large, %.*s", LIT(str)); + error(node, "#%s too large, %.*s", msg, LIT(str)); gb_free(a, str.text); return false; } i64 align = big_int_to_i64(&v); if (align < 1 || !gb_is_power_of_two(cast(isize)align)) { - error(node, "#align must be a power of 2, got %lld", align); + error(node, "#%s must be a power of 2, got %lld", msg, align); return false; } *align_ = align; @@ -251,7 +251,7 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_) } } - error(node, "#align must be an integer"); + error(node, "#%s must be an integer", msg); return false; } @@ -645,16 +645,26 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); } - if (st->align != nullptr) { - if (st->is_packed) { - syntax_error(st->align, "'#align' cannot be applied with '#packed'"); - return; - } - i64 custom_align = 1; - if (check_custom_align(ctx, st->align, &custom_align)) { - struct_type->Struct.custom_align = custom_align; - } +#define ST_ALIGN(_name) if (st->_name != nullptr) { \ + if (st->is_packed) { \ + syntax_error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ + return; \ + } \ + i64 align = 1; \ + if (check_custom_align(ctx, st->_name, &align, #_name)) { \ + struct_type->Struct.custom_##_name = align; \ + } \ } + + ST_ALIGN(field_align); + ST_ALIGN(align); + if (struct_type->Struct.custom_align < struct_type->Struct.custom_field_align) { + warning(st->align, "#align(%lld) is defined to be less than #field_name(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_field_align); + } + +#undef ST_ALIGN } gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { GB_ASSERT(is_type_union(union_type)); @@ -746,7 +756,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no if (ut->align != nullptr) { i64 custom_align = 1; - if (check_custom_align(ctx, ut->align, &custom_align)) { + if (check_custom_align(ctx, ut->align, &custom_align, "align")) { if (variants.count == 0) { error(ut->align, "An empty union cannot have a custom alignment"); } else { diff --git a/src/parser.cpp b/src/parser.cpp index 2671054df..b16a88de5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -383,10 +383,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem, f); break; case Ast_StructType: - n->StructType.fields = clone_ast_array(n->StructType.fields, f); + n->StructType.fields = clone_ast_array(n->StructType.fields, f); n->StructType.polymorphic_params = clone_ast(n->StructType.polymorphic_params, f); - n->StructType.align = clone_ast(n->StructType.align, f); - n->StructType.where_clauses = clone_ast_array(n->StructType.where_clauses, f); + n->StructType.align = clone_ast(n->StructType.align, f); + n->StructType.field_align = clone_ast(n->StructType.field_align, f); + n->StructType.where_clauses = clone_ast_array(n->StructType.where_clauses, f); break; case Ast_UnionType: n->UnionType.variants = clone_ast_array(n->UnionType.variants, f); @@ -1125,7 +1126,7 @@ gb_internal Ast *ast_dynamic_array_type(AstFile *f, Token token, Ast *elem) { gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice fields, isize field_count, Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_no_copy, - Ast *align, + Ast *align, Ast *field_align, Token where_token, Array const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_StructType); result->StructType.token = token; @@ -1136,6 +1137,7 @@ gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice fields, i result->StructType.is_raw_union = is_raw_union; result->StructType.is_no_copy = is_no_copy; result->StructType.align = align; + result->StructType.field_align = field_align; result->StructType.where_token = where_token; result->StructType.where_clauses = slice_from_array(where_clauses); return result; @@ -2507,6 +2509,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { bool is_raw_union = false; bool no_copy = false; Ast *align = nullptr; + Ast *field_align = nullptr; if (allow_token(f, Token_OpenParen)) { isize param_count = 0; @@ -2543,6 +2546,18 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { error_line("\tSuggestion: #align(%s)", s); gb_string_free(s); } + } else if (tag.string == "field_align") { + if (field_align) { + syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); + } + field_align = parse_expr(f, true); + if (field_align && field_align->kind != Ast_ParenExpr) { + ERROR_BLOCK(); + gbString s = expr_to_string(field_align); + syntax_warning(tag, "#field_align requires parentheses around the expression"); + error_line("\tSuggestion: #field_align(%s)", s); + gb_string_free(s); + } } else if (tag.string == "raw_union") { if (is_raw_union) { syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); @@ -2591,7 +2606,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { decls = fields->FieldList.list; } - return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, where_token, where_clauses); + return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, field_align, where_token, where_clauses); } break; case Token_union: { diff --git a/src/parser.hpp b/src/parser.hpp index cc1836ef3..1edb1f9dd 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -713,6 +713,7 @@ AST_KIND(_TypeBegin, "", bool) \ isize field_count; \ Ast *polymorphic_params; \ Ast *align; \ + Ast *field_align; \ Token where_token; \ Slice where_clauses; \ bool is_packed; \ diff --git a/src/types.cpp b/src/types.cpp index 2f39d5caa..b99d469e4 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -137,6 +137,7 @@ struct TypeStruct { Scope * scope; i64 custom_align; + i64 custom_field_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; @@ -3668,10 +3669,15 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align); } -gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_packed, bool is_raw_union) { +gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_packed, bool is_raw_union, i64 min_field_align) { gbAllocator a = permanent_allocator(); auto offsets = gb_alloc_array(a, i64, fields.count); i64 curr_offset = 0; + + if (min_field_align == 0) { + min_field_align = 1; + } + if (is_raw_union) { for_array(i, fields) { offsets[i] = 0; @@ -3692,7 +3698,7 @@ gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_pack offsets[i] = -1; } else { Type *t = fields[i]->type; - i64 align = gb_max(type_align_of(t), 1); + i64 align = gb_max(type_align_of(t), min_field_align); i64 size = gb_max(type_size_of( t), 0); curr_offset = align_formula(curr_offset, align); offsets[i] = curr_offset; @@ -3709,7 +3715,7 @@ gb_internal bool type_set_offsets(Type *t) { MUTEX_GUARD(&t->Struct.offset_mutex); if (!t->Struct.are_offsets_set) { t->Struct.are_offsets_being_processed = true; - t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union); + t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union, t->Struct.custom_field_align); t->Struct.are_offsets_being_processed = false; t->Struct.are_offsets_set = true; return true; @@ -3718,7 +3724,7 @@ gb_internal bool type_set_offsets(Type *t) { MUTEX_GUARD(&t->Tuple.mutex); if (!t->Tuple.are_offsets_set) { t->Tuple.are_offsets_being_processed = true; - t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false); + t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false, 1); t->Tuple.are_offsets_being_processed = false; t->Tuple.are_offsets_set = true; return true; -- cgit v1.2.3 From 09fa1c29cd014b4560b3c79c72db68af20ef8187 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 21:05:53 +0000 Subject: Move `core:runtime` to `base:runtime`; keep alias around --- base/runtime/core.odin | 681 +++++++++++++++ base/runtime/core_builtin.odin | 915 ++++++++++++++++++++ base/runtime/core_builtin_matrix.odin | 274 ++++++ base/runtime/core_builtin_soa.odin | 428 ++++++++++ base/runtime/default_allocators_arena.odin | 304 +++++++ base/runtime/default_allocators_general.odin | 23 + base/runtime/default_allocators_js.odin | 5 + base/runtime/default_allocators_nil.odin | 88 ++ base/runtime/default_allocators_wasi.odin | 5 + base/runtime/default_allocators_windows.odin | 44 + base/runtime/default_temporary_allocator.odin | 79 ++ base/runtime/docs.odin | 179 ++++ base/runtime/dynamic_array_internal.odin | 138 +++ base/runtime/dynamic_map_internal.odin | 924 ++++++++++++++++++++ base/runtime/entry_unix.odin | 59 ++ base/runtime/entry_unix_no_crt_amd64.asm | 43 + base/runtime/entry_unix_no_crt_darwin_arm64.asm | 20 + base/runtime/entry_unix_no_crt_i386.asm | 18 + base/runtime/entry_wasm.odin | 20 + base/runtime/entry_windows.odin | 50 ++ base/runtime/error_checks.odin | 292 +++++++ base/runtime/internal.odin | 1036 +++++++++++++++++++++++ base/runtime/os_specific.odin | 7 + base/runtime/os_specific_any.odin | 16 + base/runtime/os_specific_darwin.odin | 12 + base/runtime/os_specific_freestanding.odin | 7 + base/runtime/os_specific_js.odin | 12 + base/runtime/os_specific_wasi.odin | 10 + base/runtime/os_specific_windows.odin | 135 +++ base/runtime/print.odin | 489 +++++++++++ base/runtime/procs.odin | 95 +++ base/runtime/procs_darwin.odin | 21 + base/runtime/procs_js.odin | 15 + base/runtime/procs_wasm.odin | 40 + base/runtime/procs_windows_amd64.asm | 79 ++ base/runtime/procs_windows_amd64.odin | 26 + base/runtime/procs_windows_i386.odin | 29 + base/runtime/udivmod128.odin | 156 ++++ core/runtime/core.odin | 681 --------------- core/runtime/core_builtin.odin | 915 -------------------- core/runtime/core_builtin_matrix.odin | 274 ------ core/runtime/core_builtin_soa.odin | 428 ---------- core/runtime/default_allocators_arena.odin | 304 ------- core/runtime/default_allocators_general.odin | 23 - core/runtime/default_allocators_js.odin | 5 - core/runtime/default_allocators_nil.odin | 88 -- core/runtime/default_allocators_wasi.odin | 5 - core/runtime/default_allocators_windows.odin | 44 - core/runtime/default_temporary_allocator.odin | 79 -- core/runtime/docs.odin | 179 ---- core/runtime/dynamic_array_internal.odin | 138 --- core/runtime/dynamic_map_internal.odin | 924 -------------------- core/runtime/entry_unix.odin | 59 -- core/runtime/entry_unix_no_crt_amd64.asm | 43 - core/runtime/entry_unix_no_crt_darwin_arm64.asm | 20 - core/runtime/entry_unix_no_crt_i386.asm | 18 - core/runtime/entry_wasm.odin | 20 - core/runtime/entry_windows.odin | 50 -- core/runtime/error_checks.odin | 292 ------- core/runtime/internal.odin | 1036 ----------------------- core/runtime/os_specific.odin | 7 - core/runtime/os_specific_any.odin | 16 - core/runtime/os_specific_darwin.odin | 12 - core/runtime/os_specific_freestanding.odin | 7 - core/runtime/os_specific_js.odin | 12 - core/runtime/os_specific_wasi.odin | 10 - core/runtime/os_specific_windows.odin | 135 --- core/runtime/print.odin | 489 ----------- core/runtime/procs.odin | 95 --- core/runtime/procs_darwin.odin | 21 - core/runtime/procs_js.odin | 15 - core/runtime/procs_wasm.odin | 40 - core/runtime/procs_windows_amd64.asm | 79 -- core/runtime/procs_windows_amd64.odin | 26 - core/runtime/procs_windows_i386.odin | 29 - core/runtime/udivmod128.odin | 156 ---- src/build_settings.cpp | 22 +- src/checker.cpp | 28 +- src/main.cpp | 1 + src/parser.cpp | 10 +- 80 files changed, 6828 insertions(+), 6781 deletions(-) create mode 100644 base/runtime/core.odin create mode 100644 base/runtime/core_builtin.odin create mode 100644 base/runtime/core_builtin_matrix.odin create mode 100644 base/runtime/core_builtin_soa.odin create mode 100644 base/runtime/default_allocators_arena.odin create mode 100644 base/runtime/default_allocators_general.odin create mode 100644 base/runtime/default_allocators_js.odin create mode 100644 base/runtime/default_allocators_nil.odin create mode 100644 base/runtime/default_allocators_wasi.odin create mode 100644 base/runtime/default_allocators_windows.odin create mode 100644 base/runtime/default_temporary_allocator.odin create mode 100644 base/runtime/docs.odin create mode 100644 base/runtime/dynamic_array_internal.odin create mode 100644 base/runtime/dynamic_map_internal.odin create mode 100644 base/runtime/entry_unix.odin create mode 100644 base/runtime/entry_unix_no_crt_amd64.asm create mode 100644 base/runtime/entry_unix_no_crt_darwin_arm64.asm create mode 100644 base/runtime/entry_unix_no_crt_i386.asm create mode 100644 base/runtime/entry_wasm.odin create mode 100644 base/runtime/entry_windows.odin create mode 100644 base/runtime/error_checks.odin create mode 100644 base/runtime/internal.odin create mode 100644 base/runtime/os_specific.odin create mode 100644 base/runtime/os_specific_any.odin create mode 100644 base/runtime/os_specific_darwin.odin create mode 100644 base/runtime/os_specific_freestanding.odin create mode 100644 base/runtime/os_specific_js.odin create mode 100644 base/runtime/os_specific_wasi.odin create mode 100644 base/runtime/os_specific_windows.odin create mode 100644 base/runtime/print.odin create mode 100644 base/runtime/procs.odin create mode 100644 base/runtime/procs_darwin.odin create mode 100644 base/runtime/procs_js.odin create mode 100644 base/runtime/procs_wasm.odin create mode 100644 base/runtime/procs_windows_amd64.asm create mode 100644 base/runtime/procs_windows_amd64.odin create mode 100644 base/runtime/procs_windows_i386.odin create mode 100644 base/runtime/udivmod128.odin delete mode 100644 core/runtime/core.odin delete mode 100644 core/runtime/core_builtin.odin delete mode 100644 core/runtime/core_builtin_matrix.odin delete mode 100644 core/runtime/core_builtin_soa.odin delete mode 100644 core/runtime/default_allocators_arena.odin delete mode 100644 core/runtime/default_allocators_general.odin delete mode 100644 core/runtime/default_allocators_js.odin delete mode 100644 core/runtime/default_allocators_nil.odin delete mode 100644 core/runtime/default_allocators_wasi.odin delete mode 100644 core/runtime/default_allocators_windows.odin delete mode 100644 core/runtime/default_temporary_allocator.odin delete mode 100644 core/runtime/docs.odin delete mode 100644 core/runtime/dynamic_array_internal.odin delete mode 100644 core/runtime/dynamic_map_internal.odin delete mode 100644 core/runtime/entry_unix.odin delete mode 100644 core/runtime/entry_unix_no_crt_amd64.asm delete mode 100644 core/runtime/entry_unix_no_crt_darwin_arm64.asm delete mode 100644 core/runtime/entry_unix_no_crt_i386.asm delete mode 100644 core/runtime/entry_wasm.odin delete mode 100644 core/runtime/entry_windows.odin delete mode 100644 core/runtime/error_checks.odin delete mode 100644 core/runtime/internal.odin delete mode 100644 core/runtime/os_specific.odin delete mode 100644 core/runtime/os_specific_any.odin delete mode 100644 core/runtime/os_specific_darwin.odin delete mode 100644 core/runtime/os_specific_freestanding.odin delete mode 100644 core/runtime/os_specific_js.odin delete mode 100644 core/runtime/os_specific_wasi.odin delete mode 100644 core/runtime/os_specific_windows.odin delete mode 100644 core/runtime/print.odin delete mode 100644 core/runtime/procs.odin delete mode 100644 core/runtime/procs_darwin.odin delete mode 100644 core/runtime/procs_js.odin delete mode 100644 core/runtime/procs_wasm.odin delete mode 100644 core/runtime/procs_windows_amd64.asm delete mode 100644 core/runtime/procs_windows_amd64.odin delete mode 100644 core/runtime/procs_windows_i386.odin delete mode 100644 core/runtime/udivmod128.odin (limited to 'src/parser.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin new file mode 100644 index 000000000..740482493 --- /dev/null +++ b/base/runtime/core.odin @@ -0,0 +1,681 @@ +// This is the runtime code required by the compiler +// IMPORTANT NOTE(bill): Do not change the order of any of this data +// The compiler relies upon this _exact_ order +// +// Naming Conventions: +// In general, Ada_Case for types and snake_case for values +// +// Package Name: snake_case (but prefer single word) +// Import Name: snake_case (but prefer single word) +// Types: Ada_Case +// Enum Values: Ada_Case +// Procedures: snake_case +// Local Variables: snake_case +// Constant Variables: SCREAMING_SNAKE_CASE +// +// IMPORTANT NOTE(bill): `type_info_of` cannot be used within a +// #shared_global_scope due to the internals of the compiler. +// This could change at a later date if the all these data structures are +// implemented within the compiler rather than in this "preload" file +// +//+no-instrumentation +package runtime + +import "core:intrinsics" + +// NOTE(bill): This must match the compiler's +Calling_Convention :: enum u8 { + Invalid = 0, + Odin = 1, + Contextless = 2, + CDecl = 3, + Std_Call = 4, + Fast_Call = 5, + + None = 6, + Naked = 7, + + _ = 8, // reserved + + Win64 = 9, + SysV = 10, +} + +Type_Info_Enum_Value :: distinct i64 + +Platform_Endianness :: enum u8 { + Platform = 0, + Little = 1, + Big = 2, +} + +// Procedure type to test whether two values of the same type are equal +Equal_Proc :: distinct proc "contextless" (rawptr, rawptr) -> bool +// Procedure type to hash a value, default seed value is 0 +Hasher_Proc :: distinct proc "contextless" (data: rawptr, seed: uintptr = 0) -> uintptr + +Type_Info_Struct_Soa_Kind :: enum u8 { + None = 0, + Fixed = 1, + Slice = 2, + Dynamic = 3, +} + +// Variant Types +Type_Info_Named :: struct { + name: string, + base: ^Type_Info, + pkg: string, + loc: Source_Code_Location, +} +Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} +Type_Info_Rune :: struct {} +Type_Info_Float :: struct {endianness: Platform_Endianness} +Type_Info_Complex :: struct {} +Type_Info_Quaternion :: struct {} +Type_Info_String :: struct {is_cstring: bool} +Type_Info_Boolean :: struct {} +Type_Info_Any :: struct {} +Type_Info_Type_Id :: struct {} +Type_Info_Pointer :: struct { + elem: ^Type_Info, // nil -> rawptr +} +Type_Info_Multi_Pointer :: struct { + elem: ^Type_Info, +} +Type_Info_Procedure :: struct { + params: ^Type_Info, // Type_Info_Parameters + results: ^Type_Info, // Type_Info_Parameters + variadic: bool, + convention: Calling_Convention, +} +Type_Info_Array :: struct { + elem: ^Type_Info, + elem_size: int, + count: int, +} +Type_Info_Enumerated_Array :: struct { + elem: ^Type_Info, + index: ^Type_Info, + elem_size: int, + count: int, + min_value: Type_Info_Enum_Value, + max_value: Type_Info_Enum_Value, + is_sparse: bool, +} +Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int} +Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int} + +Type_Info_Parameters :: struct { // Only used for procedures parameters and results + types: []^Type_Info, + names: []string, +} +Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually + +Type_Info_Struct :: struct { + types: []^Type_Info, + names: []string, + offsets: []uintptr, + usings: []bool, + tags: []string, + is_packed: bool, + is_raw_union: bool, + is_no_copy: bool, + custom_align: bool, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set + + // These are only set iff this structure is an SOA structure + soa_kind: Type_Info_Struct_Soa_Kind, + soa_base_type: ^Type_Info, + soa_len: int, +} +Type_Info_Union :: struct { + variants: []^Type_Info, + tag_offset: uintptr, + tag_type: ^Type_Info, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set + + custom_align: bool, + no_nil: bool, + shared_nil: bool, +} +Type_Info_Enum :: struct { + base: ^Type_Info, + names: []string, + values: []Type_Info_Enum_Value, +} +Type_Info_Map :: struct { + key: ^Type_Info, + value: ^Type_Info, + map_info: ^Map_Info, +} +Type_Info_Bit_Set :: struct { + elem: ^Type_Info, + underlying: ^Type_Info, // Possibly nil + lower: i64, + upper: i64, +} +Type_Info_Simd_Vector :: struct { + elem: ^Type_Info, + elem_size: int, + count: int, +} +Type_Info_Relative_Pointer :: struct { + pointer: ^Type_Info, // ^T + base_integer: ^Type_Info, +} +Type_Info_Relative_Multi_Pointer :: struct { + pointer: ^Type_Info, // [^]T + base_integer: ^Type_Info, +} +Type_Info_Matrix :: struct { + elem: ^Type_Info, + elem_size: int, + elem_stride: int, // elem_stride >= row_count + row_count: int, + column_count: int, + // Total element count = column_count * elem_stride +} +Type_Info_Soa_Pointer :: struct { + elem: ^Type_Info, +} + +Type_Info_Flag :: enum u8 { + Comparable = 0, + Simple_Compare = 1, +} +Type_Info_Flags :: distinct bit_set[Type_Info_Flag; u32] + +Type_Info :: struct { + size: int, + align: int, + flags: Type_Info_Flags, + id: typeid, + + variant: union { + Type_Info_Named, + Type_Info_Integer, + Type_Info_Rune, + Type_Info_Float, + Type_Info_Complex, + Type_Info_Quaternion, + Type_Info_String, + Type_Info_Boolean, + Type_Info_Any, + Type_Info_Type_Id, + Type_Info_Pointer, + Type_Info_Multi_Pointer, + Type_Info_Procedure, + Type_Info_Array, + Type_Info_Enumerated_Array, + Type_Info_Dynamic_Array, + Type_Info_Slice, + Type_Info_Parameters, + Type_Info_Struct, + Type_Info_Union, + Type_Info_Enum, + Type_Info_Map, + Type_Info_Bit_Set, + Type_Info_Simd_Vector, + Type_Info_Relative_Pointer, + Type_Info_Relative_Multi_Pointer, + Type_Info_Matrix, + Type_Info_Soa_Pointer, + }, +} + +// NOTE(bill): This must match the compiler's +Typeid_Kind :: enum u8 { + Invalid, + Integer, + Rune, + Float, + Complex, + Quaternion, + String, + Boolean, + Any, + Type_Id, + Pointer, + Multi_Pointer, + Procedure, + Array, + Enumerated_Array, + Dynamic_Array, + Slice, + Tuple, + Struct, + Union, + Enum, + Map, + Bit_Set, + Simd_Vector, + Relative_Pointer, + Relative_Multi_Pointer, + Matrix, + Soa_Pointer, +} +#assert(len(Typeid_Kind) < 32) + +// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) { +// index: 8*size_of(uintptr) - 8, +// kind: 5, // Typeid_Kind +// named: 1, +// special: 1, // signed, cstring, etc +// reserved: 1, +// } +// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr)); + +// NOTE(bill): only the ones that are needed (not all types) +// This will be set by the compiler +type_table: []Type_Info + +args__: []cstring + +when ODIN_OS == .Windows { + // NOTE(Jeroen): If we're a Windows DLL, fwdReason will be populated. + // This tells a DLL if it's first loaded, about to be unloaded, or a thread is joining/exiting. + + DLL_Forward_Reason :: enum u32 { + Process_Detach = 0, // About to unload DLL + Process_Attach = 1, // Entry point + Thread_Attach = 2, + Thread_Detach = 3, + } + dll_forward_reason: DLL_Forward_Reason +} + +// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) + + +Source_Code_Location :: struct { + file_path: string, + line, column: i32, + procedure: string, +} + +Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> ! + +// Allocation Stuff +Allocator_Mode :: enum byte { + Alloc, + Free, + Free_All, + Resize, + Query_Features, + Query_Info, + Alloc_Non_Zeroed, + Resize_Non_Zeroed, +} + +Allocator_Mode_Set :: distinct bit_set[Allocator_Mode] + +Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), +} + +Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, +} + +Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error) +Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, +} + +Byte :: 1 +Kilobyte :: 1024 * Byte +Megabyte :: 1024 * Kilobyte +Gigabyte :: 1024 * Megabyte +Terabyte :: 1024 * Gigabyte +Petabyte :: 1024 * Terabyte +Exabyte :: 1024 * Petabyte + +// Logging stuff + +Logger_Level :: enum uint { + Debug = 0, + Info = 10, + Warning = 20, + Error = 30, + Fatal = 40, +} + +Logger_Option :: enum { + Level, + Date, + Time, + Short_File_Path, + Long_File_Path, + Line, + Procedure, + Terminal_Color, + Thread_Id, +} + +Logger_Options :: bit_set[Logger_Option] +Logger_Proc :: #type proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) + +Logger :: struct { + procedure: Logger_Proc, + data: rawptr, + lowest_level: Logger_Level, + options: Logger_Options, +} + +Context :: struct { + allocator: Allocator, + temp_allocator: Allocator, + assertion_failure_proc: Assertion_Failure_Proc, + logger: Logger, + + user_ptr: rawptr, + user_index: int, + + // Internal use only + _internal: rawptr, +} + + +Raw_String :: struct { + data: [^]byte, + len: int, +} + +Raw_Slice :: struct { + data: rawptr, + len: int, +} + +Raw_Dynamic_Array :: struct { + data: rawptr, + len: int, + cap: int, + allocator: Allocator, +} + +// The raw, type-erased representation of a map. +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Raw_Map :: struct { + // A single allocation spanning all keys, values, and hashes. + // { + // k: Map_Cell(K) * (capacity / ks_per_cell) + // v: Map_Cell(V) * (capacity / vs_per_cell) + // h: Map_Cell(H) * (capacity / hs_per_cell) + // } + // + // The data is allocated assuming 64-byte alignment, meaning the address is + // always a multiple of 64. This means we have 6 bits of zeros in the pointer + // to store the capacity. We can store a value as large as 2^6-1 or 63 in + // there. This conveniently is the maximum log2 capacity we can have for a map + // as Odin uses signed integers to represent capacity. + // + // Since the hashes are backed by Map_Hash, which is just a 64-bit unsigned + // integer, the cell structure for hashes is unnecessary because 64/8 is 8 and + // requires no padding, meaning it can be indexed as a regular array of + // Map_Hash directly, though for consistency sake it's written as if it were + // an array of Map_Cell(Map_Hash). + data: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits + len: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits + allocator: Allocator, // 16-bytes on 64-bits, 8-bytes on 32-bits +} + +Raw_Any :: struct { + data: rawptr, + id: typeid, +} + +Raw_Cstring :: struct { + data: [^]byte, +} + +Raw_Soa_Pointer :: struct { + data: rawptr, + index: int, +} + + + +/* + // Defined internally by the compiler + Odin_OS_Type :: enum int { + Unknown, + Windows, + Darwin, + Linux, + Essence, + FreeBSD, + OpenBSD, + WASI, + JS, + Freestanding, + } +*/ +Odin_OS_Type :: type_of(ODIN_OS) + +/* + // Defined internally by the compiler + Odin_Arch_Type :: enum int { + Unknown, + amd64, + i386, + arm32, + arm64, + wasm32, + wasm64p32, + } +*/ +Odin_Arch_Type :: type_of(ODIN_ARCH) + +/* + // Defined internally by the compiler + Odin_Build_Mode_Type :: enum int { + Executable, + Dynamic, + Object, + Assembly, + LLVM_IR, + } +*/ +Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) + +/* + // Defined internally by the compiler + Odin_Endian_Type :: enum int { + Unknown, + Little, + Big, + } +*/ +Odin_Endian_Type :: type_of(ODIN_ENDIAN) + + +/* + // Defined internally by the compiler + Odin_Platform_Subtarget_Type :: enum int { + Default, + iOS, + } +*/ +Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) + +/* + // Defined internally by the compiler + Odin_Sanitizer_Flag :: enum u32 { + Address = 0, + Memory = 1, + Thread = 2, + } + Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] + + ODIN_SANITIZER_FLAGS // is a constant +*/ +Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) + + +///////////////////////////// +// Init Startup Procedures // +///////////////////////////// + +// IMPORTANT NOTE(bill): Do not call this unless you want to explicitly set up the entry point and how it gets called +// This is probably only useful for freestanding targets +foreign { + @(link_name="__$startup_runtime") + _startup_runtime :: proc "odin" () --- + @(link_name="__$cleanup_runtime") + _cleanup_runtime :: proc "odin" () --- +} + +_cleanup_runtime_contextless :: proc "contextless" () { + context = default_context() + _cleanup_runtime() +} + + +///////////////////////////// +///////////////////////////// +///////////////////////////// + + +type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { + if info == nil { + return nil + } + + base := info + loop: for { + #partial switch i in base.variant { + case Type_Info_Named: base = i.base + case: break loop + } + } + return base +} + + +type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { + if info == nil { + return nil + } + + base := info + loop: for { + #partial switch i in base.variant { + case Type_Info_Named: base = i.base + case Type_Info_Enum: base = i.base + case: break loop + } + } + return base +} +type_info_base_without_enum :: type_info_core + +__type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check { + MASK :: 1<<(8*size_of(typeid) - 8) - 1 + data := transmute(uintptr)id + n := int(data & MASK) + if n < 0 || n >= len(type_table) { + n = 0 + } + return &type_table[n] +} + +when !ODIN_NO_RTTI { + typeid_base :: proc "contextless" (id: typeid) -> typeid { + ti := type_info_of(id) + ti = type_info_base(ti) + return ti.id + } + typeid_core :: proc "contextless" (id: typeid) -> typeid { + ti := type_info_core(type_info_of(id)) + return ti.id + } + typeid_base_without_enum :: typeid_core +} + + + +debug_trap :: intrinsics.debug_trap +trap :: intrinsics.trap +read_cycle_counter :: intrinsics.read_cycle_counter + + + +default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) { + // Nothing +} + +default_logger :: proc() -> Logger { + return Logger{default_logger_proc, nil, Logger_Level.Debug, nil} +} + + +default_context :: proc "contextless" () -> Context { + c: Context + __init_context(&c) + return c +} + +@private +__init_context_from_ptr :: proc "contextless" (c: ^Context, other: ^Context) { + if c == nil { + return + } + c^ = other^ + __init_context(c) +} + +@private +__init_context :: proc "contextless" (c: ^Context) { + if c == nil { + return + } + + // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contextless" calling convention + c.allocator.procedure = default_allocator_proc + c.allocator.data = nil + + c.temp_allocator.procedure = default_temp_allocator_proc + when !NO_DEFAULT_TEMP_ALLOCATOR { + c.temp_allocator.data = &global_default_temp_allocator_data + } + + when !ODIN_DISABLE_ASSERT { + c.assertion_failure_proc = default_assertion_failure_proc + } + + c.logger.procedure = default_logger_proc + c.logger.data = nil +} + +default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { + when ODIN_OS == .Freestanding { + // Do nothing + } else { + when !ODIN_DISABLE_ASSERT { + print_caller_location(loc) + print_string(" ") + } + print_string(prefix) + if len(message) > 0 { + print_string(": ") + print_string(message) + } + print_byte('\n') + } + trap() +} diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin new file mode 100644 index 000000000..3f4ebbc74 --- /dev/null +++ b/base/runtime/core_builtin.odin @@ -0,0 +1,915 @@ +package runtime + +import "core:intrinsics" + +@builtin +Maybe :: union($T: typeid) {T} + + +@(builtin, require_results) +container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Field_Type { + offset :: offset_of_by_string(T, field_name) + return (^T)(uintptr(ptr) - offset) if ptr != nil else nil +} + + +when !NO_DEFAULT_TEMP_ALLOCATOR { + @thread_local global_default_temp_allocator_data: Default_Temp_Allocator +} + +@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) +init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) { + when !NO_DEFAULT_TEMP_ALLOCATOR { + default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator) + } +} + + +// `copy_slice` is a built-in procedure that copies elements from a source slice `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). +// +// Prefer the procedure group `copy`. +@builtin +copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { + n := max(0, min(len(dst), len(src))) + if n > 0 { + intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) + } + return n +} +// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `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). +// +// Prefer the procedure group `copy`. +@builtin +copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { + n := max(0, min(len(dst), len(src))) + if n > 0 { + intrinsics.mem_copy(raw_data(dst), raw_data(src), n) + } + return n +} +// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `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 +copy :: proc{copy_slice, copy_from_string} + + + +// `unordered_remove` removed the element at the specified `index`. It does so by replacing the current end value +// with the old value, and reducing the length of the dynamic array by 1. +// +// Note: This is an O(1) operation. +// Note: If you the elements to remain in their order, use `ordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + n := len(array)-1 + if index != n { + array[index] = array[n] + } + (^Raw_Dynamic_Array)(array).len -= 1 +} +// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + if index+1 < len(array) { + copy(array[index:], array[index+1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 +} + +// `remove_range` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the range is out of bounds, this procedure will panic. +@builtin +remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { + slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) + n := max(hi-lo, 0) + if n > 0 { + if hi != len(array) { + copy(array[lo:], array[hi:]) + } + (^Raw_Dynamic_Array)(array).len -= n + } +} + + +// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[len(array)-1] + (^Raw_Dynamic_Array)(array).len -= 1 + return res +} + + +// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[len(array)-1], true + (^Raw_Dynamic_Array)(array).len -= 1 + return +} + +// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[0] + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 + return res +} + +// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[0], true + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Dynamic_Array)(array).len -= 1 + return +} + + +// `clear` will set the length of a passed dynamic array or map to `0` +@builtin +clear :: proc{clear_dynamic_array, clear_map} + +// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +@builtin +reserve :: proc{reserve_dynamic_array, reserve_map} + +@builtin +non_zero_reserve :: proc{non_zero_reserve_dynamic_array} + +// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`). +@builtin +resize :: proc{resize_dynamic_array} + +@builtin +non_zero_resize :: proc{non_zero_resize_dynamic_array} + +// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity. +@builtin +shrink :: proc{shrink_dynamic_array, shrink_map} + +// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation. +@builtin +free :: proc{mem_free} + +// `free_all` will try to free/reset all of the memory of the given `allocator` if the allocator supports this operation. +@builtin +free_all :: proc{mem_free_all} + + + +// `delete_string` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(str), len(str), allocator, loc) +} +// `delete_cstring` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free((^byte)(str), allocator, loc) +} +// `delete_dynamic_array` will try to free the underlying data of the passed dynamic array, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc) +} +// `delete_slice` will try to free the underlying data of the passed sliced, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc) +} +// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer the procedure group `delete`. +@builtin +delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { + return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc) +} + + +// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation. +// +// Note: Prefer `delete` over the specific `delete_*` procedures where possible. +@builtin +delete :: proc{ + delete_string, + delete_cstring, + delete_dynamic_array, + delete_slice, + delete_map, + delete_soa_slice, + delete_soa_dynamic_array, +} + + +// The new built-in procedure allocates memory. The first argument is a type, not a value, and the value +// return is a pointer to a newly allocated value of that type using the specified allocator, default is context.allocator +@(builtin, require_results) +new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) #optional_allocator_error { + return new_aligned(T, align_of(T), allocator, loc) +} +@(require_results) +new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { + data := mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return + t = (^T)(raw_data(data)) + return +} + +@(builtin, require_results) +new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { + t_data := mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return + t = (^T)(raw_data(t_data)) + if t != nil { + t^ = data + } + return +} + +DEFAULT_RESERVE_CAPACITY :: 16 + +@(require_results) +make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + make_slice_error_loc(loc, len) + data, err := mem_alloc_bytes(size_of(E)*len, alignment, allocator, loc) + if data == nil && size_of(E) != 0 { + return nil, err + } + s := Raw_Slice{raw_data(data), len} + return transmute(T)s, err +} + +// `make_slice` allocates and initializes a slice. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_aligned(T, len, align_of(E), allocator, loc) +} +// `make_dynamic_array` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc) +} +// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { + return make_dynamic_array_len_cap(T, len, len, allocator, loc) +} +// `make_dynamic_array_len_cap` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + make_dynamic_array_error_loc(loc, len, cap) + data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return + s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} + if data == nil && size_of(E) != 0 { + s.len, s.cap = 0, 0 + } + array = transmute(T)s + return +} +// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { + make_map_expr_error_loc(loc, capacity) + context.allocator = allocator + + err = reserve_map(&m, capacity, loc) + return +} +// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// This is "similar" to doing `raw_data(make([]E, len, allocator))`. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error { + make_slice_error_loc(loc, len) + data := mem_alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return + if data == nil && size_of(E) != 0 { + return + } + mp = cast(T)raw_data(data) + return +} + + +// `make` built-in procedure allocates and initializes a value of type slice, dynamic array, map, or multi-pointer (only). +// +// Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the +// type of its argument, not a pointer to it. +// Make uses the specified allocator, default is context.allocator. +@builtin +make :: proc{ + make_slice, + make_dynamic_array, + make_dynamic_array_len, + make_dynamic_array_len_cap, + make_map, + make_multi_pointer, +} + + + +// `clear_map` will set the length of a passed map to `0` +// +// Note: Prefer the procedure group `clear` +@builtin +clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { + if m == nil { + return + } + map_clear_dynamic((^Raw_Map)(m), map_info(T)) +} + +// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`). +// +// Note: Prefer the procedure group `reserve` +@builtin +reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { + return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil +} + +// Shrinks the capacity of a map down to the current length. +// +// Note: Prefer the procedure group `shrink` +@builtin +shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if m != nil { + return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc) + } + return +} + +// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map. +// If m is nil, or there is no such element, this procedure is a no-op +@builtin +delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) { + if m != nil { + key := key + old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key)) + if ok { + deleted_key = (^K)(old_k)^ + deleted_value = (^V)(old_v)^ + } + } + return +} + +_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + when size_of(E) == 0 { + array := (^Raw_Dynamic_Array)(array) + array.len += 1 + return 1, nil + } else { + if cap(array) < len(array)+1 { + cap := 2 * cap(array) + max(8, 1) + + // do not 'or_return' here as it could be a partial success + if should_zero { + err = reserve(array, cap, loc) + } else { + err = non_zero_reserve(array, cap, loc) + } + } + if cap(array)-len(array) > 0 { + a := (^Raw_Dynamic_Array)(array) + when size_of(E) != 0 { + data := ([^]E)(a.data) + assert(data != nil, loc=loc) + data[a.len] = arg + } + a.len += 1 + return 1, err + } + return 0, err + } +} + +@builtin +append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem(array, arg, true, loc=loc) +} + +@builtin +non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem(array, arg, false, loc=loc) +} + +_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + + arg_len := len(args) + if arg_len <= 0 { + return 0, nil + } + + when size_of(E) == 0 { + array := (^Raw_Dynamic_Array)(array) + array.len += arg_len + return arg_len, nil + } else { + if cap(array) < len(array)+arg_len { + cap := 2 * cap(array) + max(8, arg_len) + + // do not 'or_return' here as it could be a partial success + if should_zero { + err = reserve(array, cap, loc) + } else { + err = non_zero_reserve(array, cap, loc) + } + } + arg_len = min(cap(array)-len(array), arg_len) + if arg_len > 0 { + a := (^Raw_Dynamic_Array)(array) + when size_of(E) != 0 { + data := ([^]E)(a.data) + assert(data != nil, loc=loc) + intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) + } + a.len += arg_len + } + return arg_len, err + } +} + +@builtin +append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, true, loc, ..args) +} + +@builtin +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, false, loc, ..args) +} + +// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type +_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + args := transmute([]E)arg + if should_zero { + return append_elems(array, ..args, loc=loc) + } else { + return non_zero_append_elems(array, ..args, loc=loc) + } +} + +@builtin +append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem_string(array, arg, true, loc) +} +@builtin +non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem_string(array, arg, false, loc) +} + + +// The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type +@builtin +append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + n_arg: int + for arg in args { + n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) + n += n_arg + if err != nil { + return + } + } + return +} + +// The append built-in procedure appends elements to the end of a dynamic array +@builtin append :: proc{append_elem, append_elems, append_elem_string} +@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string} + + +@builtin +append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + prev_len := len(array) + resize(array, len(array)+1, loc) or_return + return len(array)-prev_len, nil +} + + +@builtin +inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + n := max(len(array), index) + m :: 1 + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + array[index] = arg + } + ok = true + return +} + +@builtin +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + if len(args) == 0 { + ok = true + return + } + + n := max(len(array), index) + m := len(args) + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + copy(array[index:], args) + } + ok = true + return +} + +@builtin +inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if array == nil { + return + } + if len(arg) == 0 { + ok = true + return + } + + n := max(len(array), index) + m := len(arg) + new_size := n + m + + resize(array, new_size, loc) or_return + copy(array[index+m:], array[index:]) + copy(array[index:], arg) + ok = true + return +} + +@builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string} + + + +@builtin +assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + if index < len(array) { + array[index] = arg + ok = true + } else { + resize(array, index+1, loc) or_return + array[index] = arg + ok = true + } + return +} + + +@builtin +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + new_size := index + len(args) + if len(args) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], args) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], args) + ok = true + } + return +} + + +@builtin +assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + new_size := index + len(arg) + if len(arg) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], arg) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], arg) + ok = true + } + return +} + +@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string} + + + + +// `clear_dynamic_array` will set the length of a passed dynamic array to `0` +// +// Note: Prefer the procedure group `clear`. +@builtin +clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { + if array != nil { + (^Raw_Dynamic_Array)(array).len = 0 + } +} + +// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). +// +// Note: Prefer the procedure group `reserve`. +_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + a := (^Raw_Dynamic_Array)(array) + + if capacity <= a.cap { + return nil + } + + if a.allocator.procedure == nil { + a.allocator = context.allocator + } + assert(a.allocator.procedure != nil) + + old_size := a.cap * size_of(E) + new_size := capacity * size_of(E) + allocator := a.allocator + + new_data: []byte + if should_zero { + new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } else { + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } + if new_data == nil && new_size > 0 { + return .Out_Of_Memory + } + + a.data = raw_data(new_data) + a.cap = capacity + return nil +} + +@builtin +reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array(array, capacity, true, loc) +} + +@builtin +non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_dynamic_array(array, capacity, false, loc) +} + +// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` +_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + a := (^Raw_Dynamic_Array)(array) + + if length <= a.cap { + a.len = max(length, 0) + return nil + } + + if a.allocator.procedure == nil { + a.allocator = context.allocator + } + assert(a.allocator.procedure != nil) + + old_size := a.cap * size_of(E) + new_size := length * size_of(E) + allocator := a.allocator + + new_data : []byte + if should_zero { + new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } else { + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + } + if new_data == nil && new_size > 0 { + return .Out_Of_Memory + } + + a.data = raw_data(new_data) + a.len = length + a.cap = length + return nil +} + +@builtin +resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array(array, length, true, loc=loc) +} + +@builtin +non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array(array, length, false, loc=loc) +} + +/* + Shrinks the capacity of a dynamic array down to the current length, or the given capacity. + + If `new_cap` is negative, then `len(array)` is used. + + Returns false if `cap(array) < new_cap`, or the allocator report failure. + + If `len(array) < new_cap`, then `len(array)` will be left unchanged. + + Note: Prefer the procedure group `shrink` +*/ +shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if array == nil { + return + } + a := (^Raw_Dynamic_Array)(array) + + new_cap := new_cap if new_cap >= 0 else a.len + + if new_cap > a.cap { + return + } + + if a.allocator.procedure == nil { + a.allocator = context.allocator + } + assert(a.allocator.procedure != nil) + + old_size := a.cap * size_of(E) + new_size := new_cap * size_of(E) + + new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return + + a.data = raw_data(new_data) + a.len = min(new_cap, a.len) + a.cap = new_cap + return true, nil +} + +@builtin +map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) { + key, value := key, value + return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)) +} + + +@builtin +incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { + s^ |= {elem} +} +@builtin +incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { + for elem in elems { + s^ |= {elem} + } +} +@builtin +incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { + s^ |= other +} +@builtin +excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { + s^ &~= {elem} +} +@builtin +excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { + for elem in elems { + s^ &~= {elem} + } +} +@builtin +excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { + s^ &~= other +} + +@builtin incl :: proc{incl_elem, incl_elems, incl_bit_set} +@builtin excl :: proc{excl_elem, excl_elems, excl_bit_set} + + +@builtin +card :: proc(s: $S/bit_set[$E; $U]) -> int { + when size_of(S) == 1 { + return int(intrinsics.count_ones(transmute(u8)s)) + } else when size_of(S) == 2 { + return int(intrinsics.count_ones(transmute(u16)s)) + } else when size_of(S) == 4 { + return int(intrinsics.count_ones(transmute(u32)s)) + } else when size_of(S) == 8 { + return int(intrinsics.count_ones(transmute(u64)s)) + } else when size_of(S) == 16 { + return int(intrinsics.count_ones(transmute(u128)s)) + } else { + #panic("Unhandled card bit_set size") + } +} + + + +@builtin +@(disabled=ODIN_DISABLE_ASSERT) +assert :: proc(condition: bool, message := "", loc := #caller_location) { + if !condition { + // NOTE(bill): This is wrapped in a procedure call + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: proc(message: string, loc: Source_Code_Location) { + p := context.assertion_failure_proc + if p == nil { + p = default_assertion_failure_proc + } + p("runtime assertion", message, loc) + } + internal(message, loc) + } +} + +@builtin +panic :: proc(message: string, loc := #caller_location) -> ! { + p := context.assertion_failure_proc + if p == nil { + p = default_assertion_failure_proc + } + p("panic", message, loc) +} + +@builtin +unimplemented :: proc(message := "", loc := #caller_location) -> ! { + p := context.assertion_failure_proc + if p == nil { + p = default_assertion_failure_proc + } + p("not yet implemented", message, loc) +} diff --git a/base/runtime/core_builtin_matrix.odin b/base/runtime/core_builtin_matrix.odin new file mode 100644 index 000000000..7d60d625c --- /dev/null +++ b/base/runtime/core_builtin_matrix.odin @@ -0,0 +1,274 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + + +@(builtin) +determinant :: proc{ + matrix1x1_determinant, + matrix2x2_determinant, + matrix3x3_determinant, + matrix4x4_determinant, +} + +@(builtin) +adjugate :: proc{ + matrix1x1_adjugate, + matrix2x2_adjugate, + matrix3x3_adjugate, + matrix4x4_adjugate, +} + +@(builtin) +inverse_transpose :: proc{ + matrix1x1_inverse_transpose, + matrix2x2_inverse_transpose, + matrix3x3_inverse_transpose, + matrix4x4_inverse_transpose, +} + + +@(builtin) +inverse :: proc{ + matrix1x1_inverse, + matrix2x2_inverse, + matrix3x3_inverse, + matrix4x4_inverse, +} + +@(builtin, require_results) +hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { + return conj(transpose(m)) +} + +@(builtin, require_results) +matrix_trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { + for i in 0.. (minor: T) where N > 1 { + K :: N-1 + cut_down: matrix[K, K]T + for col_idx in 0..= column) + for row_idx in 0..= row) + cut_down[row_idx, col_idx] = m[i, j] + } + } + return determinant(cut_down) +} + + + +@(builtin, require_results) +matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { + return m[0, 0] +} + +@(builtin, require_results) +matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { + return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] +} +@(builtin, require_results) +matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { + a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) + b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) + c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) + return a + b + c +} +@(builtin, require_results) +matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { + a := adjugate(m) + #no_bounds_check for i in 0..<4 { + det += m[0, i] * a[0, i] + } + return +} + + + + +@(builtin, require_results) +matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(builtin, require_results) +matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[1, 0] + y[1, 0] = -x[0, 1] + y[1, 1] = +x[0, 0] + return +} + +@(builtin, require_results) +matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + + +@(builtin, require_results) +matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, i, j) + } + } + return +} + +@(builtin, require_results) +matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(builtin, require_results) +matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[1, 0] = -x[0, 1] / d + y[0, 1] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[1, 0] = -x[0, 1] * id + y[0, 1] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(builtin, require_results) +matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[i, j] * id + } + } + } + return +} + +@(builtin, require_results) +matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d: T + for i in 0..<4 { + d += x[0, i] * a[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[i, j] * id + } + } + } + return +} + +@(builtin, require_results) +matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(builtin, require_results) +matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[0, 1] = -x[0, 1] / d + y[1, 0] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[0, 1] = -x[0, 1] * id + y[1, 0] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(builtin, require_results) +matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = a[j, i] * id + } + } + } + return +} + +@(builtin, require_results) +matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + a := adjugate(x) + d: T + for i in 0..<4 { + d += x[0, i] * a[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = a[j, i] * id + } + } + } + return +} diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin new file mode 100644 index 000000000..6313a28f5 --- /dev/null +++ b/base/runtime/core_builtin_soa.odin @@ -0,0 +1,428 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + +/* + + SOA types are implemented with this sort of layout: + + SOA Fixed Array + struct { + f0: [N]T0, + f1: [N]T1, + f2: [N]T2, + } + + SOA Slice + struct { + f0: ^T0, + f1: ^T1, + f2: ^T2, + + len: int, + } + + SOA Dynamic Array + struct { + f0: ^T0, + f1: ^T1, + f2: ^T2, + + len: int, + cap: int, + allocator: Allocator, + } + + A footer is used rather than a header purely to simplify access to the fields internally + i.e. field index of the AOS == SOA + +*/ + + +Raw_SOA_Footer_Slice :: struct { + len: int, +} + +Raw_SOA_Footer_Dynamic_Array :: struct { + len: int, + cap: int, + allocator: Allocator, +} + +@(builtin, require_results) +raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { + if array == nil { + return nil + } + field_count := uintptr(intrinsics.type_struct_field_count(E)) + footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr)) + return +} +@(builtin, require_results) +raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { + if array == nil { + return nil + } + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr)) + return +} +raw_soa_footer :: proc{ + raw_soa_footer_slice, + raw_soa_footer_dynamic_array, +} + + + +@(builtin, require_results) +make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + if length <= 0 { + return + } + + footer := raw_soa_footer(&array) + if size_of(E) == 0 { + footer.len = length + return + } + + max_align := max(alignment, align_of(E)) + + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + + field_count := uintptr(intrinsics.type_struct_field_count(E)) + + total_size := 0 + for i in 0.. (array: T, err: Allocator_Error) #optional_allocator_error { + return make_soa_aligned(T, length, align_of(E), allocator, loc) +} + +@(builtin, require_results) +make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return + return array, nil +} + +@(builtin, require_results) +make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + resize_soa(&array, length, loc) or_return + return array, nil +} + +@(builtin, require_results) +make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + context.allocator = allocator + reserve_soa(&array, capacity, loc) or_return + resize_soa(&array, length, loc) or_return + return array, nil +} + + +@builtin +make_soa :: proc{ + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, +} + + +@builtin +resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + reserve_soa(array, length, loc) or_return + footer := raw_soa_footer(array) + footer.len = length + return nil +} + +@builtin +reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + + old_cap := cap(array) + if capacity <= old_cap { + return nil + } + + if array.allocator.procedure == nil { + array.allocator = context.allocator + } + assert(array.allocator.procedure != nil) + + footer := raw_soa_footer(array) + if size_of(E) == 0 { + footer.cap = capacity + return nil + } + + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + assert(footer.cap == old_cap) + + old_size := 0 + new_size := 0 + + max_align :: align_of(E) + for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return 0, nil + } + + if cap(array) <= len(array) + 1 { + cap := 2 * cap(array) + 8 + err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + } + + footer := raw_soa_footer(array) + + if size_of(E) > 0 && cap(array)-len(array) > 0 { + ti := type_info_of(T) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + field_count: uintptr + when intrinsics.type_is_array(E) { + field_count = len(E) + } else { + field_count = uintptr(intrinsics.type_struct_field_count(E)) + } + + data := (^rawptr)(array)^ + + soa_offset := 0 + item_offset := 0 + + arg_copy := arg + arg_ptr := &arg_copy + + max_align :: align_of(E) + for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { + if array == nil { + return + } + + arg_len := len(args) + if arg_len == 0 { + return + } + + if cap(array) <= len(array)+arg_len { + cap := 2 * cap(array) + max(8, arg_len) + err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success + } + arg_len = min(cap(array)-len(array), arg_len) + + footer := raw_soa_footer(array) + if size_of(E) > 0 && arg_len > 0 { + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + field_count := uintptr(intrinsics.type_struct_field_count(E)) + + data := (^rawptr)(array)^ + + soa_offset := 0 + item_offset := 0 + + args_ptr := &args[0] + + max_align :: align_of(E) + for i in 0.. Allocator_Error { + when intrinsics.type_struct_field_count(E) != 0 { + array := array + ptr := (^rawptr)(&array)^ + free(ptr, allocator, loc) or_return + } + return nil +} + +delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error { + when intrinsics.type_struct_field_count(E) != 0 { + array := array + ptr := (^rawptr)(&array)^ + footer := raw_soa_footer(&array) + free(ptr, footer.allocator, loc) or_return + } + return nil +} + + +@builtin +delete_soa :: proc{ + delete_soa_slice, + delete_soa_dynamic_array, +} + + +clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { + when intrinsics.type_struct_field_count(E) != 0 { + footer := raw_soa_footer(array) + footer.len = 0 + } +} + +@builtin +clear_soa :: proc{ + clear_soa_dynamic_array, +} \ No newline at end of file diff --git a/base/runtime/default_allocators_arena.odin b/base/runtime/default_allocators_arena.odin new file mode 100644 index 000000000..1fe3c6cfc --- /dev/null +++ b/base/runtime/default_allocators_arena.odin @@ -0,0 +1,304 @@ +package runtime + +import "core:intrinsics" + +DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) + +Memory_Block :: struct { + prev: ^Memory_Block, + allocator: Allocator, + base: [^]byte, + used: uint, + capacity: uint, +} + +Arena :: struct { + backing_allocator: Allocator, + curr_block: ^Memory_Block, + total_used: uint, + total_capacity: uint, + minimum_block_size: uint, + temp_count: uint, +} + +@(private, require_results) +safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { + z, did_overflow := intrinsics.overflow_add(x, y) + return z, !did_overflow +} + +@(require_results) +memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { + total_size := uint(capacity + max(alignment, size_of(Memory_Block))) + base_offset := uintptr(max(alignment, size_of(Memory_Block))) + + min_alignment: int = max(16, align_of(Memory_Block), int(alignment)) + data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return + block = (^Memory_Block)(raw_data(data)) + end := uintptr(raw_data(data)[len(data):]) + + block.allocator = allocator + block.base = ([^]byte)(uintptr(block) + base_offset) + block.capacity = uint(end - uintptr(block.base)) + + // Should be zeroed + assert(block.used == 0) + assert(block.prev == nil) + return +} + +memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { + if block_to_free != nil { + allocator := block_to_free.allocator + mem_free(block_to_free, allocator, loc) + } +} + +@(require_results) +alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) { + calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { + alignment_offset := uint(0) + ptr := uintptr(block.base[block.used:]) + mask := alignment-1 + if ptr & mask != 0 { + alignment_offset = uint(alignment - (ptr & mask)) + } + return alignment_offset + + } + if block == nil { + return nil, .Out_Of_Memory + } + alignment_offset := calc_alignment_offset(block, uintptr(alignment)) + size, size_ok := safe_add(min_size, alignment_offset) + if !size_ok { + err = .Out_Of_Memory + return + } + + if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity { + err = .Out_Of_Memory + return + } + data = block.base[block.used+alignment_offset:][:min_size] + block.used += size + return +} + +@(require_results) +arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint { + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p + } + + assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) + + size := size + if size == 0 { + return + } + + needed := align_forward_uint(size, alignment) + if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity { + if arena.minimum_block_size == 0 { + arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE + } + + block_size := max(needed, arena.minimum_block_size) + + if arena.backing_allocator.procedure == nil { + arena.backing_allocator = default_allocator() + } + + new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return + new_block.prev = arena.curr_block + arena.curr_block = new_block + arena.total_capacity += new_block.capacity + } + + prev_used := arena.curr_block.used + data, err = alloc_from_memory_block(arena.curr_block, size, alignment) + arena.total_used += arena.curr_block.used - prev_used + return +} + +// `arena_init` will initialize the arena with a usuable block. +// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary +@(require_results) +arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { + arena^ = {} + arena.backing_allocator = backing_allocator + arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB + new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return + arena.curr_block = new_block + arena.total_capacity += new_block.capacity + return nil +} + + +arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { + if free_block := arena.curr_block; free_block != nil { + arena.curr_block = free_block.prev + + arena.total_capacity -= free_block.capacity + memory_block_dealloc(free_block, loc) + } +} + +// `arena_free_all` will free all but the first memory block, and then reset the memory block +arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { + for arena.curr_block != nil && arena.curr_block.prev != nil { + arena_free_last_memory_block(arena, loc) + } + + if arena.curr_block != nil { + intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) + arena.curr_block.used = 0 + } + arena.total_used = 0 +} + +arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { + for arena.curr_block != nil { + free_block := arena.curr_block + arena.curr_block = free_block.prev + + arena.total_capacity -= free_block.capacity + memory_block_dealloc(free_block, loc) + } + arena.total_used = 0 + arena.total_capacity = 0 +} + +arena_allocator :: proc(arena: ^Arena) -> Allocator { + return Allocator{arena_allocator_proc, arena} +} + +arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> (data: []byte, err: Allocator_Error) { + arena := (^Arena)(allocator_data) + + size, alignment := uint(size), uint(alignment) + old_size := uint(old_size) + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return arena_alloc(arena, size, alignment, location) + case .Free: + err = .Mode_Not_Implemented + case .Free_All: + arena_free_all(arena, location) + case .Resize, .Resize_Non_Zeroed: + old_data := ([^]byte)(old_memory) + + switch { + case old_data == nil: + return arena_alloc(arena, size, alignment, location) + case size == old_size: + // return old memory + data = old_data[:size] + return + case size == 0: + err = .Mode_Not_Implemented + return + case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: + // shrink data in-place + data = old_data[:size] + return + } + + new_memory := arena_alloc(arena, size, alignment, location) or_return + if new_memory == nil { + return + } + copy(new_memory, old_data[:old_size]) + return new_memory, nil + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features} + } + case .Query_Info: + err = .Mode_Not_Implemented + } + + return +} + + + + +Arena_Temp :: struct { + arena: ^Arena, + block: ^Memory_Block, + used: uint, +} + +@(require_results) +arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { + assert(arena != nil, "nil arena", loc) + + temp.arena = arena + temp.block = arena.curr_block + if arena.curr_block != nil { + temp.used = arena.curr_block.used + } + arena.temp_count += 1 + return +} + +arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + if temp.arena == nil { + assert(temp.block == nil) + assert(temp.used == 0) + return + } + arena := temp.arena + + if temp.block != nil { + memory_block_found := false + for block := arena.curr_block; block != nil; block = block.prev { + if block == temp.block { + memory_block_found = true + break + } + } + if !memory_block_found { + assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc) + } + + for arena.curr_block != temp.block { + arena_free_last_memory_block(arena) + } + + if block := arena.curr_block; block != nil { + assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) + amount_to_zero := min(block.used-temp.used, block.capacity-block.used) + intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) + block.used = temp.used + } + } + + assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) + arena.temp_count -= 1 +} + +// Ignore the use of a `arena_temp_begin` entirely +arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { + assert(temp.arena != nil, "nil arena", loc) + arena := temp.arena + + assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) + arena.temp_count -= 1 +} + +arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { + assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) +} diff --git a/base/runtime/default_allocators_general.odin b/base/runtime/default_allocators_general.odin new file mode 100644 index 000000000..994a672b0 --- /dev/null +++ b/base/runtime/default_allocators_general.odin @@ -0,0 +1,23 @@ +//+build !windows +//+build !freestanding +//+build !wasi +//+build !js +package runtime + +// TODO(bill): reimplement these procedures in the os_specific stuff +import "core:os" + +when ODIN_DEFAULT_TO_NIL_ALLOCATOR { + _ :: os + + // mem.nil_allocator reimplementation + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} else { + + default_allocator_proc :: os.heap_allocator_proc + + default_allocator :: proc() -> Allocator { + return os.heap_allocator() + } +} diff --git a/base/runtime/default_allocators_js.odin b/base/runtime/default_allocators_js.odin new file mode 100644 index 000000000..715073f08 --- /dev/null +++ b/base/runtime/default_allocators_js.odin @@ -0,0 +1,5 @@ +//+build js +package runtime + +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator diff --git a/base/runtime/default_allocators_nil.odin b/base/runtime/default_allocators_nil.odin new file mode 100644 index 000000000..c882f5196 --- /dev/null +++ b/base/runtime/default_allocators_nil.odin @@ -0,0 +1,88 @@ +package runtime + +nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return nil, .Out_Of_Memory + case .Free: + return nil, .None + case .Free_All: + return nil, .Mode_Not_Implemented + case .Resize, .Resize_Non_Zeroed: + if size == 0 { + return nil, .None + } + return nil, .Out_Of_Memory + case .Query_Features: + return nil, .Mode_Not_Implemented + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, .None +} + +nil_allocator :: proc() -> Allocator { + return Allocator{ + procedure = nil_allocator_proc, + data = nil, + } +} + + + +when ODIN_OS == .Freestanding { + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} + + + +panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc: + if size > 0 { + panic("panic allocator, .Alloc called", loc=loc) + } + case .Alloc_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Resize: + if size > 0 { + panic("panic allocator, .Resize called", loc=loc) + } + case .Resize_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Free: + if old_memory != nil { + panic("panic allocator, .Free called", loc=loc) + } + case .Free_All: + panic("panic allocator, .Free_All called", loc=loc) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Query_Features} + } + return nil, nil + + case .Query_Info: + panic("panic allocator, .Query_Info called", loc=loc) + } + + return nil, nil +} + +panic_allocator :: proc() -> Allocator { + return Allocator{ + procedure = panic_allocator_proc, + data = nil, + } +} diff --git a/base/runtime/default_allocators_wasi.odin b/base/runtime/default_allocators_wasi.odin new file mode 100644 index 000000000..a7e6842a6 --- /dev/null +++ b/base/runtime/default_allocators_wasi.odin @@ -0,0 +1,5 @@ +//+build wasi +package runtime + +default_allocator_proc :: panic_allocator_proc +default_allocator :: panic_allocator diff --git a/base/runtime/default_allocators_windows.odin b/base/runtime/default_allocators_windows.odin new file mode 100644 index 000000000..1b0f78428 --- /dev/null +++ b/base/runtime/default_allocators_windows.odin @@ -0,0 +1,44 @@ +//+build windows +package runtime + +when ODIN_DEFAULT_TO_NIL_ALLOCATOR { + // mem.nil_allocator reimplementation + default_allocator_proc :: nil_allocator_proc + default_allocator :: nil_allocator +} else { + default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + data, err = _windows_default_alloc(size, alignment, mode == .Alloc) + + case .Free: + _windows_default_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize, .Resize_Non_Zeroed: + data, err = _windows_default_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} + } + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return + } + + default_allocator :: proc() -> Allocator { + return Allocator{ + procedure = default_allocator_proc, + data = nil, + } + } +} diff --git a/base/runtime/default_temporary_allocator.odin b/base/runtime/default_temporary_allocator.odin new file mode 100644 index 000000000..c90f0388d --- /dev/null +++ b/base/runtime/default_temporary_allocator.odin @@ -0,0 +1,79 @@ +package runtime + +DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte) +NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR + +when NO_DEFAULT_TEMP_ALLOCATOR { + Default_Temp_Allocator :: struct {} + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {} + + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {} + + default_temp_allocator_proc :: nil_allocator_proc + + @(require_results) + default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { + return + } + + default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + } +} else { + Default_Temp_Allocator :: struct { + arena: Arena, + } + + default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) { + _ = arena_init(&s.arena, uint(size), backing_allocator) + } + + default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { + if s != nil { + arena_destroy(&s.arena) + s^ = {} + } + } + + default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + + s := (^Default_Temp_Allocator)(allocator_data) + return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc) + } + + @(require_results) + default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { + if context.temp_allocator.data == &global_default_temp_allocator_data { + temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc) + } + return + } + + default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { + arena_temp_end(temp, loc) + } + + @(fini, private) + _destroy_temp_allocator_fini :: proc() { + default_temp_allocator_destroy(&global_default_temp_allocator_data) + } +} + +@(deferred_out=default_temp_allocator_temp_end) +DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) { + if ignore { + return {}, loc + } else { + return default_temp_allocator_temp_begin(loc), loc + } +} + + +default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator { + return Allocator{ + procedure = default_temp_allocator_proc, + data = allocator, + } +} diff --git a/base/runtime/docs.odin b/base/runtime/docs.odin new file mode 100644 index 000000000..a520584c5 --- /dev/null +++ b/base/runtime/docs.odin @@ -0,0 +1,179 @@ +package runtime + +/* + +package runtime has numerous entities (declarations) which are required by the compiler to function. + + +## Basic types and calls (and anything they rely on) + +Source_Code_Location +Context +Allocator +Logger + +__init_context +_cleanup_runtime + + +## cstring calls + +cstring_to_string +cstring_len + + + +## Required when RTTI is enabled (the vast majority of targets) + +Type_Info + +type_table +__type_info_of + + +## Hashing + +default_hasher +default_hasher_cstring +default_hasher_string + + +## Pseudo-CRT required procedured due to LLVM but useful in general +memset +memcpy +memove + + +## Procedures required by the LLVM backend +umodti3 +udivti3 +modti3 +divti3 +fixdfti +fixunsdfti +fixunsdfdi +floattidf +floattidf_unsigned +truncsfhf2 +truncdfhf2 +gnu_h2f_ieee +gnu_f2h_ieee +extendhfsf2 +__ashlti3 // wasm specific +__multi3 // wasm specific + + + +## Required an entry point is defined (i.e. 'main') + +args__ + + +## When -no-crt is defined (and not a wasm target) (mostly due to LLVM) +_tls_index +_fltused + + +## Bounds checking procedures (when not disabled with -no-bounds-check) + +bounds_check_error +matrix_bounds_check_error +slice_expr_error_hi +slice_expr_error_lo_hi +multi_pointer_slice_expr_error + + +## Type assertion check + +type_assertion_check +type_assertion_check2 // takes in typeid + + +## Arithmetic + +quo_complex32 +quo_complex64 +quo_complex128 + +mul_quaternion64 +mul_quaternion128 +mul_quaternion256 + +quo_quaternion64 +quo_quaternion128 +quo_quaternion256 + +abs_complex32 +abs_complex64 +abs_complex128 + +abs_quaternion64 +abs_quaternion128 +abs_quaternion256 + + +## Comparison + +memory_equal +memory_compare +memory_compare_zero + +cstring_eq +cstring_ne +cstring_lt +cstring_gt +cstring_le +cstring_gt + +string_eq +string_ne +string_lt +string_gt +string_le +string_gt + +complex32_eq +complex32_ne +complex64_eq +complex64_ne +complex128_eq +complex128_ne + +quaternion64_eq +quaternion64_ne +quaternion128_eq +quaternion128_ne +quaternion256_eq +quaternion256_ne + + +## Map specific calls + +map_seed_from_map_data +__dynamic_map_check_grow // static map calls +map_insert_hash_dynamic // static map calls +__dynamic_map_get // dynamic map calls +__dynamic_map_set // dynamic map calls + + +## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) + +__dynamic_array_reserve +__dynamic_array_append + +__dynamic_map_reserve + + +## Objective-C specific + +objc_lookUpClass +sel_registerName +objc_allocateClassPair + + +## for-in `string` type + +string_decode_rune +string_decode_last_rune // #reverse for + +*/ \ No newline at end of file diff --git a/base/runtime/dynamic_array_internal.odin b/base/runtime/dynamic_array_internal.odin new file mode 100644 index 000000000..267ee0785 --- /dev/null +++ b/base/runtime/dynamic_array_internal.odin @@ -0,0 +1,138 @@ +package runtime + +__dynamic_array_make :: proc(array_: rawptr, elem_size, elem_align: int, len, cap: int, loc := #caller_location) { + array := (^Raw_Dynamic_Array)(array_) + array.allocator = context.allocator + assert(array.allocator.procedure != nil) + + if cap > 0 { + __dynamic_array_reserve(array_, elem_size, elem_align, cap, loc) + array.len = len + } +} + +__dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap: int, loc := #caller_location) -> bool { + array := (^Raw_Dynamic_Array)(array_) + + // NOTE(tetra, 2020-01-26): We set the allocator before earlying-out below, because user code is usually written + // assuming that appending/reserving will set the allocator, if it is not already set. + if array.allocator.procedure == nil { + array.allocator = context.allocator + } + assert(array.allocator.procedure != nil) + + if cap <= array.cap { + return true + } + + old_size := array.cap * elem_size + new_size := cap * elem_size + allocator := array.allocator + + new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) + if err != nil { + return false + } + if elem_size == 0 { + array.data = raw_data(new_data) + array.cap = cap + return true + } else if new_data != nil { + array.data = raw_data(new_data) + array.cap = min(cap, len(new_data)/elem_size) + return true + } + return false +} + +__dynamic_array_shrink :: proc(array_: rawptr, elem_size, elem_align: int, new_cap: int, loc := #caller_location) -> (did_shrink: bool) { + array := (^Raw_Dynamic_Array)(array_) + + // NOTE(tetra, 2020-01-26): We set the allocator before earlying-out below, because user code is usually written + // assuming that appending/reserving will set the allocator, if it is not already set. + if array.allocator.procedure == nil { + array.allocator = context.allocator + } + assert(array.allocator.procedure != nil) + + if new_cap > array.cap { + return + } + + new_cap := new_cap + new_cap = max(new_cap, 0) + old_size := array.cap * elem_size + new_size := new_cap * elem_size + allocator := array.allocator + + new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) + if err != nil { + return + } + + array.data = raw_data(new_data) + array.len = min(new_cap, array.len) + array.cap = new_cap + return true +} + +__dynamic_array_resize :: proc(array_: rawptr, elem_size, elem_align: int, len: int, loc := #caller_location) -> bool { + array := (^Raw_Dynamic_Array)(array_) + + ok := __dynamic_array_reserve(array_, elem_size, elem_align, len, loc) + if ok { + array.len = len + } + return ok +} + + +__dynamic_array_append :: proc(array_: rawptr, elem_size, elem_align: int, + items: rawptr, item_count: int, loc := #caller_location) -> int { + array := (^Raw_Dynamic_Array)(array_) + + if items == nil { + return 0 + } + if item_count <= 0 { + return 0 + } + + + ok := true + if array.cap < array.len+item_count { + cap := 2 * array.cap + max(8, item_count) + ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc) + } + // TODO(bill): Better error handling for failed reservation + if !ok { + return array.len + } + + assert(array.data != nil) + data := uintptr(array.data) + uintptr(elem_size*array.len) + + mem_copy(rawptr(data), items, elem_size * item_count) + array.len += item_count + return array.len +} + +__dynamic_array_append_nothing :: proc(array_: rawptr, elem_size, elem_align: int, loc := #caller_location) -> int { + array := (^Raw_Dynamic_Array)(array_) + + ok := true + if array.cap < array.len+1 { + cap := 2 * array.cap + max(8, 1) + ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc) + } + // TODO(bill): Better error handling for failed reservation + if !ok { + return array.len + } + + assert(array.data != nil) + data := uintptr(array.data) + uintptr(elem_size*array.len) + mem_zero(rawptr(data), elem_size) + array.len += 1 + return array.len +} diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin new file mode 100644 index 000000000..491a7974d --- /dev/null +++ b/base/runtime/dynamic_map_internal.odin @@ -0,0 +1,924 @@ +package runtime + +import "core:intrinsics" +_ :: intrinsics + +// High performance, cache-friendly, open-addressed Robin Hood hashing hash map +// data structure with various optimizations for Odin. +// +// Copyright 2022 (c) Dale Weiler +// +// The core of the hash map data structure is the Raw_Map struct which is a +// type-erased representation of the map. This type-erased representation is +// used in two ways: static and dynamic. When static type information is known, +// the procedures suffixed with _static should be used instead of _dynamic. The +// static procedures are optimized since they have type information. Hashing of +// keys, comparison of keys, and data lookup are all optimized. When type +// information is not known, the procedures suffixed with _dynamic should be +// used. The representation of the map is the same for both static and dynamic, +// and procedures of each can be mixed and matched. The purpose of the dynamic +// representation is to enable reflection and runtime manipulation of the map. +// The dynamic procedures all take an additional Map_Info structure parameter +// which carries runtime values describing the size, alignment, and offset of +// various traits of a given key and value type pair. The Map_Info value can +// be created by calling map_info(K, V) with the key and value typeids. +// +// This map implementation makes extensive use of uintptr for representing +// sizes, lengths, capacities, masks, pointers, offsets, and addresses to avoid +// expensive sign extension and masking that would be generated if types were +// casted all over. The only place regular ints show up is in the cap() and +// len() implementations. +// +// To make this map cache-friendly it uses a novel strategy to ensure keys and +// values of the map are always cache-line aligned and that no single key or +// value of any type ever straddles a cache-line. This cache efficiency makes +// for quick lookups because the linear-probe always addresses data in a cache +// friendly way. This is enabled through the use of a special meta-type called +// a Map_Cell which packs as many values of a given type into a local array adding +// internal padding to round to MAP_CACHE_LINE_SIZE. One other benefit to storing +// the internal data in this manner is false sharing no longer occurs when using +// a map, enabling efficient concurrent access of the map data structure with +// minimal locking if desired. + +// With Robin Hood hashing a maximum load factor of 75% is ideal. +MAP_LOAD_FACTOR :: 75 + +// Minimum log2 capacity. +MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements + +// Has to be less than 100% though. +#assert(MAP_LOAD_FACTOR < 100) + +// This is safe to change. The log2 size of a cache-line. At minimum it has to +// be six though. Higher cache line sizes are permitted. +MAP_CACHE_LINE_LOG2 :: 6 + +// The size of a cache-line. +MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2 + +// The minimum cache-line size allowed by this implementation is 64 bytes since +// we need 6 bits in the base pointer to store the integer log2 capacity, which +// at maximum is 63. Odin uses signed integers to represent length and capacity, +// so only 63 bits are needed in the maximum case. +#assert(MAP_CACHE_LINE_SIZE >= 64) + +// Map_Cell type that packs multiple T in such a way to ensure that each T stays +// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0 +// +// This means a value of type T will never straddle a cache-line. +// +// When multiple Ts can fit in a single cache-line the data array will have more +// than one element. When it cannot, the data array will have one element and +// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE. +// +// We rely on the type system to do all the arithmetic and padding for us here. +// +// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit +// more involved as there now may be internal padding. The indexing now becomes +// +// N :: len(Map_Cell(T){}.data) +// i := index / N +// j := index % N +// cell[i].data[j] +// +// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some +// optimizations we can do to eliminate the need for any divisions as N will +// be bounded by [1, 64). +// +// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated +// as a regular array of T, which is the case for hashes. +Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) { + data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T, +} + +// So we can operate on a cell data structure at runtime without any type +// information, we have a simple table that stores some traits about the cell. +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Map_Cell_Info :: struct { + size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits + elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits +} + +// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...} +map_cell_info :: intrinsics.type_map_cell_info + +// Same as the above procedure but at runtime with the cell Map_Cell_Info value. +@(require_results) +map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, index: uintptr) -> uintptr { + // Micro-optimize the common cases to save on integer division. + elements_per_cell := uintptr(info.elements_per_cell) + size_of_cell := uintptr(info.size_of_cell) + switch elements_per_cell { + case 1: + return base + (index * size_of_cell) + case 2: + cell_index := index >> 1 + data_index := index & 1 + size_of_type := uintptr(info.size_of_type) + return base + (cell_index * size_of_cell) + (data_index * size_of_type) + case: + cell_index := index / elements_per_cell + data_index := index % elements_per_cell + size_of_type := uintptr(info.size_of_type) + return base + (cell_index * size_of_cell) + (data_index * size_of_type) + } +} + +// Same as above procedure but with compile-time constant index. +@(require_results) +map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, $INDEX: uintptr) -> uintptr { + elements_per_cell := uintptr(info.elements_per_cell) + size_of_cell := uintptr(info.size_of_cell) + size_of_type := uintptr(info.size_of_type) + cell_index := INDEX / elements_per_cell + data_index := INDEX % elements_per_cell + return base + (cell_index * size_of_cell) + (data_index * size_of_type) +} + +// We always round the capacity to a power of two so this becomes [16]Foo, which +// works out to [4]Cell(Foo). +// +// The following compile-time procedure indexes such a [N]Cell(T) structure as +// if it were a flat array accounting for the internal padding introduced by the +// Cell structure. +@(require_results) +map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T), index: uintptr) -> ^T #no_bounds_check { + N :: size_of(Map_Cell(T){}.data) / size_of(T) when size_of(T) > 0 else 1 + + #assert(N <= MAP_CACHE_LINE_SIZE) + + when size_of(Map_Cell(T)) == size_of([N]T) { + // No padding case, can treat as a regular array of []T. + + return &([^]T)(cells)[index] + } else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) { + // Likely case, N is a power of two because T is a power of two. + + // Compute the integer log 2 of N, this is the shift amount to index the + // correct cell. Odin's intrinsics.count_leading_zeros does not produce a + // constant, hence this approach. We only need to check up to N = 64. + SHIFT :: 1 when N < 2 else + 2 when N < 4 else + 3 when N < 8 else + 4 when N < 16 else + 5 when N < 32 else 6 + #assert(SHIFT <= MAP_CACHE_LINE_LOG2) + // Unique case, no need to index data here since only one element. + when N == 1 { + return &cells[index >> SHIFT].data[0] + } else { + return &cells[index >> SHIFT].data[index & (N - 1)] + } + } else { + // Least likely (and worst case), we pay for a division operation but we + // assume the compiler does not actually generate a division. N will be in the + // range [1, CACHE_LINE_SIZE) and not a power of two. + return &cells[index / N].data[index % N] + } +} + +// len() for map +@(require_results) +map_len :: #force_inline proc "contextless" (m: Raw_Map) -> int { + return int(m.len) +} + +// cap() for map +@(require_results) +map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { + // The data uintptr stores the capacity in the lower six bits which gives the + // a maximum value of 2^6-1, or 63. We store the integer log2 of capacity + // since our capacity is always a power of two. We only need 63 bits as Odin + // represents length and capacity as a signed integer. + return 0 if m.data == 0 else 1 << map_log2_cap(m) +} + +// Query the load factor of the map. This is not actually configurable, but +// some math is needed to compute it. Compute it as a fixed point percentage to +// avoid floating point operations. This division can be optimized out by +// multiplying by the multiplicative inverse of 100. +@(require_results) +map_load_factor :: #force_inline proc "contextless" (log2_capacity: uintptr) -> uintptr { + return ((uintptr(1) << log2_capacity) * MAP_LOAD_FACTOR) / 100 +} + +@(require_results) +map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return map_load_factor(map_log2_cap(m)) +} + +// The data stores the log2 capacity in the lower six bits. This is primarily +// used in the implementation rather than map_cap since the check for data = 0 +// isn't necessary in the implementation. cap() on the otherhand needs to work +// when called on an empty map. +@(require_results) +map_log2_cap :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return m.data & (64 - 1) +} + +// Canonicalize the data by removing the tagged capacity stored in the lower six +// bits of the data uintptr. +@(require_results) +map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return m.data &~ uintptr(64 - 1) +} + + +Map_Hash :: uintptr + +TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1) + +// Procedure to check if a slot is empty for a given hash. This is represented +// by the zero value to make the zero value useful. This is a procedure just +// for prose reasons. +@(require_results) +map_hash_is_empty :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { + return hash == 0 +} + +@(require_results) +map_hash_is_deleted :: #force_no_inline proc "contextless" (hash: Map_Hash) -> bool { + // The MSB indicates a tombstone + return hash & TOMBSTONE_MASK != 0 +} +@(require_results) +map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { + // The MSB indicates a tombstone + return (hash != 0) & (hash & TOMBSTONE_MASK == 0) +} + +@(require_results) +map_seed :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { + return map_seed_from_map_data(map_data(m)) +} + +// splitmix for uintptr +@(require_results) +map_seed_from_map_data :: #force_inline proc "contextless" (data: uintptr) -> uintptr { + when size_of(uintptr) == size_of(u64) { + mix := data + 0x9e3779b97f4a7c15 + mix = (mix ~ (mix >> 30)) * 0xbf58476d1ce4e5b9 + mix = (mix ~ (mix >> 27)) * 0x94d049bb133111eb + return mix ~ (mix >> 31) + } else { + mix := data + 0x9e3779b9 + mix = (mix ~ (mix >> 16)) * 0x21f0aaad + mix = (mix ~ (mix >> 15)) * 0x735a2d97 + return mix ~ (mix >> 15) + } +} + +// Computes the desired position in the array. This is just index % capacity, +// but a procedure as there's some math involved here to recover the capacity. +@(require_results) +map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash) -> uintptr { + // We do not use map_cap since we know the capacity will not be zero here. + capacity := uintptr(1) << map_log2_cap(m) + return uintptr(hash & Map_Hash(capacity - 1)) +} + +@(require_results) +map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr { + // We do not use map_cap since we know the capacity will not be zero here. + capacity := uintptr(1) << map_log2_cap(m) + return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1) +} + +// When working with the type-erased structure at runtime we need information +// about the map to make working with it possible. This info structure stores +// that. +// +// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be +// modified after creation +// +// 32-bytes on 64-bit +// 16-bytes on 32-bit +Map_Info :: struct { + ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit + vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit + key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit + key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit +} + + +// The Map_Info structure is basically a pseudo-table of information for a given K and V pair. +// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...} +map_info :: intrinsics.type_map_info + +@(require_results) +map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (ks: uintptr, vs: uintptr, hs: [^]Map_Hash, sk: uintptr, sv: uintptr) { + INFO_HS := intrinsics.type_map_cell_info(Map_Hash) + + capacity := uintptr(1) << map_log2_cap(m) + ks = map_data(m) + vs = map_cell_index_dynamic(ks, info.ks, capacity) // Skip past ks to get start of vs + hs_ := map_cell_index_dynamic(vs, info.vs, capacity) // Skip past vs to get start of hs + sk = map_cell_index_dynamic(hs_, INFO_HS, capacity) // Skip past hs to get start of sk + // Need to skip past two elements in the scratch key space to get to the start + // of the scratch value space, of which there's only two elements as well. + sv = map_cell_index_dynamic_const(sk, info.ks, 2) + + hs = ([^]Map_Hash)(hs_) + return +} + +@(require_results) +map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (vs: uintptr) { + capacity := uintptr(1) << map_log2_cap(m) + return map_cell_index_dynamic(map_data(m), info.ks, capacity) // Skip past ks to get start of vs +} + + +@(private, require_results) +map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { + round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { + CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 + return (value + CACHE_MASK) &~ CACHE_MASK + } + INFO_HS := intrinsics.type_map_cell_info(Map_Hash) + + size := uintptr(0) + size = round(map_cell_index_dynamic(size, info.ks, capacity)) + size = round(map_cell_index_dynamic(size, info.vs, capacity)) + size = round(map_cell_index_dynamic(size, INFO_HS, capacity)) + size = round(map_cell_index_dynamic(size, info.ks, 2)) // Two additional ks for scratch storage + size = round(map_cell_index_dynamic(size, info.vs, 2)) // Two additional vs for scratch storage + return size +} + +// The only procedure which needs access to the context is the one which allocates the map. +@(require_results) +map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { + result.allocator = allocator // set the allocator always + if log2_capacity == 0 { + return + } + + if log2_capacity >= 64 { + // Overflowed, would be caused by log2_capacity > 64 + return {}, .Out_Of_Memory + } + + capacity := uintptr(1) << max(log2_capacity, MAP_MIN_LOG2_CAPACITY) + + CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 + + size := map_total_allocation_size(capacity, info) + + data := mem_alloc_non_zeroed(int(size), MAP_CACHE_LINE_SIZE, allocator, loc) or_return + data_ptr := uintptr(raw_data(data)) + if data_ptr == 0 { + err = .Out_Of_Memory + return + } + if intrinsics.expect(data_ptr & CACHE_MASK != 0, false) { + panic("allocation not aligned to a cache line", loc) + } else { + result.data = data_ptr | log2_capacity // Tagged pointer representation for capacity. + result.len = 0 + + map_clear_dynamic(&result, info) + } + return +} + +// This procedure has to stack allocate storage to store local keys during the +// Robin Hood hashing technique where elements are swapped in the backing +// arrays to reduce variance. This swapping can only be done with memcpy since +// there is no type information. +// +// This procedure returns the address of the just inserted value. +@(require_results) +map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { + h := h + pos := map_desired_position(m^, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(m^)) - 1 + + ks, vs, hs, sk, sv := map_kvh_data_dynamic(m^, info) + + // Avoid redundant loads of these values + size_of_k := info.ks.size_of_type + size_of_v := info.vs.size_of_type + + k := map_cell_index_dynamic(sk, info.ks, 0) + v := map_cell_index_dynamic(sv, info.vs, 0) + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(ik), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(iv), size_of_v) + + // Temporary k and v dynamic storage for swap below + tk := map_cell_index_dynamic(sk, info.ks, 1) + tv := map_cell_index_dynamic(sv, info.vs, 1) + + swap_loop: for { + element_hash := hs[pos] + + if map_hash_is_empty(element_hash) { + k_dst := map_cell_index_dynamic(ks, info.ks, pos) + v_dst := map_cell_index_dynamic(vs, info.vs, pos) + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + return result if result != 0 else v_dst + } + + if map_hash_is_deleted(element_hash) { + break swap_loop + } + + if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { + if result == 0 { + result = map_cell_index_dynamic(vs, info.vs, pos) + } + + kp := map_cell_index_dynamic(ks, info.ks, pos) + vp := map_cell_index_dynamic(vs, info.vs, pos) + + intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) + + intrinsics.mem_copy_non_overlapping(rawptr(tv), rawptr(v), size_of_v) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(vp), size_of_v) + intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(tv), size_of_v) + + th := h + h = hs[pos] + hs[pos] = th + + distance = probe_distance + } + + pos = (pos + 1) & mask + distance += 1 + } + + // backward shift loop + hs[pos] = 0 + look_ahead: uintptr = 1 + for { + la_pos := (pos + look_ahead) & mask + element_hash := hs[la_pos] + + if map_hash_is_deleted(element_hash) { + look_ahead += 1 + hs[la_pos] = 0 + continue + } + + k_dst := map_cell_index_dynamic(ks, info.ks, pos) + v_dst := map_cell_index_dynamic(vs, info.vs, pos) + + if map_hash_is_empty(element_hash) { + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + return result if result != 0 else v_dst + } + + k_src := map_cell_index_dynamic(ks, info.ks, la_pos) + v_src := map_cell_index_dynamic(vs, info.vs, la_pos) + probe_distance := map_probe_distance(m^, element_hash, la_pos) + + if probe_distance < look_ahead { + // probed can be made ideal while placing saved (ending condition) + if result == 0 { + result = v_dst + } + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + // This will be an ideal move + pos = (la_pos - probe_distance) & mask + look_ahead -= probe_distance + + // shift until we hit ideal/empty + for probe_distance != 0 { + k_dst = map_cell_index_dynamic(ks, info.ks, pos) + v_dst = map_cell_index_dynamic(vs, info.vs, pos) + + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) + hs[pos] = element_hash + hs[la_pos] = 0 + + pos = (pos + 1) & mask + la_pos = (la_pos + 1) & mask + look_ahead = (la_pos - pos) & mask + element_hash = hs[la_pos] + if map_hash_is_empty(element_hash) { + return + } + + probe_distance = map_probe_distance(m^, element_hash, la_pos) + if probe_distance == 0 { + return + } + // can be ideal? + if probe_distance < look_ahead { + pos = (la_pos - probe_distance) & mask + } + k_src = map_cell_index_dynamic(ks, info.ks, la_pos) + v_src = map_cell_index_dynamic(vs, info.vs, la_pos) + } + return + } else if distance < probe_distance - look_ahead { + // shift back probed + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) + hs[pos] = element_hash + hs[la_pos] = 0 + } else { + // place saved, save probed + if result == 0 { + result = v_dst + } + intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) + hs[pos] = h + + intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(k_src), size_of_k) + intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(v_src), size_of_v) + h = hs[la_pos] + hs[la_pos] = 0 + distance = probe_distance - look_ahead + } + + pos = (pos + 1) & mask + distance += 1 + } +} + +@(require_results) +map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { + log2_capacity := map_log2_cap(m^) + new_capacity := uintptr(1) << max(log2_capacity + 1, MAP_MIN_LOG2_CAPACITY) + return map_reserve_dynamic(m, info, new_capacity, loc) +} + + +@(require_results) +map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { + @(require_results) + ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { + z := intrinsics.count_leading_zeros(x) + if z > 0 && x & (x-1) != 0 { + z -= 1 + } + return size_of(uintptr)*8 - 1 - z + } + + if m.allocator.procedure == nil { + m.allocator = context.allocator + } + + new_capacity := new_capacity + old_capacity := uintptr(map_cap(m^)) + + if old_capacity >= new_capacity { + return nil + } + + // ceiling nearest power of two + log2_new_capacity := ceil_log2(new_capacity) + + log2_min_cap := max(MAP_MIN_LOG2_CAPACITY, log2_new_capacity) + + if m.data == 0 { + m^ = map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return + return nil + } + + resized := map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return + + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + + // Cache these loads to avoid hitting them in the for loop. + n := m.len + for i in 0.. (did_shrink: bool, err: Allocator_Error) { + if m.allocator.procedure == nil { + m.allocator = context.allocator + } + + // Cannot shrink the capacity if the number of items in the map would exceed + // one minus the current log2 capacity's resize threshold. That is the shrunk + // map needs to be within the max load factor. + log2_capacity := map_log2_cap(m^) + if uintptr(m.len) >= map_load_factor(log2_capacity - 1) { + return false, nil + } + + shrunk := map_alloc_dynamic(info, log2_capacity - 1, m.allocator) or_return + + capacity := uintptr(1) << log2_capacity + + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + + n := m.len + for i in 0.. Allocator_Error { + ptr := rawptr(map_data(m)) + size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) + err := mem_free_with_size(ptr, size, m.allocator, loc) + #partial switch err { + case .None, .Mode_Not_Implemented: + return nil + } + return err +} + +@(require_results) +map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { + if map_len(m) == 0 { + return 0, false + } + h := info.key_hasher(rawptr(k), map_seed(m)) + p := map_desired_position(m, h) + d := uintptr(0) + c := (uintptr(1) << map_log2_cap(m)) - 1 + ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) + for { + element_hash := hs[p] + if map_hash_is_empty(element_hash) { + return 0, false + } else if d > map_probe_distance(m, element_hash, p) { + return 0, false + } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { + return p, true + } + p = (p + 1) & c + d += 1 + } +} +@(require_results) +map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { + if map_len(m) == 0 { + return false + } + h := info.key_hasher(rawptr(k), map_seed(m)) + p := map_desired_position(m, h) + d := uintptr(0) + c := (uintptr(1) << map_log2_cap(m)) - 1 + ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) + for { + element_hash := hs[p] + if map_hash_is_empty(element_hash) { + return false + } else if d > map_probe_distance(m, element_hash, p) { + return false + } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { + return true + } + p = (p + 1) & c + d += 1 + } +} + + + +@(require_results) +map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { + index := map_lookup_dynamic(m^, info, k) or_return + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + hs[index] |= TOMBSTONE_MASK + old_k = map_cell_index_dynamic(ks, info.ks, index) + old_v = map_cell_index_dynamic(vs, info.vs, index) + m.len -= 1 + ok = true + + mask := (uintptr(1)< (ks: [^]Map_Cell(K), vs: [^]Map_Cell(V), hs: [^]Map_Hash) { + capacity := uintptr(cap(m)) + ks = ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) + vs = ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) + hs = ([^]Map_Hash)(map_cell_index_static(vs, capacity)) + return +} + + +@(require_results) +map_get :: proc "contextless" (m: $T/map[$K]$V, key: K) -> (stored_key: K, stored_value: V, ok: bool) { + rm := transmute(Raw_Map)m + if rm.len == 0 { + return + } + info := intrinsics.type_map_info(T) + key := key + + h := info.key_hasher(&key, map_seed(rm)) + pos := map_desired_position(rm, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(rm)) - 1 + ks, vs, hs := map_kvh_data_static(m) + for { + element_hash := hs[pos] + if map_hash_is_empty(element_hash) { + return + } else if distance > map_probe_distance(rm, element_hash, pos) { + return + } else if element_hash == h { + element_key := map_cell_index_static(ks, pos) + if info.key_equal(&key, rawptr(element_key)) { + element_value := map_cell_index_static(vs, pos) + stored_key = (^K)(element_key)^ + stored_value = (^V)(element_value)^ + ok = true + return + } + + } + pos = (pos + 1) & mask + distance += 1 + } +} + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (ptr: rawptr) { + if m.len == 0 { + return nil + } + pos := map_desired_position(m^, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(m^)) - 1 + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + for { + element_hash := hs[pos] + if map_hash_is_empty(element_hash) { + return nil + } else if distance > map_probe_distance(m^, element_hash, pos) { + return nil + } else if element_hash == h && info.key_equal(key, rawptr(map_cell_index_dynamic(ks, info.ks, pos))) { + return rawptr(map_cell_index_dynamic(vs, info.vs, pos)) + } + pos = (pos + 1) & mask + distance += 1 + } +} + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) { + if m.len >= map_resize_threshold(m^) { + return map_grow_dynamic(m, info, loc), true + } + return nil, false +} + +__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr { + return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc) +} + + +// IMPORTANT: USED WITHIN THE COMPILER +__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr { + if found := __dynamic_map_get(m, info, hash, key); found != nil { + intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type) + return found + } + + hash := hash + err, has_grown := __dynamic_map_check_grow(m, info, loc) + if err != nil { + return nil + } + if has_grown { + hash = info.key_hasher(key, map_seed(m^)) + } + + result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) + m.len += 1 + return rawptr(result) +} + +// IMPORTANT: USED WITHIN THE COMPILER +@(private) +__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { + return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) +} + + + +// NOTE: the default hashing algorithm derives from fnv64a, with some minor modifications to work for `map` type: +// +// * Convert a `0` result to `1` +// * "empty entry" +// * Prevent the top bit from being set +// * "deleted entry" +// +// Both of these modification are necessary for the implementation of the `map` + +INITIAL_HASH_SEED :: 0xcbf29ce484222325 + +HASH_MASK :: 1 << (8*size_of(uintptr) - 1) -1 + +default_hasher :: #force_inline proc "contextless" (data: rawptr, seed: uintptr, N: int) -> uintptr { + h := u64(seed) + INITIAL_HASH_SEED + p := ([^]byte)(data) + for _ in 0.. uintptr { + str := (^[]byte)(data) + return default_hasher(raw_data(str^), seed, len(str)) +} +default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr { + h := u64(seed) + INITIAL_HASH_SEED + if ptr := (^[^]byte)(data)^; ptr != nil { + for ptr[0] != 0 { + h = (h ~ u64(ptr[0])) * 0x100000001b3 + ptr = ptr[1:] + } + } + h &= HASH_MASK + return uintptr(h) | uintptr(uintptr(h) == 0) +} diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin new file mode 100644 index 000000000..f494a509e --- /dev/null +++ b/base/runtime/entry_unix.odin @@ -0,0 +1,59 @@ +//+private +//+build linux, darwin, freebsd, openbsd +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when ODIN_BUILD_MODE == .Dynamic { + @(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/) + _odin_entry_point :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/) + _odin_exit_point :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + return 0 + } +} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + when ODIN_NO_CRT { + // NOTE(flysand): We need to start from assembly because we need + // to retrieve argc and argv from the stack + when ODIN_ARCH == .amd64 { + @require foreign import entry "entry_unix_no_crt_amd64.asm" + SYS_exit :: 60 + } else when ODIN_ARCH == .i386 { + @require foreign import entry "entry_unix_no_crt_i386.asm" + SYS_exit :: 1 + } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { + @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" + SYS_exit :: 1 + } + @(link_name="_start_odin", linkage="strong", require) + _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + intrinsics.syscall(SYS_exit, 0) + unreachable() + } + } else { + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } +} diff --git a/base/runtime/entry_unix_no_crt_amd64.asm b/base/runtime/entry_unix_no_crt_amd64.asm new file mode 100644 index 000000000..f0bdce8d7 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_amd64.asm @@ -0,0 +1,43 @@ +bits 64 + +extern _start_odin +global _start + +section .text + +;; Entry point for programs that specify -no-crt option +;; This entry point should be compatible with dynamic loaders on linux +;; The parameters the dynamic loader passes to the _start function: +;; RDX = pointer to atexit function +;; The stack layout is as follows: +;; +-------------------+ +;; NULL +;; +-------------------+ +;; envp[m] +;; +-------------------+ +;; ... +;; +-------------------+ +;; envp[0] +;; +-------------------+ +;; NULL +;; +-------------------+ +;; argv[n] +;; +-------------------+ +;; ... +;; +-------------------+ +;; argv[0] +;; +-------------------+ +;; argc +;; +-------------------+ <------ RSP +;; +_start: + ;; Mark stack frame as the top of the stack + xor rbp, rbp + ;; Load argc into 1st param reg, argv into 2nd param reg + pop rdi + mov rdx, rsi + ;; Align stack pointer down to 16-bytes (sysv calling convention) + and rsp, -16 + ;; Call into odin entry point + call _start_odin + jmp $$ \ No newline at end of file diff --git a/base/runtime/entry_unix_no_crt_darwin_arm64.asm b/base/runtime/entry_unix_no_crt_darwin_arm64.asm new file mode 100644 index 000000000..0f71fbdf8 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_darwin_arm64.asm @@ -0,0 +1,20 @@ + .section __TEXT,__text + + ; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin. + ; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine. + .build_version macos, 10, 0 + + .extern __start_odin + + .global _main + .align 2 +_main: + mov x5, sp ; use x5 as the stack pointer + + str x0, [x5] ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) + str x1, [x5, #8] ; get argv into x1 + + and sp, x5, #~15 ; force 16-byte alignment of the stack + + bl __start_odin ; call into Odin entry point + ret ; should never get here diff --git a/base/runtime/entry_unix_no_crt_i386.asm b/base/runtime/entry_unix_no_crt_i386.asm new file mode 100644 index 000000000..a61d56a16 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_i386.asm @@ -0,0 +1,18 @@ +bits 32 + +extern _start_odin +global _start + +section .text + +;; NOTE(flysand): For description see the corresponding *_amd64.asm file +;; also I didn't test this on x86-32 +_start: + xor ebp, rbp + pop ecx + mov eax, esp + and esp, -16 + push eax + push ecx + call _start_odin + jmp $$ \ No newline at end of file diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin new file mode 100644 index 000000000..e7f3f156f --- /dev/null +++ b/base/runtime/entry_wasm.odin @@ -0,0 +1,20 @@ +//+private +//+build wasm32, wasm64p32 +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + @(link_name="_start", linkage="strong", require, export) + _start :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_end", linkage="strong", require, export) + _end :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } +} \ No newline at end of file diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin new file mode 100644 index 000000000..b6fbe1dcc --- /dev/null +++ b/base/runtime/entry_windows.odin @@ -0,0 +1,50 @@ +//+private +//+build windows +//+no-instrumentation +package runtime + +import "core:intrinsics" + +when ODIN_BUILD_MODE == .Dynamic { + @(link_name="DllMain", linkage="strong", require) + DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { + context = default_context() + + // Populate Windows DLL-specific global + dll_forward_reason = DLL_Forward_Reason(fdwReason) + + switch dll_forward_reason { + case .Process_Attach: + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + case .Process_Detach: + #force_no_inline _cleanup_runtime() + case .Thread_Attach: + break + case .Thread_Detach: + break + } + return true + } +} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { + when ODIN_ARCH == .i386 || ODIN_NO_CRT { + @(link_name="mainCRTStartup", linkage="strong", require) + mainCRTStartup :: proc "system" () -> i32 { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } else { + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } +} \ No newline at end of file diff --git a/base/runtime/error_checks.odin b/base/runtime/error_checks.odin new file mode 100644 index 000000000..ea6333c29 --- /dev/null +++ b/base/runtime/error_checks.odin @@ -0,0 +1,292 @@ +package runtime + +@(no_instrumentation) +bounds_trap :: proc "contextless" () -> ! { + when ODIN_OS == .Windows { + windows_trap_array_bounds() + } else { + trap() + } +} + +@(no_instrumentation) +type_assertion_trap :: proc "contextless" () -> ! { + when ODIN_OS == .Windows { + windows_trap_type_assertion() + } else { + trap() + } +} + + +bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { + if uint(index) < uint(count) { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Index ") + print_i64(i64(index)) + print_string(" is out of range 0..<") + print_i64(i64(count)) + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, index, count) +} + +@(no_instrumentation) +slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid slice indices ") + print_i64(i64(lo)) + print_string(":") + print_i64(i64(hi)) + print_string(" is out of range 0..<") + print_i64(i64(len)) + print_byte('\n') + bounds_trap() +} + +@(no_instrumentation) +multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid slice indices ") + print_i64(i64(lo)) + print_string(":") + print_i64(i64(hi)) + print_byte('\n') + bounds_trap() +} + + +multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { + if lo <= hi { + return + } + multi_pointer_slice_handle_error(file, line, column, lo, hi) +} + +slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { + if 0 <= hi && hi <= len { + return + } + slice_handle_error(file, line, column, 0, hi, len) +} + +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 + } + slice_handle_error(file, line, column, lo, hi, len) +} + +dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { + if 0 <= low && low <= high && high <= max { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid dynamic array indices ") + print_i64(i64(low)) + print_string(":") + print_i64(i64(high)) + print_string(" is out of range 0..<") + print_i64(i64(max)) + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, low, high, max) +} + + +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) { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Matrix indices [") + print_i64(i64(row_index)) + print_string(", ") + print_i64(i64(column_index)) + print_string(" is out of range [0..<") + print_i64(i64(row_count)) + print_string(", 0..<") + print_i64(i64(column_count)) + print_string("]") + print_byte('\n') + bounds_trap() + } + handle_error(file, line, column, row_index, column_index, row_count, column_count) +} + + +when ODIN_NO_RTTI { + type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion\n") + type_assertion_trap() + } + handle_error(file, line, column) + } + + type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion\n") + type_assertion_trap() + } + handle_error(file, line, column) + } +} else { + type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) { + if ok { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion from ") + print_typeid(from) + print_string(" to ") + print_typeid(to) + print_byte('\n') + type_assertion_trap() + } + handle_error(file, line, column, from, to) + } + + type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) { + if ok { + return + } + + variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid { + if id == nil || data == nil { + return id + } + ti := type_info_base(type_info_of(id)) + #partial switch v in ti.variant { + case Type_Info_Any: + return (^any)(data).id + case Type_Info_Union: + tag_ptr := uintptr(data) + v.tag_offset + idx := 0 + switch v.tag_type.size { + case 1: idx = int((^u8)(tag_ptr)^) - 1 + case 2: idx = int((^u16)(tag_ptr)^) - 1 + case 4: idx = int((^u32)(tag_ptr)^) - 1 + case 8: idx = int((^u64)(tag_ptr)^) - 1 + case 16: idx = int((^u128)(tag_ptr)^) - 1 + } + if idx < 0 { + return nil + } else if idx < len(v.variants) { + return v.variants[idx].id + } + } + return id + } + + @(cold, no_instrumentation) + handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { + + actual := variant_type(from, from_data) + + print_caller_location(Source_Code_Location{file, line, column, ""}) + print_string(" Invalid type assertion from ") + print_typeid(from) + print_string(" to ") + print_typeid(to) + if actual != from { + print_string(", actual type: ") + print_typeid(actual) + } + print_byte('\n') + type_assertion_trap() + } + handle_error(file, line, column, from, to, from_data) + } +} + + +make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { + if 0 <= len { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { + print_caller_location(loc) + print_string(" Invalid slice length for make: ") + print_i64(i64(len)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, len) +} + +make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { + if 0 <= len && len <= cap { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { + print_caller_location(loc) + print_string(" Invalid dynamic array parameters for make: ") + print_i64(i64(len)) + print_byte(':') + print_i64(i64(cap)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, len, cap) +} + +make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { + if 0 <= cap { + return + } + @(cold, no_instrumentation) + handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { + print_caller_location(loc) + print_string(" Invalid map capacity for make: ") + print_i64(i64(cap)) + print_byte('\n') + bounds_trap() + } + handle_error(loc, cap) +} + + + + + +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) +} + +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) +} + +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) +} + +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/base/runtime/internal.odin b/base/runtime/internal.odin new file mode 100644 index 000000000..a03c2a701 --- /dev/null +++ b/base/runtime/internal.odin @@ -0,0 +1,1036 @@ +package runtime + +import "core:intrinsics" + +@(private="file") +IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 + +@(private) +RUNTIME_LINKAGE :: "strong" when ( + (ODIN_USE_SEPARATE_MODULES || + ODIN_BUILD_MODE == .Dynamic || + !ODIN_NO_CRT) && + !IS_WASM) else "internal" +RUNTIME_REQUIRE :: !ODIN_TILDE + +@(private) +__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 + + +@(private) +byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { + return ([^]byte)(data)[:max(len, 0)] +} + +is_power_of_two_int :: #force_inline proc(x: int) -> bool { + if x <= 0 { + return false + } + return (x & (x-1)) == 0 +} + +align_forward_int :: #force_inline proc(ptr, align: int) -> int { + assert(is_power_of_two_int(align)) + + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p +} + +is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool { + if x <= 0 { + return false + } + return (x & (x-1)) == 0 +} + +align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two_uintptr(align)) + + p := ptr + modulo := p & (align-1) + if modulo != 0 { + p += align - modulo + } + return p +} + +mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { + if data == nil { + return nil + } + if len <= 0 { + return data + } + intrinsics.mem_zero(data, len) + return data +} + +mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { + if src != nil && dst != src && len > 0 { + // NOTE(bill): This _must_ be implemented like C's memmove + intrinsics.mem_copy(dst, src, len) + } + return dst +} + +mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { + if src != nil && dst != src && len > 0 { + // NOTE(bill): This _must_ be implemented like C's memcpy + intrinsics.mem_copy_non_overlapping(dst, src, len) + } + return dst +} + +DEFAULT_ALIGNMENT :: 2*align_of(rawptr) + +mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 { + return nil, nil + } + if allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) +} + +mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 || allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) +} + +mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + if size == 0 || allocator.procedure == nil { + return nil, nil + } + return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, size, alignment, nil, 0, loc) +} + +mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if ptr == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, 0, loc) + return err +} + +mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if ptr == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc) + return err +} + +mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + if bytes == nil || allocator.procedure == nil { + return nil + } + _, err := allocator.procedure(allocator.data, .Free, 0, 0, raw_data(bytes), len(bytes), loc) + return err +} + + +mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { + if allocator.procedure != nil { + _, err = allocator.procedure(allocator.data, .Free_All, 0, 0, nil, 0, loc) + } + return +} + +_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + if allocator.procedure == nil { + return nil, nil + } + if new_size == 0 { + if ptr != nil { + _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) + return + } + return + } else if ptr == nil { + if should_zero { + return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } + } else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 { + data = ([^]byte)(ptr)[:old_size] + return + } + + if should_zero { + data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) + } else { + data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc) + } + if err == .Mode_Not_Implemented { + if should_zero { + data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } + if err != nil { + return + } + copy(data, ([^]byte)(ptr)[:old_size]) + _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) + } + return +} + +mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) +} +non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) +} + +memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { + switch { + case n == 0: return true + case x == y: return true + } + a, b := ([^]byte)(x), ([^]byte)(y) + length := uint(n) + + for i := uint(0); i < length; i += 1 { + if a[i] != b[i] { + return false + } + } + return true + +/* + + when size_of(uint) == 8 { + if word_length := length >> 3; word_length != 0 { + for _ in 0..> 2; word_length != 0 { + for _ in 0.. int #no_bounds_check { + switch { + case a == b: return 0 + case a == nil: return -1 + case b == nil: return +1 + } + + x := uintptr(a) + y := uintptr(b) + n := uintptr(n) + + SU :: size_of(uintptr) + fast := n/SU + 1 + offset := (fast-1)*SU + curr_block := uintptr(0) + if n < SU { + fast = 0 + } + + for /**/; curr_block < fast; curr_block += 1 { + va := (^uintptr)(x + curr_block * size_of(uintptr))^ + vb := (^uintptr)(y + curr_block * size_of(uintptr))^ + if va ~ vb != 0 { + for pos := curr_block*SU; pos < n; pos += 1 { + a := (^byte)(x+pos)^ + b := (^byte)(y+pos)^ + if a ~ b != 0 { + return -1 if (int(a) - int(b)) < 0 else +1 + } + } + } + } + + for /**/; offset < n; offset += 1 { + a := (^byte)(x+offset)^ + b := (^byte)(y+offset)^ + if a ~ b != 0 { + return -1 if (int(a) - int(b)) < 0 else +1 + } + } + + return 0 +} + +memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check { + x := uintptr(a) + n := uintptr(n) + + SU :: size_of(uintptr) + fast := n/SU + 1 + offset := (fast-1)*SU + curr_block := uintptr(0) + if n < SU { + fast = 0 + } + + for /**/; curr_block < fast; curr_block += 1 { + va := (^uintptr)(x + curr_block * size_of(uintptr))^ + if va ~ 0 != 0 { + for pos := curr_block*SU; pos < n; pos += 1 { + a := (^byte)(x+pos)^ + if a ~ 0 != 0 { + return -1 if int(a) < 0 else +1 + } + } + } + } + + for /**/; offset < n; offset += 1 { + a := (^byte)(x+offset)^ + if a ~ 0 != 0 { + return -1 if int(a) < 0 else +1 + } + } + + return 0 +} + +string_eq :: proc "contextless" (lhs, rhs: string) -> bool { + x := transmute(Raw_String)lhs + y := transmute(Raw_String)rhs + if x.len != y.len { + return false + } + return #force_inline memory_equal(x.data, y.data, x.len) +} + +string_cmp :: proc "contextless" (a, b: string) -> int { + x := transmute(Raw_String)a + y := transmute(Raw_String)b + + ret := memory_compare(x.data, y.data, min(x.len, y.len)) + if ret == 0 && x.len != y.len { + return -1 if x.len < y.len else +1 + } + return ret +} + +string_ne :: #force_inline proc "contextless" (a, b: string) -> bool { return !string_eq(a, b) } +string_lt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) < 0 } +string_gt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) > 0 } +string_le :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) <= 0 } +string_ge :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) >= 0 } + +cstring_len :: proc "contextless" (s: cstring) -> int { + p0 := uintptr((^byte)(s)) + p := p0 + for p != 0 && (^byte)(p)^ != 0 { + p += 1 + } + return int(p - p0) +} + +cstring_to_string :: proc "contextless" (s: cstring) -> string { + if s == nil { + return "" + } + ptr := (^byte)(s) + n := cstring_len(s) + return transmute(string)Raw_String{ptr, n} +} + + +cstring_eq :: proc "contextless" (lhs, rhs: cstring) -> bool { + x := ([^]byte)(lhs) + y := ([^]byte)(rhs) + if x == y { + return true + } + if (x == nil) ~ (y == nil) { + return false + } + xn := cstring_len(lhs) + yn := cstring_len(rhs) + if xn != yn { + return false + } + return #force_inline memory_equal(x, y, xn) +} + +cstring_cmp :: proc "contextless" (lhs, rhs: cstring) -> int { + x := ([^]byte)(lhs) + y := ([^]byte)(rhs) + if x == y { + return 0 + } + if (x == nil) ~ (y == nil) { + return -1 if x == nil else +1 + } + xn := cstring_len(lhs) + yn := cstring_len(rhs) + ret := memory_compare(x, y, min(xn, yn)) + if ret == 0 && xn != yn { + return -1 if xn < yn else +1 + } + return ret +} + +cstring_ne :: #force_inline proc "contextless" (a, b: cstring) -> bool { return !cstring_eq(a, b) } +cstring_lt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) < 0 } +cstring_gt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) > 0 } +cstring_le :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) <= 0 } +cstring_ge :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) >= 0 } + + +complex32_eq :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex32_ne :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + +complex64_eq :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex64_ne :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + +complex128_eq :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) == real(b) && imag(a) == imag(b) } +complex128_ne :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) != real(b) || imag(a) != imag(b) } + + +quaternion64_eq :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion64_ne :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + +quaternion128_eq :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion128_ne :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + +quaternion256_eq :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } +quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } + + +string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { + // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 + + @static accept_sizes := [256]u8{ + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6f + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7f + + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8f + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9f + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xa0-0xaf + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xb0-0xbf + 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xc0-0xcf + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xd0-0xdf + 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xe0-0xef + 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xf0-0xff + } + Accept_Range :: struct {lo, hi: u8} + + @static accept_ranges := [5]Accept_Range{ + {0x80, 0xbf}, + {0xa0, 0xbf}, + {0x80, 0x9f}, + {0x90, 0xbf}, + {0x80, 0x8f}, + } + + MASKX :: 0b0011_1111 + MASK2 :: 0b0001_1111 + MASK3 :: 0b0000_1111 + MASK4 :: 0b0000_0111 + + LOCB :: 0b1000_0000 + HICB :: 0b1011_1111 + + + RUNE_ERROR :: '\ufffd' + + n := len(s) + if n < 1 { + return RUNE_ERROR, 0 + } + s0 := s[0] + x := accept_sizes[s0] + if x >= 0xF0 { + mask := rune(x) << 31 >> 31 // NOTE(bill): Create 0x0000 or 0xffff. + return rune(s[0])&~mask | RUNE_ERROR&mask, 1 + } + sz := x & 7 + accept := accept_ranges[x>>4] + if n < int(sz) { + return RUNE_ERROR, 1 + } + b1 := s[1] + if b1 < accept.lo || accept.hi < b1 { + return RUNE_ERROR, 1 + } + if sz == 2 { + return rune(s0&MASK2)<<6 | rune(b1&MASKX), 2 + } + b2 := s[2] + if b2 < LOCB || HICB < b2 { + return RUNE_ERROR, 1 + } + if sz == 3 { + return rune(s0&MASK3)<<12 | rune(b1&MASKX)<<6 | rune(b2&MASKX), 3 + } + b3 := s[3] + if b3 < LOCB || HICB < b3 { + return RUNE_ERROR, 1 + } + return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4 +} + +string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { + RUNE_ERROR :: '\ufffd' + RUNE_SELF :: 0x80 + UTF_MAX :: 4 + + r: rune + size: int + start, end, limit: int + + end = len(s) + if end == 0 { + return RUNE_ERROR, 0 + } + start = end-1 + r = rune(s[start]) + if r < RUNE_SELF { + return r, 1 + } + + limit = max(end - UTF_MAX, 0) + + for start-=1; start >= limit; start-=1 { + if (s[start] & 0xc0) != RUNE_SELF { + break + } + } + + start = max(start, 0) + r, size = string_decode_rune(s[start:end]) + if start+size != end { + return RUNE_ERROR, 1 + } + return r, size +} + +abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * f16(intrinsics.sqrt(f32(1 + q*q))) +} +abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) +} +abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { + p, q := abs(real(x)), abs(imag(x)) + if p < q { + p, q = q, p + } + if p == 0 { + return 0 + } + q = q / p + return p * intrinsics.sqrt(1 + q*q) +} +abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k))) +} +abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return intrinsics.sqrt(r*r + i*i + j*j + k*k) +} +abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 { + r, i, j, k := real(x), imag(x), jmag(x), kmag(x) + return intrinsics.sqrt(r*r + i*i + j*j + k*k) +} + + +quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 { + e, f: f16 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + + +quo_complex64 :: proc "contextless" (n, m: complex64) -> complex64 { + e, f: f32 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + +quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 { + e, f: f64 + + if abs(real(m)) >= abs(imag(m)) { + ratio := imag(m) / real(m) + denom := real(m) + ratio*imag(m) + e = (real(n) + imag(n)*ratio) / denom + f = (imag(n) - real(n)*ratio) / denom + } else { + ratio := real(m) / imag(m) + denom := imag(m) + ratio*real(m) + e = (real(n)*ratio + imag(n)) / denom + f = (imag(n)*ratio - real(n)) / denom + } + + return complex(e, f) +} + +mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 + t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 + t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 + t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { + q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) + r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + + invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) + + t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 + t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 + t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + + return quaternion(w=t0, x=t1, y=t2, z=t3) +} + +@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +truncsfhf2 :: proc "c" (value: f32) -> __float16 { + v: struct #raw_union { i: u32, f: f32 } + i, s, e, m: i32 + + v.f = value + i = i32(v.i) + + s = (i >> 16) & 0x00008000 + e = ((i >> 23) & 0x000000ff) - (127 - 15) + m = i & 0x007fffff + + + if e <= 0 { + if e < -10 { + return transmute(__float16)u16(s) + } + m = (m | 0x00800000) >> u32(1 - e) + + if m & 0x00001000 != 0 { + m += 0x00002000 + } + + return transmute(__float16)u16(s | (m >> 13)) + } else if e == 0xff - (127 - 15) { + if m == 0 { + return transmute(__float16)u16(s | 0x7c00) /* NOTE(bill): infinity */ + } else { + /* NOTE(bill): NAN */ + m >>= 13 + return transmute(__float16)u16(s | 0x7c00 | m | i32(m == 0)) + } + } else { + if m & 0x00001000 != 0 { + m += 0x00002000 + if (m & 0x00800000) != 0 { + m = 0 + e += 1 + } + } + + if e > 30 { + f := i64(1e12) + for j := 0; j < 10; j += 1 { + /* NOTE(bill): Cause overflow */ + g := intrinsics.volatile_load(&f) + g *= g + intrinsics.volatile_store(&f, g) + } + + return transmute(__float16)u16(s | 0x7c00) + } + + return transmute(__float16)u16(s | (e << 10) | (m >> 13)) + } +} + + +@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +truncdfhf2 :: proc "c" (value: f64) -> __float16 { + return truncsfhf2(f32(value)) +} + +@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 { + fp32 :: struct #raw_union { u: u32, f: f32 } + + value := transmute(u16)value_ + v: fp32 + magic, inf_or_nan: fp32 + magic.u = u32((254 - 15) << 23) + inf_or_nan.u = u32((127 + 16) << 23) + + v.u = u32(value & 0x7fff) << 13 + v.f *= magic.f + if v.f >= inf_or_nan.f { + v.u |= 255 << 23 + } + v.u |= u32(value & 0x8000) << 16 + return v.f +} + + +@(link_name="__gnu_f2h_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +gnu_f2h_ieee :: proc "c" (value: f32) -> __float16 { + return truncsfhf2(value) +} + +@(link_name="__extendhfsf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +extendhfsf2 :: proc "c" (value: __float16) -> f32 { + return gnu_h2f_ieee(value) +} + + + +@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +floattidf :: proc "c" (a: i128) -> f64 { +when IS_WASM { + return 0 +} else { + DBL_MANT_DIG :: 53 + if a == 0 { + return 0.0 + } + a := a + N :: size_of(i128) * 8 + s := a >> (N-1) + a = (a ~ s) - s + sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits + e := i32(sd - 1) // exponent + if sd > DBL_MANT_DIG { + switch sd { + case DBL_MANT_DIG + 1: + a <<= 1 + case DBL_MANT_DIG + 2: + // okay + case: + a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | + i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) + } + + a |= i128((a & 4) != 0) + a += 1 + a >>= 2 + + if a & (i128(1) << DBL_MANT_DIG) != 0 { + a >>= 1 + e += 1 + } + } else { + a <<= u128(DBL_MANT_DIG - sd) & 127 + } + fb: [2]u32 + fb[1] = (u32(s) & 0x80000000) | // sign + (u32(e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + fb[0] = u32(a) // mantissa-low + return transmute(f64)fb +} +} + + +@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +floattidf_unsigned :: proc "c" (a: u128) -> f64 { +when IS_WASM { + return 0 +} else { + DBL_MANT_DIG :: 53 + if a == 0 { + return 0.0 + } + a := a + N :: size_of(u128) * 8 + sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits + e := i32(sd - 1) // exponent + if sd > DBL_MANT_DIG { + switch sd { + case DBL_MANT_DIG + 1: + a <<= 1 + case DBL_MANT_DIG + 2: + // okay + case: + a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | + u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) + } + + a |= u128((a & 4) != 0) + a += 1 + a >>= 2 + + if a & (1 << DBL_MANT_DIG) != 0 { + a >>= 1 + e += 1 + } + } else { + a <<= u128(DBL_MANT_DIG - sd) + } + fb: [2]u32 + fb[1] = (0) | // sign + u32((e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + fb[0] = u32(a) // mantissa-low + return transmute(f64)fb +} +} + + + +@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 { + // TODO(bill): implement `fixunsdfti` correctly + x := u64(a) + return u128(x) +} + +@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { + // TODO(bill): implement `fixunsdfdi` correctly + x := i64(a) + return i128(x) +} + + + + +@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +umodti3 :: proc "c" (a, b: u128) -> u128 { + r: u128 = --- + _ = udivmod128(a, b, &r) + return r +} + + +@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { + return udivmod128(a, b, rem) +} + +@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +udivti3 :: proc "c" (a, b: u128) -> u128 { + return udivmodti4(a, b, nil) +} + + +@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +modti3 :: proc "c" (a, b: i128) -> i128 { + s_a := a >> (128 - 1) + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a + bn := (b ~ s_b) - s_b + + r: u128 = --- + _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) + return (transmute(i128)r ~ s_a) - s_a +} + + +@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { + u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) + return transmute(i128)u +} + +@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +divti3 :: proc "c" (a, b: i128) -> i128 { + u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) + return transmute(i128)u +} + + +@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfti :: proc(a: u64) -> i128 { + significandBits :: 52 + typeWidth :: (size_of(u64)*8) + exponentBits :: (typeWidth - significandBits - 1) + maxExponent :: ((1 << exponentBits) - 1) + exponentBias :: (maxExponent >> 1) + + implicitBit :: (u64(1) << significandBits) + significandMask :: (implicitBit - 1) + signBit :: (u64(1) << (significandBits + exponentBits)) + absMask :: (signBit - 1) + exponentMask :: (absMask ~ significandMask) + + // Break a into sign, exponent, significand + aRep := a + aAbs := aRep & absMask + sign := i128(-1 if aRep & signBit != 0 else 1) + exponent := u64((aAbs >> significandBits) - exponentBias) + significand := u64((aAbs & significandMask) | implicitBit) + + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate. + if exponent >= size_of(i128) * 8 { + return max(i128) if sign == 1 else min(i128) + } + + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if exponent < significandBits { + return sign * i128(significand >> (significandBits - exponent)) + } else { + return sign * (i128(significand) << (exponent - significandBits)) + } + +} diff --git a/base/runtime/os_specific.odin b/base/runtime/os_specific.odin new file mode 100644 index 000000000..022d315d4 --- /dev/null +++ b/base/runtime/os_specific.odin @@ -0,0 +1,7 @@ +package runtime + +_OS_Errno :: distinct int + +os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return _os_write(data) +} diff --git a/base/runtime/os_specific_any.odin b/base/runtime/os_specific_any.odin new file mode 100644 index 000000000..6a96655c4 --- /dev/null +++ b/base/runtime/os_specific_any.odin @@ -0,0 +1,16 @@ +//+build !darwin +//+build !freestanding +//+build !js +//+build !wasi +//+build !windows +package runtime + +import "core:os" + +// TODO(bill): reimplement `os.write` so that it does not rely on package os +// NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + context = default_context() + n, err := os.write(os.stderr, data) + return int(n), _OS_Errno(err) +} diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin new file mode 100644 index 000000000..5de9a7d57 --- /dev/null +++ b/base/runtime/os_specific_darwin.odin @@ -0,0 +1,12 @@ +//+build darwin +package runtime + +import "core:intrinsics" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + ret := intrinsics.syscall(0x2000004, 1, uintptr(raw_data(data)), uintptr(len(data))) + if ret < 0 { + return 0, _OS_Errno(-ret) + } + return int(ret), 0 +} diff --git a/base/runtime/os_specific_freestanding.odin b/base/runtime/os_specific_freestanding.odin new file mode 100644 index 000000000..a6d04cefb --- /dev/null +++ b/base/runtime/os_specific_freestanding.odin @@ -0,0 +1,7 @@ +//+build freestanding +package runtime + +// TODO(bill): reimplement `os.write` +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return 0, -1 +} diff --git a/base/runtime/os_specific_js.odin b/base/runtime/os_specific_js.odin new file mode 100644 index 000000000..246141d87 --- /dev/null +++ b/base/runtime/os_specific_js.odin @@ -0,0 +1,12 @@ +//+build js +package runtime + +foreign import "odin_env" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + foreign odin_env { + write :: proc "contextless" (fd: u32, p: []byte) --- + } + write(1, data) + return len(data), 0 +} diff --git a/base/runtime/os_specific_wasi.odin b/base/runtime/os_specific_wasi.odin new file mode 100644 index 000000000..3f69504ee --- /dev/null +++ b/base/runtime/os_specific_wasi.odin @@ -0,0 +1,10 @@ +//+build wasi +package runtime + +import "core:sys/wasm/wasi" + +_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + data := (wasi.ciovec_t)(data) + n, err := wasi.fd_write(1, {data}) + return int(n), _OS_Errno(err) +} diff --git a/base/runtime/os_specific_windows.odin b/base/runtime/os_specific_windows.odin new file mode 100644 index 000000000..4a5907466 --- /dev/null +++ b/base/runtime/os_specific_windows.odin @@ -0,0 +1,135 @@ +//+build windows +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private="file") +@(default_calling_convention="system") +foreign kernel32 { + // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency + + // os_write + GetStdHandle :: proc(which: u32) -> rawptr --- + SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- + WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- + GetLastError :: proc() -> u32 --- + + // default_allocator + GetProcessHeap :: proc() -> rawptr --- + HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- + HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- + HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- +} + +_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { + if len(data) == 0 { + return 0, 0 + } + + STD_ERROR_HANDLE :: ~u32(0) -12 + 1 + HANDLE_FLAG_INHERIT :: 0x00000001 + MAX_RW :: 1<<30 + + h := GetStdHandle(STD_ERROR_HANDLE) + when size_of(uintptr) == 8 { + SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) + } + + single_write_length: u32 + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := u32(min(i32(remaining), MAX_RW)) + + e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + err = _OS_Errno(GetLastError()) + n = int(total_write) + return + } + total_write += i64(single_write_length) + } + n = int(total_write) + return +} + +heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) +} +heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + heap_free(ptr) + return nil + } + if ptr == nil { + return heap_alloc(new_size) + } + + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) +} +heap_free :: proc "contextless" (ptr: rawptr) { + if ptr == nil { + return + } + HeapFree(GetProcessHeap(), 0, ptr) +} + + +// +// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. +// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert +// padding. We also store the original pointer returned by heap_alloc right before +// the pointer we return to the user. +// + + + +_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { + if size == 0 { + _windows_default_free(old_ptr) + return nil, nil + } + + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := ([^]rawptr)(old_ptr)[-1] + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) + } + aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):] + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space || allocated_mem == nil { + return nil, .Out_Of_Memory + } + + aligned_mem = ([^]byte)(aligned_ptr) + ([^]rawptr)(aligned_mem)[-1] = allocated_mem + + return aligned_mem[:size], nil +} + +_windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) { + return _windows_default_alloc_or_resize(size, alignment, nil, zero_memory) +} + + +_windows_default_free :: proc "contextless" (ptr: rawptr) { + if ptr != nil { + heap_free(([^]rawptr)(ptr)[-1]) + } +} + +_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) { + return _windows_default_alloc_or_resize(new_size, new_alignment, p) +} diff --git a/base/runtime/print.odin b/base/runtime/print.odin new file mode 100644 index 000000000..87c8757d5 --- /dev/null +++ b/base/runtime/print.odin @@ -0,0 +1,489 @@ +package runtime + +_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" + +@(private="file") +_INTEGER_DIGITS_VAR := _INTEGER_DIGITS + +when !ODIN_NO_RTTI { + print_any_single :: proc "contextless" (arg: any) { + x := arg + if x.data == nil { + print_string("nil") + return + } + + if loc, ok := x.(Source_Code_Location); ok { + print_caller_location(loc) + return + } + x.id = typeid_base(x.id) + switch v in x { + case typeid: print_typeid(v) + case ^Type_Info: print_type(v) + + case string: print_string(v) + case cstring: print_string(string(v)) + case []byte: print_string(string(v)) + + case rune: print_rune(v) + + case u8: print_u64(u64(v)) + case u16: print_u64(u64(v)) + case u16le: print_u64(u64(v)) + case u16be: print_u64(u64(v)) + case u32: print_u64(u64(v)) + case u32le: print_u64(u64(v)) + case u32be: print_u64(u64(v)) + case u64: print_u64(u64(v)) + case u64le: print_u64(u64(v)) + case u64be: print_u64(u64(v)) + + case i8: print_i64(i64(v)) + case i16: print_i64(i64(v)) + case i16le: print_i64(i64(v)) + case i16be: print_i64(i64(v)) + case i32: print_i64(i64(v)) + case i32le: print_i64(i64(v)) + case i32be: print_i64(i64(v)) + case i64: print_i64(i64(v)) + case i64le: print_i64(i64(v)) + case i64be: print_i64(i64(v)) + + case int: print_int(v) + case uint: print_uint(v) + case uintptr: print_uintptr(v) + case rawptr: print_uintptr(uintptr(v)) + + case bool: print_string("true" if v else "false") + case b8: print_string("true" if v else "false") + case b16: print_string("true" if v else "false") + case b32: print_string("true" if v else "false") + case b64: print_string("true" if v else "false") + + case: + ti := type_info_of(x.id) + #partial switch v in ti.variant { + case Type_Info_Pointer, Type_Info_Multi_Pointer: + print_uintptr((^uintptr)(x.data)^) + return + } + + print_string("") + } + } + println_any :: proc "contextless" (args: ..any) { + context = default_context() + loop: for arg, i in args { + assert(arg.id != nil) + if i != 0 { + print_string(" ") + } + print_any_single(arg) + } + print_string("\n") + } +} + + +encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { + r := c + + buf: [4]u8 + i := u32(r) + mask :: u8(0x3f) + if i <= 1<<7-1 { + buf[0] = u8(r) + return buf, 1 + } + if i <= 1<<11-1 { + buf[0] = 0xc0 | u8(r>>6) + buf[1] = 0x80 | u8(r) & mask + return buf, 2 + } + + // Invalid or Surrogate range + if i > 0x0010ffff || + (0xd800 <= i && i <= 0xdfff) { + r = 0xfffd + } + + if i <= 1<<16-1 { + buf[0] = 0xe0 | u8(r>>12) + buf[1] = 0x80 | u8(r>>6) & mask + buf[2] = 0x80 | u8(r) & mask + return buf, 3 + } + + buf[0] = 0xf0 | u8(r>>18) + buf[1] = 0x80 | u8(r>>12) & mask + buf[2] = 0x80 | u8(r>>6) & mask + buf[3] = 0x80 | u8(r) & mask + return buf, 4 +} + +print_string :: proc "contextless" (str: string) -> (n: int) { + n, _ = os_write(transmute([]byte)str) + return +} + +print_strings :: proc "contextless" (args: ..string) -> (n: int) { + for str in args { + m, err := os_write(transmute([]byte)str) + n += m + if err != 0 { + break + } + } + return +} + +print_byte :: proc "contextless" (b: byte) -> (n: int) { + n, _ = os_write([]byte{b}) + return +} + +print_encoded_rune :: proc "contextless" (r: rune) { + print_byte('\'') + + switch r { + case '\a': print_string("\\a") + case '\b': print_string("\\b") + case '\e': print_string("\\e") + case '\f': print_string("\\f") + case '\n': print_string("\\n") + case '\r': print_string("\\r") + case '\t': print_string("\\t") + case '\v': print_string("\\v") + case: + if r <= 0 { + print_string("\\x00") + } else if r < 32 { + n0, n1 := u8(r) >> 4, u8(r) & 0xf + print_string("\\x") + print_byte(_INTEGER_DIGITS_VAR[n0]) + print_byte(_INTEGER_DIGITS_VAR[n1]) + } else { + print_rune(r) + } + } + print_byte('\'') +} + +print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { + RUNE_SELF :: 0x80 + + if r < RUNE_SELF { + return print_byte(byte(r)) + } + + b, n := encode_rune(r) + m, _ := os_write(b[:n]) + return m +} + + +print_u64 :: proc "contextless" (x: u64) #no_bounds_check { + a: [129]byte + i := len(a) + b := u64(10) + u := x + for u >= b { + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + u /= b + } + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + + os_write(a[i:]) +} + + +print_i64 :: proc "contextless" (x: i64) #no_bounds_check { + b :: i64(10) + + u := x + neg := u < 0 + u = abs(u) + + a: [129]byte + i := len(a) + for u >= b { + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + u /= b + } + i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] + if neg { + i -= 1; a[i] = '-' + } + + os_write(a[i:]) +} + +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_string(loc.file_path) + when ODIN_ERROR_POS_STYLE == .Default { + print_byte('(') + print_u64(u64(loc.line)) + 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)) + print_byte(':') + } else { + #panic("unhandled ODIN_ERROR_POS_STYLE") + } +} +print_typeid :: proc "contextless" (id: typeid) { + when ODIN_NO_RTTI { + if id == nil { + print_string("nil") + } else { + print_string("") + } + } else { + if id == nil { + print_string("nil") + } else { + ti := type_info_of(id) + print_type(ti) + } + } +} +print_type :: proc "contextless" (ti: ^Type_Info) { + if ti == nil { + print_string("nil") + return + } + + switch info in ti.variant { + case Type_Info_Named: + print_string(info.name) + case Type_Info_Integer: + switch ti.id { + case int: print_string("int") + case uint: print_string("uint") + case uintptr: print_string("uintptr") + case: + print_byte('i' if info.signed else 'u') + print_u64(u64(8*ti.size)) + } + case Type_Info_Rune: + print_string("rune") + case Type_Info_Float: + print_byte('f') + print_u64(u64(8*ti.size)) + case Type_Info_Complex: + print_string("complex") + print_u64(u64(8*ti.size)) + case Type_Info_Quaternion: + print_string("quaternion") + print_u64(u64(8*ti.size)) + case Type_Info_String: + print_string("string") + case Type_Info_Boolean: + switch ti.id { + case bool: print_string("bool") + case: + print_byte('b') + print_u64(u64(8*ti.size)) + } + case Type_Info_Any: + print_string("any") + case Type_Info_Type_Id: + print_string("typeid") + + case Type_Info_Pointer: + if info.elem == nil { + print_string("rawptr") + } else { + print_string("^") + print_type(info.elem) + } + case Type_Info_Multi_Pointer: + print_string("[^]") + print_type(info.elem) + case Type_Info_Soa_Pointer: + print_string("#soa ^") + print_type(info.elem) + case Type_Info_Procedure: + print_string("proc") + if info.params == nil { + print_string("()") + } else { + t := info.params.variant.(Type_Info_Parameters) + print_byte('(') + for t, i in t.types { + if i > 0 { print_string(", ") } + print_type(t) + } + print_string(")") + } + if info.results != nil { + print_string(" -> ") + print_type(info.results) + } + case Type_Info_Parameters: + count := len(info.names) + if count != 1 { print_byte('(') } + for name, i in info.names { + if i > 0 { print_string(", ") } + + t := info.types[i] + + if len(name) > 0 { + print_string(name) + print_string(": ") + } + print_type(t) + } + if count != 1 { print_string(")") } + + case Type_Info_Array: + print_byte('[') + print_u64(u64(info.count)) + print_byte(']') + print_type(info.elem) + + case Type_Info_Enumerated_Array: + if info.is_sparse { + print_string("#sparse") + } + print_byte('[') + print_type(info.index) + print_byte(']') + print_type(info.elem) + + + case Type_Info_Dynamic_Array: + print_string("[dynamic]") + print_type(info.elem) + case Type_Info_Slice: + print_string("[]") + print_type(info.elem) + + case Type_Info_Map: + print_string("map[") + print_type(info.key) + print_byte(']') + print_type(info.value) + + case Type_Info_Struct: + switch info.soa_kind { + case .None: // Ignore + case .Fixed: + print_string("#soa[") + print_u64(u64(info.soa_len)) + print_byte(']') + print_type(info.soa_base_type) + return + case .Slice: + print_string("#soa[]") + print_type(info.soa_base_type) + return + case .Dynamic: + print_string("#soa[dynamic]") + print_type(info.soa_base_type) + return + } + + print_string("struct ") + if info.is_packed { print_string("#packed ") } + if info.is_raw_union { print_string("#raw_union ") } + if info.custom_align { + print_string("#align(") + print_u64(u64(ti.align)) + print_string(") ") + } + print_byte('{') + for name, i in info.names { + if i > 0 { print_string(", ") } + print_string(name) + print_string(": ") + print_type(info.types[i]) + } + print_byte('}') + + case Type_Info_Union: + print_string("union ") + if info.custom_align { + print_string("#align(") + print_u64(u64(ti.align)) + print_string(") ") + } + if info.no_nil { + print_string("#no_nil ") + } + print_byte('{') + for variant, i in info.variants { + if i > 0 { print_string(", ") } + print_type(variant) + } + print_string("}") + + case Type_Info_Enum: + print_string("enum ") + print_type(info.base) + print_string(" {") + for name, i in info.names { + if i > 0 { print_string(", ") } + print_string(name) + } + print_string("}") + + case Type_Info_Bit_Set: + print_string("bit_set[") + + #partial switch elem in type_info_base(info.elem).variant { + case Type_Info_Enum: + print_type(info.elem) + case Type_Info_Rune: + print_encoded_rune(rune(info.lower)) + print_string("..") + print_encoded_rune(rune(info.upper)) + case: + print_i64(info.lower) + print_string("..") + print_i64(info.upper) + } + if info.underlying != nil { + print_string("; ") + print_type(info.underlying) + } + print_byte(']') + + + case Type_Info_Simd_Vector: + print_string("#simd[") + print_u64(u64(info.count)) + print_byte(']') + print_type(info.elem) + + case Type_Info_Relative_Pointer: + print_string("#relative(") + print_type(info.base_integer) + print_string(") ") + print_type(info.pointer) + + case Type_Info_Relative_Multi_Pointer: + print_string("#relative(") + print_type(info.base_integer) + print_string(") ") + print_type(info.pointer) + + case Type_Info_Matrix: + print_string("matrix[") + print_u64(u64(info.row_count)) + print_string(", ") + print_u64(u64(info.column_count)) + print_string("]") + print_type(info.elem) + } +} diff --git a/base/runtime/procs.odin b/base/runtime/procs.odin new file mode 100644 index 000000000..454574c35 --- /dev/null +++ b/base/runtime/procs.odin @@ -0,0 +1,95 @@ +package runtime + +when ODIN_NO_CRT && ODIN_OS == .Windows { + foreign import lib "system:NtDll.lib" + + @(private="file") + @(default_calling_convention="system") + foreign lib { + RtlMoveMemory :: proc(dst, s: rawptr, length: int) --- + RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- + } + + @(link_name="memset", linkage="strong", require) + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + RtlFillMemory(ptr, len, val) + return ptr + } + @(link_name="memmove", linkage="strong", require) + memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + RtlMoveMemory(dst, src, len) + return dst + } + @(link_name="memcpy", linkage="strong", require) + memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + RtlMoveMemory(dst, src, len) + return dst + } +} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { + @(link_name="memset", linkage="strong", require) + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = b + } + } + return ptr + } + + @(link_name="bzero", linkage="strong", require) + bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { + if ptr != nil && len != 0 { + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = 0 + } + } + return ptr + } + + @(link_name="memmove", linkage="strong", require) + memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + d, s := ([^]byte)(dst), ([^]byte)(src) + if d == s || len == 0 { + return dst + } + if d > s && uintptr(d)-uintptr(s) < uintptr(len) { + for i := len-1; i >= 0; i -= 1 { + d[i] = s[i] + } + return dst + } + + if s > d && uintptr(s)-uintptr(d) < uintptr(len) { + for i := 0; i < len; i += 1 { + d[i] = s[i] + } + return dst + } + return memcpy(dst, src, len) + } + @(link_name="memcpy", linkage="strong", require) + memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + d, s := ([^]byte)(dst), ([^]byte)(src) + if d != s { + for i := 0; i < len; i += 1 { + d[i] = s[i] + } + } + return d + + } +} else { + memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + if ptr != nil && len != 0 { + b := byte(val) + p := ([^]byte)(ptr) + for i := 0; i < len; i += 1 { + p[i] = b + } + } + return ptr + } +} \ No newline at end of file diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin new file mode 100644 index 000000000..9c53b5b16 --- /dev/null +++ b/base/runtime/procs_darwin.odin @@ -0,0 +1,21 @@ +//+private +package runtime + +foreign import "system:Foundation.framework" + +import "core:intrinsics" + +objc_id :: ^intrinsics.objc_object +objc_Class :: ^intrinsics.objc_class +objc_SEL :: ^intrinsics.objc_selector + +foreign Foundation { + objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- + sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- + objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- + + objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- + objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- + objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- + objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- +} diff --git a/base/runtime/procs_js.odin b/base/runtime/procs_js.odin new file mode 100644 index 000000000..d3e12410c --- /dev/null +++ b/base/runtime/procs_js.odin @@ -0,0 +1,15 @@ +//+build js +package runtime + +init_default_context_for_js: Context +@(init, private="file") +init_default_context :: proc() { + init_default_context_for_js = context +} + +@(export) +@(link_name="default_context_ptr") +default_context_ptr :: proc "contextless" () -> ^Context { + return &init_default_context_for_js +} + diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin new file mode 100644 index 000000000..26dcfef77 --- /dev/null +++ b/base/runtime/procs_wasm.odin @@ -0,0 +1,40 @@ +//+build wasm32, wasm64p32 +package runtime + +@(private="file") +ti_int :: struct #raw_union { + using s: struct { lo, hi: u64 }, + all: i128, +} + +@(link_name="__ashlti3", linkage="strong") +__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { + bits_in_dword :: size_of(u32)*8 + b := u32(b_) + + input, result: ti_int + input.all = a + if b & bits_in_dword != 0 { + result.lo = 0 + result.hi = input.lo << (b-bits_in_dword) + } else { + if b == 0 { + return a + } + result.lo = input.lo<>(bits_in_dword-b)) + } + return result.all +} + + +@(link_name="__multi3", linkage="strong") +__multi3 :: proc "contextless" (a, b: i128) -> i128 { + x, y, r: ti_int + + x.all = a + y.all = b + r.all = i128(x.lo * y.lo) // TODO this is incorrect + r.hi += x.hi*y.lo + x.lo*y.hi + return r.all +} \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.asm b/base/runtime/procs_windows_amd64.asm new file mode 100644 index 000000000..f588b3453 --- /dev/null +++ b/base/runtime/procs_windows_amd64.asm @@ -0,0 +1,79 @@ +bits 64 + +global __chkstk +global _tls_index +global _fltused + +section .data + _tls_index: dd 0 + _fltused: dd 0x9875 + +section .text +; NOTE(flysand): The function call to __chkstk is called +; by the compiler, when we're allocating arrays larger than +; a page size. The reason is because the OS doesn't map the +; whole stack into memory all at once, but does so page-by-page. +; When the next page is touched, the CPU generates a page fault, +; which *the OS* is handling by allocating the next page in the +; stack until we reach the limit of stack size. +; +; This page is called the guard page, touching it will extend +; the size of the stack and overwrite the stack limit in the TEB. +; +; If we allocate a large enough array and start writing from the +; bottom of it, it's possible that we may start touching +; non-contiguous pages which are unmapped. OS only maps the stack +; page into the memory if the page above it was also mapped. +; +; Therefore the compilers insert this routine, the sole purpose +; of which is to step through the stack starting from the RSP +; down to the new RSP after allocation, and touch every page +; of the new allocation so that the stack is fully mapped for +; the new allocation +; +; I've gotten this code by disassembling the output of MSVC long +; time ago. I don't remember if I've cleaned it up, but it definately +; stinks. +; +; Additional notes: +; RAX (passed as parameter) holds the allocation's size +; GS:[0x10] references the current stack limit +; (i.e. bottom of the stack (i.e. lowest address accessible)) +; +; Also this stuff is windows-only kind of thing, because linux people +; didn't think stack that grows is cool enough for them, but the kernel +; totally supports this kind of stack. +__chkstk: + ;; Allocate 16 bytes to store values of r10 and r11 + sub rsp, 0x10 + mov [rsp], r10 + mov [rsp+0x8], r11 + ;; Set r10 to point to the stack as of the moment of the function call + lea r10, [rsp+0x18] + ;; Subtract r10 til the bottom of the stack allocation, if we overflow + ;; reset r10 to 0, we'll crash with segfault anyway + xor r11, r11 + sub r10, rax + cmovb r10, r11 + ;; Load r11 with the bottom of the stack (lowest allocated address) + mov r11, gs:[0x10] ; NOTE(flysand): gs:[0x10] is stack limit + ;; If the bottom of the allocation is above the bottom of the stack, + ;; we don't need to probe + cmp r10, r11 + jnb .end + ;; Align the bottom of the allocation down to page size + and r10w, 0xf000 +.loop: + ;; Move the pointer to the next guard page, and touch it by loading 0 + ;; into that page + lea r11, [r11-0x1000] + mov byte [r11], 0x0 + ;; Did we reach the bottom of the allocation? + cmp r10, r11 + jnz .loop +.end: + ;; Restore previous r10 and r11 and return + mov r10, [rsp] + mov r11, [rsp+0x8] + add rsp, 0x10 + ret \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin new file mode 100644 index 000000000..ea495f5fa --- /dev/null +++ b/base/runtime/procs_windows_amd64.odin @@ -0,0 +1,26 @@ +//+private +//+no-instrumentation +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private) +foreign kernel32 { + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- +} + +windows_trap_array_bounds :: proc "contextless" () -> ! { + EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C + + + RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) +} + +windows_trap_type_assertion :: proc "contextless" () -> ! { + windows_trap_array_bounds() +} + +when ODIN_NO_CRT { + @(require) + foreign import crt_lib "procs_windows_amd64.asm" +} diff --git a/base/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin new file mode 100644 index 000000000..10422cf07 --- /dev/null +++ b/base/runtime/procs_windows_i386.odin @@ -0,0 +1,29 @@ +//+private +//+no-instrumentation +package runtime + +@require foreign import "system:int64.lib" + +foreign import kernel32 "system:Kernel32.lib" + +windows_trap_array_bounds :: proc "contextless" () -> ! { + DWORD :: u32 + ULONG_PTR :: uint + + EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C + + foreign kernel32 { + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- + } + + RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) +} + +windows_trap_type_assertion :: proc "contextless" () -> ! { + windows_trap_array_bounds() +} + +@(private, export, link_name="_fltused") _fltused: i32 = 0x9875 + +@(private, export, link_name="_tls_index") _tls_index: u32 +@(private, export, link_name="_tls_array") _tls_array: u32 diff --git a/base/runtime/udivmod128.odin b/base/runtime/udivmod128.odin new file mode 100644 index 000000000..87ef73c2c --- /dev/null +++ b/base/runtime/udivmod128.odin @@ -0,0 +1,156 @@ +package runtime + +import "core:intrinsics" + +udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { + _ctz :: intrinsics.count_trailing_zeros + _clz :: intrinsics.count_leading_zeros + + n := transmute([2]u64)a + d := transmute([2]u64)b + q, r: [2]u64 + sr: u32 = 0 + + low :: 1 when ODIN_ENDIAN == .Big else 0 + high :: 1 - low + U64_BITS :: 8*size_of(u64) + U128_BITS :: 8*size_of(u128) + + // Special Cases + + if n[high] == 0 { + if d[high] == 0 { + if rem != nil { + res := n[low] % d[low] + rem^ = u128(res) + } + return u128(n[low] / d[low]) + } + + if rem != nil { + rem^ = u128(n[low]) + } + return 0 + } + + if d[low] == 0 { + if d[high] == 0 { + if rem != nil { + rem^ = u128(n[high] % d[low]) + } + return u128(n[high] / d[low]) + } + if n[low] == 0 { + if rem != nil { + r[high] = n[high] % d[high] + r[low] = 0 + rem^ = transmute(u128)r + } + return u128(n[high] / d[high]) + } + + if d[high] & (d[high]-1) == 0 { + if rem != nil { + r[low] = n[low] + r[high] = n[high] & (d[high] - 1) + rem^ = transmute(u128)r + } + return u128(n[high] >> _ctz(d[high])) + } + + sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + if sr > U64_BITS - 2 { + if rem != nil { + rem^ = a + } + return 0 + } + + sr += 1 + + q[low] = 0 + q[high] = n[low] << u64(U64_BITS - sr) + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + } else { + if d[high] == 0 { + if d[low] & (d[low] - 1) == 0 { + if rem != nil { + rem^ = u128(n[low] & (d[low] - 1)) + } + if d[low] == 1 { + return a + } + sr = u32(_ctz(d[low])) + q[high] = n[high] >> sr + q[low] = (n[high] << (U64_BITS-sr)) | (n[low] >> sr) + return transmute(u128)q + } + + sr = 1 + U64_BITS + u32(_clz(d[low])) - u32(_clz(n[high])) + + switch { + case sr == U64_BITS: + q[low] = 0 + q[high] = n[low] + r[high] = 0 + r[low] = n[high] + case sr < U64_BITS: + q[low] = 0 + q[high] = n[low] << (U64_BITS - sr) + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + case: + q[low] = n[low] << (U128_BITS - sr) + q[high] = (n[high] << (U128_BITS - sr)) | (n[low] >> (sr - U64_BITS)) + r[high] = 0 + r[low] = n[high] >> (sr - U64_BITS) + } + } else { + sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) + + if sr > U64_BITS - 1 { + if rem != nil { + rem^ = a + } + return 0 + } + + sr += 1 + + q[low] = 0 + if sr == U64_BITS { + q[high] = n[low] + r[high] = 0 + r[low] = n[high] + } else { + r[high] = n[high] >> sr + r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) + q[high] = n[low] << (U64_BITS - sr) + } + } + } + + carry: u32 = 0 + r_all: u128 + + for ; sr > 0; sr -= 1 { + r[high] = (r[high] << 1) | (r[low] >> (U64_BITS - 1)) + r[low] = (r[low] << 1) | (q[high] >> (U64_BITS - 1)) + q[high] = (q[high] << 1) | (q[low] >> (U64_BITS - 1)) + q[low] = (q[low] << 1) | u64(carry) + + r_all = transmute(u128)r + s := i128(b - r_all - 1) >> (U128_BITS - 1) + carry = u32(s & 1) + r_all -= b & transmute(u128)s + r = transmute([2]u64)r_all + } + + q_all := ((transmute(u128)q) << 1) | u128(carry) + if rem != nil { + rem^ = r_all + } + + return q_all +} diff --git a/core/runtime/core.odin b/core/runtime/core.odin deleted file mode 100644 index 740482493..000000000 --- a/core/runtime/core.odin +++ /dev/null @@ -1,681 +0,0 @@ -// This is the runtime code required by the compiler -// IMPORTANT NOTE(bill): Do not change the order of any of this data -// The compiler relies upon this _exact_ order -// -// Naming Conventions: -// In general, Ada_Case for types and snake_case for values -// -// Package Name: snake_case (but prefer single word) -// Import Name: snake_case (but prefer single word) -// Types: Ada_Case -// Enum Values: Ada_Case -// Procedures: snake_case -// Local Variables: snake_case -// Constant Variables: SCREAMING_SNAKE_CASE -// -// IMPORTANT NOTE(bill): `type_info_of` cannot be used within a -// #shared_global_scope due to the internals of the compiler. -// This could change at a later date if the all these data structures are -// implemented within the compiler rather than in this "preload" file -// -//+no-instrumentation -package runtime - -import "core:intrinsics" - -// NOTE(bill): This must match the compiler's -Calling_Convention :: enum u8 { - Invalid = 0, - Odin = 1, - Contextless = 2, - CDecl = 3, - Std_Call = 4, - Fast_Call = 5, - - None = 6, - Naked = 7, - - _ = 8, // reserved - - Win64 = 9, - SysV = 10, -} - -Type_Info_Enum_Value :: distinct i64 - -Platform_Endianness :: enum u8 { - Platform = 0, - Little = 1, - Big = 2, -} - -// Procedure type to test whether two values of the same type are equal -Equal_Proc :: distinct proc "contextless" (rawptr, rawptr) -> bool -// Procedure type to hash a value, default seed value is 0 -Hasher_Proc :: distinct proc "contextless" (data: rawptr, seed: uintptr = 0) -> uintptr - -Type_Info_Struct_Soa_Kind :: enum u8 { - None = 0, - Fixed = 1, - Slice = 2, - Dynamic = 3, -} - -// Variant Types -Type_Info_Named :: struct { - name: string, - base: ^Type_Info, - pkg: string, - loc: Source_Code_Location, -} -Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} -Type_Info_Rune :: struct {} -Type_Info_Float :: struct {endianness: Platform_Endianness} -Type_Info_Complex :: struct {} -Type_Info_Quaternion :: struct {} -Type_Info_String :: struct {is_cstring: bool} -Type_Info_Boolean :: struct {} -Type_Info_Any :: struct {} -Type_Info_Type_Id :: struct {} -Type_Info_Pointer :: struct { - elem: ^Type_Info, // nil -> rawptr -} -Type_Info_Multi_Pointer :: struct { - elem: ^Type_Info, -} -Type_Info_Procedure :: struct { - params: ^Type_Info, // Type_Info_Parameters - results: ^Type_Info, // Type_Info_Parameters - variadic: bool, - convention: Calling_Convention, -} -Type_Info_Array :: struct { - elem: ^Type_Info, - elem_size: int, - count: int, -} -Type_Info_Enumerated_Array :: struct { - elem: ^Type_Info, - index: ^Type_Info, - elem_size: int, - count: int, - min_value: Type_Info_Enum_Value, - max_value: Type_Info_Enum_Value, - is_sparse: bool, -} -Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int} -Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int} - -Type_Info_Parameters :: struct { // Only used for procedures parameters and results - types: []^Type_Info, - names: []string, -} -Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually - -Type_Info_Struct :: struct { - types: []^Type_Info, - names: []string, - offsets: []uintptr, - usings: []bool, - tags: []string, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - custom_align: bool, - - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set - - // These are only set iff this structure is an SOA structure - soa_kind: Type_Info_Struct_Soa_Kind, - soa_base_type: ^Type_Info, - soa_len: int, -} -Type_Info_Union :: struct { - variants: []^Type_Info, - tag_offset: uintptr, - tag_type: ^Type_Info, - - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set - - custom_align: bool, - no_nil: bool, - shared_nil: bool, -} -Type_Info_Enum :: struct { - base: ^Type_Info, - names: []string, - values: []Type_Info_Enum_Value, -} -Type_Info_Map :: struct { - key: ^Type_Info, - value: ^Type_Info, - map_info: ^Map_Info, -} -Type_Info_Bit_Set :: struct { - elem: ^Type_Info, - underlying: ^Type_Info, // Possibly nil - lower: i64, - upper: i64, -} -Type_Info_Simd_Vector :: struct { - elem: ^Type_Info, - elem_size: int, - count: int, -} -Type_Info_Relative_Pointer :: struct { - pointer: ^Type_Info, // ^T - base_integer: ^Type_Info, -} -Type_Info_Relative_Multi_Pointer :: struct { - pointer: ^Type_Info, // [^]T - base_integer: ^Type_Info, -} -Type_Info_Matrix :: struct { - elem: ^Type_Info, - elem_size: int, - elem_stride: int, // elem_stride >= row_count - row_count: int, - column_count: int, - // Total element count = column_count * elem_stride -} -Type_Info_Soa_Pointer :: struct { - elem: ^Type_Info, -} - -Type_Info_Flag :: enum u8 { - Comparable = 0, - Simple_Compare = 1, -} -Type_Info_Flags :: distinct bit_set[Type_Info_Flag; u32] - -Type_Info :: struct { - size: int, - align: int, - flags: Type_Info_Flags, - id: typeid, - - variant: union { - Type_Info_Named, - Type_Info_Integer, - Type_Info_Rune, - Type_Info_Float, - Type_Info_Complex, - Type_Info_Quaternion, - Type_Info_String, - Type_Info_Boolean, - Type_Info_Any, - Type_Info_Type_Id, - Type_Info_Pointer, - Type_Info_Multi_Pointer, - Type_Info_Procedure, - Type_Info_Array, - Type_Info_Enumerated_Array, - Type_Info_Dynamic_Array, - Type_Info_Slice, - Type_Info_Parameters, - Type_Info_Struct, - Type_Info_Union, - Type_Info_Enum, - Type_Info_Map, - Type_Info_Bit_Set, - Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, - Type_Info_Matrix, - Type_Info_Soa_Pointer, - }, -} - -// NOTE(bill): This must match the compiler's -Typeid_Kind :: enum u8 { - Invalid, - Integer, - Rune, - Float, - Complex, - Quaternion, - String, - Boolean, - Any, - Type_Id, - Pointer, - Multi_Pointer, - Procedure, - Array, - Enumerated_Array, - Dynamic_Array, - Slice, - Tuple, - Struct, - Union, - Enum, - Map, - Bit_Set, - Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, - Matrix, - Soa_Pointer, -} -#assert(len(Typeid_Kind) < 32) - -// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) { -// index: 8*size_of(uintptr) - 8, -// kind: 5, // Typeid_Kind -// named: 1, -// special: 1, // signed, cstring, etc -// reserved: 1, -// } -// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr)); - -// NOTE(bill): only the ones that are needed (not all types) -// This will be set by the compiler -type_table: []Type_Info - -args__: []cstring - -when ODIN_OS == .Windows { - // NOTE(Jeroen): If we're a Windows DLL, fwdReason will be populated. - // This tells a DLL if it's first loaded, about to be unloaded, or a thread is joining/exiting. - - DLL_Forward_Reason :: enum u32 { - Process_Detach = 0, // About to unload DLL - Process_Attach = 1, // Entry point - Thread_Attach = 2, - Thread_Detach = 3, - } - dll_forward_reason: DLL_Forward_Reason -} - -// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it) - - -Source_Code_Location :: struct { - file_path: string, - line, column: i32, - procedure: string, -} - -Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> ! - -// Allocation Stuff -Allocator_Mode :: enum byte { - Alloc, - Free, - Free_All, - Resize, - Query_Features, - Query_Info, - Alloc_Non_Zeroed, - Resize_Non_Zeroed, -} - -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode] - -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} - -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} - -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, - location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error) -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} - -Byte :: 1 -Kilobyte :: 1024 * Byte -Megabyte :: 1024 * Kilobyte -Gigabyte :: 1024 * Megabyte -Terabyte :: 1024 * Gigabyte -Petabyte :: 1024 * Terabyte -Exabyte :: 1024 * Petabyte - -// Logging stuff - -Logger_Level :: enum uint { - Debug = 0, - Info = 10, - Warning = 20, - Error = 30, - Fatal = 40, -} - -Logger_Option :: enum { - Level, - Date, - Time, - Short_File_Path, - Long_File_Path, - Line, - Procedure, - Terminal_Color, - Thread_Id, -} - -Logger_Options :: bit_set[Logger_Option] -Logger_Proc :: #type proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) - -Logger :: struct { - procedure: Logger_Proc, - data: rawptr, - lowest_level: Logger_Level, - options: Logger_Options, -} - -Context :: struct { - allocator: Allocator, - temp_allocator: Allocator, - assertion_failure_proc: Assertion_Failure_Proc, - logger: Logger, - - user_ptr: rawptr, - user_index: int, - - // Internal use only - _internal: rawptr, -} - - -Raw_String :: struct { - data: [^]byte, - len: int, -} - -Raw_Slice :: struct { - data: rawptr, - len: int, -} - -Raw_Dynamic_Array :: struct { - data: rawptr, - len: int, - cap: int, - allocator: Allocator, -} - -// The raw, type-erased representation of a map. -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Raw_Map :: struct { - // A single allocation spanning all keys, values, and hashes. - // { - // k: Map_Cell(K) * (capacity / ks_per_cell) - // v: Map_Cell(V) * (capacity / vs_per_cell) - // h: Map_Cell(H) * (capacity / hs_per_cell) - // } - // - // The data is allocated assuming 64-byte alignment, meaning the address is - // always a multiple of 64. This means we have 6 bits of zeros in the pointer - // to store the capacity. We can store a value as large as 2^6-1 or 63 in - // there. This conveniently is the maximum log2 capacity we can have for a map - // as Odin uses signed integers to represent capacity. - // - // Since the hashes are backed by Map_Hash, which is just a 64-bit unsigned - // integer, the cell structure for hashes is unnecessary because 64/8 is 8 and - // requires no padding, meaning it can be indexed as a regular array of - // Map_Hash directly, though for consistency sake it's written as if it were - // an array of Map_Cell(Map_Hash). - data: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits - len: uintptr, // 8-bytes on 64-bits, 4-bytes on 32-bits - allocator: Allocator, // 16-bytes on 64-bits, 8-bytes on 32-bits -} - -Raw_Any :: struct { - data: rawptr, - id: typeid, -} - -Raw_Cstring :: struct { - data: [^]byte, -} - -Raw_Soa_Pointer :: struct { - data: rawptr, - index: int, -} - - - -/* - // Defined internally by the compiler - Odin_OS_Type :: enum int { - Unknown, - Windows, - Darwin, - Linux, - Essence, - FreeBSD, - OpenBSD, - WASI, - JS, - Freestanding, - } -*/ -Odin_OS_Type :: type_of(ODIN_OS) - -/* - // Defined internally by the compiler - Odin_Arch_Type :: enum int { - Unknown, - amd64, - i386, - arm32, - arm64, - wasm32, - wasm64p32, - } -*/ -Odin_Arch_Type :: type_of(ODIN_ARCH) - -/* - // Defined internally by the compiler - Odin_Build_Mode_Type :: enum int { - Executable, - Dynamic, - Object, - Assembly, - LLVM_IR, - } -*/ -Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) - -/* - // Defined internally by the compiler - Odin_Endian_Type :: enum int { - Unknown, - Little, - Big, - } -*/ -Odin_Endian_Type :: type_of(ODIN_ENDIAN) - - -/* - // Defined internally by the compiler - Odin_Platform_Subtarget_Type :: enum int { - Default, - iOS, - } -*/ -Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) - -/* - // Defined internally by the compiler - Odin_Sanitizer_Flag :: enum u32 { - Address = 0, - Memory = 1, - Thread = 2, - } - Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] - - ODIN_SANITIZER_FLAGS // is a constant -*/ -Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) - - -///////////////////////////// -// Init Startup Procedures // -///////////////////////////// - -// IMPORTANT NOTE(bill): Do not call this unless you want to explicitly set up the entry point and how it gets called -// This is probably only useful for freestanding targets -foreign { - @(link_name="__$startup_runtime") - _startup_runtime :: proc "odin" () --- - @(link_name="__$cleanup_runtime") - _cleanup_runtime :: proc "odin" () --- -} - -_cleanup_runtime_contextless :: proc "contextless" () { - context = default_context() - _cleanup_runtime() -} - - -///////////////////////////// -///////////////////////////// -///////////////////////////// - - -type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { - if info == nil { - return nil - } - - base := info - loop: for { - #partial switch i in base.variant { - case Type_Info_Named: base = i.base - case: break loop - } - } - return base -} - - -type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { - if info == nil { - return nil - } - - base := info - loop: for { - #partial switch i in base.variant { - case Type_Info_Named: base = i.base - case Type_Info_Enum: base = i.base - case: break loop - } - } - return base -} -type_info_base_without_enum :: type_info_core - -__type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check { - MASK :: 1<<(8*size_of(typeid) - 8) - 1 - data := transmute(uintptr)id - n := int(data & MASK) - if n < 0 || n >= len(type_table) { - n = 0 - } - return &type_table[n] -} - -when !ODIN_NO_RTTI { - typeid_base :: proc "contextless" (id: typeid) -> typeid { - ti := type_info_of(id) - ti = type_info_base(ti) - return ti.id - } - typeid_core :: proc "contextless" (id: typeid) -> typeid { - ti := type_info_core(type_info_of(id)) - return ti.id - } - typeid_base_without_enum :: typeid_core -} - - - -debug_trap :: intrinsics.debug_trap -trap :: intrinsics.trap -read_cycle_counter :: intrinsics.read_cycle_counter - - - -default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location) { - // Nothing -} - -default_logger :: proc() -> Logger { - return Logger{default_logger_proc, nil, Logger_Level.Debug, nil} -} - - -default_context :: proc "contextless" () -> Context { - c: Context - __init_context(&c) - return c -} - -@private -__init_context_from_ptr :: proc "contextless" (c: ^Context, other: ^Context) { - if c == nil { - return - } - c^ = other^ - __init_context(c) -} - -@private -__init_context :: proc "contextless" (c: ^Context) { - if c == nil { - return - } - - // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contextless" calling convention - c.allocator.procedure = default_allocator_proc - c.allocator.data = nil - - c.temp_allocator.procedure = default_temp_allocator_proc - when !NO_DEFAULT_TEMP_ALLOCATOR { - c.temp_allocator.data = &global_default_temp_allocator_data - } - - when !ODIN_DISABLE_ASSERT { - c.assertion_failure_proc = default_assertion_failure_proc - } - - c.logger.procedure = default_logger_proc - c.logger.data = nil -} - -default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! { - when ODIN_OS == .Freestanding { - // Do nothing - } else { - when !ODIN_DISABLE_ASSERT { - print_caller_location(loc) - print_string(" ") - } - print_string(prefix) - if len(message) > 0 { - print_string(": ") - print_string(message) - } - print_byte('\n') - } - trap() -} diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin deleted file mode 100644 index 3f4ebbc74..000000000 --- a/core/runtime/core_builtin.odin +++ /dev/null @@ -1,915 +0,0 @@ -package runtime - -import "core:intrinsics" - -@builtin -Maybe :: union($T: typeid) {T} - - -@(builtin, require_results) -container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T - where intrinsics.type_has_field(T, field_name), - intrinsics.type_field_type(T, field_name) == Field_Type { - offset :: offset_of_by_string(T, field_name) - return (^T)(uintptr(ptr) - offset) if ptr != nil else nil -} - - -when !NO_DEFAULT_TEMP_ALLOCATOR { - @thread_local global_default_temp_allocator_data: Default_Temp_Allocator -} - -@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) -init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) { - when !NO_DEFAULT_TEMP_ALLOCATOR { - default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator) - } -} - - -// `copy_slice` is a built-in procedure that copies elements from a source slice `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). -// -// Prefer the procedure group `copy`. -@builtin -copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { - n := max(0, min(len(dst), len(src))) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) - } - return n -} -// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `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). -// -// Prefer the procedure group `copy`. -@builtin -copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { - n := max(0, min(len(dst), len(src))) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n) - } - return n -} -// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `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 -copy :: proc{copy_slice, copy_from_string} - - - -// `unordered_remove` removed the element at the specified `index`. It does so by replacing the current end value -// with the old value, and reducing the length of the dynamic array by 1. -// -// Note: This is an O(1) operation. -// Note: If you the elements to remain in their order, use `ordered_remove`. -// Note: If the index is out of bounds, this procedure will panic. -@builtin -unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { - bounds_check_error_loc(loc, index, len(array)) - n := len(array)-1 - if index != n { - array[index] = array[n] - } - (^Raw_Dynamic_Array)(array).len -= 1 -} -// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. -// -// Note: This is an O(N) operation. -// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`. -// Note: If the index is out of bounds, this procedure will panic. -@builtin -ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check { - bounds_check_error_loc(loc, index, len(array)) - if index+1 < len(array) { - copy(array[index:], array[index+1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 -} - -// `remove_range` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. -// -// Note: This is an O(N) operation. -// Note: If the range is out of bounds, this procedure will panic. -@builtin -remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { - slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) - n := max(hi-lo, 0) - if n > 0 { - if hi != len(array) { - copy(array[lo:], array[hi:]) - } - (^Raw_Dynamic_Array)(array).len -= n - } -} - - -// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. -// -// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. -@builtin -pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, loc=loc) - res = array[len(array)-1] - (^Raw_Dynamic_Array)(array).len -= 1 - return res -} - - -// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. -// If the operation is not possible, it will return false. -@builtin -pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { - if len(array) == 0 { - return - } - res, ok = array[len(array)-1], true - (^Raw_Dynamic_Array)(array).len -= 1 - return -} - -// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. -// -// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. -@builtin -pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { - assert(len(array) > 0, loc=loc) - res = array[0] - if len(array) > 1 { - copy(array[0:], array[1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 - return res -} - -// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. -// If the operation is not possible, it will return false. -@builtin -pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { - if len(array) == 0 { - return - } - res, ok = array[0], true - if len(array) > 1 { - copy(array[0:], array[1:]) - } - (^Raw_Dynamic_Array)(array).len -= 1 - return -} - - -// `clear` will set the length of a passed dynamic array or map to `0` -@builtin -clear :: proc{clear_dynamic_array, clear_map} - -// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). -@builtin -reserve :: proc{reserve_dynamic_array, reserve_map} - -@builtin -non_zero_reserve :: proc{non_zero_reserve_dynamic_array} - -// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`). -@builtin -resize :: proc{resize_dynamic_array} - -@builtin -non_zero_resize :: proc{non_zero_resize_dynamic_array} - -// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity. -@builtin -shrink :: proc{shrink_dynamic_array, shrink_map} - -// `free` will try to free the passed pointer, with the given `allocator` if the allocator supports this operation. -@builtin -free :: proc{mem_free} - -// `free_all` will try to free/reset all of the memory of the given `allocator` if the allocator supports this operation. -@builtin -free_all :: proc{mem_free_all} - - - -// `delete_string` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(str), len(str), allocator, loc) -} -// `delete_cstring` will try to free the underlying data of the passed string, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free((^byte)(str), allocator, loc) -} -// `delete_dynamic_array` will try to free the underlying data of the passed dynamic array, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc) -} -// `delete_slice` will try to free the underlying data of the passed sliced, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return mem_free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc) -} -// `delete_map` will try to free the underlying data of the passed map, with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer the procedure group `delete`. -@builtin -delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { - return map_free_dynamic(transmute(Raw_Map)m, map_info(T), loc) -} - - -// `delete` will try to free the underlying data of the passed built-in data structure (string, cstring, dynamic array, slice, or map), with the given `allocator` if the allocator supports this operation. -// -// Note: Prefer `delete` over the specific `delete_*` procedures where possible. -@builtin -delete :: proc{ - delete_string, - delete_cstring, - delete_dynamic_array, - delete_slice, - delete_map, - delete_soa_slice, - delete_soa_dynamic_array, -} - - -// The new built-in procedure allocates memory. The first argument is a type, not a value, and the value -// return is a pointer to a newly allocated value of that type using the specified allocator, default is context.allocator -@(builtin, require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) #optional_allocator_error { - return new_aligned(T, align_of(T), allocator, loc) -} -@(require_results) -new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { - data := mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return - t = (^T)(raw_data(data)) - return -} - -@(builtin, require_results) -new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { - t_data := mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return - t = (^T)(raw_data(t_data)) - if t != nil { - t^ = data - } - return -} - -DEFAULT_RESERVE_CAPACITY :: 16 - -@(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - make_slice_error_loc(loc, len) - data, err := mem_alloc_bytes(size_of(E)*len, alignment, allocator, loc) - if data == nil && size_of(E) != 0 { - return nil, err - } - s := Raw_Slice{raw_data(data), len} - return transmute(T)s, err -} - -// `make_slice` allocates and initializes a slice. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_aligned(T, len, align_of(E), allocator, loc) -} -// `make_dynamic_array` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc) -} -// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, len, len, allocator, loc) -} -// `make_dynamic_array_len_cap` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - make_dynamic_array_error_loc(loc, len, cap) - data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return - s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} - if data == nil && size_of(E) != 0 { - s.len, s.cap = 0, 0 - } - array = transmute(T)s - return -} -// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { - make_map_expr_error_loc(loc, capacity) - context.allocator = allocator - - err = reserve_map(&m, capacity, loc) - return -} -// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. -// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. -// -// This is "similar" to doing `raw_data(make([]E, len, allocator))`. -// -// Note: Prefer using the procedure group `make`. -@(builtin, require_results) -make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error { - make_slice_error_loc(loc, len) - data := mem_alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return - if data == nil && size_of(E) != 0 { - return - } - mp = cast(T)raw_data(data) - return -} - - -// `make` built-in procedure allocates and initializes a value of type slice, dynamic array, map, or multi-pointer (only). -// -// Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the -// type of its argument, not a pointer to it. -// Make uses the specified allocator, default is context.allocator. -@builtin -make :: proc{ - make_slice, - make_dynamic_array, - make_dynamic_array_len, - make_dynamic_array_len_cap, - make_map, - make_multi_pointer, -} - - - -// `clear_map` will set the length of a passed map to `0` -// -// Note: Prefer the procedure group `clear` -@builtin -clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { - if m == nil { - return - } - map_clear_dynamic((^Raw_Map)(m), map_info(T)) -} - -// `reserve_map` will try to reserve memory of a passed map to the requested element count (setting the `cap`). -// -// Note: Prefer the procedure group `reserve` -@builtin -reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { - return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil -} - -// Shrinks the capacity of a map down to the current length. -// -// Note: Prefer the procedure group `shrink` -@builtin -shrink_map :: proc(m: ^$T/map[$K]$V, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if m != nil { - return map_shrink_dynamic((^Raw_Map)(m), map_info(T), loc) - } - return -} - -// The delete_key built-in procedure deletes the element with the specified key (m[key]) from the map. -// If m is nil, or there is no such element, this procedure is a no-op -@builtin -delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: V) { - if m != nil { - key := key - old_k, old_v, ok := map_erase_dynamic((^Raw_Map)(m), map_info(T), uintptr(&key)) - if ok { - deleted_key = (^K)(old_k)^ - deleted_value = (^V)(old_v)^ - } - } - return -} - -_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 - return 1, nil - } else { - if cap(array) < len(array)+1 { - cap := 2 * cap(array) + max(8, 1) - - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - if cap(array)-len(array) > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - data[a.len] = arg - } - a.len += 1 - return 1, err - } - return 0, err - } -} - -@builtin -append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, true, loc=loc) -} - -@builtin -non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, false, loc=loc) -} - -_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - - arg_len := len(args) - if arg_len <= 0 { - return 0, nil - } - - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += arg_len - return arg_len, nil - } else { - if cap(array) < len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - arg_len = min(cap(array)-len(array), arg_len) - if arg_len > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) - } - a.len += arg_len - } - return arg_len, err - } -} - -@builtin -append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, ..args) -} - -@builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, ..args) -} - -// The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type -_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - args := transmute([]E)arg - if should_zero { - return append_elems(array, ..args, loc=loc) - } else { - return non_zero_append_elems(array, ..args, loc=loc) - } -} - -@builtin -append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem_string(array, arg, true, loc) -} -@builtin -non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem_string(array, arg, false, loc) -} - - -// The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type -@builtin -append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - n_arg: int - for arg in args { - n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) - n += n_arg - if err != nil { - return - } - } - return -} - -// The append built-in procedure appends elements to the end of a dynamic array -@builtin append :: proc{append_elem, append_elems, append_elem_string} -@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string} - - -@builtin -append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - prev_len := len(array) - resize(array, len(array)+1, loc) or_return - return len(array)-prev_len, nil -} - - -@builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - n := max(len(array), index) - m :: 1 - new_size := n + m - - resize(array, new_size, loc) or_return - when size_of(E) != 0 { - copy(array[index + m:], array[index:]) - array[index] = arg - } - ok = true - return -} - -@builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - if len(args) == 0 { - ok = true - return - } - - n := max(len(array), index) - m := len(args) - new_size := n + m - - resize(array, new_size, loc) or_return - when size_of(E) != 0 { - copy(array[index + m:], array[index:]) - copy(array[index:], args) - } - ok = true - return -} - -@builtin -inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if array == nil { - return - } - if len(arg) == 0 { - ok = true - return - } - - n := max(len(array), index) - m := len(arg) - new_size := n + m - - resize(array, new_size, loc) or_return - copy(array[index+m:], array[index:]) - copy(array[index:], arg) - ok = true - return -} - -@builtin inject_at :: proc{inject_at_elem, inject_at_elems, inject_at_elem_string} - - - -@builtin -assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - if index < len(array) { - array[index] = arg - ok = true - } else { - resize(array, index+1, loc) or_return - array[index] = arg - ok = true - } - return -} - - -@builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - new_size := index + len(args) - if len(args) == 0 { - ok = true - } else if new_size < len(array) { - copy(array[index:], args) - ok = true - } else { - resize(array, new_size, loc) or_return - copy(array[index:], args) - ok = true - } - return -} - - -@builtin -assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { - new_size := index + len(arg) - if len(arg) == 0 { - ok = true - } else if new_size < len(array) { - copy(array[index:], arg) - ok = true - } else { - resize(array, new_size, loc) or_return - copy(array[index:], arg) - ok = true - } - return -} - -@builtin assign_at :: proc{assign_at_elem, assign_at_elems, assign_at_elem_string} - - - - -// `clear_dynamic_array` will set the length of a passed dynamic array to `0` -// -// Note: Prefer the procedure group `clear`. -@builtin -clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { - if array != nil { - (^Raw_Dynamic_Array)(array).len = 0 - } -} - -// `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). -// -// Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - a := (^Raw_Dynamic_Array)(array) - - if capacity <= a.cap { - return nil - } - - if a.allocator.procedure == nil { - a.allocator = context.allocator - } - assert(a.allocator.procedure != nil) - - old_size := a.cap * size_of(E) - new_size := capacity * size_of(E) - allocator := a.allocator - - new_data: []byte - if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } - if new_data == nil && new_size > 0 { - return .Out_Of_Memory - } - - a.data = raw_data(new_data) - a.cap = capacity - return nil -} - -@builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, true, loc) -} - -@builtin -non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, false, loc) -} - -// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). -// -// Note: Prefer the procedure group `resize` -_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - a := (^Raw_Dynamic_Array)(array) - - if length <= a.cap { - a.len = max(length, 0) - return nil - } - - if a.allocator.procedure == nil { - a.allocator = context.allocator - } - assert(a.allocator.procedure != nil) - - old_size := a.cap * size_of(E) - new_size := length * size_of(E) - allocator := a.allocator - - new_data : []byte - if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return - } - if new_data == nil && new_size > 0 { - return .Out_Of_Memory - } - - a.data = raw_data(new_data) - a.len = length - a.cap = length - return nil -} - -@builtin -resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, true, loc=loc) -} - -@builtin -non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, false, loc=loc) -} - -/* - Shrinks the capacity of a dynamic array down to the current length, or the given capacity. - - If `new_cap` is negative, then `len(array)` is used. - - Returns false if `cap(array) < new_cap`, or the allocator report failure. - - If `len(array) < new_cap`, then `len(array)` will be left unchanged. - - Note: Prefer the procedure group `shrink` -*/ -shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if array == nil { - return - } - a := (^Raw_Dynamic_Array)(array) - - new_cap := new_cap if new_cap >= 0 else a.len - - if new_cap > a.cap { - return - } - - if a.allocator.procedure == nil { - a.allocator = context.allocator - } - assert(a.allocator.procedure != nil) - - old_size := a.cap * size_of(E) - new_size := new_cap * size_of(E) - - new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return - - a.data = raw_data(new_data) - a.len = min(new_cap, a.len) - a.cap = new_cap - return true, nil -} - -@builtin -map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (ptr: ^V) { - key, value := key, value - return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)) -} - - -@builtin -incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ |= {elem} -} -@builtin -incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ |= {elem} - } -} -@builtin -incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ |= other -} -@builtin -excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ &~= {elem} -} -@builtin -excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ &~= {elem} - } -} -@builtin -excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ &~= other -} - -@builtin incl :: proc{incl_elem, incl_elems, incl_bit_set} -@builtin excl :: proc{excl_elem, excl_elems, excl_bit_set} - - -@builtin -card :: proc(s: $S/bit_set[$E; $U]) -> int { - when size_of(S) == 1 { - return int(intrinsics.count_ones(transmute(u8)s)) - } else when size_of(S) == 2 { - return int(intrinsics.count_ones(transmute(u16)s)) - } else when size_of(S) == 4 { - return int(intrinsics.count_ones(transmute(u32)s)) - } else when size_of(S) == 8 { - return int(intrinsics.count_ones(transmute(u64)s)) - } else when size_of(S) == 16 { - return int(intrinsics.count_ones(transmute(u128)s)) - } else { - #panic("Unhandled card bit_set size") - } -} - - - -@builtin -@(disabled=ODIN_DISABLE_ASSERT) -assert :: proc(condition: bool, message := "", loc := #caller_location) { - if !condition { - // NOTE(bill): This is wrapped in a procedure call - // to improve performance to make the CPU not - // execute speculatively, making it about an order of - // magnitude faster - @(cold) - internal :: proc(message: string, loc: Source_Code_Location) { - p := context.assertion_failure_proc - if p == nil { - p = default_assertion_failure_proc - } - p("runtime assertion", message, loc) - } - internal(message, loc) - } -} - -@builtin -panic :: proc(message: string, loc := #caller_location) -> ! { - p := context.assertion_failure_proc - if p == nil { - p = default_assertion_failure_proc - } - p("panic", message, loc) -} - -@builtin -unimplemented :: proc(message := "", loc := #caller_location) -> ! { - p := context.assertion_failure_proc - if p == nil { - p = default_assertion_failure_proc - } - p("not yet implemented", message, loc) -} diff --git a/core/runtime/core_builtin_matrix.odin b/core/runtime/core_builtin_matrix.odin deleted file mode 100644 index 7d60d625c..000000000 --- a/core/runtime/core_builtin_matrix.odin +++ /dev/null @@ -1,274 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - - -@(builtin) -determinant :: proc{ - matrix1x1_determinant, - matrix2x2_determinant, - matrix3x3_determinant, - matrix4x4_determinant, -} - -@(builtin) -adjugate :: proc{ - matrix1x1_adjugate, - matrix2x2_adjugate, - matrix3x3_adjugate, - matrix4x4_adjugate, -} - -@(builtin) -inverse_transpose :: proc{ - matrix1x1_inverse_transpose, - matrix2x2_inverse_transpose, - matrix3x3_inverse_transpose, - matrix4x4_inverse_transpose, -} - - -@(builtin) -inverse :: proc{ - matrix1x1_inverse, - matrix2x2_inverse, - matrix3x3_inverse, - matrix4x4_inverse, -} - -@(builtin, require_results) -hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { - return conj(transpose(m)) -} - -@(builtin, require_results) -matrix_trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { - for i in 0.. (minor: T) where N > 1 { - K :: N-1 - cut_down: matrix[K, K]T - for col_idx in 0..= column) - for row_idx in 0..= row) - cut_down[row_idx, col_idx] = m[i, j] - } - } - return determinant(cut_down) -} - - - -@(builtin, require_results) -matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { - return m[0, 0] -} - -@(builtin, require_results) -matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { - return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] -} -@(builtin, require_results) -matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { - a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) - b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) - c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) - return a + b + c -} -@(builtin, require_results) -matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { - a := adjugate(m) - #no_bounds_check for i in 0..<4 { - det += m[0, i] * a[0, i] - } - return -} - - - - -@(builtin, require_results) -matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y = x - return -} - -@(builtin, require_results) -matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - y[0, 0] = +x[1, 1] - y[0, 1] = -x[1, 0] - y[1, 0] = -x[0, 1] - y[1, 1] = +x[0, 0] - return -} - -@(builtin, require_results) -matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { - y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) - y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) - y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) - y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) - y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) - y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) - y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) - y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) - y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) - return -} - - -@(builtin, require_results) -matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { - for i in 0..<4 { - for j in 0..<4 { - sign: T = 1 if (i + j) % 2 == 0 else -1 - y[i, j] = sign * matrix_minor(x, i, j) - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[1, 0] = -x[0, 1] / d - y[0, 1] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[1, 0] = -x[0, 1] * id - y[0, 1] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[0, 1] = -x[0, 1] / d - y[1, 0] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[0, 1] = -x[0, 1] * id - y[1, 0] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] * id - } - } - } - return -} diff --git a/core/runtime/core_builtin_soa.odin b/core/runtime/core_builtin_soa.odin deleted file mode 100644 index 6313a28f5..000000000 --- a/core/runtime/core_builtin_soa.odin +++ /dev/null @@ -1,428 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - -/* - - SOA types are implemented with this sort of layout: - - SOA Fixed Array - struct { - f0: [N]T0, - f1: [N]T1, - f2: [N]T2, - } - - SOA Slice - struct { - f0: ^T0, - f1: ^T1, - f2: ^T2, - - len: int, - } - - SOA Dynamic Array - struct { - f0: ^T0, - f1: ^T1, - f2: ^T2, - - len: int, - cap: int, - allocator: Allocator, - } - - A footer is used rather than a header purely to simplify access to the fields internally - i.e. field index of the AOS == SOA - -*/ - - -Raw_SOA_Footer_Slice :: struct { - len: int, -} - -Raw_SOA_Footer_Dynamic_Array :: struct { - len: int, - cap: int, - allocator: Allocator, -} - -@(builtin, require_results) -raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { - if array == nil { - return nil - } - field_count := uintptr(intrinsics.type_struct_field_count(E)) - footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr)) - return -} -@(builtin, require_results) -raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { - if array == nil { - return nil - } - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr)) - return -} -raw_soa_footer :: proc{ - raw_soa_footer_slice, - raw_soa_footer_dynamic_array, -} - - - -@(builtin, require_results) -make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - if length <= 0 { - return - } - - footer := raw_soa_footer(&array) - if size_of(E) == 0 { - footer.len = length - return - } - - max_align := max(alignment, align_of(E)) - - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - - field_count := uintptr(intrinsics.type_struct_field_count(E)) - - total_size := 0 - for i in 0.. (array: T, err: Allocator_Error) #optional_allocator_error { - return make_soa_aligned(T, length, align_of(E), allocator, loc) -} - -@(builtin, require_results) -make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return - return array, nil -} - -@(builtin, require_results) -make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - resize_soa(&array, length, loc) or_return - return array, nil -} - -@(builtin, require_results) -make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - context.allocator = allocator - reserve_soa(&array, capacity, loc) or_return - resize_soa(&array, length, loc) or_return - return array, nil -} - - -@builtin -make_soa :: proc{ - make_soa_slice, - make_soa_dynamic_array, - make_soa_dynamic_array_len, - make_soa_dynamic_array_len_cap, -} - - -@builtin -resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - reserve_soa(array, length, loc) or_return - footer := raw_soa_footer(array) - footer.len = length - return nil -} - -@builtin -reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { - if array == nil { - return nil - } - - old_cap := cap(array) - if capacity <= old_cap { - return nil - } - - if array.allocator.procedure == nil { - array.allocator = context.allocator - } - assert(array.allocator.procedure != nil) - - footer := raw_soa_footer(array) - if size_of(E) == 0 { - footer.cap = capacity - return nil - } - - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - assert(footer.cap == old_cap) - - old_size := 0 - new_size := 0 - - max_align :: align_of(E) - for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } - - if cap(array) <= len(array) + 1 { - cap := 2 * cap(array) + 8 - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - - footer := raw_soa_footer(array) - - if size_of(E) > 0 && cap(array)-len(array) > 0 { - ti := type_info_of(T) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - field_count: uintptr - when intrinsics.type_is_array(E) { - field_count = len(E) - } else { - field_count = uintptr(intrinsics.type_struct_field_count(E)) - } - - data := (^rawptr)(array)^ - - soa_offset := 0 - item_offset := 0 - - arg_copy := arg - arg_ptr := &arg_copy - - max_align :: align_of(E) - for i in 0.. (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return - } - - arg_len := len(args) - if arg_len == 0 { - return - } - - if cap(array) <= len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - arg_len = min(cap(array)-len(array), arg_len) - - footer := raw_soa_footer(array) - if size_of(E) > 0 && arg_len > 0 { - ti := type_info_of(typeid_of(T)) - ti = type_info_base(ti) - si := &ti.variant.(Type_Info_Struct) - field_count := uintptr(intrinsics.type_struct_field_count(E)) - - data := (^rawptr)(array)^ - - soa_offset := 0 - item_offset := 0 - - args_ptr := &args[0] - - max_align :: align_of(E) - for i in 0.. Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { - array := array - ptr := (^rawptr)(&array)^ - free(ptr, allocator, loc) or_return - } - return nil -} - -delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error { - when intrinsics.type_struct_field_count(E) != 0 { - array := array - ptr := (^rawptr)(&array)^ - footer := raw_soa_footer(&array) - free(ptr, footer.allocator, loc) or_return - } - return nil -} - - -@builtin -delete_soa :: proc{ - delete_soa_slice, - delete_soa_dynamic_array, -} - - -clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { - when intrinsics.type_struct_field_count(E) != 0 { - footer := raw_soa_footer(array) - footer.len = 0 - } -} - -@builtin -clear_soa :: proc{ - clear_soa_dynamic_array, -} \ No newline at end of file diff --git a/core/runtime/default_allocators_arena.odin b/core/runtime/default_allocators_arena.odin deleted file mode 100644 index 1fe3c6cfc..000000000 --- a/core/runtime/default_allocators_arena.odin +++ /dev/null @@ -1,304 +0,0 @@ -package runtime - -import "core:intrinsics" - -DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) - -Memory_Block :: struct { - prev: ^Memory_Block, - allocator: Allocator, - base: [^]byte, - used: uint, - capacity: uint, -} - -Arena :: struct { - backing_allocator: Allocator, - curr_block: ^Memory_Block, - total_used: uint, - total_capacity: uint, - minimum_block_size: uint, - temp_count: uint, -} - -@(private, require_results) -safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { - z, did_overflow := intrinsics.overflow_add(x, y) - return z, !did_overflow -} - -@(require_results) -memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { - total_size := uint(capacity + max(alignment, size_of(Memory_Block))) - base_offset := uintptr(max(alignment, size_of(Memory_Block))) - - min_alignment: int = max(16, align_of(Memory_Block), int(alignment)) - data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return - block = (^Memory_Block)(raw_data(data)) - end := uintptr(raw_data(data)[len(data):]) - - block.allocator = allocator - block.base = ([^]byte)(uintptr(block) + base_offset) - block.capacity = uint(end - uintptr(block.base)) - - // Should be zeroed - assert(block.used == 0) - assert(block.prev == nil) - return -} - -memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { - if block_to_free != nil { - allocator := block_to_free.allocator - mem_free(block_to_free, allocator, loc) - } -} - -@(require_results) -alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) { - calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { - alignment_offset := uint(0) - ptr := uintptr(block.base[block.used:]) - mask := alignment-1 - if ptr & mask != 0 { - alignment_offset = uint(alignment - (ptr & mask)) - } - return alignment_offset - - } - if block == nil { - return nil, .Out_Of_Memory - } - alignment_offset := calc_alignment_offset(block, uintptr(alignment)) - size, size_ok := safe_add(min_size, alignment_offset) - if !size_ok { - err = .Out_Of_Memory - return - } - - if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity { - err = .Out_Of_Memory - return - } - data = block.base[block.used+alignment_offset:][:min_size] - block.used += size - return -} - -@(require_results) -arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint { - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p - } - - assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) - - size := size - if size == 0 { - return - } - - needed := align_forward_uint(size, alignment) - if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity { - if arena.minimum_block_size == 0 { - arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE - } - - block_size := max(needed, arena.minimum_block_size) - - if arena.backing_allocator.procedure == nil { - arena.backing_allocator = default_allocator() - } - - new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return - new_block.prev = arena.curr_block - arena.curr_block = new_block - arena.total_capacity += new_block.capacity - } - - prev_used := arena.curr_block.used - data, err = alloc_from_memory_block(arena.curr_block, size, alignment) - arena.total_used += arena.curr_block.used - prev_used - return -} - -// `arena_init` will initialize the arena with a usuable block. -// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary -@(require_results) -arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { - arena^ = {} - arena.backing_allocator = backing_allocator - arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB - new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return - arena.curr_block = new_block - arena.total_capacity += new_block.capacity - return nil -} - - -arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { - if free_block := arena.curr_block; free_block != nil { - arena.curr_block = free_block.prev - - arena.total_capacity -= free_block.capacity - memory_block_dealloc(free_block, loc) - } -} - -// `arena_free_all` will free all but the first memory block, and then reset the memory block -arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { - for arena.curr_block != nil && arena.curr_block.prev != nil { - arena_free_last_memory_block(arena, loc) - } - - if arena.curr_block != nil { - intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) - arena.curr_block.used = 0 - } - arena.total_used = 0 -} - -arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { - for arena.curr_block != nil { - free_block := arena.curr_block - arena.curr_block = free_block.prev - - arena.total_capacity -= free_block.capacity - memory_block_dealloc(free_block, loc) - } - arena.total_used = 0 - arena.total_capacity = 0 -} - -arena_allocator :: proc(arena: ^Arena) -> Allocator { - return Allocator{arena_allocator_proc, arena} -} - -arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, - location := #caller_location) -> (data: []byte, err: Allocator_Error) { - arena := (^Arena)(allocator_data) - - size, alignment := uint(size), uint(alignment) - old_size := uint(old_size) - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return arena_alloc(arena, size, alignment, location) - case .Free: - err = .Mode_Not_Implemented - case .Free_All: - arena_free_all(arena, location) - case .Resize, .Resize_Non_Zeroed: - old_data := ([^]byte)(old_memory) - - switch { - case old_data == nil: - return arena_alloc(arena, size, alignment, location) - case size == old_size: - // return old memory - data = old_data[:size] - return - case size == 0: - err = .Mode_Not_Implemented - return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return - } - - new_memory := arena_alloc(arena, size, alignment, location) or_return - if new_memory == nil { - return - } - copy(new_memory, old_data[:old_size]) - return new_memory, nil - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features} - } - case .Query_Info: - err = .Mode_Not_Implemented - } - - return -} - - - - -Arena_Temp :: struct { - arena: ^Arena, - block: ^Memory_Block, - used: uint, -} - -@(require_results) -arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { - assert(arena != nil, "nil arena", loc) - - temp.arena = arena - temp.block = arena.curr_block - if arena.curr_block != nil { - temp.used = arena.curr_block.used - } - arena.temp_count += 1 - return -} - -arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - if temp.arena == nil { - assert(temp.block == nil) - assert(temp.used == 0) - return - } - arena := temp.arena - - if temp.block != nil { - memory_block_found := false - for block := arena.curr_block; block != nil; block = block.prev { - if block == temp.block { - memory_block_found = true - break - } - } - if !memory_block_found { - assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc) - } - - for arena.curr_block != temp.block { - arena_free_last_memory_block(arena) - } - - if block := arena.curr_block; block != nil { - assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.capacity-block.used) - intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) - block.used = temp.used - } - } - - assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) - arena.temp_count -= 1 -} - -// Ignore the use of a `arena_temp_begin` entirely -arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { - assert(temp.arena != nil, "nil arena", loc) - arena := temp.arena - - assert(arena.temp_count > 0, "double-use of arena_temp_end", loc) - arena.temp_count -= 1 -} - -arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { - assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) -} diff --git a/core/runtime/default_allocators_general.odin b/core/runtime/default_allocators_general.odin deleted file mode 100644 index 994a672b0..000000000 --- a/core/runtime/default_allocators_general.odin +++ /dev/null @@ -1,23 +0,0 @@ -//+build !windows -//+build !freestanding -//+build !wasi -//+build !js -package runtime - -// TODO(bill): reimplement these procedures in the os_specific stuff -import "core:os" - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - _ :: os - - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - - default_allocator_proc :: os.heap_allocator_proc - - default_allocator :: proc() -> Allocator { - return os.heap_allocator() - } -} diff --git a/core/runtime/default_allocators_js.odin b/core/runtime/default_allocators_js.odin deleted file mode 100644 index 715073f08..000000000 --- a/core/runtime/default_allocators_js.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build js -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_nil.odin b/core/runtime/default_allocators_nil.odin deleted file mode 100644 index c882f5196..000000000 --- a/core/runtime/default_allocators_nil.odin +++ /dev/null @@ -1,88 +0,0 @@ -package runtime - -nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return nil, .Out_Of_Memory - case .Free: - return nil, .None - case .Free_All: - return nil, .Mode_Not_Implemented - case .Resize, .Resize_Non_Zeroed: - if size == 0 { - return nil, .None - } - return nil, .Out_Of_Memory - case .Query_Features: - return nil, .Mode_Not_Implemented - case .Query_Info: - return nil, .Mode_Not_Implemented - } - return nil, .None -} - -nil_allocator :: proc() -> Allocator { - return Allocator{ - procedure = nil_allocator_proc, - data = nil, - } -} - - - -when ODIN_OS == .Freestanding { - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} - - - -panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc: - if size > 0 { - panic("panic allocator, .Alloc called", loc=loc) - } - case .Alloc_Non_Zeroed: - if size > 0 { - panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) - } - case .Resize: - if size > 0 { - panic("panic allocator, .Resize called", loc=loc) - } - case .Resize_Non_Zeroed: - if size > 0 { - panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) - } - case .Free: - if old_memory != nil { - panic("panic allocator, .Free called", loc=loc) - } - case .Free_All: - panic("panic allocator, .Free_All called", loc=loc) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Query_Features} - } - return nil, nil - - case .Query_Info: - panic("panic allocator, .Query_Info called", loc=loc) - } - - return nil, nil -} - -panic_allocator :: proc() -> Allocator { - return Allocator{ - procedure = panic_allocator_proc, - data = nil, - } -} diff --git a/core/runtime/default_allocators_wasi.odin b/core/runtime/default_allocators_wasi.odin deleted file mode 100644 index a7e6842a6..000000000 --- a/core/runtime/default_allocators_wasi.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build wasi -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_windows.odin b/core/runtime/default_allocators_windows.odin deleted file mode 100644 index 1b0f78428..000000000 --- a/core/runtime/default_allocators_windows.odin +++ /dev/null @@ -1,44 +0,0 @@ -//+build windows -package runtime - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - data, err = _windows_default_alloc(size, alignment, mode == .Alloc) - - case .Free: - _windows_default_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize, .Resize_Non_Zeroed: - data, err = _windows_default_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} - } - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return - } - - default_allocator :: proc() -> Allocator { - return Allocator{ - procedure = default_allocator_proc, - data = nil, - } - } -} diff --git a/core/runtime/default_temporary_allocator.odin b/core/runtime/default_temporary_allocator.odin deleted file mode 100644 index c90f0388d..000000000 --- a/core/runtime/default_temporary_allocator.odin +++ /dev/null @@ -1,79 +0,0 @@ -package runtime - -DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte) -NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR - -when NO_DEFAULT_TEMP_ALLOCATOR { - Default_Temp_Allocator :: struct {} - - default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {} - - default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {} - - default_temp_allocator_proc :: nil_allocator_proc - - @(require_results) - default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { - return - } - - default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - } -} else { - Default_Temp_Allocator :: struct { - arena: Arena, - } - - default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) { - _ = arena_init(&s.arena, uint(size), backing_allocator) - } - - default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) { - if s != nil { - arena_destroy(&s.arena) - s^ = {} - } - } - - default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - - s := (^Default_Temp_Allocator)(allocator_data) - return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc) - } - - @(require_results) - default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) { - if context.temp_allocator.data == &global_default_temp_allocator_data { - temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc) - } - return - } - - default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { - arena_temp_end(temp, loc) - } - - @(fini, private) - _destroy_temp_allocator_fini :: proc() { - default_temp_allocator_destroy(&global_default_temp_allocator_data) - } -} - -@(deferred_out=default_temp_allocator_temp_end) -DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) { - if ignore { - return {}, loc - } else { - return default_temp_allocator_temp_begin(loc), loc - } -} - - -default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator { - return Allocator{ - procedure = default_temp_allocator_proc, - data = allocator, - } -} diff --git a/core/runtime/docs.odin b/core/runtime/docs.odin deleted file mode 100644 index a520584c5..000000000 --- a/core/runtime/docs.odin +++ /dev/null @@ -1,179 +0,0 @@ -package runtime - -/* - -package runtime has numerous entities (declarations) which are required by the compiler to function. - - -## Basic types and calls (and anything they rely on) - -Source_Code_Location -Context -Allocator -Logger - -__init_context -_cleanup_runtime - - -## cstring calls - -cstring_to_string -cstring_len - - - -## Required when RTTI is enabled (the vast majority of targets) - -Type_Info - -type_table -__type_info_of - - -## Hashing - -default_hasher -default_hasher_cstring -default_hasher_string - - -## Pseudo-CRT required procedured due to LLVM but useful in general -memset -memcpy -memove - - -## Procedures required by the LLVM backend -umodti3 -udivti3 -modti3 -divti3 -fixdfti -fixunsdfti -fixunsdfdi -floattidf -floattidf_unsigned -truncsfhf2 -truncdfhf2 -gnu_h2f_ieee -gnu_f2h_ieee -extendhfsf2 -__ashlti3 // wasm specific -__multi3 // wasm specific - - - -## Required an entry point is defined (i.e. 'main') - -args__ - - -## When -no-crt is defined (and not a wasm target) (mostly due to LLVM) -_tls_index -_fltused - - -## Bounds checking procedures (when not disabled with -no-bounds-check) - -bounds_check_error -matrix_bounds_check_error -slice_expr_error_hi -slice_expr_error_lo_hi -multi_pointer_slice_expr_error - - -## Type assertion check - -type_assertion_check -type_assertion_check2 // takes in typeid - - -## Arithmetic - -quo_complex32 -quo_complex64 -quo_complex128 - -mul_quaternion64 -mul_quaternion128 -mul_quaternion256 - -quo_quaternion64 -quo_quaternion128 -quo_quaternion256 - -abs_complex32 -abs_complex64 -abs_complex128 - -abs_quaternion64 -abs_quaternion128 -abs_quaternion256 - - -## Comparison - -memory_equal -memory_compare -memory_compare_zero - -cstring_eq -cstring_ne -cstring_lt -cstring_gt -cstring_le -cstring_gt - -string_eq -string_ne -string_lt -string_gt -string_le -string_gt - -complex32_eq -complex32_ne -complex64_eq -complex64_ne -complex128_eq -complex128_ne - -quaternion64_eq -quaternion64_ne -quaternion128_eq -quaternion128_ne -quaternion256_eq -quaternion256_ne - - -## Map specific calls - -map_seed_from_map_data -__dynamic_map_check_grow // static map calls -map_insert_hash_dynamic // static map calls -__dynamic_map_get // dynamic map calls -__dynamic_map_set // dynamic map calls - - -## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) - -__dynamic_array_reserve -__dynamic_array_append - -__dynamic_map_reserve - - -## Objective-C specific - -objc_lookUpClass -sel_registerName -objc_allocateClassPair - - -## for-in `string` type - -string_decode_rune -string_decode_last_rune // #reverse for - -*/ \ No newline at end of file diff --git a/core/runtime/dynamic_array_internal.odin b/core/runtime/dynamic_array_internal.odin deleted file mode 100644 index 267ee0785..000000000 --- a/core/runtime/dynamic_array_internal.odin +++ /dev/null @@ -1,138 +0,0 @@ -package runtime - -__dynamic_array_make :: proc(array_: rawptr, elem_size, elem_align: int, len, cap: int, loc := #caller_location) { - array := (^Raw_Dynamic_Array)(array_) - array.allocator = context.allocator - assert(array.allocator.procedure != nil) - - if cap > 0 { - __dynamic_array_reserve(array_, elem_size, elem_align, cap, loc) - array.len = len - } -} - -__dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap: int, loc := #caller_location) -> bool { - array := (^Raw_Dynamic_Array)(array_) - - // NOTE(tetra, 2020-01-26): We set the allocator before earlying-out below, because user code is usually written - // assuming that appending/reserving will set the allocator, if it is not already set. - if array.allocator.procedure == nil { - array.allocator = context.allocator - } - assert(array.allocator.procedure != nil) - - if cap <= array.cap { - return true - } - - old_size := array.cap * elem_size - new_size := cap * elem_size - allocator := array.allocator - - new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) - if err != nil { - return false - } - if elem_size == 0 { - array.data = raw_data(new_data) - array.cap = cap - return true - } else if new_data != nil { - array.data = raw_data(new_data) - array.cap = min(cap, len(new_data)/elem_size) - return true - } - return false -} - -__dynamic_array_shrink :: proc(array_: rawptr, elem_size, elem_align: int, new_cap: int, loc := #caller_location) -> (did_shrink: bool) { - array := (^Raw_Dynamic_Array)(array_) - - // NOTE(tetra, 2020-01-26): We set the allocator before earlying-out below, because user code is usually written - // assuming that appending/reserving will set the allocator, if it is not already set. - if array.allocator.procedure == nil { - array.allocator = context.allocator - } - assert(array.allocator.procedure != nil) - - if new_cap > array.cap { - return - } - - new_cap := new_cap - new_cap = max(new_cap, 0) - old_size := array.cap * elem_size - new_size := new_cap * elem_size - allocator := array.allocator - - new_data, err := mem_resize(array.data, old_size, new_size, elem_align, allocator, loc) - if err != nil { - return - } - - array.data = raw_data(new_data) - array.len = min(new_cap, array.len) - array.cap = new_cap - return true -} - -__dynamic_array_resize :: proc(array_: rawptr, elem_size, elem_align: int, len: int, loc := #caller_location) -> bool { - array := (^Raw_Dynamic_Array)(array_) - - ok := __dynamic_array_reserve(array_, elem_size, elem_align, len, loc) - if ok { - array.len = len - } - return ok -} - - -__dynamic_array_append :: proc(array_: rawptr, elem_size, elem_align: int, - items: rawptr, item_count: int, loc := #caller_location) -> int { - array := (^Raw_Dynamic_Array)(array_) - - if items == nil { - return 0 - } - if item_count <= 0 { - return 0 - } - - - ok := true - if array.cap < array.len+item_count { - cap := 2 * array.cap + max(8, item_count) - ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc) - } - // TODO(bill): Better error handling for failed reservation - if !ok { - return array.len - } - - assert(array.data != nil) - data := uintptr(array.data) + uintptr(elem_size*array.len) - - mem_copy(rawptr(data), items, elem_size * item_count) - array.len += item_count - return array.len -} - -__dynamic_array_append_nothing :: proc(array_: rawptr, elem_size, elem_align: int, loc := #caller_location) -> int { - array := (^Raw_Dynamic_Array)(array_) - - ok := true - if array.cap < array.len+1 { - cap := 2 * array.cap + max(8, 1) - ok = __dynamic_array_reserve(array, elem_size, elem_align, cap, loc) - } - // TODO(bill): Better error handling for failed reservation - if !ok { - return array.len - } - - assert(array.data != nil) - data := uintptr(array.data) + uintptr(elem_size*array.len) - mem_zero(rawptr(data), elem_size) - array.len += 1 - return array.len -} diff --git a/core/runtime/dynamic_map_internal.odin b/core/runtime/dynamic_map_internal.odin deleted file mode 100644 index 491a7974d..000000000 --- a/core/runtime/dynamic_map_internal.odin +++ /dev/null @@ -1,924 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - -// High performance, cache-friendly, open-addressed Robin Hood hashing hash map -// data structure with various optimizations for Odin. -// -// Copyright 2022 (c) Dale Weiler -// -// The core of the hash map data structure is the Raw_Map struct which is a -// type-erased representation of the map. This type-erased representation is -// used in two ways: static and dynamic. When static type information is known, -// the procedures suffixed with _static should be used instead of _dynamic. The -// static procedures are optimized since they have type information. Hashing of -// keys, comparison of keys, and data lookup are all optimized. When type -// information is not known, the procedures suffixed with _dynamic should be -// used. The representation of the map is the same for both static and dynamic, -// and procedures of each can be mixed and matched. The purpose of the dynamic -// representation is to enable reflection and runtime manipulation of the map. -// The dynamic procedures all take an additional Map_Info structure parameter -// which carries runtime values describing the size, alignment, and offset of -// various traits of a given key and value type pair. The Map_Info value can -// be created by calling map_info(K, V) with the key and value typeids. -// -// This map implementation makes extensive use of uintptr for representing -// sizes, lengths, capacities, masks, pointers, offsets, and addresses to avoid -// expensive sign extension and masking that would be generated if types were -// casted all over. The only place regular ints show up is in the cap() and -// len() implementations. -// -// To make this map cache-friendly it uses a novel strategy to ensure keys and -// values of the map are always cache-line aligned and that no single key or -// value of any type ever straddles a cache-line. This cache efficiency makes -// for quick lookups because the linear-probe always addresses data in a cache -// friendly way. This is enabled through the use of a special meta-type called -// a Map_Cell which packs as many values of a given type into a local array adding -// internal padding to round to MAP_CACHE_LINE_SIZE. One other benefit to storing -// the internal data in this manner is false sharing no longer occurs when using -// a map, enabling efficient concurrent access of the map data structure with -// minimal locking if desired. - -// With Robin Hood hashing a maximum load factor of 75% is ideal. -MAP_LOAD_FACTOR :: 75 - -// Minimum log2 capacity. -MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements - -// Has to be less than 100% though. -#assert(MAP_LOAD_FACTOR < 100) - -// This is safe to change. The log2 size of a cache-line. At minimum it has to -// be six though. Higher cache line sizes are permitted. -MAP_CACHE_LINE_LOG2 :: 6 - -// The size of a cache-line. -MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2 - -// The minimum cache-line size allowed by this implementation is 64 bytes since -// we need 6 bits in the base pointer to store the integer log2 capacity, which -// at maximum is 63. Odin uses signed integers to represent length and capacity, -// so only 63 bits are needed in the maximum case. -#assert(MAP_CACHE_LINE_SIZE >= 64) - -// Map_Cell type that packs multiple T in such a way to ensure that each T stays -// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0 -// -// This means a value of type T will never straddle a cache-line. -// -// When multiple Ts can fit in a single cache-line the data array will have more -// than one element. When it cannot, the data array will have one element and -// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE. -// -// We rely on the type system to do all the arithmetic and padding for us here. -// -// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit -// more involved as there now may be internal padding. The indexing now becomes -// -// N :: len(Map_Cell(T){}.data) -// i := index / N -// j := index % N -// cell[i].data[j] -// -// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some -// optimizations we can do to eliminate the need for any divisions as N will -// be bounded by [1, 64). -// -// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated -// as a regular array of T, which is the case for hashes. -Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) { - data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T, -} - -// So we can operate on a cell data structure at runtime without any type -// information, we have a simple table that stores some traits about the cell. -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Map_Cell_Info :: struct { - size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits - elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits -} - -// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...} -map_cell_info :: intrinsics.type_map_cell_info - -// Same as the above procedure but at runtime with the cell Map_Cell_Info value. -@(require_results) -map_cell_index_dynamic :: #force_inline proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, index: uintptr) -> uintptr { - // Micro-optimize the common cases to save on integer division. - elements_per_cell := uintptr(info.elements_per_cell) - size_of_cell := uintptr(info.size_of_cell) - switch elements_per_cell { - case 1: - return base + (index * size_of_cell) - case 2: - cell_index := index >> 1 - data_index := index & 1 - size_of_type := uintptr(info.size_of_type) - return base + (cell_index * size_of_cell) + (data_index * size_of_type) - case: - cell_index := index / elements_per_cell - data_index := index % elements_per_cell - size_of_type := uintptr(info.size_of_type) - return base + (cell_index * size_of_cell) + (data_index * size_of_type) - } -} - -// Same as above procedure but with compile-time constant index. -@(require_results) -map_cell_index_dynamic_const :: proc "contextless" (base: uintptr, #no_alias info: ^Map_Cell_Info, $INDEX: uintptr) -> uintptr { - elements_per_cell := uintptr(info.elements_per_cell) - size_of_cell := uintptr(info.size_of_cell) - size_of_type := uintptr(info.size_of_type) - cell_index := INDEX / elements_per_cell - data_index := INDEX % elements_per_cell - return base + (cell_index * size_of_cell) + (data_index * size_of_type) -} - -// We always round the capacity to a power of two so this becomes [16]Foo, which -// works out to [4]Cell(Foo). -// -// The following compile-time procedure indexes such a [N]Cell(T) structure as -// if it were a flat array accounting for the internal padding introduced by the -// Cell structure. -@(require_results) -map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T), index: uintptr) -> ^T #no_bounds_check { - N :: size_of(Map_Cell(T){}.data) / size_of(T) when size_of(T) > 0 else 1 - - #assert(N <= MAP_CACHE_LINE_SIZE) - - when size_of(Map_Cell(T)) == size_of([N]T) { - // No padding case, can treat as a regular array of []T. - - return &([^]T)(cells)[index] - } else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) { - // Likely case, N is a power of two because T is a power of two. - - // Compute the integer log 2 of N, this is the shift amount to index the - // correct cell. Odin's intrinsics.count_leading_zeros does not produce a - // constant, hence this approach. We only need to check up to N = 64. - SHIFT :: 1 when N < 2 else - 2 when N < 4 else - 3 when N < 8 else - 4 when N < 16 else - 5 when N < 32 else 6 - #assert(SHIFT <= MAP_CACHE_LINE_LOG2) - // Unique case, no need to index data here since only one element. - when N == 1 { - return &cells[index >> SHIFT].data[0] - } else { - return &cells[index >> SHIFT].data[index & (N - 1)] - } - } else { - // Least likely (and worst case), we pay for a division operation but we - // assume the compiler does not actually generate a division. N will be in the - // range [1, CACHE_LINE_SIZE) and not a power of two. - return &cells[index / N].data[index % N] - } -} - -// len() for map -@(require_results) -map_len :: #force_inline proc "contextless" (m: Raw_Map) -> int { - return int(m.len) -} - -// cap() for map -@(require_results) -map_cap :: #force_inline proc "contextless" (m: Raw_Map) -> int { - // The data uintptr stores the capacity in the lower six bits which gives the - // a maximum value of 2^6-1, or 63. We store the integer log2 of capacity - // since our capacity is always a power of two. We only need 63 bits as Odin - // represents length and capacity as a signed integer. - return 0 if m.data == 0 else 1 << map_log2_cap(m) -} - -// Query the load factor of the map. This is not actually configurable, but -// some math is needed to compute it. Compute it as a fixed point percentage to -// avoid floating point operations. This division can be optimized out by -// multiplying by the multiplicative inverse of 100. -@(require_results) -map_load_factor :: #force_inline proc "contextless" (log2_capacity: uintptr) -> uintptr { - return ((uintptr(1) << log2_capacity) * MAP_LOAD_FACTOR) / 100 -} - -@(require_results) -map_resize_threshold :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return map_load_factor(map_log2_cap(m)) -} - -// The data stores the log2 capacity in the lower six bits. This is primarily -// used in the implementation rather than map_cap since the check for data = 0 -// isn't necessary in the implementation. cap() on the otherhand needs to work -// when called on an empty map. -@(require_results) -map_log2_cap :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return m.data & (64 - 1) -} - -// Canonicalize the data by removing the tagged capacity stored in the lower six -// bits of the data uintptr. -@(require_results) -map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return m.data &~ uintptr(64 - 1) -} - - -Map_Hash :: uintptr - -TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1) - -// Procedure to check if a slot is empty for a given hash. This is represented -// by the zero value to make the zero value useful. This is a procedure just -// for prose reasons. -@(require_results) -map_hash_is_empty :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { - return hash == 0 -} - -@(require_results) -map_hash_is_deleted :: #force_no_inline proc "contextless" (hash: Map_Hash) -> bool { - // The MSB indicates a tombstone - return hash & TOMBSTONE_MASK != 0 -} -@(require_results) -map_hash_is_valid :: #force_inline proc "contextless" (hash: Map_Hash) -> bool { - // The MSB indicates a tombstone - return (hash != 0) & (hash & TOMBSTONE_MASK == 0) -} - -@(require_results) -map_seed :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr { - return map_seed_from_map_data(map_data(m)) -} - -// splitmix for uintptr -@(require_results) -map_seed_from_map_data :: #force_inline proc "contextless" (data: uintptr) -> uintptr { - when size_of(uintptr) == size_of(u64) { - mix := data + 0x9e3779b97f4a7c15 - mix = (mix ~ (mix >> 30)) * 0xbf58476d1ce4e5b9 - mix = (mix ~ (mix >> 27)) * 0x94d049bb133111eb - return mix ~ (mix >> 31) - } else { - mix := data + 0x9e3779b9 - mix = (mix ~ (mix >> 16)) * 0x21f0aaad - mix = (mix ~ (mix >> 15)) * 0x735a2d97 - return mix ~ (mix >> 15) - } -} - -// Computes the desired position in the array. This is just index % capacity, -// but a procedure as there's some math involved here to recover the capacity. -@(require_results) -map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash) -> uintptr { - // We do not use map_cap since we know the capacity will not be zero here. - capacity := uintptr(1) << map_log2_cap(m) - return uintptr(hash & Map_Hash(capacity - 1)) -} - -@(require_results) -map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr { - // We do not use map_cap since we know the capacity will not be zero here. - capacity := uintptr(1) << map_log2_cap(m) - return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1) -} - -// When working with the type-erased structure at runtime we need information -// about the map to make working with it possible. This info structure stores -// that. -// -// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be -// modified after creation -// -// 32-bytes on 64-bit -// 16-bytes on 32-bit -Map_Info :: struct { - ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit - vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit - key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit - key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit -} - - -// The Map_Info structure is basically a pseudo-table of information for a given K and V pair. -// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...} -map_info :: intrinsics.type_map_info - -@(require_results) -map_kvh_data_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (ks: uintptr, vs: uintptr, hs: [^]Map_Hash, sk: uintptr, sv: uintptr) { - INFO_HS := intrinsics.type_map_cell_info(Map_Hash) - - capacity := uintptr(1) << map_log2_cap(m) - ks = map_data(m) - vs = map_cell_index_dynamic(ks, info.ks, capacity) // Skip past ks to get start of vs - hs_ := map_cell_index_dynamic(vs, info.vs, capacity) // Skip past vs to get start of hs - sk = map_cell_index_dynamic(hs_, INFO_HS, capacity) // Skip past hs to get start of sk - // Need to skip past two elements in the scratch key space to get to the start - // of the scratch value space, of which there's only two elements as well. - sv = map_cell_index_dynamic_const(sk, info.ks, 2) - - hs = ([^]Map_Hash)(hs_) - return -} - -@(require_results) -map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info) -> (vs: uintptr) { - capacity := uintptr(1) << map_log2_cap(m) - return map_cell_index_dynamic(map_data(m), info.ks, capacity) // Skip past ks to get start of vs -} - - -@(private, require_results) -map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { - round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { - CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 - return (value + CACHE_MASK) &~ CACHE_MASK - } - INFO_HS := intrinsics.type_map_cell_info(Map_Hash) - - size := uintptr(0) - size = round(map_cell_index_dynamic(size, info.ks, capacity)) - size = round(map_cell_index_dynamic(size, info.vs, capacity)) - size = round(map_cell_index_dynamic(size, INFO_HS, capacity)) - size = round(map_cell_index_dynamic(size, info.ks, 2)) // Two additional ks for scratch storage - size = round(map_cell_index_dynamic(size, info.vs, 2)) // Two additional vs for scratch storage - return size -} - -// The only procedure which needs access to the context is the one which allocates the map. -@(require_results) -map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { - result.allocator = allocator // set the allocator always - if log2_capacity == 0 { - return - } - - if log2_capacity >= 64 { - // Overflowed, would be caused by log2_capacity > 64 - return {}, .Out_Of_Memory - } - - capacity := uintptr(1) << max(log2_capacity, MAP_MIN_LOG2_CAPACITY) - - CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 - - size := map_total_allocation_size(capacity, info) - - data := mem_alloc_non_zeroed(int(size), MAP_CACHE_LINE_SIZE, allocator, loc) or_return - data_ptr := uintptr(raw_data(data)) - if data_ptr == 0 { - err = .Out_Of_Memory - return - } - if intrinsics.expect(data_ptr & CACHE_MASK != 0, false) { - panic("allocation not aligned to a cache line", loc) - } else { - result.data = data_ptr | log2_capacity // Tagged pointer representation for capacity. - result.len = 0 - - map_clear_dynamic(&result, info) - } - return -} - -// This procedure has to stack allocate storage to store local keys during the -// Robin Hood hashing technique where elements are swapped in the backing -// arrays to reduce variance. This swapping can only be done with memcpy since -// there is no type information. -// -// This procedure returns the address of the just inserted value. -@(require_results) -map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { - h := h - pos := map_desired_position(m^, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(m^)) - 1 - - ks, vs, hs, sk, sv := map_kvh_data_dynamic(m^, info) - - // Avoid redundant loads of these values - size_of_k := info.ks.size_of_type - size_of_v := info.vs.size_of_type - - k := map_cell_index_dynamic(sk, info.ks, 0) - v := map_cell_index_dynamic(sv, info.vs, 0) - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(ik), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(iv), size_of_v) - - // Temporary k and v dynamic storage for swap below - tk := map_cell_index_dynamic(sk, info.ks, 1) - tv := map_cell_index_dynamic(sv, info.vs, 1) - - swap_loop: for { - element_hash := hs[pos] - - if map_hash_is_empty(element_hash) { - k_dst := map_cell_index_dynamic(ks, info.ks, pos) - v_dst := map_cell_index_dynamic(vs, info.vs, pos) - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - return result if result != 0 else v_dst - } - - if map_hash_is_deleted(element_hash) { - break swap_loop - } - - if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { - if result == 0 { - result = map_cell_index_dynamic(vs, info.vs, pos) - } - - kp := map_cell_index_dynamic(ks, info.ks, pos) - vp := map_cell_index_dynamic(vs, info.vs, pos) - - intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) - - intrinsics.mem_copy_non_overlapping(rawptr(tv), rawptr(v), size_of_v) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(vp), size_of_v) - intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(tv), size_of_v) - - th := h - h = hs[pos] - hs[pos] = th - - distance = probe_distance - } - - pos = (pos + 1) & mask - distance += 1 - } - - // backward shift loop - hs[pos] = 0 - look_ahead: uintptr = 1 - for { - la_pos := (pos + look_ahead) & mask - element_hash := hs[la_pos] - - if map_hash_is_deleted(element_hash) { - look_ahead += 1 - hs[la_pos] = 0 - continue - } - - k_dst := map_cell_index_dynamic(ks, info.ks, pos) - v_dst := map_cell_index_dynamic(vs, info.vs, pos) - - if map_hash_is_empty(element_hash) { - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - return result if result != 0 else v_dst - } - - k_src := map_cell_index_dynamic(ks, info.ks, la_pos) - v_src := map_cell_index_dynamic(vs, info.vs, la_pos) - probe_distance := map_probe_distance(m^, element_hash, la_pos) - - if probe_distance < look_ahead { - // probed can be made ideal while placing saved (ending condition) - if result == 0 { - result = v_dst - } - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - // This will be an ideal move - pos = (la_pos - probe_distance) & mask - look_ahead -= probe_distance - - // shift until we hit ideal/empty - for probe_distance != 0 { - k_dst = map_cell_index_dynamic(ks, info.ks, pos) - v_dst = map_cell_index_dynamic(vs, info.vs, pos) - - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) - hs[pos] = element_hash - hs[la_pos] = 0 - - pos = (pos + 1) & mask - la_pos = (la_pos + 1) & mask - look_ahead = (la_pos - pos) & mask - element_hash = hs[la_pos] - if map_hash_is_empty(element_hash) { - return - } - - probe_distance = map_probe_distance(m^, element_hash, la_pos) - if probe_distance == 0 { - return - } - // can be ideal? - if probe_distance < look_ahead { - pos = (la_pos - probe_distance) & mask - } - k_src = map_cell_index_dynamic(ks, info.ks, la_pos) - v_src = map_cell_index_dynamic(vs, info.vs, la_pos) - } - return - } else if distance < probe_distance - look_ahead { - // shift back probed - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v) - hs[pos] = element_hash - hs[la_pos] = 0 - } else { - // place saved, save probed - if result == 0 { - result = v_dst - } - intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) - hs[pos] = h - - intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(k_src), size_of_k) - intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(v_src), size_of_v) - h = hs[la_pos] - hs[la_pos] = 0 - distance = probe_distance - look_ahead - } - - pos = (pos + 1) & mask - distance += 1 - } -} - -@(require_results) -map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { - log2_capacity := map_log2_cap(m^) - new_capacity := uintptr(1) << max(log2_capacity + 1, MAP_MIN_LOG2_CAPACITY) - return map_reserve_dynamic(m, info, new_capacity, loc) -} - - -@(require_results) -map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { - @(require_results) - ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { - z := intrinsics.count_leading_zeros(x) - if z > 0 && x & (x-1) != 0 { - z -= 1 - } - return size_of(uintptr)*8 - 1 - z - } - - if m.allocator.procedure == nil { - m.allocator = context.allocator - } - - new_capacity := new_capacity - old_capacity := uintptr(map_cap(m^)) - - if old_capacity >= new_capacity { - return nil - } - - // ceiling nearest power of two - log2_new_capacity := ceil_log2(new_capacity) - - log2_min_cap := max(MAP_MIN_LOG2_CAPACITY, log2_new_capacity) - - if m.data == 0 { - m^ = map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return - return nil - } - - resized := map_alloc_dynamic(info, log2_min_cap, m.allocator, loc) or_return - - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - - // Cache these loads to avoid hitting them in the for loop. - n := m.len - for i in 0.. (did_shrink: bool, err: Allocator_Error) { - if m.allocator.procedure == nil { - m.allocator = context.allocator - } - - // Cannot shrink the capacity if the number of items in the map would exceed - // one minus the current log2 capacity's resize threshold. That is the shrunk - // map needs to be within the max load factor. - log2_capacity := map_log2_cap(m^) - if uintptr(m.len) >= map_load_factor(log2_capacity - 1) { - return false, nil - } - - shrunk := map_alloc_dynamic(info, log2_capacity - 1, m.allocator) or_return - - capacity := uintptr(1) << log2_capacity - - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - - n := m.len - for i in 0.. Allocator_Error { - ptr := rawptr(map_data(m)) - size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) - err := mem_free_with_size(ptr, size, m.allocator, loc) - #partial switch err { - case .None, .Mode_Not_Implemented: - return nil - } - return err -} - -@(require_results) -map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { - if map_len(m) == 0 { - return 0, false - } - h := info.key_hasher(rawptr(k), map_seed(m)) - p := map_desired_position(m, h) - d := uintptr(0) - c := (uintptr(1) << map_log2_cap(m)) - 1 - ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) - for { - element_hash := hs[p] - if map_hash_is_empty(element_hash) { - return 0, false - } else if d > map_probe_distance(m, element_hash, p) { - return 0, false - } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { - return p, true - } - p = (p + 1) & c - d += 1 - } -} -@(require_results) -map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { - if map_len(m) == 0 { - return false - } - h := info.key_hasher(rawptr(k), map_seed(m)) - p := map_desired_position(m, h) - d := uintptr(0) - c := (uintptr(1) << map_log2_cap(m)) - 1 - ks, _, hs, _, _ := map_kvh_data_dynamic(m, info) - for { - element_hash := hs[p] - if map_hash_is_empty(element_hash) { - return false - } else if d > map_probe_distance(m, element_hash, p) { - return false - } else if element_hash == h && info.key_equal(rawptr(k), rawptr(map_cell_index_dynamic(ks, info.ks, p))) { - return true - } - p = (p + 1) & c - d += 1 - } -} - - - -@(require_results) -map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { - index := map_lookup_dynamic(m^, info, k) or_return - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - hs[index] |= TOMBSTONE_MASK - old_k = map_cell_index_dynamic(ks, info.ks, index) - old_v = map_cell_index_dynamic(vs, info.vs, index) - m.len -= 1 - ok = true - - mask := (uintptr(1)< (ks: [^]Map_Cell(K), vs: [^]Map_Cell(V), hs: [^]Map_Hash) { - capacity := uintptr(cap(m)) - ks = ([^]Map_Cell(K))(map_data(transmute(Raw_Map)m)) - vs = ([^]Map_Cell(V))(map_cell_index_static(ks, capacity)) - hs = ([^]Map_Hash)(map_cell_index_static(vs, capacity)) - return -} - - -@(require_results) -map_get :: proc "contextless" (m: $T/map[$K]$V, key: K) -> (stored_key: K, stored_value: V, ok: bool) { - rm := transmute(Raw_Map)m - if rm.len == 0 { - return - } - info := intrinsics.type_map_info(T) - key := key - - h := info.key_hasher(&key, map_seed(rm)) - pos := map_desired_position(rm, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(rm)) - 1 - ks, vs, hs := map_kvh_data_static(m) - for { - element_hash := hs[pos] - if map_hash_is_empty(element_hash) { - return - } else if distance > map_probe_distance(rm, element_hash, pos) { - return - } else if element_hash == h { - element_key := map_cell_index_static(ks, pos) - if info.key_equal(&key, rawptr(element_key)) { - element_value := map_cell_index_static(vs, pos) - stored_key = (^K)(element_key)^ - stored_value = (^V)(element_value)^ - ok = true - return - } - - } - pos = (pos + 1) & mask - distance += 1 - } -} - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (ptr: rawptr) { - if m.len == 0 { - return nil - } - pos := map_desired_position(m^, h) - distance := uintptr(0) - mask := (uintptr(1) << map_log2_cap(m^)) - 1 - ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) - for { - element_hash := hs[pos] - if map_hash_is_empty(element_hash) { - return nil - } else if distance > map_probe_distance(m^, element_hash, pos) { - return nil - } else if element_hash == h && info.key_equal(key, rawptr(map_cell_index_dynamic(ks, info.ks, pos))) { - return rawptr(map_cell_index_dynamic(vs, info.vs, pos)) - } - pos = (pos + 1) & mask - distance += 1 - } -} - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) { - if m.len >= map_resize_threshold(m^) { - return map_grow_dynamic(m, info, loc), true - } - return nil, false -} - -__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr { - return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc) -} - - -// IMPORTANT: USED WITHIN THE COMPILER -__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr { - if found := __dynamic_map_get(m, info, hash, key); found != nil { - intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type) - return found - } - - hash := hash - err, has_grown := __dynamic_map_check_grow(m, info, loc) - if err != nil { - return nil - } - if has_grown { - hash = info.key_hasher(key, map_seed(m^)) - } - - result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) - m.len += 1 - return rawptr(result) -} - -// IMPORTANT: USED WITHIN THE COMPILER -@(private) -__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { - return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) -} - - - -// NOTE: the default hashing algorithm derives from fnv64a, with some minor modifications to work for `map` type: -// -// * Convert a `0` result to `1` -// * "empty entry" -// * Prevent the top bit from being set -// * "deleted entry" -// -// Both of these modification are necessary for the implementation of the `map` - -INITIAL_HASH_SEED :: 0xcbf29ce484222325 - -HASH_MASK :: 1 << (8*size_of(uintptr) - 1) -1 - -default_hasher :: #force_inline proc "contextless" (data: rawptr, seed: uintptr, N: int) -> uintptr { - h := u64(seed) + INITIAL_HASH_SEED - p := ([^]byte)(data) - for _ in 0.. uintptr { - str := (^[]byte)(data) - return default_hasher(raw_data(str^), seed, len(str)) -} -default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr { - h := u64(seed) + INITIAL_HASH_SEED - if ptr := (^[^]byte)(data)^; ptr != nil { - for ptr[0] != 0 { - h = (h ~ u64(ptr[0])) * 0x100000001b3 - ptr = ptr[1:] - } - } - h &= HASH_MASK - return uintptr(h) | uintptr(uintptr(h) == 0) -} diff --git a/core/runtime/entry_unix.odin b/core/runtime/entry_unix.odin deleted file mode 100644 index f494a509e..000000000 --- a/core/runtime/entry_unix.odin +++ /dev/null @@ -1,59 +0,0 @@ -//+private -//+build linux, darwin, freebsd, openbsd -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when ODIN_BUILD_MODE == .Dynamic { - @(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/) - _odin_entry_point :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - } - @(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/) - _odin_exit_point :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - return 0 - } -} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - when ODIN_NO_CRT { - // NOTE(flysand): We need to start from assembly because we need - // to retrieve argc and argv from the stack - when ODIN_ARCH == .amd64 { - @require foreign import entry "entry_unix_no_crt_amd64.asm" - SYS_exit :: 60 - } else when ODIN_ARCH == .i386 { - @require foreign import entry "entry_unix_no_crt_i386.asm" - SYS_exit :: 1 - } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { - @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" - SYS_exit :: 1 - } - @(link_name="_start_odin", linkage="strong", require) - _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - intrinsics.syscall(SYS_exit, 0) - unreachable() - } - } else { - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } -} diff --git a/core/runtime/entry_unix_no_crt_amd64.asm b/core/runtime/entry_unix_no_crt_amd64.asm deleted file mode 100644 index f0bdce8d7..000000000 --- a/core/runtime/entry_unix_no_crt_amd64.asm +++ /dev/null @@ -1,43 +0,0 @@ -bits 64 - -extern _start_odin -global _start - -section .text - -;; Entry point for programs that specify -no-crt option -;; This entry point should be compatible with dynamic loaders on linux -;; The parameters the dynamic loader passes to the _start function: -;; RDX = pointer to atexit function -;; The stack layout is as follows: -;; +-------------------+ -;; NULL -;; +-------------------+ -;; envp[m] -;; +-------------------+ -;; ... -;; +-------------------+ -;; envp[0] -;; +-------------------+ -;; NULL -;; +-------------------+ -;; argv[n] -;; +-------------------+ -;; ... -;; +-------------------+ -;; argv[0] -;; +-------------------+ -;; argc -;; +-------------------+ <------ RSP -;; -_start: - ;; Mark stack frame as the top of the stack - xor rbp, rbp - ;; Load argc into 1st param reg, argv into 2nd param reg - pop rdi - mov rdx, rsi - ;; Align stack pointer down to 16-bytes (sysv calling convention) - and rsp, -16 - ;; Call into odin entry point - call _start_odin - jmp $$ \ No newline at end of file diff --git a/core/runtime/entry_unix_no_crt_darwin_arm64.asm b/core/runtime/entry_unix_no_crt_darwin_arm64.asm deleted file mode 100644 index 0f71fbdf8..000000000 --- a/core/runtime/entry_unix_no_crt_darwin_arm64.asm +++ /dev/null @@ -1,20 +0,0 @@ - .section __TEXT,__text - - ; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin. - ; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine. - .build_version macos, 10, 0 - - .extern __start_odin - - .global _main - .align 2 -_main: - mov x5, sp ; use x5 as the stack pointer - - str x0, [x5] ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) - str x1, [x5, #8] ; get argv into x1 - - and sp, x5, #~15 ; force 16-byte alignment of the stack - - bl __start_odin ; call into Odin entry point - ret ; should never get here diff --git a/core/runtime/entry_unix_no_crt_i386.asm b/core/runtime/entry_unix_no_crt_i386.asm deleted file mode 100644 index a61d56a16..000000000 --- a/core/runtime/entry_unix_no_crt_i386.asm +++ /dev/null @@ -1,18 +0,0 @@ -bits 32 - -extern _start_odin -global _start - -section .text - -;; NOTE(flysand): For description see the corresponding *_amd64.asm file -;; also I didn't test this on x86-32 -_start: - xor ebp, rbp - pop ecx - mov eax, esp - and esp, -16 - push eax - push ecx - call _start_odin - jmp $$ \ No newline at end of file diff --git a/core/runtime/entry_wasm.odin b/core/runtime/entry_wasm.odin deleted file mode 100644 index e7f3f156f..000000000 --- a/core/runtime/entry_wasm.odin +++ /dev/null @@ -1,20 +0,0 @@ -//+private -//+build wasm32, wasm64p32 -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - @(link_name="_start", linkage="strong", require, export) - _start :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - } - @(link_name="_end", linkage="strong", require, export) - _end :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } -} \ No newline at end of file diff --git a/core/runtime/entry_windows.odin b/core/runtime/entry_windows.odin deleted file mode 100644 index b6fbe1dcc..000000000 --- a/core/runtime/entry_windows.odin +++ /dev/null @@ -1,50 +0,0 @@ -//+private -//+build windows -//+no-instrumentation -package runtime - -import "core:intrinsics" - -when ODIN_BUILD_MODE == .Dynamic { - @(link_name="DllMain", linkage="strong", require) - DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { - context = default_context() - - // Populate Windows DLL-specific global - dll_forward_reason = DLL_Forward_Reason(fdwReason) - - switch dll_forward_reason { - case .Process_Attach: - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - case .Process_Detach: - #force_no_inline _cleanup_runtime() - case .Thread_Attach: - break - case .Thread_Detach: - break - } - return true - } -} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - when ODIN_ARCH == .i386 || ODIN_NO_CRT { - @(link_name="mainCRTStartup", linkage="strong", require) - mainCRTStartup :: proc "system" () -> i32 { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } else { - @(link_name="main", linkage="strong", require) - main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { - args__ = argv[:argc] - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - #force_no_inline _cleanup_runtime() - return 0 - } - } -} \ No newline at end of file diff --git a/core/runtime/error_checks.odin b/core/runtime/error_checks.odin deleted file mode 100644 index ea6333c29..000000000 --- a/core/runtime/error_checks.odin +++ /dev/null @@ -1,292 +0,0 @@ -package runtime - -@(no_instrumentation) -bounds_trap :: proc "contextless" () -> ! { - when ODIN_OS == .Windows { - windows_trap_array_bounds() - } else { - trap() - } -} - -@(no_instrumentation) -type_assertion_trap :: proc "contextless" () -> ! { - when ODIN_OS == .Windows { - windows_trap_type_assertion() - } else { - trap() - } -} - - -bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { - if uint(index) < uint(count) { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Index ") - print_i64(i64(index)) - print_string(" is out of range 0..<") - print_i64(i64(count)) - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, index, count) -} - -@(no_instrumentation) -slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid slice indices ") - print_i64(i64(lo)) - print_string(":") - print_i64(i64(hi)) - print_string(" is out of range 0..<") - print_i64(i64(len)) - print_byte('\n') - bounds_trap() -} - -@(no_instrumentation) -multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid slice indices ") - print_i64(i64(lo)) - print_string(":") - print_i64(i64(hi)) - print_byte('\n') - bounds_trap() -} - - -multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { - if lo <= hi { - return - } - multi_pointer_slice_handle_error(file, line, column, lo, hi) -} - -slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { - if 0 <= hi && hi <= len { - return - } - slice_handle_error(file, line, column, 0, hi, len) -} - -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 - } - slice_handle_error(file, line, column, lo, hi, len) -} - -dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { - if 0 <= low && low <= high && high <= max { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid dynamic array indices ") - print_i64(i64(low)) - print_string(":") - print_i64(i64(high)) - print_string(" is out of range 0..<") - print_i64(i64(max)) - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, low, high, max) -} - - -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) { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Matrix indices [") - print_i64(i64(row_index)) - print_string(", ") - print_i64(i64(column_index)) - print_string(" is out of range [0..<") - print_i64(i64(row_count)) - print_string(", 0..<") - print_i64(i64(column_count)) - print_string("]") - print_byte('\n') - bounds_trap() - } - handle_error(file, line, column, row_index, column_index, row_count, column_count) -} - - -when ODIN_NO_RTTI { - type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion\n") - type_assertion_trap() - } - handle_error(file, line, column) - } - - type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion\n") - type_assertion_trap() - } - handle_error(file, line, column) - } -} else { - type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) { - if ok { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion from ") - print_typeid(from) - print_string(" to ") - print_typeid(to) - print_byte('\n') - type_assertion_trap() - } - handle_error(file, line, column, from, to) - } - - type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) { - if ok { - return - } - - variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid { - if id == nil || data == nil { - return id - } - ti := type_info_base(type_info_of(id)) - #partial switch v in ti.variant { - case Type_Info_Any: - return (^any)(data).id - case Type_Info_Union: - tag_ptr := uintptr(data) + v.tag_offset - idx := 0 - switch v.tag_type.size { - case 1: idx = int((^u8)(tag_ptr)^) - 1 - case 2: idx = int((^u16)(tag_ptr)^) - 1 - case 4: idx = int((^u32)(tag_ptr)^) - 1 - case 8: idx = int((^u64)(tag_ptr)^) - 1 - case 16: idx = int((^u128)(tag_ptr)^) - 1 - } - if idx < 0 { - return nil - } else if idx < len(v.variants) { - return v.variants[idx].id - } - } - return id - } - - @(cold, no_instrumentation) - handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { - - actual := variant_type(from, from_data) - - print_caller_location(Source_Code_Location{file, line, column, ""}) - print_string(" Invalid type assertion from ") - print_typeid(from) - print_string(" to ") - print_typeid(to) - if actual != from { - print_string(", actual type: ") - print_typeid(actual) - } - print_byte('\n') - type_assertion_trap() - } - handle_error(file, line, column, from, to, from_data) - } -} - - -make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { - if 0 <= len { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { - print_caller_location(loc) - print_string(" Invalid slice length for make: ") - print_i64(i64(len)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, len) -} - -make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { - if 0 <= len && len <= cap { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { - print_caller_location(loc) - print_string(" Invalid dynamic array parameters for make: ") - print_i64(i64(len)) - print_byte(':') - print_i64(i64(cap)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, len, cap) -} - -make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { - if 0 <= cap { - return - } - @(cold, no_instrumentation) - handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { - print_caller_location(loc) - print_string(" Invalid map capacity for make: ") - print_i64(i64(cap)) - print_byte('\n') - bounds_trap() - } - handle_error(loc, cap) -} - - - - - -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) -} - -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) -} - -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) -} - -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/core/runtime/internal.odin b/core/runtime/internal.odin deleted file mode 100644 index a03c2a701..000000000 --- a/core/runtime/internal.odin +++ /dev/null @@ -1,1036 +0,0 @@ -package runtime - -import "core:intrinsics" - -@(private="file") -IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 - -@(private) -RUNTIME_LINKAGE :: "strong" when ( - (ODIN_USE_SEPARATE_MODULES || - ODIN_BUILD_MODE == .Dynamic || - !ODIN_NO_CRT) && - !IS_WASM) else "internal" -RUNTIME_REQUIRE :: !ODIN_TILDE - -@(private) -__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 - - -@(private) -byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { - return ([^]byte)(data)[:max(len, 0)] -} - -is_power_of_two_int :: #force_inline proc(x: int) -> bool { - if x <= 0 { - return false - } - return (x & (x-1)) == 0 -} - -align_forward_int :: #force_inline proc(ptr, align: int) -> int { - assert(is_power_of_two_int(align)) - - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} - -is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool { - if x <= 0 { - return false - } - return (x & (x-1)) == 0 -} - -align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { - assert(is_power_of_two_uintptr(align)) - - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} - -mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { - if data == nil { - return nil - } - if len <= 0 { - return data - } - intrinsics.mem_zero(data, len) - return data -} - -mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { - if src != nil && dst != src && len > 0 { - // NOTE(bill): This _must_ be implemented like C's memmove - intrinsics.mem_copy(dst, src, len) - } - return dst -} - -mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { - if src != nil && dst != src && len > 0 { - // NOTE(bill): This _must_ be implemented like C's memcpy - intrinsics.mem_copy_non_overlapping(dst, src, len) - } - return dst -} - -DEFAULT_ALIGNMENT :: 2*align_of(rawptr) - -mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 { - return nil, nil - } - if allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) -} - -mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 || allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) -} - -mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 || allocator.procedure == nil { - return nil, nil - } - return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, size, alignment, nil, 0, loc) -} - -mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if ptr == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, 0, loc) - return err -} - -mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if ptr == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc) - return err -} - -mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if bytes == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, raw_data(bytes), len(bytes), loc) - return err -} - - -mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { - if allocator.procedure != nil { - _, err = allocator.procedure(allocator.data, .Free_All, 0, 0, nil, 0, loc) - } - return -} - -_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - if allocator.procedure == nil { - return nil, nil - } - if new_size == 0 { - if ptr != nil { - _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) - return - } - return - } else if ptr == nil { - if should_zero { - return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) - } else { - return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) - } - } else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 { - data = ([^]byte)(ptr)[:old_size] - return - } - - if should_zero { - data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) - } else { - data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc) - } - if err == .Mode_Not_Implemented { - if should_zero { - data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) - } else { - data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) - } - if err != nil { - return - } - copy(data, ([^]byte)(ptr)[:old_size]) - _, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc) - } - return -} - -mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) -} -non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) -} - -memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { - switch { - case n == 0: return true - case x == y: return true - } - a, b := ([^]byte)(x), ([^]byte)(y) - length := uint(n) - - for i := uint(0); i < length; i += 1 { - if a[i] != b[i] { - return false - } - } - return true - -/* - - when size_of(uint) == 8 { - if word_length := length >> 3; word_length != 0 { - for _ in 0..> 2; word_length != 0 { - for _ in 0.. int #no_bounds_check { - switch { - case a == b: return 0 - case a == nil: return -1 - case b == nil: return +1 - } - - x := uintptr(a) - y := uintptr(b) - n := uintptr(n) - - SU :: size_of(uintptr) - fast := n/SU + 1 - offset := (fast-1)*SU - curr_block := uintptr(0) - if n < SU { - fast = 0 - } - - for /**/; curr_block < fast; curr_block += 1 { - va := (^uintptr)(x + curr_block * size_of(uintptr))^ - vb := (^uintptr)(y + curr_block * size_of(uintptr))^ - if va ~ vb != 0 { - for pos := curr_block*SU; pos < n; pos += 1 { - a := (^byte)(x+pos)^ - b := (^byte)(y+pos)^ - if a ~ b != 0 { - return -1 if (int(a) - int(b)) < 0 else +1 - } - } - } - } - - for /**/; offset < n; offset += 1 { - a := (^byte)(x+offset)^ - b := (^byte)(y+offset)^ - if a ~ b != 0 { - return -1 if (int(a) - int(b)) < 0 else +1 - } - } - - return 0 -} - -memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check { - x := uintptr(a) - n := uintptr(n) - - SU :: size_of(uintptr) - fast := n/SU + 1 - offset := (fast-1)*SU - curr_block := uintptr(0) - if n < SU { - fast = 0 - } - - for /**/; curr_block < fast; curr_block += 1 { - va := (^uintptr)(x + curr_block * size_of(uintptr))^ - if va ~ 0 != 0 { - for pos := curr_block*SU; pos < n; pos += 1 { - a := (^byte)(x+pos)^ - if a ~ 0 != 0 { - return -1 if int(a) < 0 else +1 - } - } - } - } - - for /**/; offset < n; offset += 1 { - a := (^byte)(x+offset)^ - if a ~ 0 != 0 { - return -1 if int(a) < 0 else +1 - } - } - - return 0 -} - -string_eq :: proc "contextless" (lhs, rhs: string) -> bool { - x := transmute(Raw_String)lhs - y := transmute(Raw_String)rhs - if x.len != y.len { - return false - } - return #force_inline memory_equal(x.data, y.data, x.len) -} - -string_cmp :: proc "contextless" (a, b: string) -> int { - x := transmute(Raw_String)a - y := transmute(Raw_String)b - - ret := memory_compare(x.data, y.data, min(x.len, y.len)) - if ret == 0 && x.len != y.len { - return -1 if x.len < y.len else +1 - } - return ret -} - -string_ne :: #force_inline proc "contextless" (a, b: string) -> bool { return !string_eq(a, b) } -string_lt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) < 0 } -string_gt :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) > 0 } -string_le :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) <= 0 } -string_ge :: #force_inline proc "contextless" (a, b: string) -> bool { return string_cmp(a, b) >= 0 } - -cstring_len :: proc "contextless" (s: cstring) -> int { - p0 := uintptr((^byte)(s)) - p := p0 - for p != 0 && (^byte)(p)^ != 0 { - p += 1 - } - return int(p - p0) -} - -cstring_to_string :: proc "contextless" (s: cstring) -> string { - if s == nil { - return "" - } - ptr := (^byte)(s) - n := cstring_len(s) - return transmute(string)Raw_String{ptr, n} -} - - -cstring_eq :: proc "contextless" (lhs, rhs: cstring) -> bool { - x := ([^]byte)(lhs) - y := ([^]byte)(rhs) - if x == y { - return true - } - if (x == nil) ~ (y == nil) { - return false - } - xn := cstring_len(lhs) - yn := cstring_len(rhs) - if xn != yn { - return false - } - return #force_inline memory_equal(x, y, xn) -} - -cstring_cmp :: proc "contextless" (lhs, rhs: cstring) -> int { - x := ([^]byte)(lhs) - y := ([^]byte)(rhs) - if x == y { - return 0 - } - if (x == nil) ~ (y == nil) { - return -1 if x == nil else +1 - } - xn := cstring_len(lhs) - yn := cstring_len(rhs) - ret := memory_compare(x, y, min(xn, yn)) - if ret == 0 && xn != yn { - return -1 if xn < yn else +1 - } - return ret -} - -cstring_ne :: #force_inline proc "contextless" (a, b: cstring) -> bool { return !cstring_eq(a, b) } -cstring_lt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) < 0 } -cstring_gt :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) > 0 } -cstring_le :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) <= 0 } -cstring_ge :: #force_inline proc "contextless" (a, b: cstring) -> bool { return cstring_cmp(a, b) >= 0 } - - -complex32_eq :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex32_ne :: #force_inline proc "contextless" (a, b: complex32) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - -complex64_eq :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex64_ne :: #force_inline proc "contextless" (a, b: complex64) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - -complex128_eq :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) == real(b) && imag(a) == imag(b) } -complex128_ne :: #force_inline proc "contextless" (a, b: complex128) -> bool { return real(a) != real(b) || imag(a) != imag(b) } - - -quaternion64_eq :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion64_ne :: #force_inline proc "contextless" (a, b: quaternion64) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - -quaternion128_eq :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion128_ne :: #force_inline proc "contextless" (a, b: quaternion128) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - -quaternion256_eq :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) == real(b) && imag(a) == imag(b) && jmag(a) == jmag(b) && kmag(a) == kmag(b) } -quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } - - -string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { - // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 - - @static accept_sizes := [256]u8{ - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x30-0x3f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x40-0x4f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x50-0x5f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x60-0x6f - 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x70-0x7f - - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x80-0x8f - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0x90-0x9f - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xa0-0xaf - 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xb0-0xbf - 0xf1, 0xf1, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xc0-0xcf - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xd0-0xdf - 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x03, // 0xe0-0xef - 0x34, 0x04, 0x04, 0x04, 0x44, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, // 0xf0-0xff - } - Accept_Range :: struct {lo, hi: u8} - - @static accept_ranges := [5]Accept_Range{ - {0x80, 0xbf}, - {0xa0, 0xbf}, - {0x80, 0x9f}, - {0x90, 0xbf}, - {0x80, 0x8f}, - } - - MASKX :: 0b0011_1111 - MASK2 :: 0b0001_1111 - MASK3 :: 0b0000_1111 - MASK4 :: 0b0000_0111 - - LOCB :: 0b1000_0000 - HICB :: 0b1011_1111 - - - RUNE_ERROR :: '\ufffd' - - n := len(s) - if n < 1 { - return RUNE_ERROR, 0 - } - s0 := s[0] - x := accept_sizes[s0] - if x >= 0xF0 { - mask := rune(x) << 31 >> 31 // NOTE(bill): Create 0x0000 or 0xffff. - return rune(s[0])&~mask | RUNE_ERROR&mask, 1 - } - sz := x & 7 - accept := accept_ranges[x>>4] - if n < int(sz) { - return RUNE_ERROR, 1 - } - b1 := s[1] - if b1 < accept.lo || accept.hi < b1 { - return RUNE_ERROR, 1 - } - if sz == 2 { - return rune(s0&MASK2)<<6 | rune(b1&MASKX), 2 - } - b2 := s[2] - if b2 < LOCB || HICB < b2 { - return RUNE_ERROR, 1 - } - if sz == 3 { - return rune(s0&MASK3)<<12 | rune(b1&MASKX)<<6 | rune(b2&MASKX), 3 - } - b3 := s[3] - if b3 < LOCB || HICB < b3 { - return RUNE_ERROR, 1 - } - return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4 -} - -string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { - RUNE_ERROR :: '\ufffd' - RUNE_SELF :: 0x80 - UTF_MAX :: 4 - - r: rune - size: int - start, end, limit: int - - end = len(s) - if end == 0 { - return RUNE_ERROR, 0 - } - start = end-1 - r = rune(s[start]) - if r < RUNE_SELF { - return r, 1 - } - - limit = max(end - UTF_MAX, 0) - - for start-=1; start >= limit; start-=1 { - if (s[start] & 0xc0) != RUNE_SELF { - break - } - } - - start = max(start, 0) - r, size = string_decode_rune(s[start:end]) - if start+size != end { - return RUNE_ERROR, 1 - } - return r, size -} - -abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * f16(intrinsics.sqrt(f32(1 + q*q))) -} -abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * intrinsics.sqrt(1 + q*q) -} -abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { - p, q := abs(real(x)), abs(imag(x)) - if p < q { - p, q = q, p - } - if p == 0 { - return 0 - } - q = q / p - return p * intrinsics.sqrt(1 + q*q) -} -abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k))) -} -abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return intrinsics.sqrt(r*r + i*i + j*j + k*k) -} -abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 { - r, i, j, k := real(x), imag(x), jmag(x), kmag(x) - return intrinsics.sqrt(r*r + i*i + j*j + k*k) -} - - -quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 { - e, f: f16 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - - -quo_complex64 :: proc "contextless" (n, m: complex64) -> complex64 { - e, f: f32 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - -quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 { - e, f: f64 - - if abs(real(m)) >= abs(imag(m)) { - ratio := imag(m) / real(m) - denom := real(m) + ratio*imag(m) - e = (real(n) + imag(n)*ratio) / denom - f = (imag(n) - real(n)*ratio) / denom - } else { - ratio := real(m) / imag(m) - denom := imag(m) + ratio*real(m) - e = (real(n)*ratio + imag(n)) / denom - f = (imag(n)*ratio - real(n)) / denom - } - - return complex(e, f) -} - -mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3 - t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2 - t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 - t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) - - invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) - - t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 - t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - - return quaternion(w=t0, x=t1, y=t2, z=t3) -} - -@(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -truncsfhf2 :: proc "c" (value: f32) -> __float16 { - v: struct #raw_union { i: u32, f: f32 } - i, s, e, m: i32 - - v.f = value - i = i32(v.i) - - s = (i >> 16) & 0x00008000 - e = ((i >> 23) & 0x000000ff) - (127 - 15) - m = i & 0x007fffff - - - if e <= 0 { - if e < -10 { - return transmute(__float16)u16(s) - } - m = (m | 0x00800000) >> u32(1 - e) - - if m & 0x00001000 != 0 { - m += 0x00002000 - } - - return transmute(__float16)u16(s | (m >> 13)) - } else if e == 0xff - (127 - 15) { - if m == 0 { - return transmute(__float16)u16(s | 0x7c00) /* NOTE(bill): infinity */ - } else { - /* NOTE(bill): NAN */ - m >>= 13 - return transmute(__float16)u16(s | 0x7c00 | m | i32(m == 0)) - } - } else { - if m & 0x00001000 != 0 { - m += 0x00002000 - if (m & 0x00800000) != 0 { - m = 0 - e += 1 - } - } - - if e > 30 { - f := i64(1e12) - for j := 0; j < 10; j += 1 { - /* NOTE(bill): Cause overflow */ - g := intrinsics.volatile_load(&f) - g *= g - intrinsics.volatile_store(&f, g) - } - - return transmute(__float16)u16(s | 0x7c00) - } - - return transmute(__float16)u16(s | (e << 10) | (m >> 13)) - } -} - - -@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -truncdfhf2 :: proc "c" (value: f64) -> __float16 { - return truncsfhf2(f32(value)) -} - -@(link_name="__gnu_h2f_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -gnu_h2f_ieee :: proc "c" (value_: __float16) -> f32 { - fp32 :: struct #raw_union { u: u32, f: f32 } - - value := transmute(u16)value_ - v: fp32 - magic, inf_or_nan: fp32 - magic.u = u32((254 - 15) << 23) - inf_or_nan.u = u32((127 + 16) << 23) - - v.u = u32(value & 0x7fff) << 13 - v.f *= magic.f - if v.f >= inf_or_nan.f { - v.u |= 255 << 23 - } - v.u |= u32(value & 0x8000) << 16 - return v.f -} - - -@(link_name="__gnu_f2h_ieee", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -gnu_f2h_ieee :: proc "c" (value: f32) -> __float16 { - return truncsfhf2(value) -} - -@(link_name="__extendhfsf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -extendhfsf2 :: proc "c" (value: __float16) -> f32 { - return gnu_h2f_ieee(value) -} - - - -@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -floattidf :: proc "c" (a: i128) -> f64 { -when IS_WASM { - return 0 -} else { - DBL_MANT_DIG :: 53 - if a == 0 { - return 0.0 - } - a := a - N :: size_of(i128) * 8 - s := a >> (N-1) - a = (a ~ s) - s - sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits - e := i32(sd - 1) // exponent - if sd > DBL_MANT_DIG { - switch sd { - case DBL_MANT_DIG + 1: - a <<= 1 - case DBL_MANT_DIG + 2: - // okay - case: - a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) - } - - a |= i128((a & 4) != 0) - a += 1 - a >>= 2 - - if a & (i128(1) << DBL_MANT_DIG) != 0 { - a >>= 1 - e += 1 - } - } else { - a <<= u128(DBL_MANT_DIG - sd) & 127 - } - fb: [2]u32 - fb[1] = (u32(s) & 0x80000000) | // sign - (u32(e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high - fb[0] = u32(a) // mantissa-low - return transmute(f64)fb -} -} - - -@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -floattidf_unsigned :: proc "c" (a: u128) -> f64 { -when IS_WASM { - return 0 -} else { - DBL_MANT_DIG :: 53 - if a == 0 { - return 0.0 - } - a := a - N :: size_of(u128) * 8 - sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits - e := i32(sd - 1) // exponent - if sd > DBL_MANT_DIG { - switch sd { - case DBL_MANT_DIG + 1: - a <<= 1 - case DBL_MANT_DIG + 2: - // okay - case: - a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) - } - - a |= u128((a & 4) != 0) - a += 1 - a >>= 2 - - if a & (1 << DBL_MANT_DIG) != 0 { - a >>= 1 - e += 1 - } - } else { - a <<= u128(DBL_MANT_DIG - sd) - } - fb: [2]u32 - fb[1] = (0) | // sign - u32((e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high - fb[0] = u32(a) // mantissa-low - return transmute(f64)fb -} -} - - - -@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 { - // TODO(bill): implement `fixunsdfti` correctly - x := u64(a) - return u128(x) -} - -@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 { - // TODO(bill): implement `fixunsdfdi` correctly - x := i64(a) - return i128(x) -} - - - - -@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -umodti3 :: proc "c" (a, b: u128) -> u128 { - r: u128 = --- - _ = udivmod128(a, b, &r) - return r -} - - -@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - return udivmod128(a, b, rem) -} - -@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) -} - - -@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -modti3 :: proc "c" (a, b: i128) -> i128 { - s_a := a >> (128 - 1) - s_b := b >> (128 - 1) - an := (a ~ s_a) - s_a - bn := (b ~ s_b) - s_b - - r: u128 = --- - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r) - return (transmute(i128)r ~ s_a) - s_a -} - - -@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem) - return transmute(i128)u -} - -@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u -} - - -@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixdfti :: proc(a: u64) -> i128 { - significandBits :: 52 - typeWidth :: (size_of(u64)*8) - exponentBits :: (typeWidth - significandBits - 1) - maxExponent :: ((1 << exponentBits) - 1) - exponentBias :: (maxExponent >> 1) - - implicitBit :: (u64(1) << significandBits) - significandMask :: (implicitBit - 1) - signBit :: (u64(1) << (significandBits + exponentBits)) - absMask :: (signBit - 1) - exponentMask :: (absMask ~ significandMask) - - // Break a into sign, exponent, significand - aRep := a - aAbs := aRep & absMask - sign := i128(-1 if aRep & signBit != 0 else 1) - exponent := u64((aAbs >> significandBits) - exponentBias) - significand := u64((aAbs & significandMask) | implicitBit) - - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0 - } - - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128) - } - - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)) - } else { - return sign * (i128(significand) << (exponent - significandBits)) - } - -} diff --git a/core/runtime/os_specific.odin b/core/runtime/os_specific.odin deleted file mode 100644 index 022d315d4..000000000 --- a/core/runtime/os_specific.odin +++ /dev/null @@ -1,7 +0,0 @@ -package runtime - -_OS_Errno :: distinct int - -os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return _os_write(data) -} diff --git a/core/runtime/os_specific_any.odin b/core/runtime/os_specific_any.odin deleted file mode 100644 index 6a96655c4..000000000 --- a/core/runtime/os_specific_any.odin +++ /dev/null @@ -1,16 +0,0 @@ -//+build !darwin -//+build !freestanding -//+build !js -//+build !wasi -//+build !windows -package runtime - -import "core:os" - -// TODO(bill): reimplement `os.write` so that it does not rely on package os -// NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - context = default_context() - n, err := os.write(os.stderr, data) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_darwin.odin b/core/runtime/os_specific_darwin.odin deleted file mode 100644 index 5de9a7d57..000000000 --- a/core/runtime/os_specific_darwin.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build darwin -package runtime - -import "core:intrinsics" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - ret := intrinsics.syscall(0x2000004, 1, uintptr(raw_data(data)), uintptr(len(data))) - if ret < 0 { - return 0, _OS_Errno(-ret) - } - return int(ret), 0 -} diff --git a/core/runtime/os_specific_freestanding.odin b/core/runtime/os_specific_freestanding.odin deleted file mode 100644 index a6d04cefb..000000000 --- a/core/runtime/os_specific_freestanding.odin +++ /dev/null @@ -1,7 +0,0 @@ -//+build freestanding -package runtime - -// TODO(bill): reimplement `os.write` -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return 0, -1 -} diff --git a/core/runtime/os_specific_js.odin b/core/runtime/os_specific_js.odin deleted file mode 100644 index 246141d87..000000000 --- a/core/runtime/os_specific_js.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build js -package runtime - -foreign import "odin_env" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - foreign odin_env { - write :: proc "contextless" (fd: u32, p: []byte) --- - } - write(1, data) - return len(data), 0 -} diff --git a/core/runtime/os_specific_wasi.odin b/core/runtime/os_specific_wasi.odin deleted file mode 100644 index 3f69504ee..000000000 --- a/core/runtime/os_specific_wasi.odin +++ /dev/null @@ -1,10 +0,0 @@ -//+build wasi -package runtime - -import "core:sys/wasm/wasi" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - data := (wasi.ciovec_t)(data) - n, err := wasi.fd_write(1, {data}) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_windows.odin b/core/runtime/os_specific_windows.odin deleted file mode 100644 index 4a5907466..000000000 --- a/core/runtime/os_specific_windows.odin +++ /dev/null @@ -1,135 +0,0 @@ -//+build windows -package runtime - -foreign import kernel32 "system:Kernel32.lib" - -@(private="file") -@(default_calling_convention="system") -foreign kernel32 { - // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency - - // os_write - GetStdHandle :: proc(which: u32) -> rawptr --- - SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- - WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- - GetLastError :: proc() -> u32 --- - - // default_allocator - GetProcessHeap :: proc() -> rawptr --- - HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- - HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- - HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- -} - -_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { - if len(data) == 0 { - return 0, 0 - } - - STD_ERROR_HANDLE :: ~u32(0) -12 + 1 - HANDLE_FLAG_INHERIT :: 0x00000001 - MAX_RW :: 1<<30 - - h := GetStdHandle(STD_ERROR_HANDLE) - when size_of(uintptr) == 8 { - SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) - } - - single_write_length: u32 - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := u32(min(i32(remaining), MAX_RW)) - - e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - err = _OS_Errno(GetLastError()) - n = int(total_write) - return - } - total_write += i64(single_write_length) - } - n = int(total_write) - return -} - -heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) -} -heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size) - } - - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) -} -heap_free :: proc "contextless" (ptr: rawptr) { - if ptr == nil { - return - } - HeapFree(GetProcessHeap(), 0, ptr) -} - - -// -// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. -// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert -// padding. We also store the original pointer returned by heap_alloc right before -// the pointer we return to the user. -// - - - -_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { - if size == 0 { - _windows_default_free(old_ptr) - return nil, nil - } - - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := ([^]rawptr)(old_ptr)[-1] - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) - } - aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):] - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = ([^]byte)(aligned_ptr) - ([^]rawptr)(aligned_mem)[-1] = allocated_mem - - return aligned_mem[:size], nil -} - -_windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(size, alignment, nil, zero_memory) -} - - -_windows_default_free :: proc "contextless" (ptr: rawptr) { - if ptr != nil { - heap_free(([^]rawptr)(ptr)[-1]) - } -} - -_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(new_size, new_alignment, p) -} diff --git a/core/runtime/print.odin b/core/runtime/print.odin deleted file mode 100644 index 87c8757d5..000000000 --- a/core/runtime/print.odin +++ /dev/null @@ -1,489 +0,0 @@ -package runtime - -_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" - -@(private="file") -_INTEGER_DIGITS_VAR := _INTEGER_DIGITS - -when !ODIN_NO_RTTI { - print_any_single :: proc "contextless" (arg: any) { - x := arg - if x.data == nil { - print_string("nil") - return - } - - if loc, ok := x.(Source_Code_Location); ok { - print_caller_location(loc) - return - } - x.id = typeid_base(x.id) - switch v in x { - case typeid: print_typeid(v) - case ^Type_Info: print_type(v) - - case string: print_string(v) - case cstring: print_string(string(v)) - case []byte: print_string(string(v)) - - case rune: print_rune(v) - - case u8: print_u64(u64(v)) - case u16: print_u64(u64(v)) - case u16le: print_u64(u64(v)) - case u16be: print_u64(u64(v)) - case u32: print_u64(u64(v)) - case u32le: print_u64(u64(v)) - case u32be: print_u64(u64(v)) - case u64: print_u64(u64(v)) - case u64le: print_u64(u64(v)) - case u64be: print_u64(u64(v)) - - case i8: print_i64(i64(v)) - case i16: print_i64(i64(v)) - case i16le: print_i64(i64(v)) - case i16be: print_i64(i64(v)) - case i32: print_i64(i64(v)) - case i32le: print_i64(i64(v)) - case i32be: print_i64(i64(v)) - case i64: print_i64(i64(v)) - case i64le: print_i64(i64(v)) - case i64be: print_i64(i64(v)) - - case int: print_int(v) - case uint: print_uint(v) - case uintptr: print_uintptr(v) - case rawptr: print_uintptr(uintptr(v)) - - case bool: print_string("true" if v else "false") - case b8: print_string("true" if v else "false") - case b16: print_string("true" if v else "false") - case b32: print_string("true" if v else "false") - case b64: print_string("true" if v else "false") - - case: - ti := type_info_of(x.id) - #partial switch v in ti.variant { - case Type_Info_Pointer, Type_Info_Multi_Pointer: - print_uintptr((^uintptr)(x.data)^) - return - } - - print_string("") - } - } - println_any :: proc "contextless" (args: ..any) { - context = default_context() - loop: for arg, i in args { - assert(arg.id != nil) - if i != 0 { - print_string(" ") - } - print_any_single(arg) - } - print_string("\n") - } -} - - -encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { - r := c - - buf: [4]u8 - i := u32(r) - mask :: u8(0x3f) - if i <= 1<<7-1 { - buf[0] = u8(r) - return buf, 1 - } - if i <= 1<<11-1 { - buf[0] = 0xc0 | u8(r>>6) - buf[1] = 0x80 | u8(r) & mask - return buf, 2 - } - - // Invalid or Surrogate range - if i > 0x0010ffff || - (0xd800 <= i && i <= 0xdfff) { - r = 0xfffd - } - - if i <= 1<<16-1 { - buf[0] = 0xe0 | u8(r>>12) - buf[1] = 0x80 | u8(r>>6) & mask - buf[2] = 0x80 | u8(r) & mask - return buf, 3 - } - - buf[0] = 0xf0 | u8(r>>18) - buf[1] = 0x80 | u8(r>>12) & mask - buf[2] = 0x80 | u8(r>>6) & mask - buf[3] = 0x80 | u8(r) & mask - return buf, 4 -} - -print_string :: proc "contextless" (str: string) -> (n: int) { - n, _ = os_write(transmute([]byte)str) - return -} - -print_strings :: proc "contextless" (args: ..string) -> (n: int) { - for str in args { - m, err := os_write(transmute([]byte)str) - n += m - if err != 0 { - break - } - } - return -} - -print_byte :: proc "contextless" (b: byte) -> (n: int) { - n, _ = os_write([]byte{b}) - return -} - -print_encoded_rune :: proc "contextless" (r: rune) { - print_byte('\'') - - switch r { - case '\a': print_string("\\a") - case '\b': print_string("\\b") - case '\e': print_string("\\e") - case '\f': print_string("\\f") - case '\n': print_string("\\n") - case '\r': print_string("\\r") - case '\t': print_string("\\t") - case '\v': print_string("\\v") - case: - if r <= 0 { - print_string("\\x00") - } else if r < 32 { - n0, n1 := u8(r) >> 4, u8(r) & 0xf - print_string("\\x") - print_byte(_INTEGER_DIGITS_VAR[n0]) - print_byte(_INTEGER_DIGITS_VAR[n1]) - } else { - print_rune(r) - } - } - print_byte('\'') -} - -print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { - RUNE_SELF :: 0x80 - - if r < RUNE_SELF { - return print_byte(byte(r)) - } - - b, n := encode_rune(r) - m, _ := os_write(b[:n]) - return m -} - - -print_u64 :: proc "contextless" (x: u64) #no_bounds_check { - a: [129]byte - i := len(a) - b := u64(10) - u := x - for u >= b { - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - u /= b - } - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - - os_write(a[i:]) -} - - -print_i64 :: proc "contextless" (x: i64) #no_bounds_check { - b :: i64(10) - - u := x - neg := u < 0 - u = abs(u) - - a: [129]byte - i := len(a) - for u >= b { - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - u /= b - } - i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - if neg { - i -= 1; a[i] = '-' - } - - os_write(a[i:]) -} - -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_string(loc.file_path) - when ODIN_ERROR_POS_STYLE == .Default { - print_byte('(') - print_u64(u64(loc.line)) - 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)) - print_byte(':') - } else { - #panic("unhandled ODIN_ERROR_POS_STYLE") - } -} -print_typeid :: proc "contextless" (id: typeid) { - when ODIN_NO_RTTI { - if id == nil { - print_string("nil") - } else { - print_string("") - } - } else { - if id == nil { - print_string("nil") - } else { - ti := type_info_of(id) - print_type(ti) - } - } -} -print_type :: proc "contextless" (ti: ^Type_Info) { - if ti == nil { - print_string("nil") - return - } - - switch info in ti.variant { - case Type_Info_Named: - print_string(info.name) - case Type_Info_Integer: - switch ti.id { - case int: print_string("int") - case uint: print_string("uint") - case uintptr: print_string("uintptr") - case: - print_byte('i' if info.signed else 'u') - print_u64(u64(8*ti.size)) - } - case Type_Info_Rune: - print_string("rune") - case Type_Info_Float: - print_byte('f') - print_u64(u64(8*ti.size)) - case Type_Info_Complex: - print_string("complex") - print_u64(u64(8*ti.size)) - case Type_Info_Quaternion: - print_string("quaternion") - print_u64(u64(8*ti.size)) - case Type_Info_String: - print_string("string") - case Type_Info_Boolean: - switch ti.id { - case bool: print_string("bool") - case: - print_byte('b') - print_u64(u64(8*ti.size)) - } - case Type_Info_Any: - print_string("any") - case Type_Info_Type_Id: - print_string("typeid") - - case Type_Info_Pointer: - if info.elem == nil { - print_string("rawptr") - } else { - print_string("^") - print_type(info.elem) - } - case Type_Info_Multi_Pointer: - print_string("[^]") - print_type(info.elem) - case Type_Info_Soa_Pointer: - print_string("#soa ^") - print_type(info.elem) - case Type_Info_Procedure: - print_string("proc") - if info.params == nil { - print_string("()") - } else { - t := info.params.variant.(Type_Info_Parameters) - print_byte('(') - for t, i in t.types { - if i > 0 { print_string(", ") } - print_type(t) - } - print_string(")") - } - if info.results != nil { - print_string(" -> ") - print_type(info.results) - } - case Type_Info_Parameters: - count := len(info.names) - if count != 1 { print_byte('(') } - for name, i in info.names { - if i > 0 { print_string(", ") } - - t := info.types[i] - - if len(name) > 0 { - print_string(name) - print_string(": ") - } - print_type(t) - } - if count != 1 { print_string(")") } - - case Type_Info_Array: - print_byte('[') - print_u64(u64(info.count)) - print_byte(']') - print_type(info.elem) - - case Type_Info_Enumerated_Array: - if info.is_sparse { - print_string("#sparse") - } - print_byte('[') - print_type(info.index) - print_byte(']') - print_type(info.elem) - - - case Type_Info_Dynamic_Array: - print_string("[dynamic]") - print_type(info.elem) - case Type_Info_Slice: - print_string("[]") - print_type(info.elem) - - case Type_Info_Map: - print_string("map[") - print_type(info.key) - print_byte(']') - print_type(info.value) - - case Type_Info_Struct: - switch info.soa_kind { - case .None: // Ignore - case .Fixed: - print_string("#soa[") - print_u64(u64(info.soa_len)) - print_byte(']') - print_type(info.soa_base_type) - return - case .Slice: - print_string("#soa[]") - print_type(info.soa_base_type) - return - case .Dynamic: - print_string("#soa[dynamic]") - print_type(info.soa_base_type) - return - } - - print_string("struct ") - if info.is_packed { print_string("#packed ") } - if info.is_raw_union { print_string("#raw_union ") } - if info.custom_align { - print_string("#align(") - print_u64(u64(ti.align)) - print_string(") ") - } - print_byte('{') - for name, i in info.names { - if i > 0 { print_string(", ") } - print_string(name) - print_string(": ") - print_type(info.types[i]) - } - print_byte('}') - - case Type_Info_Union: - print_string("union ") - if info.custom_align { - print_string("#align(") - print_u64(u64(ti.align)) - print_string(") ") - } - if info.no_nil { - print_string("#no_nil ") - } - print_byte('{') - for variant, i in info.variants { - if i > 0 { print_string(", ") } - print_type(variant) - } - print_string("}") - - case Type_Info_Enum: - print_string("enum ") - print_type(info.base) - print_string(" {") - for name, i in info.names { - if i > 0 { print_string(", ") } - print_string(name) - } - print_string("}") - - case Type_Info_Bit_Set: - print_string("bit_set[") - - #partial switch elem in type_info_base(info.elem).variant { - case Type_Info_Enum: - print_type(info.elem) - case Type_Info_Rune: - print_encoded_rune(rune(info.lower)) - print_string("..") - print_encoded_rune(rune(info.upper)) - case: - print_i64(info.lower) - print_string("..") - print_i64(info.upper) - } - if info.underlying != nil { - print_string("; ") - print_type(info.underlying) - } - print_byte(']') - - - case Type_Info_Simd_Vector: - print_string("#simd[") - print_u64(u64(info.count)) - print_byte(']') - print_type(info.elem) - - case Type_Info_Relative_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Relative_Multi_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Matrix: - print_string("matrix[") - print_u64(u64(info.row_count)) - print_string(", ") - print_u64(u64(info.column_count)) - print_string("]") - print_type(info.elem) - } -} diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin deleted file mode 100644 index 454574c35..000000000 --- a/core/runtime/procs.odin +++ /dev/null @@ -1,95 +0,0 @@ -package runtime - -when ODIN_NO_CRT && ODIN_OS == .Windows { - foreign import lib "system:NtDll.lib" - - @(private="file") - @(default_calling_convention="system") - foreign lib { - RtlMoveMemory :: proc(dst, s: rawptr, length: int) --- - RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- - } - - @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - RtlFillMemory(ptr, len, val) - return ptr - } - @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - RtlMoveMemory(dst, src, len) - return dst - } - @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - RtlMoveMemory(dst, src, len) - return dst - } -} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { - @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - if ptr != nil && len != 0 { - b := byte(val) - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = b - } - } - return ptr - } - - @(link_name="bzero", linkage="strong", require) - bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { - if ptr != nil && len != 0 { - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = 0 - } - } - return ptr - } - - @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - d, s := ([^]byte)(dst), ([^]byte)(src) - if d == s || len == 0 { - return dst - } - if d > s && uintptr(d)-uintptr(s) < uintptr(len) { - for i := len-1; i >= 0; i -= 1 { - d[i] = s[i] - } - return dst - } - - if s > d && uintptr(s)-uintptr(d) < uintptr(len) { - for i := 0; i < len; i += 1 { - d[i] = s[i] - } - return dst - } - return memcpy(dst, src, len) - } - @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { - d, s := ([^]byte)(dst), ([^]byte)(src) - if d != s { - for i := 0; i < len; i += 1 { - d[i] = s[i] - } - } - return d - - } -} else { - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { - if ptr != nil && len != 0 { - b := byte(val) - p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { - p[i] = b - } - } - return ptr - } -} \ No newline at end of file diff --git a/core/runtime/procs_darwin.odin b/core/runtime/procs_darwin.odin deleted file mode 100644 index 9c53b5b16..000000000 --- a/core/runtime/procs_darwin.odin +++ /dev/null @@ -1,21 +0,0 @@ -//+private -package runtime - -foreign import "system:Foundation.framework" - -import "core:intrinsics" - -objc_id :: ^intrinsics.objc_object -objc_Class :: ^intrinsics.objc_class -objc_SEL :: ^intrinsics.objc_selector - -foreign Foundation { - objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- - sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- - objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class --- - - objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- - objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- - objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- - objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- -} diff --git a/core/runtime/procs_js.odin b/core/runtime/procs_js.odin deleted file mode 100644 index d3e12410c..000000000 --- a/core/runtime/procs_js.odin +++ /dev/null @@ -1,15 +0,0 @@ -//+build js -package runtime - -init_default_context_for_js: Context -@(init, private="file") -init_default_context :: proc() { - init_default_context_for_js = context -} - -@(export) -@(link_name="default_context_ptr") -default_context_ptr :: proc "contextless" () -> ^Context { - return &init_default_context_for_js -} - diff --git a/core/runtime/procs_wasm.odin b/core/runtime/procs_wasm.odin deleted file mode 100644 index 26dcfef77..000000000 --- a/core/runtime/procs_wasm.odin +++ /dev/null @@ -1,40 +0,0 @@ -//+build wasm32, wasm64p32 -package runtime - -@(private="file") -ti_int :: struct #raw_union { - using s: struct { lo, hi: u64 }, - all: i128, -} - -@(link_name="__ashlti3", linkage="strong") -__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { - bits_in_dword :: size_of(u32)*8 - b := u32(b_) - - input, result: ti_int - input.all = a - if b & bits_in_dword != 0 { - result.lo = 0 - result.hi = input.lo << (b-bits_in_dword) - } else { - if b == 0 { - return a - } - result.lo = input.lo<>(bits_in_dword-b)) - } - return result.all -} - - -@(link_name="__multi3", linkage="strong") -__multi3 :: proc "contextless" (a, b: i128) -> i128 { - x, y, r: ti_int - - x.all = a - y.all = b - r.all = i128(x.lo * y.lo) // TODO this is incorrect - r.hi += x.hi*y.lo + x.lo*y.hi - return r.all -} \ No newline at end of file diff --git a/core/runtime/procs_windows_amd64.asm b/core/runtime/procs_windows_amd64.asm deleted file mode 100644 index f588b3453..000000000 --- a/core/runtime/procs_windows_amd64.asm +++ /dev/null @@ -1,79 +0,0 @@ -bits 64 - -global __chkstk -global _tls_index -global _fltused - -section .data - _tls_index: dd 0 - _fltused: dd 0x9875 - -section .text -; NOTE(flysand): The function call to __chkstk is called -; by the compiler, when we're allocating arrays larger than -; a page size. The reason is because the OS doesn't map the -; whole stack into memory all at once, but does so page-by-page. -; When the next page is touched, the CPU generates a page fault, -; which *the OS* is handling by allocating the next page in the -; stack until we reach the limit of stack size. -; -; This page is called the guard page, touching it will extend -; the size of the stack and overwrite the stack limit in the TEB. -; -; If we allocate a large enough array and start writing from the -; bottom of it, it's possible that we may start touching -; non-contiguous pages which are unmapped. OS only maps the stack -; page into the memory if the page above it was also mapped. -; -; Therefore the compilers insert this routine, the sole purpose -; of which is to step through the stack starting from the RSP -; down to the new RSP after allocation, and touch every page -; of the new allocation so that the stack is fully mapped for -; the new allocation -; -; I've gotten this code by disassembling the output of MSVC long -; time ago. I don't remember if I've cleaned it up, but it definately -; stinks. -; -; Additional notes: -; RAX (passed as parameter) holds the allocation's size -; GS:[0x10] references the current stack limit -; (i.e. bottom of the stack (i.e. lowest address accessible)) -; -; Also this stuff is windows-only kind of thing, because linux people -; didn't think stack that grows is cool enough for them, but the kernel -; totally supports this kind of stack. -__chkstk: - ;; Allocate 16 bytes to store values of r10 and r11 - sub rsp, 0x10 - mov [rsp], r10 - mov [rsp+0x8], r11 - ;; Set r10 to point to the stack as of the moment of the function call - lea r10, [rsp+0x18] - ;; Subtract r10 til the bottom of the stack allocation, if we overflow - ;; reset r10 to 0, we'll crash with segfault anyway - xor r11, r11 - sub r10, rax - cmovb r10, r11 - ;; Load r11 with the bottom of the stack (lowest allocated address) - mov r11, gs:[0x10] ; NOTE(flysand): gs:[0x10] is stack limit - ;; If the bottom of the allocation is above the bottom of the stack, - ;; we don't need to probe - cmp r10, r11 - jnb .end - ;; Align the bottom of the allocation down to page size - and r10w, 0xf000 -.loop: - ;; Move the pointer to the next guard page, and touch it by loading 0 - ;; into that page - lea r11, [r11-0x1000] - mov byte [r11], 0x0 - ;; Did we reach the bottom of the allocation? - cmp r10, r11 - jnz .loop -.end: - ;; Restore previous r10 and r11 and return - mov r10, [rsp] - mov r11, [rsp+0x8] - add rsp, 0x10 - ret \ No newline at end of file diff --git a/core/runtime/procs_windows_amd64.odin b/core/runtime/procs_windows_amd64.odin deleted file mode 100644 index ea495f5fa..000000000 --- a/core/runtime/procs_windows_amd64.odin +++ /dev/null @@ -1,26 +0,0 @@ -//+private -//+no-instrumentation -package runtime - -foreign import kernel32 "system:Kernel32.lib" - -@(private) -foreign kernel32 { - RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- -} - -windows_trap_array_bounds :: proc "contextless" () -> ! { - EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - - RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) -} - -windows_trap_type_assertion :: proc "contextless" () -> ! { - windows_trap_array_bounds() -} - -when ODIN_NO_CRT { - @(require) - foreign import crt_lib "procs_windows_amd64.asm" -} diff --git a/core/runtime/procs_windows_i386.odin b/core/runtime/procs_windows_i386.odin deleted file mode 100644 index 10422cf07..000000000 --- a/core/runtime/procs_windows_i386.odin +++ /dev/null @@ -1,29 +0,0 @@ -//+private -//+no-instrumentation -package runtime - -@require foreign import "system:int64.lib" - -foreign import kernel32 "system:Kernel32.lib" - -windows_trap_array_bounds :: proc "contextless" () -> ! { - DWORD :: u32 - ULONG_PTR :: uint - - EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - foreign kernel32 { - RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- - } - - RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) -} - -windows_trap_type_assertion :: proc "contextless" () -> ! { - windows_trap_array_bounds() -} - -@(private, export, link_name="_fltused") _fltused: i32 = 0x9875 - -@(private, export, link_name="_tls_index") _tls_index: u32 -@(private, export, link_name="_tls_array") _tls_array: u32 diff --git a/core/runtime/udivmod128.odin b/core/runtime/udivmod128.odin deleted file mode 100644 index 87ef73c2c..000000000 --- a/core/runtime/udivmod128.odin +++ /dev/null @@ -1,156 +0,0 @@ -package runtime - -import "core:intrinsics" - -udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - _ctz :: intrinsics.count_trailing_zeros - _clz :: intrinsics.count_leading_zeros - - n := transmute([2]u64)a - d := transmute([2]u64)b - q, r: [2]u64 - sr: u32 = 0 - - low :: 1 when ODIN_ENDIAN == .Big else 0 - high :: 1 - low - U64_BITS :: 8*size_of(u64) - U128_BITS :: 8*size_of(u128) - - // Special Cases - - if n[high] == 0 { - if d[high] == 0 { - if rem != nil { - res := n[low] % d[low] - rem^ = u128(res) - } - return u128(n[low] / d[low]) - } - - if rem != nil { - rem^ = u128(n[low]) - } - return 0 - } - - if d[low] == 0 { - if d[high] == 0 { - if rem != nil { - rem^ = u128(n[high] % d[low]) - } - return u128(n[high] / d[low]) - } - if n[low] == 0 { - if rem != nil { - r[high] = n[high] % d[high] - r[low] = 0 - rem^ = transmute(u128)r - } - return u128(n[high] / d[high]) - } - - if d[high] & (d[high]-1) == 0 { - if rem != nil { - r[low] = n[low] - r[high] = n[high] & (d[high] - 1) - rem^ = transmute(u128)r - } - return u128(n[high] >> _ctz(d[high])) - } - - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) - if sr > U64_BITS - 2 { - if rem != nil { - rem^ = a - } - return 0 - } - - sr += 1 - - q[low] = 0 - q[high] = n[low] << u64(U64_BITS - sr) - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - } else { - if d[high] == 0 { - if d[low] & (d[low] - 1) == 0 { - if rem != nil { - rem^ = u128(n[low] & (d[low] - 1)) - } - if d[low] == 1 { - return a - } - sr = u32(_ctz(d[low])) - q[high] = n[high] >> sr - q[low] = (n[high] << (U64_BITS-sr)) | (n[low] >> sr) - return transmute(u128)q - } - - sr = 1 + U64_BITS + u32(_clz(d[low])) - u32(_clz(n[high])) - - switch { - case sr == U64_BITS: - q[low] = 0 - q[high] = n[low] - r[high] = 0 - r[low] = n[high] - case sr < U64_BITS: - q[low] = 0 - q[high] = n[low] << (U64_BITS - sr) - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - case: - q[low] = n[low] << (U128_BITS - sr) - q[high] = (n[high] << (U128_BITS - sr)) | (n[low] >> (sr - U64_BITS)) - r[high] = 0 - r[low] = n[high] >> (sr - U64_BITS) - } - } else { - sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high]))) - - if sr > U64_BITS - 1 { - if rem != nil { - rem^ = a - } - return 0 - } - - sr += 1 - - q[low] = 0 - if sr == U64_BITS { - q[high] = n[low] - r[high] = 0 - r[low] = n[high] - } else { - r[high] = n[high] >> sr - r[low] = (n[high] << (U64_BITS - sr)) | (n[low] >> sr) - q[high] = n[low] << (U64_BITS - sr) - } - } - } - - carry: u32 = 0 - r_all: u128 - - for ; sr > 0; sr -= 1 { - r[high] = (r[high] << 1) | (r[low] >> (U64_BITS - 1)) - r[low] = (r[low] << 1) | (q[high] >> (U64_BITS - 1)) - q[high] = (q[high] << 1) | (q[low] >> (U64_BITS - 1)) - q[low] = (q[low] << 1) | u64(carry) - - r_all = transmute(u128)r - s := i128(b - r_all - 1) >> (U128_BITS - 1) - carry = u32(s & 1) - r_all -= b & transmute(u128)s - r = transmute([2]u64)r_all - } - - q_all := ((transmute(u128)q) << 1) | u128(carry) - if rem != nil { - rem^ = r_all - } - - return q_all -} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index af518bcb4..8c9e13178 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1161,7 +1161,27 @@ gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String } -gb_internal String get_fullpath_core(gbAllocator a, String path) { +gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { + String module_dir = odin_root_dir(); + + String base = str_lit("base/"); + + isize str_len = module_dir.len + base.len + path.len; + u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1); + defer (gb_free(heap_allocator(), str)); + + isize i = 0; + gb_memmove(str+i, module_dir.text, module_dir.len); i += module_dir.len; + gb_memmove(str+i, base.text, base.len); i += base.len; + gb_memmove(str+i, path.text, path.len); i += path.len; + str[i] = 0; + + String res = make_string(str, i); + res = string_trim_whitespace(res); + return path_to_fullpath(a, res); +} + +gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { String module_dir = odin_root_dir(); String core = str_lit("core/"); diff --git a/src/checker.cpp b/src/checker.cpp index 498fce7d2..563bb2781 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -770,15 +770,17 @@ gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type * rw_mutex_unlock(&d->type_info_deps_mutex); } -gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + +gb_internal AstPackage *get_runtime_package(CheckerInfo *info) { + String name = str_lit("runtime"); gbAllocator a = heap_allocator(); - String path = get_fullpath_core(a, name); + String path = get_fullpath_base_collection(a, name); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { gb_printf_err("Name: %.*s\n", LIT(name)); gb_printf_err("Fullpath: %.*s\n", LIT(path)); - + for (auto const &entry : info->packages) { gb_printf_err("%.*s\n", LIT(entry.key)); } @@ -787,6 +789,26 @@ gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { return *found; } +gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + if (name == "runtime") { + return get_runtime_package(info); + } + + gbAllocator a = heap_allocator(); + String path = get_fullpath_core_collection(a, name); + defer (gb_free(a, path.text)); + auto found = string_map_get(&info->packages, path); + if (found == nullptr) { + gb_printf_err("Name: %.*s\n", LIT(name)); + gb_printf_err("Fullpath: %.*s\n", LIT(path)); + + for (auto const &entry : info->packages) { + gb_printf_err("%.*s\n", LIT(entry.key)); + } + GB_ASSERT_MSG(found != nullptr, "Missing core package %.*s", LIT(name)); + } + return *found; +} gb_internal void add_package_dependency(CheckerContext *c, char const *package_name, char const *name) { String n = make_string_c(name); diff --git a/src/main.cpp b/src/main.cpp index 19271d667..5cff99160 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2376,6 +2376,7 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init default library collections"); array_init(&library_collections, heap_allocator()); // NOTE(bill): 'core' cannot be (re)defined by the user + add_library_collection(str_lit("base"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("base"))); add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core"))); add_library_collection(str_lit("vendor"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("vendor"))); diff --git a/src/parser.cpp b/src/parser.cpp index b16a88de5..9ed3e32f9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5460,6 +5460,11 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name.len > 0) { + // NOTE(bill): `base:runtime` == `core:runtime` + if (collection_name == "core" && string_starts_with(file_str, str_lit("runtime"))) { + collection_name = str_lit("base"); + } + if (collection_name == "system") { if (node->kind != Ast_ForeignImportDecl) { syntax_error(node, "The library collection 'system' is restrict for 'foreign_library'"); @@ -5489,7 +5494,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node #endif } - if (is_package_name_reserved(file_str)) { *path = file_str; if (collection_name == "core") { @@ -6133,7 +6137,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { { // Add these packages serially and then process them parallel TokenPos init_pos = {}; { - String s = get_fullpath_core(permanent_allocator(), str_lit("runtime")); + String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime")); try_add_import_path(p, s, s, init_pos, Package_Runtime); } @@ -6141,7 +6145,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { p->init_fullpath = init_fullpath; if (build_context.command_kind == Command_test) { - String s = get_fullpath_core(permanent_allocator(), str_lit("testing")); + String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing")); try_add_import_path(p, s, s, init_pos, Package_Normal); } -- cgit v1.2.3 From 395e0fb225816ff9699e82f6d9d5887ef3b1358a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jan 2024 22:09:20 +0000 Subject: `-default-to-panic-allocator` --- base/runtime/default_allocators_general.odin | 5 +++++ src/build_settings.cpp | 4 +++- src/checker.cpp | 27 ++++++++++++++------------- src/main.cpp | 14 ++++++++++++++ src/parser.cpp | 4 ++-- 5 files changed, 38 insertions(+), 16 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/default_allocators_general.odin b/base/runtime/default_allocators_general.odin index 994a672b0..e3b06af7b 100644 --- a/base/runtime/default_allocators_general.odin +++ b/base/runtime/default_allocators_general.odin @@ -13,6 +13,11 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR { // mem.nil_allocator reimplementation default_allocator_proc :: nil_allocator_proc default_allocator :: nil_allocator +} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR { + _ :: os + + default_allocator_proc :: panic_allocator_proc + default_allocator :: panic_allocator } else { default_allocator_proc :: os.heap_allocator_proc diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8c9e13178..8204d735f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -323,6 +323,7 @@ struct BuildContext { bool ODIN_DEBUG; // Odin in debug mode bool ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not bool ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing) + bool ODIN_DEFAULT_TO_PANIC_ALLOCATOR; // Whether the default allocator is a "panic" allocator or not (i.e. panics on any call to it) bool ODIN_FOREIGN_ERROR_PROCEDURES; bool ODIN_VALGRIND_SUPPORT; @@ -1609,7 +1610,8 @@ gb_internal bool init_build_paths(String init_filename) { } - if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR || + build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { bc->no_dynamic_literals = true; } diff --git a/src/checker.cpp b/src/checker.cpp index 47fcd3d8f..565e948f8 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1091,19 +1091,20 @@ 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_DYNAMIC_LITERALS", bc->no_dynamic_literals); - add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); - add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); - add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); - add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); - add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); - add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); - - add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); - add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); + 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_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); + add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); + add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); + add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); + add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); + add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); + + add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); + add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); diff --git a/src/main.cpp b/src/main.cpp index 5cff99160..d77f135a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -273,6 +273,7 @@ enum BuildFlagKind { BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, + BuildFlag_DefaultToPanicAllocator, BuildFlag_StrictStyle, BuildFlag_ForeignErrorProcedures, BuildFlag_NoRTTI, @@ -460,6 +461,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_StrictStyle, str_lit("strict-style"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ForeignErrorProcedures, str_lit("foreign-error-procedures"), BuildFlagParam_None, Command__does_check); @@ -1122,8 +1124,20 @@ gb_internal bool parse_build_flags(Array args) { break; case BuildFlag_DefaultToNilAllocator: + if (build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + gb_printf_err("'-default-to-panic-allocator' cannot be used with '-default-to-nil-allocator'\n"); + bad_flags = true; + } build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR = true; break; + case BuildFlag_DefaultToPanicAllocator: + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + gb_printf_err("'-default-to-nil-allocator' cannot be used with '-default-to-panic-allocator'\n"); + bad_flags = true; + } + build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; + break; + case BuildFlag_ForeignErrorProcedures: build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true; break; diff --git a/src/parser.cpp b/src/parser.cpp index 9ed3e32f9..489d6b5d5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5496,10 +5496,10 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (is_package_name_reserved(file_str)) { *path = file_str; - if (collection_name == "core") { + if (collection_name == "core" || collection_name == "base") { return true; } else { - syntax_error(node, "The package '%.*s' must be imported with the core library collection: 'core:%.*s'", LIT(file_str), LIT(file_str)); + syntax_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); return false; } } -- cgit v1.2.3 From 16bd19ed4399cfa56ee9d7d5d467b0f25cf91646 Mon Sep 17 00:00:00 2001 From: FourteenBrush Date: Fri, 2 Feb 2024 21:00:00 +0100 Subject: Fix Unhandled Ast_OrBranchExpr --- src/parser.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 2671054df..6127be38e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -230,6 +230,10 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { case Ast_OrReturnExpr: n->OrReturnExpr.expr = clone_ast(n->OrReturnExpr.expr, f); break; + case Ast_OrBranchExpr: + n->OrBranchExpr.label = clone_ast(n->OrBranchExpr.label, f); + n->OrBranchExpr.expr = clone_ast(n->OrBranchExpr.expr, f); + break; case Ast_TypeAssertion: n->TypeAssertion.expr = clone_ast(n->TypeAssertion.expr, f); n->TypeAssertion.type = clone_ast(n->TypeAssertion.type, f); -- cgit v1.2.3 From 0e5d7801dde74a3e22f9d6c0d27b749bae2a2e7f Mon Sep 17 00:00:00 2001 From: FourteenBrush Date: Fri, 2 Feb 2024 21:17:10 +0100 Subject: Fix code style --- src/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 86e2dcf68..48f2f8617 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -232,7 +232,7 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { break; case Ast_OrBranchExpr: n->OrBranchExpr.label = clone_ast(n->OrBranchExpr.label, f); - n->OrBranchExpr.expr = clone_ast(n->OrBranchExpr.expr, f); + n->OrBranchExpr.expr = clone_ast(n->OrBranchExpr.expr, f); break; case Ast_TypeAssertion: n->TypeAssertion.expr = clone_ast(n->TypeAssertion.expr, f); -- cgit v1.2.3 From a08250ac5b88068cf928552e2628d1e3c7ade95c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 7 Feb 2024 17:15:59 +0000 Subject: Improve error handling for missing library collection provided by the compiler --- src/build_settings.cpp | 26 ++++++++++++++++---------- src/checker.cpp | 4 ++-- src/main.cpp | 24 +++++++++++++++++------- src/parser.cpp | 15 ++++++++++++--- 4 files changed, 47 insertions(+), 22 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 374ecbdfa..9a773f9d3 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -876,7 +876,7 @@ gb_internal String internal_odin_root_dir(void) { #include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -930,7 +930,7 @@ gb_internal String internal_odin_root_dir(void) { // NOTE: Linux / Unix is unfinished and not tested very well. #include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -1091,7 +1091,7 @@ gb_internal String internal_odin_root_dir(void) { gb_global BlockingMutex fullpath_mutex; #if defined(GB_SYSTEM_WINDOWS) -gb_internal String path_to_fullpath(gbAllocator a, String s) { +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { String result = {}; String16 string16 = string_to_string16(heap_allocator(), s); @@ -1117,7 +1117,9 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { result.text[i] = '/'; } } + if (ok_) *ok_ = true; } else { + if (ok_) *ok_ = false; mutex_unlock(&fullpath_mutex); } @@ -1129,7 +1131,11 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); mutex_unlock(&fullpath_mutex); - if(p == nullptr) return String{}; + if(p == nullptr) { + if (ok_) *ok_ = false; + return String{}; + } + if (ok_) *ok_ = true; return make_string_c(p); } #else @@ -1137,7 +1143,7 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { #endif -gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path) { +gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path, bool *ok_) { u8 *str = gb_alloc_array(heap_allocator(), u8, base_dir.len+1+path.len+1); defer (gb_free(heap_allocator(), str)); @@ -1159,11 +1165,11 @@ gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } -gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { +gb_internal String get_fullpath_base_collection(gbAllocator a, String path, bool *ok_) { String module_dir = odin_root_dir(); String base = str_lit("base/"); @@ -1180,10 +1186,10 @@ gb_internal String get_fullpath_base_collection(gbAllocator a, String path) { String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } -gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { +gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool *ok_) { String module_dir = odin_root_dir(); String core = str_lit("core/"); @@ -1200,7 +1206,7 @@ gb_internal String get_fullpath_core_collection(gbAllocator a, String path) { String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } gb_internal bool show_error_line(void) { diff --git a/src/checker.cpp b/src/checker.cpp index e4a680a20..457ee6146 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -774,7 +774,7 @@ gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type * gb_internal AstPackage *get_runtime_package(CheckerInfo *info) { String name = str_lit("runtime"); gbAllocator a = heap_allocator(); - String path = get_fullpath_base_collection(a, name); + String path = get_fullpath_base_collection(a, name, nullptr); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { @@ -795,7 +795,7 @@ gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { } gbAllocator a = heap_allocator(); - String path = get_fullpath_core_collection(a, name); + String path = get_fullpath_core_collection(a, name, nullptr); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { diff --git a/src/main.cpp b/src/main.cpp index 1136db62a..7951ca2db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -807,9 +807,10 @@ gb_internal bool parse_build_flags(Array args) { } gbAllocator a = heap_allocator(); - String fullpath = path_to_fullpath(a, path); - if (!path_is_directory(fullpath)) { - gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(fullpath)); + bool path_ok = false; + String fullpath = path_to_fullpath(a, path, &path_ok); + if (!path_ok || !path_is_directory(fullpath)) { + gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(path_ok ? fullpath : path)); gb_free(a, fullpath.text); bad_flags = true; break; @@ -2395,9 +2396,18 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init default library collections"); array_init(&library_collections, heap_allocator()); // NOTE(bill): 'core' cannot be (re)defined by the user - add_library_collection(str_lit("base"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("base"))); - add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core"))); - add_library_collection(str_lit("vendor"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("vendor"))); + + auto const &add_collection = [](String const &name) { + bool ok = false; + add_library_collection(name, get_fullpath_relative(heap_allocator(), odin_root_dir(), name, &ok)); + if (!ok) { + compiler_error("Cannot find the library collection '%.*s'. Is the ODIN_ROOT set up correctly?", LIT(name)); + } + }; + + add_collection(str_lit("base")); + add_collection(str_lit("core")); + add_collection(str_lit("vendor")); TIME_SECTION("init args"); map_init(&build_context.defined_values); @@ -2581,7 +2591,7 @@ int main(int arg_count, char const **arg_ptr) { // NOTE(bill): add 'shared' directory if it is not already set if (!find_library_collection_path(str_lit("shared"), nullptr)) { add_library_collection(str_lit("shared"), - get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); + get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"), nullptr)); } init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr, selected_subtarget); diff --git a/src/parser.cpp b/src/parser.cpp index 48f2f8617..2a7f41b36 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5519,7 +5519,8 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (has_windows_drive) { *path = file_str; } else { - String fullpath = string_trim_whitespace(get_fullpath_relative(permanent_allocator(), base_dir, file_str)); + bool ok = false; + String fullpath = string_trim_whitespace(get_fullpath_relative(permanent_allocator(), base_dir, file_str, &ok)); *path = fullpath; } return true; @@ -6141,7 +6142,11 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { { // Add these packages serially and then process them parallel TokenPos init_pos = {}; { - String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime")); + bool ok = false; + String s = get_fullpath_base_collection(permanent_allocator(), str_lit("runtime"), &ok); + if (!ok) { + compiler_error("Unable to find The 'base:runtime' package. Is the ODIN_ROOT set up correctly?"); + } try_add_import_path(p, s, s, init_pos, Package_Runtime); } @@ -6149,7 +6154,11 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { p->init_fullpath = init_fullpath; if (build_context.command_kind == Command_test) { - String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing")); + bool ok = false; + String s = get_fullpath_core_collection(permanent_allocator(), str_lit("testing"), &ok); + if (!ok) { + compiler_error("Unable to find The 'core:testing' package. Is the ODIN_ROOT set up correctly?"); + } try_add_import_path(p, s, s, init_pos, Package_Normal); } -- cgit v1.2.3 From 7b672ac72a623310403716a229598ed0e6d5a688 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Feb 2024 14:03:03 +0000 Subject: Disallow mixture of polymorphic $ names and normal identifiers within record parameters --- src/parser.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 2a7f41b36..78ac29dfd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2164,6 +2164,49 @@ gb_internal Array parse_union_variant_list(AstFile *f) { return variants; } +gb_internal void parser_check_polymorphic_record_parameters(AstFile *f, Ast *polymorphic_params) { + if (polymorphic_params == nullptr) { + return; + } + if (polymorphic_params->kind != Ast_FieldList) { + return; + } + + + enum {Unknown, Dollar, Bare} prefix = Unknown; + gb_unused(prefix); + + for (Ast *field : polymorphic_params->FieldList.list) { + if (field == nullptr || field->kind != Ast_Field) { + continue; + } + for (Ast *name : field->Field.names) { + if (name == nullptr) { + continue; + } + bool error = false; + + if (name->kind == Ast_Ident) { + switch (prefix) { + case Unknown: prefix = Bare; break; + case Dollar: error = true; break; + case Bare: break; + } + } else if (name->kind == Ast_PolyType) { + switch (prefix) { + case Unknown: prefix = Dollar; break; + case Dollar: break; + case Bare: error = true; break; + } + } + if (error) { + syntax_error(name, "Mixture of polymorphic $ names and normal identifiers are not allowed within record parameters"); + } + } + } +} + + gb_internal Ast *parse_operand(AstFile *f, bool lhs) { Ast *operand = nullptr; // Operand switch (f->curr_token.kind) { @@ -2610,6 +2653,8 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { decls = fields->FieldList.list; } + parser_check_polymorphic_record_parameters(f, polymorphic_params); + return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, field_align, where_token, where_clauses); } break; @@ -2702,6 +2747,8 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { auto variants = parse_union_variant_list(f); Token close = expect_closing_brace_of_field_list(f); + parser_check_polymorphic_record_parameters(f, polymorphic_params); + return ast_union_type(f, token, variants, polymorphic_params, align, union_kind, where_token, where_clauses); } break; -- cgit v1.2.3 From a4b8c1ea1779ce93349b203aaf56c5aeca316b61 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Feb 2024 15:55:20 +0000 Subject: Begin work adding `bit_field` --- base/runtime/core.odin | 9 +++ base/runtime/print.odin | 14 ++++ core/encoding/json/marshal.odin | 3 + core/fmt/fmt.odin | 65 +++++++++++++++++ core/reflect/reflect.odin | 10 +++ core/reflect/types.odin | 31 ++++++++ src/check_type.cpp | 152 ++++++++++++++++++++++++++++++++++++++++ src/checker.cpp | 18 +++++ src/llvm_backend.cpp | 8 ++- src/llvm_backend_debug.cpp | 36 ++++++++++ src/llvm_backend_general.cpp | 4 +- src/llvm_backend_type.cpp | 67 ++++++++++++++++++ src/parser.cpp | 78 +++++++++++++++++++++ src/parser.hpp | 15 ++++ src/parser_pos.cpp | 3 + src/types.cpp | 24 +++++++ 16 files changed, 535 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 85e64242d..dcc1e7476 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -181,6 +181,13 @@ Type_Info_Matrix :: struct { Type_Info_Soa_Pointer :: struct { elem: ^Type_Info, } +Type_Info_Bit_Field :: struct { + backing_type: ^Type_Info, + names: []string, + types: []^Type_Info, + bit_sizes: []uintptr, + bit_offsets: []uintptr, +} Type_Info_Flag :: enum u8 { Comparable = 0, @@ -223,6 +230,7 @@ Type_Info :: struct { Type_Info_Relative_Multi_Pointer, Type_Info_Matrix, Type_Info_Soa_Pointer, + Type_Info_Bit_Field, }, } @@ -256,6 +264,7 @@ Typeid_Kind :: enum u8 { Relative_Multi_Pointer, Matrix, Soa_Pointer, + Bit_Field, } #assert(len(Typeid_Kind) < 32) diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 41ff9e1bb..c93c2ab49 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -459,6 +459,20 @@ print_type :: proc "contextless" (ti: ^Type_Info) { } print_byte(']') + case Type_Info_Bit_Field: + print_string("bit_field ") + print_type(info.backing_type) + print_string(" {") + for name, i in info.names { + if i > 0 { print_string(", ") } + print_string(name) + print_string(": ") + print_type(info.types[i]) + print_string(" | ") + print_u64(u64(info.bit_sizes[i])) + } + print_byte('}') + case Type_Info_Simd_Vector: print_string("#simd[") diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index e9285364b..e237892c3 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -228,6 +228,9 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Matrix: return .Unsupported_Type + case runtime.Type_Info_Bit_Field: + return .Unsupported_Type + case runtime.Type_Info_Array: opt_write_start(w, opt, '[') or_return for i in 0.. (res: u64) { + for i in 0.. 0 { + io.write_string(fi.writer, ", ") + } + if hash { + fmt_write_indent(fi) + } + + io.write_string(fi.writer, name, &fi.n) + io.write_string(fi.writer, " = ", &fi.n) + + + bit_offset := info.bit_offsets[i] + bit_size := info.bit_sizes[i] + + value := read_bits(([^]byte)(v.data), bit_offset, bit_size) + + fmt_value(fi, any{&value, info.types[i].id}, verb) + if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) } + + } +} + + + // Formats a value based on its type and formatting verb // // Inputs: @@ -2611,6 +2673,9 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { case runtime.Type_Info_Matrix: fmt_matrix(fi, v, verb, info) + + case runtime.Type_Info_Bit_Field: + fmt_bit_field(fi, v, verb, info) } } // Formats a complex number based on the given formatting verb diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 0af23b18e..de5dec2e3 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -35,6 +35,7 @@ Type_Info_Relative_Pointer :: runtime.Type_Info_Relative_Pointer Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer Type_Info_Matrix :: runtime.Type_Info_Matrix Type_Info_Soa_Pointer :: runtime.Type_Info_Soa_Pointer +Type_Info_Bit_Field :: runtime.Type_Info_Bit_Field Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value @@ -70,6 +71,7 @@ Type_Kind :: enum { Relative_Multi_Pointer, Matrix, Soa_Pointer, + Bit_Field, } @@ -106,6 +108,7 @@ type_kind :: proc(T: typeid) -> Type_Kind { case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer case Type_Info_Matrix: return .Matrix case Type_Info_Soa_Pointer: return .Soa_Pointer + case Type_Info_Bit_Field: return .Bit_Field } } @@ -1604,6 +1607,13 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ } } return true + + case Type_Info_Bit_Field: + x, y := a, b + x.id = v.backing_type.id + y.id = v.backing_type.id + return equal(x, y, including_indirect_array_recursion, recursion_level+0) + } runtime.print_typeid(a.id) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index cbe108d82..2b96dd4fb 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -174,6 +174,23 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { if x.row_count != y.row_count { return false } if x.column_count != y.column_count { return false } return are_types_identical(x.elem, y.elem) + + case Type_Info_Bit_Field: + y := b.variant.(Type_Info_Bit_Field) or_return + if !are_types_identical(x.backing_type, y.backing_type) { return false } + if len(x.names) != len(y.names) { return false } + for _, i in x.names { + if x.names[i] != y.names[i] { + return false + } + if !are_types_identical(x.types[i], y.types[i]) { + return false + } + if x.bit_sizes[i] != y.bit_sizes[i] { + return false + } + } + return true } return false @@ -639,6 +656,20 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - } io.write_byte(w, ']', &n) or_return + case Type_Info_Bit_Field: + io.write_string(w, "bit_field ", &n) or_return + write_type(w, info.backing_type, &n) or_return + io.write_string(w, " {", &n) or_return + for name, i in info.names { + if i > 0 { io.write_string(w, ", ", &n) or_return } + io.write_string(w, name, &n) or_return + io.write_string(w, ": ", &n) or_return + write_type(w, info.types[i], &n) or_return + io.write_string(w, " | ", &n) or_return + io.write_u64(w, u64(info.bit_sizes[i]), 10, &n) or_return + } + io.write_string(w, "}", &n) or_return + case Type_Info_Simd_Vector: io.write_string(w, "#simd[", &n) or_return io.write_i64(w, i64(info.count), 10, &n) or_return diff --git a/src/check_type.cpp b/src/check_type.cpp index 8a140d95e..8afac2fc5 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -925,6 +925,144 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.max_value_index = max_value_index; } +gb_internal bool is_valid_bit_field_backing_type(Type *type) { + if (type == nullptr) { + return nullptr; + } + type = base_type(type); + if (is_type_untyped(type)) { + return false; + } + if (is_type_integer(type)) { + return true; + } + if (type->kind == Type_Array) { + return is_type_integer(type->Array.elem); + } + return false; +} + +gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) { + ast_node(bf, BitFieldType, node); + 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"); + 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); + + u64 maximum_bit_size = 8 * type_size_of(backing_type); + u64 total_bit_size = 0; + + for_array(i, bf->fields) { + i32 field_src_index = cast(i32)i; + Ast *field = bf->fields[i]; + if (field->kind != Ast_BitFieldField) { + error(field, "Invalid AST for a bit_field"); + continue; + } + ast_node(f, BitFieldField, field); + if (f->name == nullptr || f->name->kind != Ast_Ident) { + error(field, "A bit_field's field name must be an identifier"); + continue; + } + CommentGroup *docs = f->docs; + CommentGroup *comment = f->comment; + + String name = f->name->Ident.token.string; + + if (f->type == nullptr) { + error(field, "A bit_field's field must have a type"); + continue; + } + + Type *type = check_type(ctx, f->type); + if (type_size_of(type) > 8) { + error(f->type, "The type of a bit_field's field must be <= 8 bytes, got %lld", cast(long long)type_size_of(type)); + } + + if (is_type_untyped(type)) { + gbString s = type_to_string(type); + error(f->type, "The type of a bit_field's field must be a typed integer, enum, or boolean, got %s", s); + gb_string_free(s); + } else if (!(is_type_integer(type) || is_type_enum(type) || is_type_boolean(type))) { + gbString s = type_to_string(type); + error(f->type, "The type of a bit_field's field must be an integer, enum, or boolean, got %s", s); + gb_string_free(s); + } + + if (f->bit_size == nullptr) { + error(field, "A bit_field's field must have a specified bit size"); + continue; + } + + + Operand o = {}; + check_expr(ctx, &o, f->bit_size); + if (o.mode != Addressing_Constant) { + error(f->bit_size, "A bit_field's specified bit size must be a constant"); + o.mode = Addressing_Invalid; + } + if (o.value.kind == ExactValue_Float) { + o.value = exact_value_to_integer(o.value); + } + + ExactValue bit_size = o.value; + + if (bit_size.kind != ExactValue_Integer) { + gbString s = expr_to_string(f->bit_size); + error(f->bit_size, "Expected an integer constant value for the specified bit size, got %s", s); + gb_string_free(s); + } + + if (scope_lookup_current(ctx->scope, name) != nullptr) { + error(f->name, "'%.*s' is already declared in this bit_field", LIT(name)); + } else { + i64 bit_size_i64 = exact_value_to_i64(bit_size); + u8 bit_size_u8 = 0; + if (bit_size_i64 <= 0) { + error(f->bit_size, "A bit_field's specified bit size cannot be <= 0, got %lld", cast(long long)bit_size_i64); + bit_size_i64 = 1; + } + if (bit_size_i64 > 64) { + error(f->bit_size, "A bit_field's specified bit size cannot exceed 64 bits, got %lld", cast(long long)bit_size_i64); + bit_size_i64 = 64; + } + bit_size_u8 = cast(u8)bit_size_i64; + + Entity *e = alloc_entity_field(ctx->scope, f->name->Ident.token, type, false, field_src_index); + e->Variable.docs = docs; + e->Variable.comment = comment; + + add_entity(ctx, ctx->scope, nullptr, e); + array_add(&fields, e); + array_add(&bit_sizes, bit_size_u8); + add_entity_use(ctx, field, e); + } + } + + GB_ASSERT(fields.count <= bf->fields.count); + + if (total_bit_size > maximum_bit_size) { + gbString s = type_to_string(backing_type); + error(node, "The numbers required %llu exceeds the backing type's (%s) bit size %llu", + cast(unsigned long long)total_bit_size, + s, + cast(unsigned long long)maximum_bit_size); + gb_string_free(s); + } + + bit_field_type->BitField.fields = slice_from_array(fields); + bit_field_type->BitField.bit_sizes = slice_from_array(bit_sizes); +} + gb_internal bool is_type_valid_bit_set_range(Type *t) { if (is_type_integer(t)) { return true; @@ -3051,6 +3189,20 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T return true; case_end; + case_ast_node(bf, BitFieldType, e); + bool ips = ctx->in_polymorphic_specialization; + defer (ctx->in_polymorphic_specialization = ips); + ctx->in_polymorphic_specialization = false; + + *type = alloc_type_bit_field(); + set_base_type(named_type, *type); + check_open_scope(ctx, e); + check_bit_field_type(ctx, *type, named_type, e); + check_close_scope(ctx); + (*type)->BitField.node = e; + return true; + case_end; + case_ast_node(pt, ProcType, e); bool ips = ctx->in_polymorphic_specialization; diff --git a/src/checker.cpp b/src/checker.cpp index 569a3c76f..5827fc695 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -313,6 +313,7 @@ gb_internal void add_scope(CheckerContext *c, Ast *node, Scope *scope) { case Ast_StructType: node->StructType.scope = scope; break; case Ast_UnionType: node->UnionType.scope = scope; break; case Ast_EnumType: node->EnumType.scope = scope; break; + case Ast_BitFieldType: node->BitFieldType.scope = scope; break; default: GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind])); } } @@ -334,6 +335,7 @@ gb_internal Scope *scope_of_node(Ast *node) { case Ast_StructType: return node->StructType.scope; case Ast_UnionType: return node->UnionType.scope; case Ast_EnumType: return node->EnumType.scope; + case Ast_BitFieldType: return node->BitFieldType.scope; } GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind])); return nullptr; @@ -355,6 +357,7 @@ gb_internal void check_open_scope(CheckerContext *c, Ast *node) { case Ast_EnumType: case Ast_UnionType: case Ast_BitSetType: + case Ast_BitFieldType: scope->flags |= ScopeFlag_Type; break; } @@ -2060,6 +2063,12 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->SoaPointer.elem); break; + case Type_BitField: + add_type_info_type_internal(c, bt->BitField.backing_type); + for (Entity *f : bt->BitField.fields) { + add_type_info_type_internal(c, f->type); + } + break; case Type_Generic: break; @@ -2309,6 +2318,13 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, bt->SoaPointer.elem); break; + case Type_BitField: + add_min_dep_type_info(c, bt->BitField.backing_type); + for (Entity *f : bt->BitField.fields) { + add_min_dep_type_info(c, f->type); + } + break; + default: GB_PANIC("Unhandled type: %*.s", LIT(type_strings[bt->kind])); break; @@ -2907,6 +2923,7 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_relative_multi_pointer = find_core_type(c, str_lit("Type_Info_Relative_Multi_Pointer")); t_type_info_matrix = find_core_type(c, str_lit("Type_Info_Matrix")); t_type_info_soa_pointer = find_core_type(c, str_lit("Type_Info_Soa_Pointer")); + t_type_info_bit_field = find_core_type(c, str_lit("Type_Info_Bit_Field")); t_type_info_named_ptr = alloc_type_pointer(t_type_info_named); t_type_info_integer_ptr = alloc_type_pointer(t_type_info_integer); @@ -2936,6 +2953,7 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_relative_multi_pointer_ptr = alloc_type_pointer(t_type_info_relative_multi_pointer); t_type_info_matrix_ptr = alloc_type_pointer(t_type_info_matrix); t_type_info_soa_pointer_ptr = alloc_type_pointer(t_type_info_soa_pointer); + t_type_info_bit_field_ptr = alloc_type_pointer(t_type_info_bit_field); } gb_internal void init_mem_allocator(Checker *c) { diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index fa76ac22f..45d903b43 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2719,6 +2719,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { { // Type info member buffer // NOTE(bill): Removes need for heap allocation by making it global memory isize count = 0; + isize offsets_extra = 0; for (Type *t : m->info->type_info_types) { isize index = lb_type_info_index(m->info, t, false); @@ -2736,6 +2737,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { case Type_Tuple: count += t->Tuple.variables.count; break; + case Type_BitField: + count += t->BitField.fields.count; + // Twice is needed for the bit_offsets + offsets_extra += t->BitField.fields.count; + break; } } @@ -2752,7 +2758,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_global_type_info_member_types = global_type_info_make(m, LB_TYPE_INFO_TYPES_NAME, t_type_info_ptr, count); lb_global_type_info_member_names = global_type_info_make(m, LB_TYPE_INFO_NAMES_NAME, t_string, count); - lb_global_type_info_member_offsets = global_type_info_make(m, LB_TYPE_INFO_OFFSETS_NAME, t_uintptr, count); + lb_global_type_info_member_offsets = global_type_info_make(m, LB_TYPE_INFO_OFFSETS_NAME, t_uintptr, count+offsets_extra); lb_global_type_info_member_usings = global_type_info_make(m, LB_TYPE_INFO_USINGS_NAME, t_bool, count); lb_global_type_info_member_tags = global_type_info_make(m, LB_TYPE_INFO_TAGS_NAME, t_string, count); } diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index f45cf0cbc..7d3692a53 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -461,6 +461,42 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { lb_debug_type(m, type->Matrix.elem), subscripts, gb_count_of(subscripts)); } + + case Type_BitField: { + LLVMMetadataRef parent_scope = nullptr; + LLVMMetadataRef scope = nullptr; + LLVMMetadataRef file = nullptr; + unsigned line = 0; + u64 size_in_bits = 8*cast(u64)type_size_of(type); + u32 align_in_bits = 8*cast(u32)type_align_of(type); + LLVMDIFlags flags = LLVMDIFlagZero; + + unsigned element_count = cast(unsigned)type->BitField.fields.count; + LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); + + u64 offset_in_bits = 0; + for (unsigned i = 0; i < element_count; i++) { + Entity *f = type->BitField.fields[i]; + u8 bit_size = type->BitField.bit_sizes[i]; + GB_ASSERT(f->kind == Entity_Variable); + String name = f->token.string; + unsigned field_line = 0; + LLVMDIFlags field_flags = LLVMDIFlagZero; + elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, field_line, + bit_size, offset_in_bits, offset_in_bits, + field_flags, lb_debug_type(m, f->type) + ); + + offset_in_bits += bit_size; + } + + + return LLVMDIBuilderCreateStructType(m->debug_builder, parent_scope, "", 0, file, line, + size_in_bits, align_in_bits, flags, + nullptr, elements, element_count, 0, nullptr, + "", 0 + ); + } } GB_PANIC("Invalid type %s", type_to_string(type)); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index f0f5327c6..2102420f8 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2216,7 +2216,9 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { } return LLVMStructTypeInContext(ctx, fields, field_count, false); } - + + case Type_BitField: + return lb_type_internal(m, type->BitField.backing_type); } GB_PANIC("Invalid type %s", type_to_string(type)); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index e291e40a5..3567a550b 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1788,6 +1788,73 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup lb_emit_store(p, tag, res); } break; + + case Type_BitField: + { + tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_bit_field_ptr); + LLVMValueRef vals[5] = {}; + + vals[0] = lb_type_info(m, t->BitField.backing_type).value; + isize count = t->BitField.fields.count; + if (count > 0) { + i64 names_offset = 0; + i64 types_offset = 0; + i64 bit_sizes_offset = 0; + i64 bit_offsets_offset = 0; + lbValue memory_names = lb_type_info_member_names_offset (m, count, &names_offset); + lbValue memory_types = lb_type_info_member_types_offset (m, count, &types_offset); + lbValue memory_bit_sizes = lb_type_info_member_offsets_offset(m, count, &bit_sizes_offset); + lbValue memory_bit_offsets = lb_type_info_member_offsets_offset(m, count, &bit_offsets_offset); + + u64 bit_offset = 0; + for (isize source_index = 0; source_index < count; source_index++) { + Entity *f = t->BitField.fields[source_index]; + u64 bit_size = cast(u64)t->BitField.bit_sizes[source_index]; + + lbValue index = lb_const_int(m, t_int, source_index); + if (f->token.string.len > 0) { + lbValue name = lb_emit_ptr_offset(p, memory_names, index); + lb_emit_store(p, name, lb_const_string(m, f->token.string)); + } + lbValue type_ptr = lb_emit_ptr_offset(p, memory_types, index); + lbValue bit_size_ptr = lb_emit_ptr_offset(p, memory_bit_sizes, index); + lbValue bit_offset_ptr = lb_emit_ptr_offset(p, memory_bit_offsets, index); + + lb_emit_store(p, type_ptr, lb_type_info(m, f->type)); + lb_emit_store(p, bit_size_ptr, lb_const_int(m, t_uintptr, bit_size)); + lb_emit_store(p, bit_offset_ptr, lb_const_int(m, t_uintptr, bit_offset)); + + // lb_global_type_info_member_types_values [types_offset +source_index] = get_type_info_ptr(m, f->type); + // lb_global_type_info_member_offsets_values[bit_sizes_offset +source_index] = lb_const_int(m, t_uintptr, bit_size).value; + // lb_global_type_info_member_offsets_values[bit_offsets_offset+source_index] = lb_const_int(m, t_uintptr, bit_offset).value; + // if (f->token.string.len > 0) { + // lb_global_type_info_member_names_values[names_offset+source_index] = lb_const_string(m, f->token.string).value; + // } + + bit_offset += bit_size; + } + + lbValue cv = lb_const_int(m, t_int, count); + vals[1] = llvm_const_slice(m, memory_names, cv); + vals[2] = llvm_const_slice(m, memory_types, cv); + vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); + vals[4] = llvm_const_slice(m, memory_bit_offsets, cv); + } + + for (isize i = 0; i < gb_count_of(vals); i++) { + if (vals[i] == nullptr) { + vals[i] = LLVMConstNull(lb_type(m, get_struct_field_type(tag.type, i))); + } + } + + lbValue res = {}; + res.type = type_deref(tag.type); + res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); + lb_emit_store(p, tag, res); + + break; + } + } diff --git a/src/parser.cpp b/src/parser.cpp index 78ac29dfd..70da9414d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -350,6 +350,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->Field.names = clone_ast_array(n->Field.names, f); n->Field.type = clone_ast(n->Field.type, f); break; + case Ast_BitFieldField: + n->BitFieldField.name = clone_ast(n->BitFieldField.name, f); + n->BitFieldField.type = clone_ast(n->BitFieldField.type, f); + n->BitFieldField.bit_size = clone_ast(n->BitFieldField.bit_size, f); + break; case Ast_FieldList: n->FieldList.list = clone_ast_array(n->FieldList.list, f); break; @@ -406,6 +411,10 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->BitSetType.elem = clone_ast(n->BitSetType.elem, f); n->BitSetType.underlying = clone_ast(n->BitSetType.underlying, f); break; + case Ast_BitFieldType: + n->BitFieldType.backing_type = clone_ast(n->BitFieldType.backing_type, f); + n->BitFieldType.fields = clone_ast_array(n->BitFieldType.fields, f); + break; case Ast_MapType: n->MapType.count = clone_ast(n->MapType.count, f); n->MapType.key = clone_ast(n->MapType.key, f); @@ -1045,6 +1054,17 @@ gb_internal Ast *ast_field(AstFile *f, Array const &names, Ast *type, Ast return result; } +gb_internal Ast *ast_bit_field_field(AstFile *f, Ast *name, Ast *type, Ast *bit_size, + CommentGroup *docs, CommentGroup *comment) { + Ast *result = alloc_ast_node(f, Ast_BitFieldField); + result->BitFieldField.name = name; + result->BitFieldField.type = type; + result->BitFieldField.bit_size = bit_size; + result->BitFieldField.docs = docs; + result->BitFieldField.comment = comment; + return result; +} + gb_internal Ast *ast_field_list(AstFile *f, Token token, Array const &list) { Ast *result = alloc_ast_node(f, Ast_FieldList); result->FieldList.token = token; @@ -1178,6 +1198,17 @@ gb_internal Ast *ast_bit_set_type(AstFile *f, Token token, Ast *elem, Ast *under return result; } +gb_internal Ast *ast_bit_field_type(AstFile *f, Token token, Ast *backing_type, Token open, Array const &fields, Token close) { + Ast *result = alloc_ast_node(f, Ast_BitFieldType); + result->BitFieldType.token = token; + result->BitFieldType.backing_type = backing_type; + result->BitFieldType.open = open; + result->BitFieldType.fields = slice_from_array(fields); + result->BitFieldType.close = close; + return result; +} + + gb_internal Ast *ast_map_type(AstFile *f, Token token, Ast *key, Ast *value) { Ast *result = alloc_ast_node(f, Ast_MapType); result->MapType.token = token; @@ -2549,6 +2580,53 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return ast_matrix_type(f, token, row_count, column_count, type); } break; + case Token_bit_field: { + Token token = expect_token(f, Token_bit_field); + isize prev_level; + + prev_level = f->expr_level; + f->expr_level = -1; + + Ast *backing_type = parse_type_or_ident(f); + if (backing_type == nullptr) { + Token token = advance_token(f); + syntax_error(token, "Expected a backing type for a 'bit_field'"); + backing_type = ast_bad_expr(f, token, f->curr_token); + } + + skip_possible_newline_for_literal(f); + Token open = expect_token_after(f, Token_OpenBrace, "bit_field"); + + + auto fields = array_make(ast_allocator(f), 0, 0); + + while (f->curr_token.kind != Token_CloseBrace && + f->curr_token.kind != Token_EOF) { + CommentGroup *docs = nullptr; + CommentGroup *comment = nullptr; + + Ast *name = parse_ident(f); + expect_token(f, Token_Colon); + Ast *type = parse_type(f); + expect_token(f, Token_Or); + Ast *bit_size = parse_expr(f, true); + + Ast *bf_field = ast_bit_field_field(f, name, type, bit_size, docs, comment); + array_add(&fields, bf_field); + + if (!allow_field_separator(f)) { + break; + } + } + + Token close = expect_closing_brace_of_field_list(f); + + f->expr_level = prev_level; + + return ast_bit_field_type(f, token, backing_type, open, fields, close); + } + + case Token_struct: { Token token = expect_token(f, Token_struct); Ast *polymorphic_params = nullptr; diff --git a/src/parser.hpp b/src/parser.hpp index 1edb1f9dd..ff77c88c7 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -650,6 +650,13 @@ AST_KIND(_DeclEnd, "", bool) \ CommentGroup * docs; \ CommentGroup * comment; \ }) \ + AST_KIND(BitFieldField, "bit field field", struct { \ + Ast * name; \ + Ast * type; \ + Ast * bit_size; \ + CommentGroup *docs; \ + CommentGroup *comment; \ + }) \ AST_KIND(FieldList, "field list", struct { \ Token token; \ Slice list; \ @@ -742,6 +749,14 @@ AST_KIND(_TypeBegin, "", bool) \ Ast * elem; \ Ast * underlying; \ }) \ + AST_KIND(BitFieldType, "bit field type", struct { \ + Scope *scope; \ + Token token; \ + Ast * backing_type; \ + Token open; \ + Slice fields; /* BitFieldField */ \ + Token close; \ + }) \ AST_KIND(MapType, "map type", struct { \ Token token; \ Ast *count; \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index f49c40f16..b2e12999b 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -111,6 +111,7 @@ gb_internal Token ast_token(Ast *node) { case Ast_UnionType: return node->UnionType.token; case Ast_EnumType: return node->EnumType.token; case Ast_BitSetType: return node->BitSetType.token; + case Ast_BitFieldType: return node->BitFieldType.token; case Ast_MapType: return node->MapType.token; case Ast_MatrixType: return node->MatrixType.token; } @@ -364,6 +365,8 @@ Token ast_end_token(Ast *node) { return ast_end_token(node->BitSetType.underlying); } return ast_end_token(node->BitSetType.elem); + case Ast_BitFieldType: + return node->BitFieldType.close; case Ast_MapType: return ast_end_token(node->MapType.value); case Ast_MatrixType: return ast_end_token(node->MatrixType.elem); } diff --git a/src/types.cpp b/src/types.cpp index 78d281715..1c28e6583 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -282,6 +282,13 @@ struct TypeProc { Type *generic_column_count; \ i64 stride_in_bytes; \ }) \ + TYPE_KIND(BitField, struct { \ + Scope * scope; \ + Type * backing_type; \ + Slice fields; \ + Slice bit_sizes; \ + Ast * node; \ + }) \ TYPE_KIND(SoaPointer, struct { Type *elem; }) @@ -355,6 +362,7 @@ enum Typeid_Kind : u8 { Typeid_Relative_Multi_Pointer, Typeid_Matrix, Typeid_SoaPointer, + Typeid_Bit_Field, }; // IMPORTANT NOTE(bill): This must match the same as the in core.odin @@ -641,6 +649,7 @@ gb_global Type *t_type_info_relative_pointer = nullptr; gb_global Type *t_type_info_relative_multi_pointer = nullptr; gb_global Type *t_type_info_matrix = nullptr; gb_global Type *t_type_info_soa_pointer = nullptr; +gb_global Type *t_type_info_bit_field = nullptr; gb_global Type *t_type_info_named_ptr = nullptr; gb_global Type *t_type_info_integer_ptr = nullptr; @@ -670,6 +679,7 @@ gb_global Type *t_type_info_relative_pointer_ptr = nullptr; gb_global Type *t_type_info_relative_multi_pointer_ptr = nullptr; gb_global Type *t_type_info_matrix_ptr = nullptr; gb_global Type *t_type_info_soa_pointer_ptr = nullptr; +gb_global Type *t_type_info_bit_field_ptr = nullptr; gb_global Type *t_allocator = nullptr; gb_global Type *t_allocator_ptr = nullptr; @@ -1040,6 +1050,11 @@ gb_internal Type *alloc_type_enum() { return t; } +gb_internal Type *alloc_type_bit_field() { + Type *t = alloc_type(Type_BitField); + return t; +} + gb_internal Type *alloc_type_relative_pointer(Type *pointer_type, Type *base_integer) { GB_ASSERT(is_type_pointer(pointer_type)); GB_ASSERT(is_type_integer(base_integer)); @@ -1707,6 +1722,10 @@ gb_internal bool is_type_bit_set(Type *t) { t = base_type(t); return (t->kind == Type_BitSet); } +gb_internal bool is_type_bit_field(Type *t) { + t = base_type(t); + return (t->kind == Type_BitField); +} gb_internal bool is_type_map(Type *t) { t = base_type(t); return t->kind == Type_Map; @@ -3568,6 +3587,8 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { case Type_Slice: return build_context.int_size; + case Type_BitField: + return type_align_of_internal(t->BitField.backing_type, path); case Type_Tuple: { i64 max = 1; @@ -3943,6 +3964,9 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { return stride_in_bytes * t->Matrix.column_count; } + case Type_BitField: + return type_size_of_internal(t->BitField.backing_type, path); + case Type_RelativePointer: return type_size_of_internal(t->RelativePointer.base_integer, path); case Type_RelativeMultiPointer: -- cgit v1.2.3 From 980947b3556ac4583599ff1864824bcac39c0aa5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Feb 2024 18:52:17 +0000 Subject: Give a better error message when the user uses `context` as if it was an identifier in a field list. --- src/parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 70da9414d..31d41a511 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4001,6 +4001,10 @@ gb_internal Array convert_to_ident_list(AstFile *f, Array li case Ast_Ident: case Ast_BadExpr: break; + case Ast_Implicit: + syntax_error(ident, "Expected an identifier, '%.*s' which is a keyword", LIT(ident->Implicit.string)); + ident = ast_ident(f, blank_token); + break; case Ast_PolyType: if (allow_poly_names) { @@ -4014,8 +4018,9 @@ gb_internal Array convert_to_ident_list(AstFile *f, Array li } /*fallthrough*/ + default: - syntax_error(ident, "Expected an identifier"); + syntax_error(ident, "Expected an identifier, %d", ident->kind); ident = ast_ident(f, blank_token); break; } -- cgit v1.2.3 From 8060e3170ebb973167e6beaf3dd1f5e31226f3b1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Feb 2024 18:53:02 +0000 Subject: Remove debug message --- src/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 31d41a511..6a9481693 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4020,7 +4020,7 @@ gb_internal Array convert_to_ident_list(AstFile *f, Array li default: - syntax_error(ident, "Expected an identifier, %d", ident->kind); + syntax_error(ident, "Expected an identifier"); ident = ast_ident(f, blank_token); break; } -- cgit v1.2.3 From 54515af8ccff67cae71982d1bbf5bd1c31628af3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Feb 2024 19:41:48 +0000 Subject: Add field tags to `bit_field` --- base/runtime/core.odin | 1 + core/fmt/fmt.odin | 25 +++++++++++++++++++++++-- src/check_type.cpp | 10 ++++++++++ src/llvm_backend_type.cpp | 34 +++++++++++++++++++--------------- src/parser.cpp | 10 ++++++++-- src/parser.hpp | 1 + src/types.cpp | 1 + 7 files changed, 63 insertions(+), 19 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin index dcc1e7476..2f63a7ac2 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -187,6 +187,7 @@ Type_Info_Bit_Field :: struct { types: []^Type_Info, bit_sizes: []uintptr, bit_offsets: []uintptr, + tags: []string, } Type_Info_Flag :: enum u8 { diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 51f158cd8..38e125c30 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2297,6 +2297,23 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit return } + handle_bit_field_tag :: proc(data: rawptr, info: reflect.Type_Info_Bit_Field, idx: int, verb: ^rune) -> (do_continue: bool) { + tag := info.tags[idx] + if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok { + value := strings.trim_space(string(vt)) + switch value { + case "": return false + case "-": return true + } + r, w := utf8.decode_rune_in_string(value) + value = value[w:] + if value == "" || value[0] == ',' { + verb^ = r + } + } + return false + } + io.write_string(fi.writer, "bit_field{", &fi.n) hash := fi.hash; defer fi.hash = hash @@ -2318,7 +2335,11 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit field_count := -1 for name, i in info.names { - _ = i + field_verb := verb + if handle_bit_field_tag(v.data, info, i, &field_verb) { + continue + } + field_count += 1 if !do_trailing_comma && field_count > 0 { @@ -2343,7 +2364,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit value = (value ~ m) - m } - fmt_value(fi, any{&value, type.id}, verb) + fmt_value(fi, any{&value, type.id}, field_verb) if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 74828f97f..1bcae140f 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -959,6 +959,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, auto fields = array_make(permanent_allocator(), 0, bf->fields.count); auto bit_sizes = array_make (permanent_allocator(), 0, bf->fields.count); + auto tags = array_make (permanent_allocator(), 0, bf->fields.count); u64 maximum_bit_size = 8 * type_size_of(backing_type); u64 total_bit_size = 0; @@ -1054,6 +1055,14 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, add_entity(ctx, ctx->scope, nullptr, e); array_add(&fields, e); array_add(&bit_sizes, bit_size_u8); + + String tag = f->tag.string; + if (tag.len != 0 && !unquote_string(permanent_allocator(), &tag, 0, tag.text[0] == '`')) { + error(f->tag, "Invalid string literal"); + tag = {}; + } + array_add(&tags, tag); + add_entity_use(ctx, field, e); } } @@ -1080,6 +1089,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, bit_field_type->BitField.fields = slice_from_array(fields); bit_field_type->BitField.bit_sizes = slice_from_array(bit_sizes); bit_field_type->BitField.bit_offsets = bit_offsets; + bit_field_type->BitField.tags = tags.data; } gb_internal bool is_type_valid_bit_set_range(Type *t) { diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 3567a550b..4952d75de 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -1792,19 +1792,21 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup case Type_BitField: { tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_bit_field_ptr); - LLVMValueRef vals[5] = {}; + LLVMValueRef vals[6] = {}; vals[0] = lb_type_info(m, t->BitField.backing_type).value; isize count = t->BitField.fields.count; if (count > 0) { - i64 names_offset = 0; - i64 types_offset = 0; - i64 bit_sizes_offset = 0; + i64 names_offset = 0; + i64 types_offset = 0; + i64 bit_sizes_offset = 0; i64 bit_offsets_offset = 0; + i64 tags_offset = 0; lbValue memory_names = lb_type_info_member_names_offset (m, count, &names_offset); lbValue memory_types = lb_type_info_member_types_offset (m, count, &types_offset); lbValue memory_bit_sizes = lb_type_info_member_offsets_offset(m, count, &bit_sizes_offset); lbValue memory_bit_offsets = lb_type_info_member_offsets_offset(m, count, &bit_offsets_offset); + lbValue memory_tags = lb_type_info_member_tags_offset (m, count, &tags_offset); u64 bit_offset = 0; for (isize source_index = 0; source_index < count; source_index++) { @@ -1813,8 +1815,8 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup lbValue index = lb_const_int(m, t_int, source_index); if (f->token.string.len > 0) { - lbValue name = lb_emit_ptr_offset(p, memory_names, index); - lb_emit_store(p, name, lb_const_string(m, f->token.string)); + lbValue name_ptr = lb_emit_ptr_offset(p, memory_names, index); + lb_emit_store(p, name_ptr, lb_const_string(m, f->token.string)); } lbValue type_ptr = lb_emit_ptr_offset(p, memory_types, index); lbValue bit_size_ptr = lb_emit_ptr_offset(p, memory_bit_sizes, index); @@ -1824,21 +1826,23 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup lb_emit_store(p, bit_size_ptr, lb_const_int(m, t_uintptr, bit_size)); lb_emit_store(p, bit_offset_ptr, lb_const_int(m, t_uintptr, bit_offset)); - // lb_global_type_info_member_types_values [types_offset +source_index] = get_type_info_ptr(m, f->type); - // lb_global_type_info_member_offsets_values[bit_sizes_offset +source_index] = lb_const_int(m, t_uintptr, bit_size).value; - // lb_global_type_info_member_offsets_values[bit_offsets_offset+source_index] = lb_const_int(m, t_uintptr, bit_offset).value; - // if (f->token.string.len > 0) { - // lb_global_type_info_member_names_values[names_offset+source_index] = lb_const_string(m, f->token.string).value; - // } + if (t->BitField.tags) { + String tag = t->BitField.tags[source_index]; + if (tag.len > 0) { + lbValue tag_ptr = lb_emit_ptr_offset(p, memory_tags, index); + lb_emit_store(p, tag_ptr, lb_const_string(m, tag)); + } + } bit_offset += bit_size; } lbValue cv = lb_const_int(m, t_int, count); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_types, cv); - vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); + vals[1] = llvm_const_slice(m, memory_names, cv); + vals[2] = llvm_const_slice(m, memory_types, cv); + vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); vals[4] = llvm_const_slice(m, memory_bit_offsets, cv); + vals[5] = llvm_const_slice(m, memory_tags, cv); } for (isize i = 0; i < gb_count_of(vals); i++) { diff --git a/src/parser.cpp b/src/parser.cpp index 6a9481693..03d1e7aeb 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1054,12 +1054,13 @@ gb_internal Ast *ast_field(AstFile *f, Array const &names, Ast *type, Ast return result; } -gb_internal Ast *ast_bit_field_field(AstFile *f, Ast *name, Ast *type, Ast *bit_size, +gb_internal Ast *ast_bit_field_field(AstFile *f, Ast *name, Ast *type, Ast *bit_size, Token tag, CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_BitFieldField); result->BitFieldField.name = name; result->BitFieldField.type = type; result->BitFieldField.bit_size = bit_size; + result->BitFieldField.tag = tag; result->BitFieldField.docs = docs; result->BitFieldField.comment = comment; return result; @@ -2611,7 +2612,12 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { expect_token(f, Token_Or); Ast *bit_size = parse_expr(f, true); - Ast *bf_field = ast_bit_field_field(f, name, type, bit_size, docs, comment); + Token tag = {}; + if (f->curr_token.kind == Token_String) { + tag = expect_token(f, Token_String); + } + + Ast *bf_field = ast_bit_field_field(f, name, type, bit_size, tag, docs, comment); array_add(&fields, bf_field); if (!allow_field_separator(f)) { diff --git a/src/parser.hpp b/src/parser.hpp index 1f4ec8726..f410419d4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -655,6 +655,7 @@ AST_KIND(_DeclEnd, "", bool) \ Ast * name; \ Ast * type; \ Ast * bit_size; \ + Token tag; \ CommentGroup *docs; \ CommentGroup *comment; \ }) \ diff --git a/src/types.cpp b/src/types.cpp index eac834f25..90cb130b6 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -286,6 +286,7 @@ struct TypeProc { Scope * scope; \ Type * backing_type; \ Slice fields; \ + String * tags; /*count == fields.count*/ \ Slice bit_sizes; \ Slice bit_offsets; \ Ast * node; \ -- cgit v1.2.3 From a8909f06aea541860339c8c95f2bc8fe3f637a87 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 22 Feb 2024 20:10:38 +0000 Subject: Improve parsing for `bit_field` --- src/parser.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 03d1e7aeb..14035d6d7 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2607,6 +2607,14 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { CommentGroup *comment = nullptr; Ast *name = parse_ident(f); + bool err_once = false; + while (allow_token(f, Token_Comma)) { + Ast *dummy_name = parse_ident(f); + if (!err_once) { + error(dummy_name, "'bit_field' fields do not support multiple names per field"); + err_once = true; + } + } expect_token(f, Token_Colon); Ast *type = parse_type(f); expect_token(f, Token_Or); -- cgit v1.2.3 From 433109ff52d2db76069273cd53b7aebf6aea9be0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 16:29:45 +0000 Subject: Replace `gb_exit(1)` with `exit_with_errors()` where appropriate --- src/checker.cpp | 4 ++-- src/docs_writer.cpp | 2 +- src/error.cpp | 4 ++++ src/llvm_backend.cpp | 14 +++++++------- src/main.cpp | 2 +- src/parser.cpp | 4 ++-- 6 files changed, 17 insertions(+), 13 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 836f803fc..0efe61fba 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1204,7 +1204,7 @@ gb_internal void init_universal(void) { } if (defined_values_double_declaration) { - gb_exit(1); + exit_with_errors(); } @@ -4504,7 +4504,7 @@ gb_internal void add_import_dependency_node(Checker *c, Ast *decl, PtrMapscope != nullptr); diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 26d8027a9..824445ed5 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -1170,7 +1170,7 @@ gb_internal void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename); if (err != gbFileError_None) { gb_printf_err("Failed to write .odin-doc to: %s\n", filename); - gb_exit(1); + exit_with_errors(); return; } defer (gb_file_close(&f)); diff --git a/src/error.cpp b/src/error.cpp index 509470602..8d550e969 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -613,6 +613,10 @@ gb_internal void compiler_error(char const *fmt, ...) { } +gb_internal void exit_with_errors(void) { + print_all_errors(); + gb_exit(1); +} diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b8ee7e7fa..cc9b3ac5d 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1350,7 +1350,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_emit_worker_proc) { if (LLVMTargetMachineEmitToFile(wd->target_machine, wd->m->mod, cast(char *)wd->filepath_obj.text, wd->code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); } debugf("Generated File: %.*s\n", LIT(wd->filepath_obj)); return 0; @@ -1919,7 +1919,7 @@ verify gb_printf_err("LLVM Error: %s\n", llvm_error); } } - gb_exit(1); + exit_with_errors(); return 1; } #endif @@ -2104,11 +2104,11 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { String filepath_ll = lb_filepath_ll_for_module(m); if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } } - gb_exit(1); + exit_with_errors(); return 1; } return 0; @@ -2193,7 +2193,7 @@ gb_internal bool lb_llvm_object_generation(lbGenerator *gen, bool do_threading) if (LLVMTargetMachineEmitToFile(m->target_machine, m->mod, cast(char *)filepath_obj.text, code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } debugf("Generated File: %.*s\n", LIT(filepath_obj)); @@ -2393,7 +2393,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { gb_printf_err("LLVM Error: %s\n", llvm_error); } LLVMVerifyFunction(p->value, LLVMPrintMessageAction); - gb_exit(1); + exit_with_errors(); } } @@ -2962,7 +2962,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { String filepath_ll = lb_filepath_ll_for_module(m); if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } array_add(&gen->output_temp_paths, filepath_ll); diff --git a/src/main.cpp b/src/main.cpp index 672a9318e..ab721a143 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1404,7 +1404,7 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); if (err != gbFileError_None) { gb_printf_err("Failed to export timings to: %s\n", fileName); - gb_exit(1); + exit_with_errors(); return; } else { gb_printf("\nExporting timings to '%s'... ", fileName); diff --git a/src/parser.cpp b/src/parser.cpp index 14035d6d7..1aa40ccbf 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1484,7 +1484,7 @@ gb_internal Token expect_token(AstFile *f, TokenKind kind) { String p = token_to_string(prev); syntax_error(f->curr_token, "Expected '%.*s', got '%.*s'", LIT(c), LIT(p)); if (prev.kind == Token_EOF) { - gb_exit(1); + exit_with_errors(); } } @@ -6177,7 +6177,7 @@ gb_internal ParseFileError process_imported_file(Parser *p, ImportedFile importe if (err == ParseFile_EmptyFile) { if (fi.fullpath == p->init_fullpath) { syntax_error(pos, "Initial file is empty - %.*s\n", LIT(p->init_fullpath)); - gb_exit(1); + exit_with_errors(); } } else { switch (err) { -- cgit v1.2.3 From a750fc0ba63c9f1461bba4cc0446b1b4c2d2b3a9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 19 Mar 2024 21:05:23 +0000 Subject: Add `#row_major matrix[R, C]T` As well as `#column_major matrix[R, C]T` as an alias for just `matrix[R, C]T`. This is because some libraries require a row_major internal layout but still want to be used with row or major oriented vectors. --- base/runtime/core.odin | 4 ++++ core/fmt/fmt.odin | 12 ++++++++++-- core/reflect/types.odin | 4 ++++ src/check_expr.cpp | 12 +++++++++--- src/check_type.cpp | 2 +- src/llvm_backend_const.cpp | 4 ++-- src/llvm_backend_expr.cpp | 39 +++++++++++++++++++++++++++++---------- src/llvm_backend_type.cpp | 3 ++- src/llvm_backend_utility.cpp | 22 +++++++++++++++------- src/parser.cpp | 13 +++++++++++++ src/parser.hpp | 1 + src/types.cpp | 19 +++++++++++++++---- 12 files changed, 105 insertions(+), 30 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 8f27ca674..7ad3ef1d6 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -177,6 +177,10 @@ Type_Info_Matrix :: struct { row_count: int, column_count: int, // Total element count = column_count * elem_stride + layout: enum u8 { + Column_Major, // array of column vectors + Row_Major, // array of row vectors + }, } Type_Info_Soa_Pointer :: struct { elem: ^Type_Info, diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 02803f882..6f9801bc8 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2396,7 +2396,11 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix for col in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } - offset := (row + col*info.elem_stride)*info.elem_size + offset: int + switch info.layout { + case .Column_Major: offset = (row + col*info.elem_stride)*info.elem_size + case .Row_Major: offset = (col + row*info.elem_stride)*info.elem_size + } data := uintptr(v.data) + uintptr(offset) fmt_arg(fi, any{rawptr(data), info.elem.id}, verb) @@ -2410,7 +2414,11 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix for col in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } - offset := (row + col*info.elem_stride)*info.elem_size + offset: int + switch info.layout { + case .Column_Major: offset = (row + col*info.elem_stride)*info.elem_size + case .Row_Major: offset = (col + row*info.elem_stride)*info.elem_size + } data := uintptr(v.data) + uintptr(offset) fmt_arg(fi, any{rawptr(data), info.elem.id}, verb) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 2b96dd4fb..9cff46a00 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -173,6 +173,7 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { y := b.variant.(Type_Info_Matrix) or_return if x.row_count != y.row_count { return false } if x.column_count != y.column_count { return false } + if x.layout != y.layout { return false } return are_types_identical(x.elem, y.elem) case Type_Info_Bit_Field: @@ -689,6 +690,9 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - write_type(w, info.pointer, &n) or_return case Type_Info_Matrix: + if info.layout == .Row_Major { + io.write_string(w, "#row_major ", &n) or_return + } io.write_string(w, "matrix[", &n) or_return io.write_i64(w, i64(info.row_count), 10, &n) or_return io.write_string(w, ", ", &n) or_return diff --git a/src/check_expr.cpp b/src/check_expr.cpp index f359d5a54..67c7f1a04 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3431,6 +3431,11 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand if (xt->Matrix.column_count != yt->Matrix.row_count) { goto matrix_error; } + + if (xt->Matrix.is_row_major != yt->Matrix.is_row_major) { + goto matrix_error; + } + x->mode = Addressing_Value; if (are_types_identical(xt, yt)) { if (!is_type_named(x->type) && is_type_named(y->type)) { @@ -3438,7 +3443,8 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand x->type = y->type; } } else { - x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count); + bool is_row_major = xt->Matrix.is_row_major && yt->Matrix.is_row_major; + x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count, nullptr, nullptr, is_row_major); } goto matrix_success; } else if (yt->kind == Type_Array) { @@ -3452,7 +3458,7 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand // Treat arrays as column vectors x->mode = Addressing_Value; - if (type_hint == nullptr && xt->Matrix.row_count == yt->Array.count) { + if (xt->Matrix.row_count == yt->Array.count) { x->type = y->type; } else { x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, 1); @@ -3483,7 +3489,7 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand // Treat arrays as row vectors x->mode = Addressing_Value; - if (type_hint == nullptr && yt->Matrix.column_count == xt->Array.count) { + if (yt->Matrix.column_count == xt->Array.count) { x->type = x->type; } else { x->type = alloc_type_matrix(yt->Matrix.elem, 1, yt->Matrix.column_count); diff --git a/src/check_type.cpp b/src/check_type.cpp index d5cf187a4..3fe633892 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2658,7 +2658,7 @@ gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) } type_assign:; - *type = alloc_type_matrix(elem, row_count, column_count, generic_row, generic_column); + *type = alloc_type_matrix(elem, row_count, column_count, generic_row, generic_column, mt->is_row_major); return; } diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 2291f24ac..bbb0b8387 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1302,11 +1302,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo GB_ASSERT_MSG(elem_count == max_count, "%td != %td", elem_count, max_count); LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)total_count); - for_array(i, cl->elems) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - i64 offset = matrix_row_major_index_to_offset(type, i); + i64 offset = 0; + offset = matrix_row_major_index_to_offset(type, i); values[offset] = lb_const_value(m, elem_type, tav.value, allow_local).value; } for (isize i = 0; i < total_count; i++) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 98618798b..6eb8fdcc6 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -684,12 +684,6 @@ gb_internal lbValue lb_emit_matrix_flatten(lbProcedure *p, lbValue m, Type *type Type *mt = base_type(m.type); GB_ASSERT(mt->kind == Type_Matrix); - // TODO(bill): Determine why this fails on Windows sometimes - if (false && lb_is_matrix_simdable(mt)) { - LLVMValueRef vector = lb_matrix_to_trimmed_vector(p, m); - return lb_matrix_cast_vector_to_type(p, vector, type); - } - lbAddr res = lb_add_local_generated(p, type, true); i64 row_count = mt->Matrix.row_count; @@ -763,6 +757,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, GB_ASSERT(is_type_matrix(yt)); GB_ASSERT(xt->Matrix.column_count == yt->Matrix.row_count); GB_ASSERT(are_types_identical(xt->Matrix.elem, yt->Matrix.elem)); + GB_ASSERT(xt->Matrix.is_row_major == yt->Matrix.is_row_major); Type *elem = xt->Matrix.elem; @@ -770,7 +765,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, unsigned inner = cast(unsigned)xt->Matrix.column_count; unsigned outer_columns = cast(unsigned)yt->Matrix.column_count; - if (lb_is_matrix_simdable(xt)) { + if (!xt->Matrix.is_row_major && lb_is_matrix_simdable(xt)) { unsigned x_stride = cast(unsigned)matrix_type_stride_in_elems(xt); unsigned y_stride = cast(unsigned)matrix_type_stride_in_elems(yt); @@ -812,7 +807,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, return lb_addr_load(p, res); } - { + if (!xt->Matrix.is_row_major) { lbAddr res = lb_add_local_generated(p, type, true); auto inners = slice_make(permanent_allocator(), inner); @@ -835,6 +830,30 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, } } + return lb_addr_load(p, res); + } else { + lbAddr res = lb_add_local_generated(p, type, true); + + auto inners = slice_make(permanent_allocator(), inner); + + for (unsigned i = 0; i < outer_rows; i++) { + for (unsigned j = 0; j < outer_columns; j++) { + lbValue dst = lb_emit_matrix_epi(p, res.addr, i, j); + for (unsigned k = 0; k < inner; k++) { + inners[k][0] = lb_emit_matrix_ev(p, lhs, i, k); + inners[k][1] = lb_emit_matrix_ev(p, rhs, k, j); + } + + lbValue sum = lb_const_nil(p->module, elem); + for (unsigned k = 0; k < inner; k++) { + lbValue a = inners[k][0]; + lbValue b = inners[k][1]; + sum = lb_emit_mul_add(p, a, b, sum, elem); + } + lb_emit_store(p, dst, sum); + } + } + return lb_addr_load(p, res); } } @@ -855,7 +874,7 @@ gb_internal lbValue lb_emit_matrix_mul_vector(lbProcedure *p, lbValue lhs, lbVal Type *elem = mt->Matrix.elem; - if (lb_is_matrix_simdable(mt)) { + if (!mt->Matrix.is_row_major && lb_is_matrix_simdable(mt)) { unsigned stride = cast(unsigned)matrix_type_stride_in_elems(mt); unsigned row_count = cast(unsigned)mt->Matrix.row_count; @@ -924,7 +943,7 @@ gb_internal lbValue lb_emit_vector_mul_matrix(lbProcedure *p, lbValue lhs, lbVal Type *elem = mt->Matrix.elem; - if (lb_is_matrix_simdable(mt)) { + if (!mt->Matrix.is_row_major && lb_is_matrix_simdable(mt)) { unsigned stride = cast(unsigned)matrix_type_stride_in_elems(mt); unsigned row_count = cast(unsigned)mt->Matrix.row_count; diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index aec1fb201..de83f5058 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -979,12 +979,13 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ tag_type = t_type_info_matrix; i64 ez = type_size_of(t->Matrix.elem); - LLVMValueRef vals[5] = { + LLVMValueRef vals[6] = { get_type_info_ptr(m, t->Matrix.elem), lb_const_int(m, t_int, ez).value, lb_const_int(m, t_int, matrix_type_stride_in_elems(t)).value, lb_const_int(m, t_int, t->Matrix.row_count).value, lb_const_int(m, t_int, t->Matrix.column_count).value, + lb_const_int(m, t_u8, cast(u8)t->Matrix.is_row_major).value, }; variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index f18aa5521..fb61c41c3 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1464,14 +1464,16 @@ gb_internal lbValue lb_emit_matrix_epi(lbProcedure *p, lbValue s, isize row, isi Type *t = s.type; GB_ASSERT(is_type_pointer(t)); Type *mt = base_type(type_deref(t)); - if (column == 0) { - GB_ASSERT_MSG(is_type_matrix(mt) || is_type_array_like(mt), "%s", type_to_string(mt)); - return lb_emit_epi(p, s, row); - } else if (row == 0 && is_type_array_like(mt)) { - return lb_emit_epi(p, s, column); + + if (!mt->Matrix.is_row_major) { + if (column == 0) { + GB_ASSERT_MSG(is_type_matrix(mt) || is_type_array_like(mt), "%s", type_to_string(mt)); + return lb_emit_epi(p, s, row); + } else if (row == 0 && is_type_array_like(mt)) { + return lb_emit_epi(p, s, column); + } } - GB_ASSERT_MSG(is_type_matrix(mt), "%s", type_to_string(mt)); isize offset = matrix_indices_to_offset(mt, row, column); @@ -1491,7 +1493,13 @@ gb_internal lbValue lb_emit_matrix_ep(lbProcedure *p, lbValue s, lbValue row, lb row = lb_emit_conv(p, row, t_int); column = lb_emit_conv(p, column, t_int); - LLVMValueRef index = LLVMBuildAdd(p->builder, row.value, LLVMBuildMul(p->builder, column.value, stride_elems, ""), ""); + LLVMValueRef index = nullptr; + + if (mt->Matrix.is_row_major) { + index = LLVMBuildAdd(p->builder, column.value, LLVMBuildMul(p->builder, row.value, stride_elems, ""), ""); + } else { + index = LLVMBuildAdd(p->builder, row.value, LLVMBuildMul(p->builder, column.value, stride_elems, ""), ""); + } LLVMValueRef indices[2] = { LLVMConstInt(lb_type(p->module, t_int), 0, false), diff --git a/src/parser.cpp b/src/parser.cpp index 1aa40ccbf..eb9e73342 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2329,6 +2329,19 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { break; } return original_type; + } else if (name.string == "row_major" || + name.string == "column_major") { + Ast *original_type = parse_type(f); + Ast *type = unparen_expr(original_type); + switch (type->kind) { + case Ast_MatrixType: + type->MatrixType.is_row_major = (name.string == "row_major"); + break; + default: + syntax_error(type, "Expected a matrix type after #%.*s, got %.*s", LIT(name.string), LIT(ast_strings[type->kind])); + break; + } + return original_type; } else if (name.string == "partial") { Ast *tag = ast_basic_directive(f, token, name); Ast *original_expr = parse_expr(f, lhs); diff --git a/src/parser.hpp b/src/parser.hpp index f5997c4bd..ff3c5eb34 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -772,6 +772,7 @@ AST_KIND(_TypeBegin, "", bool) \ Ast *row_count; \ Ast *column_count; \ Ast *elem; \ + bool is_row_major; \ }) \ AST_KIND(_TypeEnd, "", bool) diff --git a/src/types.cpp b/src/types.cpp index 5a3ad5d6b..ab5e4de03 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -281,6 +281,7 @@ struct TypeProc { Type *generic_row_count; \ Type *generic_column_count; \ i64 stride_in_bytes; \ + bool is_row_major; \ }) \ TYPE_KIND(BitField, struct { \ Scope * scope; \ @@ -1002,7 +1003,7 @@ gb_internal Type *alloc_type_array(Type *elem, i64 count, Type *generic_count = return t; } -gb_internal Type *alloc_type_matrix(Type *elem, i64 row_count, i64 column_count, Type *generic_row_count = nullptr, Type *generic_column_count = nullptr) { +gb_internal Type *alloc_type_matrix(Type *elem, i64 row_count, i64 column_count, Type *generic_row_count = nullptr, Type *generic_column_count = nullptr, bool is_row_major = false) { if (generic_row_count != nullptr || generic_column_count != nullptr) { Type *t = alloc_type(Type_Matrix); t->Matrix.elem = elem; @@ -1010,12 +1011,14 @@ gb_internal Type *alloc_type_matrix(Type *elem, i64 row_count, i64 column_count, t->Matrix.column_count = column_count; t->Matrix.generic_row_count = generic_row_count; t->Matrix.generic_column_count = generic_column_count; + t->Matrix.is_row_major = is_row_major; return t; } Type *t = alloc_type(Type_Matrix); t->Matrix.elem = elem; t->Matrix.row_count = row_count; t->Matrix.column_count = column_count; + t->Matrix.is_row_major = is_row_major; return t; } @@ -1512,14 +1515,18 @@ gb_internal i64 matrix_indices_to_offset(Type *t, i64 row_index, i64 column_inde GB_ASSERT(0 <= row_index && row_index < t->Matrix.row_count); GB_ASSERT(0 <= column_index && column_index < t->Matrix.column_count); i64 stride_elems = matrix_type_stride_in_elems(t); - // NOTE(bill): Column-major layout internally - return row_index + stride_elems*column_index; + if (t->Matrix.is_row_major) { + return column_index + stride_elems*row_index; + } else { + // NOTE(bill): Column-major layout internally + return row_index + stride_elems*column_index; + } } gb_internal i64 matrix_row_major_index_to_offset(Type *t, i64 index) { t = base_type(t); GB_ASSERT(t->kind == Type_Matrix); - + i64 row_index = index/t->Matrix.column_count; i64 column_index = index%t->Matrix.column_count; return matrix_indices_to_offset(t, row_index, column_index); @@ -2690,6 +2697,7 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple case Type_Matrix: return x->Matrix.row_count == y->Matrix.row_count && x->Matrix.column_count == y->Matrix.column_count && + x->Matrix.is_row_major == y->Matrix.is_row_major && are_types_identical(x->Matrix.elem, y->Matrix.elem); case Type_DynamicArray: @@ -4735,6 +4743,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha break; case Type_Matrix: + if (type->Matrix.is_row_major) { + str = gb_string_appendc(str, "#row_major "); + } str = gb_string_appendc(str, gb_bprintf("matrix[%d, %d]", cast(int)type->Matrix.row_count, cast(int)type->Matrix.column_count)); str = write_type_to_string(str, type->Matrix.elem); break; -- cgit v1.2.3 From 1cc5e2380158216ad65b4d9622c847479e495ebf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 20 Mar 2024 18:14:29 +0000 Subject: Fix #3299 --- src/parser.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index eb9e73342..54b0390bf 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5440,14 +5440,27 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const return nullptr; } + isize files_with_ext = 0; isize files_to_reserve = 1; // always reserve 1 for (FileInfo fi : list) { String name = fi.name; String ext = path_extension(name); + if (ext == FILE_EXT) { + files_with_ext += 1; + } if (ext == FILE_EXT && !is_excluded_target_filename(name)) { files_to_reserve += 1; } } + if (files_with_ext == 0 || files_to_reserve == 1) { + if (files_to_reserve == 1) { + syntax_error(pos, "Directory contains no .odin files for the specified platform: %.*s", LIT(rel_path)); + } else { + syntax_error(pos, "Empty directory that contains no .odin files: %.*s", LIT(rel_path)); + } + return nullptr; + } + array_reserve(&pkg->files, files_to_reserve); for (FileInfo fi : list) { -- cgit v1.2.3 From f39b34a8b7ce026d608532451f4f2aada40f5102 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 20 Mar 2024 18:17:06 +0000 Subject: Fix error message --- src/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 54b0390bf..6a7be8a7c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5453,7 +5453,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const } } if (files_with_ext == 0 || files_to_reserve == 1) { - if (files_to_reserve == 1) { + if (files_with_ext != 0) { syntax_error(pos, "Directory contains no .odin files for the specified platform: %.*s", LIT(rel_path)); } else { syntax_error(pos, "Empty directory that contains no .odin files: %.*s", LIT(rel_path)); -- cgit v1.2.3 From 29e5f94c2a0d666eed93a1013f895f3c86d6373f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 21 Mar 2024 11:52:48 +0000 Subject: Add `#no_broadcast` procedure parameter to disallow automatic array programming broadcasting on procedure arguments --- base/runtime/core_builtin.odin | 12 +++---- core/image/common.odin | 36 ++++++++++---------- core/math/big/prime.odin | 2 +- core/odin/ast/ast.odin | 16 +++++---- core/thread/thread.odin | 2 +- src/check_expr.cpp | 76 ++++++++++++++++++++++++++---------------- src/check_type.cpp | 16 ++++++++- src/entity.cpp | 1 + src/parser.cpp | 17 +++++----- src/parser.hpp | 3 +- vendor/raylib/raymath.odin | 4 +-- 11 files changed, 113 insertions(+), 72 deletions(-) (limited to 'src/parser.cpp') diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 401dcb857..fba2e1328 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -447,12 +447,12 @@ _append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: } @builtin -append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem(array, arg, true, loc=loc) } @builtin -non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elem(array, arg, false, loc=loc) } @@ -496,12 +496,12 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l } @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, true, loc, ..args) } @builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, false, loc, ..args) } @@ -556,7 +556,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i @builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -574,7 +574,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle } @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } diff --git a/core/image/common.odin b/core/image/common.odin index c7507a85f..b576a9521 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -651,7 +651,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel{0, key.g} } else { o = GA_Pixel{p.r, 255} @@ -710,7 +710,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel_16 for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel_16{0, key.g} } else { o = GA_Pixel_16{p.r, 65535} @@ -842,11 +842,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel's topmost byte. - bg = u8(temp_bg.r >> 8) + bg.r = u8(temp_bg.r >> 8) } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -865,8 +865,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -874,14 +874,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := f32(p.r) * a - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } @@ -951,11 +951,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel_16{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel. - bg = temp_bg.r + bg.r = temp_bg.r } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -974,8 +974,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -983,14 +983,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := f32(p.r) * a - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index cb0b08dbb..b02b7cb4e 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -1112,7 +1112,7 @@ internal_int_prime_next_prime :: proc(a: ^Int, trials: int, bbs_style: bool, all Generate the restable. */ for x := 1; x < _PRIME_TAB_SIZE; x += 1 { - res_tab = internal_mod(a, _private_prime_table[x]) or_return + res_tab = cast(type_of(res_tab))(internal_mod(a, _private_prime_table[x]) or_return) } for { diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index f6bcbab4e..3e215e0f2 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -597,6 +597,7 @@ Field_Flag :: enum { Any_Int, Subtype, By_Ptr, + No_Broadcast, Results, Tags, @@ -616,6 +617,7 @@ field_flag_strings := [Field_Flag]string{ .Any_Int = "#any_int", .Subtype = "#subtype", .By_Ptr = "#by_ptr", + .No_Broadcast ="#no_broadcast", .Results = "results", .Tags = "field tag", @@ -624,12 +626,13 @@ field_flag_strings := [Field_Flag]string{ } field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ - {"no_alias", .No_Alias}, - {"c_vararg", .C_Vararg}, - {"const", .Const}, - {"any_int", .Any_Int}, - {"subtype", .Subtype}, - {"by_ptr", .By_Ptr}, + {"no_alias", .No_Alias}, + {"c_vararg", .C_Vararg}, + {"const", .Const}, + {"any_int", .Any_Int}, + {"subtype", .Subtype}, + {"by_ptr", .By_Ptr}, + {"no_broadcast", .No_Broadcast}, } @@ -650,6 +653,7 @@ Field_Flags_Signature :: Field_Flags{ .Const, .Any_Int, .By_Ptr, + .No_Broadcast, .Default_Parameters, } diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 1c473bd1d..55f73d106 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -163,7 +163,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 1 - t.user_args = data + t.user_args[0] = data if self_cleanup { t.flags += {.Self_Cleanup} } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8fb2cf36b..51fe3fc8a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -623,7 +623,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type); #define MAXIMUM_TYPE_DISTANCE 10 -gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type) { +gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming) { if (c == nullptr) { GB_ASSERT(operand->mode == Addressing_Value); GB_ASSERT(is_type_typed(operand->type)); @@ -832,7 +832,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand if (dst->Union.variants.count == 1) { Type *vt = dst->Union.variants[0]; - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { return score+2; } @@ -840,7 +840,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand i64 prev_lowest_score = -1; i64 lowest_score = -1; for (Type *vt : dst->Union.variants) { - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { if (lowest_score < 0) { lowest_score = score; @@ -863,14 +863,14 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } if (is_type_relative_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativePointer.pointer_type); + i64 score = check_distance_between_types(c, operand, dst->RelativePointer.pointer_type, allow_array_programming); if (score >= 0) { return score+2; } } if (is_type_relative_multi_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type); + i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type, allow_array_programming); if (score >= 0) { return score+2; } @@ -896,19 +896,21 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_array(dst)) { - Type *elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, elem); - if (distance >= 0) { - return distance + 6; + if (allow_array_programming) { + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } - } - if (is_type_simd_vector(dst)) { - Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); - if (distance >= 0) { - return distance + 6; + if (is_type_simd_vector(dst)) { + Type *dst_elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } } @@ -918,7 +920,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } if (dst->Matrix.row_count == dst->Matrix.column_count) { Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); if (distance >= 0) { return distance + 7; } @@ -966,9 +968,9 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) { } -gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false) { +gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) { i64 score = 0; - i64 distance = check_distance_between_types(c, operand, type); + i64 distance = check_distance_between_types(c, operand, type, allow_array_programming); bool ok = distance >= 0; if (ok) { score = assign_score_function(distance, is_variadic); @@ -978,9 +980,9 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o } -gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type) { +gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming=true) { i64 score = 0; - return check_is_assignable_to_with_score(c, operand, type, &score); + return check_is_assignable_to_with_score(c, operand, type, &score, /*is_variadic*/false, allow_array_programming); } gb_internal bool internal_check_is_assignable_to(Type *src, Type *dst) { @@ -3142,6 +3144,14 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return true; } + + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + if (check_is_castable_to(c, operand, elem)) { + return true; + } + } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { if (src->SimdVector.count != dst->SimdVector.count) { return false; @@ -5853,15 +5863,20 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } } - auto eval_param_and_score = [](CheckerContext *c, Operand *o, Type *param_type, CallArgumentError &err, bool param_is_variadic, Entity *e, bool show_error) -> i64 { + auto eval_param_and_score = [](CheckerContext *c, Operand *o, Type *param_type, CallArgumentError &err, bool param_is_variadic, Entity *e, bool show_error, bool allow_array_programming) -> i64 { i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic)) { + if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic, allow_array_programming)) { bool ok = false; - if (e && e->flags & EntityFlag_AnyInt) { + if (e && (e->flags & EntityFlag_AnyInt)) { if (is_type_integer(param_type)) { ok = check_is_castable_to(c, o, param_type); } } + if (!allow_array_programming && check_is_assignable_to_with_score(c, o, param_type, nullptr, param_is_variadic, !allow_array_programming)) { + if (show_error) { + error(o->expr, "'#no_broadcast' disallows automatic broadcasting a value across all elements of an array-like type in a procedure argument"); + } + } if (ok) { s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { @@ -5878,7 +5893,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } err = CallArgumentError_WrongTypes; } - } else if (show_error) { check_assignment(c, o, param_type, str_lit("procedure argument")); } @@ -5963,12 +5977,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A if (param_is_variadic) { continue; } - score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error); + bool allow_array_programming = !(e && (e->flags & EntityFlag_NoBroadcast)); + score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error, allow_array_programming); } } if (variadic) { - Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; + Entity *var_entity = pt->params->Tuple.variables[pt->variadic_index]; + Type *slice = var_entity->type; GB_ASSERT(is_type_slice(slice)); Type *elem = base_type(slice)->Slice.elem; Type *t = elem; @@ -5994,7 +6010,8 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A return CallArgumentError_MultipleVariadicExpand; } } - score += eval_param_and_score(c, o, t, err, true, nullptr, show_error); + bool allow_array_programming = !(var_entity && (var_entity->flags & EntityFlag_NoBroadcast)); + score += eval_param_and_score(c, o, t, err, true, nullptr, show_error, allow_array_programming); } } @@ -11148,6 +11165,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan if (f->flags&FieldFlag_any_int) { str = gb_string_appendc(str, "#any_int "); } + if (f->flags&FieldFlag_no_broadcast) { + str = gb_string_appendc(str, "#no_broadcast "); + } if (f->flags&FieldFlag_const) { str = gb_string_appendc(str, "#const "); } diff --git a/src/check_type.cpp b/src/check_type.cpp index 3fe633892..96885bd27 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1869,6 +1869,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#any_int' can only be applied to variable fields"); p->flags &= ~FieldFlag_any_int; } + if (p->flags&FieldFlag_no_broadcast) { + error(name, "'#no_broadcast' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_broadcast; + } if (p->flags&FieldFlag_by_ptr) { error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; @@ -1926,7 +1930,13 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } } } - if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { + + bool allow_array_programming = true; + if (p->flags&FieldFlag_no_broadcast) { + allow_array_programming = false; + } + + if (type != t_invalid && !check_is_assignable_to(ctx, &op, type, allow_array_programming)) { bool ok = true; if (p->flags&FieldFlag_any_int) { if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { @@ -2002,6 +2012,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_no_alias) { param->flags |= EntityFlag_NoAlias; } + if (p->flags&FieldFlag_no_broadcast) { + param->flags |= EntityFlag_NoBroadcast; + } + if (p->flags&FieldFlag_any_int) { if (!is_type_integer(param->type) && !is_type_enum(param->type)) { gbString str = type_to_string(param->type); diff --git a/src/entity.cpp b/src/entity.cpp index 916c2b2bd..9161ea733 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -61,6 +61,7 @@ enum EntityFlag : u64 { EntityFlag_CVarArg = 1ull<<22, + EntityFlag_NoBroadcast = 1ull<<23, EntityFlag_AnyInt = 1ull<<24, EntityFlag_Disabled = 1ull<<25, diff --git a/src/parser.cpp b/src/parser.cpp index 6a7be8a7c..b6b62461f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3898,14 +3898,15 @@ struct ParseFieldPrefixMapping { FieldFlag flag; }; -gb_global ParseFieldPrefixMapping parse_field_prefix_mappings[] = { - {str_lit("using"), Token_using, FieldFlag_using}, - {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, - {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, - {str_lit("const"), Token_Hash, FieldFlag_const}, - {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, - {str_lit("subtype"), Token_Hash, FieldFlag_subtype}, - {str_lit("by_ptr"), Token_Hash, FieldFlag_by_ptr}, +gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { + {str_lit("using"), Token_using, FieldFlag_using}, + {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, + {str_lit("const"), Token_Hash, FieldFlag_const}, + {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, + {str_lit("subtype"), Token_Hash, FieldFlag_subtype}, + {str_lit("by_ptr"), Token_Hash, FieldFlag_by_ptr}, + {str_lit("no_broadcast"), Token_Hash, FieldFlag_no_broadcast}, }; diff --git a/src/parser.hpp b/src/parser.hpp index ff3c5eb34..5820275c8 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -326,6 +326,7 @@ enum FieldFlag : u32 { FieldFlag_any_int = 1<<6, FieldFlag_subtype = 1<<7, FieldFlag_by_ptr = 1<<8, + FieldFlag_no_broadcast = 1<<9, // disallow array programming // Internal use by the parser only FieldFlag_Tags = 1<<10, @@ -336,7 +337,7 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; diff --git a/vendor/raylib/raymath.odin b/vendor/raylib/raymath.odin index beeda7989..9682ffe4f 100644 --- a/vendor/raylib/raymath.odin +++ b/vendor/raylib/raymath.odin @@ -159,7 +159,7 @@ Vector2Transform :: proc "c" (v: Vector2, m: Matrix) -> Vector2 { // Calculate linear interpolation between two vectors @(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") Vector2Lerp :: proc "c" (v1, v2: Vector2, amount: f32) -> Vector2 { - return linalg.lerp(v1, v2, amount) + return linalg.lerp(v1, v2, Vector2(amount)) } // Calculate reflected vector to normal @(require_results, deprecated="Prefer = linalg.reflect(v, normal)") @@ -405,7 +405,7 @@ Vector3Transform :: proc "c" (v: Vector3, m: Matrix) -> Vector3 { // Calculate linear interpolation between two vectors @(require_results, deprecated="Prefer = linalg.lerp(v1, v2, amount)") Vector3Lerp :: proc "c" (v1, v2: Vector3, amount: f32) -> Vector3 { - return linalg.lerp(v1, v2, amount) + return linalg.lerp(v1, v2, Vector3(amount)) } // Calculate reflected vector to normal @(require_results, deprecated="Prefer = linalg.reflect(v, normal)") -- cgit v1.2.3 From 1d46adb598328b4977d5b4cfad2a1dc679b05a21 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 23 Mar 2024 16:51:04 +0000 Subject: Treat `*x` as an unary operator to improve error messages for common C-programmer mistakes --- src/check_expr.cpp | 13 +++++++++++++ src/check_type.cpp | 16 +++++++++++++++- src/parser.cpp | 7 ++++++- 3 files changed, 34 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d1f393bef..f09c8fe3c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1800,6 +1800,19 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) { } break; + case Token_Mul: + { + ERROR_BLOCK(); + error(op, "Operator '%.*s' is not a valid unary operator in Odin", LIT(op.string)); + if (is_type_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean '%s^'?\n", str); + } else if (is_type_multi_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: The value is a multi-pointer, did you mean '%s[0]'?\n", str); + } + } + break; default: error(op, "Unknown operator '%.*s'", LIT(op.string)); return false; diff --git a/src/check_type.cpp b/src/check_type.cpp index 38bfa56ef..0b9042905 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3375,7 +3375,9 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) type = t_invalid; - // NOTE(bill): Check for common mistakes from C programmers e.g. T[] and T[N] + // NOTE(bill): Check for common mistakes from C programmers + // e.g. T[] and T[N] + // e.g. *T Ast *node = unparen_expr(e); if (node && node->kind == Ast_IndexExpr) { gbString index_str = nullptr; @@ -3395,6 +3397,18 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) Ast *pseudo_array_expr = ast_array_type(e->file(), ast_token(node->IndexExpr.expr), node->IndexExpr.index, node->IndexExpr.expr); check_array_type_internal(ctx, pseudo_array_expr, &type, nullptr); } + } else if (node && node->kind == Ast_UnaryExpr && node->UnaryExpr.op.kind == Token_Mul) { + gbString type_str = expr_to_string(node->UnaryExpr.expr); + defer (gb_string_free(type_str)); + + error_line("\tSuggestion: Did you mean '^%s'?\n", type_str); + end_error_block(); + + // NOTE(bill): Minimize error propagation of bad array syntax by treating this like a type + if (node->UnaryExpr.expr != nullptr) { + Ast *pseudo_pointer_expr = ast_pointer_type(e->file(), ast_token(node->UnaryExpr.expr), node->UnaryExpr.expr); + return check_type_expr(ctx, pseudo_pointer_expr, named_type); + } } else { end_error_block(); } diff --git a/src/parser.cpp b/src/parser.cpp index b6b62461f..bb9a526fe 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2541,6 +2541,9 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return ast_pointer_type(f, token, elem); } break; + case Token_Mul: + return parse_unary_expr(f, true); + case Token_OpenBracket: { Token token = expect_token(f, Token_OpenBracket); Ast *count_expr = nullptr; @@ -3274,7 +3277,9 @@ gb_internal Ast *parse_unary_expr(AstFile *f, bool lhs) { case Token_Sub: case Token_Xor: case Token_And: - case Token_Not: { + case Token_Not: + case Token_Mul: // Used for error handling when people do C-like things + { Token token = advance_token(f); Ast *expr = parse_unary_expr(f, lhs); return ast_unary_expr(f, token, expr); -- cgit v1.2.3 From 517d7ae0b0fd400ceb6a213e7d644c19b8088bfd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 23 Mar 2024 17:51:56 +0000 Subject: Add error block around `error_line` calls --- src/check_builtin.cpp | 3 +++ src/check_decl.cpp | 1 + src/check_expr.cpp | 5 +++++ src/check_stmt.cpp | 5 +++++ src/checker.cpp | 12 +++++++++++- src/parser.cpp | 6 +++--- 6 files changed, 28 insertions(+), 4 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d3158961e..53e4acbd1 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -89,6 +89,7 @@ gb_internal void check_or_else_split_types(CheckerContext *c, Operand *x, String gb_internal void check_or_else_expr_no_value_error(CheckerContext *c, String const &name, Operand const &x, Type *type_hint) { + ERROR_BLOCK(); gbString t = type_to_string(x.type); error(x.expr, "'%.*s' does not return a value, value is of type %s", LIT(name), t); if (is_type_union(type_deref(x.type))) { @@ -1565,6 +1566,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } if (!operand->value.value_bool) { + ERROR_BLOCK(); gbString arg1 = expr_to_string(ce->args[0]); gbString arg2 = {}; @@ -1590,6 +1592,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_untyped_bool; operand->mode = Addressing_Constant; } else if (name == "panic") { + ERROR_BLOCK(); if (ce->args.count != 1) { error(call, "'#panic' expects 1 argument, got %td", ce->args.count); return false; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 2c0f7a7b8..952a877a4 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1630,6 +1630,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de Entity *uvar = entry.uvar; Entity *prev = scope_insert_no_mutex(ctx->scope, uvar); if (prev != nullptr) { + ERROR_BLOCK(); error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string)); break; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 80008d73a..ecc8a804c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5905,6 +5905,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { if (show_error) { + ERROR_BLOCK(); check_assignment(c, o, param_type, str_lit("procedure argument")); Type *src = base_type(o->type); @@ -8459,6 +8460,7 @@ gb_internal ExprKind check_or_return_expr(CheckerContext *c, Operand *o, Ast *no // NOTE(bill): allow implicit conversion between boolean types // within 'or_return' to improve the experience using third-party code } else if (!check_is_assignable_to(c, &rhs, end_type)) { + ERROR_BLOCK(); // TODO(bill): better error message gbString a = type_to_string(right_type); gbString b = type_to_string(end_type); @@ -10030,6 +10032,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint); if (is_const) { if (index < 0) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s'", str); if (!build_context.terse_errors) { @@ -10046,6 +10049,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, bool finish = false; o->value = get_constant_field_single(c, value, cast(i32)index, &success, &finish); if (!success) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s' with index %lld", str, cast(long long)index); if (!build_context.terse_errors) { @@ -10236,6 +10240,7 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, } } if (!all_constant) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot slice '%s' with non-constant indices", str); if (!build_context.terse_errors) { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index d34695a3a..1d7e7d4e9 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -883,6 +883,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod } if (ctx->inline_for_depth >= MAX_INLINE_FOR_DEPTH && prev_inline_for_depth < MAX_INLINE_FOR_DEPTH) { + ERROR_BLOCK(); if (prev_inline_for_depth > 0) { error(node, "Nested '#unroll for' loop cannot be inlined as it exceeds the maximum '#unroll for' depth (%lld levels >= %lld maximum levels)", v, MAX_INLINE_FOR_DEPTH); } else { @@ -1592,6 +1593,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { isize count = t->Tuple.variables.count; if (count < 1 || count > 3) { + 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"); break; @@ -2085,6 +2087,9 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } return; } + + ERROR_BLOCK(); + gbString expr_str = expr_to_string(operand.expr); error(node, "Expression is not used: '%s'", expr_str); gb_string_free(expr_str); diff --git a/src/checker.cpp b/src/checker.cpp index bf6a84588..6456cab0c 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3180,6 +3180,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3428,6 +3429,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } else if (mode == "speed") { ac->optimization_mode = ProcedureOptimizationMode_Speed; } else { + ERROR_BLOCK(); error(elem, "Invalid optimization_mode for '%.*s'. Valid modes:", LIT(name)); error_line("\tnone\n"); error_line("\tminimal\n"); @@ -3558,6 +3560,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { model == "localexec") { ac->thread_local_model = model; } else { + ERROR_BLOCK(); error(elem, "Invalid thread local model '%.*s'. Valid models:", LIT(model)); error_line("\tdefault\n"); error_line("\tlocaldynamic\n"); @@ -3608,6 +3611,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3762,6 +3766,7 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at if (!proc(c, elem, name, value, ac)) { if (!build_context.ignore_unknown_attributes) { + ERROR_BLOCK(); error(elem, "Unknown attribute element name '%.*s'", LIT(name)); error_line("\tDid you forget to use build flag '-ignore-unknown-attributes'?\n"); } @@ -3831,6 +3836,8 @@ gb_internal bool check_arity_match(CheckerContext *c, AstValueDecl *vd, bool is_ gb_string_free(str); return false; } else if (is_global) { + ERROR_BLOCK(); + Ast *n = vd->values[rhs-1]; error(n, "Expected %td expressions on the right hand side, got %td", lhs, rhs); error_line("Note: Global declarations do not allow for multi-valued expressions"); @@ -6052,11 +6059,14 @@ gb_internal void check_unique_package_names(Checker *c) { continue; } + + begin_error_block(); error(curr, "Duplicate declaration of 'package %.*s'", LIT(name)); error_line("\tA package name must be unique\n" "\tThere is no relation between a package name and the directory that contains it, so they can be completely different\n" "\tA package name is required for link name prefixing to have a consistent ABI\n"); - error(prev, "found at previous location"); + error_line("%s found at previous location\n", token_pos_to_string(ast_token(prev).pos)); + end_error_block(); } } diff --git a/src/parser.cpp b/src/parser.cpp index bb9a526fe..b4a2e060c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6295,7 +6295,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { - error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); + error({}, "Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return ParseFile_WrongExtension; } } else if (init_fullpath.len != 0) { @@ -6308,7 +6308,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { String short_path = filename_from_path(path); char *cpath = alloc_cstring(temporary_allocator(), short_path); if (gb_file_exists(cpath)) { - error_line("Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); + error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } } @@ -6344,7 +6344,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if (!path_is_directory(fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(fullpath, ext)) { - error_line("Expected either a directory or a .odin file, got '%.*s'\n", LIT(fullpath)); + error({}, "Expected either a directory or a .odin file, got '%.*s'\n", LIT(fullpath)); return ParseFile_WrongExtension; } } -- cgit v1.2.3 From 1ea12295165091e893bff34da73edfa916a00e7d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 24 Mar 2024 13:42:37 +0000 Subject: Fix #3319 --- src/parser.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index b4a2e060c..6e0885717 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3666,6 +3666,7 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { expect_token_after(f, Token_Colon, "identifier list"); if ((flags&StmtAllowFlag_Label) && lhs.count == 1) { bool is_partial = false; + bool is_reverse = false; Token partial_token = {}; if (f->curr_token.kind == Token_Hash) { // NOTE(bill): This is purely for error messages @@ -3675,6 +3676,11 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { partial_token = expect_token(f, Token_Hash); expect_token(f, Token_Ident); is_partial = true; + } else if (name.kind == Token_Ident && name.string == "reverse" && + peek_token_n(f, 1).kind == Token_for) { + partial_token = expect_token(f, Token_Hash); + expect_token(f, Token_Ident); + is_reverse = true; } } switch (f->curr_token.kind) { @@ -3709,6 +3715,18 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { break; } syntax_error(partial_token, "Incorrect use of directive, use '#partial %.*s: switch'", LIT(ast_token(name).string)); + } else if (is_reverse) { + switch (stmt->kind) { + case Ast_RangeStmt: + if (stmt->RangeStmt.reverse) { + syntax_error(token, "#reverse already applied to a 'for in' statement"); + } + stmt->RangeStmt.reverse = true; + break; + default: + syntax_error(token, "#reverse can only be applied to a 'for in' statement"); + break; + } } return stmt; -- cgit v1.2.3 From 68ff9454198e9eb02f46dac1777f68c594c60915 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 24 Mar 2024 14:39:42 +0000 Subject: Remove old error message for #3062 --- src/parser.cpp | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 6e0885717..d46079964 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2351,9 +2351,6 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return ast_bad_expr(f, token, name); } switch (expr->kind) { - case Ast_ArrayType: - syntax_error(expr, "#partial has been replaced with #sparse for non-contiguous enumerated array types"); - break; case Ast_CompoundLit: expr->CompoundLit.tag = tag; break; -- cgit v1.2.3 From 12ec9bce7d5ba87452f6a23b35ad7e5e78012ec9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 27 Mar 2024 13:05:15 +0000 Subject: Fix parsing bug on `bit_set[;x]` --- src/parser.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index d46079964..747677946 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2885,6 +2885,10 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { elem = parse_expr(f, true); f->allow_range = prev_allow_range; + if (elem == nullptr) { + syntax_error(token, "Expected a type or range, got nothing"); + } + if (allow_token(f, Token_Semicolon)) { underlying = parse_type(f); } else if (allow_token(f, Token_Comma)) { @@ -2894,6 +2898,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { underlying = parse_type(f); } + expect_token(f, Token_CloseBracket); return ast_bit_set_type(f, token, elem, underlying); } -- cgit v1.2.3 From 7b387fd3aa9b943929dcb73f669d8151837f0a24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 27 Mar 2024 13:10:46 +0000 Subject: Improve C-like syntax mistakes error messages --- src/parser.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 747677946..13225f622 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5014,6 +5014,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { case Token_Xor: case Token_Not: case Token_And: + case Token_Mul: // Used for error handling when people do C-like things s = parse_simple_stmt(f, StmtAllowFlag_Label); expect_semicolon(f); return s; -- cgit v1.2.3 From 83e2f5ff74a6f2224983ac4c5631b8f65024a239 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 4 Apr 2024 17:01:31 +0100 Subject: Add better error messages with suggestions for using `context` as an identifier --- src/parser.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 13225f622..bf16f5c9f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1482,7 +1482,16 @@ gb_internal Token expect_token(AstFile *f, TokenKind kind) { if (prev.kind != kind) { String c = token_strings[kind]; String p = token_to_string(prev); + begin_error_block(); syntax_error(f->curr_token, "Expected '%.*s', got '%.*s'", LIT(c), LIT(p)); + if (kind == Token_Ident) switch (prev.kind) { + case Token_context: + error_line("\tSuggestion: 'context' is a reserved keyword, would 'ctx' suffice?\n"); + break; + } + + end_error_block(); + if (prev.kind == Token_EOF) { exit_with_errors(); } @@ -4055,7 +4064,12 @@ gb_internal Array convert_to_ident_list(AstFile *f, Array li case Ast_BadExpr: break; case Ast_Implicit: + begin_error_block(); syntax_error(ident, "Expected an identifier, '%.*s' which is a keyword", LIT(ident->Implicit.string)); + if (ident->Implicit.kind == Token_context) { + error_line("\tSuggestion: Would 'ctx' suffice as an alternative name?\n"); + } + end_error_block(); ident = ast_ident(f, blank_token); break; -- cgit v1.2.3 From 0df9c8bffc3f468cab08eaad97b49952a0b6bf3e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 8 Apr 2024 12:04:33 +0100 Subject: Improve error messages for people using keywords instead of identifiers --- src/parser.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index bf16f5c9f..01a3069ff 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1486,7 +1486,15 @@ gb_internal Token expect_token(AstFile *f, TokenKind kind) { syntax_error(f->curr_token, "Expected '%.*s', got '%.*s'", LIT(c), LIT(p)); if (kind == Token_Ident) switch (prev.kind) { case Token_context: - error_line("\tSuggestion: 'context' is a reserved keyword, would 'ctx' suffice?\n"); + error_line("\tSuggestion: '%.*s' is a keyword, would 'ctx' suffice?\n", LIT(prev.string)); + break; + case Token_package: + error_line("\tSuggestion: '%.*s' is a keyword, would 'pkg' suffice?\n", LIT(prev.string)); + break; + default: + if (token_is_keyword(prev.kind)) { + error_line("\tNote: '%.*s' is a keyword\n", LIT(prev.string)); + } break; } -- cgit v1.2.3 From 7e582dd671addfd2119b0a9016635cdaddc6b22f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Apr 2024 12:43:27 +0100 Subject: Add basic suggestion to missing `package` name --- src/parser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/parser.cpp') 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; } -- cgit v1.2.3 From 60ef4fda4dd71e5474bb2598c4e0d18c58924e99 Mon Sep 17 00:00:00 2001 From: joakin Date: Wed, 3 Apr 2024 14:03:56 +0200 Subject: Recognize dynamic library names like libraylib.so.5.0.0 --- src/linker.cpp | 2 +- src/parser.cpp | 2 +- src/string.cpp | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/linker.cpp b/src/linker.cpp index 498a96c5f..245275bd3 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -432,7 +432,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { // static libs and object files, absolute full path relative to the file in which the lib was imported from lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(lib, str_lit(".so"))) { + } else if (string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { // dynamic lib, relative path to executable // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible // at runtime to the executable diff --git a/src/parser.cpp b/src/parser.cpp index f4d3dc48d..2bf25c768 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5710,7 +5710,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node // working directory of the exe to the library search paths. // Static libraries can be linked directly with the full pathname // - if (node->kind == Ast_ForeignImportDecl && string_ends_with(file_str, str_lit(".so"))) { + if (node->kind == Ast_ForeignImportDecl && (string_ends_with(file_str, str_lit(".so")) || string_contains_string(file_str, str_lit(".so.")))) { *path = file_str; return true; } diff --git a/src/string.cpp b/src/string.cpp index 3747f4564..a68cf315f 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -323,6 +323,25 @@ gb_internal bool string_contains_char(String const &s, u8 c) { return false; } +gb_internal bool string_contains_string(String const &haystack, String const &needle) { + if (needle.len == 0) return true; + if (needle.len > haystack.len) return false; + + for (isize i = 0; i <= haystack.len - needle.len; i++) { + bool found = true; + for (isize j = 0; j < needle.len; j++) { + if (haystack[i + j] != needle[j]) { + found = false; + break; + } + } + if (found) { + return true; + } + } + return false; +} + gb_internal String filename_from_path(String s) { isize i = string_extension_position(s); if (i >= 0) { -- cgit v1.2.3 From 0da6a3e214c66e2955307e2aad12d844a051f8d8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 7 May 2024 11:42:48 +0100 Subject: Fix #3530 --- src/check_type.cpp | 9 +++++++-- src/parser.cpp | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/check_type.cpp b/src/check_type.cpp index 457375779..6efac54d6 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -797,11 +797,11 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.scope = ctx->scope; Type *base_type = t_int; - if (et->base_type != nullptr) { + if (unparen_expr(et->base_type) != nullptr) { base_type = check_type(ctx, et->base_type); } - if (base_type == nullptr || !is_type_integer(base_type)) { + if (base_type == nullptr || base_type == t_invalid || !is_type_integer(base_type)) { error(node, "Base type for enumeration must be an integer"); return; } @@ -3265,6 +3265,11 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T case_end; case_ast_node(pe, ParenExpr, e); + if (pe->expr == nullptr) { + error(e, "Expected an expression or type within the parentheses"); + *type = t_invalid; + return true; + } *type = check_type_expr(ctx, pe->expr, named_type); set_base_type(named_type, *type); return true; diff --git a/src/parser.cpp b/src/parser.cpp index 2bf25c768..04505cbd7 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3499,6 +3499,10 @@ gb_internal Ast *parse_type(AstFile *f) { Token token = advance_token(f); syntax_error(token, "Expected a type"); return ast_bad_expr(f, token, f->curr_token); + } else if (type->kind == Ast_ParenExpr && + unparen_expr(type) == nullptr) { + syntax_error(type, "Expected a type within the parentheses"); + return ast_bad_expr(f, type->ParenExpr.open, type->ParenExpr.close); } return type; } -- cgit v1.2.3 From f54977336b27c32eab52b77d94e7b1610f4350cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 9 May 2024 15:56:00 +0100 Subject: With `-vet-style`, give suggestion of separating where clauses with a comma rather than '&&' This improves the error messages --- src/check_expr.cpp | 14 ++++++++++++++ src/check_type.cpp | 2 +- src/parser.cpp | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 013638e63..98aebfe4e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6193,6 +6193,20 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco } return false; } + + if (ast_file_vet_style(ctx->file)) { + Ast *c = unparen_expr(clause); + if (c->kind == Ast_BinaryExpr && c->BinaryExpr.op.kind == Token_CmpAnd) { + ERROR_BLOCK(); + error(c, "Prefer to separate 'where' clauses with a comma rather than '&&'"); + gbString x = expr_to_string(c->BinaryExpr.left); + gbString y = expr_to_string(c->BinaryExpr.right); + error_line("\tSuggestion: '%s, %s'", x, y); + gb_string_free(y); + gb_string_free(x); + } + } + } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 3d11b5012..11e332757 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1166,7 +1166,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, } } if (all_ones && all_booleans) { - if (build_context.vet_flags & VetFlag_Style) { + if (ast_file_vet_style(ctx->file)) { char const *msg = "This 'bit_field' is better expressed as a 'bit_set' since all of the fields are booleans, of 1-bit in size, and the backing type is an integer (-vet-style)"; error(node, msg); } else { diff --git a/src/parser.cpp b/src/parser.cpp index 04505cbd7..6e859fe32 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,7 +1,7 @@ #include "parser_pos.cpp" gb_internal u64 ast_file_vet_flags(AstFile *f) { - if (f->vet_flags_set) { + if (f != nullptr && f->vet_flags_set) { return f->vet_flags; } return build_context.vet_flags; -- cgit v1.2.3 From 710bb4369f45ee6dfa8b66e67e25d91a15000a65 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 10 May 2024 13:55:15 +0100 Subject: Fix #3567 --- src/parser.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 6e859fe32..ee3c56daf 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3883,10 +3883,12 @@ gb_internal Ast *parse_proc_type(AstFile *f, Token proc_token) { expect_token(f, Token_OpenParen); + f->expr_level += 1; params = parse_field_list(f, nullptr, FieldFlag_Signature, Token_CloseParen, true, true); if (file_allow_newline(f)) { skip_possible_newline(f); } + f->expr_level -= 1; expect_token_after(f, Token_CloseParen, "parameter list"); results = parse_results(f, &diverging); -- cgit v1.2.3 From 1935811b2c6118e633dd5c4cef2e9e70f7b962e5 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 12 May 2024 19:51:19 -0400 Subject: Suggest `-all-packages` if testing empty directory --- src/parser.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index ee3c56daf..ad962f53e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5508,11 +5508,15 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const } } if (files_with_ext == 0 || files_to_reserve == 1) { + ERROR_BLOCK(); if (files_with_ext != 0) { syntax_error(pos, "Directory contains no .odin files for the specified platform: %.*s", LIT(rel_path)); } else { syntax_error(pos, "Empty directory that contains no .odin files: %.*s", LIT(rel_path)); } + if (build_context.command_kind == Command_test) { + error_line("\tSuggestion: Make an .odin file that imports packages to test and use the `-all-packages` flag."); + } return nullptr; } -- cgit v1.2.3 From 215ef3d985724fa0ef7092fa8f672cc502db87be Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 13 May 2024 13:26:47 +0100 Subject: Make `core:runtime` etc a warning, and an error with `-vet` --- src/build_settings.cpp | 5 ++++- src/parser.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c6ef33af2..ec7d03a84 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -730,10 +730,11 @@ enum VetFlags : u64 { VetFlag_Semicolon = 1u<<4, VetFlag_UnusedVariables = 1u<<5, VetFlag_UnusedImports = 1u<<6, + VetFlag_Deprecated = 1u<<7, VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, - VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt, + VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt|VetFlag_Deprecated, VetFlag_Using = VetFlag_UsingStmt|VetFlag_UsingParam, }; @@ -755,6 +756,8 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_Style; } else if (name == "semicolon") { return VetFlag_Semicolon; + } else if (name == "deprecated") { + return VetFlag_Deprecated; } return VetFlag_NONE; } diff --git a/src/parser.cpp b/src/parser.cpp index ad962f53e..66e175765 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -11,6 +11,9 @@ gb_internal bool ast_file_vet_style(AstFile *f) { return (ast_file_vet_flags(f) & VetFlag_Style) != 0; } +gb_internal bool ast_file_vet_deprecated(AstFile *f) { + return (ast_file_vet_flags(f) & VetFlag_Deprecated) != 0; +} gb_internal bool file_allow_newline(AstFile *f) { bool is_strict = build_context.strict_style || ast_file_vet_style(f); @@ -5694,8 +5697,26 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name.len > 0) { // NOTE(bill): `base:runtime` == `core:runtime` - if (collection_name == "core" && string_starts_with(file_str, str_lit("runtime"))) { - collection_name = str_lit("base"); + if (collection_name == "core") { + bool replace_with_base = false; + if (string_starts_with(file_str, str_lit("runtime"))) { + replace_with_base = true; + } else if (string_starts_with(file_str, str_lit("intrinsics"))) { + replace_with_base = true; + } if (string_starts_with(file_str, str_lit("builtin"))) { + replace_with_base = true; + } + + if (replace_with_base) { + collection_name = str_lit("base"); + } + if (replace_with_base) { + if (ast_file_vet_deprecated(node->file())) { + syntax_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + } else { + syntax_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + } + } } if (collection_name == "system") { -- cgit v1.2.3 From 0cf9dcd31441a56856a60e4f73db4b005a1407ee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 13 May 2024 18:15:29 +0100 Subject: Make `..` ranges a complete error rather than a warning now. This should have been an error years ago. --- src/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 66e175765..e296e6935 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1572,7 +1572,7 @@ gb_internal Token expect_operator(AstFile *f) { LIT(p)); } if (prev.kind == Token_Ellipsis) { - syntax_warning(prev, "'..' for ranges has now been deprecated, prefer '..='"); + syntax_error(prev, "'..' for ranges are not allowed, did you mean '..<' or '..='?"); f->tokens[f->curr_token_index].flags |= TokenFlag_Replace; } -- cgit v1.2.3 From 542c3d7561df82486310f355ca84a88fa5af3f9f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 20 May 2024 13:32:16 +0100 Subject: Improve "Expected a type" syntax error --- src/parser.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index e296e6935..5aa11b5d0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3499,8 +3499,14 @@ gb_internal Array parse_ident_list(AstFile *f, bool allow_poly_names) { gb_internal Ast *parse_type(AstFile *f) { Ast *type = parse_type_or_ident(f); if (type == nullptr) { - Token token = advance_token(f); - syntax_error(token, "Expected a type"); + Token prev_token = f->curr_token; + Token token = {}; + if (f->curr_token.kind == Token_OpenBrace) { + token = f->curr_token; + } else { + token = advance_token(f); + } + syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token.string)); return ast_bad_expr(f, token, f->curr_token); } else if (type->kind == Ast_ParenExpr && unparen_expr(type) == nullptr) { -- cgit v1.2.3 From 38fffff06a76004a02bedc34172aa5107784c03f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 27 May 2024 23:51:43 +0100 Subject: Begin moving `foreign import` import paths to be evaluated in the semantic phase rather than parsing. --- src/checker.cpp | 39 +++++++++++++++++++++++++++++++- src/parser.cpp | 65 ++++++++++++++++++++++++------------------------------ src/parser.hpp | 2 +- src/parser_pos.cpp | 2 +- 4 files changed, 69 insertions(+), 39 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 9d44c34dc..4e5aed2bf 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4883,7 +4883,44 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { Scope *parent_scope = ctx->scope; GB_ASSERT(parent_scope->flags&ScopeFlag_File); - GB_ASSERT(fl->fullpaths.count > 0); + String base_dir = dir_from_path(decl->file()->fullpath); + + auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + + for (Ast *fp_node : fl->filepaths) { + Operand op = {}; + check_expr(ctx, &op, fp_node); + if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { + gbString s = expr_to_string(op.expr); + error(fp_node, "Expected a constant string value, got '%s'", s); + gb_string_free(s); + continue; + } + if (!is_type_string(op.type)) { + gbString s = type_to_string(op.type); + error(fp_node, "Expected a constant string value, got value of type '%s'", s); + gb_string_free(s); + continue; + } + + String file_str = op.value.value_string; + file_str = string_trim_whitespace(file_str); + + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path); + gb_unused(ok); + fullpath = foreign_path; + } + array_add(&fullpaths, fullpath); + } + fl->fullpaths = slice_from_array(fullpaths); + + + if (fl->fullpaths.count == 0) { + return; + } String fullpath = fl->fullpaths[0]; String library_name = path_to_entity_name(fl->library_name.string, fullpath); if (is_blank_ident(library_name)) { diff --git a/src/parser.cpp b/src/parser.cpp index 5aa11b5d0..be0d68177 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1284,7 +1284,7 @@ gb_internal Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token i return result; } -gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, +gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ForeignImportDecl); result->ForeignImportDecl.token = token; @@ -4882,14 +4882,14 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { if (is_blank_ident(lib_name)) { syntax_error(lib_name, "Illegal foreign import name: '_'"); } - Array filepaths = {}; + Array filepaths = {}; if (allow_token(f, Token_OpenBrace)) { array_init(&filepaths, ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - Token path = expect_token(f, Token_String); + Ast *path = parse_expr(f, true); array_add(&filepaths, path); if (!allow_field_separator(f)) { @@ -4898,9 +4898,10 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { } expect_closing_brace_of_field_list(f); } else { - filepaths = array_make(ast_allocator(f), 0, 1); + filepaths = array_make(ast_allocator(f), 0, 1); Token path = expect_token(f, Token_String); - array_add(&filepaths, path); + Ast *lit = ast_basic_lit(f, path); + array_add(&filepaths, lit); } Ast *s = nullptr; @@ -4909,7 +4910,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { s = ast_bad_decl(f, lib_name, f->curr_token); } else if (f->curr_proc != nullptr) { syntax_error(lib_name, "You cannot use foreign import within a procedure. This must be done at the file scope"); - s = ast_bad_decl(f, lib_name, filepaths[0]); + s = ast_bad_decl(f, lib_name, ast_token(filepaths[0])); } else { s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); } @@ -5648,9 +5649,19 @@ gb_internal bool is_package_name_reserved(String const &name) { } -gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path) { +gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path, bool use_check_errors=false) { GB_ASSERT(path != nullptr); + void (*do_error)(Ast *, char const *, ...); + void (*do_warning)(Token const &, char const *, ...); + + do_error = &syntax_error; + do_warning = &syntax_warning; + if (use_check_errors) { + do_error = &error; + do_error = &warning; + } + // NOTE(bill): if file_mutex == nullptr, this means that the code is used within the semantics stage String collection_name = {}; @@ -5677,7 +5688,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node String file_str = {}; if (colon_pos == 0) { - syntax_error(node, "Expected a collection name"); + do_error(node, "Expected a collection name"); return false; } @@ -5692,11 +5703,11 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (has_windows_drive) { String sub_file_path = substring(file_str, 3, file_str.len); if (!is_import_path_valid(sub_file_path)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } } else if (!is_import_path_valid(file_str)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } @@ -5718,16 +5729,16 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node } if (replace_with_base) { if (ast_file_vet_deprecated(node->file())) { - syntax_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + do_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); } else { - syntax_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + do_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); } } } if (collection_name == "system") { if (node->kind != Ast_ForeignImportDecl) { - syntax_error(node, "The library collection 'system' is restrict for 'foreign_library'"); + do_error(node, "The library collection 'system' is restrict for 'foreign import'"); return false; } else { *path = file_str; @@ -5735,7 +5746,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node } } else if (!find_library_collection_path(collection_name, &base_dir)) { // NOTE(bill): It's a naughty name - syntax_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); + do_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); return false; } } else { @@ -5759,7 +5770,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name == "core" || collection_name == "base") { return true; } else { - syntax_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); + do_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); return false; } } @@ -5844,30 +5855,12 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas } else if (node->kind == Ast_ForeignImportDecl) { ast_node(fl, ForeignImportDecl, node); - auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); - - for (Token const &fp : fl->filepaths) { - String file_str = string_trim_whitespace(string_value_from_token(f, fp)); - String fullpath = file_str; - if (allow_check_foreign_filepath()) { - String foreign_path = {}; - bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); - if (!ok) { - decls[i] = ast_bad_decl(f, fp, fl->filepaths[fl->filepaths.count-1]); - goto end; - } - fullpath = foreign_path; - } - array_add(&fullpaths, fullpath); - } - if (fullpaths.count == 0) { + if (fl->filepaths.count == 0) { syntax_error(decls[i], "No foreign paths found"); - decls[i] = ast_bad_decl(f, fl->filepaths[0], fl->filepaths[fl->filepaths.count-1]); + decls[i] = ast_bad_decl(f, ast_token(fl->filepaths[0]), ast_end_token(fl->filepaths[fl->filepaths.count-1])); goto end; - } - - fl->fullpaths = slice_from_array(fullpaths); + } } else if (node->kind == Ast_WhenStmt) { ast_node(ws, WhenStmt, node); diff --git a/src/parser.hpp b/src/parser.hpp index 5820275c8..1e07cfd59 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -631,7 +631,7 @@ AST_KIND(_DeclBegin, "", bool) \ }) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ - Slice filepaths; \ + Slice filepaths; \ Token library_name; \ String collection_name; \ Slice fullpaths; \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index b2e12999b..1ffd3a82f 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -278,7 +278,7 @@ Token ast_end_token(Ast *node) { case Ast_ImportDecl: return node->ImportDecl.relpath; case Ast_ForeignImportDecl: if (node->ForeignImportDecl.filepaths.count > 0) { - return node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]; + return ast_end_token(node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]); } if (node->ForeignImportDecl.library_name.kind != Token_Invalid) { return node->ForeignImportDecl.library_name; -- cgit v1.2.3 From a1b8749e74639875467cba56b0ab02c342870338 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 May 2024 00:23:23 +0100 Subject: Delay checking foreign import paths until after global scope is checked --- src/checker.cpp | 168 +++++++++++++++++++++++++++++--------------------------- src/checker.hpp | 3 + src/entity.cpp | 1 + src/parser.cpp | 28 +++++++++- src/parser.hpp | 1 + 5 files changed, 118 insertions(+), 83 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 4e5aed2bf..1ded6ea6e 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1283,6 +1283,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { mpsc_init(&i->definition_queue, a); //); // 1<<20); mpsc_init(&i->required_global_variable_queue, a); // 1<<10); mpsc_init(&i->required_foreign_imports_through_force_queue, a); // 1<<10); + mpsc_init(&i->foreign_imports_to_check_fullpaths, a); // 1<<10); mpsc_init(&i->intrinsics_entry_point_usage, a); // 1<<10); // just waste some memory here, even if it probably never used string_map_init(&i->load_directory_cache); @@ -1307,6 +1308,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { mpsc_destroy(&i->definition_queue); mpsc_destroy(&i->required_global_variable_queue); mpsc_destroy(&i->required_foreign_imports_through_force_queue); + mpsc_destroy(&i->foreign_imports_to_check_fullpaths); map_destroy(&i->objc_msgSend_types); string_map_destroy(&i->load_file_cache); @@ -4874,105 +4876,112 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { return false; } -gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { - if (decl->state_flags & StateFlag_BeenHandled) return; - decl->state_flags |= StateFlag_BeenHandled; +gb_internal void check_foreign_import_fullpaths(Checker *c) { + CheckerContext ctx = make_checker_context(c); - ast_node(fl, ForeignImportDecl, decl); + UntypedExprInfoMap untyped = {}; + defer (map_destroy(&untyped)); - Scope *parent_scope = ctx->scope; - GB_ASSERT(parent_scope->flags&ScopeFlag_File); + for (Entity *e = nullptr; mpsc_dequeue(&c->info.foreign_imports_to_check_fullpaths, &e); /**/) { + GB_ASSERT(e != nullptr); + GB_ASSERT(e->kind == Entity_LibraryName); + Ast *decl = e->LibraryName.decl; + ast_node(fl, ForeignImportDecl, decl); - String base_dir = dir_from_path(decl->file()->fullpath); + AstFile *f = decl->file(); - auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + reset_checker_context(&ctx, f, &untyped); + ctx.collect_delayed_decls = false; - for (Ast *fp_node : fl->filepaths) { - Operand op = {}; - check_expr(ctx, &op, fp_node); - if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { - gbString s = expr_to_string(op.expr); - error(fp_node, "Expected a constant string value, got '%s'", s); - gb_string_free(s); - continue; - } - if (!is_type_string(op.type)) { - gbString s = type_to_string(op.type); - error(fp_node, "Expected a constant string value, got value of type '%s'", s); - gb_string_free(s); - continue; - } + GB_ASSERT(ctx.scope == e->scope); - String file_str = op.value.value_string; - file_str = string_trim_whitespace(file_str); + if (fl->fullpaths.count == 0) { + String base_dir = dir_from_path(decl->file()->fullpath); - String fullpath = file_str; - if (allow_check_foreign_filepath()) { - String foreign_path = {}; - bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path); - gb_unused(ok); - fullpath = foreign_path; - } - array_add(&fullpaths, fullpath); - } - fl->fullpaths = slice_from_array(fullpaths); + auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + for (Ast *fp_node : fl->filepaths) { + Operand op = {}; + check_expr(&ctx, &op, fp_node); + if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { + gbString s = expr_to_string(op.expr); + error(fp_node, "Expected a constant string value, got '%s'", s); + gb_string_free(s); + continue; + } + if (!is_type_string(op.type)) { + gbString s = type_to_string(op.type); + error(fp_node, "Expected a constant string value, got value of type '%s'", s); + gb_string_free(s); + continue; + } - if (fl->fullpaths.count == 0) { - return; - } - String fullpath = fl->fullpaths[0]; - String library_name = path_to_entity_name(fl->library_name.string, fullpath); - if (is_blank_ident(library_name)) { - error(fl->token, "File name, %.*s, cannot be as a library name as it is not a valid identifier", LIT(fl->library_name.string)); - return; - } + String file_str = op.value.value_string; + file_str = string_trim_whitespace(file_str); - for (String const &path : fl->fullpaths) { - String ext = path_extension(path); - if (str_eq_ignore_case(ext, ".c") || - str_eq_ignore_case(ext, ".cpp") || - str_eq_ignore_case(ext, ".cxx") || - str_eq_ignore_case(ext, ".h") || - str_eq_ignore_case(ext, ".hpp") || - str_eq_ignore_case(ext, ".hxx") || - false - ) { - error(fl->token, "With 'foreign import', you cannot import a %.*s file directory, you must precompile the library and link against that", LIT(ext)); - break; + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); + if (ok) { + fullpath = foreign_path; + } + } + array_add(&fullpaths, fullpath); + } + fl->fullpaths = slice_from_array(fullpaths); + } + + for (String const &path : fl->fullpaths) { + String ext = path_extension(path); + if (str_eq_ignore_case(ext, ".c") || + str_eq_ignore_case(ext, ".cpp") || + str_eq_ignore_case(ext, ".cxx") || + str_eq_ignore_case(ext, ".h") || + str_eq_ignore_case(ext, ".hpp") || + str_eq_ignore_case(ext, ".hxx") || + false + ) { + error(fl->token, "With 'foreign import', you cannot import a %.*s file/directory, you must precompile the library and link against that", LIT(ext)); + break; + } } + + add_untyped_expressions(ctx.info, &untyped); + + e->LibraryName.paths = fl->fullpaths; } +} + +gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { + if (decl->state_flags & StateFlag_BeenHandled) return; + decl->state_flags |= StateFlag_BeenHandled; + ast_node(fl, ForeignImportDecl, decl); - // if (fl->collection_name != "system") { - // char *c_str = gb_alloc_array(heap_allocator(), char, fullpath.len+1); - // defer (gb_free(heap_allocator(), c_str)); - // gb_memmove(c_str, fullpath.text, fullpath.len); - // c_str[fullpath.len] = '\0'; + Scope *parent_scope = ctx->scope; + GB_ASSERT(parent_scope->flags&ScopeFlag_File); - // gbFile f = {}; - // gbFileError file_err = gb_file_open(&f, c_str); - // defer (gb_file_close(&f)); + String library_name = fl->library_name.string; + if (library_name.len == 0 && fl->fullpaths.count != 0) { + String fullpath = fl->fullpaths[0]; + library_name = path_to_entity_name(fl->library_name.string, fullpath); + } + if (library_name.len == 0 || is_blank_ident(library_name)) { + error(fl->token, "File name, '%.*s', cannot be as a library name as it is not a valid identifier", LIT(library_name)); + return; + } - // switch (file_err) { - // case gbFileError_Invalid: - // error(decl, "Invalid file or cannot be found ('%.*s')", LIT(fullpath)); - // return; - // case gbFileError_NotExists: - // error(decl, "File cannot be found ('%.*s')", LIT(fullpath)); - // return; - // } - // } GB_ASSERT(fl->library_name.pos.line != 0); fl->library_name.string = library_name; Entity *e = alloc_entity_library_name(parent_scope, fl->library_name, t_invalid, fl->fullpaths, library_name); + e->LibraryName.decl = decl; add_entity_flags_from_file(ctx, e, parent_scope); add_entity(ctx, parent_scope, nullptr, e); - AttributeContext ac = {}; check_decl_attributes(ctx, fl->attributes, foreign_import_decl_attribute, &ac); if (ac.require_declaration) { @@ -4987,12 +4996,8 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { e->LibraryName.extra_linker_flags = extra_linker_flags; } - if (has_asm_extension(fullpath)) { - if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.os != TargetOs_darwin) { - error(decl, "Assembly files are not yet supported on this platform: %.*s_%.*s", - LIT(target_os_names[build_context.metrics.os]), LIT(target_arch_names[build_context.metrics.arch])); - } - } + mpsc_enqueue(&ctx->info->foreign_imports_to_check_fullpaths, e); + } // Returns true if a new package is present @@ -6354,6 +6359,9 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check procedure bodies"); check_procedure_bodies(c); + TIME_SECTION("check foreign import fullpaths"); + check_foreign_import_fullpaths(c); + TIME_SECTION("add entities from procedure bodies"); check_merge_queues_into_arrays(c); diff --git a/src/checker.hpp b/src/checker.hpp index 2ade9312e..6ae7b90e2 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -414,6 +414,7 @@ struct CheckerInfo { MPSCQueue entity_queue; MPSCQueue required_global_variable_queue; MPSCQueue required_foreign_imports_through_force_queue; + MPSCQueue foreign_imports_to_check_fullpaths; MPSCQueue intrinsics_entry_point_usage; @@ -434,6 +435,8 @@ struct CheckerInfo { BlockingMutex load_directory_mutex; StringMap load_directory_cache; PtrMap load_directory_map; // Key: Ast_CallExpr * + + }; struct CheckerContext { diff --git a/src/entity.cpp b/src/entity.cpp index 8a7417006..1461b96d7 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -266,6 +266,7 @@ struct Entity { Scope *scope; } ImportName; struct { + Ast *decl; Slice paths; String name; i64 priority_index; diff --git a/src/parser.cpp b/src/parser.cpp index be0d68177..7e72f3c21 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1285,13 +1285,15 @@ gb_internal Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token i } gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, - CommentGroup *docs, CommentGroup *comment) { + bool multiple_filepaths, + CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ForeignImportDecl); result->ForeignImportDecl.token = token; result->ForeignImportDecl.filepaths = slice_from_array(filepaths); result->ForeignImportDecl.library_name = library_name; result->ForeignImportDecl.docs = docs; result->ForeignImportDecl.comment = comment; + result->ForeignImportDecl.multiple_filepaths = multiple_filepaths; result->ForeignImportDecl.attributes.allocator = ast_allocator(f); return result; @@ -4882,8 +4884,11 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { if (is_blank_ident(lib_name)) { syntax_error(lib_name, "Illegal foreign import name: '_'"); } + bool multiple_filepaths = false; + Array filepaths = {}; if (allow_token(f, Token_OpenBrace)) { + multiple_filepaths = true; array_init(&filepaths, ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && @@ -4912,7 +4917,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { syntax_error(lib_name, "You cannot use foreign import within a procedure. This must be done at the file scope"); s = ast_bad_decl(f, lib_name, ast_token(filepaths[0])); } else { - s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); + s = ast_foreign_import_decl(f, token, filepaths, lib_name, multiple_filepaths, docs, f->line_comment); } expect_semicolon(f); return s; @@ -5859,7 +5864,24 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas syntax_error(decls[i], "No foreign paths found"); decls[i] = ast_bad_decl(f, ast_token(fl->filepaths[0]), ast_end_token(fl->filepaths[fl->filepaths.count-1])); goto end; - + } else if (!fl->multiple_filepaths && + fl->filepaths.count == 1) { + Ast *fp = fl->filepaths[0]; + GB_ASSERT(fp->kind == Ast_BasicLit); + Token fp_token = fp->BasicLit.token; + String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); + if (!ok) { + decls[i] = ast_bad_decl(f, fp_token, fp_token); + goto end; + } + fullpath = foreign_path; + } + fl->fullpaths = slice_make(permanent_allocator(), 1); + fl->fullpaths[0] = fullpath; } } else if (node->kind == Ast_WhenStmt) { diff --git a/src/parser.hpp b/src/parser.hpp index 1e07cfd59..0e411d9ac 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -632,6 +632,7 @@ AST_KIND(_DeclBegin, "", bool) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ Slice filepaths; \ + bool multiple_filepaths; \ Token library_name; \ String collection_name; \ Slice fullpaths; \ -- cgit v1.2.3 From d91054b615e5e185ded106e4903cdd66b2c4f582 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 May 2024 00:27:13 +0100 Subject: Change parser to use `^Expr` rather than `string` for the foreign import paths --- core/odin/ast/ast.odin | 2 +- core/odin/parser/parser.odin | 10 ++++++---- src/parser.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index be541befa..7891fb12d 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -538,7 +538,7 @@ Foreign_Import_Decl :: struct { import_tok: tokenizer.Token, name: ^Ident, collection_name: string, - fullpaths: []string, + fullpaths: []^Expr, comment: ^Comment_Group, } diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index e32fbdced..813585ba4 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1190,12 +1190,12 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { error(p, name.pos, "illegal foreign import name: '_'") } - fullpaths: [dynamic]string + fullpaths: [dynamic]^ast.Expr if allow_token(p, .Open_Brace) { for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { - path := expect_token(p, .String) - append(&fullpaths, path.text) + path := parse_expr(p, false) + append(&fullpaths, path) allow_token(p, .Comma) or_break } @@ -1203,7 +1203,9 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { } else { path := expect_token(p, .String) reserve(&fullpaths, 1) - append(&fullpaths, path.text) + bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path)) + bl.tok = tok + append(&fullpaths, bl) } if len(fullpaths) == 0 { diff --git a/src/parser.cpp b/src/parser.cpp index 7e72f3c21..c004a8f65 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4894,7 +4894,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - Ast *path = parse_expr(f, true); + Ast *path = parse_expr(f, false); array_add(&filepaths, path); if (!allow_field_separator(f)) { -- cgit v1.2.3 From 9d28f2e18c80b1537b9a71b907839b20973feb21 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:46:45 -0400 Subject: Fix `or_or_` error messages --- src/parser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index c004a8f65..eff5e0c6c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -555,7 +555,7 @@ gb_internal Ast *ast_unary_expr(AstFile *f, Token op, Ast *expr) { syntax_error_with_verbose(expr, "'or_return' within an unary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(expr, "'or_%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); + syntax_error_with_verbose(expr, "'%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); break; } @@ -583,7 +583,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(left, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(left, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); + syntax_error_with_verbose(left, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); break; } if (right) switch (right->kind) { @@ -591,7 +591,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(right, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(right, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); + syntax_error_with_verbose(right, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); break; } @@ -3102,7 +3102,7 @@ gb_internal void parse_check_or_return(Ast *operand, char const *msg) { syntax_error_with_verbose(operand, "'or_return' use within %s is not wrapped in parentheses (...)", msg); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(operand, "'or_%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); + syntax_error_with_verbose(operand, "'%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); break; } } -- cgit v1.2.3 From 1945218f6df814ea95233035d0b51585e2522b2e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 14:18:33 +0100 Subject: Improve parsing for `label: #reverse for` and `label: #partial switch` --- core/odin/parser/parser.odin | 37 ++++++++++++++++++++++++++++++++++++- src/parser.cpp | 6 ++++-- 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index ad5ee9087..6b0aa2888 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1455,7 +1455,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case "unroll": return parse_unrolled_for_loop(p, tag) case "reverse": - stmt := parse_for_stmt(p) + stmt := parse_stmt(p) if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range { if range.reverse { @@ -3515,6 +3515,25 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case op.kind == .Colon: expect_token_after(p, .Colon, "identifier list") if .Label in flags && len(lhs) == 1 { + is_partial := false + is_reverse := false + + partial_token: tokenizer.Token + if p.curr_tok.kind == .Hash { + name := peek_token(p) + if name.kind == .Ident && name.text == "partial" && + peek_token(p, 1).kind == .Switch { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_partial = true + } else if name.kind == .Ident && name.text == "reverse" && + peek_token(p, 1).kind == .For { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_reverse = true + } + } + #partial switch p.curr_tok.kind { case .Open_Brace, .If, .For, .Switch: label := lhs[0] @@ -3529,6 +3548,22 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case ^ast.Type_Switch_Stmt: n.label = label case ^ast.Range_Stmt: n.label = label } + + if is_partial { + #partial switch n in stmt.derived_stmt { + case ^ast.Switch_Stmt: n.partial = true + case ^ast.Type_Switch_Stmt: n.partial = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #partial switch'", partial_token.text) + } + } + if is_reverse { + #partial switch n in stmt.derived_stmt { + case ^ast.Range_Stmt: n.reverse = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #reverse for'", partial_token.text) + } + } } return stmt diff --git a/src/parser.cpp b/src/parser.cpp index eff5e0c6c..2cdcea417 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3747,8 +3747,10 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { case Ast_TypeSwitchStmt: stmt->TypeSwitchStmt.partial = true; break; + default: + syntax_error(partial_token, "Incorrect use of directive, use '%.*s: #partial switch'", LIT(ast_token(name).string)); + break; } - syntax_error(partial_token, "Incorrect use of directive, use '#partial %.*s: switch'", LIT(ast_token(name).string)); } else if (is_reverse) { switch (stmt->kind) { case Ast_RangeStmt: @@ -5176,7 +5178,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "unroll") { return parse_unrolled_for_loop(f, name); } else if (tag == "reverse") { - Ast *for_stmt = parse_for_stmt(f); + Ast *for_stmt = parse_stmt(f); if (for_stmt->kind == Ast_RangeStmt) { if (for_stmt->RangeStmt.reverse) { syntax_error(token, "#reverse already applied to a 'for in' statement"); -- cgit v1.2.3 From f1779c85dedb8bb309a9afa8cfa7e35ad727d237 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 18:50:53 +0100 Subject: Fix #3727 --- src/parser.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 2cdcea417..0cd96f5b5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2091,6 +2091,9 @@ gb_internal bool ast_on_same_line(Token const &x, Ast *yp) { gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { Ast *expr = parse_unary_expr(f, false); Ast *e = strip_or_return_expr(expr); + if (e == nullptr) { + return expr; + } if (e->kind != Ast_ProcLit && e->kind != Ast_CallExpr) { syntax_error(expr, "%.*s must be followed by a procedure literal or call, got %.*s", LIT(token.string), LIT(ast_strings[expr->kind])); return ast_bad_expr(f, token, f->curr_token); -- cgit v1.2.3 From ca481dc52d86a6856abba7481bc849a864f0183d Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:51:03 -0400 Subject: Fix displaying error on wrong line with token at EOL Previously, this would get a token on text like "\n*\n" where `*` is the token's position, and it would advance off that line. --- src/parser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 0cd96f5b5..468f4749f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -51,6 +51,12 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) u8 *line_start = pos_offset; u8 *line_end = pos_offset; + + if (offset > 0 && *line_start == '\n') { + // Prevent an error token that starts at the boundary of a line that + // leads to an empty line from advancing off its line. + line_start -= 1; + } while (line_start >= start) { if (*line_start == '\n') { line_start += 1; -- cgit v1.2.3 From 8626d38db10f134f97ea36d34ceda072a137e779 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:02:54 -0400 Subject: Fix displaying emptiness when error is on first line --- src/parser.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 468f4749f..2f764c753 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -64,6 +64,11 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) } line_start -= 1; } + if (line_start == start - 1) { + // Prevent an error on the first line from stepping behind the boundary + // of the text. + line_start += 1; + } while (line_end < end) { if (*line_end == '\n') { -- cgit v1.2.3 From dab3c832e00a3186bddc79585d9ba7ee10d39eaf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 20 Jun 2024 15:32:30 +0100 Subject: Add `#warning()` builtin compile time procedure --- core/odin/parser/parser.odin | 2 +- src/check_builtin.cpp | 20 ++++++++++++++++++++ src/parser.cpp | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 6b0aa2888..7edd509d0 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1438,7 +1438,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case: error(p, stmt.pos, "#partial can only be applied to a switch statement") } return stmt - case "assert", "panic": + case "assert", "panic", "warning": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(tag)) bd.tok = tok bd.name = name diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 47abd42cf..e85981911 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1714,6 +1714,26 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_untyped_bool; operand->mode = Addressing_Constant; + } else if (name == "warning") { + ERROR_BLOCK(); + if (ce->args.count != 1) { + error(call, "'#warning' expects 1 argument, got %td", ce->args.count); + return false; + } + if (!is_type_string(operand->type) && operand->mode != Addressing_Constant) { + gbString str = expr_to_string(ce->args[0]); + error(call, "'%s' is not a constant string", str); + gb_string_free(str); + return false; + } + warning(call, "%.*s", LIT(operand->value.value_string)); + if (c->proc_name != "") { + gbString str = type_to_string(c->curr_proc_sig); + error_line("\tCalled within '%.*s' :: %s\n", LIT(c->proc_name), str); + gb_string_free(str); + } + operand->type = t_invalid; + operand->mode = Addressing_NoValue; } else if (name == "panic") { ERROR_BLOCK(); if (ce->args.count != 1) { diff --git a/src/parser.cpp b/src/parser.cpp index 0cd96f5b5..7383c3360 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5167,7 +5167,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { break; } return s; - } else if (tag == "assert" || tag == "panic") { + } else if (tag == "assert" || tag == "panic" || tag == "warning") { Ast *t = ast_basic_directive(f, hash_token, name); Ast *stmt = ast_expr_stmt(f, parse_call_expr(f, t)); expect_semicolon(f); -- cgit v1.2.3 From c0987394840d63fae627c1623ea2661c31adc9b9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 25 Jun 2024 09:36:59 +0100 Subject: Remove `@(warning)` and `#warning(...)` --- core/odin/parser/parser.odin | 2 +- src/check_builtin.cpp | 20 -------------------- src/checker.cpp | 14 -------------- src/parser.cpp | 2 +- 4 files changed, 2 insertions(+), 36 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 7edd509d0..6b0aa2888 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1438,7 +1438,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case: error(p, stmt.pos, "#partial can only be applied to a switch statement") } return stmt - case "assert", "panic", "warning": + case "assert", "panic": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(tag)) bd.tok = tok bd.name = name diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index e85981911..47abd42cf 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1714,26 +1714,6 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_untyped_bool; operand->mode = Addressing_Constant; - } else if (name == "warning") { - ERROR_BLOCK(); - if (ce->args.count != 1) { - error(call, "'#warning' expects 1 argument, got %td", ce->args.count); - return false; - } - if (!is_type_string(operand->type) && operand->mode != Addressing_Constant) { - gbString str = expr_to_string(ce->args[0]); - error(call, "'%s' is not a constant string", str); - gb_string_free(str); - return false; - } - warning(call, "%.*s", LIT(operand->value.value_string)); - if (c->proc_name != "") { - gbString str = type_to_string(c->curr_proc_sig); - error_line("\tCalled within '%.*s' :: %s\n", LIT(c->proc_name), str); - gb_string_free(str); - } - operand->type = t_invalid; - operand->mode = Addressing_NoValue; } else if (name == "panic") { ERROR_BLOCK(); if (ce->args.count != 1) { diff --git a/src/checker.cpp b/src/checker.cpp index 4945b3810..fdf5a8b7d 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3493,20 +3493,6 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; - } else if (name == "warning") { - ExactValue ev = check_decl_attribute_value(c, value); - - if (ev.kind == ExactValue_String) { - String msg = ev.value_string; - if (msg.len == 0) { - error(elem, "Warning message cannot be an empty string"); - } else { - ac->warning_message = msg; - } - } else { - error(elem, "Expected a string value for '%.*s'", LIT(name)); - } - return true; } else if (name == "require_results") { if (value != nullptr) { error(elem, "Expected no value for '%.*s'", LIT(name)); diff --git a/src/parser.cpp b/src/parser.cpp index 7383c3360..0cd96f5b5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5167,7 +5167,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { break; } return s; - } else if (tag == "assert" || tag == "panic" || tag == "warning") { + } else if (tag == "assert" || tag == "panic") { Ast *t = ast_basic_directive(f, hash_token, name); Ast *stmt = ast_expr_stmt(f, parse_call_expr(f, t)); expect_semicolon(f); -- cgit v1.2.3 From 862a04376f589b23c34700d8e7c746048741e19f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 28 Jun 2024 09:16:01 +0100 Subject: Improve tokenizing wrong number literals --- src/parser.cpp | 12 +++++++++++- src/tokenizer.cpp | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 0cd96f5b5..0364e2c2b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -718,7 +718,17 @@ gb_internal ExactValue exact_value_from_token(AstFile *f, Token const &token) { } ExactValue value = exact_value_from_basic_literal(token.kind, s); if (value.kind == ExactValue_Invalid) { - syntax_error(token, "Invalid token literal"); + switch (token.kind) { + case Token_Integer: + syntax_error(token, "Invalid integer literal"); + break; + case Token_Float: + syntax_error(token, "Invalid float literal"); + break; + default: + syntax_error(token, "Invalid token literal"); + break; + } } return value; } diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index f7751d840..1a37043d7 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -433,6 +433,7 @@ gb_internal gb_inline i32 digit_value(Rune r) { } gb_internal gb_inline void scan_mantissa(Tokenizer *t, i32 base) { + base = 16; // always check for any possible letter while (digit_value(t->curr_rune) < base || t->curr_rune == '_') { advance_to_next_rune(t); } -- cgit v1.2.3 From 67e9a6fd9bedf92498f41aa206d042566ad43595 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 28 Jun 2024 10:04:08 +0100 Subject: Improve error reporting on "Failed to parse fail" and show the line error if possible --- src/parser.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 0364e2c2b..0e971f792 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -35,18 +35,38 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (file == nullptr) { return nullptr; } - isize offset = pos.offset; - u8 *start = file->tokenizer.start; u8 *end = file->tokenizer.end; if (start == end) { return nullptr; } + + isize offset = pos.offset; + if (pos.line != 0 && offset == 0) { + for (i32 i = 1; i < pos.line; i++) { + while (start+offset < end) { + u8 c = start[offset++]; + if (c == '\n') { + break; + } + } + } + for (i32 i = 1; i < pos.column; i++) { + u8 *ptr = start+offset; + u8 c = *ptr; + if (c & 0x80) { + offset += utf8_decode(ptr, end-ptr, nullptr); + } else { + offset++; + } + } + } + + isize len = end-start; if (len < offset) { return nullptr; } - u8 *pos_offset = start+offset; u8 *line_start = pos_offset; @@ -70,6 +90,7 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (offset_) *offset_ = cast(i32)(pos_offset - the_line.text); + return gb_string_make_length(heap_allocator(), the_line.text, the_line.len); } @@ -5417,6 +5438,7 @@ gb_internal WORKER_TASK_PROC(parser_worker_proc) { gb_internal void parser_add_file_to_process(Parser *p, AstPackage *pkg, FileInfo fi, TokenPos pos) { ImportedFile f = {pkg, fi, pos, p->file_to_process_count++}; + f.pos.file_id = cast(i32)(f.index+1); auto wd = gb_alloc_item(permanent_allocator(), ParserWorkerData); wd->parser = p; wd->imported_file = f; @@ -5453,6 +5475,7 @@ gb_internal WORKER_TASK_PROC(foreign_file_worker_proc) { gb_internal void parser_add_foreign_file_to_process(Parser *p, AstPackage *pkg, AstForeignFileKind kind, FileInfo fi, TokenPos pos) { // TODO(bill): Use a better allocator ImportedFile f = {pkg, fi, pos, p->file_to_process_count++}; + f.pos.file_id = cast(i32)(f.index+1); auto wd = gb_alloc_item(permanent_allocator(), ForeignFileWorkerData); wd->parser = p; wd->imported_file = f; -- cgit v1.2.3 From 906afa41544c692f65467f1dba546a6ca4e71f1d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 29 Jun 2024 10:13:15 +0100 Subject: Allow for `when x in y {` (minor oversight in syntax) --- core/odin/parser/parser.odin | 5 +++++ src/parser.cpp | 3 +++ 2 files changed, 8 insertions(+) (limited to 'src/parser.cpp') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 03fb4d66d..715b96d84 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -689,7 +689,12 @@ parse_when_stmt :: proc(p: ^Parser) -> ^ast.When_Stmt { prev_level := p.expr_level p.expr_level = -1 + prev_allow_in_expr := p.allow_in_expr + p.allow_in_expr = true + cond = parse_expr(p, false) + + p.allow_in_expr = prev_allow_in_expr p.expr_level = prev_level if cond == nil { diff --git a/src/parser.cpp b/src/parser.cpp index 0e971f792..b1a179573 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4573,9 +4573,12 @@ gb_internal Ast *parse_when_stmt(AstFile *f) { isize prev_level = f->expr_level; f->expr_level = -1; + bool prev_allow_in_expr = f->allow_in_expr; + f->allow_in_expr = true; cond = parse_expr(f, false); + f->allow_in_expr = prev_allow_in_expr; f->expr_level = prev_level; if (cond == nullptr) { -- cgit v1.2.3 From 2187f3e7ff076ea6375b1a09e32908a00e479dc8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 29 Jun 2024 19:14:24 +0100 Subject: `-strict-style` enforce 1TBS (mostly) --- src/parser.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index b1a179573..9b27ae156 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1486,7 +1486,7 @@ gb_internal bool skip_possible_newline(AstFile *f) { return false; } -gb_internal bool skip_possible_newline_for_literal(AstFile *f) { +gb_internal bool skip_possible_newline_for_literal(AstFile *f, bool seen_where=false) { Token curr = f->curr_token; if (token_is_newline(curr)) { Token next = peek_token(f); @@ -1494,6 +1494,10 @@ gb_internal bool skip_possible_newline_for_literal(AstFile *f) { switch (next.kind) { case Token_OpenBrace: case Token_else: + if (build_context.strict_style && !seen_where) { + syntax_error(next, "With '-strict-style' the attached brace style (1TBS) is enforced"); + } + /*fallthrough*/ case Token_where: advance_token(f); return true; @@ -2517,7 +2521,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { return type; } - skip_possible_newline_for_literal(f); + skip_possible_newline_for_literal(f, where_token.kind == Token_where); if (allow_token(f, Token_Uninit)) { if (where_token.kind != Token_Invalid) { @@ -4499,6 +4503,9 @@ gb_internal bool parse_control_statement_semicolon_separator(AstFile *f) { } + + + gb_internal Ast *parse_if_stmt(AstFile *f) { if (f->curr_proc == nullptr) { syntax_error(f->curr_token, "You cannot use an if statement in the file scope"); -- cgit v1.2.3 From b1a1da6618ce8ea5ffc8b7fca520895f01d36929 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 29 Jun 2024 19:54:31 +0100 Subject: Add `-vet-tabs` --- src/build_settings.cpp | 3 +++ src/main.cpp | 7 +++++++ src/parser.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index d79343a8b..c3b4f2506 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -282,6 +282,7 @@ enum VetFlags : u64 { VetFlag_UnusedImports = 1u<<6, VetFlag_Deprecated = 1u<<7, VetFlag_Cast = 1u<<8, + VetFlag_Tabs = 1u<<9, VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, @@ -311,6 +312,8 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_Deprecated; } else if (name == "cast") { return VetFlag_Cast; + } else if (name == "tabs") { + return VetFlag_Tabs; } return VetFlag_NONE; } diff --git a/src/main.cpp b/src/main.cpp index ea82651f5..53e631bb0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -301,6 +301,7 @@ enum BuildFlagKind { BuildFlag_VetStyle, BuildFlag_VetSemicolon, BuildFlag_VetCast, + BuildFlag_VetTabs, BuildFlag_CustomAttribute, BuildFlag_IgnoreUnknownAttributes, @@ -502,6 +503,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_VetStyle, str_lit("vet-style"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetSemicolon, str_lit("vet-semicolon"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetCast, str_lit("vet-cast"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetTabs, str_lit("vet-tabs"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_CustomAttribute, str_lit("custom-attribute"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); @@ -1157,6 +1159,7 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_VetStyle: build_context.vet_flags |= VetFlag_Style; break; case BuildFlag_VetSemicolon: build_context.vet_flags |= VetFlag_Semicolon; break; case BuildFlag_VetCast: build_context.vet_flags |= VetFlag_Cast; break; + case BuildFlag_VetTabs: build_context.vet_flags |= VetFlag_Tabs; break; case BuildFlag_CustomAttribute: { @@ -2256,6 +2259,10 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-vet-cast"); print_usage_line(2, "Errs on casting a value to its own type or using `transmute` rather than `cast`."); print_usage_line(0, ""); + + print_usage_line(1, "-vet-tabs"); + print_usage_line(2, "Errs when the use of tabs has not been used for indentation."); + print_usage_line(0, ""); } if (check) { diff --git a/src/parser.cpp b/src/parser.cpp index 9b27ae156..51dc03085 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5286,12 +5286,53 @@ gb_internal Ast *parse_stmt(AstFile *f) { return ast_bad_stmt(f, token, f->curr_token); } + + +gb_internal u64 check_vet_flags(AstFile *file) { + if (file && file->vet_flags_set) { + return file->vet_flags; + } + return build_context.vet_flags; +} + + +gb_internal void parse_enforce_tabs(AstFile *f) { + Token prev = f->prev_token; + Token curr = f->curr_token; + if (prev.pos.line < curr.pos.line) { + u8 *start = f->tokenizer.start+prev.pos.offset; + u8 *end = f->tokenizer.start+curr.pos.offset; + u8 *it = end; + while (it > start) { + if (*it == '\n') { + it++; + break; + } + it--; + } + + isize len = end-it; + for (isize i = 0; i < len; i++) { + if (it[i] == ' ') { + syntax_error(curr, "With '-vet-tabs', tabs must be used for indentation"); + break; + } + } + } +} + gb_internal Array parse_stmt_list(AstFile *f) { auto list = array_make(ast_allocator(f)); while (f->curr_token.kind != Token_case && f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { + + // Checks to see if tabs have been used for indentation + if (check_vet_flags(f) & VetFlag_Tabs) { + parse_enforce_tabs(f); + } + Ast *stmt = parse_stmt(f); if (stmt && stmt->kind != Ast_EmptyStmt) { array_add(&list, stmt); -- cgit v1.2.3 From 34fce83d668f1f1754e88c29a8f0e3df71c60057 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 29 Jun 2024 20:04:34 +0100 Subject: Improve `-strict-style` rules for `if-else` statements --- src/parser.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 51dc03085..583f4a57d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1486,7 +1486,7 @@ gb_internal bool skip_possible_newline(AstFile *f) { return false; } -gb_internal bool skip_possible_newline_for_literal(AstFile *f, bool seen_where=false) { +gb_internal bool skip_possible_newline_for_literal(AstFile *f, bool ignore_strict_style=false) { Token curr = f->curr_token; if (token_is_newline(curr)) { Token next = peek_token(f); @@ -1494,7 +1494,7 @@ gb_internal bool skip_possible_newline_for_literal(AstFile *f, bool seen_where=f switch (next.kind) { case Token_OpenBrace: case Token_else: - if (build_context.strict_style && !seen_where) { + if (build_context.strict_style && !ignore_strict_style) { syntax_error(next, "With '-strict-style' the attached brace style (1TBS) is enforced"); } /*fallthrough*/ @@ -4548,7 +4548,11 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { body = parse_block_stmt(f, false); } - skip_possible_newline_for_literal(f); + bool ignore_strict_style = false; + if (token.pos.line == ast_end_token(body).pos.line) { + ignore_strict_style = true; + } + skip_possible_newline_for_literal(f, ignore_strict_style); if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { @@ -4600,7 +4604,11 @@ gb_internal Ast *parse_when_stmt(AstFile *f) { body = parse_block_stmt(f, true); } - skip_possible_newline_for_literal(f); + bool ignore_strict_style = false; + if (token.pos.line == ast_end_token(body).pos.line) { + ignore_strict_style = true; + } + skip_possible_newline_for_literal(f, ignore_strict_style); if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { -- cgit v1.2.3 From 6f1cc8071c3ff49c5431cc8ad078d12883f91545 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 2 Jul 2024 15:28:08 +0200 Subject: wasm: add foreign import and linking of wasm object files --- src/build_settings.cpp | 9 --------- src/check_decl.cpp | 7 +++++-- src/checker.cpp | 3 +-- src/linker.cpp | 14 ++++++++++++++ src/llvm_backend_utility.cpp | 6 +++++- src/parser.cpp | 3 +-- 6 files changed, 26 insertions(+), 16 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c3b4f2506..9c93a5b69 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -860,15 +860,6 @@ gb_internal bool is_arch_x86(void) { return false; } -gb_internal bool allow_check_foreign_filepath(void) { - switch (build_context.metrics.arch) { - case TargetArch_wasm32: - case TargetArch_wasm64p32: - return false; - } - return true; -} - // TODO(bill): OS dependent versions for the BuildContext // join_path // is_dir diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 883cfcba9..3c4a4b3de 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1178,9 +1178,12 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (foreign_library->LibraryName.paths.count >= 1) { module_name = foreign_library->LibraryName.paths[0]; } - name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + + if (!string_ends_with(module_name, str_lit(".o"))) { + name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + } } - + e->Procedure.is_foreign = true; e->Procedure.link_name = name; diff --git a/src/checker.cpp b/src/checker.cpp index 734659510..c3d2ae5eb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5000,9 +5000,8 @@ gb_internal void check_foreign_import_fullpaths(Checker *c) { String file_str = op.value.value_string; file_str = string_trim_whitespace(file_str); - String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(file_str, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); if (ok) { diff --git a/src/linker.cpp b/src/linker.cpp index 371736743..34c0af7e5 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -85,6 +85,20 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (extra_linker_flags.len != 0) { lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); } + + for_array(i, e->LibraryName.paths) { + String lib = e->LibraryName.paths[i]; + + if (lib.len == 0) { + continue; + } + + if (!string_ends_with(lib, str_lit(".o"))) { + continue; + } + + inputs = gb_string_append_fmt(inputs, " \"%.*s\"", LIT(lib)); + } } if (build_context.metrics.os == TargetOs_orca) { diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 98ed0c57e..1165476be 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2029,7 +2029,11 @@ gb_internal void lb_set_wasm_procedure_import_attributes(LLVMValueRef value, Ent GB_ASSERT(foreign_library->LibraryName.paths.count == 1); module_name = foreign_library->LibraryName.paths[0]; - + + if (string_ends_with(module_name, str_lit(".o"))) { + return; + } + if (string_starts_with(import_name, module_name)) { import_name = substring(import_name, module_name.len+WASM_MODULE_NAME_SEPARATOR.len, import_name.len); } diff --git a/src/parser.cpp b/src/parser.cpp index 583f4a57d..997d32fa5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5813,7 +5813,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node return false; } - if (collection_name.len > 0) { // NOTE(bill): `base:runtime` == `core:runtime` if (collection_name == "core") { @@ -5968,7 +5967,7 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas Token fp_token = fp->BasicLit.token; String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); String fullpath = file_str; - if (allow_check_foreign_filepath()) { + if (!is_arch_wasm() || string_ends_with(fullpath, str_lit(".o"))) { String foreign_path = {}; bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); if (!ok) { -- cgit v1.2.3 From 1eb0bc1408735401a2ba6fdfbc2dd27726be5137 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 4 Jul 2024 14:43:57 +0100 Subject: Remove `*_test.odin`; always compile it for all targets --- src/parser.cpp | 7 ------- src/parser.hpp | 1 - 2 files changed, 8 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 37f9c35de..548e46cbe 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6342,8 +6342,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } else if (lc == "+lazy") { if (build_context.ignore_lazy) { // Ignore - } else if (f->flags & AstFile_IsTest) { - // Ignore } else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) { // Ignore } else { @@ -6461,11 +6459,6 @@ gb_internal ParseFileError process_imported_file(Parser *p, ImportedFile importe if (build_context.command_kind == Command_test) { String name = file->fullpath; name = remove_extension_from_path(name); - - String test_suffix = str_lit("_test"); - if (string_ends_with(name, test_suffix) && name != test_suffix) { - file->flags |= AstFile_IsTest; - } } diff --git a/src/parser.hpp b/src/parser.hpp index 02f2af28d..521fd7a37 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -74,7 +74,6 @@ enum AstFileFlag : u32 { AstFile_IsPrivatePkg = 1<<0, AstFile_IsPrivateFile = 1<<1, - AstFile_IsTest = 1<<3, AstFile_IsLazy = 1<<4, AstFile_NoInstrumentation = 1<<5, -- cgit v1.2.3 From 886ee66e7fcabbd09c20fd55d98051e3854dfd76 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 9 Jul 2024 14:16:56 +0100 Subject: Cache files, env, and args --- src/build_settings.cpp | 7 +- src/cached.cpp | 170 +++++++++++++++++++++++++++++++++++++++++-------- src/main.cpp | 13 +++- src/parser.cpp | 10 +++ src/parser.hpp | 4 ++ 5 files changed, 175 insertions(+), 29 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index be896f6fa..28ca0f088 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -329,7 +329,12 @@ enum SanitizerFlags : u32 { struct BuildCacheData { u64 crc; String cache_dir; - String manifest_path; + + // manifests + String files_path; + String args_path; + String env_path; + bool copy_already_done; }; diff --git a/src/cached.cpp b/src/cached.cpp index ab6d99848..da72e9989 100644 --- a/src/cached.cpp +++ b/src/cached.cpp @@ -1,4 +1,4 @@ -gb_internal GB_COMPARE_PROC(cached_file_cmp) { +gb_internal GB_COMPARE_PROC(string_cmp) { String const &x = *(String *)a; String const &y = *(String *)b; return string_compare(x, y); @@ -182,7 +182,9 @@ bool try_copy_executable_from_cache(void) { // returns false if different, true if it is the same -bool try_cached_build(Checker *c) { +bool try_cached_build(Checker *c, Array const &args) { + TEMPORARY_ALLOCATOR_GUARD(); + Parser *p = c->parser; auto files = array_make(heap_allocator()); @@ -200,7 +202,7 @@ bool try_cached_build(Checker *c) { array_add(&files, cache->path); } - array_sort(files, cached_file_cmp); + array_sort(files, string_cmp); u64 crc = 0; for (String const &path : files) { @@ -213,28 +215,58 @@ bool try_cached_build(Checker *c) { gbString crc_str = gb_string_make_reserve(permanent_allocator(), 16); crc_str = gb_string_append_fmt(crc_str, "%016llx", crc); - String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); - String manifest_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("odin.manifest")); + String cache_dir = concatenate3_strings(permanent_allocator(), base_cache_dir, str_lit("/"), make_string_c(crc_str)); + String files_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("files.manifest")); + String args_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("args.manifest")); + String env_path = concatenate3_strings(permanent_allocator(), cache_dir, str_lit("/"), str_lit("env.manifest")); build_context.build_cache_data.cache_dir = cache_dir; - build_context.build_cache_data.manifest_path = manifest_path; + build_context.build_cache_data.files_path = files_path; + build_context.build_cache_data.args_path = args_path; + build_context.build_cache_data.env_path = env_path; + + auto envs = array_make(heap_allocator()); + defer (array_free(&envs)); + { + #if defined(GB_SYSTEM_WINDOWS) + wchar_t *strings = GetEnvironmentStringsW(); + defer (FreeEnvironmentStringsW(strings)); + + wchar_t *curr_string = strings; + while (curr_string && *curr_string) { + String16 wstr = make_string16_c(curr_string); + curr_string += wstr.len+1; + String str = string16_to_string(temporary_allocator(), wstr); + array_add(&envs, str); + } + #endif + } + array_sort(envs, string_cmp); if (check_if_exists_directory_otherwise_create(cache_dir)) { - goto do_write_file; + goto write_cache; } - if (check_if_exists_file_otherwise_create(manifest_path)) { - goto do_write_file; - } else { + if (check_if_exists_file_otherwise_create(files_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(args_path)) { + goto write_cache; + } + if (check_if_exists_file_otherwise_create(env_path)) { + goto write_cache; + } + + { // exists already LoadedFile loaded_file = {}; LoadedFileError file_err = load_file_32( - alloc_cstring(temporary_allocator(), manifest_path), + alloc_cstring(temporary_allocator(), files_path), &loaded_file, false ); - if (file_err) { + if (file_err > LoadedFile_Empty) { return false; } @@ -250,7 +282,7 @@ bool try_cached_build(Checker *c) { } isize sep = string_index_byte(line, ' '); if (sep < 0) { - goto do_write_file; + goto write_cache; } String timestamp_str = substring(line, 0, sep); @@ -260,43 +292,131 @@ bool try_cached_build(Checker *c) { path_str = string_trim_whitespace(path_str); if (file_count >= files.count) { - goto do_write_file; + goto write_cache; } if (files[file_count] != path_str) { - goto do_write_file; + goto write_cache; } u64 timestamp = exact_value_to_u64(exact_value_integer_from_string(timestamp_str)); gbFileTime last_write_time = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path_str)); if (last_write_time != timestamp) { - goto do_write_file; + goto write_cache; } } if (file_count != files.count) { - goto do_write_file; + goto write_cache; } + } + { + LoadedFile loaded_file = {}; - goto try_copy_executable; + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), args_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize args_count = 0; + + for (; it.pos < data.len; args_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (args_count >= args.count) { + goto write_cache; + } + + if (line != args[args_count]) { + goto write_cache; + } + } } + { + LoadedFile loaded_file = {}; -do_write_file:; + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), env_path), + &loaded_file, + false + ); + if (file_err > LoadedFile_Empty) { + return false; + } + + String data = {cast(u8 *)loaded_file.data, loaded_file.size}; + String_Iterator it = {data, 0}; + + isize env_count = 0; + + for (; it.pos < data.len; env_count++) { + String line = string_split_iterator(&it, '\n'); + line = string_trim_whitespace(line); + if (line.len == 0) { + break; + } + if (env_count >= envs.count) { + goto write_cache; + } + + if (line != envs[env_count]) { + goto write_cache; + } + } + } + + return try_copy_executable_from_cache(); + +write_cache:; { - char const *manifest_path_c = alloc_cstring(temporary_allocator(), manifest_path); - gb_file_remove(manifest_path_c); + char const *path_c = alloc_cstring(temporary_allocator(), files_path); + gb_file_remove(path_c); gbFile f = {}; defer (gb_file_close(&f)); - gb_file_open_mode(&f, gbFileMode_Write, manifest_path_c); + gb_file_open_mode(&f, gbFileMode_Write, path_c); for (String const &path : files) { gbFileTime ft = gb_file_last_write_time(alloc_cstring(temporary_allocator(), path)); gb_fprintf(&f, "%llu %.*s\n", cast(unsigned long long)ft, LIT(path)); } - return false; } + { + char const *path_c = alloc_cstring(temporary_allocator(), args_path); + gb_file_remove(path_c); -try_copy_executable:; - return try_copy_executable_from_cache(); + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &arg : args) { + String targ = string_trim_whitespace(arg); + gb_fprintf(&f, "%.*s\n", LIT(targ)); + } + } + { + char const *path_c = alloc_cstring(temporary_allocator(), env_path); + gb_file_remove(path_c); + + gbFile f = {}; + defer (gb_file_close(&f)); + gb_file_open_mode(&f, gbFileMode_Write, path_c); + + for (String const &env : envs) { + gb_fprintf(&f, "%.*s\n", LIT(env)); + } + } + + + return false; } diff --git a/src/main.cpp b/src/main.cpp index 49c34014b..f2312f248 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3271,12 +3271,19 @@ int main(int arg_count, char const **arg_ptr) { print_all_errors(); } - MAIN_TIME_SECTION("type check"); checker->parser = parser; init_checker(checker); - defer (destroy_checker(checker)); + defer (destroy_checker(checker)); // this is here because of a `goto` + + if (build_context.cached && parser->total_seen_load_directive_count.load() == 0) { + MAIN_TIME_SECTION("check cached build (pre-semantic check)"); + if (try_cached_build(checker, args)) { + goto end_of_code_gen; + } + } + MAIN_TIME_SECTION("type check"); check_parsed_files(checker); check_defines(&build_context, checker); if (any_errors()) { @@ -3331,7 +3338,7 @@ int main(int arg_count, char const **arg_ptr) { if (build_context.cached) { MAIN_TIME_SECTION("check cached build"); - if (try_cached_build(checker)) { + if (try_cached_build(checker, args)) { goto end_of_code_gen; } } diff --git a/src/parser.cpp b/src/parser.cpp index 548e46cbe..eb012e980 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -787,6 +787,9 @@ gb_internal Ast *ast_basic_directive(AstFile *f, Token token, Token name) { Ast *result = alloc_ast_node(f, Ast_BasicDirective); result->BasicDirective.token = token; result->BasicDirective.name = name; + if (string_starts_with(name.string, str_lit("load"))) { + f->seen_load_directive_count++; + } return result; } @@ -6576,6 +6579,13 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } } } + + for (AstPackage *pkg : p->packages) { + for (AstFile *file : pkg->files) { + p->total_seen_load_directive_count += file->seen_load_directive_count; + } + } + return ParseFile_None; } diff --git a/src/parser.hpp b/src/parser.hpp index 521fd7a37..86b3393af 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -140,6 +140,8 @@ struct AstFile { // This is effectively a queue but does not require any multi-threading capabilities Array delayed_decls_queues[AstDelayQueue_COUNT]; + std::atomic seen_load_directive_count; + #define PARSER_MAX_FIX_COUNT 6 isize fix_count; TokenPos fix_prev_pos; @@ -210,6 +212,8 @@ struct Parser { std::atomic total_token_count; std::atomic total_line_count; + std::atomic total_seen_load_directive_count; + // TODO(bill): What should this mutex be per? // * Parser // * Package -- cgit v1.2.3 From 36301d03595a7829e2e2e0b6483650887d6016ec Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Jul 2024 13:03:38 +0100 Subject: Give better syntax error messages for things like `#define Example 123` --- src/parser.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index eb012e980..9ce3d563d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3138,7 +3138,7 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) { Ast *call = ast_call_expr(f, operand, args, open_paren, close_paren, ellipsis); Ast *o = unparen_expr(operand); - if (o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { + if (o && o->kind == Ast_SelectorExpr && o->SelectorExpr.token.kind == Token_ArrowRight) { return ast_selector_call_expr(f, o->SelectorExpr.token, o, call); } @@ -5257,6 +5257,38 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "include") { syntax_error(token, "#include is not a valid import declaration kind. Did you mean 'import'?"); s = ast_bad_stmt(f, token, f->curr_token); + } else if (tag == "define") { + s = ast_bad_stmt(f, token, f->curr_token); + + if (name.pos.line == f->curr_token.pos.line) { + bool call_like = false; + Ast *macro_expr = nullptr; + Token ident = f->curr_token; + if (allow_token(f, Token_Ident) && + name.pos.line == f->curr_token.pos.line) { + if (f->curr_token.kind == Token_OpenParen && f->curr_token.pos.column == ident.pos.column+ident.string.len) { + call_like = true; + (void)parse_call_expr(f, nullptr); + } + + if (name.pos.line == f->curr_token.pos.line && f->curr_token.kind != Token_Semicolon) { + macro_expr = parse_expr(f, false); + } + } + + ERROR_BLOCK(); + syntax_error(ident, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + if (macro_expr == nullptr || call_like) { + error_line("\tNote: Odin does not support macros\n"); + } else { + gbString s = expr_to_string(macro_expr); + error_line("\tSuggestion: Did you mean '%.*s :: %s'?\n", LIT(ident.string), s); + gb_string_free(s); + } + } else { + syntax_error(token, "#define is not a valid declaration, Odin does not have a C-like preprocessor."); + } + } else { syntax_error(token, "Unknown tag directive used: '%.*s'", LIT(tag)); s = ast_bad_stmt(f, token, f->curr_token); -- cgit v1.2.3 From edc793d7c123a38826860ef72684308902a7012c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 11:39:05 +0100 Subject: Add `#no_capture args: ..T` to reuse the backing array stack memory --- core/fmt/fmt.odin | 58 +++++++++++++++++++++++------------------------ core/fmt/fmt_js.odin | 24 ++++++++++---------- core/fmt/fmt_os.odin | 24 ++++++++++---------- src/check_expr.cpp | 17 ++++++++++++++ src/check_type.cpp | 17 ++++++++++++++ src/checker.cpp | 1 + src/checker.hpp | 7 ++++++ src/entity.cpp | 2 +- src/llvm_backend.hpp | 7 ++++++ src/llvm_backend_proc.cpp | 27 +++++++++++++++++++++- src/parser.cpp | 1 + src/parser.hpp | 8 +++++-- 12 files changed, 136 insertions(+), 57 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 234f4afbd..e56211346 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -125,7 +125,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 { +aprint :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprint(&str, ..args, sep=sep) @@ -141,7 +141,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 { +aprintln :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintln(&str, ..args, sep=sep) @@ -158,7 +158,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 { +aprintf :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -174,7 +174,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 { +aprintfln :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator) -> string { return aprintf(fmt, ..args, allocator=allocator, newline=true) } // Creates a formatted string @@ -188,7 +188,7 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s // Returns: A formatted string. // @(require_results) -tprint :: proc(args: ..any, sep := " ") -> string { +tprint :: proc(#no_capture args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprint(&str, ..args, sep=sep) @@ -204,7 +204,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 { +tprintln :: proc(#no_capture args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintln(&str, ..args, sep=sep) @@ -221,7 +221,7 @@ tprintln :: proc(args: ..any, sep := " ") -> string { // Returns: A formatted string. // @(require_results) -tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { +tprintf :: proc(fmt: string, #no_capture args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -237,7 +237,7 @@ tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { // Returns: A formatted string. // @(require_results) -tprintfln :: proc(fmt: string, args: ..any) -> string { +tprintfln :: proc(fmt: string, #no_capture args: ..any) -> string { return tprintf(fmt, ..args, newline=true) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. @@ -249,7 +249,7 @@ tprintfln :: proc(fmt: string, args: ..any) -> string { // // Returns: A formatted string // -bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { +bprint :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprint(&sb, ..args, sep=sep) } @@ -262,7 +262,7 @@ bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { // // Returns: A formatted string with a newline character at the end // -bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { +bprintln :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprintln(&sb, ..args, sep=sep) } @@ -276,7 +276,7 @@ bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { // // Returns: A formatted string // -bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> string { +bprintf :: proc(buf: []byte, fmt: string, #no_capture args: ..any, newline := false) -> string { sb := strings.builder_from_bytes(buf) return sbprintf(&sb, fmt, ..args, newline=newline) } @@ -289,7 +289,7 @@ bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> stri // // Returns: A formatted string // -bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { +bprintfln :: proc(buf: []byte, fmt: string, #no_capture args: ..any) -> string { return bprintf(buf, fmt, ..args, newline=true) } // Runtime assertion with a formatted message @@ -301,14 +301,14 @@ bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { // - loc: The location of the caller // @(disabled=ODIN_DISABLE_ASSERT) -assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { +assertf :: proc(condition: bool, fmt: string, #no_capture args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) - internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, #no_capture args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -326,7 +326,7 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati // - args: A variadic list of arguments to be formatted // - loc: The location of the caller // -panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { +panicf :: proc(fmt: string, #no_capture args: ..any, loc := #caller_location) -> ! { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -346,7 +346,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 { +caprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str) sbprintf(&str, format, ..args, newline=newline) @@ -365,7 +365,7 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -caprintfln :: proc(format: string, args: ..any) -> cstring { +caprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { return caprintf(format, ..args, newline=true) } // Creates a formatted C string @@ -379,7 +379,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // Returns: A formatted C string. // @(require_results) -ctprint :: proc(args: ..any, sep := " ") -> cstring { +ctprint :: proc(#no_capture args: ..any, sep := " ") -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprint(&str, ..args, sep=sep) @@ -399,7 +399,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring { // Returns: A formatted C string // @(require_results) -ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { +ctprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprintf(&str, format, ..args, newline=newline) @@ -418,7 +418,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -ctprintfln :: proc(format: string, args: ..any) -> cstring { +ctprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { return ctprintf(format, ..args, newline=true) } // Formats using the default print settings and writes to the given strings.Builder @@ -430,7 +430,7 @@ ctprintfln :: proc(format: string, args: ..any) -> cstring { // // Returns: A formatted string // -sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { +sbprint :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { wprint(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -443,7 +443,7 @@ sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // // Returns: The resulting formatted string // -sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { +sbprintln :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { wprintln(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -457,7 +457,7 @@ sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // // Returns: The resulting formatted string // -sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := false) -> string { +sbprintf :: proc(buf: ^strings.Builder, fmt: string, #no_capture args: ..any, newline := false) -> string { wprintf(strings.to_writer(buf), fmt, ..args, flush=true, newline=newline) return strings.to_string(buf^) } @@ -469,7 +469,7 @@ sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := fal // // Returns: A formatted string // -sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string { +sbprintfln :: proc(buf: ^strings.Builder, format: string, #no_capture args: ..any) -> string { return sbprintf(buf, format, ..args, newline=true) } // Formats and writes to an io.Writer using the default print settings @@ -481,7 +481,7 @@ sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string // // Returns: The number of bytes written // -wprint :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { +wprint :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -522,7 +522,7 @@ wprint :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // // Returns: The number of bytes written // -wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { +wprintln :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -549,11 +549,11 @@ wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // // Returns: The number of bytes written // -wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline := false) -> int { +wprintf :: proc(w: io.Writer, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { MAX_CHECKED_ARGS :: 64 assert(len(args) <= MAX_CHECKED_ARGS, "number of args > 64 is unsupported") - parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], args: ..any) -> int { + parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], #no_capture args: ..any) -> int { i := index // Prefix @@ -809,7 +809,7 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline : // // Returns: The number of bytes written. // -wprintfln :: proc(w: io.Writer, format: string, args: ..any, flush := true) -> int { +wprintfln :: proc(w: io.Writer, format: string, #no_capture args: ..any, flush := true) -> int { return wprintf(w, format, ..args, flush=flush, newline=true) } // Writes a ^runtime.Type_Info value to an io.Writer diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index acf218eb5..4389b8d87 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -43,7 +43,7 @@ fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { } // 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 { +fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -54,7 +54,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #ca } // 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 { +fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -66,7 +66,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := # } // 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 { +fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -78,24 +78,24 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, 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 { +fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture 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) } +print :: proc(#no_capture 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 -println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } +println :: proc(#no_capture 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) } +printf :: proc(fmt: string, #no_capture 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) } +printfln :: proc(fmt: string, #no_capture 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) } +eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } +eprintln :: proc(#no_capture 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) } +eprintf :: proc(fmt: string, #no_capture 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) } +eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 9de0d43be..538f7a08b 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -9,7 +9,7 @@ import "core:io" import "core:bufio" // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -20,7 +20,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -31,7 +31,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { 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) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -42,7 +42,7 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline 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) -> int { +fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(fd, fmt, ..args, flush=flush, newline=true) } fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { @@ -67,19 +67,19 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: } // print formats using the default print settings and writes to os.stdout -print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } +print :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } // println formats using the default print settings and writes to os.stdout -println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } +println :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } // printf formats according to the specified format string and writes to os.stdout -printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to os.stdout, followed by a newline. -printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to os.stderr -eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } +eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to os.stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } +eprintln :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } // eprintf formats according to the specified format string and writes to os.stderr -eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to os.stderr, followed by a newline. -eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 12acca0cb..645d8ac5a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6033,6 +6033,23 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; + + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameter that use `#no_capture` + // on the variadic parameter + if (c->decl && (vt->flags & EntityFlag_NoCapture)) { + bool found = false; + for (NoCaptureData &nc : c->decl->no_captures) { + if (are_types_identical(vt->type, nc.slice_type)) { + nc.max_count = gb_max(nc.max_count, variadic_operands.count); + found = true; + break; + } + } + if (!found) { + array_add(&c->decl->no_captures, NoCaptureData{vt->type, variadic_operands.count}); + } + } + } else { dummy_argument_count += 1; o.type = t_untyped_nil; diff --git a/src/check_type.cpp b/src/check_type.cpp index dd8559114..d1c9bb381 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1953,6 +1953,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable variadic fields"); + p->flags &= ~FieldFlag_no_capture; + } param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved); param->TypeName.is_type_alias = true; @@ -2054,6 +2058,15 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para p->flags &= ~FieldFlag_by_ptr; // Remove the flag } } + if (p->flags&FieldFlag_no_capture) { + if (!(is_variadic && variadic_index == variables.count)) { + error(name, "'#no_capture' can only be applied to a variadic parameter"); + p->flags &= ~FieldFlag_no_capture; + } else if (p->flags & FieldFlag_c_vararg) { + error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); + p->flags &= ~FieldFlag_no_capture; + } + } if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { @@ -2115,6 +2128,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_by_ptr) { param->flags |= EntityFlag_ByPtr; } + if (p->flags&FieldFlag_no_capture) { + param->flags |= EntityFlag_NoCapture; + } + param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it add_entity(ctx, scope, name, param); diff --git a/src/checker.cpp b/src/checker.cpp index 8756cce1a..abacc13cb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -184,6 +184,7 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->deps, 0); ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); + d->no_captures.allocator = heap_allocator(); } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { diff --git a/src/checker.hpp b/src/checker.hpp index 781737140..17722f6b6 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { "Checked", }; +struct NoCaptureData { + Type *slice_type; // ..elem_type + isize max_count; +}; + // DeclInfo is used to store information of certain declarations to allow for "any order" usage struct DeclInfo { DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment @@ -219,6 +224,8 @@ struct DeclInfo { Array labels; + Array no_captures; + // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; }; diff --git a/src/entity.cpp b/src/entity.cpp index 41d84e0f7..db6ffdd52 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,7 +45,7 @@ enum EntityFlag : u64 { EntityFlag_Value = 1ull<<11, EntityFlag_BitFieldField = 1ull<<12, - + EntityFlag_NoCapture = 1ull<<13, // #no_capture EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 005358734..dd1041702 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -296,6 +296,11 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; +struct lbNoCaptureData { + Type *slice_type; + lbAddr base_array; +}; + struct lbProcedure { u32 flags; u16 state_flags; @@ -336,6 +341,8 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; + Array no_captures; + LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 610c34de2..ec244e185 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -517,6 +517,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_start_block(p, p->entry_block); map_init(&p->direct_parameters); + p->no_captures.allocator = heap_allocator(); GB_ASSERT(p->type != nullptr); @@ -3450,8 +3451,32 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } isize slice_len = var_args.count; if (slice_len > 0) { + lbAddr base_array = {}; + if (e->flags & EntityFlag_NoCapture) { + for (lbNoCaptureData const &nc : p->no_captures) { + if (are_types_identical(nc.slice_type, slice_type)) { + base_array = nc.base_array; + break; + } + } + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && base_array.addr.value == nullptr) { + for (NoCaptureData const &nc : d->no_captures) { + if (are_types_identical(nc.slice_type, slice_type)) { + base_array = lb_add_local_generated(p, alloc_type_array(elem_type, nc.max_count), true); + array_add(&p->no_captures, lbNoCaptureData{slice_type, base_array}); + break; + } + } + } + } + + if (base_array.addr.value == nullptr) { + base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + } + GB_ASSERT(base_array.addr.value != nullptr); + lbAddr slice = lb_add_local_generated(p, slice_type, true); - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); for (isize i = 0; i < var_args.count; i++) { lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); diff --git a/src/parser.cpp b/src/parser.cpp index 9ce3d563d..a6a146cfd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4014,6 +4014,7 @@ struct ParseFieldPrefixMapping { gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { {str_lit("using"), Token_using, FieldFlag_using}, {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("no_capture"), Token_Hash, FieldFlag_no_capture}, {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, {str_lit("const"), Token_Hash, FieldFlag_const}, {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, diff --git a/src/parser.hpp b/src/parser.hpp index 86b3393af..15176f789 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -330,9 +330,10 @@ enum FieldFlag : u32 { FieldFlag_subtype = 1<<7, FieldFlag_by_ptr = 1<<8, FieldFlag_no_broadcast = 1<<9, // disallow array programming + FieldFlag_no_capture = 1<<10, // Internal use by the parser only - FieldFlag_Tags = 1<<10, + FieldFlag_Tags = 1<<11, FieldFlag_Results = 1<<16, @@ -340,7 +341,10 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg| + FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast| + FieldFlag_no_capture, + FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; -- cgit v1.2.3 From 139c1bcdda68c30c56ae26a9715a38074b9a1129 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:25:41 +0100 Subject: Comment out debug code --- src/parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index a6a146cfd..4924dd37d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -112,7 +112,7 @@ gb_internal isize ast_node_size(AstKind kind) { } -gb_global std::atomic global_total_node_memory_allocated; +// gb_global std::atomic global_total_node_memory_allocated; // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { @@ -122,7 +122,7 @@ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { node->kind = kind; node->file_id = f ? f->id : 0; - global_total_node_memory_allocated.fetch_add(size); + // global_total_node_memory_allocated.fetch_add(size); return node; } -- cgit v1.2.3 From 018026d844c8ad3b625f019acee470dbb865d085 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:36:00 +0100 Subject: Use `gb_zero_*` calls --- src/checker.cpp | 6 +++--- src/common_memory.cpp | 7 ------- src/gb/gb.h | 2 +- src/parser.cpp | 2 +- src/types.cpp | 2 +- 5 files changed, 6 insertions(+), 13 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 336440d32..0a671cc2d 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -658,7 +658,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { } } - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Shadowed; ve->entity = e; ve->other = shadowed; @@ -677,7 +677,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { } case Entity_ImportName: case Entity_LibraryName: - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Unused; ve->entity = e; return true; @@ -1389,7 +1389,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp auto type_path = ctx->type_path; array_clear(type_path); - zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); + gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); ctx->file = nullptr; ctx->scope = builtin_pkg->scope; diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 60e570eee..42a2125dc 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -2,13 +2,6 @@ #include #endif -gb_internal gb_inline void zero_size(void *ptr, isize len) { - memset(ptr, 0, len); -} - -#define zero_item(ptr) zero_size((ptr), gb_size_of(ptr)) - - template gb_internal gb_inline U bit_cast(V &v) { return reinterpret_cast(v); } diff --git a/src/gb/gb.h b/src/gb/gb.h index 22a30a04b..38dabc9bd 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -2534,7 +2534,7 @@ gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } -gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } +gb_inline void gb_zero_size(void *ptr, isize size) { memset(ptr, 0, size); } #if defined(_MSC_VER) && !defined(__clang__) diff --git a/src/parser.cpp b/src/parser.cpp index 4924dd37d..02c37815b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5413,7 +5413,7 @@ gb_internal ParseFileError init_ast_file(AstFile *f, String const &fullpath, Tok if (!string_ends_with(f->fullpath, str_lit(".odin"))) { return ParseFile_WrongExtension; } - zero_item(&f->tokenizer); + gb_zero_item(&f->tokenizer); f->tokenizer.curr_file_id = f->id; TokenizerInitError err = init_tokenizer_from_fullpath(&f->tokenizer, f->fullpath, build_context.copy_file_contents); diff --git a/src/types.cpp b/src/types.cpp index 92b187cdb..fdc174d81 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -964,7 +964,7 @@ gb_internal Type *alloc_type(TypeKind kind) { // gbAllocator a = heap_allocator(); gbAllocator a = permanent_allocator(); Type *t = gb_alloc_item(a, Type); - zero_item(t); + gb_zero_item(t); t->kind = kind; t->cached_size = -1; t->cached_align = -1; -- cgit v1.2.3 From a45e05bb180ad5ac3f9bc4199ebbf0b3bcadbf25 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:36:54 +0100 Subject: Remove need for `BlockingMutex` in `Arena` --- src/common_memory.cpp | 15 +++------------ src/parser.cpp | 2 +- src/parser.hpp | 4 +--- 3 files changed, 5 insertions(+), 16 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/common_memory.cpp b/src/common_memory.cpp index bfe4c2e68..47b2796a9 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -45,7 +45,7 @@ struct MemoryBlock { struct Arena { MemoryBlock * curr_block; isize minimum_block_size; - BlockingMutex mutex; + // BlockingMutex mutex; isize temp_count; Thread * parent_thread; }; @@ -82,12 +82,7 @@ gb_internal void thread_init_arenas(Thread *t) { gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); - - if (arena->parent_thread == nullptr) { - mutex_lock(&arena->mutex); - } else { - GB_ASSERT(arena->parent_thread == get_current_thread()); - } + GB_ASSERT(arena->parent_thread == get_current_thread()); isize size = 0; if (arena->curr_block != nullptr) { @@ -113,11 +108,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { curr_block->used += size; GB_ASSERT(curr_block->used <= curr_block->size); - - if (arena->parent_thread == nullptr) { - mutex_unlock(&arena->mutex); - } - + // NOTE(bill): memory will be zeroed by default due to virtual memory return ptr; } diff --git a/src/parser.cpp b/src/parser.cpp index 02c37815b..5a3fc1634 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -118,7 +118,7 @@ gb_internal isize ast_node_size(AstKind kind) { gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { isize size = ast_node_size(kind); - Ast *node = cast(Ast *)arena_alloc(&global_thread_local_ast_arena, size, 16); + Ast *node = cast(Ast *)arena_alloc(get_arena(ThreadArena_Permanent), size, 16); node->kind = kind; node->file_id = f ? f->id : 0; diff --git a/src/parser.hpp b/src/parser.hpp index 451cdf53d..565a8e621 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -878,10 +878,8 @@ gb_internal gb_inline bool is_ast_when_stmt(Ast *node) { return node->kind == Ast_WhenStmt; } -gb_global gb_thread_local Arena global_thread_local_ast_arena = {}; - gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { - return arena_allocator(&global_thread_local_ast_arena); + return permanent_allocator(); } gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); -- cgit v1.2.3 From 39657e4d968711c11254fa0ee26d576cfc367071 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 15:15:51 +0200 Subject: Fix #3473 Fix the problem where the initial package's directory name ended in .odin. --- src/parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 5a3fc1634..f2d2a1d15 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5609,7 +5609,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const pkg->foreign_files.allocator = permanent_allocator(); // NOTE(bill): Single file initial package - if (kind == Package_Init && string_ends_with(path, FILE_EXT)) { + if (kind == Package_Init && !path_is_directory(path) && string_ends_with(path, FILE_EXT)) { FileInfo fi = {}; fi.name = filename_from_path(path); fi.fullpath = path; @@ -6529,6 +6529,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { GB_ASSERT(init_filename.text[init_filename.len] == 0); String init_fullpath = path_to_full_path(permanent_allocator(), init_filename); + if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { @@ -6543,6 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String short_path = filename_from_path(path); + char *cpath = alloc_cstring(temporary_allocator(), short_path); if (gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); -- cgit v1.2.3 From 90a4d12b30f24c4a44a8c8e606ce56efde56cf50 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:11:33 +0200 Subject: Fix .exe path is directory check. --- src/parser.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index f2d2a1d15..eaf43b5bc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6543,10 +6543,9 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { - String short_path = filename_from_path(path); - - char *cpath = alloc_cstring(temporary_allocator(), short_path); - if (gb_file_exists(cpath)) { + String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); + char *cpath = alloc_cstring(temporary_allocator(), output_path); + if (path_is_directory(output_path) && gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } -- cgit v1.2.3 From 07d2aba31037c645327f28eba179d6cdba245cca Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:36:21 +0200 Subject: Simplify exe path check. --- src/parser.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index eaf43b5bc..aba2b8276 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6544,8 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); - char *cpath = alloc_cstring(temporary_allocator(), output_path); - if (path_is_directory(output_path) && gb_file_exists(cpath)) { + if (path_is_directory(output_path)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } -- cgit v1.2.3 From 9553bc3689583158d044c82a3fd75248114f2291 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 17 Aug 2024 17:25:52 +0200 Subject: If missing type is newline, print "newline", not \n Turns: W:/Odin/bug/bug.odin(3:27) Syntax Error: Expected a type, got ' ' Storage :: distinct map[] Into: W:/Odin/bug/bug.odin(3:27) Syntax Error: Expected a type, got newline Storage :: distinct map[] --- src/parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index aba2b8276..5796012d9 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3560,7 +3560,12 @@ gb_internal Ast *parse_type(AstFile *f) { } else { token = advance_token(f); } - syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token.string)); + String prev_token_str = prev_token.string; + if (prev_token_str == str_lit("\n")) { + syntax_error(token, "Expected a type, got newline"); + } else { + syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token_str)); + } return ast_bad_expr(f, token, f->curr_token); } else if (type->kind == Ast_ParenExpr && unparen_expr(type) == nullptr) { -- cgit v1.2.3 From a4cc207022712c238e827bda9e871870970cca25 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 24 Aug 2024 12:59:17 +0100 Subject: Add a recursion depth limit for #3987 with a consideration to use a `switch` statement or refactor the code to not use a large if-else chain --- src/parser.cpp | 8 ++++++++ src/parser.hpp | 2 ++ 2 files changed, 10 insertions(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 5796012d9..6461c9dd0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4532,6 +4532,12 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { return ast_bad_stmt(f, f->curr_token, f->curr_token); } + if (f->recursion_depth_else_if > 256) { + syntax_error(f->curr_token, "if-else chain recursion depth limit hit. Consider using a 'switch' statement instead or refactor the code to not require a large if-else chain"); + f->recursion_depth_else_if = 0; + return ast_bad_stmt(f, f->curr_token, f->curr_token); + } + Token token = expect_token(f, Token_if); Ast *init = nullptr; Ast *cond = nullptr; @@ -4577,7 +4583,9 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { case Token_if: + f->recursion_depth_else_if += 1; else_stmt = parse_if_stmt(f); + f->recursion_depth_else_if -= 1; break; case Token_OpenBrace: else_stmt = parse_block_stmt(f, false); diff --git a/src/parser.hpp b/src/parser.hpp index 565a8e621..711e6f060 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -120,6 +120,8 @@ struct AstFile { bool allow_type; bool in_when_statement; + isize recursion_depth_else_if; + isize total_file_decl_count; isize delayed_decl_count; Slice decls; -- cgit v1.2.3 From b6d9a0c32e64fd4ce4d3350fca9a671d5daac126 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 24 Aug 2024 13:16:55 +0100 Subject: Manually implement tail-recursion for `parse_if_stmt` --- src/parser.cpp | 30 ++++++++++++++++++++---------- src/parser.hpp | 2 -- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 6461c9dd0..1a45c4ea5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4532,12 +4532,10 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { return ast_bad_stmt(f, f->curr_token, f->curr_token); } - if (f->recursion_depth_else_if > 256) { - syntax_error(f->curr_token, "if-else chain recursion depth limit hit. Consider using a 'switch' statement instead or refactor the code to not require a large if-else chain"); - f->recursion_depth_else_if = 0; - return ast_bad_stmt(f, f->curr_token, f->curr_token); - } + Ast *top_if_stmt = nullptr; + Ast *prev_if_stmt = nullptr; +if_else_chain:; Token token = expect_token(f, Token_if); Ast *init = nullptr; Ast *cond = nullptr; @@ -4579,14 +4577,24 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { ignore_strict_style = true; } skip_possible_newline_for_literal(f, ignore_strict_style); + + Ast *curr_if_stmt = ast_if_stmt(f, token, init, cond, body, nullptr); + if (top_if_stmt == nullptr) { + top_if_stmt = curr_if_stmt; + } + if (prev_if_stmt != nullptr) { + prev_if_stmt->IfStmt.else_stmt = curr_if_stmt; + } + if (f->curr_token.kind == Token_else) { Token else_token = expect_token(f, Token_else); switch (f->curr_token.kind) { case Token_if: - f->recursion_depth_else_if += 1; - else_stmt = parse_if_stmt(f); - f->recursion_depth_else_if -= 1; - break; + // NOTE(bill): Instead of relying on recursive descent for an if-else chain + // we can just inline the tail-recursion manually with a simple loop like + // construct using a `goto` + prev_if_stmt = curr_if_stmt; + goto if_else_chain; case Token_OpenBrace: else_stmt = parse_block_stmt(f, false); break; @@ -4601,7 +4609,9 @@ gb_internal Ast *parse_if_stmt(AstFile *f) { } } - return ast_if_stmt(f, token, init, cond, body, else_stmt); + curr_if_stmt->IfStmt.else_stmt = else_stmt; + + return top_if_stmt; } gb_internal Ast *parse_when_stmt(AstFile *f) { diff --git a/src/parser.hpp b/src/parser.hpp index 711e6f060..565a8e621 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -120,8 +120,6 @@ struct AstFile { bool allow_type; bool in_when_statement; - isize recursion_depth_else_if; - isize total_file_decl_count; isize delayed_decl_count; Slice decls; -- cgit v1.2.3 From 8ba87e01bd9a26ffd391d2ad69ff0eb6a023d1cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 24 Aug 2024 13:56:30 +0100 Subject: Improve `parse_enforce_tabs` usage --- src/parser.cpp | 22 +++++++++++++++++----- src/parser.hpp | 5 ++++- 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 1a45c4ea5..b250a3163 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1921,6 +1921,9 @@ gb_internal Array parse_enum_field_list(AstFile *f) { f->curr_token.kind != Token_EOF) { CommentGroup *docs = f->lead_comment; CommentGroup *comment = nullptr; + + parse_enforce_tabs(f); + Ast *name = parse_value(f); Ast *value = nullptr; if (f->curr_token.kind == Token_Eq) { @@ -2259,6 +2262,7 @@ gb_internal Array parse_union_variant_list(AstFile *f) { auto variants = array_make(ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { + parse_enforce_tabs(f); Ast *type = parse_type(f); if (type->kind != Ast_BadExpr) { array_add(&variants, type); @@ -4274,6 +4278,7 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl while (f->curr_token.kind != follow && f->curr_token.kind != Token_Colon && f->curr_token.kind != Token_EOF) { + if (!is_signature) parse_enforce_tabs(f); u32 flags = parse_field_prefixes(f); Ast *param = parse_var_type(f, allow_ellipsis, allow_typeid_token); if (param->kind == Ast_Ellipsis) { @@ -4363,6 +4368,8 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl f->curr_token.kind != Token_EOF && f->curr_token.kind != Token_Semicolon) { CommentGroup *docs = f->lead_comment; + + if (!is_signature) parse_enforce_tabs(f); u32 set_flags = parse_field_prefixes(f); Token tag = {}; Array names = parse_ident_list(f, allow_poly_names); @@ -5375,6 +5382,11 @@ gb_internal u64 check_vet_flags(AstFile *file) { gb_internal void parse_enforce_tabs(AstFile *f) { + // Checks to see if tabs have been used for indentation + if ((check_vet_flags(f) & VetFlag_Tabs) == 0) { + return; + } + Token prev = f->prev_token; Token curr = f->curr_token; if (prev.pos.line < curr.pos.line) { @@ -5391,6 +5403,10 @@ gb_internal void parse_enforce_tabs(AstFile *f) { isize len = end-it; for (isize i = 0; i < len; i++) { + if (it[i] == '/') { + // ignore comments + break; + } if (it[i] == ' ') { syntax_error(curr, "With '-vet-tabs', tabs must be used for indentation"); break; @@ -5405,11 +5421,7 @@ gb_internal Array parse_stmt_list(AstFile *f) { while (f->curr_token.kind != Token_case && f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - - // Checks to see if tabs have been used for indentation - if (check_vet_flags(f) & VetFlag_Tabs) { - parse_enforce_tabs(f); - } + parse_enforce_tabs(f); Ast *stmt = parse_stmt(f); if (stmt && stmt->kind != Ast_EmptyStmt) { diff --git a/src/parser.hpp b/src/parser.hpp index 565a8e621..f1794f79a 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -885,4 +885,7 @@ gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); gb_internal gbString expr_to_string(Ast *expression); -gb_internal bool allow_field_separator(AstFile *f); \ No newline at end of file +gb_internal bool allow_field_separator(AstFile *f); + + +gb_internal void parse_enforce_tabs(AstFile *f); \ No newline at end of file -- cgit v1.2.3 From 43ec2b9253379d03b69f40b57ef2aafd2c968416 Mon Sep 17 00:00:00 2001 From: avanspector Date: Mon, 26 Aug 2024 20:59:16 +0200 Subject: checker: delay foreign block checking if file scope, otherwise as before --- src/checker.cpp | 28 ++++++++++++++++++++-------- src/parser.cpp | 2 +- src/parser.hpp | 1 + 3 files changed, 22 insertions(+), 9 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/checker.cpp b/src/checker.cpp index 35b84c155..fdc1ce840 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4474,8 +4474,6 @@ gb_internal void correct_type_aliases_in_scope(CheckerContext *c, Scope *s) { } } -bool is_collect_entities_post = false; - // NOTE(bill): If file_scopes == nullptr, this will act like a local scope gb_internal void check_collect_entities(CheckerContext *c, Slice const &nodes) { AstFile *curr_file = nullptr; @@ -4534,8 +4532,8 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n case_end; case_ast_node(fb, ForeignBlockDecl, decl); - if (is_collect_entities_post) { - check_add_foreign_block_decl(c, decl); + if (curr_file != nullptr) { + array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); } case_end; @@ -4552,6 +4550,14 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n // NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something // declared after this stmt in source if (curr_file == nullptr) { + // For 'foreign' block statements that are not in file scope. + for_array(decl_index, nodes) { + Ast *decl = nodes[decl_index]; + if (decl->kind == Ast_ForeignBlockDecl) { + check_add_foreign_block_decl(c, decl); + } + } + for_array(decl_index, nodes) { Ast *decl = nodes[decl_index]; if (decl->kind == Ast_WhenStmt) { @@ -5512,6 +5518,16 @@ gb_internal void check_import_entities(Checker *c) { correct_type_aliases_in_scope(&ctx, pkg->scope); } + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + reset_checker_context(&ctx, f, &untyped); + + for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_ForeignBlock]) { + check_add_foreign_block_decl(&ctx, decl); + } + array_clear(&f->delayed_decls_queues[AstDelayQueue_ForeignBlock]); + } + for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); @@ -6441,10 +6457,6 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("export entities - post"); check_export_entities(c); - TIME_SECTION("collect entities - post"); - is_collect_entities_post = true; - check_collect_entities_all(c); - TIME_SECTION("add entities from packages"); check_merge_queues_into_arrays(c); diff --git a/src/parser.cpp b/src/parser.cpp index b250a3163..e3143dd33 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6446,7 +6446,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } f->total_file_decl_count += calc_decl_count(stmt); - if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl) { + if (stmt->kind == Ast_WhenStmt || stmt->kind == Ast_ExprStmt || stmt->kind == Ast_ImportDecl || stmt->kind == Ast_ForeignBlockDecl) { f->delayed_decl_count += 1; } } diff --git a/src/parser.hpp b/src/parser.hpp index f1794f79a..d5117a31e 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -82,6 +82,7 @@ enum AstFileFlag : u32 { enum AstDelayQueueKind { AstDelayQueue_Import, AstDelayQueue_Expr, + AstDelayQueue_ForeignBlock, AstDelayQueue_COUNT, }; -- cgit v1.2.3 From 1a7c1d107a26df00bc6c9379178fccb55569b667 Mon Sep 17 00:00:00 2001 From: Laytan Date: Wed, 4 Sep 2024 22:18:55 +0200 Subject: set -rpath to \$ORIGIN and expect libraries next to executable just like Windows --- src/linker.cpp | 14 ++++++++------ src/parser.cpp | 14 -------------- 2 files changed, 8 insertions(+), 20 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/linker.cpp b/src/linker.cpp index faca28932..25b9cfdc3 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -548,14 +548,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { // available at runtime wherever the executable is run, so we make require those to be // local to the executable (unless the system collection is used, in which case we search // the system library paths for the library file). - if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { + if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { // static libs and object files, absolute full path relative to the file in which the lib was imported from lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { - // dynamic lib, relative path to executable - // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtime to the executable - lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); + + // NOTE(laytan): If .so, I think we can check for the existence of "$OUT_DIRECTORY/$lib" here and print an error telling the user to copy over the file, or we can even do the copy for them? + } else { // dynamic or static system lib, just link regularly searching system library paths lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); @@ -643,6 +641,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } + // Set the rpath to the $ORIGIN (the path of the executable), + // so that dynamic libraries are looked for at that path. + gb_string_appendc(link_settings, "-Wl,-rpath,\\$ORIGIN "); + if (!build_context.no_crt) { platform_lib_str = gb_string_appendc(platform_lib_str, "-lm "); if (build_context.metrics.os == TargetOs_darwin) { diff --git a/src/parser.cpp b/src/parser.cpp index e3143dd33..88147d465 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5932,20 +5932,6 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node do_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); return false; } - } else { -#if !defined(GB_SYSTEM_WINDOWS) - // @NOTE(vassvik): foreign imports of shared libraries that are not in the system collection on - // linux/mac have to be local to the executable for consistency with shared libraries. - // Unix does not have a concept of "import library" for shared/dynamic libraries, - // so we need to pass the relative path to the linker, and add the current - // working directory of the exe to the library search paths. - // Static libraries can be linked directly with the full pathname - // - if (node->kind == Ast_ForeignImportDecl && (string_ends_with(file_str, str_lit(".so")) || string_contains_string(file_str, str_lit(".so.")))) { - *path = file_str; - return true; - } -#endif } if (is_package_name_reserved(file_str)) { -- cgit v1.2.3 From dc767da12b0e03a6cd9ff20085565c95c06ef7bc Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Thu, 5 Sep 2024 21:17:40 +0200 Subject: Make tags use #+ syntax instead of //+ syntax so it no longer looks like a comment. Old style still works but is deprecated with a warning. Using unknown tags is now an error instead of a warning. There is a new token for #+ which consumes the whole line (or until it hits a comment). The tags are parsed like before. There are errors to tell you if you use something invalid in the pre-package-line block. --- src/parser.cpp | 155 +++++++++++++++++++++++++++++++++++++----------------- src/tokenizer.cpp | 15 ++++++ 2 files changed, 123 insertions(+), 47 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index e3143dd33..03ed725c5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5337,6 +5337,12 @@ gb_internal Ast *parse_stmt(AstFile *f) { s = ast_empty_stmt(f, token); expect_semicolon(f); return s; + + case Token_FileTag: + // This is always an error because all valid file tags will have been processed in `parse_file` already. + // Any remaining file tags must be past the package line and thus invalid. + syntax_error(token, "Lines starting with #+ (file tags) are only allowed before the package line."); + return ast_bad_stmt(f, token, f->curr_token); } // Error correction statements @@ -6091,7 +6097,7 @@ gb_internal String build_tag_get_token(String s, String *out) { } gb_internal bool parse_build_tag(Token token_for_pos, String s) { - String const prefix = str_lit("+build"); + String const prefix = str_lit("build"); GB_ASSERT(string_starts_with(s, prefix)); s = string_trim_whitespace(substring(s, prefix.len, s.len)); @@ -6176,7 +6182,7 @@ gb_internal String vet_tag_get_token(String s, String *out) { gb_internal u64 parse_vet_tag(Token token_for_pos, String s) { - String const prefix = str_lit("+vet"); + String const prefix = str_lit("vet"); GB_ASSERT(string_starts_with(s, prefix)); s = string_trim_whitespace(substring(s, prefix.len, s.len)); @@ -6281,7 +6287,7 @@ gb_internal isize calc_decl_count(Ast *decl) { } gb_internal bool parse_build_project_directory_tag(Token token_for_pos, String s) { - String const prefix = str_lit("+build-project-name"); + String const prefix = str_lit("build-project-name"); GB_ASSERT(string_starts_with(s, prefix)); s = string_trim_whitespace(substring(s, prefix.len, s.len)); if (s.len == 0) { @@ -6325,6 +6331,48 @@ gb_internal bool parse_build_project_directory_tag(Token token_for_pos, String s return any_correct; } +gb_internal bool process_file_tag(const String &lc, const Token &tok, AstFile *f) { + if (string_starts_with(lc, str_lit("build-project-name"))) { + if (!parse_build_project_directory_tag(tok, lc)) { + return false; + } + } else if (string_starts_with(lc, str_lit("build"))) { + if (!parse_build_tag(tok, lc)) { + return false; + } + } else if (string_starts_with(lc, str_lit("vet"))) { + f->vet_flags = parse_vet_tag(tok, lc); + f->vet_flags_set = true; + } else if (string_starts_with(lc, str_lit("ignore"))) { + return false; + } else if (string_starts_with(lc, str_lit("private"))) { + f->flags |= AstFile_IsPrivatePkg; + String command = string_trim_starts_with(lc, str_lit("private ")); + command = string_trim_whitespace(command); + if (lc == "private") { + f->flags |= AstFile_IsPrivatePkg; + } else if (command == "package") { + f->flags |= AstFile_IsPrivatePkg; + } else if (command == "file") { + f->flags |= AstFile_IsPrivateFile; + } + } else if (lc == "lazy") { + if (build_context.ignore_lazy) { + // Ignore + } else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) { + // Ignore + } else { + f->flags |= AstFile_IsLazy; + } + } else if (lc == "no-instrumentation") { + f->flags |= AstFile_NoInstrumentation; + } else { + error(tok, "Unknown tag '%.*s'", LIT(lc)); + } + + return true; +} + gb_internal bool parse_file(Parser *p, AstFile *f) { if (f->tokens.count == 0) { return true; @@ -6337,15 +6385,38 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { String filepath = f->tokenizer.fullpath; String base_dir = dir_from_path(filepath); - if (f->curr_token.kind == Token_Comment) { - consume_comment_groups(f, f->prev_token); + + Array tags = array_make(ast_allocator(f)); + + bool has_first_invalid_pre_package_token = false; + Token first_invalid_pre_package_token; + + while (f->curr_token.kind != Token_package && f->curr_token.kind != Token_EOF) { + if (f->curr_token.kind == Token_Comment) { + consume_comment_groups(f, f->prev_token); + } else if (f->curr_token.kind == Token_FileTag) { + array_add(&tags, f->curr_token); + advance_token(f); + } else { + if (!has_first_invalid_pre_package_token) { + has_first_invalid_pre_package_token = true; + first_invalid_pre_package_token = f->curr_token; + } + + advance_token(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"); + + // The while loop above scanned until it found the package token. If we never + // found one, then make this error appear on the first invalid token line. + Token t = has_first_invalid_pre_package_token ? first_invalid_pre_package_token : f->curr_token; + syntax_error(t, "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 != "") { @@ -6354,6 +6425,12 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { return false; } + // There was an OK package declaration. But there some invalid token was hit before the package declaration. + if (has_first_invalid_pre_package_token) { + syntax_error(first_invalid_pre_package_token, "There can only be lines starting with #+ or // before package declaration"); + return false; + } + f->package_token = expect_token(f, Token_package); if (f->package_token.kind != Token_package) { return false; @@ -6379,55 +6456,39 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } f->package_name = package_name.string; - if (!f->pkg->is_single_file && docs != nullptr && docs->list.count > 0) { - for (Token const &tok : docs->list) { - GB_ASSERT(tok.kind == Token_Comment); - String str = tok.string; - if (string_starts_with(str, str_lit("//"))) { - String lc = string_trim_whitespace(substring(str, 2, str.len)); - if (lc.len > 0 && lc[0] == '+') { - if (string_starts_with(lc, str_lit("+build-project-name"))) { - if (!parse_build_project_directory_tag(tok, lc)) { - return false; - } - } else if (string_starts_with(lc, str_lit("+build"))) { - if (!parse_build_tag(tok, lc)) { + if (!f->pkg->is_single_file) { + if (docs != nullptr && docs->list.count > 0) { + for (Token const &tok : docs->list) { + GB_ASSERT(tok.kind == Token_Comment); + String str = tok.string; + if (string_starts_with(str, str_lit("//"))) { + String lc = string_trim_whitespace(substring(str, 2, str.len)); + if (string_starts_with(lc, str_lit("+"))) { + syntax_warning(tok, "//+ is deprecated: Use #+ instead"); + String lt = substring(lc, 1, lc.len); + if (process_file_tag(lt, tok, f) == false) { return false; } - } else if (string_starts_with(lc, str_lit("+vet"))) { - f->vet_flags = parse_vet_tag(tok, lc); - f->vet_flags_set = true; - } else if (string_starts_with(lc, str_lit("+ignore"))) { - return false; - } else if (string_starts_with(lc, str_lit("+private"))) { - f->flags |= AstFile_IsPrivatePkg; - String command = string_trim_starts_with(lc, str_lit("+private ")); - command = string_trim_whitespace(command); - if (lc == "+private") { - f->flags |= AstFile_IsPrivatePkg; - } else if (command == "package") { - f->flags |= AstFile_IsPrivatePkg; - } else if (command == "file") { - f->flags |= AstFile_IsPrivateFile; - } - } else if (lc == "+lazy") { - if (build_context.ignore_lazy) { - // Ignore - } else if (f->pkg->kind == Package_Init && build_context.command_kind == Command_doc) { - // Ignore - } else { - f->flags |= AstFile_IsLazy; - } - } else if (lc == "+no-instrumentation") { - f->flags |= AstFile_NoInstrumentation; - } else { - warning(tok, "Ignoring unknown tag '%.*s'", LIT(lc)); } } } } + + for (Token const &tok : tags) { + GB_ASSERT(tok.kind == Token_FileTag); + String str = tok.string; + + if (string_starts_with(str, str_lit("#+"))) { + String lt = string_trim_whitespace(substring(str, 2, str.len)); + if (process_file_tag(lt, tok, f) == false) { + return false; + } + } + } } + array_free(&tags); + Ast *pd = ast_package_decl(f, f->package_token, package_name, docs, f->line_comment); expect_semicolon(f); f->pkg_decl = pd; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 4425bee29..e9bad390e 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -2,6 +2,7 @@ TOKEN_KIND(Token_Invalid, "Invalid"), \ TOKEN_KIND(Token_EOF, "EOF"), \ TOKEN_KIND(Token_Comment, "Comment"), \ + TOKEN_KIND(Token_FileTag, "FileTag"), \ \ TOKEN_KIND(Token__LiteralBegin, ""), \ TOKEN_KIND(Token_Ident, "identifier"), \ @@ -939,6 +940,20 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { if (t->curr_rune == '!') { token->kind = Token_Comment; tokenizer_skip_line(t); + } else if (t->curr_rune == '+') { + token->kind = Token_FileTag; + + // Skip the line or until it ends or until we hit was is probably a comment. + // The parsing of tags happens in `parse_file`. + while (t->curr_rune != GB_RUNE_EOF) { + if (t->curr_rune == '\n') { + break; + } + if (t->curr_rune == '/') { + break; + } + advance_to_next_rune(t); + } } break; case '/': -- cgit v1.2.3 From 73e495434666b230e16ea7300c957ddc978e3e1a Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Sun, 8 Sep 2024 02:52:43 +0200 Subject: Better #+build tag error messages: Error when using more than one !notted operating system per build line. Error when using more than one operating system within a 'kind', such as writing #+build windows linux. --- src/parser.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 03ed725c5..7f2fa9b70 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6106,9 +6106,12 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { } bool any_correct = false; + bool any_notted_os_seen = false; + bool any_os_seen = false; while (s.len > 0) { bool this_kind_correct = true; + bool this_kind_os_seen = false; do { String p = string_trim_whitespace(build_tag_get_token(s, &s)); @@ -6133,12 +6136,30 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { continue; } + TargetOsKind os = get_target_os_from_string(p); TargetArchKind arch = get_target_arch_from_string(p); if (os != TargetOs_Invalid) { + // Catches cases where you have multiple !notted operating systems on a line. This never does what you think since + // you need a new build line to get a logical AND. + if (any_notted_os_seen && is_notted) { + syntax_error(token_for_pos, "Invalid build tag: Use a separate '#+build' line for each platform that has '!' in front."); + break; + } + + // Catches 'windows linux', which is an impossible combination. + if (this_kind_os_seen) { + syntax_error(token_for_pos, "Invalid build tag: Missing ',' before '%.*s'. Format: '#+build linux, windows, darwin' or '#+build linux amd64, darwin, windows i386'", LIT(p)); + break; + } + + this_kind_os_seen = true; + any_os_seen = true; + GB_ASSERT(arch == TargetArch_Invalid); if (is_notted) { this_kind_correct = this_kind_correct && (os != build_context.metrics.os); + any_notted_os_seen = true; } else { this_kind_correct = this_kind_correct && (os == build_context.metrics.os); } @@ -6427,7 +6448,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { // There was an OK package declaration. But there some invalid token was hit before the package declaration. if (has_first_invalid_pre_package_token) { - syntax_error(first_invalid_pre_package_token, "There can only be lines starting with #+ or // before package declaration"); + syntax_error(first_invalid_pre_package_token, "There can only be lines starting with '#+' or '//' before package declaration"); return false; } @@ -6464,7 +6485,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { if (string_starts_with(str, str_lit("//"))) { String lc = string_trim_whitespace(substring(str, 2, str.len)); if (string_starts_with(lc, str_lit("+"))) { - syntax_warning(tok, "//+ is deprecated: Use #+ instead"); + syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); String lt = substring(lc, 1, lc.len); if (process_file_tag(lt, tok, f) == false) { return false; -- cgit v1.2.3 From 3637dcbd04d328a110f0626171f2d49f7a96b09b Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 9 Sep 2024 21:03:23 +0200 Subject: Simplified error messages in parse_build_tag, removed the idea of making multiple notted operating systems since it was misinformed. --- src/parser.cpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 7f2fa9b70..2df876d73 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6106,12 +6106,13 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { } bool any_correct = false; - bool any_notted_os_seen = false; - bool any_os_seen = false; while (s.len > 0) { bool this_kind_correct = true; + bool this_kind_os_seen = false; + bool this_kind_arch_seen = false; + int num_tokens = 0; do { String p = string_trim_whitespace(build_tag_get_token(s, &s)); @@ -6136,34 +6137,29 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { continue; } - TargetOsKind os = get_target_os_from_string(p); TargetArchKind arch = get_target_arch_from_string(p); - if (os != TargetOs_Invalid) { - // Catches cases where you have multiple !notted operating systems on a line. This never does what you think since - // you need a new build line to get a logical AND. - if (any_notted_os_seen && is_notted) { - syntax_error(token_for_pos, "Invalid build tag: Use a separate '#+build' line for each platform that has '!' in front."); - break; - } + num_tokens += 1; - // Catches 'windows linux', which is an impossible combination. - if (this_kind_os_seen) { - syntax_error(token_for_pos, "Invalid build tag: Missing ',' before '%.*s'. Format: '#+build linux, windows, darwin' or '#+build linux amd64, darwin, windows i386'", LIT(p)); - break; - } + // Catches 'windows linux', which is an impossible combination. + // Also catches usage of more than two things within a comma separated group. + if (num_tokens > 2 || (this_kind_os_seen && os != TargetOs_Invalid) || (this_kind_arch_seen && arch != TargetArch_Invalid)) { + syntax_error(token_for_pos, "Invalid build tag: Missing ',' before '%.*s'. Format: '#+build linux, windows amd64, darwin'", LIT(p)); + break; + } + if (os != TargetOs_Invalid) { this_kind_os_seen = true; - any_os_seen = true; GB_ASSERT(arch == TargetArch_Invalid); if (is_notted) { this_kind_correct = this_kind_correct && (os != build_context.metrics.os); - any_notted_os_seen = true; } else { this_kind_correct = this_kind_correct && (os == build_context.metrics.os); } } else if (arch != TargetArch_Invalid) { + this_kind_arch_seen = true; + if (is_notted) { this_kind_correct = this_kind_correct && (arch != build_context.metrics.arch); } else { -- cgit v1.2.3 From 957cd64699f1f8f1d1f689025c708aebc3c32476 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 9 Sep 2024 21:06:43 +0200 Subject: Rename process_file_tag -> parse_file_tag --- src/parser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 2df876d73..3634da7fa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6348,7 +6348,7 @@ gb_internal bool parse_build_project_directory_tag(Token token_for_pos, String s return any_correct; } -gb_internal bool process_file_tag(const String &lc, const Token &tok, AstFile *f) { +gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) { if (string_starts_with(lc, str_lit("build-project-name"))) { if (!parse_build_project_directory_tag(tok, lc)) { return false; @@ -6483,7 +6483,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { if (string_starts_with(lc, str_lit("+"))) { syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); String lt = substring(lc, 1, lc.len); - if (process_file_tag(lt, tok, f) == false) { + if (parse_file_tag(lt, tok, f) == false) { return false; } } @@ -6497,7 +6497,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { if (string_starts_with(str, str_lit("#+"))) { String lt = string_trim_whitespace(substring(str, 2, str.len)); - if (process_file_tag(lt, tok, f) == false) { + if (parse_file_tag(lt, tok, f) == false) { return false; } } -- cgit v1.2.3 From cc724ff5d2ecc358f36c6c0ed3a25db6373ac95c Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 9 Sep 2024 21:13:39 +0200 Subject: Made error handling code in parse_file clearer. --- src/parser.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 3634da7fa..aaaf02cee 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6405,8 +6405,8 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { Array tags = array_make(ast_allocator(f)); - bool has_first_invalid_pre_package_token = false; - Token first_invalid_pre_package_token; + bool first_invalid_token_set = false; + Token first_invalid_token = {}; while (f->curr_token.kind != Token_package && f->curr_token.kind != Token_EOF) { if (f->curr_token.kind == Token_Comment) { @@ -6415,9 +6415,9 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { array_add(&tags, f->curr_token); advance_token(f); } else { - if (!has_first_invalid_pre_package_token) { - has_first_invalid_pre_package_token = true; - first_invalid_pre_package_token = f->curr_token; + if (!first_invalid_token_set) { + first_invalid_token_set = true; + first_invalid_token = f->curr_token; } advance_token(f); @@ -6431,7 +6431,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { // The while loop above scanned until it found the package token. If we never // found one, then make this error appear on the first invalid token line. - Token t = has_first_invalid_pre_package_token ? first_invalid_pre_package_token : f->curr_token; + Token t = first_invalid_token_set ? first_invalid_token : f->curr_token; syntax_error(t, "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 @@ -6443,8 +6443,8 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } // There was an OK package declaration. But there some invalid token was hit before the package declaration. - if (has_first_invalid_pre_package_token) { - syntax_error(first_invalid_pre_package_token, "There can only be lines starting with '#+' or '//' before package declaration"); + if (first_invalid_token_set) { + syntax_error(first_invalid_token, "There can only be lines starting with '#+' or '//' before package declaration"); return false; } @@ -6481,7 +6481,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { if (string_starts_with(str, str_lit("//"))) { String lc = string_trim_whitespace(substring(str, 2, str.len)); if (string_starts_with(lc, str_lit("+"))) { - syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); + //syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); String lt = substring(lc, 1, lc.len); if (parse_file_tag(lt, tok, f) == false) { return false; -- cgit v1.2.3 From 580f0599cde713f5e1c7495174821abe6dc4a2a1 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 9 Sep 2024 21:24:41 +0200 Subject: parse_file: Removed some nesting and removed probable incorrect safety check. --- src/parser.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index aaaf02cee..c2a9ff138 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6452,14 +6452,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { if (f->package_token.kind != Token_package) { return false; } - if (docs != nullptr) { - TokenPos end = token_pos_end(docs->list[docs->list.count-1]); - if (end.line == f->package_token.pos.line || end.line+1 == f->package_token.pos.line) { - // Okay - } else { - docs = nullptr; - } - } Token package_name = expect_token_after(f, Token_Ident, "package"); if (package_name.kind == Token_Ident) { @@ -6478,14 +6470,17 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { for (Token const &tok : docs->list) { GB_ASSERT(tok.kind == Token_Comment); String str = tok.string; - if (string_starts_with(str, str_lit("//"))) { - String lc = string_trim_whitespace(substring(str, 2, str.len)); - if (string_starts_with(lc, str_lit("+"))) { - //syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); - String lt = substring(lc, 1, lc.len); - if (parse_file_tag(lt, tok, f) == false) { - return false; - } + + if (!string_starts_with(str, str_lit("//"))) { + continue; + } + + String lc = string_trim_whitespace(substring(str, 2, str.len)); + if (string_starts_with(lc, str_lit("+"))) { + syntax_warning(tok, "'//+' is deprecated: Use '#+' instead"); + String lt = substring(lc, 1, lc.len); + if (parse_file_tag(lt, tok, f) == false) { + return false; } } } -- cgit v1.2.3 From 8b84b9a4a26d7f2fb07a7e5dcd49b678d1c9be91 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Sat, 14 Sep 2024 14:32:46 +0200 Subject: Docs are generated as expected again. --- src/parser.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 51da21e9d..523dad8b8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6388,9 +6388,13 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { String filepath = f->tokenizer.fullpath; String base_dir = dir_from_path(filepath); + if (f->curr_token.kind == Token_Comment) { + consume_comment_groups(f, f->prev_token); + } - Array tags = array_make(ast_allocator(f)); + CommentGroup *docs = f->lead_comment; + Array tags = array_make(temporary_allocator()); bool first_invalid_token_set = false; Token first_invalid_token = {}; @@ -6410,8 +6414,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } } - CommentGroup *docs = f->lead_comment; - if (f->curr_token.kind != Token_package) { ERROR_BLOCK(); @@ -6451,6 +6453,8 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } f->package_name = package_name.string; + // TODO: Shouldn't single file only matter for build tags? no-instrumentation for example + // should be respected even when in single file mode. if (!f->pkg->is_single_file) { if (docs != nullptr && docs->list.count > 0) { for (Token const &tok : docs->list) { @@ -6485,8 +6489,6 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } } - array_free(&tags); - Ast *pd = ast_package_decl(f, f->package_token, package_name, docs, f->line_comment); expect_semicolon(f); f->pkg_decl = pd; -- cgit v1.2.3 From c24e18bf1063cd86c200d51d585059f8aa099615 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Sat, 14 Sep 2024 14:36:33 +0200 Subject: Fix incorrect syntax error in parse_file --- src/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 523dad8b8..22d92e87b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6432,7 +6432,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { // There was an OK package declaration. But there some invalid token was hit before the package declaration. if (first_invalid_token_set) { - syntax_error(first_invalid_token, "There can only be lines starting with '#+' or '//' before package declaration"); + syntax_error(first_invalid_token, "Expected only comments or lines starting with '#+' before the package declaration"); return false; } -- cgit v1.2.3 From 19c1ed154cc9e36433fe23e1e34810f9c53ec01d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 17 Sep 2024 11:01:26 +0100 Subject: Add `-vet-packages:` --- src/build_settings.cpp | 3 +-- src/checker.cpp | 16 ++++------------ src/main.cpp | 30 +++++++++++++++++++++++++++++- src/parser.cpp | 31 ++++++++++++++++++++----------- 4 files changed, 54 insertions(+), 26 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 1aca5d424..341cbe3e1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -383,6 +383,7 @@ struct BuildContext { u64 vet_flags; u32 sanitizer_flags; + StringSet vet_packages; bool has_resource; String link_flags; @@ -1462,8 +1463,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->thread_count = gb_max(bc->affinity.thread_count, 1); } - string_set_init(&bc->custom_attributes); - bc->ODIN_VENDOR = str_lit("odin"); bc->ODIN_VERSION = ODIN_VERSION; bc->ODIN_ROOT = odin_root_dir(); diff --git a/src/checker.cpp b/src/checker.cpp index 64c66c8a6..deb83bf97 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -533,18 +533,13 @@ gb_internal u64 check_vet_flags(CheckerContext *c) { c->curr_proc_decl->proc_lit) { file = c->curr_proc_decl->proc_lit->file(); } - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + + return ast_file_vet_flags(file); } gb_internal u64 check_vet_flags(Ast *node) { AstFile *file = node->file(); - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + return ast_file_vet_flags(file); } enum VettedEntityKind { @@ -6497,10 +6492,7 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check scope usage"); for (auto const &entry : c->info.files) { AstFile *f = entry.value; - u64 vet_flags = build_context.vet_flags; - if (f->vet_flags_set) { - vet_flags = f->vet_flags; - } + u64 vet_flags = ast_file_vet_flags(f); check_scope_usage(c, f->scope, vet_flags); } diff --git a/src/main.cpp b/src/main.cpp index 06c200442..9b1dcf0fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -346,6 +346,7 @@ enum BuildFlagKind { BuildFlag_VetSemicolon, BuildFlag_VetCast, BuildFlag_VetTabs, + BuildFlag_VetPackages, BuildFlag_CustomAttribute, BuildFlag_IgnoreUnknownAttributes, @@ -555,6 +556,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_VetSemicolon, str_lit("vet-semicolon"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetCast, str_lit("vet-cast"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetTabs, str_lit("vet-tabs"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetPackages, str_lit("vet-packages"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_CustomAttribute, str_lit("custom-attribute"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None, Command__does_check); @@ -1221,6 +1223,29 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_VetCast: build_context.vet_flags |= VetFlag_Cast; break; case BuildFlag_VetTabs: build_context.vet_flags |= VetFlag_Tabs; break; + case BuildFlag_VetPackages: + { + GB_ASSERT(value.kind == ExactValue_String); + String val = value.value_string; + String_Iterator it = {val, 0}; + for (;;) { + String pkg = string_split_iterator(&it, ','); + if (pkg.len == 0) { + break; + } + + pkg = string_trim_whitespace(pkg); + if (!string_is_valid_identifier(pkg)) { + gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(pkg)); + bad_flags = true; + continue; + } + + string_set_add(&build_context.vet_packages, pkg); + } + } + break; + case BuildFlag_CustomAttribute: { GB_ASSERT(value.kind == ExactValue_String); @@ -1234,7 +1259,7 @@ gb_internal bool parse_build_flags(Array args) { attr = string_trim_whitespace(attr); if (!string_is_valid_identifier(attr)) { - gb_printf_err("-custom-attribute '%.*s' must be a valid identifier\n", LIT(attr)); + gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(attr)); bad_flags = true; continue; } @@ -3150,6 +3175,9 @@ int main(int arg_count, char const **arg_ptr) { build_context.command = command; + string_set_init(&build_context.custom_attributes); + string_set_init(&build_context.vet_packages); + if (!parse_build_flags(args)) { return 1; } diff --git a/src/parser.cpp b/src/parser.cpp index 88147d465..56bea8131 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,10 +1,28 @@ #include "parser_pos.cpp" +gb_internal bool in_vet_packages(AstFile *file) { + if (file == nullptr) { + return true; + } + if (file->pkg == nullptr) { + return true; + } + if (build_context.vet_packages.entries.count == 0) { + return true; + } + return string_set_exists(&build_context.vet_packages, file->pkg->name); +} + gb_internal u64 ast_file_vet_flags(AstFile *f) { if (f != nullptr && f->vet_flags_set) { return f->vet_flags; } - return build_context.vet_flags; + + bool found = in_vet_packages(f); + if (found) { + return build_context.vet_flags; + } + return 0; } gb_internal bool ast_file_vet_style(AstFile *f) { @@ -5372,18 +5390,9 @@ gb_internal Ast *parse_stmt(AstFile *f) { } - -gb_internal u64 check_vet_flags(AstFile *file) { - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; -} - - gb_internal void parse_enforce_tabs(AstFile *f) { // Checks to see if tabs have been used for indentation - if ((check_vet_flags(f) & VetFlag_Tabs) == 0) { + if ((ast_file_vet_flags(f) & VetFlag_Tabs) == 0) { return; } -- cgit v1.2.3 From 29fedc1808c347774971666ecc90b44e1900dc90 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Tue, 17 Sep 2024 19:39:48 +0200 Subject: Changed some recently added //+ usages to #+ and also fixed some //+ usages in some code generators. --- core/testing/runner_windows.odin | 2 +- src/parser.cpp | 2 +- tests/core/sys/windows/win32gen/win32gen.cpp | 2 +- tests/documentation/documentation_tester.odin | 2 +- vendor/box2d/box2d_wasm.odin | 2 +- vendor/cgltf/cgltf_wasm.odin | 2 +- vendor/stb/image/stb_image_wasm.odin | 2 +- vendor/stb/rect_pack/stb_rect_pack_wasm.odin | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index fa233ff84..401804c71 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing import win32 "core:sys/windows" diff --git a/src/parser.cpp b/src/parser.cpp index 242671af0..520a23c5a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4967,7 +4967,7 @@ gb_internal Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) { } if (f->in_when_statement) { - syntax_error(import_name, "Cannot use 'import' within a 'when' statement. Prefer using the file suffixes (e.g. foo_windows.odin) or '//+build' tags"); + syntax_error(import_name, "Cannot use 'import' within a 'when' statement. Prefer using the file suffixes (e.g. foo_windows.odin) or '#+build' tags"); } if (kind != ImportDecl_Standard) { diff --git a/tests/core/sys/windows/win32gen/win32gen.cpp b/tests/core/sys/windows/win32gen/win32gen.cpp index 731173aa6..57a094cd5 100644 --- a/tests/core/sys/windows/win32gen/win32gen.cpp +++ b/tests/core/sys/windows/win32gen/win32gen.cpp @@ -902,7 +902,7 @@ static void verify_error_helpers(ofstream& out) { } static void test_core_sys_windows(ofstream& out) { - out << "//+build windows" << endl + out << "#+build windows" << endl << "package " << __func__ << " // generated by " << path(__FILE__).filename().replace_extension("").string() << endl << endl diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index ce1849e1c..7b125d4e4 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -264,7 +264,7 @@ write_test_suite :: proc(example_tests: []Example_Test) { test_runner := strings.builder_make() strings.write_string(&test_runner, -`//+private +`#+private package documentation_verification import "core:os" diff --git a/vendor/box2d/box2d_wasm.odin b/vendor/box2d/box2d_wasm.odin index eab369a0d..0fcaf753f 100644 --- a/vendor/box2d/box2d_wasm.odin +++ b/vendor/box2d/box2d_wasm.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package vendor_box2d @(require) import _ "vendor:libc" diff --git a/vendor/cgltf/cgltf_wasm.odin b/vendor/cgltf/cgltf_wasm.odin index f2da86a2c..fb612b2ac 100644 --- a/vendor/cgltf/cgltf_wasm.odin +++ b/vendor/cgltf/cgltf_wasm.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package cgltf @(require) import _ "vendor:libc" diff --git a/vendor/stb/image/stb_image_wasm.odin b/vendor/stb/image/stb_image_wasm.odin index 77bb44f02..f43012383 100644 --- a/vendor/stb/image/stb_image_wasm.odin +++ b/vendor/stb/image/stb_image_wasm.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package stb_image @(require) import _ "vendor:libc" diff --git a/vendor/stb/rect_pack/stb_rect_pack_wasm.odin b/vendor/stb/rect_pack/stb_rect_pack_wasm.odin index fb75552ec..c4e2e5160 100644 --- a/vendor/stb/rect_pack/stb_rect_pack_wasm.odin +++ b/vendor/stb/rect_pack/stb_rect_pack_wasm.odin @@ -1,4 +1,4 @@ -//+build wasm32, wasm64p32 +#+build wasm32, wasm64p32 package stb_rect_pack @(require) import _ "vendor:libc" -- cgit v1.2.3 From 84700e09c9903e5e40badb829b599dd67886f68c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 20 Sep 2024 19:02:00 -0400 Subject: Forbid parsing more fields if no separator was found Fixes #4278 --- src/parser.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 520a23c5a..5b4604c6d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4377,10 +4377,14 @@ gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_fl } } - allow_field_separator(f); + bool more_fields = allow_field_separator(f); Ast *param = ast_field(f, names, type, default_value, set_flags, tag, docs, f->line_comment); array_add(¶ms, param); + if (!more_fields) { + if (name_count_) *name_count_ = total_name_count; + return ast_field_list(f, start_token, params); + } while (f->curr_token.kind != follow && f->curr_token.kind != Token_EOF && -- cgit v1.2.3 From a7d7c92a5302a9d0db503af37fe96c737a536544 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 Sep 2024 13:05:28 +0100 Subject: `#min_field_align` & `#max_field_align`; deprecate `#field_align` in favour of `#min_field_align` --- core/odin/ast/ast.odin | 23 ++++++++++---------- core/odin/ast/clone.odin | 3 ++- core/odin/parser/parser.odin | 43 ++++++++++++++++++++++++------------- src/check_type.cpp | 31 +++++++++++++++++++++------ src/parser.cpp | 50 ++++++++++++++++++++++++++++++++++---------- src/parser.hpp | 3 ++- src/types.cpp | 12 +++++++---- 7 files changed, 116 insertions(+), 49 deletions(-) (limited to 'src/parser.cpp') diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index d67eb31f3..f62feec8c 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -776,17 +776,18 @@ Dynamic_Array_Type :: struct { Struct_Type :: struct { using node: Expr, - tok_pos: tokenizer.Pos, - poly_params: ^Field_List, - align: ^Expr, - field_align: ^Expr, - where_token: tokenizer.Token, - where_clauses: []^Expr, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - fields: ^Field_List, - name_count: int, + tok_pos: tokenizer.Pos, + poly_params: ^Field_List, + align: ^Expr, + min_field_align: ^Expr, + max_field_align: ^Expr, + where_token: tokenizer.Token, + where_clauses: []^Expr, + is_packed: bool, + is_raw_union: bool, + is_no_copy: bool, + fields: ^Field_List, + name_count: int, } Union_Type_Kind :: enum u8 { diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index b0a1673b2..67f7ffa95 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -316,7 +316,8 @@ clone_node :: proc(node: ^Node) -> ^Node { case ^Struct_Type: r.poly_params = auto_cast clone(r.poly_params) r.align = clone(r.align) - r.field_align = clone(r.field_align) + r.min_field_align = clone(r.min_field_align) + r.max_field_align = clone(r.max_field_align) r.fields = auto_cast clone(r.fields) case ^Union_Type: r.poly_params = auto_cast clone(r.poly_params) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index d42766bde..32246be3a 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2610,9 +2610,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Struct: tok := expect_token(p, .Struct) - poly_params: ^ast.Field_List - align: ^ast.Expr - field_align: ^ast.Expr + poly_params: ^ast.Field_List + align: ^ast.Expr + min_field_align: ^ast.Expr + max_field_align: ^ast.Expr is_packed: bool is_raw_union: bool is_no_copy: bool @@ -2645,10 +2646,21 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } align = parse_expr(p, true) case "field_align": - if field_align != nil { + if min_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + warn(p, tag.pos, "#field_align has been deprecated in favour of #min_field_align") + min_field_align = parse_expr(p, true) + case "min_field_align": + if min_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + min_field_align = parse_expr(p, true) + case "max_field_align": + if max_field_align != nil { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) } - field_align = parse_expr(p, true) + max_field_align = parse_expr(p, true) case "raw_union": if is_raw_union { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) @@ -2689,16 +2701,17 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_closing_brace_of_field_list(p) st := ast.new(ast.Struct_Type, tok.pos, end_pos(close)) - st.poly_params = poly_params - st.align = align - st.field_align = field_align - st.is_packed = is_packed - st.is_raw_union = is_raw_union - st.is_no_copy = is_no_copy - st.fields = fields - st.name_count = name_count - st.where_token = where_token - st.where_clauses = where_clauses + st.poly_params = poly_params + st.align = align + st.min_field_align = min_field_align + st.max_field_align = max_field_align + st.is_packed = is_packed + st.is_raw_union = is_raw_union + st.is_no_copy = is_no_copy + st.fields = fields + st.name_count = name_count + st.where_token = where_token + st.where_clauses = where_clauses return st case .Union: diff --git a/src/check_type.cpp b/src/check_type.cpp index f0e0acb9b..bbeff9ca7 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -673,7 +673,7 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * #define ST_ALIGN(_name) if (st->_name != nullptr) { \ if (st->is_packed) { \ - syntax_error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ + error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ return; \ } \ i64 align = 1; \ @@ -682,12 +682,31 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * } \ } - ST_ALIGN(field_align); + ST_ALIGN(min_field_align); + ST_ALIGN(max_field_align); ST_ALIGN(align); - if (struct_type->Struct.custom_align < struct_type->Struct.custom_field_align) { - warning(st->align, "#align(%lld) is defined to be less than #field_name(%lld)", - cast(long long)struct_type->Struct.custom_align, - cast(long long)struct_type->Struct.custom_field_align); + if (struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { + error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_min_field_align); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_max_field_align); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_min_field_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#min_field_align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_min_field_align, + cast(long long)struct_type->Struct.custom_max_field_align); + + i64 a = gb_min(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + i64 b = gb_max(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + // NOTE(bill): sort them to keep code consistent + struct_type->Struct.custom_min_field_align = a; + struct_type->Struct.custom_max_field_align = b; } #undef ST_ALIGN diff --git a/src/parser.cpp b/src/parser.cpp index 5b4604c6d..4029d49de 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -448,7 +448,8 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->StructType.fields = clone_ast_array(n->StructType.fields, f); n->StructType.polymorphic_params = clone_ast(n->StructType.polymorphic_params, f); n->StructType.align = clone_ast(n->StructType.align, f); - n->StructType.field_align = clone_ast(n->StructType.field_align, f); + n->StructType.min_field_align = clone_ast(n->StructType.min_field_align, f); + n->StructType.max_field_align = clone_ast(n->StructType.max_field_align, f); n->StructType.where_clauses = clone_ast_array(n->StructType.where_clauses, f); break; case Ast_UnionType: @@ -1217,7 +1218,7 @@ gb_internal Ast *ast_dynamic_array_type(AstFile *f, Token token, Ast *elem) { gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice fields, isize field_count, Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_no_copy, - Ast *align, Ast *field_align, + Ast *align, Ast *min_field_align, Ast *max_field_align, Token where_token, Array const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_StructType); result->StructType.token = token; @@ -1228,7 +1229,8 @@ gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice fields, i result->StructType.is_raw_union = is_raw_union; result->StructType.is_no_copy = is_no_copy; result->StructType.align = align; - result->StructType.field_align = field_align; + result->StructType.min_field_align = min_field_align; + result->StructType.max_field_align = max_field_align; result->StructType.where_token = where_token; result->StructType.where_clauses = slice_from_array(where_clauses); return result; @@ -2757,7 +2759,8 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { bool is_raw_union = false; bool no_copy = false; Ast *align = nullptr; - Ast *field_align = nullptr; + Ast *min_field_align = nullptr; + Ast *max_field_align = nullptr; if (allow_token(f, Token_OpenParen)) { isize param_count = 0; @@ -2795,18 +2798,43 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { gb_string_free(s); } } else if (tag.string == "field_align") { - if (field_align) { + if (min_field_align) { syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); } - field_align = parse_expr(f, true); - if (field_align && field_align->kind != Ast_ParenExpr) { + syntax_warning(tag, "#field_align has been deprecated in favour of #min_field_align"); + min_field_align = parse_expr(f, true); + if (min_field_align && min_field_align->kind != Ast_ParenExpr) { ERROR_BLOCK(); - gbString s = expr_to_string(field_align); + gbString s = expr_to_string(min_field_align); syntax_warning(tag, "#field_align requires parentheses around the expression"); - error_line("\tSuggestion: #field_align(%s)", s); + error_line("\tSuggestion: #min_field_align(%s)", s); gb_string_free(s); } - } else if (tag.string == "raw_union") { + } else if (tag.string == "min_field_align") { + if (min_field_align) { + syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); + } + min_field_align = parse_expr(f, true); + if (min_field_align && min_field_align->kind != Ast_ParenExpr) { + ERROR_BLOCK(); + gbString s = expr_to_string(min_field_align); + syntax_warning(tag, "#min_field_align requires parentheses around the expression"); + error_line("\tSuggestion: #min_field_align(%s)", s); + gb_string_free(s); + } + } else if (tag.string == "max_field_align") { + if (max_field_align) { + syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); + } + max_field_align = parse_expr(f, true); + if (max_field_align && max_field_align->kind != Ast_ParenExpr) { + ERROR_BLOCK(); + gbString s = expr_to_string(max_field_align); + syntax_warning(tag, "#max_field_align requires parentheses around the expression"); + error_line("\tSuggestion: #max_field_align(%s)", s); + gb_string_free(s); + } + }else if (tag.string == "raw_union") { if (is_raw_union) { syntax_error(tag, "Duplicate struct tag '#%.*s'", LIT(tag.string)); } @@ -2856,7 +2884,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { parser_check_polymorphic_record_parameters(f, polymorphic_params); - return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, field_align, where_token, where_clauses); + return ast_struct_type(f, token, decls, name_count, polymorphic_params, is_packed, is_raw_union, no_copy, align, min_field_align, max_field_align, where_token, where_clauses); } break; case Token_union: { diff --git a/src/parser.hpp b/src/parser.hpp index d5117a31e..e332fed50 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -736,7 +736,8 @@ AST_KIND(_TypeBegin, "", bool) \ isize field_count; \ Ast *polymorphic_params; \ Ast *align; \ - Ast *field_align; \ + Ast *min_field_align; \ + Ast *max_field_align; \ Token where_token; \ Slice where_clauses; \ bool is_packed; \ diff --git a/src/types.cpp b/src/types.cpp index a9a7d6dda..ec9917506 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -137,7 +137,8 @@ struct TypeStruct { Scope * scope; i64 custom_align; - i64 custom_field_align; + i64 custom_min_field_align; + i64 custom_max_field_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; Wait_Signal polymorphic_wait_signal; @@ -3950,7 +3951,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align); } -gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_packed, bool is_raw_union, i64 min_field_align) { +gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_packed, bool is_raw_union, i64 min_field_align, i64 max_field_align) { gbAllocator a = permanent_allocator(); auto offsets = gb_alloc_array(a, i64, fields.count); i64 curr_offset = 0; @@ -3980,6 +3981,9 @@ gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_pack } else { Type *t = fields[i]->type; i64 align = gb_max(type_align_of(t), min_field_align); + if (max_field_align > min_field_align) { + align = gb_min(align, max_field_align); + } i64 size = gb_max(type_size_of( t), 0); curr_offset = align_formula(curr_offset, align); offsets[i] = curr_offset; @@ -3996,7 +4000,7 @@ gb_internal bool type_set_offsets(Type *t) { MUTEX_GUARD(&t->Struct.offset_mutex); if (!t->Struct.are_offsets_set) { t->Struct.are_offsets_being_processed = true; - t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union, t->Struct.custom_field_align); + t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union, t->Struct.custom_min_field_align, t->Struct.custom_max_field_align); t->Struct.are_offsets_being_processed = false; t->Struct.are_offsets_set = true; return true; @@ -4005,7 +4009,7 @@ gb_internal bool type_set_offsets(Type *t) { MUTEX_GUARD(&t->Tuple.mutex); if (!t->Tuple.are_offsets_set) { t->Tuple.are_offsets_being_processed = true; - t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false, 1); + t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false, 1, 0); t->Tuple.are_offsets_being_processed = false; t->Tuple.are_offsets_set = true; return true; -- cgit v1.2.3 From b59647084b11a7f08ad3aa54ae47ceb157f12c46 Mon Sep 17 00:00:00 2001 From: bobsayshilol Date: Sun, 27 Oct 2024 20:26:34 +0000 Subject: Plug a memory leak The call to |array_make()| always allocates and since this variable was unused it lead to a leak. Simply plug it by removing it. --- src/parser.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 4029d49de..9d8b0d231 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4262,8 +4262,6 @@ gb_internal bool allow_field_separator(AstFile *f) { gb_internal Ast *parse_struct_field_list(AstFile *f, isize *name_count_) { Token start_token = f->curr_token; - auto decls = array_make(ast_allocator(f)); - isize total_name_count = 0; Ast *params = parse_field_list(f, &total_name_count, FieldFlag_Struct, Token_CloseBrace, false, false); -- cgit v1.2.3 From b3d1d7b835fa5b05620f91420838e4dcdee65dfd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 14 Nov 2024 16:08:53 +0000 Subject: Make `#relative` types an error in parsing --- src/parser.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index 9d8b0d231..aa90651d3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2488,6 +2488,7 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { tag = parse_call_expr(f, tag); } Ast *type = parse_type(f); + syntax_error(tag, "#relative types have now been removed in favour of \"core:relative\""); return ast_relative_type(f, tag, type); } else if (name.string == "force_inline" || name.string == "force_no_inline") { -- cgit v1.2.3 From 2efe4c2d68f486006e405ba7d30be03ec121ae6c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 5 Jan 2025 13:19:10 +0000 Subject: Add `#+feature dynamic-literals` --- src/build_settings.cpp | 12 +++++++++ src/check_expr.cpp | 10 ++++++-- src/parser.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/parser.hpp | 2 ++ 4 files changed, 88 insertions(+), 2 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4c3f4b782..a8261612e 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -324,6 +324,18 @@ u64 get_vet_flag_from_name(String const &name) { return VetFlag_NONE; } +enum OptInFeatureFlags : u64 { + OptInFeatureFlag_NONE = 0, + OptInFeatureFlag_DynamicLiterals = 1u<<0, +}; + +u64 get_feature_flag_from_name(String const &name) { + if (name == "dynamic-literals") { + return OptInFeatureFlag_DynamicLiterals; + } + return OptInFeatureFlag_NONE; +} + enum SanitizerFlags : u32 { SanitizerFlag_NONE = 0, diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fba9b8dad..fb3040e71 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9730,8 +9730,11 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (t->kind == Type_DynamicArray) { - if (build_context.no_dynamic_literals && cl->elems.count) { + if (build_context.no_dynamic_literals && cl->elems.count && (node->file()->feature_flags & OptInFeatureFlag_DynamicLiterals) != 0) { + ERROR_BLOCK(); error(node, "Compound literals of dynamic types have been disabled"); + error_line("\tSuggestion: If you want to enable them for this specific file, use '#+feature dynamic-literals' at the top of the file\n"); + error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); } } @@ -10120,8 +10123,11 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } } - if (build_context.no_dynamic_literals && cl->elems.count) { + if (build_context.no_dynamic_literals && cl->elems.count && (node->file()->feature_flags & OptInFeatureFlag_DynamicLiterals) != 0) { + ERROR_BLOCK(); error(node, "Compound literals of dynamic types have been disabled"); + error_line("\tSuggestion: If you want to enable them for this specific file, use '#+feature dynamic-literals' at the top of the file\n"); + error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); } else { add_map_reserve_dependencies(c); add_map_set_dependencies(c); diff --git a/src/parser.cpp b/src/parser.cpp index aa90651d3..01ed46ebc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6265,10 +6265,16 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s) { syntax_error(token_for_pos, "Invalid vet flag name: %.*s", LIT(p)); error_line("\tExpected one of the following\n"); error_line("\tunused\n"); + error_line("\tunused-variables\n"); + error_line("\tunused-imports\n"); + error_line("\tunused-procedures\n"); error_line("\tshadowing\n"); error_line("\tusing-stmt\n"); error_line("\tusing-param\n"); + error_line("\tstyle\n"); error_line("\textra\n"); + error_line("\tcast\n"); + error_line("\ttabs\n"); return build_context.vet_flags; } } @@ -6286,6 +6292,63 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s) { return vet_flags &~ vet_not_flags; } +gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { + String const prefix = str_lit("feature"); + GB_ASSERT(string_starts_with(s, prefix)); + s = string_trim_whitespace(substring(s, prefix.len, s.len)); + + if (s.len == 0) { + return OptInFeatureFlag_NONE; + } + + u64 feature_flags = 0; + u64 feature_not_flags = 0; + + while (s.len > 0) { + String p = string_trim_whitespace(vet_tag_get_token(s, &s)); + if (p.len == 0) { + break; + } + + bool is_notted = false; + if (p[0] == '!') { + is_notted = true; + p = substring(p, 1, p.len); + if (p.len == 0) { + syntax_error(token_for_pos, "Expected a feature flag name after '!'"); + return OptInFeatureFlag_NONE; + } + } + + u64 flag = get_vet_flag_from_name(p); + if (flag != OptInFeatureFlag_NONE) { + if (is_notted) { + feature_not_flags |= flag; + } else { + feature_flags |= flag; + } + } else { + ERROR_BLOCK(); + syntax_error(token_for_pos, "Invalid feature flag name: %.*s", LIT(p)); + error_line("\tExpected one of the following\n"); + error_line("\tdynamic-literals\n"); + return OptInFeatureFlag_NONE; + } + } + + if (feature_flags == 0 && feature_not_flags == 0) { + return OptInFeatureFlag_NONE; + } + if (feature_flags == 0 && feature_not_flags != 0) { + return OptInFeatureFlag_NONE &~ feature_not_flags; + } + if (feature_flags != 0 && feature_not_flags == 0) { + return feature_flags; + } + GB_ASSERT(feature_flags != 0 && feature_not_flags != 0); + return feature_flags &~ feature_not_flags; +} + gb_internal String dir_from_path(String path) { String base_dir = path; for (isize i = path.len-1; i >= 0; i--) { @@ -6409,6 +6472,9 @@ gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) } } else if (lc == "no-instrumentation") { f->flags |= AstFile_NoInstrumentation; + } else if (string_starts_with(lc, str_lit("feature"))) { + f->feature_flags = parse_feature_tag(tok, lc); + f->feature_flags_set = true; } else { error(tok, "Unknown tag '%.*s'", LIT(lc)); } diff --git a/src/parser.hpp b/src/parser.hpp index e332fed50..bbf70d03e 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -108,7 +108,9 @@ struct AstFile { String package_name; u64 vet_flags; + u64 feature_flags; bool vet_flags_set; + bool feature_flags_set; // >= 0: In Expression // < 0: In Control Clause -- cgit v1.2.3 From bca08d3b85f59c35f4eb43731099bc96730b12cd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 5 Jan 2025 13:22:27 +0000 Subject: Make `-no-dynamic-literals` the default now --- examples/demo/demo.odin | 1 + src/build_settings.cpp | 6 ------ src/check_expr.cpp | 37 +++++++++++++++++++++---------------- src/checker.cpp | 18 +++++++++++++++++- src/llvm_backend.cpp | 2 -- src/llvm_backend_expr.cpp | 4 ++-- src/main.cpp | 2 +- src/parser.cpp | 4 ++-- 8 files changed, 44 insertions(+), 30 deletions(-) (limited to 'src/parser.cpp') diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 36d1359ca..82b047103 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1,4 +1,5 @@ #+vet !using-stmt !using-param +#+feature dynamic-literals package main import "core:fmt" diff --git a/src/build_settings.cpp b/src/build_settings.cpp index a8261612e..93168cf77 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -441,7 +441,6 @@ struct BuildContext { bool ignore_unknown_attributes; bool no_bounds_check; bool no_type_assert; - bool no_dynamic_literals; bool no_output_files; bool no_crt; bool no_rpath; @@ -1867,11 +1866,6 @@ gb_internal bool init_build_paths(String init_filename) { produces_output_file = true; } - if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR || - build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { - bc->no_dynamic_literals = true; - } - if (!produces_output_file) { // Command doesn't produce output files. We're done. return true; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fb3040e71..ba021a98c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9351,6 +9351,23 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) { return false; } +gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) { + if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) { + ERROR_BLOCK(); + error(node, "Compound literals of dynamic types are disabled by default"); + error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n"); + error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + error_line("\tWarning: As '-default-to-panic-allocator' has been set, the dynamic compound literal may not be initialized as expected\n"); + } else if (build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + error_line("\tWarning: As '-default-to-panic-allocator' has been set, the dynamic compound literal may not be initialized as expected\n"); + } + return false; + } + + return cl->elems.count > 0; +} + gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { ExprKind kind = Expr_Expr; ast_node(cl, CompoundLit, node); @@ -9551,11 +9568,6 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * elem_type = t->DynamicArray.elem; context_name = str_lit("dynamic array literal"); is_constant = false; - - if (!build_context.no_dynamic_literals) { - add_package_dependency(c, "runtime", "__dynamic_array_reserve"); - add_package_dependency(c, "runtime", "__dynamic_array_append"); - } } else if (t->kind == Type_SimdVector) { elem_type = t->SimdVector.elem; context_name = str_lit("simd vector literal"); @@ -9730,11 +9742,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (t->kind == Type_DynamicArray) { - if (build_context.no_dynamic_literals && cl->elems.count && (node->file()->feature_flags & OptInFeatureFlag_DynamicLiterals) != 0) { - ERROR_BLOCK(); - error(node, "Compound literals of dynamic types have been disabled"); - error_line("\tSuggestion: If you want to enable them for this specific file, use '#+feature dynamic-literals' at the top of the file\n"); - error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); + if (check_for_dynamic_literals(c, node, cl)) { + add_package_dependency(c, "runtime", "__dynamic_array_reserve"); + add_package_dependency(c, "runtime", "__dynamic_array_append"); } } @@ -10123,12 +10133,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } } - if (build_context.no_dynamic_literals && cl->elems.count && (node->file()->feature_flags & OptInFeatureFlag_DynamicLiterals) != 0) { - ERROR_BLOCK(); - error(node, "Compound literals of dynamic types have been disabled"); - error_line("\tSuggestion: If you want to enable them for this specific file, use '#+feature dynamic-literals' at the top of the file\n"); - error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); - } else { + if (check_for_dynamic_literals(c, node, cl)) { add_map_reserve_dependencies(c); add_map_set_dependencies(c); } diff --git a/src/checker.cpp b/src/checker.cpp index 7e0a64d75..5d3263789 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -542,6 +542,23 @@ gb_internal u64 check_vet_flags(Ast *node) { return ast_file_vet_flags(file); } +gb_internal u64 check_feature_flags(CheckerContext *c, Ast *node) { + AstFile *file = c->file; + if (file == nullptr && + c->curr_proc_decl && + c->curr_proc_decl->proc_lit) { + file = c->curr_proc_decl->proc_lit->file(); + } + if (file == nullptr) { + file = node->file(); + } + if (file != nullptr && file->feature_flags_set) { + return file->feature_flags; + } + return 0; +} + + enum VettedEntityKind { VettedEntity_Invalid, @@ -1164,7 +1181,6 @@ gb_internal void init_universal(void) { 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); add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 01ded321e..696ced0df 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1096,8 +1096,6 @@ gb_internal void lb_internal_dynamic_map_set(lbProcedure *p, lbValue const &map_ } gb_internal lbValue lb_dynamic_map_reserve(lbProcedure *p, lbValue const &map_ptr, isize const capacity, TokenPos const &pos) { - GB_ASSERT(!build_context.no_dynamic_literals); - TEMPORARY_ALLOCATOR_GUARD(); String proc_name = {}; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 3b238bcd8..df9dca801 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4813,7 +4813,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (cl->elems.count == 0) { break; } - GB_ASSERT(!build_context.no_dynamic_literals); + GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos); gb_unused(err); @@ -4902,7 +4902,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (cl->elems.count == 0) { break; } - GB_ASSERT(!build_context.no_dynamic_literals); + GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); Type *et = bt->DynamicArray.elem; lbValue size = lb_const_int(p->module, t_int, type_size_of(et)); diff --git a/src/main.cpp b/src/main.cpp index 0450c61ec..41c7170f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1192,7 +1192,7 @@ gb_internal bool parse_build_flags(Array args) { build_context.no_type_assert = true; break; case BuildFlag_NoDynamicLiterals: - build_context.no_dynamic_literals = true; + gb_printf_err("Warning: Use of -no-dynamic-literals is now redundant\n"); break; case BuildFlag_NoCRT: build_context.no_crt = true; diff --git a/src/parser.cpp b/src/parser.cpp index 01ed46ebc..e190bc5a5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6320,7 +6320,7 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { } } - u64 flag = get_vet_flag_from_name(p); + u64 flag = get_feature_flag_from_name(p); if (flag != OptInFeatureFlag_NONE) { if (is_notted) { feature_not_flags |= flag; @@ -6473,7 +6473,7 @@ gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) } else if (lc == "no-instrumentation") { f->flags |= AstFile_NoInstrumentation; } else if (string_starts_with(lc, str_lit("feature"))) { - f->feature_flags = parse_feature_tag(tok, lc); + f->feature_flags |= parse_feature_tag(tok, lc); f->feature_flags_set = true; } else { error(tok, "Unknown tag '%.*s'", LIT(lc)); -- cgit v1.2.3 From 7da7d4e4103d20d757a371f614d3343f8fd15c85 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 5 Jan 2025 15:41:51 +0000 Subject: Allow `#+` tags on single files --- src/parser.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/parser.cpp') diff --git a/src/parser.cpp b/src/parser.cpp index e190bc5a5..03c5a5962 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6462,6 +6462,9 @@ gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) } else if (command == "file") { f->flags |= AstFile_IsPrivateFile; } + } else if (string_starts_with(lc, str_lit("feature"))) { + f->feature_flags |= parse_feature_tag(tok, lc); + f->feature_flags_set = true; } else if (lc == "lazy") { if (build_context.ignore_lazy) { // Ignore @@ -6472,9 +6475,6 @@ gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) } } else if (lc == "no-instrumentation") { f->flags |= AstFile_NoInstrumentation; - } else if (string_starts_with(lc, str_lit("feature"))) { - f->feature_flags |= parse_feature_tag(tok, lc); - f->feature_flags_set = true; } else { error(tok, "Unknown tag '%.*s'", LIT(lc)); } @@ -6559,9 +6559,7 @@ gb_internal bool parse_file(Parser *p, AstFile *f) { } f->package_name = package_name.string; - // TODO: Shouldn't single file only matter for build tags? no-instrumentation for example - // should be respected even when in single file mode. - if (!f->pkg->is_single_file) { + { if (docs != nullptr && docs->list.count > 0) { for (Token const &tok : docs->list) { GB_ASSERT(tok.kind == Token_Comment); -- cgit v1.2.3