llst
llst copied to clipboard
Eliminate shadow stack in favor of execution context
Overview
Execution context of a Smalltalk method is represented by an instance of Context
class. It holds all information required to execute a method:
- Pointer to a method which is used to access bytecodes and literals
- Current instance (self) and provided arguments
- Storage for temporary values
- Stack of intermediate values
- Pointer to current stack top
- Pointer to the next instruction
- Pointer to a previous context in chain
Jit version of VM takes advantage of hardware stack and links intermediate values directly. This allows us to eliminate the value stack completely. Instances of Context
that are created during jit execution are allocated on the hardware stack. Even their stack
, bytePointer
and stackTop
fields aren't initialized and left simply as nil
.
However, everything has it's cost. In particular, every intermediate value created during jit execution should be protected in terms of GC safety. If garbage collection occur, it may effectively change the location of many objects rendering their pointers completely unusable unless protected. Much worse, locations of intermediate values may be stored in register, not in memory. If GC will not be able to track such values it will not update them.
Problem is solved by introducing so called shadow stack: a linked list of pointers to a stack frames that may contain intermediate values. GC knows about shadow stack and traverse it when garbage collection occurs. It is jit code generator responsibility to maintain the shadow stack along method execution and to re-load the values which may be changed since last GC. Finally, when method is finished and a value need to be returned shadow stack is unwound. This also happens during exception propagation.
The problem
Shadow stacks work well, but their maintenance takes time. Top entry in the shadow stack is located in a global variable which is not very flexible and not scalable when we start think about threading. Finally, shadow stack is redundant. In fact, it duplicates all information that may be deduced by looking at context instances. We're dealing with fully reflexive object oriented language, by the way :)
Let's try to use existing Smalltalk objects and solve the issue on our own.
The solution
The only purpose of shadow stack is to pin point locations on the hardware stack which should be tracked by GC. We know that context objects are tracked by GC, have unused fields and are accessible from the jit code, hmm…
Okay, so here's the deal:
- In the method prologue another array object is allocated on the stack
- Pointer to array is stored at… well… the
stack
field of the context - Intermediate values are stored in that array
- Cleanup is done automatically: just throw away the stack frame!
Simple as it is. We even may pin point the locations of spilled registers that are known to hold object pointers and update them too. In that case it will be redundant to protect the context and self pointers!