This page is for people working on the interpreter itself. Kiwi source is compiled to bytecode and executed by a stack-based virtual machine. The code lives in src/VM/.
The standard library is parsed and compiled together with the script into a single top-level chunk, so it is loaded before the first line of user code runs.
A Chunk (src/VM/Chunk.cs) is the compiled form of one unit of code: the program, a function, or a lambda. Functions and lambdas get their own chunks, stored in their parent’s SubChunks.
Field
Contents
Code
The instructions.
Constants
Literal values referenced by index.
Names
Interned identifier strings referenced by index.
SubChunks
Nested function and lambda chunks.
Upvalues
Descriptors for variables captured from enclosing functions.
Lines, Columns, FileIds
Source positions for each instruction, used in error messages.
The VM uses one value stack of 8,192 slots shared by all frames, and a frame stack of 512 frames. Each frame (VMFrame) records its chunk, instruction pointer, the stack index where its locals begin (StackBase), its captured upvalues, the current instance (@) and struct (@@), and its active try handlers.
StackBase + 0 parameter 0
StackBase + 1 parameter 1
StackBase + 2 ... other locals
StackBase + LocalCount temporaries (grow upward)
RunLoop fetches and dispatches instructions for the top frame. A call to compiled Kiwi code pushes a new frame and keeps dispatching; native builtins run in place and push their result. Return pops the frame and resumes the caller.
A closure captures variables through upvalues. An upvalue starts open, pointing at the variable’s stack slot so that the function and the closure share it. When the enclosing frame returns, its open upvalues are closed: the value moves into the upvalue itself, so the closure keeps working after the frame is gone. Two closures that capture the same variable share one upvalue. A for loop closes the loop variable’s upvalue (CloseUpvalueAt) at the start of each iteration, so the closures created in one iteration keep that iteration’s value.
try blocks push a handler (PushTryHandler) recording the catch and finally targets and the stack height. When an error is thrown, the VM unwinds frames to the nearest handler, restores the stack height and jumps to the catch target, where LoadCatchError loads the error in the form the catch clause asks for.
A finally block is compiled inline on every path out of the try statement: after the try block, after the catch block, before a return, break or next that leaves it, and on an error path that keeps the error in a hidden local, runs the block and then throws the error again (Rethrow).
Close all open upvalues whose absolute slot >= base + A
CloseUpvalueAt
Close the open upvalue for slot base + A only, if there is one. Emitted at the top of each for iteration so closures created in the body keep that iteration's value.
A = arg count. Stack layout before: [..., func_value, arg0, …, argN-1]. Calls the function, replaces func+args with the return value.
CallMethod
A = arg count, B = name index (names[B] = method name). Stack before: [..., obj, arg0, …, argN-1]. Dispatches the method call; replaces obj+args with result.
CallSuperMethod
A = arg count, B = name index (names[B] = method name). Stack before: [..., arg0, …, argN-1] (no obj — uses frame.Self and frame.OwnerStruct). Dispatches a super.method() call starting from the base of the defining struct.
A = sub-chunk index, B = name index (names[B] = function name). Register a compiled KFunction in the context. Does not push a value.
DefFuncAndPush
A = sub-chunk index, B = name index (names[B] = raw function name). Like DefFunc but also wraps the function as an internal KLambda and pushes a lambda-ref onto the stack. Used by decorator compilation so the function can be passed as a value to the decorator callable without a separate LoadGlobal.
StoreDecoratedFunc
A = name index (raw function name). Pop the top-of-stack (the final decorated value); compute the package-qualified function name; remove the original KFunction entry from Context.Functions; then register the decorated result (lambda mapping or global assignment). Used as the last step of decorator application.
MakeClosure
A = sub-chunk index, B = number of upvalue descriptors that follow as inline (isLocal, index) pairs encoded in subsequent Nop-like slots. Pushes the resulting closure Value.
pop value, pop key, pop obj → obj[key] = value (pushes value)
IndexOpAssign
A = inner opcode (as int). pop rhs, pop key, pop obj → old = obj[key]; new = op(old, rhs); obj[key] = new (pushes new). Handles compound index assignment: lst[i] += v.
UnpackList
A = count. Pop a value; if it's a list unpack A elements; else push value then A-1 nulls. Used for PackAssignment: a, b = func_returning_list().
SliceGet
A = flags: bit0=hasStart, bit1=hasStop, bit2=hasStep. Pops (in order): obj, start?, stop?, step? → push slice.
SliceSet
A = flags: bit0=hasStart, bit1=hasStop, bit2=hasStep. Pops (in order from stack top): rhs, step?, stop?, start?, obj. Applies rhs to the slice range of obj in-place; pushes rhs.
Pop the collection and push a ForIterState (held directly in the stack slot).
ForIterInitRange
Pop stop, pop start → push a ForIterState that counts from start to stop inclusive (downward when stop < start). Used for for x in [a to b] so the range is never materialized as a list.
ForIterNext
A = jump-if-done target IP, B = number of loop vars to push (1 or 2). Peek at the ForIterState on top of stack: - If done: pop state, jump to A. - Else: push next value(s) (B values) without removing state.
Push 0 onto the splat-adjustment side-stack. Emitted before the function/object of any call that contains splat arguments, so nested splat calls each get their own adjustment slot.
SplatPush
Pop value. If it is a list, push each element and add (count-1) to the top of the splat-adjustment side-stack. Otherwise push the value unchanged (single-element, zero adjustment).
CallSplat
Like Call but argc = A + splatAdjStack.Pop(). Used for function/lambda calls that contain one or more splat arguments.
MethodCallSplat
Like CallMethod but argc = A + splatAdjStack.Pop(). Used for method calls that contain one or more splat arguments.
CallNamed
Like Call but reorders args to match the callee's declared parameter order. A = argc, B = index into chunk.ArgNameSets (parallel string[] of arg names; empty string = positional, non-empty = named arg name).
CallMethodNamed
Like CallMethod but reorders args before dispatch. A = argc, B = packed: lower 16 bits = method name index in chunk.Names, upper 16 bits = index into chunk.ArgNameSets.
A = catch IP, B = finally IP (0 = none). Pushes a try-handler frame: on KiwiError, restore _sp to current value, jump to catch IP, and store the caught error in _caughtError.
PopTryHandler
Pop the top try-handler frame (try body completed normally).
LoadCatchError
A = 0 → push error hashmap {error, message} (single-param catch) A = 1 → push error type string (first of two-param catch) A = 2 → push error message string (second of two-param catch) A = 3 → push the error itself, to rethrow after a finally block Reads from the VM's _caughtError field set when a handler fires.
CheckType
A = type-check index. Throw a TypeError unless the value on top of the stack (left in place) matches the type hint chunk.TypeChecks[A]: a parameter, return value or var.
Rethrow
Pop an error pushed by LoadCatchError (A = 3) and throw it again, so an error leaving a try with a finally block keeps propagating once the block has run.
A = name index. B = packed: bit 0 = isAbstract, bits 1+ = baseNameIdx+1 (0 = no base). Create KStruct and push onto the pending-struct stack.
DefMethod
A = sub-chunk index, B = nameIdx | (isAbstract ? 0x80000000 : 0) | (isStatic ? 0x40000000 : 0) | (isPrivate ? 0x20000000 : 0) | (isDecorated ? 0x10000000 : 0). Like DefFunc but registers the method into the current pending struct. A decorated method is also pushed as a value for its decorators.
StoreDecoratedMethod
A = name index. Pop the decorated value of a method and install it in the pending struct: calling the method calls that value, with @ still the instance.
InitStructStatic
A = name index. Pop value → store in pending struct's StaticVariables[names[A]].
StructEnd
Validate abstract compliance, register pending struct in Context.Structs.
A = name index, B = node-pool index (stores PackageNode AST for retry). Build qualified name, push onto PackageStack, register KPackage.
PackageEnd
Pop PackageStack; register type builtins; mark package imported.
PackageAbort
Catch path for a failed package activation: pop PackageStack without marking as imported. Allows ImportPackage to retry after dependencies are resolved.
A = node-pool index (FunctionCallNode, used for Token and Op). B = argc. Pop B args (in order), call _interp.ExecuteBuiltin, push result. Used for C# native builtin calls (KiwiBuiltin, SocketBuiltin, Math, etc.) that are not CoreBuiltin method calls.