[DRAFT] Stack Frames and calling convention - scramjet-js/scramjet GitHub Wiki

Goals

  1. Minimize function call overhead
  2. Minimize copying
  3. Maximize locality
  4. Maximize function performance

Overview

The interpreter maintains a stack of Frame objects; the top of the stack is the one being executed. When a function is called, a new Frame is created and pushed onto the stack. When a function returns, the last frame is popped and execution continues with the previous frame (if there was one) or stops if there are no more frames.

Calling convention

Invocation

  1. Push arguments on stack
  2. Push argument count on stack
  3. Push function address (or offset) on stack
  4. Execute appropriate call opcode

Return

  1. Push return value onto stack
  2. Execute e_Exit opcode

Stack Frame Structure

A given stack frame has as context:

  1. firstCode -- pointer to first opcode, used to compute offsets
  2. pc -- program counter, next operation to execute
  3. bottom -- bottom of the stack for this frame

Interpreter operation

Invocation

  1. When a function is called, the interpreter pops off the stack an integer containing the number of arguments for the function.
  2. If the number of argument is less than Bytecode::s_MinimumLocals, the interpreter pushes numLocals - s_MinimumLocals values containing DatumUdtUtil::s_Undefined.

Return

  1. The interpreter saves the top of the stack -- the return value.
  2. Then it resets the top of the stack back to the index before the bottom of the last rame.

Implications for function evaluation

  1. Arguments are immediately available as locals without copying.
  2. Access to locals must be computed (albeit fairly simply).
  3. Functions with more (named) arguments than the minimum guaranteed number of locals (or those wanting more locals for whatever reason) may call reserve to get more.
  4. Basically, by convention (but not required), a function may treat the bottom of its stack as a set of local variables; its arguments are put there by default.

Notes on performance

  1. Uniform value size (of Datum) allows for random access of the stack.
  2. Other than pushing arguments on the stack, no additional copying is required.
  3. Function call overhead involves just initializing a few integers and pointers.
  4. Only dynamic per-frame storage is in the (single) program stack.