ffi issue-0046 fix: save/restore outer state in createComptimeFunction
`createComptimeFunction` wraps a comptime expression into a fresh fn that the interp executes in isolation. The wrapper must not inherit the enclosing call's lowering state — any leaked slot, binding, or scope flag corrupts the wrapper's own lowering. Pre-fix, only `func` / `current_block` / `inst_counter` / `scope` / `current_ctx_ref` were saved. Specifically NOT saved: - `inline_return_target` — set by `lowerComptimeCall` for an outer comptime body with `return X;`. The wrapper's body was lowering through this slot, routing the wrapper's `ret` into a basic block from a different function. - `pack_arg_nodes`, `pack_param_count`, `pack_arg_types` — active during a pack-fn mono's body lowering. (Pack-fn face of 0046 was already fixed by step 2b moving pack-fn calls off the inline path; these saves close a latent cross-contamination if any future pack-mono body invokes the comptime interp.) - `comptime_param_nodes` — active during an outer `lowerComptimeCall` to bind `$fmt`-style substitutions. - `block_terminated`, `target_type`, `func_defer_base` — fn- local flags that the wrapper's lowering needs fresh. All eight now save/restore in `createComptimeFunction`. The wrapper runs in a clean state. `examples/issue-0046.sx` flips from the non-deterministic interp panic to "inside\n" + "n=42\n". 204/204 example tests + `zig build test` green. Issue file marked FIXED with a pointer to the regression test.
This commit is contained in:
@@ -1,3 +1,22 @@
|
||||
**FIXED.** `createComptimeFunction` now saves/restores the
|
||||
outer `lowerComptimeCall`'s state — specifically
|
||||
`inline_return_target`, `pack_arg_nodes`, `pack_param_count`,
|
||||
`pack_arg_types`, `comptime_param_nodes`, `block_terminated`,
|
||||
`target_type`, and `func_defer_base` — so the wrapper fn it
|
||||
builds for the nested comptime expression runs in isolation.
|
||||
Without the saves, the wrapper inherited an inline-return slot
|
||||
belonging to a different basic block; the interp executed it
|
||||
and tripped a null pointer store at `storeAtRawPtr`.
|
||||
|
||||
The pack-fn face of this bug (filed as face 2) was fixed
|
||||
incidentally by step 2b's mono refactor — pack-fn calls
|
||||
bypass the inline-return-slot setup entirely. Plain
|
||||
`($x: s32)` comptime fns stay on the inline path; the
|
||||
`createComptimeFunction` save/restore fix covers that path.
|
||||
|
||||
Regression test:
|
||||
[examples/issue-0046.sx](../examples/issue-0046.sx).
|
||||
|
||||
# Symptom
|
||||
|
||||
A comptime fn body containing BOTH a nested comptime call
|
||||
|
||||
@@ -9548,13 +9548,49 @@ pub const Lowering = struct {
|
||||
const name = std.fmt.bufPrint(&buf, "{s}_{d}", .{ prefix, self.comptime_counter }) catch prefix;
|
||||
self.comptime_counter += 1;
|
||||
|
||||
// Save current builder state
|
||||
// Save current builder + lowering state. The wrapper fn we're
|
||||
// about to build runs the comptime expression in isolation —
|
||||
// it must NOT inherit the enclosing call's `inline_return_target`
|
||||
// (which would re-route a `return` inside the wrapper into a
|
||||
// slot belonging to a different basic block), pack bindings
|
||||
// (which would substitute caller's `args` inside the wrapper),
|
||||
// or comptime-param bindings (which would substitute caller's
|
||||
// `$fmt` inside the wrapper's #insert children). Without these
|
||||
// saves, nested comptime calls leak outer state into the
|
||||
// interp-executed wrapper, producing garbage stores (issue-0046
|
||||
// face 1 — storeAtRawPtr null).
|
||||
const saved_func = self.builder.func;
|
||||
const saved_block = self.builder.current_block;
|
||||
const saved_counter = self.builder.inst_counter;
|
||||
const saved_scope = self.scope;
|
||||
const saved_ctx_ref = self.current_ctx_ref;
|
||||
defer self.current_ctx_ref = saved_ctx_ref;
|
||||
const saved_iri = self.inline_return_target;
|
||||
const saved_pan = self.pack_arg_nodes;
|
||||
const saved_ppc = self.pack_param_count;
|
||||
const saved_pat = self.pack_arg_types;
|
||||
const saved_cpn = self.comptime_param_nodes;
|
||||
const saved_block_terminated = self.block_terminated;
|
||||
const saved_target_type = self.target_type;
|
||||
const saved_func_defer_base = self.func_defer_base;
|
||||
self.inline_return_target = null;
|
||||
self.pack_arg_nodes = null;
|
||||
self.pack_param_count = null;
|
||||
self.pack_arg_types = null;
|
||||
self.comptime_param_nodes = null;
|
||||
self.block_terminated = false;
|
||||
self.target_type = null;
|
||||
self.func_defer_base = self.defer_stack.items.len;
|
||||
defer {
|
||||
self.current_ctx_ref = saved_ctx_ref;
|
||||
self.inline_return_target = saved_iri;
|
||||
self.pack_arg_nodes = saved_pan;
|
||||
self.pack_param_count = saved_ppc;
|
||||
self.pack_arg_types = saved_pat;
|
||||
self.comptime_param_nodes = saved_cpn;
|
||||
self.block_terminated = saved_block_terminated;
|
||||
self.target_type = saved_target_type;
|
||||
self.func_defer_base = saved_func_defer_base;
|
||||
}
|
||||
|
||||
// Build params: implicit `__sx_ctx` at slot 0 when the program
|
||||
// uses Context (so the body's `context.X` reads + transitive calls
|
||||
|
||||
Reference in New Issue
Block a user