Merge branch 'flow/sx-foundation/F0.3' into dist-foundation
This commit is contained in:
57
examples/0136-types-global-array-element-store.sx
Normal file
57
examples/0136-types-global-array-element-store.sx
Normal file
@@ -0,0 +1,57 @@
|
||||
// A store to a module-global array element writes the global's live storage,
|
||||
// so a subsequent read sees the stored value — not the array initializer.
|
||||
// Covers constant index, variable index, and a cross-function store, on a
|
||||
// scalar global array, a struct-element global array (element-stride), and a
|
||||
// nested-array global (recursive lvalue).
|
||||
// Regression (issue 0079): global-array element stores were silently dropped
|
||||
// (read returned the initializer) because the indexed lvalue base loaded the
|
||||
// global by value into a temp instead of addressing the global's storage.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
g : [3]s64 = .[10, 20, 30];
|
||||
|
||||
Pair :: struct { a: s64; b: s64; }
|
||||
gp : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
|
||||
|
||||
grid : [2][3]s64 = .[ .[0, 0, 0], .[0, 0, 0] ];
|
||||
|
||||
write_global :: (i: s64, v: s64) { g[i] = v; }
|
||||
|
||||
main :: () {
|
||||
// Scalar global array — const index.
|
||||
g[1] = 222;
|
||||
print("g[1]={}\n", g[1]); // 222
|
||||
|
||||
// Scalar global array — variable index.
|
||||
k := 2;
|
||||
g[k] = 333;
|
||||
print("g[k]={}\n", g[k]); // 333
|
||||
|
||||
// Scalar global array — store from another function.
|
||||
write_global(0, 111);
|
||||
print("g[0]={}\n", g[0]); // 111
|
||||
|
||||
// Struct-element global array (16-byte stride) — const and var index.
|
||||
gp[0] = .{ a = 10, b = 20 };
|
||||
j := 1;
|
||||
gp[j] = .{ a = 30, b = 40 };
|
||||
print("gp[0]={},{}\n", gp[0].a, gp[0].b); // 10,20
|
||||
print("gp[j]={},{}\n", gp[j].a, gp[j].b); // 30,40
|
||||
|
||||
// Nested-array global — element is [3]s64, recursive indexed lvalue.
|
||||
grid[1][2] = 7;
|
||||
r := 0;
|
||||
grid[r][0] = 5;
|
||||
print("grid[1][2]={}\n", grid[1][2]); // 7
|
||||
print("grid[0][0]={}\n", grid[r][0]); // 5
|
||||
|
||||
if g[1] == 222 and g[2] == 333 and g[0] == 111
|
||||
and gp[0].a == 10 and gp[0].b == 20
|
||||
and gp[1].a == 30 and gp[1].b == 40
|
||||
and grid[1][2] == 7 and grid[0][0] == 5 {
|
||||
print("PASS\n");
|
||||
} else {
|
||||
print("FAIL: global array element store dropped\n");
|
||||
}
|
||||
}
|
||||
49
examples/0137-types-global-aggregate-literal-init.sx
Normal file
49
examples/0137-types-global-aggregate-literal-init.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
// A module-global aggregate (array of struct literals, a struct literal, and
|
||||
// nested array/struct shapes) materializes its DECLARED field values into the
|
||||
// global's static initializer, so reading the fields without any prior store
|
||||
// returns the literal values — not zero.
|
||||
// Regression (issue 0080): a global `[N]Struct` initialized with struct literals
|
||||
// was emitted as `zeroinitializer`, silently dropping every field, because the
|
||||
// constant-aggregate serializer had no struct-literal arm and collapsed the
|
||||
// whole initializer to null. The fix threads the element/field type so struct
|
||||
// and nested-array leaves serialize correctly; a genuinely non-constant
|
||||
// initializer is now rejected loudly instead of silently zeroed.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Pair :: struct { a: s64; b: s64; }
|
||||
WithArr :: struct { id: s64; xs: [3]s64; }
|
||||
|
||||
// global array of struct literals
|
||||
pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
|
||||
// global struct literal
|
||||
solo : Pair = .{ a = 7, b = 9 };
|
||||
// global struct containing a fixed array (struct-with-array)
|
||||
wa : WithArr = .{ id = 5, xs = .[ 11, 22, 33 ] };
|
||||
// nested: global array of structs each containing an array
|
||||
nested : [2]WithArr = .[ .{ id = 1, xs = .[ 1, 2, 3 ] }, .{ id = 2, xs = .[ 4, 5, 6 ] } ];
|
||||
|
||||
main :: () {
|
||||
// Read the declared initializer values back with NO prior store.
|
||||
print("pairs={},{} {},{}\n", pairs[0].a, pairs[0].b, pairs[1].a, pairs[1].b);
|
||||
print("solo={},{}\n", solo.a, solo.b);
|
||||
print("wa={} xs={},{},{}\n", wa.id, wa.xs[0], wa.xs[1], wa.xs[2]);
|
||||
print("nested0={} xs={},{},{}\n", nested[0].id, nested[0].xs[0], nested[0].xs[1], nested[0].xs[2]);
|
||||
print("nested1={} xs={},{},{}\n", nested[1].id, nested[1].xs[0], nested[1].xs[1], nested[1].xs[2]);
|
||||
|
||||
// A store on top of the materialized initializer still works (live storage).
|
||||
pairs[0].a = 100;
|
||||
nested[1].xs[2] = 999;
|
||||
print("after-store={} {}\n", pairs[0].a, nested[1].xs[2]);
|
||||
|
||||
if pairs[0].b == 2 and pairs[1].a == 3 and pairs[1].b == 4
|
||||
and solo.a == 7 and solo.b == 9
|
||||
and wa.id == 5 and wa.xs[0] == 11 and wa.xs[2] == 33
|
||||
and nested[0].id == 1 and nested[0].xs[0] == 1 and nested[0].xs[2] == 3
|
||||
and nested[1].id == 2 and nested[1].xs[0] == 4
|
||||
and pairs[0].a == 100 and nested[1].xs[2] == 999 {
|
||||
print("PASS\n");
|
||||
} else {
|
||||
print("FAIL: global aggregate literal initializer zeroed\n");
|
||||
}
|
||||
}
|
||||
48
examples/0138-types-global-aggregate-null-pointer-field.sx
Normal file
48
examples/0138-types-global-aggregate-null-pointer-field.sx
Normal file
@@ -0,0 +1,48 @@
|
||||
// A module-global aggregate initializer may carry `null` in a pointer field:
|
||||
// `null` is a compile-time constant (the zero pointer), so the field reads back
|
||||
// as null with NO prior store, and its non-pointer neighbors keep their declared
|
||||
// values. Covered shapes: an array-of-struct with a null pointer field, a global
|
||||
// array of all-null pointers, and a nested struct-in-struct with a null pointer.
|
||||
// Regression (issue 0081): the constant-aggregate serializer had no
|
||||
// `.null_literal` arm, so a `null` in a pointer field made the whole aggregate
|
||||
// look non-constant and the global was rejected with "must be initialized by a
|
||||
// compile-time constant". The fix serializes a null literal to a constant zero
|
||||
// pointer (the same way a top-level pointer global `p : *s64 = null;` does)
|
||||
// while still rejecting genuinely non-constant fields (see diagnostics 1126).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct { p: *s64; marker: s64; }
|
||||
Inner :: struct { q: *s64; tag: s64; }
|
||||
Outer :: struct { inner: Inner; label: s64; }
|
||||
|
||||
// array-of-struct with null pointer fields + scalar neighbors
|
||||
boxes : [2]Box = .[ .{ p = null, marker = 11 }, .{ p = null, marker = 22 } ];
|
||||
// global array of all-null pointers
|
||||
ptrs : [3]*s64 = .[ null, null, null ];
|
||||
// nested: struct containing a struct with a null pointer field
|
||||
nested : [2]Outer = .[
|
||||
.{ inner = .{ q = null, tag = 1 }, label = 100 },
|
||||
.{ inner = .{ q = null, tag = 2 }, label = 200 },
|
||||
];
|
||||
|
||||
main :: () {
|
||||
print("boxes ptrs={},{} markers={},{}\n",
|
||||
boxes[0].p == null, boxes[1].p == null, boxes[0].marker, boxes[1].marker);
|
||||
print("ptr arr nulls={},{},{}\n", ptrs[0] == null, ptrs[1] == null, ptrs[2] == null);
|
||||
print("nested q nulls={},{} tags={},{} labels={},{}\n",
|
||||
nested[0].inner.q == null, nested[1].inner.q == null,
|
||||
nested[0].inner.tag, nested[1].inner.tag,
|
||||
nested[0].label, nested[1].label);
|
||||
|
||||
if boxes[0].p == null and boxes[1].p == null
|
||||
and boxes[0].marker == 11 and boxes[1].marker == 22
|
||||
and ptrs[0] == null and ptrs[1] == null and ptrs[2] == null
|
||||
and nested[0].inner.q == null and nested[1].inner.q == null
|
||||
and nested[0].inner.tag == 1 and nested[1].inner.tag == 2
|
||||
and nested[0].label == 100 and nested[1].label == 200 {
|
||||
print("PASS\n");
|
||||
} else {
|
||||
print("FAIL: global aggregate null pointer field mis-serialized\n");
|
||||
}
|
||||
}
|
||||
51
examples/0139-types-global-enum-literal-init.sx
Normal file
51
examples/0139-types-global-enum-literal-init.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
// A module-global initialized with an enum literal (`.Variant`) reads back the
|
||||
// declared tag — scalar, inside a global array, and as a struct field, for both
|
||||
// a plain enum (tag == declaration index) and an explicit-value enum (`enum u16
|
||||
// { ok :: 200; ... }`, larger backing for element-stride coverage).
|
||||
// Regression (issue 0082): `globalInitValue` had an `.enum_literal => null`
|
||||
// carve-out (kept for the compiler-injected `OS`/`ARCH` globals) that silently
|
||||
// zero-initialized EVERY enum global to the first tag — so `chosen : Color =
|
||||
// .green` read back as `.red` — while a global array/struct of enums was
|
||||
// rejected outright as non-constant. The fix serializes the enum literal to its
|
||||
// tag value (respecting explicit variant values) against the destination enum
|
||||
// type, for the scalar global, the array element, and the nested aggregate
|
||||
// field. (Explicit-value enums print as `.` because the `{}` formatter indexes
|
||||
// variants by position — a separate, pre-existing limitation — so those are
|
||||
// asserted by equality, not by their printed name.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum u8 { red; green; blue; }
|
||||
Code :: enum u16 { ok :: 200; not_found :: 404; teapot :: 418; }
|
||||
|
||||
Pair :: struct { a: Color; b: Color; }
|
||||
Row :: struct { status: Code; pad: s64; }
|
||||
|
||||
// scalar enum global
|
||||
chosen : Color = .green;
|
||||
// global array of enum
|
||||
palette : [3]Color = .[ .blue, .green, .red ];
|
||||
// enum field(s) inside a global struct
|
||||
pair : Pair = .{ a = .blue, b = .green };
|
||||
// explicit-value enum: scalar, array (2-byte stride), and inside a struct array
|
||||
status : Code = .teapot;
|
||||
codes : [3]Code = .[ .ok, .not_found, .teapot ];
|
||||
rows : [2]Row = .[ .{ status = .not_found, pad = 11 }, .{ status = .teapot, pad = 22 } ];
|
||||
|
||||
main :: () {
|
||||
print("chosen={}\n", chosen);
|
||||
print("palette={},{},{}\n", palette[0], palette[1], palette[2]);
|
||||
print("pair.a={} pair.b={}\n", pair.a, pair.b);
|
||||
|
||||
if chosen == .green
|
||||
and palette[0] == .blue and palette[1] == .green and palette[2] == .red
|
||||
and pair.a == .blue and pair.b == .green
|
||||
and status == .teapot
|
||||
and codes[0] == .ok and codes[1] == .not_found and codes[2] == .teapot
|
||||
and rows[0].status == .not_found and rows[0].pad == 11
|
||||
and rows[1].status == .teapot and rows[1].pad == 22 {
|
||||
print("PASS\n");
|
||||
} else {
|
||||
print("FAIL: global enum-literal initializer mis-serialized\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// A module-global aggregate with a NULL pointer field is fine (null is a
|
||||
// compile-time constant), but a sibling field initialized from a NON-constant
|
||||
// expression (here a runtime function call) must still be rejected loudly. The
|
||||
// presence of an accepted `null` must NOT widen the gate to admit the
|
||||
// non-constant neighbor.
|
||||
// Regression (issue 0081): the null-pointer fix must not regress the
|
||||
// reject-loud behavior for genuinely non-constant initializers (issues
|
||||
// 0072/0080). Expected: "global 'boxes' must be initialized by a compile-time
|
||||
// constant"; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
runtime_marker :: () -> s64 { return 7; }
|
||||
|
||||
Box :: struct { p: *s64; marker: s64; }
|
||||
boxes : [1]Box = .[ .{ p = null, marker = runtime_marker() } ];
|
||||
|
||||
main :: () -> s32 {
|
||||
print("marker={}\n", boxes[0].marker);
|
||||
return 0;
|
||||
}
|
||||
16
examples/1127-diagnostics-global-enum-literal-bad-variant.sx
Normal file
16
examples/1127-diagnostics-global-enum-literal-bad-variant.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// A module-global enum-literal initializer naming a variant that does not exist
|
||||
// must be rejected loudly — never silently zero-initialized to the first tag.
|
||||
// Regression (issue 0082): the enum-literal global serializer resolves the tag
|
||||
// against the destination enum type; an unknown variant emits a diagnostic and
|
||||
// fails the build instead of falling back to a null (zero-tag) initializer.
|
||||
// Expected: "'.purple' is not a variant of enum 'Color'"; exit 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum u8 { red; green; blue; }
|
||||
bad : Color = .purple;
|
||||
|
||||
main :: () -> s32 {
|
||||
print("{}\n", bad);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// A comptime `#run` global initializer that yields a function reference cannot
|
||||
// be serialized to a static constant: at global-init time (Pass 0) functions
|
||||
// are not yet declared, and the comptime serialization path has no later
|
||||
// re-emit, so the func_ref can never resolve to a real function pointer. The
|
||||
// compiler must reject this with a diagnostic AND a CLEAN non-zero exit — never
|
||||
// print the error and then fall through into an undef initializer that crashes
|
||||
// (pre-fix: the diagnostic printed, emission continued, and the JIT segfaulted
|
||||
// calling through the undef pointer → exit 134).
|
||||
// Regression (issue 0079 follow-up): every global-init serialization bail now
|
||||
// routes through `failGlobalInit`, which sets the halt flag so the driver aborts
|
||||
// after emit() instead of shipping the placeholder.
|
||||
// Expected: "comptime init of 'fp' produced a reference to function 'add'…";
|
||||
// exit 1, no segfault.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
add :: (a: s32, b: s32) -> s32 { a + b }
|
||||
|
||||
pick :: () -> (s32, s32) -> s32 { return add; }
|
||||
|
||||
fp :: #run pick();
|
||||
|
||||
main :: () -> s32 {
|
||||
print("{}\n", fp(3, 4));
|
||||
return 0;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
g[1]=222
|
||||
g[k]=333
|
||||
g[0]=111
|
||||
gp[0]=10,20
|
||||
gp[j]=30,40
|
||||
grid[1][2]=7
|
||||
grid[0][0]=5
|
||||
PASS
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
pairs=1,2 3,4
|
||||
solo=7,9
|
||||
wa=5 xs=11,22,33
|
||||
nested0=1 xs=1,2,3
|
||||
nested1=2 xs=4,5,6
|
||||
after-store=100 999
|
||||
PASS
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
boxes ptrs=true,true markers=11,22
|
||||
ptr arr nulls=true,true,true
|
||||
nested q nulls=true,true tags=1,2 labels=100,200
|
||||
PASS
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
chosen=.green
|
||||
palette=.blue,.green,.red
|
||||
pair.a=.blue pair.b=.green
|
||||
PASS
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@__Counter__SimpleCounter__vtable = internal constant { ptr, ptr } { ptr @__thunk_SimpleCounter_Counter_inc, ptr @__thunk_SimpleCounter_Counter_get }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: global 'boxes' must be initialized by a compile-time constant
|
||||
--> examples/1126-diagnostics-global-aggregate-non-const-field-rejected.sx:16:18
|
||||
|
|
||||
16 | boxes : [1]Box = .[ .{ p = null, marker = runtime_marker() } ];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: '.purple' is not a variant of enum 'Color'
|
||||
--> examples/1127-diagnostics-global-enum-literal-bad-variant.sx:11:15
|
||||
|
|
||||
11 | bad : Color = .purple;
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
error: comptime init of 'fp' produced a reference to function 'add', which cannot be serialized as a static constant (function declarations are not available at global-init time)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
@__SxFoo_state_ivar = internal global ptr null
|
||||
@__SxFoo_class = internal global ptr null
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
@__SxFoo_state_ivar = internal global ptr null
|
||||
@__SxFoo_class = internal global ptr null
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@__sx_objc_cstr_dealloc = internal global [8 x i8] c"dealloc\00"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
@__SxBox_state_ivar = internal global ptr null
|
||||
@__SxBox_class = internal global ptr null
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@OBJC_CLASSLIST_REFERENCES_SxBox = internal global ptr null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@OBJC_SELECTOR_REFERENCES_init = internal global ptr null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@OBJC_SELECTOR_REFERENCES_tripleValue = internal global ptr null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@OBJC_SELECTOR_REFERENCES_length = internal global ptr null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@OS = internal global i64 6
|
||||
@ARCH = internal global i64 4
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@g_held_view = internal global ptr null
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
|
||||
102
issues/0079-global-array-element-store-dropped.md
Normal file
102
issues/0079-global-array-element-store-dropped.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 0079 — stores to module-global array elements are silently dropped
|
||||
|
||||
> **RESOLVED.**
|
||||
> **Root cause:** `Lowering.lowerExprAsPtr` (`src/ir/lower.zig`) — the lvalue/
|
||||
> address path — handled only *local* identifiers (alloca pointers). A
|
||||
> module-global identifier fell through to the value fallback `lowerExpr`,
|
||||
> which emits `global_get` (loads the whole array *by value*). The LLVM
|
||||
> backend's `emitIndexGep` then sees an array *value*, allocas a throwaway
|
||||
> temp, copies the value in, and GEPs into the temp — so `g[i] = v` wrote a
|
||||
> discarded copy and a later `g[i]` read the global's untouched initializer.
|
||||
> Local arrays worked because they hit the alloca-pointer path; global
|
||||
> *scalar* stores worked via `global_set`.
|
||||
> **Fix:** teach `lowerExprAsPtr`'s identifier arm about globals — emit
|
||||
> `global_addr` (a pointer into the global's live storage) for a normal
|
||||
> global, or `global_get` for a pointer-typed global (mirroring the local
|
||||
> pointer case). The same array-base resolution in the `address_of(index_expr)`
|
||||
> path now routes through `lowerExprAsPtr` so `&g[i]` is also an lvalue into
|
||||
> the global. `index_gep` then GEPs directly into `@g` for const AND variable
|
||||
> index, across functions, in both `sx run` and `sx build`.
|
||||
> **Regression:** `examples/0136-types-global-array-element-store.sx`
|
||||
> (const-index, var-index, cross-function store on a scalar global array; a
|
||||
> struct-element global array for element-stride; a nested-array global for the
|
||||
> recursive indexed lvalue). FAILS on the pre-fix compiler (reads return the
|
||||
> initializer / zero), PASSES after.
|
||||
|
||||
## Symptom
|
||||
|
||||
A store to a module-global (file-scope) ARRAY element is silently lost: after
|
||||
`g[i] = v`, reading `g[i]` returns the array's INITIALIZER value, not `v`. No
|
||||
diagnostic. Reproduces with a constant index, a variable index, and a store from
|
||||
another function, in BOTH `sx run` (JIT) and `sx build` (AOT). Local-array stores
|
||||
and global-SCALAR stores work correctly — so the indexed load/store on a *global*
|
||||
array appears to read/write the initializer constant rather than the global's
|
||||
live storage. This is a SILENT data-corruption bug (the dangerous class per the
|
||||
project rules), not a crash.
|
||||
|
||||
Manager-reproduced output (JIT):
|
||||
```
|
||||
global[1] const-idx=20 (want 222)
|
||||
global[k] var-idx=30 (want 333)
|
||||
global[0] via fn=10 (want 111)
|
||||
```
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
g : [3]s64 = .[10, 20, 30];
|
||||
write_global :: (i: s64, v: s64) { g[i] = v; }
|
||||
|
||||
main :: () {
|
||||
loc : [3]s64 = .[10, 20, 30];
|
||||
loc[1] = 222;
|
||||
print("local[1]={}\n", loc[1]); // 222 (correct)
|
||||
g[1] = 222;
|
||||
print("global[1] const-idx={}\n", g[1]); // 20 (WRONG, want 222)
|
||||
k := 2;
|
||||
g[k] = 333;
|
||||
print("global[k] var-idx={}\n", g[k]); // 30 (WRONG, want 333)
|
||||
write_global(0, 111);
|
||||
print("global[0] via fn={}\n", g[0]); // 10 (WRONG, want 111)
|
||||
}
|
||||
```
|
||||
|
||||
`./zig-out/bin/sx run <file>` → prints the WRONG values above. Expected:
|
||||
`local[1]=222 / global[1]=222 / global[k]=333 / global[0]=111`.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
A store to a module-global array element (`g[i] = v`) is silently lost: a
|
||||
subsequent `g[i]` yields the initializer value, in BOTH the JIT (`sx run`) and
|
||||
compiled (`sx build`) paths. Global SCALAR stores and LOCAL array stores both
|
||||
work, so the defect is specific to INDEXED lvalue access on a GLOBAL array.
|
||||
Suspect the IR-gen / codegen for an indexed expression on a global symbol:
|
||||
the load of `global_array[idx]` is likely decaying / constant-folding to the
|
||||
array's initializer constant (or the address computation for the store targets a
|
||||
private copy of the initializer rather than the global's live storage). Look in
|
||||
the index-expression lowering and global-symbol address resolution (and how
|
||||
global array initializers are materialized) — `src/ir/lower.zig` /
|
||||
`src/ir/emit_llvm.zig` and the global-symbol/constant-initializer paths.
|
||||
|
||||
The fix must make `load(global_array[idx])` read the global's live storage and
|
||||
`store(global_array[idx], v)` write it — for constant AND variable indices, and
|
||||
when the store happens in a different function than the read. Do NOT special-case;
|
||||
fix the address resolution so a global array element is an lvalue into the
|
||||
global's storage like any other.
|
||||
|
||||
## Verification (once fixed)
|
||||
- The repro above prints `222 / 333 / 111` for the global cases (exit 0).
|
||||
- Add a pinned regression `examples/NNNN-*.sx` covering const-index, var-index,
|
||||
and cross-function store to a global array (+ a global array of a struct/larger
|
||||
element if practical).
|
||||
- `zig build && zig build test && bash tests/run_examples.sh` all green.
|
||||
|
||||
## Provenance
|
||||
|
||||
Discovered by the `distribution` flow (sx-foundation step F3.1, std.cli argv
|
||||
accessor) while exploring argv backing strategies. The shipped F3.1 code does NOT
|
||||
depend on this (it uses caller-provided buffers + zero-copy views over the C argv
|
||||
block), so F3.1 is correct independently — this is a separate latent
|
||||
silent-miscompile surfaced per the STOP-on-compiler-bug rule.
|
||||
121
issues/0080-global-array-struct-literal-initializer-zero.md
Normal file
121
issues/0080-global-array-struct-literal-initializer-zero.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# 0080 - global array of struct literals silently zero-initializes
|
||||
|
||||
> **RESOLVED.**
|
||||
> **Root cause:** `Lowering.constExprValue` (`src/ir/lower.zig`) — the constant-
|
||||
> aggregate serializer for global initializers — handled primitive and nested-
|
||||
> array leaves but had **no `.struct_literal` arm**. A module-global `[N]Struct`
|
||||
> initialized with struct literals reached `constArrayLiteral` → `constExprValue`
|
||||
> per element; each struct-literal element returned `null`, collapsing the whole
|
||||
> array initializer to `null`. `globalInitValue` then emitted no payload, so the
|
||||
> LLVM backend zero-initialized the global (`@pairs = ... zeroinitializer`),
|
||||
> silently dropping every declared field — the same silent-zero class as
|
||||
> 0071/0072, one level inside an array literal. (A global *struct* literal and a
|
||||
> *struct-with-array* already worked, because `constStructLiteral` existed and was
|
||||
> reached directly; the gap was specifically struct literals *as array elements*.)
|
||||
> **Fix:** make `constExprValue` type-aware — thread the destination element/field
|
||||
> `TypeId` so a `.struct_literal` leaf routes through `constStructLiteral` and a
|
||||
> nested `.array_literal` through `constArrayLiteral` with the correct element
|
||||
> type. `constArrayLiteral` derives its element type from the array `TypeId`;
|
||||
> `constStructLiteral` passes each field's type. A global aggregate initializer
|
||||
> that still does not fully reduce to a compile-time constant is now **rejected
|
||||
> loudly** (`diagnoseNonConstGlobal`) instead of falling through to a zeroed
|
||||
> global. The downstream `emitConstAggregate` already recurses over nested
|
||||
> aggregates, so const/AOT (`sx build`) and JIT (`sx run`) both materialize the
|
||||
> declared values.
|
||||
> **Regression:** `examples/0137-types-global-aggregate-literal-init.sx` (global
|
||||
> `[N]Struct` literal, global struct literal, struct-with-array, nested array-of-
|
||||
> struct-with-array; values read back with no prior store, plus a store on top).
|
||||
> FAILS on the pre-fix compiler (array-of-struct fields read 0), PASSES after.
|
||||
|
||||
## Symptom
|
||||
|
||||
A module-global fixed array whose elements are struct literals is emitted as
|
||||
zero-initialized storage instead of preserving the literal fields.
|
||||
|
||||
Observed: reading `pairs[0].b` and `pairs[1].a` prints `0`.
|
||||
Expected: the global should contain the declared struct literal values
|
||||
(`2` and `3`), or the compiler should reject the initializer loudly if this
|
||||
constant shape is unsupported.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Pair :: struct {
|
||||
a: s64;
|
||||
b: s64;
|
||||
}
|
||||
|
||||
pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
|
||||
|
||||
main :: () -> s32 {
|
||||
print("pairs[0]={},{}\n", pairs[0].a, pairs[0].b);
|
||||
print("pairs[1]={},{}\n", pairs[1].a, pairs[1].b);
|
||||
if pairs[0].a == 1 and pairs[0].b == 2 and pairs[1].a == 3 and pairs[1].b == 4 {
|
||||
print("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
print("FAIL: global array struct literal initializer zeroed\n");
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
On the current compiler this prints:
|
||||
|
||||
```text
|
||||
pairs[0]=0,0
|
||||
pairs[1]=0,0
|
||||
FAIL: global array struct literal initializer zeroed
|
||||
```
|
||||
|
||||
`sx ir <file>` shows the global as:
|
||||
|
||||
```llvm
|
||||
@pairs = internal global [2 x { i64, i64 }] zeroinitializer
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
Fix issue 0080: a module-global array initialized with struct literal elements
|
||||
silently becomes `zeroinitializer`.
|
||||
|
||||
Suspected area:
|
||||
- `src/ir/lower.zig`, `Lowering.globalInitValue`.
|
||||
- `src/ir/lower.zig`, `Lowering.constArrayLiteral`.
|
||||
- `src/ir/lower.zig`, `Lowering.constExprValue`.
|
||||
- `src/ir/lower.zig`, `Lowering.constStructLiteral`.
|
||||
|
||||
Likely root cause: `globalInitValue` handles a top-level `.array_literal` by
|
||||
calling `constArrayLiteral`, and `constArrayLiteral` serializes each element via
|
||||
`constExprValue`. `constExprValue` handles primitive literals and nested arrays,
|
||||
but not `.struct_literal`, so an array whose element is a struct literal returns
|
||||
`null`. That null initializer payload is later emitted as zero-initialized
|
||||
storage, recreating the silent zero pattern from issues 0071/0072 one level
|
||||
inside an otherwise-supported array literal.
|
||||
|
||||
Likely fix:
|
||||
- Thread the expected element `TypeId` into `constArrayLiteral`, or otherwise
|
||||
make `constExprValue` type-aware for struct literals.
|
||||
- Serialize each struct element through `constStructLiteral` with the array's
|
||||
element type.
|
||||
- If any element shape is still unsupported, emit a diagnostic naming the global
|
||||
instead of returning `null` and allowing zero-initialization.
|
||||
|
||||
Verification:
|
||||
- Run the repro above and expect:
|
||||
|
||||
```text
|
||||
pairs[0]=1,2
|
||||
pairs[1]=3,4
|
||||
PASS
|
||||
```
|
||||
|
||||
- Add a pinned regression in the `01xx` types block.
|
||||
- Run:
|
||||
|
||||
```sh
|
||||
zig build
|
||||
zig build test
|
||||
bash tests/run_examples.sh
|
||||
```
|
||||
116
issues/0081-global-aggregate-null-literal-rejected.md
Normal file
116
issues/0081-global-aggregate-null-literal-rejected.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 0081 - global aggregate null literal rejected as non-constant
|
||||
|
||||
> **RESOLVED.**
|
||||
> **Root cause:** `Lowering.constExprValue` (`src/ir/lower.zig`) — the
|
||||
> constant-aggregate serializer for global initializers — had no
|
||||
> `.null_literal` arm. A `null` in a pointer (or optional-pointer) field
|
||||
> therefore returned no constant, which propagated up through
|
||||
> `constStructLiteral` / `constArrayLiteral` and made the whole aggregate look
|
||||
> non-constant, so `globalInitValue` rejected it with "must be initialized by a
|
||||
> compile-time constant". A `null` is a compile-time constant (the zero
|
||||
> pointer) and a top-level scalar pointer global (`p : *s64 = null;`) already
|
||||
> serialized fine — only the nested-aggregate path was wrong.
|
||||
> **Fix:** add `.null_literal => .null_val` to `constExprValue` so a null leaf
|
||||
> serializes to a constant zero pointer. Made the LLVM constant emitters
|
||||
> exhaustive while at it: `emitConstAggregate` and the top-level `init_val`
|
||||
> switch in `src/ir/emit_llvm.zig` previously ended in a silent
|
||||
> `else => LLVMConstNull(...)` catch-all (the precise silent-arm class CLAUDE.md
|
||||
> mandates rooting out); they now handle every `ConstantValue` tag explicitly
|
||||
> (`.null_val`/`.zeroinit` → all-zero constant, `.undef` → `LLVMGetUndef`,
|
||||
> `.func_ref` resolved, nested `.vtable` is a hard `@panic` tripwire since
|
||||
> vtables are top-level-only). The reject-loud path for genuinely non-constant
|
||||
> fields (a runtime call, etc.) is preserved.
|
||||
> **Regression:** `examples/0138-types-global-aggregate-null-pointer-field.sx`
|
||||
> (array-of-struct with null pointer fields, global array of all-null pointers,
|
||||
> nested struct-in-struct null pointer — asserts null reads + correct neighbors)
|
||||
> and the negative `examples/1126-diagnostics-global-aggregate-non-const-field-rejected.sx`
|
||||
> (a null pointer field beside a non-constant field still errors loudly).
|
||||
> Verified fail-before (pre-fix rejects 0138) / pass-after.
|
||||
|
||||
## Symptom
|
||||
|
||||
A module-global aggregate initializer rejects a `null` literal in a pointer
|
||||
field as "not a compile-time constant"; expected the null pointer to serialize
|
||||
as a constant zero pointer the same way a top-level pointer global does.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct {
|
||||
p: *s64;
|
||||
marker: s64;
|
||||
}
|
||||
|
||||
boxes : [2]Box = .[
|
||||
.{ p = null, marker = 11 },
|
||||
.{ p = null, marker = 22 },
|
||||
];
|
||||
|
||||
main :: () -> s32 {
|
||||
print("ptrs={} {} markers={} {}\n",
|
||||
boxes[0].p == null,
|
||||
boxes[1].p == null,
|
||||
boxes[0].marker,
|
||||
boxes[1].marker);
|
||||
if boxes[0].p == null and boxes[1].p == null and boxes[0].marker == 11 and boxes[1].marker == 22 {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
```text
|
||||
error: global 'boxes' must be initialized by a compile-time constant
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
ptrs=true true markers=11 22
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
Fix issue 0081: module-global aggregate initializers reject `null` literals in
|
||||
pointer fields even though `null` is a compile-time constant pointer value.
|
||||
|
||||
Suspected area:
|
||||
- `src/ir/lower.zig`, `Lowering.constExprValue` — the switch has no
|
||||
`.null_literal` arm, so `constStructLiteral` treats a pointer field initialized
|
||||
with `null` as non-constant and `globalInitValue` reports the whole aggregate.
|
||||
- `src/ir/emit_llvm.zig`, top-level `global.init_val` emission and
|
||||
`LLVMEmitter.emitConstAggregate` — both currently rely on catch-all
|
||||
`else => LLVMConstNull(...)` for several `ConstantValue` tags. If `.null_val`
|
||||
is threaded through aggregate constants, add explicit `.null_val` handling
|
||||
there (and explicit `.zeroinit` / `.undef` handling as appropriate) rather
|
||||
than depending on the catch-all.
|
||||
|
||||
Likely fix:
|
||||
- Add `.null_literal => .null_val` to `constExprValue` for constant aggregate
|
||||
serialization.
|
||||
- Ensure LLVM constant emission handles `.null_val` explicitly for both
|
||||
top-level constants and nested aggregate leaves.
|
||||
- Keep unsupported aggregate expressions loud: non-constant calls/field-accesses
|
||||
should still diagnose instead of zero-initializing.
|
||||
|
||||
Verification:
|
||||
- Run the repro above and expect:
|
||||
|
||||
```text
|
||||
ptrs=true true markers=11 22
|
||||
```
|
||||
|
||||
- Add a pinned regression in the `01xx` types block covering a global
|
||||
array-of-struct with pointer-null fields (and, if straightforward, optional
|
||||
null fields too).
|
||||
- Run:
|
||||
|
||||
```sh
|
||||
zig build
|
||||
zig build test
|
||||
bash tests/run_examples.sh
|
||||
```
|
||||
110
issues/0082-global-enum-literal-initializer-zeroes.md
Normal file
110
issues/0082-global-enum-literal-initializer-zeroes.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 0082 - global enum-literal initializer silently zero-initializes
|
||||
|
||||
> **RESOLVED.**
|
||||
> **Root cause:** `Lowering.globalInitValue` (`src/ir/lower.zig`) carried an
|
||||
> `.enum_literal => null` carve-out: any enum-literal global initializer returned
|
||||
> a null payload, which the LLVM/interp emitters turn into a zero-initialized
|
||||
> global — so `chosen : Color = .green` read back as the first tag (`.red`).
|
||||
> `constExprValue` had no enum-literal arm either, so an enum tag inside a global
|
||||
> array (`[2]Color = .[.green, .blue]`) or struct field made the whole aggregate
|
||||
> look non-constant and the global was rejected outright.
|
||||
> **Fix:** a new `Lowering.constEnumLiteral` serializes an enum literal to a
|
||||
> `ConstantValue.int` holding the variant's tag value, resolved against the
|
||||
> destination enum type and respecting explicit variant values (`enum { a; b ::
|
||||
> 5; }`); the global's type drives the backing width at emit time. Wired into both
|
||||
> `globalInitValue` (scalar global) and `constExprValue` (array element / struct
|
||||
> field / nested aggregate). A non-enum destination or an unknown variant is
|
||||
> diagnosed loudly — never silently zero-initialized. The compiler-injected
|
||||
> `OS`/`ARCH` globals now serialize to their real `.unknown` tag (6 / 4) instead
|
||||
> of relying on the null→zero fallback; runtime reads are unchanged because they
|
||||
> resolve through `comptime_constants`. As part of the same exhaustiveness pass,
|
||||
> the silent `func_ref => … orelse LLVMConstNull` fallbacks in the LLVM constant
|
||||
> emitters (`src/ir/emit_llvm.zig`) were removed: aggregate func_ref leaves carry
|
||||
> a `require_resolved` flag (transient null in Pass 0, loud diagnostic if still
|
||||
> unresolved in the Pass-1.5 re-emit), a top-level func_ref global is resolved in
|
||||
> `initVtableGlobals`, and the comptime (`#run`) path bails loudly instead of
|
||||
> emitting a null function pointer.
|
||||
> **Regression:** `examples/0139-types-global-enum-literal-init.sx` (scalar enum
|
||||
> global, global array of enum, enum struct field, explicit-value `enum u16` for
|
||||
> element-stride, struct-array with enum field) — FAILS on the pre-fix compiler
|
||||
> (wrong tag / rejected as non-constant), PASSES after. Negative:
|
||||
> `examples/1127-diagnostics-global-enum-literal-bad-variant.sx` (unknown variant
|
||||
> rejected loudly, exit 1).
|
||||
|
||||
## Symptom
|
||||
|
||||
A module-global enum initialized with a non-zero enum literal silently reads back
|
||||
as the zero tag. Observed: `chosen : Color = .green;` prints `.red` and the
|
||||
program exits 1. Expected: it should print `.green` and exit 0, or the compiler
|
||||
should reject unsupported enum-literal global initializers loudly instead of
|
||||
zero-initializing.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum u8 { red; green; blue; }
|
||||
|
||||
chosen : Color = .green;
|
||||
|
||||
main :: () -> s32 {
|
||||
print("chosen={}\n", chosen);
|
||||
if chosen == .green {
|
||||
print("PASS\n");
|
||||
return 0;
|
||||
}
|
||||
print("FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
```text
|
||||
chosen=.red
|
||||
FAIL
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
chosen=.green
|
||||
PASS
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
Fix issue 0082: module-global enum literal initializers silently become the zero
|
||||
tag.
|
||||
|
||||
Suspected area:
|
||||
- `src/ir/lower.zig`, `Lowering.globalInitValue`: the `.enum_literal => null`
|
||||
carve-out preserves the stdlib's historical zero-init path for compiler-
|
||||
injected `OS : OperatingSystem = .unknown`, but it also silently drops any
|
||||
user-written non-zero enum literal such as `.green`.
|
||||
- `src/ir/lower.zig`, `Lowering.constExprValue`: aggregate enum-literal fields
|
||||
are currently not serialized either, so audit both top-level and aggregate
|
||||
enum literals.
|
||||
|
||||
Likely fix:
|
||||
- Resolve the destination enum type from `var_ty` / `expected_ty` and serialize
|
||||
the enum tag as a `ConstantValue.int` with the variant index/value.
|
||||
- If a particular enum literal shape cannot be serialized yet (payload variants,
|
||||
unsupported explicit tag values, etc.), emit a diagnostic instead of returning
|
||||
`null`.
|
||||
- Preserve the compiler-injected `OperatingSystem` / `Architecture` behavior by
|
||||
making those globals real constants, not by relying on null initializer
|
||||
fallback.
|
||||
|
||||
Verification:
|
||||
- Run the repro above and expect `chosen=.green` / `PASS` / exit 0.
|
||||
- Add a pinned regression in the `01xx` types block for a non-zero enum global
|
||||
and, if supported by the fix, an enum field inside a global aggregate.
|
||||
- Run:
|
||||
|
||||
```sh
|
||||
zig build
|
||||
zig build test
|
||||
bash tests/run_examples.sh
|
||||
```
|
||||
@@ -106,8 +106,11 @@ pub const LLVMEmitter = struct {
|
||||
// IR Module being emitted
|
||||
ir_mod: *const Module,
|
||||
|
||||
// Set when a comptime `#run` raised an unhandled error (E5.2). The driver
|
||||
// (core.generateCode) aborts with a non-zero exit after emit() when set.
|
||||
// Set when a comptime `#run` raised an unhandled error (E5.2), or when a
|
||||
// global initializer could not be serialized to a valid static constant.
|
||||
// The driver (core.generateCode) aborts with a non-zero exit after emit()
|
||||
// when set, so an invalid/placeholder initializer never reaches the object
|
||||
// file or the JIT — the emit-time diagnostic is the surfaced error.
|
||||
comptime_failed: bool = false,
|
||||
|
||||
// Allocator for temporary bookkeeping
|
||||
@@ -875,6 +878,7 @@ pub const LLVMEmitter = struct {
|
||||
const sep: []const u8 = if (detail.len > 0) ": " else "";
|
||||
const gname = self.ir_mod.types.getString(global.name);
|
||||
std.debug.print("error: comptime init of '{s}' failed: {s} (op={s}{s}{s})\n", .{ gname, @errorName(err), op, sep, detail });
|
||||
self.comptime_failed = true;
|
||||
break :blk .void_val;
|
||||
};
|
||||
// A bare failable `NAME :: #run f();`: the comptime function
|
||||
@@ -900,9 +904,16 @@ pub const LLVMEmitter = struct {
|
||||
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
|
||||
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty, false),
|
||||
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
||||
else => c.LLVMConstNull(llvm_ty),
|
||||
// A top-level null-pointer global (`p : *s64 = null;`) and a
|
||||
// zero-initialized global both emit as the all-zero constant
|
||||
// of the global's type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(llvm_ty),
|
||||
.undef => c.LLVMGetUndef(llvm_ty),
|
||||
// func_map is empty in Pass 0 (functions are declared in
|
||||
// Pass 1). Emit a placeholder and resolve in initVtableGlobals.
|
||||
.func_ref => c.LLVMConstNull(llvm_ty),
|
||||
};
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
} else {
|
||||
@@ -929,7 +940,14 @@ pub const LLVMEmitter = struct {
|
||||
defer field_vals.deinit(self.alloc);
|
||||
for (func_ids) |fid| {
|
||||
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||
std.debug.print(
|
||||
"error: vtable global '{s}' references function '{s}' which has no declaration\n",
|
||||
.{ self.ir_mod.types.getString(global.name), self.ir_mod.types.getString(self.ir_mod.getFunction(fid).name) },
|
||||
);
|
||||
// Keep the struct shape so module construction can
|
||||
// finish; comptime_failed halts before it ships.
|
||||
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
||||
self.comptime_failed = true;
|
||||
continue;
|
||||
};
|
||||
field_vals.append(self.alloc, llvm_func) catch unreachable;
|
||||
@@ -941,10 +959,23 @@ pub const LLVMEmitter = struct {
|
||||
.aggregate => |agg| {
|
||||
// Re-emit. The first pass in `emitGlobals` already ran,
|
||||
// but func_ref leaves resolved to null then (func_map
|
||||
// wasn't populated yet). Now they resolve properly.
|
||||
const init_val = self.emitConstAggregate(agg, llvm_ty);
|
||||
// wasn't populated yet). Now they must resolve — a still-
|
||||
// unresolved func_ref here is a loud diagnostic, never a
|
||||
// silent null.
|
||||
const init_val = self.emitConstAggregate(agg, llvm_ty, true);
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
},
|
||||
.func_ref => |fid| {
|
||||
const llvm_func = self.func_map.get(fid.index()) orelse {
|
||||
std.debug.print(
|
||||
"error: global '{s}' references function '{s}' which has no declaration\n",
|
||||
.{ self.ir_mod.types.getString(global.name), self.ir_mod.types.getString(self.ir_mod.getFunction(fid).name) },
|
||||
);
|
||||
self.comptime_failed = true;
|
||||
continue;
|
||||
};
|
||||
c.LLVMSetInitializer(llvm_global, llvm_func);
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
}
|
||||
@@ -962,6 +993,17 @@ pub const LLVMEmitter = struct {
|
||||
return ptr[0..len];
|
||||
}
|
||||
|
||||
/// Record that a global initializer could not be serialized to a valid
|
||||
/// static constant: set the halt flag (the driver aborts with a non-zero
|
||||
/// exit after `emit()`) and return an `undef` placeholder so in-process
|
||||
/// LLVM module construction can finish without tripping over an invalid
|
||||
/// value before the halt is observed. The placeholder is never shipped —
|
||||
/// `comptime_failed` guarantees we stop before object emission / JIT.
|
||||
fn failGlobalInit(self: *LLVMEmitter, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||
self.comptime_failed = true;
|
||||
return c.LLVMGetUndef(llvm_ty);
|
||||
}
|
||||
|
||||
/// Serialize an interp `Value` to an LLVM constant for use as a static
|
||||
/// global initializer. `ty` is the IR-level type of the destination;
|
||||
/// the LLVM type is derived from it. `interp` gives access to the
|
||||
@@ -969,8 +1011,10 @@ pub const LLVMEmitter = struct {
|
||||
/// is included in any diagnostic the path produces so the user can
|
||||
/// locate the offending `#run` site.
|
||||
///
|
||||
/// Returns `LLVMGetUndef` on bail — the build continues so adjacent
|
||||
/// constants can still emit, but the diagnostic makes the problem clear.
|
||||
/// On bail, prints the diagnostic and routes through `failGlobalInit`
|
||||
/// (sets `comptime_failed`, returns `undef`): the in-process module
|
||||
/// finishes constructing, but the driver halts with a non-zero exit
|
||||
/// before object emission / JIT, so the placeholder never ships.
|
||||
fn valueToLLVMConst(
|
||||
self: *LLVMEmitter,
|
||||
val: Value,
|
||||
@@ -996,7 +1040,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced a raw integer for a pointer field — needs IR-typed heap-walk serialization (Phase 1.4a heap-walk follow-up)\n",
|
||||
.{global_name},
|
||||
);
|
||||
break :blk c.LLVMGetUndef(llvm_ty);
|
||||
break :blk self.failGlobalInit(llvm_ty);
|
||||
}
|
||||
break :blk c.LLVMConstInt(llvm_ty, @bitCast(v), 1);
|
||||
},
|
||||
@@ -1004,7 +1048,17 @@ pub const LLVMEmitter = struct {
|
||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||
.null_val => c.LLVMConstNull(llvm_ty),
|
||||
.void_val, .undef => c.LLVMGetUndef(llvm_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(llvm_ty),
|
||||
// Comptime globals are serialized here in Pass 0, before functions
|
||||
// are declared (Pass 1) and with no later re-emit. A func_ref can
|
||||
// therefore never resolve to a real function pointer at this point;
|
||||
// bail loudly rather than ship a silently-null function pointer.
|
||||
.func_ref => |fid| blk: {
|
||||
std.debug.print(
|
||||
"error: comptime init of '{s}' produced a reference to function '{s}', which cannot be serialized as a static constant (function declarations are not available at global-init time)\n",
|
||||
.{ global_name, self.ir_mod.types.getString(self.ir_mod.getFunction(fid).name) },
|
||||
);
|
||||
break :blk self.failGlobalInit(llvm_ty);
|
||||
},
|
||||
.string => |s| self.emitConstStringGlobal(s),
|
||||
.aggregate => |fields| self.serializeAggregateValue(fields, ty, interp, global_name),
|
||||
// The remaining Value variants cannot become static binary
|
||||
@@ -1017,7 +1071,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced a {s} value, which cannot be serialized as a static constant\n",
|
||||
.{ global_name, @tagName(val) },
|
||||
);
|
||||
break :blk c.LLVMGetUndef(llvm_ty);
|
||||
break :blk self.failGlobalInit(llvm_ty);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1054,7 +1108,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced a fat-pointer aggregate whose len field is not an integer\n",
|
||||
.{global_name},
|
||||
);
|
||||
return c.LLVMGetUndef(llvm_ty);
|
||||
return self.failGlobalInit(llvm_ty);
|
||||
};
|
||||
const len: usize = @intCast(len_i);
|
||||
|
||||
@@ -1078,7 +1132,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced a fat-pointer aggregate whose data field ({s}) cannot be resolved to {} bytes — needs Phase 1.4a heap-walk for this shape\n",
|
||||
.{ global_name, @tagName(data), len },
|
||||
);
|
||||
return c.LLVMGetUndef(llvm_ty);
|
||||
return self.failGlobalInit(llvm_ty);
|
||||
};
|
||||
|
||||
return self.emitConstStringGlobal(bytes);
|
||||
@@ -1094,7 +1148,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced aggregate with {} fields but struct '{s}' expects {}\n",
|
||||
.{ global_name, fields.len, self.ir_mod.types.getString(info.@"struct".name), ir_fields.len },
|
||||
);
|
||||
return c.LLVMGetUndef(llvm_ty);
|
||||
return self.failGlobalInit(llvm_ty);
|
||||
}
|
||||
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
||||
defer field_vals.deinit(self.alloc);
|
||||
@@ -1119,7 +1173,7 @@ pub const LLVMEmitter = struct {
|
||||
"error: comptime init of '{s}' produced an aggregate but the destination type ({s}) is neither struct, array, string, nor slice\n",
|
||||
.{ global_name, self.ir_mod.types.typeName(ty) },
|
||||
);
|
||||
return c.LLVMGetUndef(llvm_ty);
|
||||
return self.failGlobalInit(llvm_ty);
|
||||
}
|
||||
|
||||
// ── Function declaration ────────────────────────────────────────
|
||||
@@ -2415,7 +2469,13 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
||||
}
|
||||
|
||||
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||
/// Serialize a constant aggregate to an LLVM constant. `require_resolved`
|
||||
/// governs the func_ref leaves: in Pass 0 (`emitGlobals`) func_map is empty,
|
||||
/// so func_refs are left as a transient null placeholder (`false`) and the
|
||||
/// whole aggregate is re-emitted by `initVtableGlobals` after Pass 1 with
|
||||
/// `true`, where any still-unresolved func_ref is a loud diagnostic — never
|
||||
/// a silently-null function pointer.
|
||||
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef, require_resolved: bool) c.LLVMValueRef {
|
||||
const kind = c.LLVMGetTypeKind(llvm_ty);
|
||||
const is_struct = kind == c.LLVMStructTypeKind;
|
||||
const n: c_uint = @intCast(agg.len);
|
||||
@@ -2431,9 +2491,26 @@ pub const LLVMEmitter = struct {
|
||||
.float => |v| c.LLVMConstReal(elem_ty, v),
|
||||
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
||||
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse c.LLVMConstNull(elem_ty),
|
||||
else => c.LLVMConstNull(elem_ty),
|
||||
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty, require_resolved),
|
||||
.func_ref => |fid| self.func_map.get(fid.index()) orelse blk: {
|
||||
if (require_resolved) {
|
||||
std.debug.print(
|
||||
"error: static initializer references function '{s}' which has no declaration\n",
|
||||
.{self.ir_mod.types.getString(self.ir_mod.getFunction(fid).name)},
|
||||
);
|
||||
break :blk self.failGlobalInit(elem_ty);
|
||||
}
|
||||
// Pass 0 placeholder: func_map is empty until Pass 1, so the
|
||||
// whole aggregate is re-emitted with require_resolved=true.
|
||||
break :blk c.LLVMConstNull(elem_ty);
|
||||
},
|
||||
// A null pointer field and a zero-initialized field both emit as
|
||||
// the all-zero constant of the leaf type (issue 0081).
|
||||
.null_val, .zeroinit => c.LLVMConstNull(elem_ty),
|
||||
.undef => c.LLVMGetUndef(elem_ty),
|
||||
// Vtable constants are only ever produced for top-level protocol
|
||||
// vtable globals (lower.zig), never as a nested aggregate leaf.
|
||||
.vtable => @panic("nested vtable constant in aggregate is unsupported — vtables are top-level globals only"),
|
||||
};
|
||||
}
|
||||
if (is_struct) {
|
||||
|
||||
130
src/ir/lower.zig
130
src/ir/lower.zig
@@ -926,25 +926,26 @@ pub const Lowering = struct {
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
.float_literal => |fl| .{ .float = fl.value },
|
||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, var_ty),
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements, var_ty) orelse self.diagnoseNonConstGlobal(vd, v),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, var_ty) orelse self.diagnoseNonConstGlobal(vd, v),
|
||||
.identifier => |id| blk: {
|
||||
// A global initialized from a module constant copies the
|
||||
// constant's recorded value (typed module consts land in
|
||||
// `module_const_map` via `registerTypedModuleConst`, run in the
|
||||
// same pass-2 before this).
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci| {
|
||||
if (self.constExprValue(ci.value)) |cv| break :blk cv;
|
||||
if (self.constExprValue(ci.value, var_ty)) |cv| break :blk cv;
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant; '{s}' is not a usable constant here", .{ vd.name, id.name });
|
||||
break :blk null;
|
||||
},
|
||||
// Enum-literal shorthand globals (`OS : OperatingSystem = .unknown;`)
|
||||
// keep their established zero-init: it is load-bearing for
|
||||
// compile-time `inline if OS == .X` in the stdlib (issue 0071 scope
|
||||
// note). Carved out explicitly — not folded into a silent fallthrough.
|
||||
.enum_literal => null,
|
||||
// An enum-literal global (`chosen : Color = .green;`) serializes to
|
||||
// the variant's tag value against the destination enum type (issue
|
||||
// 0082). The compiler-injected `OS`/`ARCH` globals flow through here
|
||||
// too; their runtime reads resolve via `comptime_constants`, so the
|
||||
// serialized tag only affects the static initializer.
|
||||
.enum_literal => |el| self.constEnumLiteral(&el, var_ty, v.span),
|
||||
// Any other initializer shape (`.field_access` on a const, a call, an
|
||||
// arithmetic expression, …) is not a static constant the compiler can
|
||||
// evaluate here. Diagnose loudly rather than emit a null payload that
|
||||
@@ -957,6 +958,16 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// A global aggregate initializer (array/struct literal) that does not fully
|
||||
/// reduce to a compile-time constant is rejected loudly. Without this the
|
||||
/// `null` payload would fall through to a zero-initialized global, silently
|
||||
/// dropping the declared fields (issues 0071/0072/0080).
|
||||
fn diagnoseNonConstGlobal(self: *Lowering, vd: *const ast.VarDecl, v: *const Node) ?inst_mod.ConstantValue {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant", .{vd.name});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve identifier-RHS type aliases whose target is declared LATER in the
|
||||
/// file. The forward scan above only registers an alias (`A :: B`) when `B`
|
||||
/// is already in `type_alias_map` / the `TypeTable`; a forward target isn't
|
||||
@@ -993,25 +1004,40 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
|
||||
/// Returns null if any element is not a compile-time constant.
|
||||
fn constArrayLiteral(self: *Lowering, elements: []const *const Node) ?inst_mod.ConstantValue {
|
||||
/// Try to convert an array literal's elements into a compile-time
|
||||
/// ConstantValue.aggregate. `array_ty` is the array's resolved TypeId; its
|
||||
/// element type drives type-aware serialization of struct-literal and
|
||||
/// nested-array elements. Returns null if `array_ty` is not an array type or
|
||||
/// any element is not a compile-time constant.
|
||||
fn constArrayLiteral(self: *Lowering, elements: []const *const Node, array_ty: TypeId) ?inst_mod.ConstantValue {
|
||||
if (array_ty.isBuiltin()) return null;
|
||||
const elem_ty: TypeId = switch (self.module.types.get(array_ty)) {
|
||||
.array => |a| a.element,
|
||||
else => return null,
|
||||
};
|
||||
const vals = self.alloc.alloc(inst_mod.ConstantValue, elements.len) catch return null;
|
||||
for (elements, 0..) |elem, i| {
|
||||
vals[i] = self.constExprValue(elem) orelse return null;
|
||||
vals[i] = self.constExprValue(elem, elem_ty) orelse return null;
|
||||
}
|
||||
return .{ .aggregate = vals };
|
||||
}
|
||||
|
||||
/// Try to convert a single AST expression into a compile-time ConstantValue.
|
||||
/// Returns null if the expression is not constant-foldable here.
|
||||
fn constExprValue(self: *Lowering, expr: *const Node) ?inst_mod.ConstantValue {
|
||||
/// `expected_ty` is the destination element/field type — it lets aggregate
|
||||
/// leaves (struct literals, nested arrays) serialize with the correct shape
|
||||
/// rather than collapsing to null (issue 0080). Returns null if the
|
||||
/// expression is not constant-foldable here.
|
||||
fn constExprValue(self: *Lowering, expr: *const Node, expected_ty: TypeId) ?inst_mod.ConstantValue {
|
||||
return switch (expr.data) {
|
||||
.int_literal => |il| .{ .int = il.value },
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
.float_literal => |fl| .{ .float = fl.value },
|
||||
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
|
||||
.undef_literal => .zeroinit,
|
||||
// A `null` in a pointer (or optional-pointer) field is a
|
||||
// compile-time constant: the zero pointer. Without this arm the
|
||||
// aggregate is wrongly rejected as non-constant (issue 0081).
|
||||
.null_literal => .null_val,
|
||||
.unary_op => |uo| switch (uo.op) {
|
||||
.negate => switch (uo.operand.data) {
|
||||
.int_literal => |il| .{ .int = -il.value },
|
||||
@@ -1020,11 +1046,46 @@ pub const Lowering = struct {
|
||||
},
|
||||
else => null,
|
||||
},
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements),
|
||||
.array_literal => |al| self.constArrayLiteral(al.elements, expected_ty),
|
||||
.struct_literal => |sl| self.constStructLiteral(&sl, expected_ty),
|
||||
// An enum tag as an aggregate leaf (`[2]Color = .[.green, .blue]`, or
|
||||
// an enum field inside a global struct) serializes to its tag int
|
||||
// against the leaf's declared enum type (issue 0082).
|
||||
.enum_literal => |el| self.constEnumLiteral(&el, expected_ty, expr.span),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Serialize an enum-literal initializer (`.Variant`) into a static
|
||||
/// `ConstantValue.int` holding the variant's tag value, resolved against the
|
||||
/// destination enum type `ty`. The tag respects explicit variant values
|
||||
/// (`enum { a; b :: 5; }`); the enum's backing width is applied by the
|
||||
/// const emitters via the destination type's LLVM type. Plain enums only —
|
||||
/// a tagged-union or non-enum destination is diagnosed loudly rather than
|
||||
/// silently zero-initialized (issue 0082).
|
||||
fn constEnumLiteral(self: *Lowering, el: *const ast.EnumLiteral, ty: TypeId, span: ast.Span) ?inst_mod.ConstantValue {
|
||||
if (!ty.isBuiltin()) {
|
||||
const info = self.module.types.get(ty);
|
||||
if (info == .@"enum") {
|
||||
const e = info.@"enum";
|
||||
const name_id = self.module.types.internString(el.name);
|
||||
for (e.variants, 0..) |variant, i| {
|
||||
if (variant != name_id) continue;
|
||||
if (e.explicit_values) |vals| {
|
||||
if (i < vals.len) return .{ .int = vals[i] };
|
||||
}
|
||||
return .{ .int = @intCast(i) };
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "'.{s}' is not a variant of enum '{s}'", .{ el.name, self.module.types.getString(e.name) });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "enum-literal global initializer '.{s}' is only supported for a plain enum destination type", .{el.name});
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Try to convert a struct literal into a compile-time ConstantValue.aggregate of the
|
||||
/// struct's fields in declaration order, filling missing fields from the struct's
|
||||
/// field defaults. Returns null if any value is not constant-foldable.
|
||||
@@ -1055,7 +1116,7 @@ pub const Lowering = struct {
|
||||
break :blk null;
|
||||
};
|
||||
if (init_expr) |e| {
|
||||
vals[fi] = self.constExprValue(e) orelse return null;
|
||||
vals[fi] = self.constExprValue(e, sf.ty) orelse return null;
|
||||
} else {
|
||||
vals[fi] = .zeroinit;
|
||||
}
|
||||
@@ -2324,20 +2385,29 @@ pub const Lowering = struct {
|
||||
fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
||||
switch (node.data) {
|
||||
.identifier => |id| {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(id.name)) |binding| {
|
||||
if (binding.is_alloca) {
|
||||
// If the variable IS a pointer (e.g., p: *Vec2), load it
|
||||
// to get the actual pointer value for GEP/store operations
|
||||
if (!binding.ty.isBuiltin()) {
|
||||
const info = self.module.types.get(binding.ty);
|
||||
if (info == .pointer) {
|
||||
return self.builder.load(binding.ref, binding.ty);
|
||||
}
|
||||
const local = if (self.scope) |scope| scope.lookup(id.name) else null;
|
||||
if (local) |binding| {
|
||||
if (binding.is_alloca) {
|
||||
// If the variable IS a pointer (e.g., p: *Vec2), load it
|
||||
// to get the actual pointer value for GEP/store operations
|
||||
if (!binding.ty.isBuiltin()) {
|
||||
const info = self.module.types.get(binding.ty);
|
||||
if (info == .pointer) {
|
||||
return self.builder.load(binding.ref, binding.ty);
|
||||
}
|
||||
return binding.ref;
|
||||
}
|
||||
return binding.ref;
|
||||
}
|
||||
} else if (self.program_index.global_names.get(id.name)) |gi| {
|
||||
// Module-global lvalue: address into the global's live storage
|
||||
// so a downstream GEP/store targets the global itself, not a
|
||||
// loaded copy. A pointer-typed global is loaded first to get
|
||||
// the pointer value to GEP through (mirrors the local pointer
|
||||
// case above); any other global yields its storage address.
|
||||
if (!gi.ty.isBuiltin() and self.module.types.get(gi.ty) == .pointer) {
|
||||
return self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
||||
}
|
||||
return self.builder.emit(.{ .global_addr = gi.id }, self.module.types.ptrTo(gi.ty));
|
||||
}
|
||||
},
|
||||
.field_access => |fa| {
|
||||
@@ -2697,9 +2767,11 @@ pub const Lowering = struct {
|
||||
const obj_ty = self.inferExprType(ie.object);
|
||||
const elem_ty = self.getElementType(obj_ty);
|
||||
const ptr_ty = self.module.types.ptrTo(elem_ty);
|
||||
// For array targets, use the alloca directly so the pointer is persistent
|
||||
// For array targets, use the storage pointer (alloca for a
|
||||
// local, global_addr for a module global) so the resulting
|
||||
// pointer is into live storage, not a loaded copy.
|
||||
const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array;
|
||||
const base = if (is_array) (self.getExprAlloca(ie.object) orelse self.lowerExpr(ie.object)) else self.lowerExpr(ie.object);
|
||||
const base = if (is_array) (self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object)) else self.lowerExpr(ie.object);
|
||||
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx } }, ptr_ty);
|
||||
}
|
||||
// address_of(field_access) → use lowerExprAsPtr for GEP chain
|
||||
|
||||
Reference in New Issue
Block a user