src/nsengine/program.ts defines the instruction set; src/nsengine/executor.ts
defines its semantics.
interface Instruction {
opcode: number;
operand: any; // number | string | boolean | JS object | null
index?: number; // compile-time only, stripped by finalize()
label?: string; // compile-time only
}
interface Program {
engineVersion: string;
nenv: Nenv;
instructions: Instruction[];
}Operands are untyped JavaScript values. OP_LOAD_EXTERNAL carries the host
object itself; OP_LOAD_CONST_COPY carries a pre-built array/object/Set;
OP_LOAD_LITERAL_OBJECT carries an IObjectGenerator. A Program is therefore a
live JS structure, not a serialisable buffer — the WASM backend deals with this by
interning non-numeric operands into a reference table
(see wasm-engine.md).
Opcodes are assigned sequentially by an incrementing counter, so the declaration
order in program.ts is the numeric order, and executor.ts's ops array must
be kept in exactly the same order. OP_NAMES is a parallel array used by
getOpName, printInstruction and printProgram.
OP_<NAME><dtype><size>_<location>
dtype i int f float s string b bool o object
size 8 32 64
location o = instruction operand s = stack
The 8/32/64 suffixes describe the intended width of a future typed value
representation. In the current JS executor all three variants map to the same
handler — OP_LOAD_LOCAL8, OP_LOAD_LOCAL32 and OP_LOAD_LOCAL64 are all
op_load_local. The distinction is preserved because the compiler uses it to pick
the right store/increment rewrite.
[ … ] → [ … ] shows the top of stack on the right.
| Opcode | Operand | Stack | Effect |
|---|---|---|---|
OP_NOOP |
— | [] → [] |
Advance. Emitted to flush a pending label. |
OP_TERM |
— | — | Halt. The executor returns the top of stack, or 0 if empty. |
OP_JUMP |
target index | [] → [] |
ip = operand |
OP_BRANCH_TRUE |
target index | [v] → [] |
Jump if v truthy |
OP_BRANCH_FALSE |
target index | [v] → [] |
Jump if v falsy |
OP_BRANCH_NULL |
target index | [v] → [] |
Jump if v == null |
OP_BRANCH_NOT_NULL |
target index | [v] → [] |
Jump if v != null |
OP_BRANCH_EQUAL8/32/64, OP_BRANCH_NOT_EQUAL8/32/64,
OP_BRANCH_GREATER_THAN8/32/64, OP_BRANCH_LESS_THAN8/32/64,
OP_BRANCH_GREATER_THAN_OR_EQUAL8/32/64, OP_BRANCH_LESS_THAN_OR_EQUAL8/32/64
All pop two values and jump on the comparison. The compiler never emits them — it
emits a comparison opcode followed by OP_BRANCH_FALSE. They exist as a
peephole-optimisation target.
All three width variants share one handler. The handler pops
bthenaand testsa OP b, the same operand order as the non-branching comparisons, so a future peephole pass can substitute one for the other.testing/regressions.test.tscovers them with hand-built programs.
| Opcode | Operand | Stack | Effect |
|---|---|---|---|
OP_LOAD_LITERAL_BOOL |
boolean | [] → [b] |
|
OP_LOAD_LITERAL_INT32 |
number | [] → [n] |
|
OP_LOAD_LITERAL_FLOAT64 |
number | [] → [n] |
|
OP_LOAD_LITERAL_NULL |
— | [] → [null] |
|
OP_LOAD_LITERAL_STRING |
string | [] → [s] |
|
OP_LOAD_INSTRUCTION_REFERENCE |
"$name" |
[] → [addr] |
Pushes a code address. Normally rewritten in place to OP_CALL_INTERNAL. |
OP_LOAD_PTR |
any JS value | [] → [v] |
Pushes a pre-built constant by reference (no longer emitted) |
OP_LOAD_CONST_COPY |
any JS value | [] → [v] |
Pushes a deep copy of a pre-built constant. What folded collection literals compile to |
OP_DUP |
— | [v] → [v v] |
Duplicates the top slot. Used by &&, || and ?. |
OP_LOAD_LITERAL_LIST |
count n |
[v1…vn] → [array] |
Pops n, builds an Array |
OP_LOAD_LITERAL_SET |
count n |
[v1…vn] → [Set] |
Pops n, builds a Set |
OP_LOAD_LITERAL_OBJECT |
IObjectGenerator |
[v1…vn] → [object] |
Pops one value per declared property |
Elements for list/set/object literals are compiled in reverse source order so that popping restores the correct order.
| Opcode | Stack | Effect |
|---|---|---|
OP_ADDi / OP_ADDf / OP_ADDs |
[a b] → [a+b] |
All three run JS + |
OP_SUBi / OP_SUBf |
[a b] → [a-b] |
|
OP_MULi / OP_MULf |
[a b] → [a*b] |
|
OP_DIVi / OP_DIVf |
[a b] → [a/b] |
Integer division is not truncated |
OP_MODi / OP_MODf |
[a b] → [a%b] |
|
OP_POWi / OP_POWf |
[a b] → [a**b] |
|
OP_NEGi / OP_NEGf |
[a] → [-a] |
|
OP_RANGEi32 / OP_RANGEf64 |
[start end] → [array] |
Inclusive of both ends; always steps by 1 |
OP_SB_REPLACE_s — operand: placeholder count n.
Pops the template string, then pops n values, replacing ${0}…${n-1} in turn.
| Opcode | Stack | Effect |
|---|---|---|
OP_GREATER_THANi/f |
[a b] → [a>b] |
|
OP_LESS_THANi/f |
[a b] → [a<b] |
|
OP_GREATER_THAN_OR_EQUALi/f |
[a b] → [a>=b] |
|
OP_LESS_THAN_OR_EQUALi/f |
[a b] → [a<=b] |
|
OP_EQUALi/f/b/s/o |
[a b] → [a==b] |
Loose == |
OP_NOT_EQUALi/f/b/s/o |
[a b] → [a!=b] |
Loose != |
OP_ANDb |
[a b] → [a&&b] |
Never emitted: && compiles to a branch |
OP_ORb |
[a b] → [a||b] |
Never emitted: || compiles to a branch |
OP_XORb |
[a b] → [a^b] |
Bitwise XOR on the JS values |
OP_NOTb |
[a] → [!a] |
| Opcode | Effect |
|---|---|
OP_INT_TO_FLOAT |
No-op (both are JS numbers) |
OP_FLOAT_TO_INT |
Math.floor |
OP_INT_TO_STRING |
.toString() |
OP_FLOAT_TO_STRING |
.toString() |
| Opcode | Operand | Stack | Effect |
|---|---|---|---|
OP_LOAD_LOCAL8/32/64 |
slot offset | [] → [v] |
push(stack[fp + operand]). Negative offsets address arguments. |
OP_STORE_LOCAL8/32/64 |
slot offset | [v] → [] |
stack[fp + operand] = pop() |
OP_LOAD_MEMBER8/32/64 |
property name | [obj] → [v] |
An absent member pushes null; a null object throws. Functions are .bind(obj)-ed. |
OP_STORE_MEMBER8/32/64 |
property name | [v obj] → [] |
obj[name] = v |
OP_LOAD_ELEMENT8/32/64 |
index count | [obj idx] → [v] |
Always one index; the compiler rejects a[i, j] |
OP_STORE_ELEMENT8/32/64 |
index count | [v obj idx] → [] |
|
OP_LOAD_EXTERNAL |
the host object | [] → [obj] |
|
OP_INCREMENT_LOCALi32/f64 |
slot offset | [d] → [] |
stack[fp+op] += pop() |
OP_DECREMENT_LOCALi32/f64 |
slot offset | [d] → [] |
stack[fp+op] -= pop() |
OP_INCREMENT_MEMBERi32/f64 |
property name | [d obj] → [] |
obj[name] += d |
OP_DECREMENT_MEMBERi32/f64 |
property name | [d obj] → [] |
|
OP_INCREMENT_ELEMENTi32/f64 |
— | [d obj idx] → [] |
obj[idx] += d |
OP_DECREMENT_ELEMENTi32/f64 |
— | [d obj idx] → [] |
The increment/decrement family leaves nothing on the stack, so
x++cannot be used as a sub-expression that yields a value. The compiler enforces this: using an assignment or increment in value position is a compile error.
| Opcode | Operand | Effect |
|---|---|---|
OP_ALLOC_STACK |
count | Push count × null |
OP_POP_STACK |
count | Pop count values |
OP_ALLOC_HEAP |
count | Push count × null onto heap (never emitted) |
OP_POP_HEAP |
count | Pop count from heap (never emitted) |
| Opcode | Operand | Effect |
|---|---|---|
OP_CALL_INTERNAL |
target index | push(fp), push(ip+1), fp = stack.length, ip = operand |
OP_CALL_EXTERNAL |
arg count n |
Pops the callee; reads (does not pop) n args below it; result → ret |
OP_CALL_STACK |
— | Like OP_CALL_INTERNAL but takes the target from the stack (never emitted) |
OP_STACKPOP_VOID |
— | ret = null, unwind to fp, ip = pop(), fp = pop() |
OP_STACKPOP_RET8/32/64 |
— | ret = pop(), unwind to fp, ip = pop(), fp = pop() |
OP_PUSH_RETURN8/32/64 |
— | push(ret) |
The three RET widths and the three PUSH_RETURN widths are identical handlers.
The return value travels in the executor's ret register, not on the stack, which
is why OP_PUSH_RETURN64 is a separate instruction emitted only when the caller
wants the value.
OP_STACKPOP_VOIDunwinds the frame's locals exactly like theRETvariants, and clearsret, so a void call cannot resurrect the previous call's value.
| Opcode | Operand | Stack | Effect |
|---|---|---|---|
OP_WRAP_COLLECTION |
1 | [coll] → [iter] |
Wraps in a CollectionIterator |
OP_INCREMENT_ITERATOR |
target index | [iter] → [iter v] or [iter] → [] |
If exhausted: pop the iterator and jump to operand. Otherwise push the next value. |
The iterator stays on the stack for the loop's whole lifetime, one slot below the
loop body's working area. It is popped by OP_INCREMENT_ITERATOR when the
collection runs out; a break or a multi-level continue that leaves the loop
early emits its own OP_POP_STACK for each iterator it escapes.
import { printProgram, programToString, printInstruction } from './nsengine/program';
printProgram(program); // console.table of opcode/operand
programToString(program); // "[0]\tOP_LOAD_LITERAL_INT32 5\n…"Compiling with { verboseMode: true } prints both the pre-link and final program
plus the instruction reference table.