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.