Summary
The fused ++/-- path reads a local's .Register without checking IsSpilled, so an increment/decrement on a spilled local targets R0 instead of the variable's spill slot. The variable never updates (and R0 is corrupted).
Where
pkg/compiler/compile_expression.go:705 and :719 set identInfo.targetReg = symbolRef.Register for a local without an !IsSpilled guard; that reg is later used by emitFusedUpdate (~line 883). A spilled symbol's Register defaults to 0 (!= nilRegister = 255), so it aliases R0.
Repro (270 locals force the counter to spill)
function f(): number {
let v0 = 0; /* ... let v1 .. v269 ... */ let v269 = 269;
let c = 100;
c++;
return c; // returns 100 — the ++ hit R0, not c's spill slot
}
Actual: 100. Expected: 101. (c--, --c, ++c behave the same way.)
Relationship
Surfaced during the audit for #19. Same class of defect (code reusing a spilled symbol's zero-valued Register), but on the fused-update use path, not a binding-init path — so it was deliberately left out of #19's scope. Fix: check IsSpilled first and route the fused update through the spill slot (emitLoadSpill → update → emitStoreSpill), or fall back to the non-fused path for spilled locals.
Summary
The fused
++/--path reads a local's.Registerwithout checkingIsSpilled, so an increment/decrement on a spilled local targetsR0instead of the variable's spill slot. The variable never updates (and R0 is corrupted).Where
pkg/compiler/compile_expression.go:705and:719setidentInfo.targetReg = symbolRef.Registerfor a local without an!IsSpilledguard; that reg is later used byemitFusedUpdate(~line 883). A spilled symbol'sRegisterdefaults to0(!= nilRegister= 255), so it aliases R0.Repro (270 locals force the counter to spill)
Actual:
100. Expected:101. (c--,--c,++cbehave the same way.)Relationship
Surfaced during the audit for #19. Same class of defect (code reusing a spilled symbol's zero-valued
Register), but on the fused-update use path, not a binding-init path — so it was deliberately left out of #19's scope. Fix: checkIsSpilledfirst and route the fused update through the spill slot (emitLoadSpill → update → emitStoreSpill), or fall back to the non-fused path for spilled locals.