A bailout is the mechanism used to exit an Ion code and resume the execution in a slower mode of execution, such as the baseline compiler code.
To handle bailout mechanism we have multiple stages in the compiler which are used to capture the stack layout and the associated register/stack allocations, such as we can recover the structure of the baseline frames.
Resume points are used at the MIR level to capture the state of the interpreter stack. When the MIR is constructed, we maintain a stack similar to the stack of the interpreter except that the stack stored on MBasicBlocks contains MDefinition pointers instead of JS::Value. When a resume point is constructed is set its operands to be identical to the stack maintained in the MBasicBlock.
A LSnapshot captures the list of virtual register, associated with MDefinition pointers contained in the last visited Resume Point. This list of virtual register is them updated by the Register allocator. LSnapshot are attached to LInstructions as a way to find which register and stack slot are used at the time of the bailout.
A Snapshot Iterator is reading an LSnapshot and takes a MachineState either build from an call-site or from a the spill made by the trampoline to the bailout code. The Machine state is an array which maps register index to the stack pointer location of the register spills.
Not all registers are recoverable when we are looking at a call-site (double & non-GC Cells).
A bailout reconstructs the stack previously encoded by a resume point, by using a Snapshot Iterator.
The on-stack invalidation, is either caused by constraints, by a GC, or by an unexpected return value (which was specialized before)
A safe-point, is a list of type and allocations used by the GC. They are used to root when the GC iterates over Ion activations. Differences between safepoints and snapshots:
- Safepoints are made to recover GC-Things as fast as possible, Snapshots are made to recover all the content of interpreter frames.
- Safepoints are indexed by the return address of the last call, Snapshots indexes are pushed by the bailout path.
An On-Stack-Invalidation point is an empty instruction which is used to reserve some instruction space after an instruction which is making a call, such as if the next instruction is also making a call, we would have reserved enough space to patch the code in-place without modifying the return path of the next call.
When an invalidation occur, and the script is active on the stack, we patch the code at the OSI-point located after each call to unconditionally jump to a bailout.
When a script is invalidated, the JSScript no longer keep any reference to the IonScript which is live on the stack. To prevent GC-ing the script which is still on the stack, we patch before the return address with the value of the IonScript pointer. Note that having 2 fallible calls for one LIR is dangerous, as the backward patch might modify the return path of another call-site.