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_CALLpops 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
Environmentand a recursive C++ call torunVM(...). -
Environment chain:
-
globals: top‐level scope environment: current scope, chained viaenclosingpointers-
On
OP_CALLfor scripted functions:- Save current
environmentand stack depth - Create child
Environmentfor parameters and locals - Invoke
runVM(...)recursively on the function’s bytecode - Restore the previous
environmentand trim the stack back - Memory impact:
- Save current
-
Each
Environmentis ashared_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):
Valueholdsstd::shared_ptr<ObjX>for objects → internally increments/decrements counts.- Object Creation
Nil) 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 Valueis astd::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_ptrprevents 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_ptrgives 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.