The Frame object has no public member variables from the SuperCollider
language side. Inside the interpreter, Legacy SuperCollider (henceforth LSC) defines the
I haven’t found any documentation about the intended uses of the members of
PyrFrame, my reading of the code,
executeMethodWithKeys inside of
lang/LangSource/PyrMessage.cpp, leads me to suppose the
method: Contains a
Methodinstance associated with the executable currently running. On frame creation, LSC sets
methodto the method about to be called and then sets a global variable method field to the same value. The interpreter resolves
thisMethodto the same global variable method field, so the
methodfield usually has the same semantics as
Framefrom the calling code, restored as the active frame when returning.
Framedefining the next outermost context for any nested functions. LSC makes this self-referential for top-level method code with no outer context.
Framedefining the top outermost context for any nested functions. For a top-level method, LSC makes this self-referential.
ip: An instruction pointer for continuing in this frame, should the code call another method or yield.
vars: Storage for every local variable in the frame. A size one array as the last element in a structure is a common idiom in LSC code that implies that
PyrFrameinstances will size this array to accommodate all the local variables stored in the frame.
SuperCollider supports lexical closure, meaning that in some instances,
Frame objects may outlive the code invocation they support. A contained
Function object keeps a reference to the outer context
Frame in its
context member. This reference prevents the premature garbage collection of the
Frame until the inner
Most methods are closed, meaning they don’t use lexical closure and don’t need the frame to outlive their invocation. Future optimization work could skip allocations of separate frame objects for closed method chains.
Hadron reserves a CPU register for a frame pointer that points at the current
Frame instance. Hadron maintains
function arguments and local variables here, saving any modifications to their location relative to the frame pointer.
On method invocations, Hadron writes the instruction pointer in the
Frame instance for returning to the calling code
on method return. At runtime, Hadron uses the following frame pointer structure:
||Argument 0 (this) / Return Value|
||Argument n - 1|
||Local Variable 0|
||Local Variable m - 1|
||< register spill area >|
Hadron also reserves a CPU register for a stack pointer, which points at an incomplete
Frame instance used for
constructing new messages. The calling code copies the in-order and keyword-based arguments into the new
SuperCollider is a dynamic programming language, so we often don’t know the message’s intended recipient at compile time. That means we don’t know how many arguments the callee code expects or the names of those arguments. SuperCollider copies default values into the frame for any missing in-order arguments and then overwrites any named arguments with the provided key/value pairs. See the spec for details about parameter assignments to arguments.
Frames contain the interpreter state variables, arguments, local variables including any inlined frames, and register spill space. Hadron maintains a per-selector maximum size tracking the upper bound of frame size across all methods with the same name. The upper bound is computed as the maximum number of arguments for that selector, plus the maximum number of local variables for that message, plus the fixed constant maximum number of spill registers.
Computing the maximum number of arguments ensures that when we set up the new frame for the message we won’t overwrite argument defaults with keyword/value argument pairs. We use the total maximum size to determine if there is sufficient room to support calling a message without allocating additional memory.
We also compute the exact frame size needed for each
Method, allowing for more accurate size computation when the
method calls are unambiguous. This figure is also useful when allocating separate frames for open functions.
TODO: variadic functions?
Large-size allocations, perhaps with some hysteresis on garbage collection when calling across boundaries. Normally the frame pointer points ahead of the stack in the same large allocation.
Dynamic dispatch refers to all method calls where the target object is unknown at compile time. We include special cases for variadic arguments and non-closed functions.
To avoid recopying arguments we lay out a
Frame object instance with the arguments in their expected position, but
re-use some of the other fields before the arguments start to contain all the information known about the message at
compile time. At message send time the Frame object is laid out as follows:
||Preserved to support object identification|
||Selector symbol||Dynamic dispatch will replace with the
||Caller saves a copy of their frame pointer here|
||Number of In-Order Arguments (j)|
||Number of Keyword / Value Pairs (k)|
||Caller saves their return instruction address here|
||Argument 0 (callee this)||In-Order arguments start here with
||Argument j - 1|
||Keyword / Value Pair 0|
sp is pointing at the first free spot on stack after the current
Check for available stack space in
sp against the maximum frame size for the given selector.
If there’s not enough room:
Interrupt for allocate stack frame, will redirect
sp to new frame
HadronPrototypeFrame object according to above
Save selector symbol into
sp->selector - redundant because we’re branching to that code?
Save instruction pointer for return address label into
Save in-order and key/value arguments
Branch to Selector-specific dispatch code.
Selector-specific dispatch code:
In its simplest form, this is a series of
if statements comparing against the class name hash in the message target.
If it finds a match on the class name hash, it executes a preamble that completes frame initialization and branches to the compiled method code.
Variable argument messages expect the last argument to be an
Array containing any additional arguments specified.
The dispatch code creates a new
Array object from those arguments during stack setup. We then overwrite the final
argument in the stack with a pointer to that new