Skip to content

Stack & Memory Model

🗂️ Stack & Memory Model

CrossBasic’s VM employs a stack-based execution model alongside Automatic Reference Counting (ARC) for memory management. This section breaks down how the VM’s evaluation stack, call frames, and ARC work together to manage memory safely and efficiently.


🆙 VM Evaluation Stack

  • Single shared stack (VM.stack: std::vector<Value>) holds all intermediate values during execution.
  • Push: Instructions like OP_CONSTANT, arithmetic ops, constructors, etc., push results onto the stack.
  • Pop: Instructions such as OP_POP, binary operators, and function calls remove values.
  • Stack discipline:

  • Arguments are pushed in order before a call.

  • OP_CALL pops N arguments + callee, invokes the function, then pushes exactly one return value.
  • Temporary values are cleaned up by popping to a saved depth after calls or on OP_POP.
  • Example
// Bytecode:     OP_CONSTANT 0   OP_CONSTANT 1   OP_ADD   OP_PRINT
// Constants[0]=2, [1]=3
Stack states:
  [] 
  [2]           ← OP_CONSTANT
  [2,3]         ← OP_CONSTANT
  [5]           ← OP_ADD (popped 2,3 → pushed 5)
  []            ← OP_PRINT (pops and prints 5)

🏗️ Call Frames & Environments

  • No explicit call-stack: Each script function invocation uses a new Environment and a recursive C++ call to runVM(...).
  • Environment chain:

  • globals: top‐level scope

  • environment: current scope, chained via enclosing pointers
  • On OP_CALL for scripted functions:

    1. Save current environment and stack depth
    2. Create child Environment for parameters and locals
    3. Invoke runVM(...) recursively on the function’s bytecode
    4. Restore the previous environment and trim the stack back
    5. Memory impact:
  • Each Environment is a shared_ptr—freed when no longer referenced.

  • No large stacks of frames build up in the VM.memory; C++ call stack handles nesting.

🔄 ARC & Object Lifetimes

Automatic Reference Counting (ARC) ensures deterministic deallocation of heap-allocated objects (classes, arrays, functions, modules, enums):

  1. Value holds std::shared_ptr<ObjX> for objects → internally increments/decrements counts.
  2. Object Creation

Var obj = New MyClass    ' ref count = 1
3. Assignment & Copying

Var alias = obj          ' ref count = 2
4. Release (goes out of scope or set to Nil)

obj = Nil                ' ref count = 1
alias = Nil              ' ref count = 0 → destructor runs
5. Immediate deallocation when count reaches zero.

⚠️ Circular References Two objects referring to each other never reach zero refs. Break cycles using weak references (not counted by ARC) or manual nulling.


🛠️ Value Representation

  • struct Value is a std::variant<…> covering:

  • Primitives: int, double, bool, std::string, Color

  • Objects: shared_ptr<ObjFunction>, ObjClass, ObjInstance, ObjArray, ObjModule, ObjEnum
  • Builtin FNs: std::function<Value(const std::vector<Value>&)>
  • PropertiesType, Overloads, Pointer (void*)
  • Memory on the heap:

  • Complex types live on the heap and are ref-counted.

  • Primitives and small variants live inline within the Value.

⚙️ Memory Safety Guarantees

CrossBasic enforces memory safety at the VM level:

  • No uninitialized reads: all stack slots are explicitly pushed.
  • No buffer overruns: array access checks bounds and auto‐grows on write.
  • No dangling pointers: ARC frees only when truly unused.
  • No double frees: shared_ptr prevents multiple deletions.

Note: ARC prevents use-after-free but cannot detect logical leaks (circular refs).


📌 Key Takeaways

  • The VM stack holds all intermediate values; it’s tightly managed per‐instruction.
  • Environments create scoped storage for locals & parameters, freed when calls return.
  • ARC via shared_ptr gives deterministic object lifetimes—objects are destroyed the moment they’re no longer referenced.
  • Memory safety is guaranteed, but memory leaks can still occur from cycles—use weak references to break them.

With this model, CrossBasic marries the predictability of a stack machine with the efficiency of ARC, ensuring both fast execution and safe memory management.