Commit fd4b570
committed
fix(compiler): keep parameters live for the whole body
Residual B1 slot-aliasing defect: the liveness allocator only made
parameters interfere with each other and with slots live at body
entry, so a local defined after entry could still be colored onto a
parameter slot. The callee frame then read the wrong slot while
evaluating call arguments and the VM callable-schema check failed
(type mismatch: expected string) even though every value was
correctly typed. A five-parameter caller dispatching to imported
parse helpers reproduced it; an identical two-parameter caller
passed, isolating the corruption to the caller's parameter-slot
layout (plan section 11a).
Seed the function's parameter slots into the body live-out before
collecting interference constraints and re-mark them after every
statement in the backward sweep, so every parameter stays live for
the whole body no matter what the body does to it. Parameter slots
are caller-written frame-entry state that the callee frame may read
at any point; body statements *can* define them (an Assign may
target a parameter, and the liveness rewriter may Drop one after its
last use), so the rule is a conservative safety invariant, not a
claim that the body never defines a parameter slot. Non-parameter
locals keep sharing physical slots exactly as before.
Closure bodies execute in their own callee frame drawn from the same
flat slot space, so the same full-body rule applies to them: each
closure's own parameter slots and capture targets seed a fresh
live-out, and the backward collector computes the real tail/body
uses itself. The closure collection is deliberately precise:
- The allocator now computes live sets without the conservative
dynamic-local-call fill, so a `LocalCall` cannot turn every
statement's live set - and therefore the whole interference graph -
into one complete clique. The drop-insertion rewriter keeps the
conservative fill so captured slots are never cleared before a
dynamic call executes.
- The program-wide cross-live for dynamic local calls is skipped
inside closure bodies: the target still runs in its own callee
frame, and the cross-live would only turn the closure body's slots
into a program-wide clique, destroying compaction and spuriously
failing frames near the 256-slot limit.
- Seeding the closure live-out with every slot used in the body is
gone; the body's real uses come from the backward sweep.
The callable-materialization classification now runs before local-slot
compaction: it tracks named-function values through slot flows, and
merged physical slots would collapse distinct flows into one slot,
producing spurious dynamic-target facts (the old allocator's
all-interfere coloring masked this by never merging).
Tests were re-audited against base d8cf291 in a temporary worktree;
every regression below is genuinely RED there and GREEN here:
- body_defined_local_never_aliases_parameter_slot: RED at base (body
local aliases a parameter slot, VM TypeMismatch("string"))
- closure_parameter_stays_live_for_whole_closure_body: RED at base
(closure parameter aliases its body local; previously the frame had
only 8 pre-compaction slots so the allocator never ran)
- nested_closure_parameters_stay_scoped_to_own_bodies: RED at base
(inner closure parameter aliases its body local; previously the
dynamic LocalCall liveness fill masked it)
- parameter_heavy_frame_near_boundary_returns_typed_error: RED at
base (the 250-parameter frame compiles there; the full-body rule
makes it fail with the typed frame-limit error)
- non_param_locals_still_compact_beside_wide_parameter_frames: RED at
base (spurious "too many simultaneously live locals" from the
dynamic-call clique)
- closure_local_call_keeps_unrelated_locals_compact (new): RED at
base (spurious >256-slot error); after the fix a closure whose body
calls another closure through a local binding compacts 258 declared
slots to 8 and runs correctly
parameter_interference_preserves_local_slot_compaction and
assign_to_parameter_keeps_full_body_interference cannot be made RED
at base (the base allocator's def-edges and closure body-footprint
unions already separate those slots, and base compacts at least as
well without the full-body rule), so they are renamed to explicit
smoke guards and are not counted as regressions.
- src/compiler/lifetime/liveness.rs: parameter seeding for function
bodies, precise allocator liveness, scoped closure body collection
- src/compiler/lifetime/availability.rs, lifetime/mod.rs,
pipeline.rs: split local-slot allocation out of the lifetime pass so
the callable classification runs on pre-compaction slots
- tests/compiler/module_import_tests.rs: base-RED regressions plus
smoke guards1 parent d8cf291 commit fd4b570
5 files changed
Lines changed: 1236 additions & 94 deletions
File tree
- src/compiler
- lifetime
- tests/compiler
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
147 | 160 | | |
148 | 161 | | |
149 | 162 | | |
| |||
0 commit comments