From 3ea27c697953ad73396f2a82666885e2fc837382 Mon Sep 17 00:00:00 2001 From: Lucas Czerny Date: Sun, 9 Aug 2026 16:49:37 +0200 Subject: [PATCH 01/93] Update user32.odin --- core/sys/windows/user32.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index d2f54b127..f137dc22c 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -34,6 +34,7 @@ foreign user32 { UnregisterClassW :: proc(lpClassName: LPCWSTR, hInstance: HINSTANCE) -> BOOL --- RegisterHotKey :: proc(hnwd: HWND, id: c_int, fsModifiers: UINT, vk: UINT) -> BOOL --- + UnregisterHotKey :: proc(hnwd: HWND, id: c_int) -> BOOL --- CreateWindowExW :: proc( dwExStyle: DWORD, From 046db295a6d5cd8787b49cad3a2b22fef89d3acb Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Sun, 9 Aug 2026 13:30:04 -0400 Subject: [PATCH 02/93] Fix unparenthesized attributes missing end position in its AST node --- core/odin/parser/parser.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 3b2a1481e..8539ab2ab 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1110,6 +1110,7 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind: open, close: tokenizer.Token if p.curr_tok.kind == .Ident { + close = p.curr_tok elem := parse_ident(p) append(&elems, elem) } else { From acb8806a9611b2aff3b325327f5c5344c8100f66 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 9 Aug 2026 11:00:03 -0700 Subject: [PATCH 03/93] fix rol typo --- src/name_canonicalization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 13fa01830..ab9f33d1d 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -284,7 +284,7 @@ void typeid_hash_context_init(TypeidHashContext *hash_ctx) { u64 rotate_left64(u64 x, u64 k) { static u64 const n = 64; u64 s = k & (n-1); - return (x<>(n-2)); + return (x<>(n-s)); } void sip_compress(SipHashContext *sip) { From f26ed4dccd5522170a5c9710bcf1963c0141a7fd Mon Sep 17 00:00:00 2001 From: nic-vdwalt <36562088+nic-vdwalt@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:19:41 +0200 Subject: [PATCH 04/93] vendor: add Box3D support for WebAssembly (#7266) vendor: add Box3D support for WebAssembly Generate the WASM object directly from the existing build script so vendor binaries stay out of source control while JS target checks remain reproducible. --- .github/workflows/ci.yml | 4 ++-- .gitignore | 2 ++ examples/all/all_vendor_js.odin | 1 + vendor/box3d/box3d.odin | 9 ++++----- vendor/box3d/box3d_wasm.odin | 4 ++++ vendor/box3d/src/build.sh | 24 ++++++++++++++++++++++++ vendor/box3d/src/wasm_compat.c | 8 ++++++++ vendor/box3d/src/wasm_compat.h | 13 +++++++++++++ 8 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 vendor/box3d/box3d_wasm.odin create mode 100644 vendor/box3d/src/wasm_compat.c create mode 100644 vendor/box3d/src/wasm_compat.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3ae67161..3874a65ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,8 +131,8 @@ jobs: ./vendor/cgltf/src/build_cgltf.sh ./vendor/miniaudio/src/build_miniaudio.sh ./vendor/kb_text_shape/src/build_unix.sh - - name: Compile Box3D (Ubuntu ARM) - if: matrix.os == 'ubuntu-24.04-arm' + - name: Compile Box3D (Ubuntu) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' run: ./vendor/box3d/src/build.sh - name: Odin check run: ./odin check examples/demo -vet diff --git a/.gitignore b/.gitignore index bf0efe2a0..56a75c788 100644 --- a/.gitignore +++ b/.gitignore @@ -319,3 +319,5 @@ build/ cmake-build*/ CMakeLists.txt sandbox/ + +vendor/box3d/lib/box3d_wasm.o diff --git a/examples/all/all_vendor_js.odin b/examples/all/all_vendor_js.odin index d7975541d..128747c05 100644 --- a/examples/all/all_vendor_js.odin +++ b/examples/all/all_vendor_js.odin @@ -2,6 +2,7 @@ package all @(require) import "vendor:box2d" +@(require) import "vendor:box3d" @(require) import "vendor:cgltf" @(require) import "vendor:fontstash" @(require) import "vendor:microui" diff --git a/vendor/box3d/box3d.odin b/vendor/box3d/box3d.odin index 25be6ebad..a7de1325e 100644 --- a/vendor/box3d/box3d.odin +++ b/vendor/box3d/box3d.odin @@ -11,16 +11,15 @@ BOX3D_SHARED :: #config(BOX3D_SHARED, false) @(private) LIB_PATH :: ( - "lib/linux-amd64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .amd64 && !BOX3D_SHARED + "lib/box3d_wasm.o" when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 + else "lib/linux-amd64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .amd64 && !BOX3D_SHARED else "lib/linux-arm64/libbox3d.a" when ODIN_OS == .Linux && ODIN_ARCH == .arm64 && !BOX3D_SHARED else "lib/darwin/libbox3d.a" when ODIN_OS == .Darwin && (ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64) && !BOX3D_SHARED + else "lib/box3d.lib" when ODIN_OS == .Windows else "" ) -when ODIN_OS == .Windows { - @(export) - foreign import lib "lib/box3d.lib" -} else when LIB_PATH != "" { +when LIB_PATH != "" { when !#exists(LIB_PATH) { #panic("Could not find the compiled Box3D library at \"" + LIB_PATH + "\", it can be compiled by running `\"" + ODIN_ROOT + "vendor/box3d/src/build.sh\"`") } diff --git a/vendor/box3d/box3d_wasm.odin b/vendor/box3d/box3d_wasm.odin new file mode 100644 index 000000000..bb61e0a3d --- /dev/null +++ b/vendor/box3d/box3d_wasm.odin @@ -0,0 +1,4 @@ +#+build wasm32, wasm64p32 +package vendor_box3d + +@(require) import _ "vendor:libc-shim" diff --git a/vendor/box3d/src/build.sh b/vendor/box3d/src/build.sh index 0513d8ea5..b116d1672 100755 --- a/vendor/box3d/src/build.sh +++ b/vendor/box3d/src/build.sh @@ -5,6 +5,8 @@ cc=${CC:-cc} ar=${AR:-ar} ranlib=${RANLIB:-ranlib} lipo=${LIPO:-lipo} +wasm_cc=${WASM_CC:-clang} +wasm_ld=${WASM_LD:-wasm-ld} ODIN_ROOT=${ODIN_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd)} cd "$ODIN_ROOT/vendor/box3d/src" || exit 1 @@ -87,3 +89,25 @@ Linux) exit 1 ;; esac + +echo "Building Box3D for wasm32" +mkdir -p build/wasm +for src in src/*.c; do + obj="build/wasm/$(basename "${src%.c}.o")" + "$wasm_cc" -c -O3 -std=gnu17 --target=wasm32 \ + --sysroot="$ODIN_ROOT/vendor/libc-shim" \ + -Iinclude \ + -include wasm_compat.h \ + -DBOX3D_DISABLE_SIMD \ + -DNDEBUG \ + "$src" -o "$obj" +done +"$wasm_cc" -c -O3 -std=gnu17 --target=wasm32 \ + --sysroot="$ODIN_ROOT/vendor/libc-shim" \ + -Iinclude \ + -include wasm_compat.h \ + -DBOX3D_DISABLE_SIMD \ + -DNDEBUG \ + wasm_compat.c -o build/wasm/wasm_compat.o +"$wasm_ld" -r -o ../lib/box3d_wasm.o build/wasm/*.o +rm -rf build/wasm diff --git a/vendor/box3d/src/wasm_compat.c b/vendor/box3d/src/wasm_compat.c new file mode 100644 index 000000000..c9c169c1d --- /dev/null +++ b/vendor/box3d/src/wasm_compat.c @@ -0,0 +1,8 @@ +#include + +int fscanf(FILE* restrict stream, const char* restrict format, ...) +{ + (void)stream; + (void)format; + return -1; +} diff --git a/vendor/box3d/src/wasm_compat.h b/vendor/box3d/src/wasm_compat.h new file mode 100644 index 000000000..60073a58b --- /dev/null +++ b/vendor/box3d/src/wasm_compat.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifndef PRIx64 +#define PRIx64 "llx" +#endif + +#ifndef PRIu64 +#define PRIu64 "llu" +#endif + +int fscanf(FILE* restrict stream, const char* restrict format, ...); From fa682654917366dea5e38b7d1993db8081949a8f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 9 Aug 2026 20:23:34 +0200 Subject: [PATCH 05/93] Add Box3d WASM object --- .gitattributes | 1 + vendor/box3d/lib/box3d_wasm.o | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 vendor/box3d/lib/box3d_wasm.o diff --git a/.gitattributes b/.gitattributes index dd9d1a0c8..289340f27 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ vendor/box2d/lib/box2d_windows_amd64_sse2.lib filter=lfs diff=lfs merge=lfs -tex vendor/box3d/lib/linux-amd64/libbox3d.a filter=lfs diff=lfs merge=lfs -text vendor/box3d/lib/darwin/libbox3d.a filter=lfs diff=lfs merge=lfs -text vendor/box3d/lib/box3d.lib filter=lfs diff=lfs merge=lfs -text +vendor/box3d/lib/box3d_wasm.o filter=lfs diff=lfs merge=lfs -text vendor/miniaudio/lib/miniaudio.lib filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text diff --git a/vendor/box3d/lib/box3d_wasm.o b/vendor/box3d/lib/box3d_wasm.o new file mode 100644 index 000000000..b84ce8e8b --- /dev/null +++ b/vendor/box3d/lib/box3d_wasm.o @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03c8fda79eed1028b9ad4788c9361d78f17717826b98616a70694806507c411 +size 1367290 From ffd7ef024cd7038446fc5d657992a31986df1b29 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 9 Aug 2026 12:04:08 -0700 Subject: [PATCH 06/93] remove dead simd_rem --- src/checker_builtin_procs.hpp | 2 -- src/llvm_backend_proc.cpp | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 457591615..f1b2f1ab3 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -153,7 +153,6 @@ BuiltinProc__simd_begin, BuiltinProc_simd_sub, BuiltinProc_simd_mul, BuiltinProc_simd_div, - BuiltinProc_simd_rem, BuiltinProc_simd_shl, // Odin logic BuiltinProc_simd_shr, // Odin logic BuiltinProc_simd_shl_masked, // C logic @@ -564,7 +563,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_div"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_rem"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shl"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shr"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index b528980ec..be1b3724f 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1610,7 +1610,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn case BuiltinProc_simd_sub: case BuiltinProc_simd_mul: case BuiltinProc_simd_div: - case BuiltinProc_simd_rem: if (is_float) { switch (builtin_id) { case BuiltinProc_simd_add: op_code = LLVMFAdd; break; @@ -1630,13 +1629,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn op_code = LLVMUDiv; } break; - case BuiltinProc_simd_rem: - if (is_signed) { - op_code = LLVMSRem; - } else { - op_code = LLVMURem; - } - break; } } if (op_code) { From 1a631b2f3fe2bb0f791850987f5e2b87b72b66a5 Mon Sep 17 00:00:00 2001 From: kalsprite <52809771+kalsprite@users.noreply.github.com> Date: Sun, 9 Aug 2026 12:22:13 -0700 Subject: [PATCH 07/93] Fix constant `string16` carrying UTF-8, and inverted slice indices reaching `substring` (#7268) * str16 fix * remove stale call --- src/check_expr.cpp | 21 +++-- tests/internal/test_string16.odin | 136 ++++++++++++++++++++++++++++++ tests/issues/run.bat | 1 - tests/issues/run.sh | 1 - tests/issues/test_issue_6101.odin | 23 ----- 5 files changed, 152 insertions(+), 30 deletions(-) create mode 100644 tests/internal/test_string16.odin delete mode 100644 tests/issues/test_issue_6101.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e73ec65c4..332f15836 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2306,7 +2306,15 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i if (in_value.kind == ExactValue_String16) { return is_type_string16(type) || is_type_cstring16(type); } - return in_value.kind == ExactValue_String; + if (in_value.kind != ExactValue_String) { + return false; + } + // NOTE: a UTF-8 constant has to be re-expressed in UTF-16, otherwise its length and + // indices stay those of the UTF-8 encoding + if (is_type_string16(type) || is_type_cstring16(type)) { + if (out_value) *out_value = exact_value_string16(string_to_string16(permanent_allocator(), in_value.value_string)); + } + return true; } else if (is_type_integer(type) || is_type_rune(type)) { ExactValue v = exact_value_to_integer(in_value); if (v.kind != ExactValue_Integer) { @@ -4005,14 +4013,15 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb Type *dst = core_type(type); if (is_type_string(src) && is_type_string(dst)) { - bool src_utf16 = is_type_string16(src) || is_type_cstring16(src); bool dst_utf16 = is_type_string16(dst) || is_type_cstring16(dst); - if (!src_utf16 && dst_utf16) { + // NOTE: keyed off the value's encoding rather than the source type; it may have been re-expressed + // when it was checked against the target type + if (dst_utf16 && x->value.kind == ExactValue_String) { x->value = exact_value_string16(string_to_string16(permanent_allocator(), x->value.value_string)); } - if (src_utf16 && !dst_utf16) { + if (!dst_utf16 && x->value.kind == ExactValue_String16) { x->value = exact_value_string(string16_to_string(permanent_allocator(), x->value.value_string16)); } } @@ -12218,12 +12227,14 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, indices[i] = index; } + bool invalid_indices = false; for (isize i = 0; i < gb_count_of(indices); i++) { i64 a = indices[i]; for (isize j = i+1; j < gb_count_of(indices); j++) { i64 b = indices[j]; if (a > b && b >= 0) { error(se->close, "Invalid slice indices: [%td > %td]", a, b); + invalid_indices = true; } } } @@ -12249,7 +12260,7 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, o->mode = Addressing_Value; - if (is_type_string(t) && max_count >= 0) { + if (is_type_string(t) && max_count >= 0 && !invalid_indices) { bool all_constant = true; for (isize i = 0; i < gb_count_of(nodes); i++) { if (nodes[i] != nullptr) { diff --git a/tests/internal/test_string16.odin b/tests/internal/test_string16.odin new file mode 100644 index 000000000..c67739168 --- /dev/null +++ b/tests/internal/test_string16.odin @@ -0,0 +1,136 @@ +package test_internal + +import "core:testing" + +// The compiler folds constant `string16` operations at check time and emits the same +// operations at runtime. Every case below computes a value both ways and compares them, +// because the interesting failures are the ones where the two disagree silently. + +// Related previous issue #6101 + +@(private="file") +opaque :: proc(v: $T) -> T { return v } + +@(private="file") +Ascii : string16 : "hello" // 5 bytes utf-8, 5 units utf-16 +@(private="file") +Latin : string16 : "héllo" // 6 bytes utf-8, 5 units utf-16 +@(private="file") +Cjk : string16 : "日本語" // 9 bytes utf-8, 3 units utf-16 +@(private="file") +NonBmp : string16 : "\U0001F63A" // 4 bytes utf-8, 2 units utf-16 (surrogate pair) +@(private="file") +Mixed : string16 : "a日\U0001F63A" // 8 bytes utf-8, 4 units utf-16 +@(private="file") +Empty : string16 : "" + +@test +string16_constant_length :: proc(t: ^testing.T) { + // lengths are in utf-16 code units, not utf-8 bytes + testing.expect_value(t, len(Ascii), 5) + testing.expect_value(t, len(Latin), 5) + testing.expect_value(t, len(Cjk), 3) + testing.expect_value(t, len(NonBmp), 2) + testing.expect_value(t, len(Mixed), 4) + testing.expect_value(t, len(Empty), 0) + + // the constant length must match the length of the same value at runtime + testing.expect_value(t, len(Ascii), len(opaque(Ascii))) + testing.expect_value(t, len(Latin), len(opaque(Latin))) + testing.expect_value(t, len(Cjk), len(opaque(Cjk))) + testing.expect_value(t, len(NonBmp), len(opaque(NonBmp))) + testing.expect_value(t, len(Mixed), len(opaque(Mixed))) + testing.expect_value(t, len(Empty), len(opaque(Empty))) +} + +@test +string16_constant_index :: proc(t: ^testing.T) { + testing.expect_value(t, Latin[0], 'h') + testing.expect_value(t, Latin[1], 0x00E9) // é stays one unit + testing.expect_value(t, Cjk[0], 0x65E5) + testing.expect_value(t, Cjk[2], 0x8A9E) + testing.expect_value(t, NonBmp[0], 0xD83D) // high surrogate + testing.expect_value(t, NonBmp[1], 0xDE3A) // low surrogate + + // each constant-folded unit must match the same unit read at runtime + m := opaque(Mixed) + testing.expect_value(t, Mixed[0], m[0]) + testing.expect_value(t, Mixed[1], m[1]) + testing.expect_value(t, Mixed[2], m[2]) + testing.expect_value(t, Mixed[3], m[3]) +} + +@test +string16_constant_slice :: proc(t: ^testing.T) { + A :: Latin[0:2] + B :: Cjk[1:3] + C :: NonBmp[0:2] + D :: Mixed[1:2] + E :: Ascii[2:2] + + testing.expect_value(t, len(A), 2) + testing.expect_value(t, len(B), 2) + testing.expect_value(t, len(C), 2) + testing.expect_value(t, len(D), 1) + testing.expect_value(t, len(E), 0) + + testing.expect_value(t, A[0], 'h') + testing.expect_value(t, A[1], 0x00E9) + testing.expect_value(t, B[0], 0x672C) + testing.expect_value(t, C[1], 0xDE3A) + testing.expect_value(t, D[0], 0x65E5) + + // open-ended and full slices + F :: Cjk[:] + G :: Cjk[1:] + H :: Cjk[:2] + testing.expect_value(t, len(F), 3) + testing.expect_value(t, len(G), 2) + testing.expect_value(t, len(H), 2) + + // folded slice must equal the same slice taken at runtime + l := opaque(Latin) + rt := l[0:2] + testing.expect_value(t, len(A), len(rt)) + testing.expect_value(t, A[0], rt[0]) + testing.expect_value(t, A[1], rt[1]) +} + +@test +string16_from_cast_and_assignment :: proc(t: ^testing.T) { + // the three ways a constant acquires a string16 type must agree + Typed : string16 : "日本語" + Casted :: string16("日本語") + testing.expect_value(t, len(Typed), len(Casted)) + testing.expect_value(t, Typed[0], Casted[0]) + testing.expect_value(t, Typed[2], Casted[2]) + + assigned: string16 = "日本語" + testing.expect_value(t, len(assigned), len(Typed)) + testing.expect_value(t, assigned[0], Typed[0]) +} + +@test +string16_underlying_units :: proc(t: ^testing.T) { + // transmute exposes the utf-16 code units directly + u := transmute([]u16)opaque(NonBmp) + testing.expect_value(t, len(u), 2) + testing.expect_value(t, u[0], 0xD83D) + testing.expect_value(t, u[1], 0xDE3A) + + c := transmute([]u16)opaque(Cjk) + testing.expect_value(t, len(c), 3) + testing.expect_value(t, c[0], 0x65E5) + + // a utf-8 string of the same text keeps its byte length + testing.expect_value(t, len("日本語"), 9) +} + +@test +string16_comparison :: proc(t: ^testing.T) { + X : string16 : "日本語" + testing.expect(t, X == Cjk) + testing.expect(t, X != Ascii) + testing.expect(t, opaque(X) == Cjk) + testing.expect(t, Empty == "") +} diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 4bd301ac1..ff02105d7 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -29,7 +29,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_5573.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_5699.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6068.odin %COMMON% || exit /b -..\..\..\odin test ..\test_issue_6101.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6165.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_6240.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin build ..\test_issue_6401.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 9c3cfecc3..fe4db8a4b 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -41,7 +41,6 @@ else fi $ODIN test ../test_issue_5699.odin $COMMON $ODIN test ../test_issue_6068.odin $COMMON -$ODIN test ../test_issue_6101.odin $COMMON $ODIN test ../test_issue_6165.odin $COMMON $ODIN test ../test_issue_6344.odin $COMMON $ODIN test ../test_issue_6344.odin $COMMON -o:speed diff --git a/tests/issues/test_issue_6101.odin b/tests/issues/test_issue_6101.odin deleted file mode 100644 index 9f24ade52..000000000 --- a/tests/issues/test_issue_6101.odin +++ /dev/null @@ -1,23 +0,0 @@ -// Tests issue #6101 https://github.com/odin-lang/Odin/issues/6101 -package test_issues - -import "core:testing" - -@(test) -test_issue_6101_bmp :: proc(t: ^testing.T) { - s := string16("\u732b") - testing.expect_value(t, len(s), 1) - - u := transmute([]u16)s - testing.expect_value(t, u[0], 0x732b) -} - -@(test) -test_issue_6101_non_bmp :: proc(t: ^testing.T) { - s := string16("\U0001F63A") - testing.expect_value(t, len(s), 2) - - u := transmute([]u16)s - testing.expect_value(t, u[0], 0xD83D) - testing.expect_value(t, u[1], 0xDE3A) -} From e374541599b79bd0e31b4a9207b5156047b3c24a Mon Sep 17 00:00:00 2001 From: corley <28909106+corleypc@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:23:32 +0300 Subject: [PATCH 08/93] fixes chained indexing for soa containers with array element type --- src/check_expr.cpp | 13 +- src/llvm_backend_expr.cpp | 60 ++++++- src/llvm_backend_general.cpp | 14 ++ tests/core/runtime/test_core_runtime.odin | 208 ++++++++++++++++++++++ 4 files changed, 287 insertions(+), 8 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e73ec65c4..c7a51469a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2996,8 +2996,13 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * if (ast_node_expect(index_expr, Ast_IndexExpr)) { ast_node(ie, IndexExpr, index_expr); Type *soa_type = type_deref(type_of_expr(ie->expr)); - GB_ASSERT(is_type_soa_struct(soa_type)); - o->type = alloc_type_soa_pointer(soa_type); + if (is_type_soa_struct(soa_type)) { + o->type = alloc_type_soa_pointer(soa_type); + } else { + // &soa[i][j] + GB_ASSERT_MSG(is_type_array(soa_type), "%s", type_to_string(soa_type)); + o->type = alloc_type_pointer(o->type); + } } else { o->type = alloc_type_pointer(o->type); } @@ -9158,7 +9163,11 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 if (indirection) { o->mode = Addressing_Variable; } else if (o->mode != Addressing_Variable && + o->mode != Addressing_SoaVariable && o->mode != Addressing_Constant) { + // NOTE: an #soa element of array type keeps SoaVariable, so soa[i][j] stays an + // lvalue. Its components are one per lane rather than contiguous, but a single + // component still has a real address, the same one soa[i].y denotes. o->mode = Addressing_Value; } o->type = t->Array.elem; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 5a478edd0..4973716b3 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -5068,6 +5068,47 @@ gb_internal void lb_build_addr_compound_lit_assign_array(lbProcedure *p, Arraykind == Type_Array, "%s", type_to_string(elem_type)); + + // this is a no-op when the index is in range by construction, like in a for-in loop + lb_emit_soa_index_bounds_check(p, soa_addr.addr, soa_addr.soa.index, soa_addr.soa.index_expr); + + i64 const component_count = elem_type->Array.count; + + auto const index_tv = type_and_value_of_expr(ie->index); + if (index_tv.mode == Addressing_Constant && index_tv.value.kind == ExactValue_Integer) { + i64 component = big_int_to_i64(&index_tv.value.value_integer); + GB_ASSERT_MSG(0 <= component && component < component_count, "%s", expr_to_string(expr)); + return lb_addr_soa_field_elem(lb_soa_field_elem_ptr(p, soa_addr.addr, cast(i32)component, soa_addr.soa.index)); + } + + // for non-constant index do chain select between the component pointers; + // an element array holds at most 4 components, so this is at most 3 compares and 3 selects (branchless), + // and should be folded if j turns out constant after inlining; + // TODO: more efficient codegen can be done, most definitely for fixed kind, possibly for slice/dynamic, + // (but this works for both kinds) + // + // Note: a temp holding the whole element would be simpler but would not be an lvalue, + // and writes go through here + lbValue index = lb_emit_conv(p, lb_build_expr(p, ie->index), t_int); + lb_emit_bounds_check(p, ast_token(ie->index), index, lb_const_int(p->module, t_int, component_count)); + + lbValue ptr = lb_soa_field_elem_ptr(p, soa_addr.addr, 0, soa_addr.soa.index); + // lb_emit_select evaluates both arms, so every candidate address is formed; + // only the selected one is loaded or stored through + for (i64 component = 1; component < component_count; component++) { + lbValue candidate = lb_soa_field_elem_ptr(p, soa_addr.addr, cast(i32)component, soa_addr.soa.index); + lbValue is_component = lb_emit_comp(p, Token_CmpEq, index, lb_const_int(p->module, t_int, component)); + ptr = lb_emit_select(p, is_component, candidate, ptr); + } + return lb_addr_soa_field_elem(ptr); +} + gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { ast_node(ie, IndexExpr, expr); @@ -5086,10 +5127,10 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { return lb_addr_soa_variable(val, index, ie->index); } - if (ie->expr->tav.mode == Addressing_SoaVariable) { - // SOA Structures for slices/dynamic arrays - GB_ASSERT_MSG(is_type_multi_pointer(type_of_expr(ie->expr)), "%s", type_to_string(type_of_expr(ie->expr))); - + if (ie->expr->tav.mode == Addressing_SoaVariable && is_type_multi_pointer(type_of_expr(ie->expr))) { + // soa.x[i], indexing one field's multipointer; + // the soa element of array type, soa[i][j] carries Addressing_SoaVariable too but has + // no multipointer to index; it is handled in the Type_Array case below lbValue field = lb_build_expr(p, ie->expr); lbValue index = lb_build_expr(p, ie->index); @@ -5145,8 +5186,15 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { switch (t->kind) { case Type_Array: { - lbValue array = {}; - array = lb_build_addr_ptr(p, ie->expr); + lbAddr array_addr = lb_build_addr(p, ie->expr); + // soa[i][j], or v[j] in a for-in loop, with v being the looping var over soa container + // + // keyed on the lowered lbAddr kind rather than the addressing mode, + // because, unlike soa[i], v is seen by the checker as Addressing_Variable + if (array_addr.kind == lbAddr_SoaVariable) { + return lb_build_addr_soa_elem_index(p, expr, array_addr, t); + } + lbValue array = lb_addr_get_ptr(p, array_addr); if (deref) { array = lb_emit_load(p, array); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 72b11bac5..17a2db143 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -617,6 +617,12 @@ gb_internal lbAddr lb_addr_soa_variable(lbValue addr, lbValue index, Ast *index_ } // pointer to the index element of the field_index component +// +// the returned pointer type depends on the soa kind (because the field types do): +// ^T for StructSoa_Fixed (field is [N]T array), but [^]T for the slice and dynamic +// kinds (field is the [^]T this offsets); +// loads and stores work with either, but an lbAddr must not hold the multipointer, +// so use lb_addr_soa_field_elem to build an lbAddr from this gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 field_index, lbValue index) { Type *t = base_type(type_deref(soa_ptr.type)); GB_ASSERT_MSG(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None, "%s", type_to_string(t)); @@ -628,6 +634,14 @@ gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 f return lb_emit_ptr_offset(p, lb_emit_load(p, field), index); } +// lbAddr over an lb_soa_field_elem_ptr pointer, retyped ^T when it came as [^]T +gb_internal lbAddr lb_addr_soa_field_elem(lbValue ptr) { + if (is_type_multi_pointer(ptr.type)) { + ptr.type = alloc_type_multi_pointer_to_pointer(ptr.type); + } + return lb_addr(ptr); +} + // bounds check for an #soa element index gb_internal void lb_emit_soa_index_bounds_check(lbProcedure *p, lbValue soa_ptr, lbValue index, Ast *index_expr) { if (index_expr == nullptr) { diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 7db985dea..8faa2e580 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -384,6 +384,214 @@ test_soa_array_elem_swizzle :: proc(t: ^testing.T) { testing.expect_value(t, fixed[1].xy, [2]u16{10, 9}) } +// chained indexing of #soa container when element type is an array -> soa[i][j] +@(test) +test_soa_array_elem_chained_indexing :: proc(t: ^testing.T) { + + fixed: #soa[3][4]u16 + for i in 0 ..< 3 { + fixed.x[i] = u16(i*10 + 0) + fixed.y[i] = u16(i*10 + 1) + fixed.z[i] = u16(i*10 + 2) + fixed.w[i] = u16(i*10 + 3) + } + + // const inner index + testing.expect_value(t, fixed[1][0], 10) + testing.expect_value(t, fixed[2][3], 23) + + // var inner index + for i in 0 ..< 3 { + tmp := fixed[i] + for j in 0 ..< 4 { + testing.expect_value(t, fixed[i][j], tmp[j]) + testing.expect_value(t, fixed[i][j], u16(i*10 + j)) + } + } + + // index and the .x/y/z/w name must agree + for i in 0 ..< 3 { + testing.expect_value(t, fixed[i][0], fixed[i].x) + testing.expect_value(t, fixed[i][3], fixed[i].w) + } + + // dynamic + slice + dyn := make(#soa[dynamic][4]u16, 2) + defer delete(dyn) + dyn[0] = [4]u16{10, 11, 12, 13} + dyn[1] = [4]u16{20, 21, 22, 23} + + testing.expect_value(t, dyn[0][3], 13) + testing.expect_value(t, dyn[1][0], 20) + + slice := dyn[:] + testing.expect_value(t, slice[0][3], 13) + testing.expect_value(t, slice[1][2], 22) + + for i in 0 ..< 2 { + dyn_tmp := dyn[i] + slice_tmp := slice[i] + for j in 0 ..< 4 { + testing.expect_value(t, dyn[i][j], dyn_tmp[j]) + testing.expect_value(t, slice[i][j], dyn_tmp[j]) + testing.expect_value(t, dyn[i][j], slice_tmp[j]) + testing.expect_value(t, slice[i][j], slice_tmp[j]) + } + } + + // soa[i][j] must equal v[j] where v is the for-in looping variable + // test fixed, dynamic and sliec + for v, i in fixed { + testing.expect_value(t, v[3], u16(i*10 + 3)) + for j in 0 ..< 4 { + testing.expect_value(t, v[j], fixed[i][j]) + } + } + for &v, i in fixed { + testing.expect_value(t, v[1], u16(i*10 + 1)) + j := 2 + testing.expect_value(t, v[j], u16(i*10 + j)) + } + for v, i in dyn { + for j in 0 ..< 4 { + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + } + for &v, i in dyn { + testing.expect_value(t, v[1], u16((i + 1)*10 + 1)) + j := 3 + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + for v, i in slice { + testing.expect_value(t, v[2], slice[i][2]) + } + for &v, i in slice { + testing.expect_value(t, v[2], slice[i][2]) + j := 0 + testing.expect_value(t, v[j], u16((i + 1)*10 + j)) + } + + // write access must work through the looping var + fixed_scatter: #soa[2][4]u16 + for &v, i in fixed_scatter { + v[0] = u16(i) + for j in 1 ..< 4 { + v[j] = u16(i*10 + j) + } + v[3] += 5 + } + for i in 0 ..< 2 { + testing.expect_value(t, fixed_scatter.x[i], u16(i)) + testing.expect_value(t, fixed_scatter.y[i], u16(i*10 + 1)) + testing.expect_value(t, fixed_scatter.z[i], u16(i*10 + 2)) + testing.expect_value(t, fixed_scatter.w[i], u16(i*10 + 3 + 5)) + } + // for fixed soa you can get fancy and select a whole .x/y/z/w lane + testing.expect_value(t, fixed_scatter.x, [2]u16{0, 1}) + testing.expect_value(t, fixed_scatter.y, [2]u16{1, 11}) + testing.expect_value(t, fixed_scatter.z, [2]u16{2, 12}) + testing.expect_value(t, fixed_scatter.w, [2]u16{3 + 5, 13 + 5}) + + dyn_scatter := make(#soa[dynamic][4]u16, 2) + defer delete(dyn_scatter) + for &v, i in dyn_scatter { + v[0] = u16(i + 1) + for j in 1 ..< 4 { + v[j] = u16((i + 1)*10 + j) + } + v[3] += 5 + } + for i in 0 ..< 2 { + testing.expect_value(t, dyn_scatter.x[i], u16(i + 1)) + testing.expect_value(t, dyn_scatter.y[i], u16((i + 1)*10 + 1)) + testing.expect_value(t, dyn_scatter.z[i], u16((i + 1)*10 + 2)) + testing.expect_value(t, dyn_scatter.w[i], u16((i + 1)*10 + 3 + 5)) + } + + slice_scatter := dyn_scatter[:] + for &v in slice_scatter { + v[1] += 100 + } + testing.expect_value(t, dyn_scatter.y[0], 111) + testing.expect_value(t, dyn_scatter.y[1], 121) + testing.expect_value(t, dyn_scatter.x[0], 1) + + // soa[i][j] writes + fixed_write: #soa[3][4]u16 + fixed_write[1][0] = 5 + k := 2 + fixed_write[1][k] = 6 + fixed_write[1][1] += 7 + fixed_write[1][0], fixed_write[1][3] = fixed_write[1][3], fixed_write[1][0] + testing.expect_value(t, fixed_write.x, [3]u16{0, 0, 0}) + testing.expect_value(t, fixed_write.y, [3]u16{0, 7, 0}) + testing.expect_value(t, fixed_write.z, [3]u16{0, 6, 0}) + testing.expect_value(t, fixed_write.w, [3]u16{0, 5, 0}) + + dyn_write := make(#soa[dynamic][4]u16, 2) + defer delete(dyn_write) + dyn_write[0][3] = 41 + dyn_write[1][k] = 42 + slice_write := dyn_write[:] + slice_write[0][1] = 43 + slice_write[0][k] = 44 + testing.expect_value(t, dyn_write.w[0], 41) + testing.expect_value(t, dyn_write.z[1], 42) + testing.expect_value(t, dyn_write.y[0], 43) + testing.expect_value(t, dyn_write.z[0], 44) + testing.expect_value(t, dyn_write.x[0], 0) + + // a single component has a real address + testing.expect_value(t, &fixed_write[1][2], &fixed_write.z[1]) + pw := &fixed_write[1][2] + pw^ = 60 + testing.expect_value(t, fixed_write.z[1], 60) + for j in 0 ..< 4 { + p := &fixed_write[1][j] + p^ = u16(70 + j) + } + testing.expect_value(t, fixed_write.x, [3]u16{0, 70, 0}) + testing.expect_value(t, fixed_write.y, [3]u16{0, 71, 0}) + testing.expect_value(t, fixed_write.z, [3]u16{0, 72, 0}) + testing.expect_value(t, fixed_write.w, [3]u16{0, 73, 0}) + + for j in 0 ..< 4 { + p := &dyn_write[0][j] + p^ = u16(80 + j) + } + testing.expect_value(t, dyn_write.x[0], 80) + testing.expect_value(t, dyn_write.w[0], 83) + for j in 0 ..< 4 { + p := &slice_write[1][j] + p^ = u16(90 + j) + } + testing.expect_value(t, dyn_write.x[1], 90) + testing.expect_value(t, dyn_write.w[1], 93) + + // multi dim array element type, only the outer index is scattered + nested: #soa[2][3][2]u16 + nested[1].x = {1, 3} + nested[1].z = {7, 11} + testing.expect_value(t, nested[1][0][1], 3) + testing.expect_value(t, nested[1][2][0], 7) + + nested[1][0][1] = 8 + nested[1][2][0] = 9 + testing.expect_value(t, nested[1][0], [2]u16{1, 8}) + testing.expect_value(t, nested[1][2], [2]u16{9, 11}) + + for j in 0 ..< 3 { + nested[1][j][0] = u16(50 + j) + } + for j in 0 ..< 3 { + testing.expect_value(t, nested[1][j][0], u16(50 + j)) + } + testing.expect_value(t, nested.x[1][0], 50) + testing.expect_value(t, nested.y[1][0], 51) + testing.expect_value(t, nested.z[1][0], 52) + testing.expect_value(t, nested[0][0], [2]u16{0, 0}) +} + // "using" on an #soa for-in looping variable @(test) test_soa_for_in_using :: proc(t: ^testing.T) { From 1f9b4c4555ea690a63a1160d801e6996fa3eb32d Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 10 Aug 2026 12:27:31 +0200 Subject: [PATCH 09/93] Add `text` field to `utf8.Grapheme`, fix caret translation by grapheme `decode_grapheme_iterate` already computed the text of each grapheme cluster, but `decode_grapheme_clusters` discarded it, forcing callers to reconstruct each cluster's range by looking ahead to the next grapheme's `byte_index`. Store it on `Grapheme` instead; it is a slice of the input string, so no allocation is involved. `core:text/edit` was using `Grapheme.width` -- the number of monospace cells -- as a byte count when moving the caret by grapheme, so it landed in the middle of a cluster for anything wider than one byte per cell. Use `len(Grapheme.text)`, and add a test package for the translations. Co-Authored-By: Claude Opus 5 --- core/text/edit/text_edit.odin | 4 +- core/unicode/utf8/grapheme.odin | 9 +- tests/core/normal.odin | 1 + tests/core/text/edit/test_core_text_edit.odin | 113 ++++++++++++++++++ 4 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 tests/core/text/edit/test_core_text_edit.odin diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 58e184309..eebe938f4 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -311,7 +311,7 @@ translate_position :: proc(s: ^State, t: Translation) -> int { for { _, g = utf8.decode_grapheme_iterate(&it) or_break } - pos -= max(g.width, 1) + pos -= max(len(g.text), 1) } else { pos -= 1 for pos >= 0 && is_continuation_byte(buf[pos]) { @@ -323,7 +323,7 @@ translate_position :: proc(s: ^State, t: Translation) -> int { it := utf8.decode_grapheme_iterator_make(string(buf[pos:])) _, g, _ := utf8.decode_grapheme_iterate(&it) - pos += max(g.width, 1) + pos += max(len(g.text), 1) } else { pos += 1 for pos < len(buf) && is_continuation_byte(buf[pos]) { diff --git a/core/unicode/utf8/grapheme.odin b/core/unicode/utf8/grapheme.odin index bcd234972..c4ef3767e 100644 --- a/core/unicode/utf8/grapheme.odin +++ b/core/unicode/utf8/grapheme.odin @@ -21,6 +21,7 @@ normalized_east_asian_width :: unicode.normalized_east_asian_width Grapheme :: struct { + text: string, // The text of the grapheme, a slice of the string it was decoded from. byte_index: int, rune_index: int, width: int, @@ -152,10 +153,11 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe it.width += normalized_east_asian_width(this_rune) if it.continue_grapheme { grapheme = it.current_grapheme - text = it.str[it.current_grapheme.byte_index:byte_index] + grapheme.text = it.str[it.current_grapheme.byte_index:byte_index] + text = grapheme.text ok = true } - it.current_grapheme = Grapheme{byte_index, it.rune_count, it.width - it.last_width} + it.current_grapheme = Grapheme{byte_index = byte_index, rune_index = it.rune_count, width = it.width - it.last_width} it.continue_grapheme = true @@ -392,7 +394,8 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe // a new grapheme is encountered. if !ok && it.continue_grapheme { grapheme = it.current_grapheme - text = it.str[it.current_grapheme.byte_index:] + grapheme.text = it.str[it.current_grapheme.byte_index:] + text = grapheme.text ok = true it.continue_grapheme = false } diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 99a30343c..303acd232 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -50,6 +50,7 @@ download_assets :: proc "contextless" () { @(require) import "sys/posix" @(require) import "sys/kqueue" @(require) import "sys/windows" +@(require) import "text/edit" @(require) import "text/i18n" @(require) import "text/match" @(require) import "text/regex" diff --git a/tests/core/text/edit/test_core_text_edit.odin b/tests/core/text/edit/test_core_text_edit.odin new file mode 100644 index 000000000..ab651b3b9 --- /dev/null +++ b/tests/core/text/edit/test_core_text_edit.odin @@ -0,0 +1,113 @@ +package test_core_text_edit + +import "core:slice" +import "core:strings" +import "core:testing" +import "core:text/edit" + +// "hi", a thumbs-up with a skin tone modifier, an "e" with a combining acute, and "!". +// The escapes are spelled out so the byte offsets below do not depend on how this +// file happens to be normalized. +// +// byte: 0 1 2 6 10 11 13 14 +// rune: h i U+1F44D U+1F3FD e U+0301 ! +GRAPHEME_SAMPLE :: "hi\U0001F44D\U0001F3FDe\u0301!" + +WORD_SAMPLE :: "foo bar baz" + +State :: struct { + state: edit.State, + builder: strings.Builder, +} + +state_init :: proc(s: ^State, str: string, translate_by_grapheme: bool) { + s.builder = strings.builder_make() + strings.write_string(&s.builder, str) + + edit.init(&s.state, context.allocator, context.allocator) + edit.setup_once(&s.state, &s.builder) + s.state.translate_by_grapheme = translate_by_grapheme +} + +state_destroy :: proc(s: ^State) { + edit.destroy(&s.state) + strings.builder_destroy(&s.builder) +} + +// Walk the caret from `start` in direction `t` until it stops moving, collecting +// every position it comes to rest on. +walk :: proc(s: ^State, start: int, t: edit.Translation) -> (stops: [dynamic]int) { + s.state.selection = {start, start} + for { + prev := s.state.selection[0] + edit.move_to(&s.state, t) + if s.state.selection[0] == prev { + return + } + append(&stops, s.state.selection[0]) + } +} + +expect_walk :: proc(t: ^testing.T, s: ^State, start: int, translation: edit.Translation, expected: []int) { + stops := walk(s, start, translation) + defer delete(stops) + + testing.expectf(t, slice.equal(stops[:], expected), + "%v from %v: expected stops %v, got %v", translation, start, expected, stops[:]) +} + +// Moving by grapheme must stop on grapheme cluster boundaries, never inside the +// emoji modifier sequence or between a base rune and its combining marks. +@(test) +test_translate_by_grapheme :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, true) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Right, {1, 2, 10, 13, 14}) + expect_walk(t, &s, len(GRAPHEME_SAMPLE), .Left, {13, 10, 2, 1, 0}) +} + +// The default translation moves by codepoint, so it steps through the two runes +// of the emoji sequence and the two runes of "e" + combining acute separately. +@(test) +test_translate_by_codepoint :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, false) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Right, {1, 2, 6, 10, 11, 13, 14}) + expect_walk(t, &s, len(GRAPHEME_SAMPLE), .Left, {13, 11, 10, 6, 2, 1, 0}) +} + +@(test) +test_translate_by_word :: proc(t: ^testing.T) { + s: State + state_init(&s, WORD_SAMPLE, false) + defer state_destroy(&s) + + expect_walk(t, &s, 0, .Word_Right, {4, 9, 12}) + expect_walk(t, &s, len(WORD_SAMPLE), .Word_Left, {9, 4, 0}) + + // From inside "bar", to the edges of that word. + s.state.selection = {5, 5} + testing.expect_value(t, edit.translate_position(&s.state, .Word_Start), 4) + testing.expect_value(t, edit.translate_position(&s.state, .Word_End), 7) +} + +@(test) +test_translate_to_bounds :: proc(t: ^testing.T) { + s: State + state_init(&s, GRAPHEME_SAMPLE, true) + defer state_destroy(&s) + + s.state.selection = {5, 5} + testing.expect_value(t, edit.translate_position(&s.state, .Start), 0) + testing.expect_value(t, edit.translate_position(&s.state, .End), len(GRAPHEME_SAMPLE)) + + // Translating past either end must clamp rather than run off the buffer. + s.state.selection = {0, 0} + testing.expect_value(t, edit.translate_position(&s.state, .Left), 0) + s.state.selection = {len(GRAPHEME_SAMPLE), len(GRAPHEME_SAMPLE)} + testing.expect_value(t, edit.translate_position(&s.state, .Right), len(GRAPHEME_SAMPLE)) +} From 59b09aa55943de312b291b26e812306f56e08e7f Mon Sep 17 00:00:00 2001 From: Alexander Zhura Date: Mon, 10 Aug 2026 14:07:05 +0300 Subject: [PATCH 10/93] Impl fix simd arm neon table lookup on be --- core/simd/arm/neon.odin | 72 ++++++++++++++++++++-------------------- core/simd/arm/pmull.odin | 48 +++++++++++++-------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/core/simd/arm/neon.odin b/core/simd/arm/neon.odin index 30f7f8b21..e89caff64 100644 --- a/core/simd/arm/neon.odin +++ b/core/simd/arm/neon.odin @@ -891,12 +891,12 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl2(t.x, t.y, idx) } else { - v := int8x16x2_t { + t := int8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl2(v.x, v.y, idx) + c := _vqtbl2(t.x, t.y, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -913,14 +913,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := uint8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl2( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -935,12 +935,12 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl2q(t.x, t.y, idx) } else { - v := int8x16x2_t { + t := int8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl2q(v.x, v.y, idx) + c := _vqtbl2q(t.x, t.y, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -957,14 +957,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := uint8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl2q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -979,13 +979,13 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl3(t.x, t.y, t.z, idx) } else { - v := int8x16x3_t { + t := int8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl3(v.x, v.y, v.z, idx) + c := _vqtbl3(t.x, t.y, t.z, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1003,16 +1003,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := uint8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl3( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1027,13 +1027,13 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl3q(t.x, t.y, t.z, idx) } else { - v := int8x16x3_t { + t := int8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl3q(v.x, v.y, v.z, idx) + c := _vqtbl3q(t.x, t.y, t.z, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1051,16 +1051,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := uint8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl3q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1075,14 +1075,14 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl4(t.x, t.y, t.z, t.w, idx) } else { - v := int8x16x4_t { + t := int8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl4(v.x, v.y, v.z, v.w, idx) + c := _vqtbl4(t.x, t.y, t.z, t.w, idx) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1101,7 +1101,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := uint8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -1109,10 +1109,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x8_t)_vqtbl4( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -1127,14 +1127,14 @@ when ODIN_ARCH == .arm64 { when ODIN_ENDIAN == .Little { return _vqtbl4q(t.x, t.y, t.z, t.w, idx) } else { - v := int8x16x4_t { + t := int8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - c := _vqtbl4q(v.x, v.y, v.z, v.w, idx) + c := _vqtbl4q(t.x, t.y, t.z, t.w, idx) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } @@ -1153,7 +1153,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := uint8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -1161,10 +1161,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(uint8x16_t)_vqtbl4q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) diff --git a/core/simd/arm/pmull.odin b/core/simd/arm/pmull.odin index 5cfbe3117..56053503f 100644 --- a/core/simd/arm/pmull.odin +++ b/core/simd/arm/pmull.odin @@ -571,14 +571,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := poly8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl2( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -597,14 +597,14 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x2_t { + t := poly8x16x2_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl2q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -624,16 +624,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := poly8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl3( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -653,16 +653,16 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x3_t { + t := poly8x16x3_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl3q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) @@ -683,7 +683,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := poly8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -691,10 +691,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x8_t)_vqtbl4( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) @@ -715,7 +715,7 @@ when ODIN_ARCH == .arm64 { idx, ) } else { - v := int8x16x4_t { + t := poly8x16x4_t { simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), @@ -723,10 +723,10 @@ when ODIN_ARCH == .arm64 { } idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) c := transmute(poly8x16_t)_vqtbl4q( - transmute(int8x16_t)v.x, - transmute(int8x16_t)v.y, - transmute(int8x16_t)v.z, - transmute(int8x16_t)v.w, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, idx, ) return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) From 5385710fa575e509abb78be7b897d471c2056c21 Mon Sep 17 00:00:00 2001 From: Alexander Zhura Date: Mon, 10 Aug 2026 20:03:54 +0300 Subject: [PATCH 11/93] Impl simd arm neon extended table lookup --- core/simd/arm/neon.odin | 582 +++++++++++++++++++++++++++++++++++++++ core/simd/arm/pmull.odin | 334 ++++++++++++++++++++++ 2 files changed, 916 insertions(+) diff --git a/core/simd/arm/neon.odin b/core/simd/arm/neon.odin index e89caff64..33713d128 100644 --- a/core/simd/arm/neon.odin +++ b/core/simd/arm/neon.odin @@ -822,6 +822,172 @@ vtbl4_u8 :: #force_inline proc "c" (t: uint8x8x4_t, idx: uint8x8_t) -> uint8x8_t } } +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_s8) +@(require_results, enable_target_feature = "neon") +vtbx1_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, int8x8_t(8)), + vqtbx1_s8(v, vcombine_s8(t, int8x8_t{}), transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx1(v, t, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_u8) +@(require_results, enable_target_feature = "neon") +vtbx1_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(8)), + vqtbx1_u8(v, vcombine_u8(t, uint8x8_t{}), idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx1( + transmute(int8x8_t)v, + transmute(int8x8_t)t, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_s8) +@(require_results, enable_target_feature = "neon") +vtbx2_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x2_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, int8x8_t(16)), + vqtbx1_s8(v, vcombine_s8(t.x, t.y), transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx2(v, t.x, t.y, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_u8) +@(require_results, enable_target_feature = "neon") +vtbx2_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x2_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(16)), + vqtbx1_u8(v, vcombine_u8(t.x, t.y), idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx2( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_s8) +@(require_results, enable_target_feature = "neon") +vtbx3_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x3_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + x := int8x16x2_t { + vcombine_s8(t.x, t.y), + vcombine_s8(t.z, int8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, int8x8_t(24)), + vqtbx2_s8(v, x, transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx3(v, t.x, t.y, t.z, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_u8) +@(require_results, enable_target_feature = "neon") +vtbx3_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x3_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + x := uint8x16x2_t { + vcombine_u8(t.x, t.y), + vcombine_u8(t.z, uint8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(24)), + vqtbx2_u8(v, x, idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx3( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_s8) +@(require_results, enable_target_feature = "neon") +vtbx4_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x8x4_t, idx: int8x8_t) -> int8x8_t { + when ODIN_ARCH == .arm64 { + x := int8x16x2_t { + vcombine_s8(t.x, t.y), + vcombine_s8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, int8x8_t(32)), + vqtbx2_s8(v, x, transmute(uint8x8_t)idx), + v, + ) + } else { + return _vtbx4(v, t.x, t.y, t.z, t.w, idx) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_u8) +@(require_results, enable_target_feature = "neon") +vtbx4_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x4_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ARCH == .arm64 { + x := uint8x16x2_t { + vcombine_u8(t.x, t.y), + vcombine_u8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(32)), + vqtbx2_u8(v, x, idx), + v, + ) + } else { + return transmute(uint8x8_t)_vtbx4( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)t.w, + transmute(int8x8_t)idx, + ) + } +} + when ODIN_ARCH == .arm64 { // Table Lookup. // @@ -1170,6 +1336,398 @@ when ODIN_ARCH == .arm64 { return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_s8) + @(require_results, enable_target_feature = "neon") + vqtbx1_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx1(v, t, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx1(v, t, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_u8) + @(require_results, enable_target_feature = "neon") + vqtbx1_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx1q(v, t, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx1q(v, t, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_s8) + @(require_results, enable_target_feature = "neon") + vqtbx2_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x2_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx2(v, t.x, t.y, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx2(v, t.x, t.y, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_u8) + @(require_results, enable_target_feature = "neon") + vqtbx2_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x2_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x2_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx2q(v, t.x, t.y, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx2q(v, t.x, t.y, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x2_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_s8) + @(require_results, enable_target_feature = "neon") + vqtbx3_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x3_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx3(v, t.x, t.y, t.z, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx3(v, t.x, t.y, t.z, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_u8) + @(require_results, enable_target_feature = "neon") + vqtbx3_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x3_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x3_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx3q(v, t.x, t.y, t.z, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx3q(v, t.x, t.y, t.z, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x3_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_s8) + @(require_results, enable_target_feature = "neon") + vqtbx4_s8 :: #force_inline proc "c" (v: int8x8_t, t: int8x16x4_t, idx: uint8x8_t) -> int8x8_t { + when ODIN_ENDIAN == .Little { + return _vqtbx4(v, t.x, t.y, t.z, t.w, idx) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx4(v, t.x, t.y, t.z, t.w, idx) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_u8) + @(require_results, enable_target_feature = "neon") + vqtbx4_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x16x4_t, idx: uint8x8_t) -> uint8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_s8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_s8 :: #force_inline proc "c" (v: int8x16_t, t: int8x16x4_t, idx: uint8x16_t) -> int8x16_t { + when ODIN_ENDIAN == .Little { + return _vqtbx4q(v, t.x, t.y, t.z, t.w, idx) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := int8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := _vqtbx4q(v, t.x, t.y, t.z, t.w, idx) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_u8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_u8 :: #force_inline proc "c" (v: uint8x16_t, t: uint8x16x4_t, idx: uint8x16_t) -> uint8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(uint8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := uint8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(uint8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } } @(private, default_calling_convention = "none") @@ -1199,6 +1757,14 @@ when ODIN_ARCH == .arm32 { _vtbl3 :: proc(t0, t1, t2: int8x8_t, idx: int8x8_t) -> int8x8_t --- @(link_name = "llvm.arm.neon.vtbl4") _vtbl4 :: proc(t0, t1, t2, t3: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx1") + _vtbx1 :: proc(v: int8x8_t, t: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx2") + _vtbx2 :: proc(v: int8x8_t, t0, t1: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx3") + _vtbx3 :: proc(v: int8x8_t, t0, t1, t2: int8x8_t, idx: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.arm.neon.vtbx4") + _vtbx4 :: proc(v: int8x8_t, t0, t1, t2, t3: int8x8_t, idx: int8x8_t) -> int8x8_t --- } } @@ -1221,5 +1787,21 @@ when ODIN_ARCH == .arm64 { _vqtbl4 :: proc(t0, t1, t2, t3: int8x16_t, idx: uint8x8_t) -> int8x8_t --- @(link_name = "llvm.aarch64.neon.tbl4.v16i8") _vqtbl4q :: proc(t0, t1, t2, t3: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx1.v8i8") + _vqtbx1 :: proc(v: int8x8_t, t: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx1.v16i8") + _vqtbx1q :: proc(v: int8x16_t, t: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx2.v8i8") + _vqtbx2 :: proc(v: int8x8_t, t0, t1: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx2.v16i8") + _vqtbx2q :: proc(v: int8x16_t, t0, t1: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx3.v8i8") + _vqtbx3 :: proc(v: int8x8_t, t0, t1, t2: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx3.v16i8") + _vqtbx3q :: proc(v: int8x16_t, t0, t1, t2: int8x16_t, idx: uint8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.tbx4.v8i8") + _vqtbx4 :: proc(v: int8x8_t, t0, t1, t2, t3: int8x16_t, idx: uint8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.tbx4.v16i8") + _vqtbx4q :: proc(v: int8x16_t, t0, t1, t2, t3: int8x16_t, idx: uint8x16_t) -> int8x16_t --- } } diff --git a/core/simd/arm/pmull.odin b/core/simd/arm/pmull.odin index 56053503f..b2baa85db 100644 --- a/core/simd/arm/pmull.odin +++ b/core/simd/arm/pmull.odin @@ -502,6 +502,100 @@ vtbl4_p8 :: #force_inline proc "c" (t: poly8x8x4_t, idx: poly8x8_t) -> poly8x8_t } } +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx1_p8) +@(require_results, enable_target_feature = "neon") +vtbx1_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(8)), + vqtbx1_p8(v, vcombine_p8(t, poly8x8_t{}), idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx1( + transmute(int8x8_t)v, + transmute(int8x8_t)t, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx2_p8) +@(require_results, enable_target_feature = "neon") +vtbx2_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x2_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + return simd.select( + simd.lanes_lt(idx, uint8x8_t(16)), + vqtbx1_p8(v, vcombine_p8(t.x, t.y), idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx2( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx3_p8) +@(require_results, enable_target_feature = "neon") +vtbx3_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x3_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + x := poly8x16x2_t { + vcombine_p8(t.x, t.y), + vcombine_p8(t.z, poly8x8_t{}), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(24)), + vqtbx2_p8(v, x, idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx3( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)idx, + ) + } +} + +// Extended Table Lookup. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vtbx4_p8) +@(require_results, enable_target_feature = "neon") +vtbx4_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x4_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ARCH == .arm64 { + x := poly8x16x2_t { + vcombine_p8(t.x, t.y), + vcombine_p8(t.z, t.w), + } + return simd.select( + simd.lanes_lt(idx, uint8x8_t(32)), + vqtbx2_p8(v, x, idx), + v, + ) + } else { + return transmute(poly8x8_t)_vtbx4( + transmute(int8x8_t)v, + transmute(int8x8_t)t.x, + transmute(int8x8_t)t.y, + transmute(int8x8_t)t.z, + transmute(int8x8_t)t.w, + transmute(int8x8_t)idx, + ) + } +} + when ODIN_ARCH == .arm64 { // Polynomial multiply long // @@ -732,6 +826,246 @@ when ODIN_ARCH == .arm64 { return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1_p8) + @(require_results, enable_target_feature = "neon") + vqtbx1_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx1( + transmute(int8x8_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx1q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx1q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := simd.shuffle(t, t, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx1q( + transmute(int8x16_t)v, + transmute(int8x16_t)t, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2_p8) + @(require_results, enable_target_feature = "neon") + vqtbx2_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x2_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx2( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx2q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx2q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x2_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x2_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx2q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3_p8) + @(require_results, enable_target_feature = "neon") + vqtbx3_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x3_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx3( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx3q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx3q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x3_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x3_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx3q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4_p8) + @(require_results, enable_target_feature = "neon") + vqtbx4_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x16x4_t, idx: uint8x8_t) -> poly8x8_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x8_t)_vqtbx4( + transmute(int8x8_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 7, 6, 5, 4, 3, 2, 1, 0) + } + } + + // Extended Table Lookup. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqtbx4q_p8) + @(require_results, enable_target_feature = "neon") + vqtbx4q_p8 :: #force_inline proc "c" (v: poly8x16_t, t: poly8x16x4_t, idx: uint8x16_t) -> poly8x16_t { + when ODIN_ENDIAN == .Little { + return transmute(poly8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + } else { + v := simd.shuffle(v, v, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + t := poly8x16x4_t { + simd.shuffle(t.x, t.x, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.y, t.y, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.z, t.z, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + simd.shuffle(t.w, t.w, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), + } + idx := simd.shuffle(idx, idx, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + c := transmute(poly8x16_t)_vqtbx4q( + transmute(int8x16_t)v, + transmute(int8x16_t)t.x, + transmute(int8x16_t)t.y, + transmute(int8x16_t)t.z, + transmute(int8x16_t)t.w, + idx, + ) + return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + } + } } @(private, default_calling_convention = "none") From 87912a8b8889d08493c44cf4f89ae3150b061337 Mon Sep 17 00:00:00 2001 From: corley <28909106+corleypc@users.noreply.github.com> Date: Tue, 11 Aug 2026 03:20:13 +0300 Subject: [PATCH 12/93] fixes range loop over array elements in soa containers --- src/check_expr.cpp | 10 + src/check_stmt.cpp | 7 +- src/llvm_backend_expr.cpp | 34 +--- src/llvm_backend_general.cpp | 50 ++++- src/llvm_backend_stmt.cpp | 49 ++++- tests/core/runtime/test_core_runtime.odin | 226 ++++++++++++++++++++++ 6 files changed, 341 insertions(+), 35 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index cb04b2558..2ca6e6df4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -12124,6 +12124,16 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, case Type_Array: valid = true; max_count = t->Array.count; + if (is_type_soa_pointer(o->type)) { + // #soa element pointer; the pointed element is scattered like soa[i] itself, + // so it can't be sliced through the ptr (nor directly -> soa[i][:] is also rejected below) + gbString str = expr_to_string(node); + error(node, "Cannot slice '%s' through an #soa pointer, element is not contiguous in memory", str); + gb_string_free(str); + o->mode = Addressing_Invalid; + o->expr = node; + return kind; + } if (o->mode != Addressing_Variable && !is_type_pointer(o->type)) { gbString str = expr_to_string(node); error(node, "Cannot slice array '%s', value is not addressable", str); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f9ab8198f..c5657f008 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1883,7 +1883,12 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; case Type_Array: - is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; + // for #soa container with array element type, the element carries Addressing_SoaVariable, + // rather than Addressing_Variable; the element itself has no address, + // but each component does, so for &v in soa[i] is addressable + is_possibly_addressable = operand.mode == Addressing_Variable || + operand.mode == Addressing_SoaVariable || + is_ptr; array_add(&vals, t->Array.elem); array_add(&vals, t_int); break; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 4973716b3..2ef7f9b29 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -5087,26 +5087,11 @@ gb_internal lbAddr lb_build_addr_soa_elem_index(lbProcedure *p, Ast *expr, lbAdd return lb_addr_soa_field_elem(lb_soa_field_elem_ptr(p, soa_addr.addr, cast(i32)component, soa_addr.soa.index)); } - // for non-constant index do chain select between the component pointers; - // an element array holds at most 4 components, so this is at most 3 compares and 3 selects (branchless), - // and should be folded if j turns out constant after inlining; - // TODO: more efficient codegen can be done, most definitely for fixed kind, possibly for slice/dynamic, - // (but this works for both kinds) - // // Note: a temp holding the whole element would be simpler but would not be an lvalue, // and writes go through here lbValue index = lb_emit_conv(p, lb_build_expr(p, ie->index), t_int); lb_emit_bounds_check(p, ast_token(ie->index), index, lb_const_int(p->module, t_int, component_count)); - - lbValue ptr = lb_soa_field_elem_ptr(p, soa_addr.addr, 0, soa_addr.soa.index); - // lb_emit_select evaluates both arms, so every candidate address is formed; - // only the selected one is loaded or stored through - for (i64 component = 1; component < component_count; component++) { - lbValue candidate = lb_soa_field_elem_ptr(p, soa_addr.addr, cast(i32)component, soa_addr.soa.index); - lbValue is_component = lb_emit_comp(p, Token_CmpEq, index, lb_const_int(p->module, t_int, component)); - ptr = lb_emit_select(p, is_component, candidate, ptr); - } - return lb_addr_soa_field_elem(ptr); + return lb_addr_soa_field_elem(lb_soa_array_component_elem_ptr(p, soa_addr.addr, index, soa_addr.soa.index, component_count)); } gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { @@ -5194,6 +5179,13 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { if (array_addr.kind == lbAddr_SoaVariable) { return lb_build_addr_soa_elem_index(p, expr, array_addr, t); } + // p[j], where p is an #soa element pointer (e.g. p := &soa[i]) + if (is_type_soa_pointer(type_of_expr(ie->expr))) { + // build a soa variable from the soa ptr to get the address of the component + lbValue soa_ptr = lb_addr_load(p, array_addr); + lbAddr soa_addr = lb_addr_soa_variable_from_soa_ptr(p, soa_ptr); + return lb_build_addr_soa_elem_index(p, expr, soa_addr, t); + } lbValue array = lb_addr_get_ptr(p, array_addr); if (deref) { array = lb_emit_load(p, array); @@ -6578,10 +6570,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { // base p doesn't lower to an lbAddr_SoaVariable on its own // (it is a local holding the soa pointer), so build the element // addr here the same way an explicit p^ does - lbValue value = lb_build_expr(p, se->expr); - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - addr = lb_addr_soa_variable(ptr, idx, nullptr); + addr = lb_addr_soa_variable_from_soa_ptr(p, lb_build_expr(p, se->expr)); } else { addr = lb_build_addr(p, se->expr); } @@ -6803,10 +6792,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { case_ast_node(de, DerefExpr, expr); Type *t = type_of_expr(de->expr); if (is_type_soa_pointer(t)) { - lbValue value = lb_build_expr(p, de->expr); - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - return lb_addr_soa_variable(ptr, idx, nullptr); + return lb_addr_soa_variable_from_soa_ptr(p, lb_build_expr(p, de->expr)); } lbValue addr = lb_build_expr(p, de->expr); return lb_addr(addr); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 17a2db143..9e9c09051 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -616,6 +616,16 @@ gb_internal lbAddr lb_addr_soa_variable(lbValue addr, lbValue index, Ast *index_ return v; } +// lbAddr_SoaVariable for the element pointed by an #soa pointer, +// unpacked from the ptr's {^container, index} pair +// +// the nullptr index_expr is deliberate, there is no source index expression, +// the index was already bounds checked when the pointer was formed (e.g. p := &soa[i]) +gb_internal lbAddr lb_addr_soa_variable_from_soa_ptr(lbProcedure *p, lbValue soa_ptr) { + GB_ASSERT_MSG(is_type_soa_pointer(soa_ptr.type), "%s", type_to_string(soa_ptr.type)); + return lb_addr_soa_variable(lb_emit_struct_ev(p, soa_ptr, 0), lb_emit_struct_ev(p, soa_ptr, 1), nullptr); +} + // pointer to the index element of the field_index component // // the returned pointer type depends on the soa kind (because the field types do): @@ -634,6 +644,41 @@ gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 f return lb_emit_ptr_offset(p, lb_emit_load(p, field), index); } +// the same address as lb_soa_field_elem_ptr, for a component index only known at runtime, +// this is array-element #soa only, unlike lb_soa_field_elem_ptr which serves any soa kind +// +// Note: the caller bounds checks component_index if needed +gb_internal lbValue lb_soa_array_component_elem_ptr(lbProcedure *p, lbValue soa_ptr, lbValue component_index, lbValue elem_index, i64 component_count) { + Type *t = base_type(type_deref(soa_ptr.type)); + GB_ASSERT_MSG(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None, "%s", type_to_string(t)); + GB_ASSERT_MSG(base_type(t->Struct.soa_elem)->kind == Type_Array, + "indexing a component at runtime needs uniformly typed fields, got element %s", + type_to_string(t->Struct.soa_elem)); + if (component_count == 0) { + // a [0]T element has no components, nor does its soa struct have a field to get a ptr to; + // emit a typed nil so that code is well formed; + // callers either check bounds (driect indexing) or skip the code (e.g. range loop over array element), + // so this is never dereferenced when bounds checks are on; + // with bounds checks off the access is out of bounds by definition + return lb_const_nil(p->module, alloc_type_pointer(base_type(t->Struct.soa_elem)->Array.elem)); + } + // do chain select between the component pointers; + // an element array holds at most 4 components, so this is at most 3 compares and 3 selects (branchless), + // and it is folded if j is resolved to constant after inlining/unrolling; + // TODO: more efficient codegen can be done, most definitely for fixed kind, possibly for slice/dynamic, + // (but this works for both kinds) + // + // lb_emit_select evaluates both arms, so every candidate address is + // formed; only the selected one is dereferenced + lbValue ptr = lb_soa_field_elem_ptr(p, soa_ptr, 0, elem_index); + for (i64 component = 1; component < component_count; component++) { + lbValue candidate = lb_soa_field_elem_ptr(p, soa_ptr, cast(i32)component, elem_index); + lbValue is_component = lb_emit_comp(p, Token_CmpEq, component_index, lb_const_int(p->module, t_int, component)); + ptr = lb_emit_select(p, is_component, candidate, ptr); + } + return ptr; +} + // lbAddr over an lb_soa_field_elem_ptr pointer, retyped ^T when it came as [^]T gb_internal lbAddr lb_addr_soa_field_elem(lbValue ptr) { if (is_type_multi_pointer(ptr.type)) { @@ -1502,10 +1547,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); return lbValue{v, t}; } else if (is_type_soa_pointer(value.type)) { - lbValue ptr = lb_emit_struct_ev(p, value, 0); - lbValue idx = lb_emit_struct_ev(p, value, 1); - lbAddr addr = lb_addr_soa_variable(ptr, idx, nullptr); - return lb_addr_load(p, addr); + return lb_addr_load(p, lb_addr_soa_variable_from_soa_ptr(p, value)); } GB_ASSERT_MSG(is_type_pointer(value.type), "%s", type_to_string(value.type)); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index c834dc192..47cf5a10d 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -377,11 +377,22 @@ gb_internal void lb_build_when_stmt(lbProcedure *p, AstWhenStmt *ws) { gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_type, lbValue count_ptr, lbValue *val_, lbValue *idx_, lbBlock **loop_, lbBlock **done_, - bool is_reverse, i64 unroll_count=0) { + bool is_reverse, i64 unroll_count=0, lbAddr const *soa_elem=nullptr) { lbModule *m = p->module; + // when ranging over an #soa element rather than an array, expr is unused, there being no array + // to point at, so expr_type (the [N]T being ranged over) comes from the container's soa_elem + bool const is_soa_elem = soa_elem != nullptr && soa_elem->kind == lbAddr_SoaVariable; + lbValue count = {}; - Type *expr_type = base_type(type_deref(expr.type)); + Type *expr_type = nullptr; + if (is_soa_elem) { + Type *soa = base_type(type_deref(soa_elem->addr.type)); + GB_ASSERT(soa->kind == Type_Struct && soa->Struct.soa_kind != StructSoa_None); + expr_type = base_type(soa->Struct.soa_elem); + } else { + expr_type = base_type(type_deref(expr.type)); + } switch (expr_type->kind) { case Type_Array: count = lb_const_int(m, t_int, expr_type->Array.count); @@ -472,7 +483,13 @@ gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_ switch (expr_type->kind) { case Type_Array: { if (val_type != nullptr) { - val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); + if (is_soa_elem) { + lbValue ptr = lb_soa_array_component_elem_ptr(p, soa_elem->addr, idx, soa_elem->soa.index, expr_type->Array.count); + lbAddr component = lb_addr_soa_field_elem(ptr); + val = lb_emit_load(p, component.addr); + } else { + val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); + } } break; } @@ -1343,7 +1360,8 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc break; } case Type_Array: { - lbValue array; + lbValue array = {}; + lbAddr const *soa_elem = nullptr; lbAddr addr = lb_build_addr(p, expr); switch (addr.kind) { case lbAddr_Swizzle: @@ -1352,9 +1370,28 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc // NOTE(laytan): apply the swizzle. array = lb_address_from_load(p, lb_addr_load(p, addr)); break; + case lbAddr_SoaVariable: + // for v in soa[i] (and other direct forms); + // there is no array here to range over, the element is scattered across the field + // arrays and has no address of its own, only a container pointer and the element + // index, which is what this lbAddr carries. lb_build_range_indexed uses those + // with the loop counter as the component index to address each component directly + // + // the element index bounds check is hoisted here, + // the component index is in range by construction and needs none + lb_emit_soa_index_bounds_check(p, addr.addr, addr.soa.index, addr.soa.index_expr); + soa_elem = &addr; + break; default: array = lb_addr_get_ptr(p, addr); - if (is_type_pointer(type_deref(array.type))) { + if (is_type_soa_pointer(type_deref(array.type))) { + // for v in p, where p is an #soa element ptr (e.g. p := &soa[i]); + // we need to build the soa variable from the soa ptr + // and then produce the soa_elem as in the lbAddr_SoaVariable case above; + lbValue soa_ptr = lb_emit_load(p, array); + addr = lb_addr_soa_variable_from_soa_ptr(p, soa_ptr); + soa_elem = &addr; + } else if (is_type_pointer(type_deref(array.type))) { array = lb_emit_load(p, array); } break; @@ -1362,7 +1399,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lbAddr count_ptr = lb_add_local_generated(p, t_int, false); lb_addr_store(p, count_ptr, lb_const_int(p->module, t_int, et->Array.count)); - lb_build_range_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse); + lb_build_range_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse, 0, soa_elem); break; } case Type_EnumeratedArray: { diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 8faa2e580..fafd16674 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -568,6 +568,27 @@ test_soa_array_elem_chained_indexing :: proc(t: ^testing.T) { testing.expect_value(t, dyn_write.x[1], 90) testing.expect_value(t, dyn_write.w[1], 93) + // through a pointer to the element + ep := &fixed_write[1] + k = 2 + testing.expect_value(t, ep[k], 72) + testing.expect_value(t, ep[1], ep^[1]) + ep[0] = 100 + testing.expect_value(t, fixed_write.x[1], 100) + ep[k] = 102 + testing.expect_value(t, fixed_write.z[1], 102) + pe := &ep[3] + pe^ = 103 + testing.expect_value(t, fixed_write.w[1], 103) + + eps := &slice_write[0] + eps[1] = 143 + testing.expect_value(t, dyn_write.y[0], 143) + epd := &dyn_write[1] + testing.expect_value(t, epd[k], 92) + epd[0] = 190 + testing.expect_value(t, dyn_write.x[1], 190) + // multi dim array element type, only the outer index is scattered nested: #soa[2][3][2]u16 nested[1].x = {1, 3} @@ -590,6 +611,211 @@ test_soa_array_elem_chained_indexing :: proc(t: ^testing.T) { testing.expect_value(t, nested.y[1][0], 51) testing.expect_value(t, nested.z[1][0], 52) testing.expect_value(t, nested[0][0], [2]u16{0, 0}) + + pn := &nested[1] + testing.expect_value(t, pn[0][0], 50) + pn[0][1] = 60 + testing.expect_value(t, nested.x[1][1], 60) +} + +// ranging over an element of array-element typed #soa, +// for v in soa[i], or for &v in soa[i] +@(test) +test_soa_array_elem_range :: proc(t: ^testing.T) { + fixed: #soa[3][4]u16 + fixed[0] = [4]u16{90, 91, 92, 93} + fixed[1] = [4]u16{10, 11, 12, 13} + + for v, j in fixed[1] { + testing.expect_value(t, v, fixed[1][j]) + } + + #reverse for v, j in fixed[0] { + testing.expect_value(t, v, fixed[0][j]) + } + + got: [4]u16 + i := 0 + for v in fixed[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // double loop + sum : u16 = 0 + for e in fixed { + for v in e { + sum += v + } + } + testing.expect_value(t, sum, 90 + 91 + 92 + 93 + 10 + 11 + 12 + 13) + + // through pointer to the container + got = {} + i = 0 + p := &fixed + k := 1 + for v in p[k] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // slice + slice := fixed[:] + got = {} + i = 0 + for v in slice[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{10, 11, 12, 13}) + + // dynamic + dyn: #soa[dynamic][4]u16 + defer delete(dyn) + append_soa(&dyn, [4]u16{20, 21, 22, 23}) + append_soa(&dyn, [4]u16{30, 31, 32, 33}) + got = {} + i = 0 + for v in dyn[1] { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{30, 31, 32, 33}) + + // multi dim array type + nested: #soa[2][3][2]u16 + nested[1] = [3][2]u16{{1, 2}, {3, 4}, {5, 6}} + flat: [6]u16 + n := 0 + for row in nested[1] { + for v in row { + flat[n] = v + n += 1 + } + } + testing.expect_value(t, flat, [6]u16{1, 2, 3, 4, 5, 6}) + + // a write during the loop is visible to later iterations + // (same as normal arrays) + live: #soa[2][4]u16 + live[0] = [4]u16{1, 2, 3, 4} + seen: [4]u16 + for v, j in live[0] { + if j == 0 { + live.z[0] = 99 + } + seen[j] = v + } + testing.expect_value(t, seen, [4]u16{1, 2, 99, 4}) + + ////////////////////////// + // for &v in soa[i] + ////////////////////////// + + // fixed: #soa[3][4]u16 + fixed[1] = [4]u16{1, 2, 3, 4} + for &v in fixed[1] { + v *= 10 + } + testing.expect_value(t, fixed[1], [4]u16{10, 20, 30, 40}) + testing.expect_value(t, fixed.x[1], 10) + testing.expect_value(t, fixed.w[1], 40) + + fixed[1] = [4]u16{1, 2, 3, 4} + for &v, j in fixed[1] { + v += u16(j) + } + testing.expect_value(t, fixed[1], [4]u16{1, 3, 5, 7}) + + fixed[1] = [4]u16{1, 2, 3, 4} + #reverse for &v in fixed[1] { + v *= 2 + } + testing.expect_value(t, fixed[1], [4]u16{2, 4, 6, 8}) + + // through pointer to the container + fixed[1] = [4]u16{1, 2, 3, 4} + k = 1 + p = &fixed + for &v in p[k] { + v += 100 + } + testing.expect_value(t, fixed[1], [4]u16{101, 102, 103, 104}) + + // slice + fixed[1] = [4]u16{1, 2, 3, 4} + slice = fixed[:] + for &v in slice[1] { + v *= 3 + } + testing.expect_value(t, fixed[1], [4]u16{3, 6, 9, 12}) + + // dynamic + dyn[0] = [4]u16{1, 2, 3, 4} + for &v in dyn[0] { + v += 5 + } + testing.expect_value(t, dyn[0], [4]u16{6, 7, 8, 9}) + + // multi dim array type + nested[0] = [2]u16{1, 2} + nested[1] = [2]u16{5, 6} + for &e in nested { + for &v in e { + v += 1 + } + } + testing.expect_value(t, nested[0], [2]u16{2, 3}) + testing.expect_value(t, nested[1], [2]u16{6, 7}) + + ////////////////////////// + // through a pointer to the element + // for v in ep^, or for v in ep + ////////////////////////// + + fixed[1] = [4]u16{1, 2, 3, 4} + ep := &fixed[1] + + got = {} + i = 0 + for v in ep^ { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{1, 2, 3, 4}) + + for &v, j in ep^ { + v += u16(10 * (j + 1)) + } + testing.expect_value(t, fixed[1], [4]u16{11, 22, 33, 44}) + + // auto-deref + fixed[1] = [4]u16{1, 2, 3, 4} + got = {} + i = 0 + for v in ep { + got[i] = v + i += 1 + } + testing.expect_value(t, got, [4]u16{1, 2, 3, 4}) + + for &v in ep { + v *= 2 + } + testing.expect_value(t, fixed[1], [4]u16{2, 4, 6, 8}) + + // a [0]T element has no components; the loop must not run + // (and the compiler must not crash :) + c0: #soa[2][0]u16 + n = 0 + for v in c0[0] { + _ = v + n += 1 + } + testing.expect_value(t, n, 0) } // "using" on an #soa for-in looping variable From 2f39bd797d4873cf59606d1e5a721d761e11312f Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 10 Aug 2026 20:57:47 -0700 Subject: [PATCH 13/93] riscv abi bug --- src/llvm_abi.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 528ddca88..36397ce71 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1764,49 +1764,53 @@ namespace lbAbiRiscv64 { // Flatten down the type so it is easier to check all the ABI conditions. // Note that we also need to remove all implicit padding fields Odin adds so we keep ABI // compatibility for struct declarations. + // The flattened form is for the floating-point rules, which are about the MEMBERS; the + // integer fallback below is about the OBJECT, so `size` stays the size of the original. + LLVMTypeRef fp_type = type; + LLVMTypeKind fp_kind = kind; + i64 fp_size = size; if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); if (fields.count == 1) { - type = fields[0]; + fp_type = fields[0]; } else { - type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + fp_type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); } - kind = LLVMGetTypeKind(type); - size = lb_sizeof(type); - GB_ASSERT_MSG(size == lb_sizeof(orig_type), "flattened: %s of size %d, original: %s of size %d", LLVMPrintTypeToString(type), size, LLVMPrintTypeToString(orig_type), lb_sizeof(orig_type)); + fp_kind = LLVMGetTypeKind(fp_type); + fp_size = lb_sizeof(fp_type); } - if (is_float(type) && size <= flen && *fprs_left >= 1) { + if (is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) { *fprs_left -= 1; return non_struct(c, orig_type); } - if (kind == LLVMStructTypeKind && size <= 2*flen) { - unsigned elem_count = LLVMCountStructElementTypes(type); + if (fp_kind == LLVMStructTypeKind && fp_size <= 2*flen) { + unsigned elem_count = LLVMCountStructElementTypes(fp_type); if (elem_count == 2) { - LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(type, 0); + LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(fp_type, 0); i64 ty1s = lb_sizeof(ty1); - LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(type, 1); + LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(fp_type, 1); i64 ty2s = lb_sizeof(ty2); if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { *fprs_left -= 2; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_register(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; - return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } } } From a50660b5bb672738049dcccb77733ca4de54b9f8 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 10 Aug 2026 21:11:43 -0700 Subject: [PATCH 14/93] apply the FP rules to over-aligned structs --- src/llvm_abi.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 36397ce71..e06aa9244 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1766,10 +1766,12 @@ namespace lbAbiRiscv64 { // compatibility for struct declarations. // The flattened form is for the floating-point rules, which are about the MEMBERS; the // integer fallback below is about the OBJECT, so `size` stays the size of the original. + // The rules are stated over the members alone, so the aggregate's size does not gate + // them: over-alignment grows a struct without changing any member type. LLVMTypeRef fp_type = type; LLVMTypeKind fp_kind = kind; i64 fp_size = size; - if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { + if (kind == LLVMStructTypeKind) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); @@ -1785,6 +1787,11 @@ namespace lbAbiRiscv64 { if (is_float(fp_type) && fp_size <= flen && *fprs_left >= 1) { *fprs_left -= 1; + if (fp_type != orig_type) { + // A struct that flattened to a single float has to be coerced to that float; + // handing back the original sends an over-aligned one to integer registers. + return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); + } return non_struct(c, orig_type); } From aec971197a727cfe06e9013d3b250750a5c65880 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 10 Aug 2026 21:34:08 -0700 Subject: [PATCH 15/93] riscv64: a pointer is not an integer in the FP calling convention --- src/llvm_abi.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index e06aa9244..06a083a2f 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1738,6 +1738,13 @@ namespace lbAbiRiscv64 { } } + // The psABI's rule is "one floating-point real and one integer (or bitfield)", and a pointer + // is not an integer. `is_register` admits pointers and keeps that meaning for its other + // callers, so the floating-point arms need their own predicate. + gb_internal bool is_int_member(LLVMTypeRef type) { + return LLVMGetTypeKind(type) == LLVMIntegerTypeKind && lb_sizeof(type) > 0; + } + gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_type) { LLVMContextRef c = m->ctx; @@ -1808,13 +1815,13 @@ namespace lbAbiRiscv64 { return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } - if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { + if (is_float(ty1) && is_int_member(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } - if (is_register(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { + if (is_int_member(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); From b3c254d629f715ea6cc225509df14736c2152365 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 10 Aug 2026 21:48:10 -0700 Subject: [PATCH 16/93] i386: pass aggregate arguments by value on the stack --- src/llvm_abi.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 528ddca88..3d4af355d 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -437,7 +437,9 @@ namespace lbAbi386 { if (sz == 0) { args[i] = lb_arg_type_ignore(t); } else { - args[i] = lb_arg_type_indirect(t, nullptr); + // Aggregates are pushed onto the stack by value, not passed as a pointer + // to a caller-owned copy. This is the rule for both i386 targets. + args[i] = lb_arg_type_indirect_byval(c, t); } } else { args[i] = non_struct(c, t, false); From 37e1c98b1f2ddf4f3c69b40e09d7ba281d86f2c6 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 10 Aug 2026 22:08:15 -0700 Subject: [PATCH 17/93] i386: return aggregates through the hidden pointer on System V targets --- src/llvm_abi.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 528ddca88..ffc224bda 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -450,12 +450,21 @@ namespace lbAbi386 { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { + // Only some i386 targets return a small aggregate in EDX:EAX. The Intel386 System V + // psABI returns every structure and union through the hidden pointer, with no size + // threshold; Windows and the BSDs return one of eight bytes or fewer in registers. + bool small_in_registers = build_context.metrics.os == TargetOs_windows || + build_context.metrics.os == TargetOs_freebsd || + build_context.metrics.os == TargetOs_openbsd; + i64 sz = lb_sizeof(return_type); - switch (sz) { - case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); - case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); - case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); - case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + if (small_in_registers) { + switch (sz) { + case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); + case 2: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 16), nullptr, nullptr); + case 4: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 32), nullptr, nullptr); + case 8: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 64), nullptr, nullptr); + } } LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); From a311d8f6df19964470f7a7ecbf3800577d58160d Mon Sep 17 00:00:00 2001 From: Franz Hoeltermann Date: Tue, 11 Aug 2026 13:51:06 +0200 Subject: [PATCH 18/93] Use enum value init expression instead of numeric value in doc format --- src/check_type.cpp | 1 + src/docs_writer.cpp | 3 +++ src/entity.cpp | 1 + 3 files changed, 5 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index 9414b794e..b56acc276 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1009,6 +1009,7 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam e->Constant.flags |= entity_flags; e->Constant.docs = docs; e->Constant.comment = comment; + e->Constant.init_expr = init; auto interned = entity_interned_name(e); diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 67d350f24..5fbe5a58c 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -890,6 +890,9 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) } break; case Entity_Constant: + if (init_expr == nullptr) { + init_expr = e->Constant.init_expr; + } field_group_index = e->Constant.field_group_index; break; case Entity_Procedure: diff --git a/src/entity.cpp b/src/entity.cpp index d3170c823..196380831 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -205,6 +205,7 @@ struct Entity { i32 field_group_index; CommentGroup *docs; CommentGroup *comment; + Ast *init_expr; // only used for enum values } Constant; struct { Ast *type_expr; // only used for some variables within procedure bodies From 9e73c2019747b8f20fab3e4d95f8f6c2f253dd95 Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:21:23 +0300 Subject: [PATCH 19/93] adds the pop procedure family for soa arrays --- base/runtime/core_builtin.odin | 15 +++++ base/runtime/core_builtin_soa.odin | 63 ++++++++++++++++++- tests/core/runtime/test_core_runtime.odin | 74 +++++++++++++++++++++-- 3 files changed, 145 insertions(+), 7 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 4913b53d9..56037f500 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -215,6 +215,8 @@ remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #a unordered_remove :: proc{ unordered_remove_dynamic_array, unordered_remove_fixed_capacity_dynamic_array, + + unordered_remove_soa, } @@ -222,6 +224,8 @@ unordered_remove :: proc{ ordered_remove :: proc{ ordered_remove_dynamic_array, ordered_remove_fixed_capacity_dynamic_array, + + ordered_remove_soa, } @builtin @@ -271,6 +275,8 @@ pop_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc := #cal pop :: proc{ pop_dynamic_array, pop_fixed_capacity_dynamic_array, + + pop_soa, } // `pop_safe_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. @@ -303,6 +309,8 @@ pop_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic pop_safe :: proc{ pop_safe_dynamic_array, pop_safe_fixed_capacity_dynamic_array, + + pop_safe_soa, } @@ -342,6 +350,8 @@ pop_front_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc : pop_front :: proc{ pop_front_dynamic_array, pop_front_fixed_capacity_dynamic_array, + + pop_front_soa, } @@ -381,6 +391,8 @@ pop_front_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[d pop_front_safe :: proc { pop_front_safe_dynamic_array, pop_front_safe_fixed_capacity_dynamic_array, + + pop_front_safe_soa, } @@ -1212,6 +1224,9 @@ inject_at :: proc{ inject_at_elem_fixed_capacity_dynamic_array, inject_at_elems_fixed_capacity_dynamic_array, inject_at_elem_string_fixed_capacity_dynamic_array, + + inject_at_elem_soa, + inject_at_elems_soa, } diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 61b93efd7..d355e3cee 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -56,7 +56,7 @@ Raw_SOA_Footer_Dynamic_Array :: struct { // Multipointer indexing lowers to GEP and doesn't capture, so prefer that throughout. @(builtin, require_results) -raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { +raw_soa_footer_slice :: proc "contextless" (array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) { if array == nil { return nil } @@ -65,7 +65,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl return } @(builtin, require_results) -raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { +raw_soa_footer_dynamic_array :: proc "contextless" (array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) { if array == nil { return nil } @@ -704,6 +704,58 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { return d } +// `pop_soa` will remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[len(array)-1] + raw_soa_footer_dynamic_array(array).len -= 1 + return +} + +// `pop_safe_soa` trys to remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[len(array)-1], true + raw_soa_footer_dynamic_array(array).len -= 1 + return +} + +// `pop_front_soa` will remove and return the first value of the #soa dynamic array `array` and reduces the length of `array` by 1, +// whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[0] + _ordered_remove_soa(array, 0) + return +} + +// `pop_front_safe_soa` trys to remove and return the first value of the #soa dynamic array `array` and reduces the +// length of `array` by 1, whilst keeping the order of the other elements. +// If the operation is not possible, it will return false. +// +// Note: This is an O(N) operation. +@builtin +pop_front_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + + res, ok = array[0], true + _ordered_remove_soa(array, 0) + return +} + // `unordered_remove_soa` 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. // @@ -714,7 +766,6 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { - // Use the compiler's #soa element load and store lowering. array[index] = array[len(array)-1] } raw_soa_footer_dynamic_array(array).len -= 1 @@ -728,6 +779,12 @@ unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int inde @builtin ordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) + _ordered_remove_soa(array, index) +} + +// the unchecked body of ordered_remove_soa, shared with the front pops. +// index must already be known to be in bounds. +_ordered_remove_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E, index: int) #no_bounds_check { if index+1 < len(array) { ti := type_info_of(typeid_of(T)) ti = type_info_base(ti) diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index fafd16674..13b0f02a0 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -1081,11 +1081,9 @@ test_memory_compare_zero :: proc(t: ^testing.T) { } } -// Runs identical append/inject/remove sequences for a #soa[dynamic] array +// Runs identical append/inject/remove/pop sequences for a #soa[dynamic] array // and an AoS [dynamic] reference, comparing all elements after each -// stage. Covers appends, injections (interior, at the end, and past the -// end where the gap must read as zero elements), unordered and ordered -// removes. +// stage. Covers appends, injections, pops, unordered and ordered removes. @(test) test_soa_array_append_inject_remove :: proc(t: ^testing.T) { check :: proc(t: ^testing.T, $E: typeid, mk: proc(i: int) -> E) { @@ -1242,6 +1240,74 @@ test_soa_array_append_inject_remove :: proc(t: ^testing.T) { testing.expect_value(t, err, nil) non_zero_append(&ref, mk(42)) expect_same(t, soa, ref) + + // interleave pops from both ends + n, err = append(&soa, ..buf[:]) + testing.expect_value(t, n, 32) + testing.expect_value(t, err, nil) + append(&ref, ..buf[:]) + for _ in 0..<8 { + testing.expect_value(t, pop_soa(&soa), pop(&ref)) + testing.expect_value(t, pop_front_soa(&soa), pop_front(&ref)) + } + expect_same(t, soa, ref) + + // safe pops from both ends, down to empty + popped: E + for len(ref) > 0 { + popped, ok = pop_safe_soa(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop(&ref)) + if len(ref) == 0 { + break + } + popped, ok = pop_front_safe_soa(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop_front(&ref)) + } + expect_same(t, soa, ref) + testing.expect_value(t, len(soa), 0) + + // safe pops must report failure on an empty array + _, ok = pop_safe_soa(&soa) + testing.expect(t, !ok) + _, ok = pop_front_safe_soa(&soa) + testing.expect(t, !ok) + + // test procedure groups resolution for the builtin names + for i in 0..<10 { + append(&soa, mk(700 + i)) + append(&ref, mk(700 + i)) + } + ok, err = inject_at(&soa, 3, mk(800)) + testing.expect(t, ok) + testing.expect_value(t, err, nil) + inject_at(&ref, 3, mk(800)) + expect_same(t, soa, ref) + + ok, err = inject_at(&soa, 2, ..buf[:BATCH_LEN]) + testing.expect(t, ok) + testing.expect_value(t, err, nil) + inject_at(&ref, 2, ..buf[:BATCH_LEN]) + expect_same(t, soa, ref) + + ordered_remove(&soa, 4) + ordered_remove(&ref, 4) + unordered_remove(&soa, 1) + unordered_remove(&ref, 1) + expect_same(t, soa, ref) + + testing.expect_value(t, pop(&soa), pop(&ref)) + testing.expect_value(t, pop_front(&soa), pop_front(&ref)) + expect_same(t, soa, ref) + + popped, ok = pop_safe(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop(&ref)) + popped, ok = pop_front_safe(&soa) + testing.expect(t, ok) + testing.expect_value(t, popped, pop_front(&ref)) + expect_same(t, soa, ref) } // mixed field widths + padding From 8c23c01c7196a1b81a743f17fb51d169fcf33a91 Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:30:00 +0300 Subject: [PATCH 20/93] minor wording tweaks --- base/runtime/core_builtin_soa.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index d355e3cee..48c48f305 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -760,7 +760,7 @@ pop_front_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) // 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_soa`. +// Note: If you want the elements to remain in their order, use `ordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { @@ -774,7 +774,7 @@ unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int inde // `ordered_remove_soa` 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_soa`. +// Note: If the elements do not have to remain in their order, prefer `unordered_remove_soa`. // Note: If the index is out of bounds, this procedure will panic. @builtin ordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { From a08682bcd3bb80636cccca169b736ec6c6df9af3 Mon Sep 17 00:00:00 2001 From: A1029384756 Date: Tue, 11 Aug 2026 16:30:35 -0400 Subject: [PATCH 21/93] [llvm_backend] fix duplicated name for `.ll` files --- src/llvm_backend.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 8b1474fb6..95df563fd 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2694,10 +2694,9 @@ gb_internal void lb_llvm_module_passes_and_verification(lbGenerator *gen, bool d } gb_internal String lb_filepath_ll_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), + String path = concatenate_strings(permanent_allocator(), build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name + STR_LIT("/") ); GB_ASSERT(m->module_name != nullptr); From 2285f4e061cf49472f2b6e2d62ee5016727a5c3d Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 16:53:57 -0700 Subject: [PATCH 22/93] sysv abi --- src/llvm_abi.cpp | 311 ++++++++++++++++++++++++-- tests/issues/run.bat | 2 + tests/issues/run.sh | 3 + tests/issues/test_issue_sysv_abi.c | 23 ++ tests/issues/test_issue_sysv_abi.odin | 64 ++++++ 5 files changed, 388 insertions(+), 15 deletions(-) create mode 100644 tests/issues/test_issue_sysv_abi.c create mode 100644 tests/issues/test_issue_sysv_abi.odin diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 528ddca88..20f6f33fd 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -599,11 +599,12 @@ namespace lbAbiAmd64SysV { }; gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off); + gb_internal void unify(Array *cls, i64 i, RegClass const newv); gb_internal void fixup(LLVMTypeRef t, Array *cls); gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, - i32 *int_regs, i32 *sse_regs); - gb_internal Array classify(LLVMTypeRef t); + i32 *int_regs, i32 *sse_regs, Type *source_type); + gb_internal Array classify(LLVMTypeRef t, Type *source_type); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { @@ -614,11 +615,11 @@ namespace lbAbiAmd64SysV { return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention, false, - nullptr, nullptr); + nullptr, nullptr, nullptr); } gb_internal LB_ABI_INFO(abi_info) { - LLVMContextRef c = m->ctx; + LLVMContextRef c = m->ctx; lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->calling_convention = calling_convention; @@ -626,14 +627,44 @@ namespace lbAbiAmd64SysV { i32 int_regs = 6; // rdi, rsi, rdx, rcx, r8, r9 i32 sse_regs = 8; // xmm0-xmm7 - ft->args = array_make(lb_function_type_args_allocator(), arg_count); - for (unsigned i = 0; i < arg_count; i++) { - ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention, - true, - &int_regs, &sse_regs); + // The source type of each parameter, where one exists. `arg_types` can carry entries + // with no counterpart. This walks the tuple the way lbAbiArm64 does and hands back nullptr once it runs out. + Entity **params = nullptr; + isize param_count = 0; + if (original_type != nullptr && original_type->kind == Type_Proc && original_type->Proc.params != nullptr) { + params = original_type->Proc.params->Tuple.variables.data; + param_count = original_type->Proc.params->Tuple.variables.count; } - ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + ft->args = array_make(lb_function_type_args_allocator(), arg_count); + for (unsigned i = 0, j = 0; i < arg_count; i++, j++) { + while (cast(isize)j < param_count && params[j]->kind != Entity_Variable) { + j++; + } + Type *source_type = cast(isize)j < param_count ? params[j]->type : nullptr; + + ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention, + true, + &int_regs, &sse_regs, source_type); + } + + // A single result can be classified from its source type too. A tuple keeps the lowered + // path: it is split into out-pointers below, and C has no such return shape anyway. + Type *return_source = nullptr; + if (return_is_defined && !return_is_tuple && + original_type != nullptr && original_type->kind == Type_Proc && + original_type->Proc.results != nullptr && + original_type->Proc.results->Tuple.variables.count == 1) { + return_source = original_type->Proc.results->Tuple.variables[0]->type; + } + + if (return_source != nullptr) { + ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention, + false, + nullptr, nullptr, return_source); + } else { + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + } return ft; } @@ -694,8 +725,8 @@ namespace lbAbiAmd64SysV { gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention, bool is_arg, - i32 *int_regs, i32 *sse_regs) { - auto cls = classify(type); + i32 *int_regs, i32 *sse_regs, Type *source_type) { + auto cls = classify(type, source_type); i32 needed_int = 0; i32 needed_sse = 0; for (auto c : cls) { @@ -782,15 +813,158 @@ namespace lbAbiAmd64SysV { return lb_arg_type_direct(type, nullptr, nullptr, attr); } - gb_internal Array classify(LLVMTypeRef t) { + // `classify_with` walks the LOWERED type, and lowering has already destroyed two + // distinctions the ABI rules need: Odin materializes padding as an explicit `[N x i8]` + // member, which is indistinguishable from a real `[N]u8` field, and a `#raw_union` becomes + // an opaque integer, which is indistinguishable from a real integer. §3.2.3 says padding + // contributes no class, and that a union merges the classes of all of its members. + // + // The source type still has both, so classify that instead where it is available. Only the + // kinds handled below are eligible; anything else falls back to the lowered walk, so an + // unrecognised type behaves exactly as it did before. + gb_internal bool source_is_classifiable(Type *t) { + Type *bt = base_type(t); + if (bt == nullptr) { + return false; + } + switch (bt->kind) { + case Type_Basic: + switch (bt->Basic.kind) { + case Basic_bool: case Basic_b8: case Basic_b16: case Basic_b32: case Basic_b64: + case Basic_i8: case Basic_u8: case Basic_i16: case Basic_u16: + case Basic_i32: case Basic_u32: case Basic_i64: case Basic_u64: + case Basic_i128: case Basic_u128: case Basic_int: case Basic_uint: + case Basic_uintptr: case Basic_rawptr: case Basic_rune: + case Basic_f16: case Basic_f32: case Basic_f64: + return true; + // Multi-word, but every word of them is a pointer or an integer, so the leaf rule + // below classifies them correctly without knowing their shape. + case Basic_string: case Basic_cstring: case Basic_any: case Basic_typeid: + return true; + } + return false; + case Type_Pointer: + case Type_MultiPointer: + case Type_Proc: + // Integer-backed, or aggregates of pointers and integers. None of them can contain a + // floating-point member, which is the only thing the leaf rule needs to tell apart. + case Type_Enum: + case Type_BitSet: + case Type_Slice: + case Type_DynamicArray: + return true; + case Type_Array: + return source_is_classifiable(bt->Array.elem); + // Odin matrices are laid out with no padding at all; see the note on + // matrix_type_stride_in_bytes + case Type_Matrix: + return source_is_classifiable(bt->Matrix.elem); + case Type_Struct: + if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { + return false; + } + for (Entity *f : bt->Struct.fields) { + if (!source_is_classifiable(f->type)) { + return false; + } + } + return true; + } + return false; + } + + gb_internal void classify_source(Type *t, Array *cls, i64 ix, i64 off) { + Type *bt = base_type(t); + i64 t_size = type_size_of(bt); + i64 t_align = type_align_of(bt); + + if (t_align != 0 && (off % t_align) != 0) { + i64 e = (off + t_size + 7) / 8; + for (i64 i = off / 8; i < e; i++) { + unify(cls, ix+i, RegClass_Memory); + } + return; + } + + switch (bt->kind) { + case Type_Struct: + // A `#raw_union` has every member at offset zero, and §3.2.3 merges them all -- + // which is what makes `union{f32, u32}` INTEGER while `union{f32, f32}` is SSE. + if (bt->Struct.is_raw_union) { + for (Entity *f : bt->Struct.fields) { + classify_source(f->type, cls, ix, off); + } + } else { + for_array(i, bt->Struct.fields) { + Type *ft = nullptr; + i64 foff = type_offset_of(bt, i, &ft); + classify_source(ft, cls, ix, off + foff); + } + } + break; + case Type_Array: { + Type *elem = bt->Array.elem; + i64 stride = type_size_of(elem); + for (i64 i = 0; i < bt->Array.count; i++) { + classify_source(elem, cls, ix, off + i*stride); + } + break; + } + case Type_Matrix: { + Type *elem = bt->Matrix.elem; + i64 stride = type_size_of(elem); + i64 count = matrix_type_total_internal_elems(bt); + for (i64 i = 0; i < count; i++) { + classify_source(elem, cls, ix, off + i*stride); + } + break; + } + default: + if (is_type_float(bt)) { + switch (t_size) { + case 2: unify(cls, ix + off/8, (off%8 != 0) ? RegClass_SSEHv : RegClass_SSEHs); break; + case 4: unify(cls, ix + off/8, (off%8 == 4) ? RegClass_SSEFv : RegClass_SSEFs); break; + default: unify(cls, ix + off/8, RegClass_SSEDs); break; + } + } else { + i64 s = t_size; + while (s > 0) { + unify(cls, ix + off/8, RegClass_Int); + off += 8; + s -= 8; + } + } + break; + } + } + + gb_internal Array classify(LLVMTypeRef t, Type *source_type) { i64 sz = lb_sizeof(t); i64 words = (sz + 7)/8; auto reg_classes = array_make(heap_allocator(), cast(isize)words); if (words > 4) { all_mem(®_classes); } else { - classify_with(t, ®_classes, 0, 0); + bool from_source = source_type != nullptr && source_is_classifiable(source_type) && + type_size_of(base_type(source_type)) == sz; + if (from_source) { + classify_source(source_type, ®_classes, 0, 0); + } else { + classify_with(t, ®_classes, 0, 0); + } fixup(t, ®_classes); + if (from_source) { + // An eightbyte that ends up NO_CLASS is not passed at all. Only the source walk + // can produce one, the lowered walk classifies padding as INTEGER, and + // nothing downstream has a case for it. + // + // This has to come AFTER `fixup`, which counts eightbytes to apply "larger than + // two eightbytes is MEMORY". Dropping them first makes `#align(32){f32}` look + // like a single SSE eightbyte instead of the memory argument it is. + while (reg_classes.count > 0 && reg_classes[reg_classes.count-1] == RegClass_NoClass) { + array_pop(®_classes); + } + } } return reg_classes; } @@ -1125,6 +1299,10 @@ namespace lbAbiAmd64SysV { namespace lbAbiArm64 { gb_internal Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, Type* original_type); + gb_internal bool is_register(LLVMTypeRef type); + gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); + gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_); + gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count); gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); @@ -1133,7 +1311,30 @@ namespace lbAbiArm64 { lbFunctionType *ft = permanent_alloc_item(); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, original_type); - ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + + // The same union case as in compute_arg_types, in return position. A tuple keeps the + // lowered path; C has no such return shape, and the split into out-pointers below is + // driven by the lowered type. + Type *return_source = nullptr; + if (return_is_defined && !return_is_tuple && + original_type != nullptr && original_type->kind == Type_Proc && + original_type->Proc.results != nullptr && + original_type->Proc.results->Tuple.variables.count == 1) { + return_source = original_type->Proc.results->Tuple.variables[0]->type; + } + + LLVMTypeRef ret_base_type = nullptr; + unsigned ret_member_count = 0; + if (return_source != nullptr && + !is_register(return_type) && + !is_homogenous_aggregate(c, return_type, nullptr, nullptr) && + is_homogenous_aggregate_source(c, return_source, &ret_base_type, &ret_member_count) && + is_homogenous_aggregate_small_enough(ret_base_type, ret_member_count)) { + ft->ret = lb_arg_type_direct(return_type, llvm_array_type(ret_base_type, ret_member_count), nullptr, nullptr); + } else { + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); + } + ft->calling_convention = calling_convention; return ft; } @@ -1261,6 +1462,77 @@ namespace lbAbiArm64 { return false; } + // §5.9.5 defines a Homogeneous Floating-point Aggregate over Composite Types + // Odin lowers `#raw_union` to an opaque integer, so by the time the lowered type is + // inspected the members are gone and `union{f32, f32}` is indistinguishable from an `i32`. + // The source type still has them. + gb_internal bool is_homogenous_aggregate_source(LLVMContextRef c, Type *t, LLVMTypeRef *base_type_, unsigned *member_count_) { + if (t == nullptr) { + return false; + } + Type *bt = base_type(t); + if (bt == nullptr) { + return false; + } + switch (bt->kind) { + case Type_Basic: + switch (bt->Basic.kind) { + case Basic_f32: + if (base_type_) *base_type_ = LLVMFloatTypeInContext(c); + if (member_count_) *member_count_ = 1; + return true; + case Basic_f64: + if (base_type_) *base_type_ = LLVMDoubleTypeInContext(c); + if (member_count_) *member_count_ = 1; + return true; + } + return false; + case Type_Array: { + LLVMTypeRef elem_base = nullptr; + unsigned elem_count = 0; + if (!is_homogenous_aggregate_source(c, bt->Array.elem, &elem_base, &elem_count)) { + return false; + } + if (base_type_) *base_type_ = elem_base; + if (member_count_) *member_count_ = cast(unsigned)(elem_count * bt->Array.count); + return true; + } + case Type_Struct: { + if (bt->Struct.is_packed || bt->Struct.soa_kind != StructSoa_None) { + return false; + } + LLVMTypeRef found_base = nullptr; + unsigned total = 0; + for (Entity *f : bt->Struct.fields) { + LLVMTypeRef field_base = nullptr; + unsigned field_count = 0; + if (!is_homogenous_aggregate_source(c, f->type, &field_base, &field_count)) { + return false; + } + if (found_base == nullptr) { + found_base = field_base; + total = field_count; + } else if (found_base != field_base) { + return false; + } else { + total = bt->Struct.is_raw_union ? gb_max(total, field_count) : total + field_count; + } + } + if (found_base == nullptr) { + return false; + } + // Rejects anything with padding, matching is_homogenous_struct. + if (type_size_of(bt) != lb_sizeof(found_base) * cast(i64)total) { + return false; + } + if (base_type_) *base_type_ = found_base; + if (member_count_) *member_count_ = total; + return true; + } + } + return false; + } + gb_internal unsigned is_homogenous_aggregate_small_enough(LLVMTypeRef base_type, unsigned member_count) { return (member_count <= 4); } @@ -1325,6 +1597,12 @@ namespace lbAbiArm64 { LLVMTypeRef homo_base_type = {}; unsigned homo_member_count = 0; + // A `#raw_union` lowers to a struct wrapping an opaque integer, so it is not a + // homogeneous aggregate by the lowered type and falls through to the generic size + // path below. §5.9.5 counts a union as a Composite Type, so ask the source type. + LLVMTypeRef src_base_type = nullptr; + unsigned src_member_count = 0; + if (is_register(type)) { args[i] = non_struct(c, type, ptype); } else if (is_homogenous_aggregate(c, type, &homo_base_type, &homo_member_count)) { @@ -1333,6 +1611,9 @@ namespace lbAbiArm64 { } else { args[i] = lb_arg_type_indirect(type, nullptr);; } + } else if (is_homogenous_aggregate_source(c, ptype, &src_base_type, &src_member_count) && + is_homogenous_aggregate_small_enough(src_base_type, src_member_count)) { + args[i] = lb_arg_type_direct(type, llvm_array_type(src_base_type, src_member_count), nullptr, nullptr); } else { i64 size = lb_sizeof(type); if (size <= 16) { diff --git a/tests/issues/run.bat b/tests/issues/run.bat index ff02105d7..d01c85e14 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -43,6 +43,8 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b ..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b +clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b +..\..\..\odin test ..\test_issue_sysv_abi.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7073-1.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index fe4db8a4b..c4dae65f9 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -110,6 +110,9 @@ fi clang -c ../test_issue_7010.c -o test_issue_7010_c.o $ODIN test ../test_issue_7010.odin $COMMON +clang -c ../test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o +$ODIN test ../test_issue_sysv_abi.odin $COMMON + clang -c ../test_issue_6809_6816.c -o test_issue_6809_6816_c.o -O3 $ODIN test ../test_issue_6809_6816.odin -o:speed $COMMON diff --git a/tests/issues/test_issue_sysv_abi.c b/tests/issues/test_issue_sysv_abi.c new file mode 100644 index 000000000..0c7de35a1 --- /dev/null +++ b/tests/issues/test_issue_sysv_abi.c @@ -0,0 +1,23 @@ +// Support file for test_issue_sysv_abi.odin +// +// Each callee returns its second argument, so the value that comes back says +// where the struct before it went. If the aggregate consumes the wrong number or +// the wrong file of registers, the following argument is read from the wrong +// place deterministically rather than by scratch-register coincidence. + +typedef struct { long a; float b; } Pad_Int_Float; +typedef struct { float a; double b; } Pad_Float_Double; +typedef struct { float a, b; } No_Pad; +typedef struct { struct { float x; } a; double b; } Nested; +typedef union { float x; float y; } Union_Float; +typedef struct { union { float x; float y; } u; double b; } Union_In_Struct; + +double c_pad_int_float (Pad_Int_Float s, double next) { (void)s; return next; } +double c_pad_float_double(Pad_Float_Double s, double next) { (void)s; return next; } +double c_no_pad (No_Pad s, double next) { (void)s; return next; } +double c_nested (Nested s, double next) { (void)s; return next; } +double c_union_float (Union_Float s, double next) { (void)s; return next; } +double c_union_in_struct (Union_In_Struct s, double next) { (void)s; return next; } + +Pad_Int_Float c_make_pad_int_float(void) { Pad_Int_Float s = {11, 2.5f}; return s; } +Union_Float c_make_union_float(void) { Union_Float s; s.x = 2.5f; return s; } diff --git a/tests/issues/test_issue_sysv_abi.odin b/tests/issues/test_issue_sysv_abi.odin new file mode 100644 index 000000000..1be3e70c3 --- /dev/null +++ b/tests/issues/test_issue_sysv_abi.odin @@ -0,0 +1,64 @@ +// The ABI classifiers ran over the lowered type, where Odin has already turned +// padding into an explicit `[N x i8]` member and a `#raw_union` into an opaque +// integer. SysV contributes no class for padding and merges a union's members, +// and AAPCS64 counts a union as a Composite Type. So a struct with an `f32` +// alone in an eightbyte went to an integer register where C uses SSE, and a +// union of floats never reached a floating-point register at all. +// +// Being an ABI guarantee, must be cross-checked against a c compiler +package test_issues + +import "core:testing" + +Pad_Int_Float :: struct { a: i64, b: f32 } // f32 alone in eightbyte 1 +Pad_Float_Double :: struct { a: f32, b: f64 } // f32 alone in eightbyte 0 +No_Pad :: struct { a: f32, b: f32 } // fills its eightbyte exactly +Nested :: struct { a: struct{ x: f32 }, b: f64 } +Union_Float :: struct #raw_union { x: f32, y: f32 } +Union_In_Struct :: struct { u: Union_Float, b: f64 } + +foreign import lib "build/test_issue_sysv_abi_c.o" + +@(default_calling_convention="c") +foreign lib { + c_pad_int_float :: proc(s: Pad_Int_Float, next: f64) -> f64 --- + c_pad_float_double :: proc(s: Pad_Float_Double, next: f64) -> f64 --- + c_no_pad :: proc(s: No_Pad, next: f64) -> f64 --- + c_nested :: proc(s: Nested, next: f64) -> f64 --- + c_union_float :: proc(s: Union_Float, next: f64) -> f64 --- + c_union_in_struct :: proc(s: Union_In_Struct, next: f64) -> f64 --- + + c_make_pad_int_float :: proc() -> Pad_Int_Float --- + c_make_union_float :: proc() -> Union_Float --- +} + +// The control. It has no padding and no union, so it was correct before the fix +// and must stay correct. Without it, "padding is misclassified" and "f32 pairs +// are broken" would look the same. +@(test) +test_no_padding_control :: proc(t: ^testing.T) { + testing.expect_value(t, c_no_pad(No_Pad{1, 3.5}, 7), f64(7)) +} + +@(test) +test_padded_struct_arguments :: proc(t: ^testing.T) { + testing.expect_value(t, c_pad_int_float(Pad_Int_Float{1, 3.5}, 7), f64(7)) + testing.expect_value(t, c_pad_float_double(Pad_Float_Double{3.5, 2}, 7), f64(7)) + testing.expect_value(t, c_nested(Nested{{3.5}, 2}, 7), f64(7)) +} + +@(test) +test_raw_union_arguments :: proc(t: ^testing.T) { + testing.expect_value(t, c_union_float(Union_Float{x = 3.5}, 7), f64(7)) + testing.expect_value(t, c_union_in_struct(Union_In_Struct{Union_Float{x = 3.5}, 2}, 7), f64(7)) +} + +@(test) +test_returns :: proc(t: ^testing.T) { + s := c_make_pad_int_float() + testing.expect_value(t, s.a, i64(11)) + testing.expect_value(t, s.b, f32(2.5)) + + u := c_make_union_float() + testing.expect_value(t, u.x, f32(2.5)) +} From d785843eedbdd6b1187bb7479f200443ca396a20 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 17:34:36 -0700 Subject: [PATCH 23/93] bitfield missing field offsets --- src/check_builtin.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..38d91fd67 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -7313,6 +7313,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As i64 bit_offset = 0; i64 bit_size = 0; + bool found = false; for_array(i, type->BitField.fields) { Entity *f = type->BitField.fields[i]; if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) { @@ -7322,9 +7323,18 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (field_name == str) { bit_offset = type->BitField.bit_offsets[i]; bit_size = type->BitField.bit_sizes[i]; + found = true; break; } } + // A missing field would otherwise return 0, which is also the correct offset of the + // first declared field, so the caller has nothing to test for. + if (!found) { + gbString t = type_to_string(type); + error(ce->args[1], "'%s' is not a field of type %s", field_name.cstring(), t); + gb_string_free(t); + return false; + } i64 value = 0; switch (id) { From 0ed628b22f1a7920dc004117ad803680e3c70cbd Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 17:46:31 -0700 Subject: [PATCH 24/93] fix byte swap on untyped intlike types --- src/check_builtin.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..d52e2a896 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5770,6 +5770,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(x.expr, "Invalid type passed to '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); } + // An untyped constant is integer-like, so it reaches the size check below, and + // `type_size_of` asserts on a type that has no size. + // no-op on typed types + convert_to_typed(c, &x, default_type(x.type)); + if (x.mode == Addressing_Invalid) { + return false; + } + i64 sz = type_size_of(x.type); if (sz < 2) { gbString xts = type_to_string(x.type); From 882be52d6640c414cbeec94fa86da857a711dba5 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 18:08:32 -0700 Subject: [PATCH 25/93] bound constant arguments before range-checking them --- src/check_builtin.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..9ccecb394 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1691,6 +1691,11 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &y, t_int); + if (y.mode == Addressing_Invalid) { + return false; + } n = exact_value_to_u64(y.value); } @@ -1936,6 +1941,11 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan error(n.expr, "'%.*s' expected a constant integer divisible by the count of the #simd vector", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64`; here it also sets the return arity + convert_to_typed(c, &n, t_int); + if (n.mode == Addressing_Invalid) { + return false; + } i64 divisor = exact_value_to_i64(n.value); if (divisor < 1 || divisor > max_count || (max_count % divisor != 0)) { error(n.expr, "'%.*s' expected a constant integer divisible by the count of the #simd vector , got %lld, which must have been divisible by %lld", LIT(builtin_name), cast(long long)divisor, cast(long long)max_count); @@ -1972,6 +1982,11 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan gb_string_free(xs); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, x+i, t_int); + if (x[i].mode == Addressing_Invalid) { + return false; + } i64 val = exact_value_to_i64(x[i].value); if (val < 0 || val > 3) { error(x[i].expr, "'%.*s' expected a constant integer in the range 0..<4, got %lld", LIT(builtin_name), cast(long long)val); @@ -6016,6 +6031,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &len, t_int); + if (len.mode == Addressing_Invalid) { + return false; + } + if (len.mode == Addressing_Constant) { i64 n = exact_value_to_i64(len.value); if (n < 0) { @@ -6058,6 +6079,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &len, t_int); + if (len.mode == Addressing_Invalid) { + return false; + } + if (len.mode == Addressing_Constant) { i64 n = exact_value_to_i64(len.value); if (n < 0) { @@ -6586,6 +6613,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(z.expr, "Expected a constant integer for the scale in '%.*s'", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &z, t_int); + if (z.mode == Addressing_Invalid) { + return false; + } i64 n = exact_value_to_i64(z.value); if (n <= 0) { error(z.expr, "Scale parameter in '%.*s' must be positive, got %lld", LIT(builtin_name), n); @@ -6713,6 +6745,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As error(y.expr, "Second argument to '%.*s' representing the locality must be an integer in the range 0..=3", LIT(builtin_name)); return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &y, t_int); + if (y.mode == Addressing_Invalid) { + return false; + } i64 locality = exact_value_to_i64(y.value); if (!(0 <= locality && locality <= 3)) { error(y.expr, "Second argument to '%.*s' representing the locality must be an integer in the range 0..=3", LIT(builtin_name)); @@ -7754,6 +7791,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of procedure parameter value, got %lld", cast(long long)index); @@ -7813,6 +7855,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of procedure parameter value, got %lld", cast(long long)index); @@ -7907,6 +7954,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + // convert constant from BigInt to a type before `exact_value_to_i64` + convert_to_typed(c, &op, t_int); + if (op.mode == Addressing_Invalid) { + return false; + } i64 index = exact_value_to_i64(op.value); if (index < 0) { error(op.expr, "Expected a non-negative integer for the index of record parameter value, got %lld", cast(long long)index); From fef29f0f42b40959f5b062556d08633c0bcbd468 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 18:59:37 -0700 Subject: [PATCH 26/93] reject unspec poly --- src/types.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types.cpp b/src/types.cpp index 0e41b1b9b..1de7e2a07 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2824,6 +2824,10 @@ gb_internal bool is_type_comparable(Type *t) { if (t->Struct.soa_kind != StructSoa_None) { return false; } + // an unspecialized polymorphic record has no values to compare + if (is_type_polymorphic_record_unspecialized(t)) { + return false; + } if (t->Struct.is_raw_union) { return is_type_simple_compare(t); } From e31bf47913276ffb5f0d839dc1976d58f8cfbcaa Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 19:44:08 -0700 Subject: [PATCH 27/93] correct error guard --- src/check_builtin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..05b5d589e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5116,7 +5116,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Operand o = {}; check_expr(c, &o, ce->args[0]); - if (!is_type_integer(o.type) && (o.mode != Addressing_Constant)) { + if (!is_type_integer(o.type) || o.mode != Addressing_Constant) { error(ce->args[0], "Expected a constant integer for '%.*s'", LIT(builtin_name)); return false; } @@ -5137,7 +5137,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Operand o = {}; check_expr(c, &o, ce->args[0]); - if (!is_type_integer_or_float(o.type) && (o.mode != Addressing_Constant)) { + if (!is_type_integer_or_float(o.type) || o.mode != Addressing_Constant) { error(ce->args[0], "Expected a constant number for '%.*s'", LIT(builtin_name)); return false; } From 2a50eaa84d49f2f90269b1936c7d3848db41f6bd Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 19:52:31 -0700 Subject: [PATCH 28/93] improve err msgs --- src/check_builtin.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..75f86c4a2 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5815,7 +5815,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(ct->kind == Type_Basic); if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5866,7 +5866,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(ct->kind == Type_Basic); if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5903,7 +5903,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(elem->kind == Type_Basic); if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected a float type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } @@ -5952,7 +5952,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As GB_ASSERT(elem->kind == Type_Basic); if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { gbString xts = type_to_string(x.type); - error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + error(x.expr, "Expected a float type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; } From 613067a677eb09a9408552bb5371be75d57d6944 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 20:13:47 -0700 Subject: [PATCH 29/93] make endiant types partition --- src/types.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index 0e41b1b9b..66f1ca5fd 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2091,10 +2091,11 @@ gb_internal bool is_type_endian_big(Type *t) { return build_context.endian_kind == TargetEndian_Big; } else if (t->kind == Type_BitSet) { return is_type_endian_big(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_big(&basic_types[Basic_uintptr]); } - return build_context.endian_kind == TargetEndian_Big; + // a type with no endianness is neither little nor big + return false; } gb_internal bool is_type_endian_little(Type *t) { t = core_type(t); @@ -2108,10 +2109,11 @@ gb_internal bool is_type_endian_little(Type *t) { return build_context.endian_kind == TargetEndian_Little; } else if (t->kind == Type_BitSet) { return is_type_endian_little(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_little(&basic_types[Basic_uintptr]); } - return build_context.endian_kind == TargetEndian_Little; + // a type with no endianness is neither little nor big + return false; } gb_internal bool is_type_endian_platform(Type *t) { @@ -2121,7 +2123,7 @@ gb_internal bool is_type_endian_platform(Type *t) { return (t->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) == 0; } else if (t->kind == Type_BitSet) { return is_type_endian_platform(bit_set_to_int(t)); - } else if (t->kind == Type_Pointer) { + } else if (t->kind == Type_Pointer || t->kind == Type_MultiPointer) { return is_type_endian_platform(&basic_types[Basic_uintptr]); } return false; @@ -2179,6 +2181,10 @@ gb_internal bool is_type_dereferenceable(Type *t) { gb_internal bool is_type_different_to_arch_endianness(Type *t) { + // a type with no endianness never needs swapping + if (!is_type_endian_specific(t)) { + return false; + } switch (build_context.endian_kind) { case TargetEndian_Little: return !is_type_endian_little(t); From a594c9960cb089cf034ad7c009c4f59730e2a0ef Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 20:55:12 -0700 Subject: [PATCH 30/93] reject a lane count that exceeds the bit_set limit --- src/check_builtin.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..acf0fbeac 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1441,6 +1441,13 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } i64 num_elems = get_array_type_count(x.type); + // the range is taken from the lane count; it has to meet the same limit a written bit_set does + if (num_elems > 128) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' would produce a bit_set of %lld bits, exceeding the maximum of 128, got '%s'", LIT(builtin_name), cast(long long)num_elems, xs); + gb_string_free(xs); + return false; + } Type *result_type = alloc_type_bit_set(); result_type->BitSet.elem = t_int; From 6b6bf9a8eed7f0656ffa0d698c7cb6a0e4216f6a Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 21:07:53 -0700 Subject: [PATCH 31/93] clarify shared fields behavior --- base/intrinsics/intrinsics.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index d91bc2e79..c4ffd02dc 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -251,6 +251,8 @@ type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_uni type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) --- type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) --- +// Directional: true when U contains every field of V, matched on name and type. +// Swapping the arguments can change the answer, and an empty V will return true. type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- From 973d90d7b1c802ead677da85a262f080444baa41 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 21:31:36 -0700 Subject: [PATCH 32/93] enforce matrix size limits on mul; overflow dims --- src/check_expr.cpp | 16 +++++++++++++++- src/check_type.cpp | 17 ++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2ca6e6df4..888c93e3d 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4268,8 +4268,22 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand x->type = y->type; } } else { + // the result takes its rows from one operand and its columns from the other, + // so it can be larger than either. Each dimension is at least + // MATRIX_ELEMENT_COUNT_MIN, so testing them first keeps the product in range. + i64 row_count = xt->Matrix.row_count; + i64 column_count = yt->Matrix.column_count; + if (row_count > MATRIX_ELEMENT_COUNT_MAX || + column_count > MATRIX_ELEMENT_COUNT_MAX || + row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { + error(x->expr, "Matrix multiplication result exceeds the maximum matrix element count, got %lld, expected a maximum of %d", cast(long long)(row_count*column_count), MATRIX_ELEMENT_COUNT_MAX); + x->mode = Addressing_Invalid; + x->type = t_invalid; + return; + } + 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); + x->type = alloc_type_matrix(xt->Matrix.elem, row_count, column_count, nullptr, nullptr, is_row_major); } goto matrix_success; } else if (yt->kind == Type_Array) { diff --git a/src/check_type.cpp b/src/check_type.cpp index b56acc276..06419f5fc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3131,9 +3131,20 @@ gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) } } - if ((generic_row == nullptr && generic_column == nullptr) && row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { - i64 element_count = row_count*column_count; - error(node, "Matrix types are limited to a maximum of %d elements, got %lld", MATRIX_ELEMENT_COUNT_MAX, cast(long long)element_count); + if (generic_row == nullptr && generic_column == nullptr) { + // row_count*column_count can overflow and wrap back under the limit, so test the + // dimensions first; each is at least MATRIX_ELEMENT_COUNT_MIN. Either one exceeding + // the maximum means the product does too + if (row_count > MATRIX_ELEMENT_COUNT_MAX || column_count > MATRIX_ELEMENT_COUNT_MAX || + row_count*column_count > MATRIX_ELEMENT_COUNT_MAX) { + // the element count is only printable when the multiply cannot overflow, which is + // exactly the case the dimension test above catches + if (row_count != 0 && column_count > I64_MAX/row_count) { + error(node, "Matrix types are limited to a maximum of %d elements, got %lld by %lld", MATRIX_ELEMENT_COUNT_MAX, cast(long long)row_count, cast(long long)column_count); + } else { + error(node, "Matrix types are limited to a maximum of %d elements, got %lld by %lld (%lld elements)", MATRIX_ELEMENT_COUNT_MAX, cast(long long)row_count, cast(long long)column_count, cast(long long)(row_count*column_count)); + } + } } From 0dbd55168f7d823fc681736e29028f56b1c85607 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 22:25:44 -0700 Subject: [PATCH 33/93] hoist if stmt --- src/llvm_backend_stmt.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 47cf5a10d..8b4b021fb 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -496,11 +496,12 @@ gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_ case Type_EnumeratedArray: { if (val_type != nullptr) { val = lb_emit_load(p, lb_emit_array_ep(p, expr, idx)); - // NOTE(bill): Override the idx value for the enumeration - Type *index_type = expr_type->EnumeratedArray.index; - if (compare_exact_values(Token_NotEq, *expr_type->EnumeratedArray.min_value, exact_value_u64(0))) { - idx = lb_emit_arith(p, Token_Add, idx, lb_const_value(m, index_type, *expr_type->EnumeratedArray.min_value), index_type); - } + } + // NOTE(bill): Override the idx value for the enumeration + // this does not depend on the value operand, which may be blank + Type *index_type = expr_type->EnumeratedArray.index; + if (compare_exact_values(Token_NotEq, *expr_type->EnumeratedArray.min_value, exact_value_u64(0))) { + idx = lb_emit_arith(p, Token_Add, idx, lb_const_value(m, index_type, *expr_type->EnumeratedArray.min_value), index_type); } break; } From 3f7d961bf6b0e8001d4225c144cdc0574a8154f8 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 22:39:12 -0700 Subject: [PATCH 34/93] check enumerated array length --- src/check_type.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index b56acc276..0e034883e 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3477,6 +3477,22 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t Type *bt = base_type(index); GB_ASSERT(bt->kind == Type_Enum); + // the length is `max - min + 1`, computed exactly and then narrowed to an i64. a + // wide enough enumeration wraps & nothing tests downstream; reject here + if (bt->Enum.fields.count > 0 && + bt->Enum.min_value != nullptr && bt->Enum.max_value != nullptr) { + ExactValue span = exact_value_sub(*bt->Enum.max_value, *bt->Enum.min_value); + ExactValue len = exact_value_add(span, exact_value_i64(1)); + if (len.kind == ExactValue_Integer && len.value_integer.used > 1) { + gbAllocator a = heap_allocator(); + String str = big_int_to_string(a, &len.value_integer); + error(e, "Enumerated array length too large, %.*s", LIT(str)); + gb_free(a, str.text); + *type = t_invalid; + return; + } + } + Type *t = alloc_type_enumerated_array(elem, index, bt->Enum.min_value, bt->Enum.max_value, bt->Enum.fields.count, Token_Invalid); bool is_sparse = false; From 419050f82a44e082d6ed3613138c9763745292d8 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 22:52:33 -0700 Subject: [PATCH 35/93] fix four more guards using && where they need || --- src/check_builtin.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 05b5d589e..98c6a297d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2667,7 +2667,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o error(call, "'#panic' expects 1 argument, got %td", ce->args.count); return false; } - if (!is_type_string(operand->type) && operand->mode != Addressing_Constant) { + 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); @@ -4928,7 +4928,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - if (!is_type_array(x.type) && !is_type_array(y.type)) { + if (!is_type_array(x.type) || !is_type_array(y.type)) { gbString s1 = type_to_string(x.type); gbString s2 = type_to_string(y.type); error(call, "'%.*s' expects only arrays, got %s and %s", LIT(builtin_name), s1, s2); @@ -5058,7 +5058,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_is_package_imported: { bool value = false; - if (!is_type_string(operand->type) && (operand->mode != Addressing_Constant)) { + if (!is_type_string(operand->type) || operand->mode != Addressing_Constant) { error(ce->args[0], "Expected a constant string for '%.*s'", LIT(builtin_name)); } else if (operand->value.kind == ExactValue_String) { String pkg_name = operand->value.value_string; @@ -6709,7 +6709,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (x.mode == Addressing_Invalid) { return false; } - if (y.mode != Addressing_Constant && is_type_integer(y.type)) { + if (y.mode != Addressing_Constant || !is_type_integer(y.type)) { error(y.expr, "Second argument to '%.*s' representing the locality must be an integer in the range 0..=3", LIT(builtin_name)); return false; } From f053a4035992f9c9579c9bdf454e0061b43ef820 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 23:07:07 -0700 Subject: [PATCH 36/93] check_matrix_type_hint: require the element type and layout to match --- src/check_expr.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2ca6e6df4..78c1c1144 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4215,8 +4215,9 @@ gb_internal Type *check_matrix_type_hint(Type *matrix, Type *type_hint) { } else if (xt->kind == Type_Matrix && th->kind == Type_Matrix) { if (!are_types_identical(xt->Matrix.elem, th->Matrix.elem)) { // ignore - } if (xt->Matrix.row_count == th->Matrix.row_count && - xt->Matrix.column_count == th->Matrix.column_count) { + } else if (xt->Matrix.row_count == th->Matrix.row_count && + xt->Matrix.column_count == th->Matrix.column_count && + xt->Matrix.is_row_major == th->Matrix.is_row_major) { return type_hint; } } else if (xt->kind == Type_Matrix && th->kind == Type_Array) { From 586f87c0e11493d9edd9d3eec8186eb79fce9bff Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 23:15:29 -0700 Subject: [PATCH 37/93] apply max field align --- src/types.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index 0e41b1b9b..6d6878f6f 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4496,8 +4496,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { if (t->Struct.custom_min_field_align > 0) { max = gb_max(max, t->Struct.custom_min_field_align); } - if (t->Struct.custom_max_field_align != 0 && - t->Struct.custom_max_field_align > t->Struct.custom_min_field_align) { + if (t->Struct.custom_max_field_align != 0) { max = gb_min(max, t->Struct.custom_max_field_align); } return max; @@ -4567,7 +4566,7 @@ 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_internal(t, &path), min_field_align); - if (max_field_align > min_field_align) { + if (max_field_align != 0) { align = gb_min(align, max_field_align); } i64 size = gb_max(type_size_of_internal(t, &path), 0); From c61cf7d684f5e0a1a985fc91344091404510b994 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 11 Aug 2026 23:37:41 -0700 Subject: [PATCH 38/93] dont clear value on ok --- src/check_builtin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4910dafb0..eb93b25fe 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1968,7 +1968,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan for (unsigned i = 0; i < 4; i++) { if (!is_type_integer(x[i].type) || x[i].mode != Addressing_Constant) { gbString xs = type_to_string(x[i].type); - error(x[i].expr, "'%.*s' expected a constant integer", LIT(builtin_name), xs); + error(x[i].expr, "'%.*s' expected a constant integer, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } @@ -2872,8 +2872,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!ok) { operand->type = t_invalid; operand->mode = Addressing_Value; + operand->value = {}; } - operand->value = {}; operand->expr = call; return ok; } From 639606ae63d0f56251b0a748042cbe93aa3138f3 Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:06:21 +0300 Subject: [PATCH 39/93] fixes non_zero_append_elem_fixed_capacity_string --- base/runtime/core_builtin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 4913b53d9..5bcdad724 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -921,7 +921,7 @@ non_zero_append_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, arg: $A // Note: Prefer using the procedure group `non_zero_append`. @builtin non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (num_appended: int) { - return append_fixed_capacity_elem(array, transmute([]byte)arg) + return append_fixed_capacity_elems(array, ..transmute([]E)arg) } From e5bf7ba6e31f41c3389c097999f571e2c3e6e21e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 12 Aug 2026 13:59:51 +0200 Subject: [PATCH 40/93] Skip some tests on riscv --- tests/core/net/test_core_net.odin | 5 +++++ tests/core/sync/test_core_sync.odin | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index b0cf57cad..17eb3bdd0 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -608,6 +608,11 @@ test_udp_echo :: proc(t: ^testing.T) { test_dns_resolve :: proc(t: ^testing.T) { // NOTE: This test depends on external factors, so if it fails, an IP // address may have changed or become unavailable. + // It regularly fails on the RISC-V CI runners, so we skip it there + when ODIN_ARCH == .riscv64 { + log.infof("Skipped on riscv64") + return + } // The net API returns only one address per protocol version, and DNS // records can store many, so we'll have to check all possibilities. diff --git a/tests/core/sync/test_core_sync.odin b/tests/core/sync/test_core_sync.odin index d6a7a9517..1cc52ca3e 100644 --- a/tests/core/sync/test_core_sync.odin +++ b/tests/core/sync/test_core_sync.odin @@ -538,6 +538,14 @@ test_ticket_mutex :: proc(t: ^testing.T) { @test test_benaphore :: proc(t: ^testing.T) { + // NOTE(Jeroen): Regularly times out on the simulated RISC-V CI runners, + // so we're skipping the test there. + + when ODIN_ARCH == .riscv64 { + log.infof("Skipped on riscv64") + return + } + testing.set_fail_timeout(t, FAIL_TIME) Data :: struct { From 44b2f201f5d08102a12ea2147446611bc5f818a7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 12 Aug 2026 14:09:07 +0200 Subject: [PATCH 41/93] Remove log --- tests/core/sync/test_core_sync.odin | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/core/sync/test_core_sync.odin b/tests/core/sync/test_core_sync.odin index 1cc52ca3e..28c144935 100644 --- a/tests/core/sync/test_core_sync.odin +++ b/tests/core/sync/test_core_sync.odin @@ -8,7 +8,6 @@ package test_core_sync import "base:intrinsics" -// import "core:log" import "core:sync" import "core:testing" import "core:thread" @@ -538,11 +537,9 @@ test_ticket_mutex :: proc(t: ^testing.T) { @test test_benaphore :: proc(t: ^testing.T) { - // NOTE(Jeroen): Regularly times out on the simulated RISC-V CI runners, - // so we're skipping the test there. - when ODIN_ARCH == .riscv64 { - log.infof("Skipped on riscv64") + // NOTE(Jeroen): Regularly times out on the simulated RISC-V CI runners, + // so we're skipping the test there. return } From 6dda0198f0d96cc95e954006a800eb4e46d3258a Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:02:49 +0300 Subject: [PATCH 42/93] fixes f32 to 64-bit int/uint conversion --- src/llvm_backend_expr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 2ef7f9b29..63c45711f 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2546,7 +2546,9 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lbValue res_i128 = lb_emit_runtime_call(p, call, args); return lb_emit_conv(p, res_i128, t); } - i64 sz = type_size_of(src); + // the intermediate int must be at least as wide as the dest, + // otherwise e.g. f32 -> u64 truncates through a 32-bit fptoui + i64 sz = gb_max(type_size_of(src), type_size_of(dst)); lbValue res = {}; res.type = t; From 6a81351de39c80d38cea769f197ee2a936a28274 Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:25:46 +0300 Subject: [PATCH 43/93] fixes float to 128-bit int/uint conversion --- base/runtime/internal_i128.odin | 143 ++++++++++++------ src/check_expr.cpp | 2 +- src/llvm_backend_expr.cpp | 4 +- tests/internal/test_128_float_conversion.odin | 48 ++++++ 4 files changed, 144 insertions(+), 53 deletions(-) create mode 100644 tests/internal/test_128_float_conversion.odin diff --git a/base/runtime/internal_i128.odin b/base/runtime/internal_i128.odin index f4a0f90ed..df5066843 100644 --- a/base/runtime/internal_i128.odin +++ b/base/runtime/internal_i128.odin @@ -91,22 +91,107 @@ floattidf_unsigned :: proc "c" (a: u128) -> f64 { } +// f64 -> unsigned integer conversion (truncating toward zero) +// port of clang/compiler-rt's fp_fixuint_impl.inc; +// decompose the f64 into significand and exponent, shift the significand into place, +// saturate out of range values +@(private="file") +fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is_unsigned(U) { + BITS :: 8 * size_of(U) + SIGNIFICAND_BITS :: 52 + EXPONENT_BIAS :: 1023 + + IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS) + SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1) + + // Break a into sign, exponent, significand parts. + a_rep := transmute(u64)a + negative := (a_rep >> 63) != 0 + exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS + significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT + + // If either the value or the exponent is negative, the result is zero. + if negative || exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate. + if exponent >= BITS { + return max(U) + } + + // If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result. + // Otherwise, shift left. + if exponent < SIGNIFICAND_BITS { + return U(significand >> u32(SIGNIFICAND_BITS - exponent)) + } + return U(significand) << u32(exponent - SIGNIFICAND_BITS) +} + +// f64 -> signed integer conversion (truncating toward zero) +// port of clang/compiler-rt's fp_fixint_impl.inc; +// decompose the f64 into significand and exponent, shift the significand into place, +// saturate out of range values +@(private="file") +fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T) && !intrinsics.type_is_unsigned(T) { + BITS :: 8 * size_of(T) + SIGNIFICAND_BITS :: 52 + EXPONENT_BIAS :: 1023 + + IMPLICIT_BIT :: (u64(1) << SIGNIFICAND_BITS) + SIGNIFICAND_MASK :: (IMPLICIT_BIT - 1) + + // Break a into sign, exponent, significand parts. + a_rep := transmute(u64)a + negative := (a_rep >> 63) != 0 + exponent := i32((a_rep >> SIGNIFICAND_BITS) & 0x7ff) - EXPONENT_BIAS + significand := (a_rep & SIGNIFICAND_MASK) | IMPLICIT_BIT + + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0 + } + + // If the value is too large for the integer type, saturate; + // (the only exactly representable value with exponent BITS-1 is min(T)) + // this deviates from clang/compiler-rt (it instead wraps from BITS-1 to BITS, + // that is, f64(1 << 127) would wrap to min(i128) (for T=i128)), + // while this saturates consistently + if exponent >= BITS - 1 { + return min(T) if negative else max(T) + } + + // If 0 <= exponent < SIGNIFICAND_BITS, right shift to get the result. + // Otherwise, shift left. After saturation the magnitude is below + // 1 << (BITS-1), so it fits in T and negation can't overflow + r: T + if exponent < SIGNIFICAND_BITS { + r = T(significand >> u32(SIGNIFICAND_BITS - exponent)) + } else { + r = T(significand) << u32(exponent - SIGNIFICAND_BITS) + } + return -r if negative else r +} @(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) +fixunsdfti :: proc "c" (a: f64) -> u128 { + return fixuint(u128, a) } @(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) +fixunsdfdi :: proc "c" (a: f64) -> u64 { + return fixuint(u64, a) } +@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfti :: proc "c" (a: f64) -> i128 { + return fixint(i128, a) +} +@(link_name="__fixdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) +fixdfdi :: proc "c" (a: f64) -> i64 { + return fixint(i64, a) +} @(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) @@ -173,45 +258,3 @@ divti3 :: proc "c" (a, b: i128) -> i128 { return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative } - -@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -fixdfti :: proc "c" (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/src/check_expr.cpp b/src/check_expr.cpp index 81936119b..ec276884a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3982,7 +3982,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb add_package_dependency(c, "runtime", "floattidf", REQUIRE); } else if (is_type_integer_128bit(dst) && is_type_float(src)) { add_package_dependency(c, "runtime", "fixunsdfti", REQUIRE); - add_package_dependency(c, "runtime", "fixunsdfdi", REQUIRE); + add_package_dependency(c, "runtime", "fixdfti", REQUIRE); } else if (src == t_f16 && is_type_float(dst)) { add_package_dependency(c, "runtime", "gnu_h2f_ieee", REQUIRE); add_package_dependency(c, "runtime", "extendhfsf2", REQUIRE); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 63c45711f..128b688ce 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2538,8 +2538,8 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { TEMPORARY_ALLOCATOR_GUARD(); auto args = array_make(temporary_allocator(), 1); - args[0] = value; - char const *call = "fixunsdfdi"; + args[0] = lb_emit_conv(p, value, t_f64); + char const *call = "fixdfti"; if (is_type_unsigned(dst)) { call = "fixunsdfti"; } diff --git a/tests/internal/test_128_float_conversion.odin b/tests/internal/test_128_float_conversion.odin new file mode 100644 index 000000000..6d82dd817 --- /dev/null +++ b/tests/internal/test_128_float_conversion.odin @@ -0,0 +1,48 @@ +package test_internal + +import "core:testing" + +// #force_no_inline so conversions are not folded; +// they must go through the runtime __fixunsdfti/__fixdfti +@(private="file") f64_to_u128 :: #force_no_inline proc(f: f64) -> u128 { return u128(f) } +@(private="file") f64_to_i128 :: #force_no_inline proc(f: f64) -> i128 { return i128(f) } +@(private="file") f32_to_u128 :: #force_no_inline proc(f: f32) -> u128 { return u128(f) } +@(private="file") f32_to_i128 :: #force_no_inline proc(f: f32) -> i128 { return i128(f) } + +@test +test_f64_to_u128 :: proc(t: ^testing.T) { + testing.expect_value(t, f64_to_u128(2.75), 2) // trunc toward 0 + testing.expect_value(t, f64_to_u128(0.75), 0) // < 1 + testing.expect_value(t, f64_to_u128(-3.5), 0) // negative to 0 + testing.expect_value(t, f64_to_u128(f64(1 << 51)), u128(1) << 51) // significand shifts right + testing.expect_value(t, f64_to_u128(f64(1 << 52)), u128(1) << 52) // no shift + testing.expect_value(t, f64_to_u128(f64(1 << 53)), u128(1) << 53) // significand shifts left + testing.expect_value(t, f64_to_u128(1e19), 10_000_000_000_000_000_000) + testing.expect_value(t, f64_to_u128(f64(1 << 80)), u128(1) << 80) // > max(u64) + testing.expect_value(t, f64_to_u128(2 * f64(1 << 127)), max(u128)) // out of range saturates, implementation specific +} + +@test +test_f64_to_i128 :: proc(t: ^testing.T) { + testing.expect_value(t, f64_to_i128(-2.75), -2) // trunc toward 0 + testing.expect_value(t, f64_to_i128(-0.75), 0) // |f| < 1 + testing.expect_value(t, f64_to_i128(1e19), 10_000_000_000_000_000_000) // > max(i64) + testing.expect_value(t, f64_to_i128(f64(-(1 << 80))), -(i128(1) << 80)) + testing.expect_value(t, f64_to_i128(f64(1 << 126)), i128(1) << 126) + testing.expect_value(t, f64_to_i128(f64(min(i128))), min(i128)) // exact + // out of range saturates, implementation specific + testing.expect_value(t, f64_to_i128(f64(1 << 127)), max(i128)) + testing.expect_value(t, f64_to_i128(2 * f64(1 << 127)), max(i128)) +} + +@test +test_f32_to_u128 :: proc(t: ^testing.T) { + testing.expect_value(t, f32_to_u128(2.75), 2) // trunc toward 0 + testing.expect_value(t, f32_to_u128(f32(1 << 80)), u128(1) << 80) // > max(u64) +} + +@test +test_f32_to_i128 :: proc(t: ^testing.T) { + testing.expect_value(t, f32_to_i128(-2.75), -2) // trunc toward 0 + testing.expect_value(t, f32_to_i128(f32(-(1 << 80))), -(i128(1) << 80)) +} From 8329f59edc04dfc6c14a2375873c630da99f97f5 Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:50:28 +0300 Subject: [PATCH 44/93] comma in where to satisfy the vet cerberus :) --- base/runtime/internal_i128.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/runtime/internal_i128.odin b/base/runtime/internal_i128.odin index df5066843..39f153849 100644 --- a/base/runtime/internal_i128.odin +++ b/base/runtime/internal_i128.odin @@ -133,7 +133,7 @@ fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is // decompose the f64 into significand and exponent, shift the significand into place, // saturate out of range values @(private="file") -fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T) && !intrinsics.type_is_unsigned(T) { +fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T), !intrinsics.type_is_unsigned(T) { BITS :: 8 * size_of(T) SIGNIFICAND_BITS :: 52 EXPONENT_BIAS :: 1023 From ea0b476c1f7c78eb7d8c9d8526f976a5e3d99dc7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 12 Aug 2026 19:19:51 +0200 Subject: [PATCH 45/93] Add compiler-rt license info --- base/runtime/LICENSE-compiler-rt.txt | 313 +++++++++++++++++++++++++++ base/runtime/internal_i128.odin | 11 +- 2 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 base/runtime/LICENSE-compiler-rt.txt diff --git a/base/runtime/LICENSE-compiler-rt.txt b/base/runtime/LICENSE-compiler-rt.txt new file mode 100644 index 000000000..39eadfc4f --- /dev/null +++ b/base/runtime/LICENSE-compiler-rt.txt @@ -0,0 +1,313 @@ +The below LLVM license applies to `runtime.fixuint` and `runtime.fixint`. + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The compiler_rt library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/base/runtime/internal_i128.odin b/base/runtime/internal_i128.odin index 39f153849..8c70002c9 100644 --- a/base/runtime/internal_i128.odin +++ b/base/runtime/internal_i128.odin @@ -92,9 +92,11 @@ floattidf_unsigned :: proc "c" (a: u128) -> f64 { // f64 -> unsigned integer conversion (truncating toward zero) -// port of clang/compiler-rt's fp_fixuint_impl.inc; // decompose the f64 into significand and exponent, shift the significand into place, // saturate out of range values +// +// Uses parts of compiler-rt's [fp_fixuint_impl.inc](https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/fp_fixuint_impl.inc), licensed under the [Apache License v2.0 with LLVM Exceptions](https://github.com/llvm/llvm-project/blob/main/compiler-rt/LICENSE.TXT). +// For a copy of the license, see `LICENSE-compiler-rt.txt`. @(private="file") fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is_unsigned(U) { BITS :: 8 * size_of(U) @@ -129,9 +131,11 @@ fixuint :: proc "contextless" ($U: typeid, a: f64) -> U where intrinsics.type_is } // f64 -> signed integer conversion (truncating toward zero) -// port of clang/compiler-rt's fp_fixint_impl.inc; // decompose the f64 into significand and exponent, shift the significand into place, // saturate out of range values +// +// Uses parts of compiler-rt's [fp_fixuint_impl.inc](https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/fp_fixuint_impl.inc), licensed under the [Apache License v2.0 with LLVM Exceptions](https://github.com/llvm/llvm-project/blob/main/compiler-rt/LICENSE.TXT). +// For a copy of the license, see `LICENSE-compiler-rt.txt`. @(private="file") fixint :: proc "contextless" ($T: typeid, a: f64) -> T where intrinsics.type_is_integer(T), !intrinsics.type_is_unsigned(T) { BITS :: 8 * size_of(T) @@ -256,5 +260,4 @@ divti3 :: proc "c" (a, b: i128) -> i128 { u_s_a := u128(s_a) return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative -} - +} \ No newline at end of file From a3573c83cd6cc6ffb78069c4e724675ba5b34375 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Wed, 12 Aug 2026 18:52:33 -0700 Subject: [PATCH 46/93] fix long -> int64_t for win64 --- tests/issues/test_issue_sysv_abi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/issues/test_issue_sysv_abi.c b/tests/issues/test_issue_sysv_abi.c index 0c7de35a1..933775f94 100644 --- a/tests/issues/test_issue_sysv_abi.c +++ b/tests/issues/test_issue_sysv_abi.c @@ -4,8 +4,11 @@ // where the struct before it went. If the aggregate consumes the wrong number or // the wrong file of registers, the following argument is read from the wrong // place deterministically rather than by scratch-register coincidence. +// -typedef struct { long a; float b; } Pad_Int_Float; +#include + +typedef struct { int64_t a; float b; } Pad_Int_Float; typedef struct { float a; double b; } Pad_Float_Double; typedef struct { float a, b; } No_Pad; typedef struct { struct { float x; } a; double b; } Nested; From 939802e713db92090916f0a6b7e7c0d3fc7d714b Mon Sep 17 00:00:00 2001 From: kalsprite Date: Wed, 12 Aug 2026 19:23:41 -0700 Subject: [PATCH 47/93] fix bitfield intrinsic --- src/types.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types.cpp b/src/types.cpp index 13d335348..e9ac4260c 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1446,11 +1446,13 @@ gb_internal bool is_type_ordered(Type *t) { return false; } gb_internal bool is_type_ordered_numeric(Type *t) { - t = core_type(t); + t = base_type(t); if (t == nullptr) { return false; } switch (t->kind) { case Type_Basic: return (t->Basic.flags & BasicFlag_OrderedNumeric) != 0; + case Type_Enum: + return is_type_ordered_numeric(t->Enum.base_type); } return false; } From 02adbc11e33673c6ca531d14db79b482d608d3c0 Mon Sep 17 00:00:00 2001 From: FourteenBrush <74827262+FourteenBrush@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:51:42 +0200 Subject: [PATCH 48/93] Fix oob for zero arg `#load_directory` --- src/check_builtin.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4a59a59b7..84406c423 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2244,7 +2244,10 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c String name = bd->name.string; GB_ASSERT(name == "load_directory"); - if (ce->args.count != 1) { + if (ce->args.count == 0) { + error(ce->close, "'#%.*s' expects 1 argument, got 0", LIT(name)); + return LoadDirective_Error; + } else if (ce->args.count != 1) { error(ce->args[0], "'#%.*s' expects 1 argument, got %td", LIT(name), ce->args.count); return LoadDirective_Error; } From ac7110e098a38c003c0c3d780069a5ff76f3cc73 Mon Sep 17 00:00:00 2001 From: diego Date: Fri, 14 Aug 2026 13:21:03 +0200 Subject: [PATCH 49/93] Remove unused variable in fixed-point write procedure --- core/math/fixed/fixed.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/math/fixed/fixed.odin b/core/math/fixed/fixed.odin index 6b9ef364b..65d7af772 100644 --- a/core/math/fixed/fixed.odin +++ b/core/math/fixed/fixed.odin @@ -148,7 +148,7 @@ write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { } } - n := copy(dst, buf[:i]) + copy(dst, buf[:i]) return string(dst[:i]) } From 64a5887f831833be334833c146351f15c27275fa Mon Sep 17 00:00:00 2001 From: Alexander Zhura Date: Fri, 14 Aug 2026 22:01:59 +0300 Subject: [PATCH 50/93] Impl simd arm neon logical --- core/simd/arm/neon.odin | 962 ++++++++++++++++++++++++++++++++++++++- core/simd/arm/pmull.odin | 34 ++ 2 files changed, 980 insertions(+), 16 deletions(-) diff --git a/core/simd/arm/neon.odin b/core/simd/arm/neon.odin index 33713d128..17f0f2fa9 100644 --- a/core/simd/arm/neon.odin +++ b/core/simd/arm/neon.odin @@ -211,14 +211,6 @@ vcnt_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { return transmute(uint8x8_t)vcnt_s8(transmute(int8x8_t)a) } -// Population count per byte. -// -// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_p8) -@(require_results, enable_target_feature = "neon") -vcnt_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { - return transmute(poly8x8_t)vcnt_s8(transmute(int8x8_t)a) -} - // Population count per byte. // // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_s8) @@ -235,14 +227,6 @@ vcntq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { return transmute(uint8x16_t)vcntq_s8(transmute(int8x16_t)a) } -// Population count per byte. -// -// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_p8) -@(require_results, enable_target_feature = "neon") -vcntq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { - return transmute(poly8x16_t)vcntq_s8(transmute(int8x16_t)a) -} - // Vector bitwise bit clear. // // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s8) @@ -988,6 +972,864 @@ vtbx4_u8 :: #force_inline proc "c" (v: uint8x8_t, t: uint8x8x4_t, idx: uint8x8_t } } +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s8) +@(require_results, enable_target_feature = "neon") +vdup_n_s8 :: #force_inline proc "c" (value: int8_t) -> int8x8_t { + return int8x8_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s16) +@(require_results, enable_target_feature = "neon") +vdup_n_s16 :: #force_inline proc "c" (value: int16_t) -> int16x4_t { + return int16x4_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s32) +@(require_results, enable_target_feature = "neon") +vdup_n_s32 :: #force_inline proc "c" (value: int32_t) -> int32x2_t { + return int32x2_t(value) +} + +// Duplicate vector element to vector or scalar +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vdup_n_s64) +@(require_results, enable_target_feature = "neon") +vdup_n_s64 :: #force_inline proc "c" (value: int64_t) -> int64x1_t { + return int64x1_t(value) +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s8) +@(require_results, enable_target_feature = "neon") +vget_lane_s8 :: #force_inline proc "c" (v: int8x8_t, $LANE: int32_t) -> int8_t where 0 <= LANE, LANE < 8 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u8) +@(require_results, enable_target_feature = "neon") +vget_lane_u8 :: #force_inline proc "c" (v: uint8x8_t, $LANE: int32_t) -> uint8_t where 0 <= LANE, LANE < 8 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 7, 6, 5, 4, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s16) +@(require_results, enable_target_feature = "neon") +vget_lane_s16 :: #force_inline proc "c" (v: int16x4_t, $LANE: int32_t) -> int16_t where 0 <= LANE, LANE < 4 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u16) +@(require_results, enable_target_feature = "neon") +vget_lane_u16 :: #force_inline proc "c" (v: uint16x4_t, $LANE: int32_t) -> uint16_t where 0 <= LANE, LANE < 4 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 3, 2, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s32) +@(require_results, enable_target_feature = "neon") +vget_lane_s32 :: #force_inline proc "c" (v: int32x2_t, $LANE: int32_t) -> int32_t where 0 <= LANE, LANE < 2 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u32) +@(require_results, enable_target_feature = "neon") +vget_lane_u32 :: #force_inline proc "c" (v: uint32x2_t, $LANE: int32_t) -> uint32_t where 0 <= LANE, LANE < 2 { + when ODIN_ENDIAN == .Little { + return simd.extract(v, LANE) + } else { + v := simd.shuffle(v, v, 1, 0) + return simd.extract(v, LANE) + } +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_s64) +@(require_results, enable_target_feature = "neon") +vget_lane_s64 :: #force_inline proc "c" (v: int64x1_t, $LANE: int32_t) -> int64_t where LANE == 0 { + return simd.extract(v, LANE) +} + +// Move vector element to general-purpose register +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vget_lane_u64) +@(require_results, enable_target_feature = "neon") +vget_lane_u64 :: #force_inline proc "c" (v: uint64x1_t, $LANE: int32_t) -> uint64_t where LANE == 0 { + return simd.extract(v, LANE) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s8) +@(require_results, enable_target_feature = "neon") +vneg_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s8) +@(require_results, enable_target_feature = "neon") +vnegq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s16) +@(require_results, enable_target_feature = "neon") +vneg_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s16) +@(require_results, enable_target_feature = "neon") +vnegq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s32) +@(require_results, enable_target_feature = "neon") +vneg_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return simd.neg(a) +} + +// Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s32) +@(require_results, enable_target_feature = "neon") +vnegq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return simd.neg(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s8) +@(require_results, enable_target_feature = "neon") +vqneg_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return _vqneg_s8(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s16) +@(require_results, enable_target_feature = "neon") +vqneg_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return _vqneg_s16(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s32) +@(require_results, enable_target_feature = "neon") +vqneg_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return _vqneg_s32(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s8) +@(require_results, enable_target_feature = "neon") +vqnegq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return _vqnegq_s8(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s16) +@(require_results, enable_target_feature = "neon") +vqnegq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return _vqnegq_s16(a) +} + +// Signed saturating Negate. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s32) +@(require_results, enable_target_feature = "neon") +vqnegq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return _vqnegq_s32(a) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s8) +@(require_results, enable_target_feature = "neon") +vmvn_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + b := int8x8_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u8) +@(require_results, enable_target_feature = "neon") +vmvn_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { + b := uint8x8_t(max(uint8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s16) +@(require_results, enable_target_feature = "neon") +vmvn_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + b := int16x4_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u16) +@(require_results, enable_target_feature = "neon") +vmvn_u16 :: #force_inline proc "c" (a: uint16x4_t) -> uint16x4_t { + b := uint16x4_t(max(uint16_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_s32) +@(require_results, enable_target_feature = "neon") +vmvn_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + b := int32x2_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_u32) +@(require_results, enable_target_feature = "neon") +vmvn_u32 :: #force_inline proc "c" (a: uint32x2_t) -> uint32x2_t { + b := uint32x2_t(max(uint32_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s8) +@(require_results, enable_target_feature = "neon") +vmvnq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + b := int8x16_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u8) +@(require_results, enable_target_feature = "neon") +vmvnq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { + b := uint8x16_t(max(uint8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s16) +@(require_results, enable_target_feature = "neon") +vmvnq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + b := int16x8_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u16) +@(require_results, enable_target_feature = "neon") +vmvnq_u16 :: #force_inline proc "c" (a: uint16x8_t) -> uint16x8_t { + b := uint16x8_t(max(uint16_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_s32) +@(require_results, enable_target_feature = "neon") +vmvnq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + b := int32x4_t(-1) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_u32) +@(require_results, enable_target_feature = "neon") +vmvnq_u32 :: #force_inline proc "c" (a: uint32x4_t) -> uint32x4_t { + b := uint32x4_t(max(uint32_t)) + return simd.bit_xor(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s8) +@(require_results, enable_target_feature = "neon") +vand_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u8) +@(require_results, enable_target_feature = "neon") +vand_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s16) +@(require_results, enable_target_feature = "neon") +vand_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u16) +@(require_results, enable_target_feature = "neon") +vand_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s32) +@(require_results, enable_target_feature = "neon") +vand_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u32) +@(require_results, enable_target_feature = "neon") +vand_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_s64) +@(require_results, enable_target_feature = "neon") +vand_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vand_u64) +@(require_results, enable_target_feature = "neon") +vand_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s8) +@(require_results, enable_target_feature = "neon") +vandq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u8) +@(require_results, enable_target_feature = "neon") +vandq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s16) +@(require_results, enable_target_feature = "neon") +vandq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u16) +@(require_results, enable_target_feature = "neon") +vandq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s32) +@(require_results, enable_target_feature = "neon") +vandq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u32) +@(require_results, enable_target_feature = "neon") +vandq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_s64) +@(require_results, enable_target_feature = "neon") +vandq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_and(a, b) +} + +// Bitwise And. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vandq_u64) +@(require_results, enable_target_feature = "neon") +vandq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_and(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s8) +@(require_results, enable_target_feature = "neon") +vorr_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u8) +@(require_results, enable_target_feature = "neon") +vorr_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s16) +@(require_results, enable_target_feature = "neon") +vorr_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u16) +@(require_results, enable_target_feature = "neon") +vorr_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s32) +@(require_results, enable_target_feature = "neon") +vorr_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u32) +@(require_results, enable_target_feature = "neon") +vorr_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_s64) +@(require_results, enable_target_feature = "neon") +vorr_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorr_u64) +@(require_results, enable_target_feature = "neon") +vorr_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s8) +@(require_results, enable_target_feature = "neon") +vorrq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u8) +@(require_results, enable_target_feature = "neon") +vorrq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s16) +@(require_results, enable_target_feature = "neon") +vorrq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u16) +@(require_results, enable_target_feature = "neon") +vorrq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s32) +@(require_results, enable_target_feature = "neon") +vorrq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u32) +@(require_results, enable_target_feature = "neon") +vorrq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_s64) +@(require_results, enable_target_feature = "neon") +vorrq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Inclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorrq_u64) +@(require_results, enable_target_feature = "neon") +vorrq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_or(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s8) +@(require_results, enable_target_feature = "neon") +veor_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u8) +@(require_results, enable_target_feature = "neon") +veor_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s16) +@(require_results, enable_target_feature = "neon") +veor_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u16) +@(require_results, enable_target_feature = "neon") +veor_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s32) +@(require_results, enable_target_feature = "neon") +veor_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u32) +@(require_results, enable_target_feature = "neon") +veor_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_s64) +@(require_results, enable_target_feature = "neon") +veor_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veor_u64) +@(require_results, enable_target_feature = "neon") +veor_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s8) +@(require_results, enable_target_feature = "neon") +veorq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u8) +@(require_results, enable_target_feature = "neon") +veorq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s16) +@(require_results, enable_target_feature = "neon") +veorq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u16) +@(require_results, enable_target_feature = "neon") +veorq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s32) +@(require_results, enable_target_feature = "neon") +veorq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u32) +@(require_results, enable_target_feature = "neon") +veorq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_s64) +@(require_results, enable_target_feature = "neon") +veorq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Exclusive Or. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/veorq_u64) +@(require_results, enable_target_feature = "neon") +veorq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + return simd.bit_xor(a, b) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s8) +@(require_results, enable_target_feature = "neon") +vorn_s8 :: #force_inline proc "c" (a, b: int8x8_t) -> int8x8_t { + c := int8x8_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u8) +@(require_results, enable_target_feature = "neon") +vorn_u8 :: #force_inline proc "c" (a, b: uint8x8_t) -> uint8x8_t { + c := uint8x8_t(max(uint8_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s16) +@(require_results, enable_target_feature = "neon") +vorn_s16 :: #force_inline proc "c" (a, b: int16x4_t) -> int16x4_t { + c := int16x4_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u16) +@(require_results, enable_target_feature = "neon") +vorn_u16 :: #force_inline proc "c" (a, b: uint16x4_t) -> uint16x4_t { + c := uint16x4_t(max(uint16_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s32) +@(require_results, enable_target_feature = "neon") +vorn_s32 :: #force_inline proc "c" (a, b: int32x2_t) -> int32x2_t { + c := int32x2_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u32) +@(require_results, enable_target_feature = "neon") +vorn_u32 :: #force_inline proc "c" (a, b: uint32x2_t) -> uint32x2_t { + c := uint32x2_t(max(uint32_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_s64) +@(require_results, enable_target_feature = "neon") +vorn_s64 :: #force_inline proc "c" (a, b: int64x1_t) -> int64x1_t { + c := int64x1_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vorn_u64) +@(require_results, enable_target_feature = "neon") +vorn_u64 :: #force_inline proc "c" (a, b: uint64x1_t) -> uint64x1_t { + c := uint64x1_t(max(uint64_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s8) +@(require_results, enable_target_feature = "neon") +vornq_s8 :: #force_inline proc "c" (a, b: int8x16_t) -> int8x16_t { + c := int8x16_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u8) +@(require_results, enable_target_feature = "neon") +vornq_u8 :: #force_inline proc "c" (a, b: uint8x16_t) -> uint8x16_t { + c := uint8x16_t(max(uint8_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s16) +@(require_results, enable_target_feature = "neon") +vornq_s16 :: #force_inline proc "c" (a, b: int16x8_t) -> int16x8_t { + c := int16x8_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u16) +@(require_results, enable_target_feature = "neon") +vornq_u16 :: #force_inline proc "c" (a, b: uint16x8_t) -> uint16x8_t { + c := uint16x8_t(max(uint16_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s32) +@(require_results, enable_target_feature = "neon") +vornq_s32 :: #force_inline proc "c" (a, b: int32x4_t) -> int32x4_t { + c := int32x4_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u32) +@(require_results, enable_target_feature = "neon") +vornq_u32 :: #force_inline proc "c" (a, b: uint32x4_t) -> uint32x4_t { + c := uint32x4_t(max(uint32_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_s64) +@(require_results, enable_target_feature = "neon") +vornq_s64 :: #force_inline proc "c" (a, b: int64x2_t) -> int64x2_t { + c := int64x2_t(-1) + return simd.bit_or(simd.bit_xor(b, c), a) +} + +// Bitwise Inclusive Or Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vornq_u64) +@(require_results, enable_target_feature = "neon") +vornq_u64 :: #force_inline proc "c" (a, b: uint64x2_t) -> uint64x2_t { + c := uint64x2_t(max(uint64_t)) + return simd.bit_or(simd.bit_xor(b, c), a) +} + when ODIN_ARCH == .arm64 { // Table Lookup. // @@ -1728,6 +2570,78 @@ when ODIN_ARCH == .arm64 { return simd.shuffle(c, c, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) } } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vneg_s64) + @(require_results, enable_target_feature = "neon") + vneg_s64 :: #force_inline proc "c" (a: int64x1_t) -> int64x1_t { + return simd.neg(a) + } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegd_s64) + @(require_results, enable_target_feature = "neon") + vnegd_s64 :: #force_inline proc "c" (a: int64_t) -> int64_t { + return -a + } + + // Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vnegq_s64) + @(require_results, enable_target_feature = "neon") + vnegq_s64 :: #force_inline proc "c" (a: int64x2_t) -> int64x2_t { + return simd.neg(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqneg_s64) + @(require_results, enable_target_feature = "neon") + vqneg_s64 :: #force_inline proc "c" (a: int64x1_t) -> int64x1_t { + return _vqneg_s64(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegq_s64) + @(require_results, enable_target_feature = "neon") + vqnegq_s64 :: #force_inline proc "c" (a: int64x2_t) -> int64x2_t { + return _vqnegq_s64(a) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegb_s8) + @(require_results, enable_target_feature = "neon") + vqnegb_s8 :: #force_inline proc "c" (a: int8_t) -> int8_t { + return vget_lane_s8(vqneg_s8(vdup_n_s8(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegh_s16) + @(require_results, enable_target_feature = "neon") + vqnegh_s16 :: #force_inline proc "c" (a: int16_t) -> int16_t { + return vget_lane_s16(vqneg_s16(vdup_n_s16(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegs_s32) + @(require_results, enable_target_feature = "neon") + vqnegs_s32 :: #force_inline proc "c" (a: int32_t) -> int32_t { + return vget_lane_s32(vqneg_s32(vdup_n_s32(a)), 0) + } + + // Signed saturating Negate. + // + // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vqnegd_s64) + @(require_results, enable_target_feature = "neon") + vqnegd_s64 :: #force_inline proc "c" (a: int64_t) -> int64_t { + return vget_lane_s64(vqneg_s64(vdup_n_s64(a)), 0) + } } @(private, default_calling_convention = "none") @@ -1744,6 +2658,18 @@ foreign _ { _vclsq_s16 :: proc(a: int16x8_t) -> int16x8_t --- @(link_name = "llvm.aarch64.neon.cls.v4i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v4i32") _vclsq_s32 :: proc(a: int32x4_t) -> int32x4_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v8i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v8i8") + _vqneg_s8 :: proc(a: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v4i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v4i16") + _vqneg_s16 :: proc(a: int16x4_t) -> int16x4_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v2i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v2i32") + _vqneg_s32 :: proc(a: int32x2_t) -> int32x2_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v16i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v16i8") + _vqnegq_s8 :: proc(a: int8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v8i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v8i16") + _vqnegq_s16 :: proc(a: int16x8_t) -> int16x8_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v4i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vqneg.v4i32") + _vqnegq_s32 :: proc(a: int32x4_t) -> int32x4_t --- } when ODIN_ARCH == .arm32 { @@ -1771,6 +2697,10 @@ when ODIN_ARCH == .arm32 { when ODIN_ARCH == .arm64 { @(private, default_calling_convention = "none") foreign _ { + @(link_name = "llvm.aarch64.neon.sqneg.v1i64") + _vqneg_s64 :: proc(a: int64x1_t) -> int64x1_t --- + @(link_name = "llvm.aarch64.neon.sqneg.v2i64") + _vqnegq_s64 :: proc(a: int64x2_t) -> int64x2_t --- @(link_name = "llvm.aarch64.neon.tbl1.v8i8") _vqtbl1 :: proc(t: int8x16_t, idx: uint8x8_t) -> int8x8_t --- @(link_name = "llvm.aarch64.neon.tbl1.v16i8") diff --git a/core/simd/arm/pmull.odin b/core/simd/arm/pmull.odin index b2baa85db..cb2940b2d 100644 --- a/core/simd/arm/pmull.odin +++ b/core/simd/arm/pmull.odin @@ -596,6 +596,40 @@ vtbx4_p8 :: #force_inline proc "c" (v: poly8x8_t, t: poly8x8x4_t, idx: uint8x8_t } } +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_p8) +@(require_results, enable_target_feature = "neon") +vcnt_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { + return transmute(poly8x8_t)vcnt_s8(transmute(int8x8_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_p8) +@(require_results, enable_target_feature = "neon") +vcntq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { + return transmute(poly8x16_t)vcntq_s8(transmute(int8x16_t)a) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvn_p8) +@(require_results, enable_target_feature = "neon") +vmvn_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { + b := poly8x8_t(max(poly8_t)) + return simd.bit_xor(a, b) +} + +// Bitwise Not. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vmvnq_p8) +@(require_results, enable_target_feature = "neon") +vmvnq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { + b := poly8x16_t(max(poly8_t)) + return simd.bit_xor(a, b) +} + when ODIN_ARCH == .arm64 { // Polynomial multiply long // From de9d6d7dcc498012095158b3ec801848e1b1468f Mon Sep 17 00:00:00 2001 From: corley <28909106+corleypc@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:12:25 +0300 Subject: [PATCH 51/93] Reenables LLVMSetUnnamedAddress for global constants folding --- src/llvm_backend.cpp | 2 +- src/llvm_backend_general.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 95df563fd..792cb6253 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3274,7 +3274,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMValueRef g = LLVMAddGlobal(m->mod, internal_llvm_type, LB_TYPE_INFO_DATA_NAME); LLVMSetInitializer(g, LLVMConstNull(internal_llvm_type)); LLVMSetLinkage(g, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); - // LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(g, true); lbValue value = {}; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 9e9c09051..303a713f6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -364,7 +364,7 @@ gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) { gb_internal void lb_make_global_private_const(LLVMValueRef global_data) { LLVMSetLinkage(global_data, LLVMLinkerPrivateLinkage); - // LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(global_data, true); } gb_internal void lb_make_global_private_const(lbAddr const &addr) { @@ -3786,7 +3786,7 @@ gb_internal lbValue lb_generate_global_array(lbModule *m, Type *elem_type, i64 c g.type = alloc_type_pointer(t); LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, t))); LLVMSetLinkage(g.value, LLVMPrivateLinkage); - // LLVMSetUnnamedAddress(g.value, LLVMGlobalUnnamedAddr); + LLVMSetUnnamedAddress(g.value, LLVMGlobalUnnamedAddr); string_map_set(&m->members, s, g); return g; } From ba708a2ad21e8e58fc40412751ee271ffa7d4997 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 14 Aug 2026 19:15:11 -0700 Subject: [PATCH 52/93] riscv64: read a flattened aggregate's members from their real offsets --- src/llvm_abi.cpp | 112 ++++++++++++++++++++++++++++++++++++++ src/llvm_backend_proc.cpp | 76 ++++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 5 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 6a60b64c0..a760c47b8 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -16,6 +16,14 @@ struct lbArgType { i64 byval_alignment; bool is_byval; bool no_capture; + + // For RiscV (Optional for others): A `cast_type` is normally applied by reinterpreting the value's + // bits from offset zero. Only correct when the two layouts agree. When an ABI flattens an aggregate + // it drops padding, and the dense type it produces puts the surviving members at different offsets + // than they really have, eg `struct #min_field_align(16){i8, f32}` flattens to `{i8, float}`, moving + // the float from offset 16 to offset 4. These give the real byte offset of each `cast_type` element. + i64 *coerce_offsets; + isize coerce_offset_count; }; @@ -25,6 +33,14 @@ gb_internal i64 lb_alignof(LLVMTypeRef type); gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type, LLVMTypeRef cast_type, LLVMTypeRef pad_type, LLVMAttributeRef attr) { return lbArgType{lbArg_Direct, type, cast_type, pad_type, attr, nullptr, 0, false}; } +// Same as above, except coercion reads each element of `cast_type` from its real offset in `type` +// instead of reinterpreting the bits from offset zero. See `coerce_offsets`. +gb_internal lbArgType lb_arg_type_direct_fields(LLVMTypeRef type, LLVMTypeRef cast_type, i64 *offsets, isize count) { + lbArgType arg = lb_arg_type_direct(type, cast_type, nullptr, nullptr); + arg.coerce_offsets = offsets; + arg.coerce_offset_count = count; + return arg; +} gb_internal lbArgType lb_arg_type_direct(LLVMTypeRef type) { return lb_arg_type_direct(type, nullptr, nullptr, nullptr); } @@ -2030,6 +2046,78 @@ namespace lbAbiRiscv64 { } } + // `flatten` records which members survive; this records where they are. The two are walked + // together so the dense type it builds can still be read from the real object: an over-aligned + // member leaves a gap that the flatten removes, and reinterpreting the bits from offset zero + // then reads the member from where the padding used to be. + gb_internal void flatten_offsets(lbModule *m, Array *offsets, LLVMTypeRef type, i64 base) { + switch (LLVMGetTypeKind(type)) { + case LLVMStructTypeKind: { + if (LLVMIsPackedStruct(type)) { + array_add(offsets, base); + break; + } + unsigned elem_count = LLVMCountStructElementTypes(type); + // element offsets the way `lb_alignof` models LLVM's own layout + auto elem_offsets = array_make(temporary_allocator(), 0, elem_count); + i64 off = 0; + for (unsigned i = 0; i < elem_count; i += 1) { + LLVMTypeRef et = LLVMStructGetTypeAtIndex(type, i); + i64 a = lb_alignof(et); + if (a > 0) { + off = align_formula(off, a); + } + array_add(&elem_offsets, off); + off += lb_sizeof(et); + } + + auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); + if (field_remapping) { + auto remap = *field_remapping; + for_array(i, remap) { + flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, remap[i]), base + elem_offsets[remap[i]]); + } + break; + } + for (unsigned i = 0; i < elem_count; i += 1) { + flatten_offsets(m, offsets, LLVMStructGetTypeAtIndex(type, i), base + elem_offsets[i]); + } + break; + } + case LLVMArrayTypeKind: { + unsigned len = LLVMGetArrayLength(type); + LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); + i64 stride = lb_sizeof(elem); + for (unsigned i = 0; i < len; i += 1) { + flatten_offsets(m, offsets, elem, base + cast(i64)i*stride); + } + break; + } + default: + array_add(offsets, base); + } + } + + // The offsets the dense `cast_type` implies, so the two can be compared. When they agree the + // ordinary bit-reinterpreting coercion is right and nothing needs to change. + gb_internal bool flatten_moved_a_member(Array const &fields, Array const &offsets) { + if (fields.count != offsets.count) { + return false; + } + i64 off = 0; + for_array(i, fields) { + i64 a = lb_alignof(fields[i]); + if (a > 0) { + off = align_formula(off, a); + } + if (off != offsets[i]) { + return true; + } + off += lb_sizeof(fields[i]); + } + return false; + } + // The psABI's rule is "one floating-point real and one integer (or bitfield)", and a pointer // is not an integer. `is_register` admits pointers and keeps that meaning for its other // callers, so the floating-point arms need their own predicate. @@ -2070,10 +2158,22 @@ namespace lbAbiRiscv64 { LLVMTypeRef fp_type = type; LLVMTypeKind fp_kind = kind; i64 fp_size = size; + i64 *fp_offsets = nullptr; + isize fp_offset_count = 0; if (kind == LLVMStructTypeKind) { Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); flatten(m, &fields, type, false); + auto offsets = array_make(temporary_allocator(), 0, fields.count); + flatten_offsets(m, &offsets, type, 0); + if (flatten_moved_a_member(fields, offsets)) { + fp_offsets = gb_alloc_array(permanent_allocator(), i64, offsets.count); + for_array(i, offsets) { + fp_offsets[i] = offsets[i]; + } + fp_offset_count = offsets.count; + } + if (fields.count == 1) { fp_type = fields[0]; } else { @@ -2089,6 +2189,9 @@ namespace lbAbiRiscv64 { if (fp_type != orig_type) { // A struct that flattened to a single float has to be coerced to that float; // handing back the original sends an over-aligned one to integer registers. + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } return non_struct(c, orig_type); @@ -2104,18 +2207,27 @@ namespace lbAbiRiscv64 { if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { *fprs_left -= 2; + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_float(ty1) && is_int_member(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } if (is_int_member(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { *fprs_left -= 1; *gprs_left -= 1; + if (fp_offset_count > 0) { + return lb_arg_type_direct_fields(orig_type, fp_type, fp_offsets, fp_offset_count); + } return lb_arg_type_direct(orig_type, fp_type, nullptr, nullptr); } } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index be1b3724f..0ff3147c3 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1,3 +1,6 @@ +gb_internal LLVMValueRef lb_coerce_fields_load(lbProcedure *p, lbValue x, lbArgType const *arg); +gb_internal LLVMValueRef lb_coerce_fields_store(lbProcedure *p, LLVMValueRef coerced, Type *original_type, lbArgType const *arg); + gb_internal LLVMValueRef lb_call_intrinsic(lbProcedure *p, const char *name, LLVMValueRef* args, unsigned arg_count, LLVMTypeRef* types, unsigned type_count) { unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); GB_ASSERT_MSG(id != 0, "Unable to find %s", name); @@ -681,7 +684,9 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { LLVMTypeRef param_type = lb_type(p->module, e->type); LLVMValueRef original_value = LLVMGetParam(p->value, param_offset+llvm_param_index); - LLVMValueRef value = OdinLLVMBuildTransmute(p, original_value, param_type); + LLVMValueRef value = arg_type->coerce_offset_count > 0 + ? lb_coerce_fields_store(p, original_value, e->type, arg_type) + : OdinLLVMBuildTransmute(p, original_value, param_type); lbValue param = {}; param.value = value; @@ -922,6 +927,60 @@ gb_internal Array lb_value_to_array(lbProcedure *p, gbAllocator const & +// A `cast_type` is normally applied by reinterpreting the value's bits from offset zero. When the +// ABI flattened an aggregate and dropped padding, the dense type it produced puts the survivors at +// different offsets than they really have, and these two read and write them where they actually +// live. See `lbArgType::coerce_offsets`. +gb_internal LLVMValueRef lb_coerce_fields_load(lbProcedure *p, lbValue x, lbArgType const *arg) { + LLVMContextRef ctx = p->module->ctx; + LLVMTypeRef i8 = LLVMInt8TypeInContext(ctx); + LLVMTypeRef i64t = LLVMInt64TypeInContext(ctx); + + lbValue base = lb_address_from_load_or_generate_local(p, x); + + if (LLVMGetTypeKind(arg->cast_type) != LLVMStructTypeKind) { + GB_ASSERT(arg->coerce_offset_count == 1); + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[0], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, base.value, &index, 1, ""); + return LLVMBuildLoad2(p->builder, arg->cast_type, ptr, ""); + } + + unsigned count = LLVMCountStructElementTypes(arg->cast_type); + GB_ASSERT(cast(isize)count == arg->coerce_offset_count); + LLVMValueRef result = LLVMGetUndef(arg->cast_type); + for (unsigned i = 0; i < count; i += 1) { + LLVMTypeRef elem_type = LLVMStructGetTypeAtIndex(arg->cast_type, i); + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[i], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, base.value, &index, 1, ""); + LLVMValueRef elem = LLVMBuildLoad2(p->builder, elem_type, ptr, ""); + result = LLVMBuildInsertValue(p->builder, result, elem, i, ""); + } + return result; +} + +// The reverse: scatter a coerced value back into a full-sized object of the original type. +gb_internal LLVMValueRef lb_coerce_fields_store(lbProcedure *p, LLVMValueRef coerced, Type *original_type, lbArgType const *arg) { + LLVMContextRef ctx = p->module->ctx; + LLVMTypeRef i8 = LLVMInt8TypeInContext(ctx); + LLVMTypeRef i64t = LLVMInt64TypeInContext(ctx); + + lbAddr slot = lb_add_local_generated(p, original_type, true); + + unsigned count = 1; + bool is_struct = LLVMGetTypeKind(arg->cast_type) == LLVMStructTypeKind; + if (is_struct) { + count = LLVMCountStructElementTypes(arg->cast_type); + } + GB_ASSERT(cast(isize)count == arg->coerce_offset_count); + for (unsigned i = 0; i < count; i += 1) { + LLVMValueRef elem = is_struct ? LLVMBuildExtractValue(p->builder, coerced, i, "") : coerced; + LLVMValueRef index = LLVMConstInt(i64t, cast(unsigned long long)arg->coerce_offsets[i], false); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, i8, slot.addr.value, &index, 1, ""); + LLVMBuildStore(p->builder, elem, ptr); + } + return lb_addr_load(p, slot).value; +} + gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining, ProcTailing tailing) { GB_ASSERT(p->module->ctx == LLVMGetTypeContext(LLVMTypeOf(value.value))); @@ -1191,7 +1250,10 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c if (!abi_type) { abi_type = arg->type; } - if (xt == abi_type) { + if (arg->coerce_offset_count > 0) { + x.value = lb_coerce_fields_load(p, x, arg); + array_add(&processed_args, x); + } else if (xt == abi_type) { array_add(&processed_args, x); } else { x.value = OdinLLVMBuildTransmute(p, x.value, abi_type); @@ -1261,10 +1323,14 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c result = lb_emit_load(p, return_ptr); } else if (rt != nullptr) { result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining, tailing); - if (ft->ret.cast_type) { - result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type); + if (ft->ret.coerce_offset_count > 0) { + result.value = lb_coerce_fields_store(p, result.value, rt, &ft->ret); + } else { + if (ft->ret.cast_type) { + result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type); + } + result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.type); } - result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.type); result.type = rt; if (LLVMTypeOf(result.value) == LLVMInt1TypeInContext(p->module->ctx)) { result.type = t_llvm_bool; From 5787cbc68b362a1724317a4910e1c03cf19b009f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 15 Aug 2026 21:34:58 +0200 Subject: [PATCH 53/93] Fix backend support for #unroll on [dynamic]T --- src/llvm_backend_stmt.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 8b4b021fb..9fbb2d15d 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1740,7 +1740,13 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * slice = lb_emit_load(p, slice); } else { count_ptr = lb_add_local_generated(p, t_int, false).addr; - lb_emit_store(p, count_ptr, lb_slice_len(p, slice)); + if (t->kind == Type_Slice) { + lb_emit_store(p, count_ptr, lb_slice_len(p, slice)); + } else if (t->kind == Type_DynamicArray) { + lb_emit_store(p, count_ptr, lb_dynamic_array_len(p, slice)); + } else { + GB_ASSERT_MSG(false, "Need to add support for this type."); + } } data_ptr = lb_emit_struct_ev(p, slice, 0); break; From 1dbafbab632eb2888c1cd2d60e0cefb9bbbd7bae Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Sat, 15 Aug 2026 17:13:52 -0400 Subject: [PATCH 54/93] Explicitly specify all required LLVM components on linux build. This fixes missing symbols errors when building with LLVM with separate components. --- build_odin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_odin.sh b/build_odin.sh index 9eab985e9..ebf2804c6 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -125,7 +125,7 @@ NetBSD) ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" + LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native passes arm aarch64 x86 webassembly riscv --system-libs --libfiles)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ From b17c10162de1d71260d938701a2eb8ff5790609d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Sun, 16 Aug 2026 00:30:42 +0200 Subject: [PATCH 55/93] Fix bit_set upper endpoint representability check --- src/check_type.cpp | 2 +- tests/issues/run.sh | 7 +++++++ tests/issues/test_issue_7304.odin | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/issues/test_issue_7304.odin diff --git a/src/check_type.cpp b/src/check_type.cpp index 14da12340..c5f78fd57 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1368,7 +1368,7 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t gb_free(a, s.text); return; } - if (!check_representable_as_constant(c, iv, t, nullptr)) { + if (!check_representable_as_constant(c, jv, t, nullptr)) { gbAllocator a = heap_allocator(); String s = big_int_to_string(a, &j); gbString ts = type_to_string(t); diff --git a/tests/issues/run.sh b/tests/issues/run.sh index c4dae65f9..393bd1eda 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -107,6 +107,13 @@ else exit 1 fi +if [[ $($ODIN check ../test_issue_7304.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "9223372036854775808 is not representable by int") -eq 1 ]]; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi + clang -c ../test_issue_7010.c -o test_issue_7010_c.o $ODIN test ../test_issue_7010.odin $COMMON diff --git a/tests/issues/test_issue_7304.odin b/tests/issues/test_issue_7304.odin new file mode 100644 index 000000000..c96a0bae2 --- /dev/null +++ b/tests/issues/test_issue_7304.odin @@ -0,0 +1,4 @@ +// Tests issue #7304 https://github.com/odin-lang/Odin/issues/7304 +package test_issues + +Bad_Bit_Set :: bit_set[-1 ..< 9223372036854775808] From 617293c5f053baafdfa9859f918d108e0c278e4d Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 20:08:27 -0700 Subject: [PATCH 56/93] quat/cmplx nan cmp --- src/exact_value.cpp | 27 ++++ .../internal/test_quaternion_comparison.odin | 130 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tests/internal/test_quaternion_comparison.odin diff --git a/src/exact_value.cpp b/src/exact_value.cpp index 2f82a52cf..5c436fee6 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -1019,6 +1019,10 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) f64 b = x.value_complex->imag; f64 c = y.value_complex->real; f64 d = y.value_complex->imag; + if (isnan(a) || isnan(b) || isnan(c) || isnan(d)) { + return op == Token_NotEq; + } + switch (op) { case Token_CmpEq: return cmp_f64(a, c) == 0 && cmp_f64(b, d) == 0; case Token_NotEq: return cmp_f64(a, c) != 0 || cmp_f64(b, d) != 0; @@ -1026,6 +1030,29 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y) break; } + case ExactValue_Quaternion: { + Quaternion256 a = *x.value_quaternion; + Quaternion256 b = *y.value_quaternion; + if (isnan(a.real) || isnan(a.imag) || isnan(a.jmag) || isnan(a.kmag) || + isnan(b.real) || isnan(b.imag) || isnan(b.jmag) || isnan(b.kmag)) { + return op == Token_NotEq; + } + + switch (op) { + case Token_CmpEq: + return cmp_f64(a.real, b.real) == 0 && + cmp_f64(a.imag, b.imag) == 0 && + cmp_f64(a.jmag, b.jmag) == 0 && + cmp_f64(a.kmag, b.kmag) == 0; + case Token_NotEq: + return cmp_f64(a.real, b.real) != 0 || + cmp_f64(a.imag, b.imag) != 0 || + cmp_f64(a.jmag, b.jmag) != 0 || + cmp_f64(a.kmag, b.kmag) != 0; + } + break; + } + case ExactValue_String: { String a = x.value_string; String b = y.value_string; diff --git a/tests/internal/test_quaternion_comparison.odin b/tests/internal/test_quaternion_comparison.odin new file mode 100644 index 000000000..93de83b8e --- /dev/null +++ b/tests/internal/test_quaternion_comparison.odin @@ -0,0 +1,130 @@ +package test_internal + +import "core:testing" + +// Each constant case is paired with the same comparison on variables: the folded answer +// has to be the answer the backend produces. + +@(test) +compare_constant_quaternions :: proc(t: ^testing.T) { + I :: quaternion128(0+1i+0j+0k) + J :: quaternion128(0+0i+1j+0k) + K :: quaternion128(0+0i+0j+1k) + R :: quaternion128(1+0i+0j+0k) + + testing.expect_value(t, R == 1, true) + testing.expect_value(t, 1 == R, true) + testing.expect_value(t, R != 1, false) + testing.expect_value(t, I == J, false) + testing.expect_value(t, J == K, false) + testing.expect_value(t, K == R, false) + testing.expect_value(t, I != J, true) + testing.expect_value(t, I == I, true) + + // every lane must take part, not just the real one + A :: quaternion128(2+3i+4j+5k) + testing.expect_value(t, A == quaternion128(2+3i+4j+5k), true) + testing.expect_value(t, A == quaternion128(9+3i+4j+5k), false) + testing.expect_value(t, A == quaternion128(2+9i+4j+5k), false) + testing.expect_value(t, A == quaternion128(2+3i+9j+5k), false) + testing.expect_value(t, A == quaternion128(2+3i+4j+9k), false) + + // promotion of the other operand, from integer, float and complex + testing.expect_value(t, R == 1.0, true) + testing.expect_value(t, quaternion128(2+3i+0j+0k) == 2+3i, true) + testing.expect_value(t, quaternion128(2+3i+0j+0k) == 2+4i, false) + + testing.expect_value(t, quaternion64(0) == 0, true) + testing.expect_value(t, quaternion256(0) == 0, true) + testing.expect_value(t, quaternion128(0) == quaternion128(-0.0), true) +} + +@(test) +compare_variable_quaternions :: proc(t: ^testing.T) { + I := quaternion128(0+1i+0j+0k) + J := quaternion128(0+0i+1j+0k) + K := quaternion128(0+0i+0j+1k) + R := quaternion128(1+0i+0j+0k) + + testing.expect_value(t, R == 1, true) + testing.expect_value(t, 1 == R, true) + testing.expect_value(t, R != 1, false) + testing.expect_value(t, I == J, false) + testing.expect_value(t, J == K, false) + testing.expect_value(t, K == R, false) + testing.expect_value(t, I != J, true) + testing.expect_value(t, I == I, true) + + A := quaternion128(2+3i+4j+5k) + testing.expect_value(t, A == quaternion128(2+3i+4j+5k), true) + testing.expect_value(t, A == quaternion128(9+3i+4j+5k), false) + testing.expect_value(t, A == quaternion128(2+9i+4j+5k), false) + testing.expect_value(t, A == quaternion128(2+3i+9j+5k), false) + testing.expect_value(t, A == quaternion128(2+3i+4j+9k), false) + + testing.expect_value(t, R == 1.0, true) + testing.expect_value(t, quaternion128(2+3i+0j+0k) == 2+3i, true) + testing.expect_value(t, quaternion128(2+3i+0j+0k) == 2+4i, false) + + q64 := quaternion64(0) + q256 := quaternion256(0) + testing.expect_value(t, q64 == 0, true) + testing.expect_value(t, q256 == 0, true) + testing.expect_value(t, quaternion128(0) == quaternion128(-0.0), true) +} + +@(test) +compare_constant_quaternion_nans :: proc(t: ^testing.T) { + NaN :: f64(0h7ff8_0000_0000_0000) + Q :: quaternion(w=NaN, x=0, y=0, z=0) + L :: quaternion(w=0, x=0, y=0, z=NaN) + + testing.expect_value(t, Q == Q, false) + testing.expect_value(t, Q != Q, true) + testing.expect_value(t, L == L, false) + testing.expect_value(t, L != L, true) + testing.expect_value(t, Q == 0, false) + testing.expect_value(t, Q != 0, true) +} + +@(test) +compare_variable_quaternion_nans :: proc(t: ^testing.T) { + NaN := f64(0h7ff8_0000_0000_0000) + Q := quaternion(w=NaN, x=0, y=0, z=0) + L := quaternion(w=0, x=0, y=0, z=NaN) + + testing.expect_value(t, Q == Q, false) + testing.expect_value(t, Q != Q, true) + testing.expect_value(t, L == L, false) + testing.expect_value(t, L != L, true) + testing.expect_value(t, Q == 0, false) + testing.expect_value(t, Q != 0, true) +} + +@(test) +compare_constant_complex_nans :: proc(t: ^testing.T) { + NaN :: f64(0h7ff8_0000_0000_0000) + C :: complex(NaN, 0) + D :: complex(0, NaN) + + testing.expect_value(t, C == C, false) + testing.expect_value(t, C != C, true) + testing.expect_value(t, D == D, false) + testing.expect_value(t, D != D, true) + testing.expect_value(t, C == 0, false) + testing.expect_value(t, C != 0, true) +} + +@(test) +compare_variable_complex_nans :: proc(t: ^testing.T) { + NaN := f64(0h7ff8_0000_0000_0000) + C := complex(NaN, 0) + D := complex(0, NaN) + + testing.expect_value(t, C == C, false) + testing.expect_value(t, C != C, true) + testing.expect_value(t, D == D, false) + testing.expect_value(t, D != D, true) + testing.expect_value(t, C == 0, false) + testing.expect_value(t, C != 0, true) +} From 1658e2d841a528b18d07c3ac570c9edc7617cba6 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 20:13:25 -0700 Subject: [PATCH 57/93] fix typo on cmplx -> quat --- src/check_builtin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4a59a59b7..7498a4004 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3866,7 +3866,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { - x->type = t_untyped_complex; + x->type = t_untyped_quaternion; } } else{ convert_to_typed(c, x, t_quaternion256); From b378f21f4c0259fdee6514ad168f4f5360b8fde9 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 20:24:41 -0700 Subject: [PATCH 58/93] update tests --- tests/internal/test_nan_comparison.odin | 97 +++++++ tests/internal/test_quat_cmplx.odin | 272 ++++++++++++++++++ .../internal/test_quaternion_comparison.odin | 130 --------- 3 files changed, 369 insertions(+), 130 deletions(-) create mode 100644 tests/internal/test_quat_cmplx.odin delete mode 100644 tests/internal/test_quaternion_comparison.odin diff --git a/tests/internal/test_nan_comparison.odin b/tests/internal/test_nan_comparison.odin index 5740be477..b47cc9dfc 100644 --- a/tests/internal/test_nan_comparison.odin +++ b/tests/internal/test_nan_comparison.odin @@ -89,3 +89,100 @@ compare_variable_nans_f64 :: proc(t: ^testing.T) { testing.expect_value(t, NaN > NaN, false) testing.expect_value(t, NaN >= NaN, false) } + +// A complex or quaternion compares componentwise, so a NaN in any one lane makes the whole +// comparison fail. The folded form used to disagree: `cmp_f64` is `(a>b)-(a Date: Sat, 15 Aug 2026 20:41:22 -0700 Subject: [PATCH 59/93] fix #5964 --- src/check_builtin.cpp | 8 ---- tests/internal/test_quat_cmplx.odin | 57 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 7498a4004..521570800 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3846,10 +3846,6 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { - operand->type = type_hint; - } - break; } @@ -3902,10 +3898,6 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As default: GB_PANIC("Invalid type"); break; } - if (type_hint != nullptr && check_is_castable_to(c, operand, type_hint)) { - operand->type = type_hint; - } - break; } diff --git a/tests/internal/test_quat_cmplx.odin b/tests/internal/test_quat_cmplx.odin index 9edef4da5..bf8df6f0d 100644 --- a/tests/internal/test_quat_cmplx.odin +++ b/tests/internal/test_quat_cmplx.odin @@ -270,3 +270,60 @@ accessor_results_are_untyped :: proc(t: ^testing.T) { testing.expect_value(t, R32, 2) testing.expect_value(t, I32, 3) } + +// An enclosing conversion passes its destination down as a type hint, and the accessors +// used to adopt it as their own result type. That decides which type the arithmetic +// inside the conversion happens in, so the wrong answer is observable: the division +// below was checked in i8, where `3.2` does not exist. + +@(test) +accessors_ignore_the_enclosing_conversions_type :: proc(t: ^testing.T) { + c: complex128 = 10 + testing.expect_value(t, i8(real(c) / 3.2), 3) + testing.expect_value(t, i8(imag(c) / 3.2), 0) + + d: complex128 = 0 + 10i + testing.expect_value(t, i8(imag(d) / 3.2), 3) + + q: quaternion256 = quaternion(w=10, x=10, y=10, z=10) + testing.expect_value(t, i8(real(q) / 3.2), 3) + testing.expect_value(t, i8(imag(q) / 3.2), 3) + testing.expect_value(t, i8(jmag(q) / 3.2), 3) + testing.expect_value(t, i8(kmag(q) / 3.2), 3) + + // the same on constants + C :: complex128(10) + Q :: quaternion256(1+2i+3j+4k) + testing.expect_value(t, int(real(C) / 2.5), 4) + testing.expect_value(t, int(kmag(Q) / 0.5), 8) +} + +// `transmute` supplies a hint too, and it is the spelling that failed silently: the +// accessor was retyped to the destination, so what got reinterpreted was already an +// integer and the float's bits were gone. Naming the intermediate was the workaround, +// so a named one is the control here. + +@(test) +accessors_keep_their_bits_through_transmute :: proc(t: ^testing.T) { + c32 : complex32 = complex(f16(1.5), f16(2.5)) + c64 : complex64 = complex(f32(1), f32(2)) + c128 : complex128 = complex(f64(1), f64(2)) + + n := real(c32) + r := real(c64) + i := imag(c128) + + testing.expect_value(t, transmute(u16)real(c32), transmute(u16)n) + testing.expect_value(t, transmute(u32)real(c64), transmute(u32)r) + testing.expect_value(t, transmute(u64)imag(c128), transmute(u64)i) + + testing.expect_value(t, transmute(u16)real(c32), 15872) + testing.expect_value(t, transmute(u32)real(c64), 1065353216) + testing.expect_value(t, transmute(u64)imag(c128), 4611686018427387904) + + q : quaternion256 = quaternion(w=1, x=2, y=3, z=4) + j := jmag(q) + k := kmag(q) + testing.expect_value(t, transmute(u64)jmag(q), transmute(u64)j) + testing.expect_value(t, transmute(u64)kmag(q), transmute(u64)k) +} From ae742cb9f3c68dc5b0916dffd5d0b437911bbc3d Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 20:53:03 -0700 Subject: [PATCH 60/93] rune unsigned mapping --- src/check_builtin.cpp | 7 +++++++ tests/internal/test_intrinsics_integer_to.odin | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4a59a59b7..d984f230e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -6995,6 +6995,13 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (bt->Basic.kind == Basic_rune) { + gbString t = type_to_string(operand->type); + error(operand->expr, "Type %s does not have an unsigned integer mapping for '%.*s'", t, LIT(builtin_name)); + gb_string_free(t); + return false; + } + Type *u_type = &basic_types[bt->Basic.kind + 1]; operand->type = u_type; diff --git a/tests/internal/test_intrinsics_integer_to.odin b/tests/internal/test_intrinsics_integer_to.odin index 108318c9a..8e80089aa 100644 --- a/tests/internal/test_intrinsics_integer_to.odin +++ b/tests/internal/test_intrinsics_integer_to.odin @@ -12,6 +12,18 @@ example_usage :: proc(#any_int x: int) -> intrinsics.type_integer_to_unsigned(ty @test test_intrinsic_integer_to :: proc(t: ^testing.T) { + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i8)), typeid_of(u8)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i16)), typeid_of(u16)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i32)), typeid_of(u32)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i64)), typeid_of(u64)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i128)), typeid_of(u128)) + + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u8)), typeid_of(i8)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u16)), typeid_of(i16)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u32)), typeid_of(i32)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u64)), typeid_of(i64)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_signed(u128)), typeid_of(i128)) + testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i16le)), typeid_of(u16le)) testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i32le)), typeid_of(u32le)) testing.expect_value(t, typeid_of(intrinsics.type_integer_to_unsigned(i64le)), typeid_of(u64le)) From e699d2185e542af165193792da4ad92ddcdbdbc1 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 21:07:17 -0700 Subject: [PATCH 61/93] simd diagnostics --- src/check_builtin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4a59a59b7..50c679c7b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1787,7 +1787,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) return false; convert_to_typed(c, &offset, t_i64); if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) { - error(offset.expr, "'%.*s' expected a constant integer offset"); + error(offset.expr, "'%.*s' expected a constant integer offset", LIT(builtin_name)); return false; } check_assignment(c, &offset, t_i64, builtin_name); @@ -1917,7 +1917,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan i64 max_count = 64; if (count > max_count) { - error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", cast(long long)max_count, cast(long long)count); + error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", LIT(builtin_name), cast(long long)max_count, cast(long long)count); return false; } From b2f2b653c57fc961e9ba721ca524621357548655 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 21:18:36 -0700 Subject: [PATCH 62/93] add pow2 guard on interleave --- src/check_builtin.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 50c679c7b..290481c54 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1920,6 +1920,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", LIT(builtin_name), cast(long long)max_count, cast(long long)count); return false; } + // the lane count is the operand width times the argument count, so it is a power + // of two only when the argument count is + if (!is_power_of_two(count)) { + error(ce->proc, "'%.*s' must produce a power of two #simd count, got %lld", LIT(builtin_name), cast(long long)count); + return false; + } operand->type = alloc_type_simd_vector(count, elem); operand->mode = Addressing_Value; From 4ceba928c76307f588485201d447bd3e641d564c Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 21:28:43 -0700 Subject: [PATCH 63/93] fix swizzle and shuffle max size bypass --- src/check_builtin.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 290481c54..457252354 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1530,6 +1530,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } + // the result is as wide as the index list, which may be twice the operand width + if (arg_count > SIMD_ELEMENT_COUNT_MAX) { + error(call, "'%.*s' constructs a #simd vector beyond the maximum element count of %d, got %lld", LIT(builtin_name), SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count); + return false; + } + operand->mode = Addressing_Value; operand->type = alloc_type_simd_vector(arg_count, elem); return true; @@ -1915,7 +1921,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan i64 base_count = get_array_type_count(x.type); i64 count = base_count * cast(i64)ce->args.count; - i64 max_count = 64; + i64 max_count = SIMD_ELEMENT_COUNT_MAX; if (count > max_count) { error(ce->proc, "'%.*s' exceeds the maximum #simd count %lld, got %lld", LIT(builtin_name), cast(long long)max_count, cast(long long)count); return false; @@ -3517,6 +3523,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (is_type_simd_vector(type) && arg_count > SIMD_ELEMENT_COUNT_MAX) { + error(call, "'swizzle' constructs a #simd vector beyond the maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)arg_count); + return false; + } + operand->type = determine_swizzle_array_type(original_type, type_hint, arg_count); break; } From 0243398b19f5a24529dbf23b6a6134be5b7e3984 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 21:52:07 -0700 Subject: [PATCH 64/93] swizzle arg guard --- src/check_builtin.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 457252354..5d9c6ac08 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3500,10 +3500,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As arg_count++; } - if (false && arg_count > max_count) { - error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); - return false; - } else if (arg_count < 2) { + // No upper bound on the index count + if (arg_count < 2) { error(call, "Not enough 'swizzle' indices, %td < 2", arg_count); return false; } From 69fb453e518197626a1899124c5c452d1ce9b449 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 22:02:06 -0700 Subject: [PATCH 65/93] redecl type; pairwise guard --- core/simd/simd.odin | 2 +- src/check_builtin.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index fcbceb81a..7d61f523a 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2899,7 +2899,7 @@ abs_diff :: #force_inline proc "contextless" (a, b: $T/#simd[$LANES]$E) -> T whe } pairwise_add :: intrinsics.simd_pairwise_add -pairwise_sub :: intrinsics.simd_pairwise_add +pairwise_sub :: intrinsics.simd_pairwise_sub interleave :: intrinsics.simd_interleave deinterleave :: intrinsics.simd_deinterleave \ No newline at end of file diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 5d9c6ac08..ed35a928a 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -949,6 +949,16 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan // don't return } + if (id == BuiltinProc_simd_pairwise_add || id == BuiltinProc_simd_pairwise_sub) { + i64 lanes = get_array_type_count(x.type); + if (lanes % 2 != 0) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an even lane count, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + } + operand->mode = Addressing_Value; operand->type = x.type; return true; From e53fc6a8796728cd1634cdfbeff9a7b747e5c104 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 22:23:15 -0700 Subject: [PATCH 66/93] simd [?] type fix; count recovery --- src/check_expr.cpp | 34 ++++- src/check_type.cpp | 10 +- tests/internal/test_simd_lane_counts.odin | 172 ++++++++++++++++++++++ 3 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 tests/internal/test_simd_lane_counts.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ec276884a..96772a778 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -10688,7 +10688,25 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (count != nullptr) { if (count->kind == Ast_UnaryExpr && count->UnaryExpr.op.kind == Token_Question) { - type = alloc_type_array(check_type(c, type_expr->ArrayType.elem), -1); + Type *elem = check_type(c, type_expr->ArrayType.elem); + + bool is_simd_tag = false; + if (type_expr->ArrayType.tag != nullptr) { + GB_ASSERT(type_expr->ArrayType.tag->kind == Ast_BasicDirective); + is_simd_tag = type_expr->ArrayType.tag->BasicDirective.name.string == "simd"; + } + if (is_simd_tag) { + if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { + gbString str = type_to_string(elem); + error(type_expr->ArrayType.elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str); + gb_string_free(str); + type = alloc_type_array(elem, -1); + } else { + type = alloc_type_simd_vector(-1, elem); + } + } else { + type = alloc_type_array(elem, -1); + } is_to_be_determined_array_count = true; } } else { @@ -10912,7 +10930,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } else if (t->kind == Type_SimdVector) { elem_type = t->SimdVector.elem; context_name = str_lit("simd vector literal"); - max_type_count = t->SimdVector.count; + if (!is_to_be_determined_array_count) { + max_type_count = t->SimdVector.count; + } } else if (t->kind == Type_Matrix) { elem_type = t->Matrix.elem; context_name = str_lit("matrix literal"); @@ -11088,6 +11108,16 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * error(node, "Expected %lld values for this array literal, got %lld", cast(long long)t->Array.count, cast(long long)max); } } + } else if (t->kind == Type_SimdVector) { + // the length laws cannot be applied until the literal has supplied the count + if (is_to_be_determined_array_count) { + t->SimdVector.count = max; + if (max < 1 || !is_power_of_two(max)) { + error(node, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)max); + } else if (max > SIMD_ELEMENT_COUNT_MAX) { + error(node, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)max); + } + } } else if (t->kind == Type_Struct) { GB_ASSERT(t->Struct.soa_kind == StructSoa_Fixed); if (is_to_be_determined_array_count) { diff --git a/src/check_type.cpp b/src/check_type.cpp index 14da12340..0fdf6d6b9 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3538,9 +3538,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t return; } + // Track user input and recovery value seperate, since both could be '0' + bool count_recovered = false; if (count < 0) { error(at->count, "? can only be used in conjunction with compound literals"); count = 0; + count_recovered = true; } @@ -3562,7 +3565,12 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t // Ignore } else if (count < 1 || !is_power_of_two(count)) { *type = alloc_type_array(elem, count, generic_type); - if (ctx->disallow_polymorphic_return_types && count == 0) { + if (count_recovered) { + return; + } + // a polymorphic value used as the count is still unresolved while the + // signature is checked and reads as 0; only a written count is constant + if (ctx->disallow_polymorphic_return_types && o.mode != Addressing_Constant) { return; } error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin new file mode 100644 index 000000000..1d06790f0 --- /dev/null +++ b/tests/internal/test_simd_lane_counts.odin @@ -0,0 +1,172 @@ +package test_internal + +import "base:intrinsics" +import "core:simd" +import "core:testing" + +// `simd_interleave` derives its lane count as operand width times argument count, so it is a +// power of two only when the argument count is. The rejection of the rest cannot be asserted +// from a test -- it is a compile error -- so what is pinned here is the other side: the widths +// that must keep working, and the values they carry. + +@(test) +simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { + a: #simd[4]i32 = {1, 2, 3, 4} + b: #simd[4]i32 = {5, 6, 7, 8} + c: #simd[4]i32 = {9, 10, 11, 12} + d: #simd[4]i32 = {13, 14, 15, 16} + + two := intrinsics.simd_interleave(a, b) + testing.expect_value(t, len(two), 8) + testing.expect_value(t, simd_extract_i32(two, 0), 1) + testing.expect_value(t, simd_extract_i32(two, 1), 5) + testing.expect_value(t, simd_extract_i32(two, 2), 2) + testing.expect_value(t, simd_extract_i32(two, 3), 6) + + four := intrinsics.simd_interleave(a, b, c, d) + testing.expect_value(t, len(four), 16) + testing.expect_value(t, simd_extract_i32(four, 0), 1) + testing.expect_value(t, simd_extract_i32(four, 1), 5) + testing.expect_value(t, simd_extract_i32(four, 2), 9) + testing.expect_value(t, simd_extract_i32(four, 3), 13) + + // a single argument is a power of two count as well, and must not be caught by the guard + one := intrinsics.simd_interleave(a) + testing.expect_value(t, len(one), 4) + + x, y := intrinsics.simd_deinterleave(two, 2) + testing.expect_value(t, len(x), 4) + testing.expect_value(t, len(y), 4) + testing.expect_value(t, simd_extract_i32(x, 0), 1) + testing.expect_value(t, simd_extract_i32(y, 0), 5) +} + +simd_extract_i32 :: #force_inline proc(v: $V/#simd[$N]i32, $I: int) -> i32 { + return intrinsics.simd_extract(v, I) +} + +// `simd_shuffle` and `swizzle` size their result from the index list, which can be twice the +// operand width, so both can construct a `#simd` wider than the syntax accepts. What is pinned +// here is the boundary that must keep working: exactly SIMD_ELEMENT_COUNT_MAX lanes, and a +// `swizzle` over a plain array, which is not bound by the `#simd` limit at all. + +@(test) +simd_construction_at_the_element_count_max :: proc(t: ^testing.T) { + a: #simd[32]i32 = 1 + at_max := intrinsics.simd_shuffle(a, a, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63) + testing.expect_value(t, len(at_max), 64) + + b: #simd[8]i32 = {1, 2, 3, 4, 5, 6, 7, 8} + under := intrinsics.simd_shuffle(b, b, 0, 1, 2, 3, 8, 9, 10, 11) + testing.expect_value(t, len(under), 8) + testing.expect_value(t, simd_extract_i32(under, 4), 1) + + sw := swizzle(b, 7, 6, 5, 4, 3, 2, 1, 0) + testing.expect_value(t, len(sw), 8) + testing.expect_value(t, simd_extract_i32(sw, 0), 8) + + // a plain array is not a #simd vector, so the element-count limit must not reach it + arr: [96]i32 + arr[95] = 7 + asw := swizzle(arr, 95, 0, 1) + testing.expect_value(t, len(asw), 3) + testing.expect_value(t, asw[0], 7) + + d, e: #simd[32]i32 + ilv := intrinsics.simd_interleave(d, e) + testing.expect_value(t, len(ilv), 64) +} + +// A swizzle may repeat indices to produce a result wider than its operand. `core:crypto` +// depends on it -- chacha20's simd256 path doubles a 4-lane state with eight indices -- so +// there is deliberately no upper bound on the index count. + +@(test) +swizzle_may_widen_its_operand :: proc(t: ^testing.T) { + v: [2]f32 = {1, 2} + testing.expect_value(t, swizzle(v, 0, 0, 0, 0), [4]f32{1, 1, 1, 1}) + testing.expect_value(t, swizzle(v, 0, 1, 0, 1), [4]f32{1, 2, 1, 2}) + + a: [3]i32 = {7, 8, 9} + testing.expect_value(t, swizzle(a, 2, 2, 2, 2, 2, 2), [6]i32{9, 9, 9, 9, 9, 9}) + + // the same shape on a #simd vector: four lanes widened to eight + q: #simd[4]u32 = {1, 2, 3, 4} + w := swizzle(q, 0, 1, 2, 3, 0, 1, 2, 3) + testing.expect_value(t, len(w), 8) + testing.expect_value(t, intrinsics.simd_extract(w, 4), u32(1)) + testing.expect_value(t, intrinsics.simd_extract(w, 7), u32(4)) +} + +// A pairwise operation folds adjacent lanes within each operand, so it needs an even lane +// count -- `base:intrinsics` declares `LANES % 2 == 0`. At one lane it has nothing to pair +// with and silently switches to combining the two operands instead, which is why that width +// is rejected rather than defined. + +@(test) +simd_pairwise_folds_adjacent_lanes :: proc(t: ^testing.T) { + a: #simd[2]i32 = {10, 3} + b: #simd[2]i32 = {20, 4} + + add := intrinsics.simd_pairwise_add(a, b) + testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13)) + testing.expect_value(t, intrinsics.simd_extract(add, 1), i32(24)) + + sub := intrinsics.simd_pairwise_sub(a, b) + testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7)) + testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16)) + + c: #simd[4]f32 = {1, 2, 3, 4} + d: #simd[4]f32 = {5, 6, 7, 8} + f := intrinsics.simd_pairwise_add(c, d) + testing.expect_value(t, intrinsics.simd_extract(f, 0), f32(3)) + testing.expect_value(t, intrinsics.simd_extract(f, 1), f32(7)) + + // the other six builtins sharing this arm take a single lane, and must stay unaffected + o: #simd[1]i32 = 7 + p: #simd[1]i32 = 2 + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(o, p), 0), i32(9)) + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_max(o, p), 0), i32(7)) +} + +// `#simd[?]T{...}` The inferred form must agree with the explicit one. + +@(test) +simd_inferred_length_from_a_compound_literal :: proc(t: ^testing.T) { + a := #simd[?]i32{1, 2, 3, 4} + testing.expect_value(t, len(a), 4) + testing.expect_value(t, intrinsics.type_is_simd_vector(type_of(a)), true) + testing.expect_value(t, typeid_of(type_of(a)), typeid_of(#simd[4]i32)) + testing.expect_value(t, intrinsics.simd_extract(a, 0), i32(1)) + testing.expect_value(t, intrinsics.simd_extract(a, 3), i32(4)) + + b := #simd[?]f32{1.5, 2.5} + testing.expect_value(t, len(b), 2) + testing.expect_value(t, typeid_of(type_of(b)), typeid_of(#simd[2]f32)) + testing.expect_value(t, intrinsics.simd_extract(b, 1), f32(2.5)) + + // the inferred vector must work with the intrinsics, which is the point of the tag + testing.expect_value(t, intrinsics.simd_extract(intrinsics.simd_add(a, a), 3), i32(8)) + + // a plain `[?]` array is unaffected + c := [?]i32{1, 2, 3} + testing.expect_value(t, len(c), 3) + testing.expect_value(t, intrinsics.type_is_array(type_of(c)), true) +} + +// `core:simd` aliased `pairwise_sub` to `simd_pairwise_add`, so it silently added +@(test) +simd_pairwise_aliases_are_distinct :: proc(t: ^testing.T) { + a: #simd[2]i32 = {10, 3} + b: #simd[2]i32 = {20, 4} + + add := simd.pairwise_add(a, b) + sub := simd.pairwise_sub(a, b) + testing.expect_value(t, intrinsics.simd_extract(add, 0), i32(13)) + testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7)) + testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16)) +} From 5f0251fec24dc72eb4331ce4043ff75033fc6e77 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 22:44:52 -0700 Subject: [PATCH 67/93] 5791,6748 --- src/check_builtin.cpp | 13 +++------ tests/internal/test_simd_lane_counts.odin | 33 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ed35a928a..f67c06dcb 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1305,10 +1305,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { return false; } - if (max_count < 0) { - error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); - return false; - } operand->mode = Addressing_Value; operand->type = elem; @@ -1330,10 +1326,6 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { return false; } - if (max_count < 0) { - error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); - return false; - } Operand y = {}; check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) return false; @@ -1801,12 +1793,13 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } Operand offset = {}; check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) return false; - convert_to_typed(c, &offset, t_i64); + // `base:intrinsics` declares the offset as `int` and does not mark it #any_int + convert_to_typed(c, &offset, t_int); if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) { error(offset.expr, "'%.*s' expected a constant integer offset", LIT(builtin_name)); return false; } - check_assignment(c, &offset, t_i64, builtin_name); + check_assignment(c, &offset, t_int, builtin_name); operand->type = x.type; operand->mode = Addressing_Value; diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index 1d06790f0..5f5ff6e49 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -170,3 +170,36 @@ simd_pairwise_aliases_are_distinct :: proc(t: ^testing.T) { testing.expect_value(t, intrinsics.simd_extract(sub, 0), i32(7)) testing.expect_value(t, intrinsics.simd_extract(sub, 1), i32(16)) } + +// The rotate offset is declared `$offset: int`; the checker demanded `i64`, so the declared +// spelling did not compile. Other integer types stay rejected -- the declaration is not #any_int. + +@(test) +simd_lanes_rotate_offset_is_int :: proc(t: ^testing.T) { + rot :: proc(v: #simd[4]u32, $offset: int) -> #simd[4]u32 { + return intrinsics.simd_lanes_rotate_right(v, offset) + } + + v: #simd[4]u32 = {0, 1, 2, 3} + r := rot(v, 1) + testing.expect_value(t, intrinsics.simd_extract(r, 0), u32(3)) + testing.expect_value(t, intrinsics.simd_extract(r, 1), u32(0)) + + l := intrinsics.simd_lanes_rotate_left(v, 1) + testing.expect_value(t, intrinsics.simd_extract(l, 0), u32(1)) + testing.expect_value(t, intrinsics.simd_extract(l, 3), u32(0)) +} + +// `simd_extract` / `simd_replace` declare a plain `idx: uint`, so a runtime index is allowed and +// lowers to a dynamic extractelement. Bounds are enforced only where the index is constant. + +@(test) +simd_extract_accepts_a_runtime_index :: proc(t: ^testing.T) { + v: #simd[4]u32 = {10, 20, 30, 40} + i := 2 + testing.expect_value(t, intrinsics.simd_extract(v, i), u32(30)) + + w := intrinsics.simd_replace(v, i, u32(99)) + testing.expect_value(t, intrinsics.simd_extract(w, 2), u32(99)) + testing.expect_value(t, intrinsics.simd_extract(w, 0), u32(10)) +} From 885e12d21d945ad5d6d07e81af8040cadfc79be1 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 23:06:35 -0700 Subject: [PATCH 68/93] and-not fold --- src/big_int.cpp | 12 +- tests/internal/test_constant_folding.odin | 139 ++++++++++++++++++++++ 2 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/internal/test_constant_folding.odin diff --git a/src/big_int.cpp b/src/big_int.cpp index 6db3301da..1d81a0483 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -489,7 +489,8 @@ gb_internal void big_int_and(BigInt *dst, BigInt const *x, BigInt const *y) { gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) { if (mp_iszero(x)) { - big_int_init(dst, y); + // 0 &~ y == 0 & ~y == 0 + big_int_from_i64(dst, 0); return; } if (mp_iszero(y)) { @@ -505,13 +506,13 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) mp_decr(&x1); mp_decr(&y1); - BigInt ny1 = {}; - mp_complement(&y1, &ny1); - mp_and(&x1, &ny1, dst); + BigInt nx1 = {}; + mp_complement(&x1, &nx1); + mp_and(&y1, &nx1, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); - big_int_dealloc(&ny1); + big_int_dealloc(&nx1); return; } @@ -532,6 +533,7 @@ gb_internal void big_int_and_not(BigInt *dst, BigInt const *x, BigInt const *y) BigInt z1 = {}; big_int_or(&z1, &x1, &y1); mp_add_d(&z1, 1, dst); + big_int_neg(dst, dst); big_int_dealloc(&x1); big_int_dealloc(&y1); diff --git a/tests/internal/test_constant_folding.odin b/tests/internal/test_constant_folding.odin new file mode 100644 index 000000000..71e70488d --- /dev/null +++ b/tests/internal/test_constant_folding.odin @@ -0,0 +1,139 @@ +package test_internal + +import "core:testing" + +// Constant folding against the answer the backend produces. A folded constant that is merely +// wrong still compiles, so a harness comparing accept/reject sees agreement +// Every case here pairs a constant with the same expression on variables for that reason. +// +// `a &~ b` is `a & ~b`. `big_int_and_not` had three independent faults: `0 &~ y` returned `y`, +// the both-negative branch used its operands the wrong way round, and the negative-left branch +// dropped the sign of its result. + +@(test) +and_not_constant_folding_matches_runtime :: proc(t: ^testing.T) { + // the zero short-circuit: 0 &~ anything is 0 + { + a, b := 0, 3 + testing.expect_value(t, 0 &~ 3, a &~ b) + testing.expect_value(t, 0 &~ 3, 0) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 &~ -3, a &~ b) + testing.expect_value(t, 0 &~ -3, 0) + } + + // negative left operand: the result must stay negative + { + a, b := -7, 3 + testing.expect_value(t, -7 &~ 3, a &~ b) + testing.expect_value(t, -7 &~ 3, -8) + } + { + a, b := -255, 5 + testing.expect_value(t, -255 &~ 5, a &~ b) + testing.expect_value(t, -255 &~ 5, -256) + } + + // both negative + { + a, b := -7, -3 + testing.expect_value(t, -7 &~ -3, a &~ b) + testing.expect_value(t, -7 &~ -3, 0) + } + { + a, b := -3, -7 + testing.expect_value(t, -3 &~ -7, a &~ b) + testing.expect_value(t, -3 &~ -7, 4) + } + + // the cases that were already correct, so a fix cannot regress them + { + a, b := 7, 3 + testing.expect_value(t, 7 &~ 3, a &~ b) + testing.expect_value(t, 7 &~ 3, 4) + } + { + a, b := 7, -3 + testing.expect_value(t, 7 &~ -3, a &~ b) + testing.expect_value(t, 7 &~ -3, 2) + } + { + a, b := 7, 0 + testing.expect_value(t, 7 &~ 0, a &~ b) + testing.expect_value(t, 7 &~ 0, 7) + } +} + +@(test) +and_not_constant_folding_every_width :: proc(t: ^testing.T) { + // the zero-left shape reaches unsigned types too + { + a, b := u8(0), u8(1) + testing.expect_value(t, u8(0) &~ u8(1), a &~ b) + testing.expect_value(t, u8(0) &~ u8(1), u8(0)) + } + { + a, b := u64(0), u64(255) + testing.expect_value(t, u64(0) &~ u64(255), a &~ b) + testing.expect_value(t, u64(0) &~ u64(255), u64(0)) + } + + // signed, at the extremes of each width + { + a, b := i8(-128), i8(1) + testing.expect_value(t, i8(-128) &~ i8(1), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(1), i8(-128)) + } + { + a, b := i8(-128), i8(127) + testing.expect_value(t, i8(-128) &~ i8(127), a &~ b) + testing.expect_value(t, i8(-128) &~ i8(127), i8(-128)) + } + { + a, b := i8(-7), i8(-128) + testing.expect_value(t, i8(-7) &~ i8(-128), a &~ b) + testing.expect_value(t, i8(-7) &~ i8(-128), i8(121)) + } + { + a, b := i16(-7), i16(3) + testing.expect_value(t, i16(-7) &~ i16(3), a &~ b) + testing.expect_value(t, i16(-7) &~ i16(3), i16(-8)) + } + { + a, b := i32(-255), i32(5) + testing.expect_value(t, i32(-255) &~ i32(5), a &~ b) + testing.expect_value(t, i32(-255) &~ i32(5), i32(-256)) + } + { + a, b := i64(-7), i64(-3) + testing.expect_value(t, i64(-7) &~ i64(-3), a &~ b) + testing.expect_value(t, i64(-7) &~ i64(-3), i64(0)) + } +} + +// `&~` was the only operator found divergent; the rest of the bitwise family shares the sign +// handling and must stay agreeing. + +@(test) +bitwise_constant_folding_matches_runtime :: proc(t: ^testing.T) { + { + a, b := -7, 3 + testing.expect_value(t, -7 & 3, a & b) + testing.expect_value(t, -7 | 3, a | b) + testing.expect_value(t, -7 ~ 3, a ~ b) + } + { + a, b := -7, -3 + testing.expect_value(t, -7 & -3, a & b) + testing.expect_value(t, -7 | -3, a | b) + testing.expect_value(t, -7 ~ -3, a ~ b) + } + { + a, b := 0, -3 + testing.expect_value(t, 0 & -3, a & b) + testing.expect_value(t, 0 | -3, a | b) + testing.expect_value(t, 0 ~ -3, a ~ b) + } +} From a7029ef7b6b94d7a28dbe14e4aec67ad3b547790 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 23:25:13 -0700 Subject: [PATCH 69/93] bitset subset --- src/check_expr.cpp | 18 +++--- tests/internal/test_constant_folding.odin | 78 +++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 tests/internal/test_constant_folding.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ec276884a..344842978 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3343,27 +3343,29 @@ gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Oper case Token_Lt: case Token_LtEq: { + // subset: (lhs & rhs) == lhs. a proper subset also requires lhs != rhs ExactValue lhs = x->value; ExactValue rhs = y->value; - ExactValue res = exact_binary_operator_value(Token_And, lhs, rhs); - res = exact_value_bool(compare_exact_values(op, res, lhs)); + ExactValue both = exact_binary_operator_value(Token_And, lhs, rhs); + bool res = compare_exact_values(Token_CmpEq, both, lhs); if (op == Token_Lt) { - res = exact_binary_operator_value(Token_And, res, exact_value_bool(compare_exact_values(op, lhs, rhs))); + res = res && compare_exact_values(Token_NotEq, lhs, rhs); } - x->value = res; + x->value = exact_value_bool(res); break; } case Token_Gt: case Token_GtEq: { + // superset: (lhs & rhs) == rhs ExactValue lhs = x->value; ExactValue rhs = y->value; - ExactValue res = exact_binary_operator_value(Token_And, lhs, rhs); - res = exact_value_bool(compare_exact_values(op, res, rhs)); + ExactValue both = exact_binary_operator_value(Token_And, lhs, rhs); + bool res = compare_exact_values(Token_CmpEq, both, rhs); if (op == Token_Gt) { - res = exact_binary_operator_value(Token_And, res, exact_value_bool(compare_exact_values(op, lhs, rhs))); + res = res && compare_exact_values(Token_NotEq, lhs, rhs); } - x->value = res; + x->value = exact_value_bool(res); break; } } diff --git a/tests/internal/test_constant_folding.odin b/tests/internal/test_constant_folding.odin new file mode 100644 index 000000000..385ba6835 --- /dev/null +++ b/tests/internal/test_constant_folding.odin @@ -0,0 +1,78 @@ +package test_internal + +import "core:testing" + +// `<=` and `<` on a `bit_set` are subset and proper subset, `>=` and `>` superset. The folder +// asked `(lhs & rhs) <= lhs` where the definition is `(lhs & rhs) == lhs`, which is true for +// any operands, so `<=` folded true unconditionally; `<` compounded it by requiring `lhs < rhs` +// where it needs `lhs != rhs`. Under `when` this decides which declarations exist. + +@(test) +bit_set_subset_folding_matches_runtime :: proc(t: ^testing.T) { + B :: bit_set[0..<4] + + { // disjoint: neither a subset nor a superset + a, b := B{0, 3}, B{0, 1} + testing.expect_value(t, B{0, 3} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0, 3} <= B{0, 1}, false) + testing.expect_value(t, B{0, 3} >= B{0, 1}, a >= b) + testing.expect_value(t, B{0, 3} >= B{0, 1}, false) + } + { // proper subset + a, b := B{0}, B{0, 1} + testing.expect_value(t, B{0} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0} <= B{0, 1}, true) + testing.expect_value(t, B{0} < B{0, 1}, a < b) + testing.expect_value(t, B{0} < B{0, 1}, true) + } + { // equal: a subset but not a proper one + a, b := B{0, 1}, B{0, 1} + testing.expect_value(t, B{0, 1} <= B{0, 1}, a <= b) + testing.expect_value(t, B{0, 1} <= B{0, 1}, true) + testing.expect_value(t, B{0, 1} < B{0, 1}, a < b) + testing.expect_value(t, B{0, 1} < B{0, 1}, false) + } + { // superset + a, b := B{0, 1}, B{0} + testing.expect_value(t, B{0, 1} <= B{0}, a <= b) + testing.expect_value(t, B{0, 1} <= B{0}, false) + testing.expect_value(t, B{0, 1} > B{0}, a > b) + testing.expect_value(t, B{0, 1} > B{0}, true) + } + { // the empty set is a subset of everything, and a proper one unless both are empty + a, b := B{}, B{0} + testing.expect_value(t, B{} < B{0}, a < b) + testing.expect_value(t, B{} < B{0}, true) + } + { + a, b := B{}, B{} + testing.expect_value(t, B{} <= B{}, a <= b) + testing.expect_value(t, B{} <= B{}, true) + testing.expect_value(t, B{} < B{}, a < b) + testing.expect_value(t, B{} < B{}, false) + } + + // equality was never affected, so a fix here must not disturb it + { + a, b := B{0, 1}, B{1, 0} + testing.expect_value(t, B{0, 1} == B{1, 0}, a == b) + testing.expect_value(t, B{0, 1} == B{1, 0}, true) + testing.expect_value(t, B{0, 1} != B{0}, a != B{0}) + } +} + +// a mis-folded subset test selects the wrong `when` arm, which changes which declarations exist +@(test) +bit_set_subset_folding_selects_the_right_when_arm :: proc(t: ^testing.T) { + B :: bit_set[0..<4] + + when (B{0} < B{0, 1}) { W1 :: 1 } else { W1 :: 0 } + when (B{0, 3} <= B{0, 1}) { W2 :: 0 } else { W2 :: 1 } + when (B{0, 1} <= B{0, 1}) { W3 :: 1 } else { W3 :: 0 } + when (B{0, 1} > B{0}) { W4 :: 1 } else { W4 :: 0 } + + testing.expect_value(t, W1, 1) + testing.expect_value(t, W2, 1) + testing.expect_value(t, W3, 1) + testing.expect_value(t, W4, 1) +} From d52aff5c4b4fee17113a110f22b9cde43e453009 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sat, 15 Aug 2026 23:53:54 -0700 Subject: [PATCH 70/93] impl riffle for llvm<=20 fallback on interleave --- src/llvm_backend_proc.cpp | 43 ++++++++++++++++++++--- tests/internal/test_simd_lane_counts.odin | 6 ++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index be1b3724f..011219c08 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1563,12 +1563,45 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn args[i] = arg.value; } - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n); - defer (gb_string_free(name)); + // `llvm.vector.interleave` for N > 2 is only usable on recent LLVM: it does not + // exist at all on 20, and where it does exist a backend may still have no pattern + // for it (x86 on 20 fails to select). Measured working on 22, so take it there. + #if LLVM_VERSION_MAJOR >= 22 + if (n > 2) { + gbString name = gb_string_make(heap_allocator(), ""); + name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n); + defer (gb_string_free(name)); - LLVMTypeRef types[1] = {lb_type(m, tv.type)}; - res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types)); + LLVMTypeRef types[1] = {lb_type(m, tv.type)}; + res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types)); + return res; + } + #endif + + // otherwise build the same permutation from interleave2, which every supported LLVM + // has. The operand count is a power of two (the checker requires the lane count to + // be one), and pairing each operand with the one a half-step away is what makes the + // orders agree: interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)). + LLVMTypeRef elem_type = LLVMGetElementType(LLVMTypeOf(args[0])); + unsigned width = LLVMGetVectorSize(LLVMTypeOf(args[0])); + + LLVMValueRef *cur = args; + for (int count = n; count > 1; /**/) { + int half = count/2; + width *= 2; + + LLVMTypeRef types[1] = {LLVMVectorType(elem_type, width)}; + LLVMValueRef *next = temporary_alloc_array(half); + for (int i = 0; i < half; i++) { + LLVMValueRef pair[2] = {cur[i], cur[i+half]}; + next[i] = lb_call_intrinsic(p, "llvm.vector.interleave2", pair, 2, types, gb_count_of(types)); + } + + cur = next; + count = half; + } + + res.value = cur[0]; return res; } diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index 5f5ff6e49..c6ad57051 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -9,6 +9,9 @@ import "core:testing" // from a test -- it is a compile error -- so what is pinned here is the other side: the widths // that must keep working, and the values they carry. +// More than two operands take one of two lowerings, `llvm.vector.interleave` on a LLVM 21+, +// a riffle of `interleave2` elsewhere. The lane order and width are both asserted. + @(test) simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { a: #simd[4]i32 = {1, 2, 3, 4} @@ -29,6 +32,9 @@ simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { testing.expect_value(t, simd_extract_i32(four, 1), 5) testing.expect_value(t, simd_extract_i32(four, 2), 9) testing.expect_value(t, simd_extract_i32(four, 3), 13) + testing.expect_value(t, simd_extract_i32(four, 4), 2) + testing.expect_value(t, simd_extract_i32(four, 7), 14) + testing.expect_value(t, simd_extract_i32(four, 15), 16) // a single argument is a power of two count as well, and must not be caught by the guard one := intrinsics.simd_interleave(a) From 95c4004239a8e13da425e538c446bc52884a294d Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 00:16:21 -0700 Subject: [PATCH 71/93] switch to shuffle intrinsic since #simd is pow2 --- src/llvm_backend_proc.cpp | 43 ++++++++++------------- tests/internal/test_simd_lane_counts.odin | 4 +-- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 011219c08..2ca2594c5 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1563,38 +1563,31 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn args[i] = arg.value; } - // `llvm.vector.interleave` for N > 2 is only usable on recent LLVM: it does not - // exist at all on 20, and where it does exist a backend may still have no pattern - // for it (x86 on 20 fails to select). Measured working on 22, so take it there. - #if LLVM_VERSION_MAJOR >= 22 - if (n > 2) { - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.interleave%d", n); - defer (gb_string_free(name)); - - LLVMTypeRef types[1] = {lb_type(m, tv.type)}; - res.value = lb_call_intrinsic(p, name, args, n, types, gb_count_of(types)); - return res; - } - #endif - - // otherwise build the same permutation from interleave2, which every supported LLVM - // has. The operand count is a power of two (the checker requires the lane count to - // be one), and pairing each operand with the one a half-step away is what makes the - // orders agree: interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)). - LLVMTypeRef elem_type = LLVMGetElementType(LLVMTypeOf(args[0])); - unsigned width = LLVMGetVectorSize(LLVMTypeOf(args[0])); + // `llvm.vector.interleave` is not usable across the supported targets: N > 2 does + // not exist before LLVM 22. Riscv & Darwin AMD64 has no `interleave2` either. + // A shuffle is the one primitive every target has, and it expresses a two-way + // interleave directly. + // + // The operand count is a power of two. The result is a riffle: pairing each operand + // with the one a half-step away is what makes the order come out right, as + // interleave4(a,b,c,d) == interleave2(interleave2(a,c), interleave2(b,d)). + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); LLVMValueRef *cur = args; for (int count = n; count > 1; /**/) { int half = count/2; - width *= 2; + unsigned width = LLVMGetVectorSize(LLVMTypeOf(cur[0])); + + LLVMValueRef *mask = temporary_alloc_array(2*width); + for (unsigned i = 0; i < width; i++) { + mask[2*i + 0] = LLVMConstInt(llvm_u32, i, false); + mask[2*i + 1] = LLVMConstInt(llvm_u32, width+i, false); + } + LLVMValueRef mask_value = LLVMConstVector(mask, 2*width); - LLVMTypeRef types[1] = {LLVMVectorType(elem_type, width)}; LLVMValueRef *next = temporary_alloc_array(half); for (int i = 0; i < half; i++) { - LLVMValueRef pair[2] = {cur[i], cur[i+half]}; - next[i] = lb_call_intrinsic(p, "llvm.vector.interleave2", pair, 2, types, gb_count_of(types)); + next[i] = LLVMBuildShuffleVector(p->builder, cur[i], cur[i+half], mask_value, ""); } cur = next; diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index c6ad57051..2d6bea312 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -9,8 +9,8 @@ import "core:testing" // from a test -- it is a compile error -- so what is pinned here is the other side: the widths // that must keep working, and the values they carry. -// More than two operands take one of two lowerings, `llvm.vector.interleave` on a LLVM 21+, -// a riffle of `interleave2` elsewhere. The lane order and width are both asserted. +// More than two operands lower to a riffle of two-way shuffles rather than one intrinsic, so +// the lane ORDER is asserted and not just the width. @(test) simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { From f7b734c82b3ca99bf8b7eea48126dacd082d28aa Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 00:24:09 -0700 Subject: [PATCH 72/93] deinterleave -> shuffle --- src/llvm_backend_proc.cpp | 25 ++++++++++++---- tests/internal/test_simd_lane_counts.odin | 35 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2ca2594c5..6da9314c5 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1601,7 +1601,6 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn case BuiltinProc_simd_deinterleave: { lbValue arg0 = lb_build_expr(p, ce->args[0]); - LLVMTypeRef types[1] = {lb_type(m, arg0.type)}; GB_ASSERT(ce->args[1]->tav.value.kind == ExactValue_Integer); int n = cast(int)exact_value_to_i64(ce->args[1]->tav.value); @@ -1611,11 +1610,27 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } - gbString name = gb_string_make(heap_allocator(), ""); - name = gb_string_append_fmt(name, "llvm.vector.deinterleave%d", n); - defer (gb_string_free(name)); + // `llvm.vector.deinterleave` for N > 2 cannot be selected or legalized on most + // targets, only arm64 takes it. The split is done with shuffles, same as + // `simd_interleave`. Output `j` is the input strided by N starting at lane `j`. + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + LLVMTypeRef vector_type = LLVMTypeOf(arg0.value); + LLVMValueRef undef = LLVMGetUndef(vector_type); - res.value = lb_call_intrinsic(p, name, &arg0.value, 1, types, gb_count_of(types)); + unsigned width = LLVMGetVectorSize(vector_type); + unsigned part = width/cast(unsigned)n; + + LLVMValueRef agg = LLVMGetUndef(lb_type(m, tv.type)); + LLVMValueRef *mask = temporary_alloc_array(part); + for (int j = 0; j < n; j++) { + for (unsigned i = 0; i < part; i++) { + mask[i] = LLVMConstInt(llvm_u32, i*cast(unsigned)n + cast(unsigned)j, false); + } + LLVMValueRef lanes = LLVMBuildShuffleVector(p->builder, arg0.value, undef, LLVMConstVector(mask, part), ""); + agg = LLVMBuildInsertValue(p->builder, agg, lanes, cast(unsigned)j, ""); + } + + res.value = agg; return res; } } diff --git a/tests/internal/test_simd_lane_counts.odin b/tests/internal/test_simd_lane_counts.odin index 2d6bea312..3931fc065 100644 --- a/tests/internal/test_simd_lane_counts.odin +++ b/tests/internal/test_simd_lane_counts.odin @@ -47,6 +47,41 @@ simd_interleave_power_of_two_widths :: proc(t: ^testing.T) { testing.expect_value(t, simd_extract_i32(y, 0), 5) } +// `simd_deinterleave` splits by stride, so output `j` is the input taken every N lanes from +// lane `j`. N > 2 lowers to shuffles for the same reason interleave does, only arm64 could +// select the intrinsic. The round trip is asserted, not just the widths. + +@(test) +simd_deinterleave_splits_by_stride :: proc(t: ^testing.T) { + v: #simd[16]i32 + for i in 0..<16 { + v = intrinsics.simd_replace(v, i, i32(i)) + } + + a2, b2 := intrinsics.simd_deinterleave(v, 2) + testing.expect_value(t, len(a2), 8) + testing.expect_value(t, simd_extract_i32(a2, 0), 0) + testing.expect_value(t, simd_extract_i32(a2, 1), 2) + testing.expect_value(t, simd_extract_i32(b2, 0), 1) + testing.expect_value(t, simd_extract_i32(b2, 7), 15) + + a4, b4, c4, d4 := intrinsics.simd_deinterleave(v, 4) + testing.expect_value(t, len(a4), 4) + testing.expect_value(t, simd_extract_i32(a4, 0), 0) + testing.expect_value(t, simd_extract_i32(b4, 0), 1) + testing.expect_value(t, simd_extract_i32(c4, 0), 2) + testing.expect_value(t, simd_extract_i32(d4, 0), 3) + testing.expect_value(t, simd_extract_i32(a4, 3), 12) + testing.expect_value(t, simd_extract_i32(d4, 3), 15) + + // interleave is the inverse, so the pair must round trip + r := intrinsics.simd_interleave(a4, b4, c4, d4) + testing.expect_value(t, len(r), 16) + testing.expect_value(t, simd_extract_i32(r, 0), 0) + testing.expect_value(t, simd_extract_i32(r, 7), 7) + testing.expect_value(t, simd_extract_i32(r, 15), 15) +} + simd_extract_i32 :: #force_inline proc(v: $V/#simd[$N]i32, $I: int) -> i32 { return intrinsics.simd_extract(v, I) } From 4e0a71c24c8a087ba71150b1cdb4a3c4e5a4e5d1 Mon Sep 17 00:00:00 2001 From: mo Date: Sun, 16 Aug 2026 19:48:19 +1200 Subject: [PATCH 73/93] Add non-allocating hex.decode_into_buffer Also minor fixes to documentation of hex.decode(). Adds tests of the new procedure with buffers the correct size, larger, and too small. --- core/encoding/hex/hex.odin | 38 ++++++++++++++- tests/core/encoding/hex/test_core_hex.odin | 56 +++++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index c4726d9e9..f7a39a081 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -95,12 +95,12 @@ Decodes a hex sequence into a byte slice *Allocates Using Provided Allocator* Inputs: -- dst: The hex sequence decoded into bytes - src: The `[]byte` to be hex-decoded - allocator: (default: context.allocator) - loc: The caller location for debugging purposes (default: #caller_location) Returns: +- dst: The hex sequence decoded into bytes - ok: A bool, `true` if decoding succeeded, `false` otherwise */ decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) { @@ -123,6 +123,40 @@ decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_locat return dst, true } +/* +Decodes a hex sequence into a byte slice + +Inputs: +- src: The `[]byte` to be hex-decoded +- buf: A buffer large enough to hold the decoded sequence + +Returns: +- dst: The hex sequence decoded into bytes +- ok: A bool, `true` if decoding succeeded, `false` otherwise +*/ +decode_into_buffer :: proc(src: []byte, buf: []byte) -> (dst: []byte, ok: bool) #optional_ok { + if len(src) % 2 == 1 { + return + } + dst_len := len(src) / 2 + if len(buf) < dst_len { + return + } + + #no_bounds_check for i, j := 0, 1; j < len(src); j += 2 { + p := src[j-1] + q := src[j] + + a := hex_digit(p) or_return + b := hex_digit(q) or_return + + buf[i] = (a << 4) | b + i += 1 + } + + return buf[:dst_len], true +} + /* Decodes the first byte in a hex sequence to a byte @@ -173,4 +207,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) { case 'A' ..= 'F': return char - 'A' + 10, true case: return 0, false } -} \ No newline at end of file +} diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index 6a00c9705..fdb3a2734 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -47,6 +47,60 @@ hex_decode :: proc(t: ^testing.T) { } } +@(test) +hex_decode_into_buffer :: proc(t: ^testing.T) { + for test in CASES { + buffer := make([]u8, len(test[1]) / 2) + defer delete(buffer) + decoded, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, ok, "decode_into_buffer: not ok") + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( + t, + string(decoded) == test[0], + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], + ) + } + + // destination buffer is larger + for test in CASES { + buffer := make([]u8, len(test[1]) / 2 + 1) + defer delete(buffer) + decoded, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, ok, "decode_into_buffer: not ok") + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( + t, + string(decoded) == test[0], + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], + ) + } + + // destination buffer is too small + for test in CASES { + buffer := make([]u8, len(test[1]) / 2 - 1) + defer delete(buffer) + _, ok := hex.decode_into_buffer(transmute([]byte)test[1], buffer) + testing.expect(t, !ok, "decode_into_buffer: should not be ok") + } +} + @(test) hex_decode_sequence :: proc(t: ^testing.T) { b, ok := hex.decode_sequence("0x23") @@ -83,4 +137,4 @@ hex_decode_sequence :: proc(t: ^testing.T) { _, ok = hex.decode_sequence("123") testing.expect(t, !ok, "decode_sequence: 123 should be too long") -} \ No newline at end of file +} From e2ae9c7447e1a4dcfe2f4b63167ee547f8559163 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 16 Aug 2026 11:26:14 +0200 Subject: [PATCH 74/93] Disable Wycheproof on Windows CI --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3874a65ab..9810b17de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -254,8 +254,9 @@ jobs: - name: Wycheproof tests shell: cmd run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native + rem call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + rem odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native + echo Skipping Wycheproof on Windows because of CI flakiness that can't be replicated on real hardware - name: Noise Protocol Framework tests shell: cmd run: | From 267c745ac70754952f65a19f4b50e5d8970ea0df Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:07:09 +0200 Subject: [PATCH 75/93] rotate_merge --- core/slice/sort_private.odin | 104 ++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index efa9c596b..35886c8c4 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -13,7 +13,7 @@ Sort_Kind :: enum { Cmp, } -_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { +_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { less :: #force_inline proc(a, b: E, call: P) -> bool { when KIND == .Ordered { return a < b @@ -25,14 +25,102 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O #panic("unhandled Sort_Kind") } } - - // insertion sort - // TODO(bill): use a different algorithm as insertion sort is O(n^2) - n := len(data) - for i in 1.. 0 && less(data[j], data[j-1], call); j -= 1 { - swap(data, j, j-1) + + merge_rotate(data, call) + + insertion_sort :: proc(data: T, call: P){ + for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { + data[j] = data[j - 1] + } + data[j] = temp + } + } + + merge_rotate :: proc(data: T, call: P){ + if len(data) <= 64 { + insertion_sort(data, call) + return + } + + mid := len(data) / 2 + merge_rotate(data[:mid], call) + merge_rotate(data[mid:], call) + + merge(data, mid, len(data) - mid, call) + } + + bin_search_left :: proc(data:T, value: E, call: P) -> int{ + from := 0 + len := len(data) + + for len > 0 { + half := len / 2 + mid := from + half + + if less(data[mid], value, call){ + from = mid + 1 + len -= half + 1 + } else { + len = half + } } + return from + } + + bin_search_right :: proc(data: T, value: E, call: P) -> int { + from := 0 + len := len(data) + + for len > 0 { + half := len / 2 + mid := from + half + + if less(value, data[mid], call){ + len = half + } else { + from = mid + 1 + len -= half + 1 + } + } + return from + } + + merge :: proc(data: T, left, right: int, call: P) { + if left == 0 || right == 0 { + return + } + + if left + right == 2 { + if less(data[1],data[0], call) { + data[1], data[0] = data[0], data[1] + } + return + } + + first_cut, second_cut : int + left2, right2 : int + if left > right { + left2 = left / 2 + first_cut = left2 + + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 + + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut + } + + slice.rotate_left(data[first_cut:second_cut], left - first_cut) + new_mid := first_cut + right2 + + merge(data[:new_mid], left2, right2, call) + merge(data[new_mid:], left-left2, right-right2, call) } } From df54692a9b05f6a9e0a86d97ebc3f05ab3fd9e37 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:22:25 +0200 Subject: [PATCH 76/93] addedtest --- core/slice/sort_private.odin | 2 +- tests/core/slice/test_core_slice.odin | 45 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 35886c8c4..f568671a8 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -116,7 +116,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { left2 = first_cut } - slice.rotate_left(data[first_cut:second_cut], left - first_cut) + rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 merge(data[:new_mid], left2, right2, call) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index b0fb791a6..4f1148983 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -140,6 +140,51 @@ test_sort_by_indices :: proc(t: ^testing.T) { } } +@test +test_sort_stability :: proc(t: ^testing.T) { + // Test sizes are all prime. + test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} + Data :: struct { + rand: int, + index: int, + } + + for test_size in test_sizes { + rand.reset(t.seed) + + vals := make([]Data, test_size) + defer { + delete(vals) + } + + // Set up test values + for _, i in vals { + vals[i].rand = rand.int_max(10) + vals[i].index = i + } + + // Sort + slice.stable_sort_by(vals, proc(l, r: Data) -> bool {return l.rand < r.rand}) + + // Verify sorted test values + rand.reset(t.seed) + + for i in 1.. vals[i].rand { + testing.expect(t, false, "Expected slice to be sorted") + } + if vals[i - 1].index > vals[i].index { + testing.expect(t, false, "Expected slice to be stable") + } + } + + } +} + + @test test_binary_search :: proc(t: ^testing.T) { index: int From 3f851d2b02d71dd63e087d80459813654020ca08 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:27:30 +0200 Subject: [PATCH 77/93] smarter_test --- tests/core/slice/test_core_slice.odin | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 4f1148983..cda22c1ec 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -169,18 +169,22 @@ test_sort_stability :: proc(t: ^testing.T) { // Verify sorted test values rand.reset(t.seed) + sum := vals[0].index for i in 1.. vals[i].rand { + sum += vals[i].index + if vals[i-1].rand > vals[i].rand { testing.expect(t, false, "Expected slice to be sorted") } - if vals[i - 1].index > vals[i].index { + if vals[i-1].rand < vals[i].rand { + continue + } + if vals[i-1].index > vals[i].index { testing.expect(t, false, "Expected slice to be stable") } } + testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") + } } From e62ed261c55a3acaaa9cce73257181eb22cb4601 Mon Sep 17 00:00:00 2001 From: KBS Date: Sun, 16 Aug 2026 19:54:16 +0900 Subject: [PATCH 78/93] base/runtime: do not free the original block on a failed heap resize `base/runtime`: do not free the original block on a failed heap resize The default heap allocator freed old_ptr when the underlying allocation failed (allocated_mem == nil). On the realloc path (heap_resize) the original block is left intact on failure, and on the copy/fresh path old_ptr has not been copied or freed yet, so freeing it left the caller holding a dangling pointer. A [dynamic] array whose resize failed therefore double-freed its data on the next delete (reported as free(): invalid pointer / use-after-free). Return .Out_Of_Memory without freeing anything; the caller retains ownership of the original block. Fixes #7262 --- base/runtime/heap_allocator.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index e2667a78c..4dfe95393 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -42,8 +42,10 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, ptr := uintptr(aligned_mem) aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1) if allocated_mem == nil { - aligned_free(old_ptr) - aligned_free(allocated_mem) + // On failure nothing must be freed: heap_resize (realloc) leaves the + // original block intact, and on the copy/fresh path old_ptr has not + // been copied or freed yet. Freeing old_ptr here left the caller's + // pointer dangling, causing a later double free. (#7262) return nil, .Out_Of_Memory } From 4c945c995a606db06d87b3577ef8f06ae9cba01d Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 12:58:23 +0200 Subject: [PATCH 79/93] added_back_where --- core/slice/sort_private.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index f568671a8..1a2a24b5b 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -13,7 +13,7 @@ Sort_Kind :: enum { Cmp, } -_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) { +_stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { when KIND == .Ordered { return a < b From d8177f7716c4cf4444a46c7924366be7294984e1 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 13:33:32 +0200 Subject: [PATCH 80/93] adjusted_smallsort_threshhold --- core/slice/sort_private.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 1a2a24b5b..0818a7e25 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -40,7 +40,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O } merge_rotate :: proc(data: T, call: P){ - if len(data) <= 64 { + if len(data) <= 200 { insertion_sort(data, call) return } From 9757fed9f987e75564fbcf87e9f6e422b876ee71 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 14:28:38 +0200 Subject: [PATCH 81/93] changed_to_tabs --- core/slice/sort_private.odin | 64 +++++++++++++-------------- tests/core/slice/test_core_slice.odin | 36 +++++++-------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 0818a7e25..a9932fd75 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -15,7 +15,7 @@ Sort_Kind :: enum { _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { - when KIND == .Ordered { + when KIND == .Ordered { return a < b } else when KIND == .Less { return call(a, b) @@ -29,21 +29,21 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge_rotate(data, call) insertion_sort :: proc(data: T, call: P){ - for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { - data[j] = data[j - 1] - } - data[j] = temp - } - } + for i in 1.. 0 && less(temp, data[j - 1], call); j -= 1 { + data[j] = data[j - 1] + } + data[j] = temp + } + } merge_rotate :: proc(data: T, call: P){ - if len(data) <= 200 { - insertion_sort(data, call) - return - } + if len(data) <= 200 { + insertion_sort(data, call) + return + } mid := len(data) / 2 merge_rotate(data[:mid], call) @@ -51,7 +51,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge(data, mid, len(data) - mid, call) } - + bin_search_left :: proc(data:T, value: E, call: P) -> int{ from := 0 len := len(data) @@ -75,10 +75,10 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O len := len(data) for len > 0 { - half := len / 2 - mid := from + half + half := len / 2 + mid := from + half - if less(value, data[mid], call){ + if less(value, data[mid], call){ len = half } else { from = mid + 1 @@ -90,32 +90,32 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge :: proc(data: T, left, right: int, call: P) { if left == 0 || right == 0 { - return - } + return + } if left + right == 2 { if less(data[1],data[0], call) { - data[1], data[0] = data[0], data[1] - } + data[1], data[0] = data[0], data[1] + } return } first_cut, second_cut : int left2, right2 : int if left > right { - left2 = left / 2 - first_cut = left2 + left2 = left / 2 + first_cut = left2 - second_cut = left + bin_search_left(data[left:], data[first_cut], call) - right2 = second_cut - left - } else { - right2 = right / 2 - second_cut = left + right2 + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 - first_cut = bin_search_right(data[:left], data[second_cut], call) - left2 = first_cut + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut } - + rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index cda22c1ec..7fa122966 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -144,10 +144,10 @@ test_sort_by_indices :: proc(t: ^testing.T) { test_sort_stability :: proc(t: ^testing.T) { // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} - Data :: struct { - rand: int, - index: int, - } + Data :: struct { + rand: int, + index: int, + } for test_size in test_sizes { rand.reset(t.seed) @@ -169,21 +169,21 @@ test_sort_stability :: proc(t: ^testing.T) { // Verify sorted test values rand.reset(t.seed) - sum := vals[0].index - for i in 1.. vals[i].rand { - testing.expect(t, false, "Expected slice to be sorted") - } - if vals[i-1].rand < vals[i].rand { - continue - } - if vals[i-1].index > vals[i].index { - testing.expect(t, false, "Expected slice to be stable") - } - } + sum := vals[0].index + for i in 1.. vals[i].rand { + testing.expect(t, false, "Expected slice to be sorted") + } + if vals[i-1].rand < vals[i].rand { + continue + } + if vals[i-1].index > vals[i].index { + testing.expect(t, false, "Expected slice to be stable") + } + } - testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") + testing.expect(t, sum == test_size * (test_size - 1) / 2, "Expected slice to have all indecies") } } From 1a70315797761ba827f3ad650b13b14b9e1566ed Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:09:12 +0200 Subject: [PATCH 82/93] style_corrections --- core/slice/sort_private.odin | 49 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index a9932fd75..7148b1297 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -15,7 +15,7 @@ Sort_Kind :: enum { _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check { less :: #force_inline proc(a, b: E, call: P) -> bool { - when KIND == .Ordered { + when KIND == .Ordered { return a < b } else when KIND == .Less { return call(a, b) @@ -28,7 +28,7 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O merge_rotate(data, call) - insertion_sort :: proc(data: T, call: P){ + insertion_sort :: proc(data: T, call: P) { for i in 1.. int{ - from := 0 - len := len(data) + bin_search_left :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) - for len > 0 { - half := len / 2 + for n > 0 { + half := n / 2 mid := from + half - if less(data[mid], value, call){ + if less(data[mid], value, call) { from = mid + 1 - len -= half + 1 + n -= half + 1 } else { - len = half + n = half } } + return from } - bin_search_right :: proc(data: T, value: E, call: P) -> int { - from := 0 - len := len(data) + bin_search_right :: proc(data: T, value: E, call: P) -> (from: int) { + n := len(data) - for len > 0 { - half := len / 2 + for n > 0 { + half := n / 2 mid := from + half - if less(value, data[mid], call){ - len = half + if less(value, data[mid], call) { + n = half } else { from = mid + 1 - len -= half + 1 + n -= half + 1 } } + return from } @@ -94,14 +94,15 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O } if left + right == 2 { - if less(data[1],data[0], call) { + if less(data[1], data[0], call) { data[1], data[0] = data[0], data[1] } return } - first_cut, second_cut : int - left2, right2 : int + first_cut, second_cut: int + left2, right2: int + if left > right { left2 = left / 2 first_cut = left2 @@ -119,8 +120,8 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O rotate_left(data[first_cut:second_cut], left - first_cut) new_mid := first_cut + right2 - merge(data[:new_mid], left2, right2, call) - merge(data[new_mid:], left-left2, right-right2, call) + merge(data[:new_mid], left2 , right2 , call) + merge(data[new_mid:], left - left2, right - right2, call) } } From f6b8eafdfd4a82e13288313fd8a60974c46b9d52 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:10:01 +0200 Subject: [PATCH 83/93] imporved_style --- tests/core/slice/test_core_slice.odin | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 7fa122966..ee5168e37 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -153,14 +153,11 @@ test_sort_stability :: proc(t: ^testing.T) { rand.reset(t.seed) vals := make([]Data, test_size) - defer { - delete(vals) - } + defer delete(vals) // Set up test values - for _, i in vals { - vals[i].rand = rand.int_max(10) - vals[i].index = i + for &val, i in vals { + val = {rand.int_max(10), i} } // Sort @@ -172,13 +169,13 @@ test_sort_stability :: proc(t: ^testing.T) { sum := vals[0].index for i in 1.. vals[i].rand { + if vals[i - 1].rand > vals[i].rand { testing.expect(t, false, "Expected slice to be sorted") } - if vals[i-1].rand < vals[i].rand { + if vals[i - 1].rand < vals[i].rand { continue } - if vals[i-1].index > vals[i].index { + if vals[i - 1].index > vals[i].index { testing.expect(t, false, "Expected slice to be stable") } } From 0b0778f9813d1327a593e17a5e482b2acb584522 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:24:19 +0200 Subject: [PATCH 84/93] morestyle improvements mybe this is it? --- core/slice/sort_private.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 7148b1297..54a92d986 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -74,10 +74,10 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O n := len(data) for n > 0 { - half := n / 2 - mid := from + half + half := n / 2 + mid := from + half - if less(value, data[mid], call) { + if less(value, data[mid], call) { n = half } else { from = mid + 1 From 7d9367e3b443024e7037b69a579627313072b6a5 Mon Sep 17 00:00:00 2001 From: TheRadischen Date: Sun, 16 Aug 2026 15:24:55 +0200 Subject: [PATCH 85/93] morestyle improvements mybe this is it? --- core/slice/sort_private.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 54a92d986..d0a2147b5 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -104,17 +104,17 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O left2, right2: int if left > right { - left2 = left / 2 - first_cut = left2 + left2 = left / 2 + first_cut = left2 - second_cut = left + bin_search_left(data[left:], data[first_cut], call) - right2 = second_cut - left - } else { - right2 = right / 2 - second_cut = left + right2 + second_cut = left + bin_search_left(data[left:], data[first_cut], call) + right2 = second_cut - left + } else { + right2 = right / 2 + second_cut = left + right2 - first_cut = bin_search_right(data[:left], data[second_cut], call) - left2 = first_cut + first_cut = bin_search_right(data[:left], data[second_cut], call) + left2 = first_cut } rotate_left(data[first_cut:second_cut], left - first_cut) From 0b7b8ba4a22786c327a739be67e285ded9348b34 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 16 Aug 2026 16:16:35 +0200 Subject: [PATCH 86/93] Set console codepage to utf-8 (on Windows), and restore old one on exit Avoid mojibake. --- base/runtime/entry_windows.odin | 59 ++++++++++++++++++++++++++++----- src/common.cpp | 14 ++++++++ src/main.cpp | 7 ++++ 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index d3b38bc9c..56cb9063d 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -5,6 +5,14 @@ package runtime import "base:intrinsics" +when !ODIN_BEDROCK { + @(private="file") + old_console_codepage: u32 + + @(private="file") + UTF_8 :: 65001 +} + when ODIN_BUILD_MODE == .Dynamic { @(link_name="DllMain", linkage="strong", require) DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { @@ -16,10 +24,17 @@ when ODIN_BUILD_MODE == .Dynamic { switch dll_forward_reason { case .Process_Attach: - when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _startup_runtime() + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() case .Process_Detach: - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _cleanup_runtime() + SetConsoleOutputCP(old_console_codepage) + } case .Thread_Attach: break case .Thread_Detach: @@ -35,18 +50,32 @@ when ODIN_BUILD_MODE == .Dynamic { main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { args__ = argv[:argc] context = default_context() - when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _startup_runtime() + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _cleanup_runtime() + SetConsoleOutputCP(old_console_codepage) + } return 0 } } else when ODIN_NO_CRT { @(link_name="mainCRTStartup", linkage="strong", require) mainCRTStartup :: proc "system" () -> i32 { context = default_context() - when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _startup_runtime() + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _cleanup_runtime() + SetConsoleOutputCP(old_console_codepage) + } return 0 } } else { @@ -54,10 +83,24 @@ when ODIN_BUILD_MODE == .Dynamic { main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { args__ = argv[:argc] context = default_context() - when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _startup_runtime() + old_console_codepage = GetConsoleOutputCP() + SetConsoleOutputCP(UTF_8) + } intrinsics.__entry_point() - when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when !ODIN_BEDROCK { + #force_no_inline _cleanup_runtime() + SetConsoleOutputCP(old_console_codepage) + } return 0 } } +} + +foreign import kernel32 "system:Kernel32.lib" +@(default_calling_convention="system") +foreign kernel32 { + GetConsoleOutputCP :: proc() -> u32 --- + SetConsoleOutputCP :: proc(codepage: u32) -> b32 --- } \ No newline at end of file diff --git a/src/common.cpp b/src/common.cpp index 8338ec707..3b4557cde 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -65,6 +65,20 @@ template struct TypeIs64BitInteger { enum {value = false}; }; template <> struct TypeIs64BitInteger { enum {value = true}; }; template <> struct TypeIs64BitInteger { enum {value = true}; }; +#if defined(GB_SYSTEM_WINDOWS) +uint32_t old_console_codepage = 0; +void set_utf8_codepage() { + old_console_codepage = GetConsoleOutputCP(); +} + +void restore_old_codepage() { + // Nothing we can do if this fails, so we're not asserting or anything. + SetConsoleOutputCP(old_console_codepage); +} +#else +void set_utf8_codepage() {} +void restore_old_codepage() {} +#endif #include "unicode.cpp" diff --git a/src/main.cpp b/src/main.cpp index 74900587f..7d2f4d713 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3794,6 +3794,13 @@ int main(int arg_count, char const **arg_ptr) { defer (timings_destroy(&global_timings)); MAIN_TIME_SECTION("initialization"); + // NOTE(Jeroen): Set codepage to UTF-8 (Windows only) and restore on exit. + // Keep in mind this is for the compiler's own output only. + // Like error messages on lines containing unicode. + // Child processes will inherit the default codepage, + // and so must do their own codepage management if they want. + set_utf8_codepage(); + defer (restore_old_codepage()); init_string_interner(); init_global_error_collector(); From 7f532cad149f6da57d98d91d1299d704549bdbc4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 16 Aug 2026 17:01:41 +0200 Subject: [PATCH 87/93] Add -define:ODIN_CODEPAGE_MAGIC=false opt-out to the runtime --- base/runtime/entry_windows.odin | 44 ++++++++++++++------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 56cb9063d..119d50b54 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -5,7 +5,9 @@ package runtime import "base:intrinsics" -when !ODIN_BEDROCK { +ODIN_CODEPAGE_MAGIC :: #config(ODIN_CODEPAGE_MAGIC, !ODIN_BEDROCK) + +when ODIN_CODEPAGE_MAGIC { @(private="file") old_console_codepage: u32 @@ -24,17 +26,15 @@ when ODIN_BUILD_MODE == .Dynamic { switch dll_forward_reason { case .Process_Attach: - when !ODIN_BEDROCK { - #force_no_inline _startup_runtime() + when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { old_console_codepage = GetConsoleOutputCP() SetConsoleOutputCP(UTF_8) } intrinsics.__entry_point() case .Process_Detach: - when !ODIN_BEDROCK { - #force_no_inline _cleanup_runtime() - SetConsoleOutputCP(old_console_codepage) - } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } case .Thread_Attach: break case .Thread_Detach: @@ -50,32 +50,28 @@ when ODIN_BUILD_MODE == .Dynamic { main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { args__ = argv[:argc] context = default_context() - when !ODIN_BEDROCK { - #force_no_inline _startup_runtime() + when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { old_console_codepage = GetConsoleOutputCP() SetConsoleOutputCP(UTF_8) } intrinsics.__entry_point() - when !ODIN_BEDROCK { - #force_no_inline _cleanup_runtime() - SetConsoleOutputCP(old_console_codepage) - } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } else when ODIN_NO_CRT { @(link_name="mainCRTStartup", linkage="strong", require) mainCRTStartup :: proc "system" () -> i32 { context = default_context() - when !ODIN_BEDROCK { - #force_no_inline _startup_runtime() + when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { old_console_codepage = GetConsoleOutputCP() SetConsoleOutputCP(UTF_8) } intrinsics.__entry_point() - when !ODIN_BEDROCK { - #force_no_inline _cleanup_runtime() - SetConsoleOutputCP(old_console_codepage) - } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } else { @@ -83,16 +79,14 @@ when ODIN_BUILD_MODE == .Dynamic { main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { args__ = argv[:argc] context = default_context() - when !ODIN_BEDROCK { - #force_no_inline _startup_runtime() + when !ODIN_BEDROCK { #force_no_inline _startup_runtime() } + when ODIN_CODEPAGE_MAGIC { old_console_codepage = GetConsoleOutputCP() SetConsoleOutputCP(UTF_8) } intrinsics.__entry_point() - when !ODIN_BEDROCK { - #force_no_inline _cleanup_runtime() - SetConsoleOutputCP(old_console_codepage) - } + when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() } + when ODIN_CODEPAGE_MAGIC { SetConsoleOutputCP(old_console_codepage) } return 0 } } From c63561b700b954717456cc4a73db79add35e1a13 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 16 Aug 2026 17:50:49 +0200 Subject: [PATCH 88/93] Also restore codepage on trap. --- base/runtime/core.odin | 20 ++++++++++++++++++++ base/runtime/entry_windows.odin | 17 ----------------- base/runtime/procs_windows_amd64.odin | 5 +++-- base/runtime/procs_windows_i386.odin | 4 +++- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 41d620bdd..0453583c0 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -945,5 +945,25 @@ default_assertion_contextless_failure_proc :: proc "contextless" (prefix, messag print_byte('\n') } } + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } trap() } + +ODIN_CODEPAGE_MAGIC :: ODIN_OS == .Windows && #config(ODIN_CODEPAGE_MAGIC, !ODIN_BEDROCK) + +when ODIN_CODEPAGE_MAGIC { + @(private) + old_console_codepage: u32 + + @(private) + UTF_8 :: 65001 +} + +foreign import kernel32 "system:Kernel32.lib" +@(default_calling_convention="system") +foreign kernel32 { + GetConsoleOutputCP :: proc() -> u32 --- + SetConsoleOutputCP :: proc(codepage: u32) -> b32 --- +} \ No newline at end of file diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 119d50b54..5e7753d6a 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -5,16 +5,6 @@ package runtime import "base:intrinsics" -ODIN_CODEPAGE_MAGIC :: #config(ODIN_CODEPAGE_MAGIC, !ODIN_BEDROCK) - -when ODIN_CODEPAGE_MAGIC { - @(private="file") - old_console_codepage: u32 - - @(private="file") - UTF_8 :: 65001 -} - when ODIN_BUILD_MODE == .Dynamic { @(link_name="DllMain", linkage="strong", require) DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { @@ -90,11 +80,4 @@ when ODIN_BUILD_MODE == .Dynamic { return 0 } } -} - -foreign import kernel32 "system:Kernel32.lib" -@(default_calling_convention="system") -foreign kernel32 { - GetConsoleOutputCP :: proc() -> u32 --- - SetConsoleOutputCP :: proc(codepage: u32) -> b32 --- } \ No newline at end of file diff --git a/base/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin index 81d2cfb5d..cedbc2bd8 100644 --- a/base/runtime/procs_windows_amd64.odin +++ b/base/runtime/procs_windows_amd64.odin @@ -11,8 +11,9 @@ foreign kernel32 { windows_trap_array_bounds :: proc "contextless" () -> ! { EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C - - + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) } diff --git a/base/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin index 99c314228..9dd97116d 100644 --- a/base/runtime/procs_windows_i386.odin +++ b/base/runtime/procs_windows_i386.odin @@ -15,7 +15,9 @@ windows_trap_array_bounds :: proc "contextless" () -> ! { foreign kernel32 { RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- } - + when ODIN_CODEPAGE_MAGIC { + SetConsoleOutputCP(old_console_codepage) + } RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) } From a9b7d3dfc638f7dbdbc20be1c74283a652c2c742 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 16 Aug 2026 18:08:17 +0200 Subject: [PATCH 89/93] Fix #7350 --- core/os/dir_windows.odin | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 3fd045560..9d245f6ad 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -16,8 +16,15 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al } temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return + filename := win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else "" + pieces: []string + if len(base_path) > 0 && is_path_separator(base_path[len(base_path) - 1]) { + pieces = {base_path, filename} + } else { + pieces = {base_path, Path_Separator_String, filename} + } + path := concatenate(pieces, allocator) or_return handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0) defer win32.CloseHandle(handle) From 4a08f5c48a66d51d97ebe60efac5404e7629b7c6 Mon Sep 17 00:00:00 2001 From: Samuel Elgozi Date: Mon, 17 Aug 2026 00:21:50 +0300 Subject: [PATCH 90/93] mem: Preserve alignment when reusing Dynamic_Arena blocks --- core/mem/allocators.odin | 6 +-- tests/core/mem/test_mem_dynamic_arena.odin | 53 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index dcd126230..92e95ea2c 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -1813,7 +1813,7 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, align } memory := align_forward(a.current_pos, uintptr(actual_alignment)) margin := int(uintptr(memory) - uintptr(a.current_pos)) - if a.bytes_left < margin + n { + for a.bytes_left < margin + n { err := _dynamic_arena_cycle_new_block(a, alignment, loc) if err != nil { return nil, err @@ -1821,8 +1821,8 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, align if a.current_block == nil { return nil, .Out_Of_Memory } - margin = 0 - memory = a.current_pos + memory = align_forward(a.current_pos, uintptr(actual_alignment)) + margin = int(uintptr(memory) - uintptr(a.current_pos)) } a.current_pos = ([^]byte)(memory)[n:] a.bytes_left -= margin + n diff --git a/tests/core/mem/test_mem_dynamic_arena.odin b/tests/core/mem/test_mem_dynamic_arena.odin index 7d2b32f1a..81059cb5e 100644 --- a/tests/core/mem/test_mem_dynamic_arena.odin +++ b/tests/core/mem/test_mem_dynamic_arena.odin @@ -74,6 +74,59 @@ test_dynamic_arena_alloc_unaligned :: proc(t: ^testing.T) { expect_arena_allocation(t, expected_used_bytes = 16, num_bytes = 9, alignment = 8) } +expect_reused_block_alignment :: proc( + t: ^testing.T, + backing: []byte, + block_size: int, + should_reuse: bool, +) { + block_arena: mem.Arena + mem.arena_init(&block_arena, backing) + + arena: mem.Dynamic_Arena + mem.dynamic_arena_init( + &arena, + block_allocator = mem.arena_allocator(&block_arena), + block_size = block_size, + out_band_size = block_size + 1, + ) + defer mem.dynamic_arena_destroy(&arena) + + allocator := mem.dynamic_arena_allocator(&arena) + first, first_err := mem.alloc(1, 16, allocator) + testing.expect_value(t, first_err, mem.Allocator_Error.None) + testing.expect(t, uintptr(first) % 64 == 16) + + mem.dynamic_arena_reset(&arena) + + aligned, aligned_err := mem.alloc(1, 64, allocator) + testing.expect_value(t, aligned_err, mem.Allocator_Error.None) + testing.expect(t, uintptr(aligned) % 64 == 0) + testing.expect_value(t, arena.current_block == first, should_reuse) +} + +@(test) +test_dynamic_arena_realigns_reused_block :: proc(t: ^testing.T) { + Backing :: struct #align(64) { + _: [16]byte, + data: [128]byte, + } + + backing: Backing + expect_reused_block_alignment(t, backing.data[:], 128, true) +} + +@(test) +test_dynamic_arena_skips_incompatible_reused_block :: proc(t: ^testing.T) { + Backing :: struct #align(64) { + _: [16]byte, + data: [176]byte, + } + + backing: Backing + expect_reused_block_alignment(t, backing.data[:], 64, false) +} + @(test) test_dynamic_arena_alloc_out_of_band :: proc(t: ^testing.T) { expect_arena_allocation_out_of_band(t, num_bytes = 128, block_size = 512, out_band_size = 128) From 35061af11bf13f0b28dfbeb69c9c1274fad9d544 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 17:31:59 -0700 Subject: [PATCH 91/93] fix eol diagnostic --- src/error.cpp | 6 ++++-- src/parser.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index 53bc01654..ee2d79c27 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -432,8 +432,10 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { window_close_bytes = line_length_bytes; } - for (i32 i = error_start_index_graphemes; i > 0; i -= 1) { - if (graphemes[i].byte_index == window_open_bytes) { + // counts the graphemes before the error. It must not read the one at it: an error at the + // end of a line indexes one past the last grapheme + for (i32 i = error_start_index_graphemes-1; i >= 0; i -= 1) { + if (graphemes[i].byte_index < window_open_bytes) { break; } squiggle_padding += graphemes[i].width; diff --git a/src/parser.cpp b/src/parser.cpp index d981491dd..f8d5cb1b8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -113,6 +113,12 @@ gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset_) if (len < offset) { return nullptr; } + + // a column past the first cannot belong to a line the offset has already left + if (offset > 0 && pos.column > 1 && start[offset-1] == '\n') { + offset -= 1; + } + u8 *pos_offset = start+offset; u8 *line_start = pos_offset; From 55d0aecd924c1c29cb3833a680bf8feb73368f27 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 19:51:27 -0700 Subject: [PATCH 92/93] add missing break on fix dyn --- src/checker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/checker.cpp b/src/checker.cpp index 23ac59a3e..a9f22e99a 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2682,6 +2682,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, alloc_type_pointer(bt->FixedCapacityDynamicArray.elem)); add_min_dep_type_info(c, alloc_type_array(bt->FixedCapacityDynamicArray.elem, bt->FixedCapacityDynamicArray.capacity)); add_min_dep_type_info(c, t_int); + break; case Type_Enum: add_min_dep_type_info(c, bt->Enum.base_type); From 72d1829fec587a41150963015769317934059fc5 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 16 Aug 2026 22:08:52 -0700 Subject: [PATCH 93/93] #any_int range check constants --- src/check_expr.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index aef69fff7..5dee0cda4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6917,7 +6917,12 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A bool ok = false; if (e && (e->flags & EntityFlag_AnyInt)) { if (o->mode != Addressing_Type && is_type_integer(param_type) && (is_type_integer(o->type) || is_type_enum(o->type))) { - ok = check_is_castable_to(c, o, param_type); + if (o->mode == Addressing_Constant) { + // constants have to fit the parameter + ok = check_representable_as_constant(c, o->value, param_type, &o->value); + } else { + 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)) {