|Version 2 (modified by gb, 6 years ago) (diff)|
Per-thread bindings of special variables
In the thread-context record (TCR), there is a vector (tcr.tlb_pointer) that contains thread-local special binding values. (The "vector" is actually allocated with malloc(), but it always contains tagged lisp objects.)
Each symbol that's used in a special-binding construct is assigned (either at compile/load time or during PROGV pre-processing) a unique integer, called the binding index (symbol.binding_index). This integer (a fixnum) is used as an index into the vector mentioned above.
An attempt to dynamically bind a symbol whose binding index is outside the bounds of the current thread's tlb table (tcr.tlb_limit) will trap and grow the table.
The value of a special variable may be found in the tlb table, indexed by the symbol's binding index. If there is no thread local binding for the symbol, the value in the tlb table will be a no-thread-local-binding marker. The symbol's vcell will be returned in this case.
A symbol that's never been assigned an explicit binding index will have a binding index of 0; the 0th entry in each thread's tcr.tlb_pointer is always no-thread-local-binding-marker (so references and assignments to such a symbol will always affect the symbol's value cell.
A few symbols (currently CCL::*INTERRUPT-LEVEL*, which is bound by WITHOUT-INTERRUPTS) have fixed, canonical binding indices (CCL::*INTERRUPT-LEVEL* has a binding index of 1); this allows the per-thread bindings of some symbols to be accessed from the kernel. In general, binding indices are assigned arbitrarily, and a symbol's binding index may change from session to session.
Another field in the TCR, called tcr.db_link, is the dynamic binding chain (a linked list of binding records).
When dynamically binding a symbol, a binding record (link, binding_index, value) representing the current binding is created on the stack, and becomes the new head of the chain. The tlb table is then updated with the new value.
Dynamic binding information is always pushed onto the lisp stack, and therefore pointers to any of that information (link, etc.) will always be fixnum-tagged.
Note that the symbol's vcell is never affected.
Unbinding is just a matter of updating the tlb table using the data in the binding record at the front of the dynamic binding chain, and then popping off said record.