← All reports

[JSC] DFG SwitchChar releases its operand before speculating that it is a string

Component: JSC DFG JIT | 3ddc704

Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

@@ emitSwitchChar (StringUse case) @@
+ speculateString(node->child1(), op1GPR);
op1.use();
-
- speculateString(node->child1(), op1GPR);
emitSwitchCharStringJump(node, data, op1GPR, tempGPR, node->child1());

WebKit's DFG JIT compiles hot functions speculatively, tracking operand liveness via a variable event stream so that on a type-check failure — an OSR exit — it can reconstruct the exact interpreter state baseline needs to resume. use() marks an operand's last use (its death) in that stream, while speculateString() emits the guard that can trigger the exit.

The ordering was reversed in SwitchChar's string case: the death event was recorded before the guard.

Before:                                  After:
  op1.use()          <- operand dead       speculateString()  <- exit sees it live
  speculateString()  <- may exit here      op1.use()          <- dead after exit point
  -> exit reconstructs scrutinee local     -> exit reconstructs with correct value
     as dead-value recovery

Any local whose value the DFG associates with that node — via MovHint, here because the SetLocal was eliminated — gets reconstructed as a dead placeholder instead of its real value once the guard fails. Baseline then re-executes the switch on an incorrectly reconstructed operand. SwitchString already used the correct ordering.

The forward-facing pattern is a liveness-death event emitted before a guard that can exit, in a JIT whose OSR reconstruction reads that stream. Narrow: grep the other emit* and speculate* call sites in DFGSpeculativeJIT.cpp for the same use()-before-speculate*() ordering — the match tell is a .use() call textually above any speculate/spill/type-check emitter operating on the same operand, since the correct form is always guard-then-release. Wider: this class was caught by the poisonDeadOSRExitVariables debug mode rather than static analysis, so the productive move is running that mode broadly rather than reading code — extend to the FTL's equivalent liveness bookkeeping and to any node type with multiple operands where only some are speculated. Widest: a live object reconstructed as dead is a state-confusion primitive, not merely a correctness crash — for each site found, ask whether baseline resumption on the bogus value reaches a path that reads it as a pointer, which is the difference between a wrong-answer bug and a type confusion.