Skip to content

Commit fd4b570

Browse files
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 guards
1 parent d8cf291 commit fd4b570

5 files changed

Lines changed: 1236 additions & 94 deletions

File tree

‎src/compiler/lifetime/availability.rs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ pub(super) fn enforce_local_availability(
144144
// grows past the compat threshold, compact onto the minimal physical slot
145145
// set while still rejecting programs that need more than 256 simultaneous
146146
// locals.
147+
Ok(ir)
148+
}
149+
150+
/// Compact the flat local slot space onto the minimal physical slot set.
151+
///
152+
/// Kept separate from `enforce_local_availability` so callers can run the
153+
/// callable-materialization classification on the *pre-compaction* IR: the
154+
/// classifier tracks named-function values through slot flows, and merged
155+
/// physical slots would collapse distinct flows into one slot, producing
156+
/// spurious dynamic-target facts. Pre-compaction slots are the true
157+
/// frame-relative value identities, so the classification is strictly more
158+
/// precise on the unallocated IR.
159+
pub(crate) fn allocate_local_slots(mut ir: FrontendIr) -> Result<FrontendIr, ParseError> {
147160
if ir.locals > LOCAL_SLOT_ALLOCATOR_COMPAT_THRESHOLD {
148161
let allocator = LocalSlotAllocator::new(ir.locals, &ir.local_bindings, &ir.function_impls);
149162
ir = allocator.allocate(ir)?;

0 commit comments

Comments
 (0)