28. Stack and register scanning¶
28.1. Introduction¶
.intro: This is the design of the stack and register scanning module.
.readership: Any MPS developer; anyone porting the MPS to a new platform.
.overview: This module locates and scans references in the control stack and registers of the current thread.
.other: The thread manager module is responsible for scanning the control stack and registers of other threads. See design.mps.thread-manager.if.scan.
28.2. Requirements¶
.req.stack.top: Must locate the top of the mutator’s stack. (This is needed to support conservative garbage collection of uncooperative code, where references might be stored by mutator on its stack.)
.req.stack.bottom.not: There is no requirement to locate the bottom
of the stack. (The mutator supplies this as an argument to
mps_root_create_reg()
.)
.req.registers: Must locate and scan all references in the root registers, the subset of registers which might contain references that do not also appear on the stack. (This is needed to support conservative garbage collection of uncooperative code, where references might appear in registers.)
28.3. Design¶
.sol.stack.top: Implementations find the top of the stack by taking the address of a local variable.
.sol.registers: Implementations spill the root registers onto the stack so that they can be scanned there.
.sol.registers.root: The root registers are the subset of the callee-save registers that may contain pointers.
.sol.registers.root.justify: The caller-save registers will have been spilled onto the stack by the time the MPS is entered, so will be scanned by the stack scan. Floating-point registers and debugging registers do not, as far as we are aware, contain pointers.
.sol.inner: Having located the hot end of the stack (stackHot
),
and spilled the root registers into the next n
words,
implementations call the generic higher-order function
StackScanInner(ss, stackCold, stackHot, n, scan_area, closure)
to actually do the scanning.
28.4. Interface¶
-
Res StackScan(ScanState ss, Word *stackCold,
-
mps_area_scan_t scan_area,
-
void *closure)
.if.scan: Scan the root registers of the current thread, and the
control stack between stackCold
and the hot end of the stack, in
the context of the given scan state, using scan_area
. Return
ResOK
if successful, or another result code if not.
28.5. Issue¶
.issue.overscan: This design leads to over-scanning, because by the
time StackScan()
is called, there are several MPS functions on the
stack. The scan thus ends up scanning references that belong the MPS,
not to the mutator. See job003525.
28.6. Implementations¶
.impl.an: Generic implementation in ssan.c
. This calls
setjmp()
with a stack-allocated jmp_buf
to spill the registers
onto the stack. The C standard specifies that jmp_buf
“is an array
type suitable for holding the information needed to restore a calling
environment. The environment of a call to the setjmp
macro
consists of information sufficient for a call to the longjmp
function to return execution to the correct block and invocation of
that block, were it called recursively.” Note that the C standard does
not specify where the callee-save registers appear in the jmp_buf
,
so the whole buffer must be scanned.
.impl.ix: Unix implementation in ssixi3.c
and ssixi6.c
.
Assembler instructions are used to spill exactly the callee-save
registers. (Clang and GCC support a common assembler syntax.)
.impl.w3: Windows implementation in ssw3i3mv.c
and
ssw3i6mv.c
. Like .impl.an, this implementation uses
setjmp()
with a stack-allocated jmp_buf
to spill the registers
onto the stack. However, we know the layout of the jmp_buf
used by
the compiler, and so can scan exactly the subset of registers we need.