`valueToLLVMConst` in emit_llvm previously handled int / float / boolean
and collapsed everything else into `LLVMConstNull(ty)`. A `#run` returning
a struct, string, function pointer, or anything aggregate produced a
zero-initialized global silently — the comptime result was computed by
the interp, then thrown away when emit_llvm couldn't represent it.
Replaced with a real walk:
- int / float / boolean — as before.
- null_val — `LLVMConstNull`.
- void_val / undef — `LLVMGetUndef`.
- func_ref — `func_map` lookup (already populated for the implicit-Context
static initializer of `__sx_default_context`).
- string — `emitConstStringGlobal`, returns a pointer to the byte array.
- aggregate — recurse field-by-field. Struct: walk
`LLVMStructGetTypeAtIndex` and emit `LLVMConstNamedStruct`. Array:
walk `LLVMGetElementType` and emit `LLVMConstArray2`.
The remaining variants (heap_ptr, byte_ptr, slot_ptr, closure, type_tag)
bail loudly with a `std.debug.print` carrying the global name — per
CLAUDE.md REJECTED PATTERNS, no more silent unimplemented arms. heap_ptr
serialization requires threading the IR `TypeId` so the heap content can
be walked recursively; deferred to Phase 1.4a alongside cycle detection.
The call site at emit_llvm.zig:676 now passes `global.name` so the
diagnostic locates the offending `#run` binding.
Type-inference fix at the binding site: `NAME :: #run expr;` with no
annotation used to default to `s64` via `resolveType(null) -> .s64`,
so even a successful Phase 1.4 serialization would emit `{0, 0}` —
the global's destination type was wrong. `lowerComptimeGlobal` now
calls `inferExprType(expr)` when no annotation is given, so the
inferred type matches the comptime function's return type. The
broader `resolveType(null)` fallback is left in place for other
callers — flagged in the MEM checkpoint as a follow-up audit.
Regression: `examples/134-comptime-aggregate-global.sx` exercises
`POINT :: #run make_point()` returning a `Point { x: s32, y: s32 }`.
Both interp (`sx run`) and codegen (`sx build`) now print
`POINT.x = 7 / POINT.y = 13` instead of `0 / 0`. 156/156 example
tests pass; chess unchanged.
32 lines
974 B
Plaintext
32 lines
974 B
Plaintext
#import "modules/std.sx";
|
|
|
|
// MEM Phase 1.4 regression: an aggregate (struct) returned from a `#run`
|
|
// initializer must serialize correctly into the static binary, not
|
|
// silently collapse to a zero-init constant.
|
|
//
|
|
// Before Phase 1.4 the LLVM `valueToLLVMConst` only handled int/float/bool
|
|
// and dropped everything else into `LLVMConstNull` — so a global like
|
|
// `POINT :: #run make_point();` ended up emitting `{0, 0}` regardless of
|
|
// what the interp computed. Reading POINT.x would give 0, hiding the bug.
|
|
//
|
|
// Also exercises type inference at the `NAME :: #run expr;` binding site:
|
|
// without an explicit annotation, the global's type is taken from the
|
|
// comptime expression's return shape (here, `Point`).
|
|
|
|
Point :: struct {
|
|
x: s32;
|
|
y: s32;
|
|
}
|
|
|
|
make_point :: () -> Point {
|
|
return Point.{ x = 7, y = 13 };
|
|
}
|
|
|
|
POINT :: #run make_point();
|
|
|
|
main :: () -> s32 {
|
|
print("POINT.x = {}\n", POINT.x);
|
|
print("POINT.y = {}\n", POINT.y);
|
|
return 0;
|
|
}
|