From 711f08a6419fa5ce3a5c612a966bc05a7d961728 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 7 Oct 2026 21:58:32 +0100 Subject: [PATCH] Make `is_type_lock_free` follow what each target lowers without library calls, and reject atomics on types which are not lock-free --- core/rexcode/isa/x86/encoder_match.odin | 12 +++-- src/check_builtin.cpp | 10 +++++ src/check_expr.cpp | 14 ------ src/types.cpp | 47 ++++++++++++++++++-- tests/core/encoding/cbor/test_core_cbor.odin | 28 ++++++++++++ 5 files changed, 90 insertions(+), 21 deletions(-) diff --git a/core/rexcode/isa/x86/encoder_match.odin b/core/rexcode/isa/x86/encoder_match.odin index 7f758ca0a..a025a26b4 100644 --- a/core/rexcode/isa/x86/encoder_match.odin +++ b/core/rexcode/isa/x86/encoder_match.odin @@ -102,16 +102,20 @@ match_key :: #force_inline proc "contextless" (inst: ^Instruction) -> (key: u64, @(private, require_results) match_cache_get :: #force_inline proc "contextless" (key: u64) -> (form_index: int, hit: bool) { - v := intrinsics.atomic_load_explicit(&MATCH_CACHE[match_hash(key)], .Relaxed) - if v != 0 && (v >> 16) == key { - return int(v & 0xFFFF) - 1, true + when intrinsics.atomic_type_is_lock_free(u64) { + v := intrinsics.atomic_load_explicit(&MATCH_CACHE[match_hash(key)], .Relaxed) + if v != 0 && (v >> 16) == key { + return int(v & 0xFFFF) - 1, true + } } return -1, false } @(private) match_cache_put :: #force_inline proc "contextless" (key: u64, form_index: int) { - intrinsics.atomic_store_explicit(&MATCH_CACHE[match_hash(key)], (key << 16) | u64(form_index + 1), .Relaxed) + when intrinsics.atomic_type_is_lock_free(u64) { + intrinsics.atomic_store_explicit(&MATCH_CACHE[match_hash(key)], (key << 16) | u64(form_index + 1), .Relaxed) + } } // Resolve an instruction to its encoding form on the matcher path: long-mode diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 63ec8c716..3c7cf880a 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2174,6 +2174,16 @@ gb_internal bool check_atomic_ptr_argument(Operand *operand, String const &built error(operand->expr, "Only an integer, floating-point, boolean, or pointer can be used as an atomic for '%.*s'", LIT(builtin_name)); return false; } + if (!target_atomics_are_plain() && !is_type_lock_free(elem)) { + ERROR_BLOCK(); + gbString str = type_to_string(elem); + error(operand->expr, "'%s' cannot be used as an atomic for '%.*s' on this target, as it is not lock-free", str, LIT(builtin_name)); + gb_string_free(str); + if (build_context.metrics.arch == TargetArch_amd64 && type_size_of(elem) == 16) { + error_line("\tSuggestion: A 16 byte atomic needs 'cx16', e.g. with -microarch:x86-64-v2 or later\n"); + } + return false; + } // the address, through any conversions of it Ast *ptr = unparen_expr(operand->expr); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 1bc889aec..6af3b394f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6600,20 +6600,6 @@ gb_internal bool is_type_normal_pointer(Type *ptr, Type **elem) { return false; } -gb_internal bool is_type_valid_atomic_type(Type *elem) { - elem = core_type(elem); - if (is_type_internally_pointer_like(elem)) { - return true; - } - if (elem->kind == Type_BitSet) { - elem = bit_set_to_int(elem); - } - if (elem->kind != Type_Basic) { - return false; - } - return (elem->Basic.flags & (BasicFlag_Boolean|BasicFlag_OrderedNumeric)) != 0; -} - gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = false, Scope **out_scope = nullptr) { switch (node->kind) { case_ast_node(i, Ident, node); diff --git a/src/types.cpp b/src/types.cpp index 8a3189cbf..02720149a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2901,14 +2901,55 @@ gb_internal bool elem_cannot_be_constant(Type *t) { } +gb_internal i64 target_max_atomic_size(void) { + switch (build_context.metrics.arch) { + case TargetArch_amd64: + if (check_target_feature_is_enabled(str_lit("cx16"), nullptr)) { + return 16; + } + return 8; + case TargetArch_arm64: + return 16; + case TargetArch_wasm32: + case TargetArch_wasm64p32: + case TargetArch_i386: + case TargetArch_arm32: + case TargetArch_riscv64: + return 8; + } + return build_context.ptr_size; +} + +gb_internal bool is_type_valid_atomic_type(Type *elem) { + elem = core_type(elem); + if (is_type_internally_pointer_like(elem)) { + return true; + } + if (elem->kind == Type_BitSet) { + elem = bit_set_to_int(elem); + } + if (elem->kind != Type_Basic) { + return false; + } + return (elem->Basic.flags & (BasicFlag_Boolean|BasicFlag_OrderedNumeric)) != 0; +} + +gb_internal bool target_atomics_are_plain(void) { + return is_arch_wasm() && !check_target_feature_is_enabled(str_lit("atomics"), nullptr); +} + gb_internal bool is_type_lock_free(Type *t) { t = core_type(t); if (t == t_invalid) { return false; } - i64 sz = type_size_of(t); - // TODO(bill): Figure this out correctly - return sz <= build_context.max_align; + i64 size = type_size_of(t); + if (target_atomics_are_plain() || !is_type_valid_atomic_type(t)) { + return size <= build_context.max_align; + } + return size <= target_max_atomic_size() && + (size & (size-1)) == 0 && + type_align_of(t) >= size; } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 37c27eebd..43eb3edeb 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -403,6 +403,34 @@ test_marshalling_union :: proc(t: ^testing.T) { } } +@(test) +test_marshalling_deterministic_map :: proc(t: ^testing.T) { + // Keys that are not strings are each encoded into their own buffer to be sorted. + m := map[int]int{1 = 10, 2 = 20, 300 = 30, -4 = 40} + defer delete(m) + + data, err := cbor.marshal(m, cbor.ENCODE_FULLY_DETERMINISTIC) + defer delete(data) + testing.expect_value(t, err, nil) + + val, derr := cbor.decode(string(data)) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) + + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, "{1: 10, 2: 20, 300: 30, -4: 40}") + + dest: map[int]int + defer delete(dest) + uerr := cbor.unmarshal(string(data), &dest) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, len(dest), len(m)) + for k, v in m { + testing.expect_value(t, dest[k], v) + } +} + @(test) test_lying_length_array :: proc(t: ^testing.T) { // Input says this is an array of length max(u64), this should not allocate that amount.