The Phase 1.4 serializer left a silent malformed-const case: when the
interp evaluated a `#run` returning a string (or anything with a fat
pointer inside), the data field came in as a `.int` holding a libc
host address. `LLVMConstInt(ptr_type, addr, 1)` happily emitted `i0 0`
in the static const, and the runtime segfaulted on the first read.
Phase 1.4a closes this for string and slice destinations. The signature
of `valueToLLVMConst` now takes the IR `TypeId` (instead of just the
LLVM type) and a borrowed `*Interpreter`. A new helper
`serializeAggregateValue` splits on the IR type:
- `string` / `slice` (fat pointer `{data, len}`): extract `len`, read
that many bytes from the data field's address (via `interp.heapSlice`
for `heap_ptr`, via a new `readHostBytes` for `byte_ptr` / `.int`,
via slice indexing for string literals). Emit the bytes as a private
global byte array using the existing `emitConstStringGlobal`. The
fat-pointer aggregate's data ptr resolves to the byte array's address.
- `struct`: walk the IR field types in lockstep with the value's
fields; recurse with each declared field TypeId. This replaces the
old LLVM-type-walk via `LLVMStructGetTypeAtIndex` which couldn't tell
string-typed fields from generic ptr fields.
- `array`: walk with the element TypeId.
The remaining `.int → ptr` trap (a host address landing in a bare ptr
field outside a fat pointer) now bails loudly with a named diagnostic
identifying it as Phase 1.4a heap-walk follow-up territory. No
practical trigger in-tree, so deferred.
`Interpreter.heapSlice` promoted from package-private to `pub` so
the serializer can read interp-managed heap data.
Regression: `examples/136-comptime-string-global.sx` —
`GREETING :: #run build_greeting();` where `build_greeting` returns
`concat("hello", " world")`. Runtime prints `greeting = 'hello world'`
and `greeting.len = 11`. Pre-1.4a this segfaulted on the first read.
158/158 example tests; chess clean on macOS / iOS sim / Android via
`tools/verify-step.sh`.
27 lines
1008 B
Plaintext
27 lines
1008 B
Plaintext
// Phase 1.4a — a `#run` that returns a string (or any aggregate
|
|
// containing a heap-allocated buffer) must serialize correctly into
|
|
// the static binary. The interp computes the string at build time,
|
|
// allocating its backing through `context.allocator` (which bottoms
|
|
// out at libc_malloc in the default context). The serializer reads
|
|
// the resulting `{addr, len}` aggregate, captures the bytes from
|
|
// host memory, emits them as a private global byte array, and
|
|
// rebuilds the aggregate to point at that array.
|
|
//
|
|
// Before Phase 1.4a this segfaulted at runtime — the pointer field
|
|
// in the static const ended up as `i0 0` (malformed) because the
|
|
// interp's host-address `.int` value can't be lowered as `ptr` by
|
|
// `LLVMConstInt`.
|
|
#import "modules/std.sx";
|
|
|
|
build_greeting :: () -> string {
|
|
return concat("hello", " world");
|
|
}
|
|
|
|
GREETING :: #run build_greeting();
|
|
|
|
main :: () -> s32 {
|
|
print("greeting = '{}'\n", GREETING);
|
|
print("greeting.len = {}\n", GREETING.len);
|
|
return 0;
|
|
}
|