The instruction set on one page. The normative version — with stack effects, errors, and the
validator rules — is the opcode reference .
[..., a, b] means b is on top. k = constant index, s = slot, u = upvalue index,
t = absolute jump target. Result and argument counts are biased : 0 means MULTRET,
n means n-1 values (Lab 17 ).
Op Operands Effect
LOAD_CONSTk:u16push constants[k]
LOAD_INTn:i32push Integer(n) — inline, no pool slot
LOAD_NILpush Nil
LOAD_TRUE / LOAD_FALSEpush the boolean
Op Operands Effect
GET_LOCALs:u8push stack[base+s] — one add, one load
SET_LOCALs:u8pop into stack[base+s]
POPn:u8drop n — how a scope closes
Op Operands Effect
GET_GLOBALk:u16push globals[constants[k]]; missing → Nil
SET_GLOBALk:u16pop into the globals table
Op Operands Effect
GET_UPVALu:u8push the upvalue's value (open → stack, closed → heap)
SET_UPVALu:u8pop into the upvalue
CLOSE_UPVALSs:u8close every open upvalue at or above base+s, then truncate
Op Stack Errors
ADD SUB MUL[a,b] → [c]non-number operand
DIV POW[a,b] → [Float]non-number operand
IDIV MOD[a,b] → [c]non-number; integer divisor 0
CONCAT[a,b] → [Str]operand not string/number
NEG[a] → [b]non-number
NOT[a] → [Bool]never errors
LEN[a] → [Int]not a string or table
Op Stack Errors
EQ NE[a,b] → [Bool]never error
LT LE GT GE[a,b] → [Bool]incomparable types
GT/GE exist because Ember guarantees left-to-right operand evaluation and therefore cannot use
Lua's operand-swap trick.
Op Operands Effect
JUMPt:u32ip = t
JUMP_IF_FALSEt:u32pop; jump if falsy
JUMP_IF_FALSE_KEEPt:u32peek; jump if falsy; does not pop — for and
JUMP_IF_TRUE_KEEPt:u32peek; jump if truthy; does not pop — for or
Op Operands Effect
CALLargc:u8, nres:u8callee sits below its args; nres = 0 → all results
RETURNn:u8n = 0 → all; closes upvalues before truncating
CLOSUREp:u16build a closure from protos[p], capturing per its descriptors
Op Operands Stack
NEW_TABLE[] → [Table]
GET_INDEX[t,k] → [v]
SET_INDEX[t,k,v] → []
GET_FIELDk:u16[t] → [v] — constant key; the inline-cache site
SET_FIELDk:u16[t,v] → []
SET_LISTn:u16, off:u32[t,v₁..vₙ] → [t]
SELF_FIELDk:u16[t] → [t.k, t] — evaluates t once
Op Operands Effect
FOR_PREPt:u32validate; convert; compute the iteration count ; jump to t if zero
FOR_LOOPt:u32decrement the count; advance; jump back to t if positive
Counting down is what makes for i = math.maxinteger - 1, math.maxinteger do terminate.
Op Operands Effect
VARARGn:u8push n-1 varargs; 0 → all
code.len() == lines.len()
Constant indices in range
Name operands are string constants
Jump targets ≤ code.len(), and none is u32::MAX (unpatched)
Slot numbers < max_stack
Upvalue indices < upvals.len()
Proto indices in range
The last instruction is a RETURN
Stack-depth abstraction : never negative, agrees at every join, ends at 0
Rule 9 is only tractable because Ember is a stack machine — the same reason the JVM and WebAssembly
chose one.
Ember Lua 5.4 CPython 3.12 JVM Wasm 1.0
Model stack register stack stack stack
Opcodes ~44 83 ~120 202 ~180
Instruction size 8 B enum 4 B packed 2 B + caches 1–n B LEB128
Jump targets absolute relative relative relative structured
Verification validator none none full full
Next: ADR Index .