From c7d44584d98e9547f7eab05491d07c86bfbc0cbd Mon Sep 17 00:00:00 2001 From: uditdey Date: Wed, 2 Sep 2026 13:16:05 +0000 Subject: [PATCH] Initial SSA impl + docs --- compiler/src/flatten.rs | 4 +- compiler/src/main.rs | 6 +- compiler/src/parse.rs | 10 +- compiler/src/ssa.rs | 140 +++++++-- docs/FORMAT.md | 502 ++++++++++++++++++++++++++++++++ docs/OPCODES.md | 626 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1255 insertions(+), 33 deletions(-) create mode 100644 docs/FORMAT.md create mode 100644 docs/OPCODES.md diff --git a/compiler/src/flatten.rs b/compiler/src/flatten.rs index 4350673..c1910a4 100644 --- a/compiler/src/flatten.rs +++ b/compiler/src/flatten.rs @@ -1,7 +1,7 @@ //! This module takes the nested QuickJS binary structure and separates the functions into a flat indexed namespace use crate::parse::{ - Atom, AtomRef, ClosureKind, Function, Instruction, JsString, JsValue, QuickJSBinary, atom_names, + Atom, AtomRef, ClosureKind, Function, Instr, JsString, JsValue, QuickJSBinary, atom_names, }; #[derive(Debug, Clone, Copy)] @@ -34,7 +34,7 @@ pub struct FlatFn { pub var_count: usize, // num locals pub captures: Vec, pub cpool: Vec, - pub code: Vec, + pub code: Vec, } pub fn flatten(bin: QuickJSBinary) -> (Vec, Vec) { diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 8bf16b0..2f46a05 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -6,6 +6,7 @@ use std::process::Command; use flatten::flatten; use parse::QuickJSBinary; +use ssa::convert_ssa; const QJSC: &str = "../third_party/quickjs/build/qjsc"; const BIN_OUT: &str = "/tmp/qjsrt.bin"; @@ -16,7 +17,7 @@ fn main() { // Compile source file let output = Command::new(QJSC) - .args(["-b", "-m", "-o", BIN_OUT]) + .args(["-b", "-o", BIN_OUT]) .arg(filename) .output() .unwrap(); @@ -27,4 +28,7 @@ fn main() { let bin = std::fs::read(BIN_OUT).unwrap(); let (_, parsed) = QuickJSBinary::parse(&bin).unwrap(); let (flat_fns, atoms) = flatten(parsed); + + println!("{atoms:#?}"); + convert_ssa(flat_fns); } diff --git a/compiler/src/parse.rs b/compiler/src/parse.rs index 84408a4..4a6aa71 100644 --- a/compiler/src/parse.rs +++ b/compiler/src/parse.rs @@ -516,13 +516,13 @@ pub enum ImmOperand { }, } -pub struct Instruction { +pub struct Instr { pub pc: usize, pub op: opcodes::OpCode, pub imm_operands: ImmOperand, } -impl Debug for Instruction { +impl Debug for Instr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:04} {}", self.pc, self.op.name())?; match &self.imm_operands { @@ -544,7 +544,7 @@ impl Debug for Instruction { // - Jump operands are offsets from the operand field's own position, // not from the start or end of the instruction. For the // `atom_label_*` formats that base sits 4 bytes into the operands. -fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec> { +fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec> { let jump = |base: usize, offset: i64| (base as i64 + offset) as usize; let mut out = Vec::new(); @@ -635,7 +635,7 @@ fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec> { }, }; - out.push(Instruction { + out.push(Instr { pc, op: info.op, imm_operands: operands, @@ -665,7 +665,7 @@ pub struct Function { pub closure_vars: Vec, pub cpool: Vec, /// LLM GENERATED, TENTATIVE - pub byte_code: Vec, + pub byte_code: Vec, } impl Function { diff --git a/compiler/src/ssa.rs b/compiler/src/ssa.rs index 3967cfa..4861335 100644 --- a/compiler/src/ssa.rs +++ b/compiler/src/ssa.rs @@ -1,45 +1,68 @@ #![allow(non_snake_case)] -use crate::parse::{ImmOperand, Instruction}; +use crate::{ + flatten::FlatFn, + parse::{ImmOperand, opcodes::OpCode}, +}; +fn is_jump_op(op: OpCode) -> bool { + use OpCode::*; + matches!( + op, + Goto | Goto8 | Goto16 | IfFalse | IfTrue | IfFalse8 | IfTrue8 + ) +} + +fn is_terminal_op(op: OpCode) -> bool { + use OpCode::*; + matches!(op, Return | ReturnUndef | ReturnAsync | Throw) +} + +#[derive(Debug)] +enum Terminator { + UncondJump(usize), + CondJump { + truthy: bool, + value: usize, + true_block: usize, + false_block: usize, + }, + Terminal, +} + +#[derive(Debug)] struct BlockSpan { start: usize, end: usize, + term: Terminator, } -fn find_basic_blocks(code: &[Instruction]) -> Vec { +fn find_basic_blocks(flat_fn: &FlatFn) -> Vec { // Find block starts: the first instruction, every jump target, // and whatever follows a jump or a return - let mut starts = vec![0usize]; + let code = &flat_fn.code; + let mut starts = vec![0]; for (i, instr) in code.iter().enumerate() { - use crate::parse::opcodes::OpCode::*; + // If cond/uncond jump, jump target is a start, next instr (if exists) is another start + if is_jump_op(instr.op) { + let jump_target = match instr.imm_operands { + ImmOperand::Label { target } => code.iter().position(|x| x.pc == target).unwrap(), + _ => unreachable!(), + }; - match instr.op { - // If cond/uncond jump, jump target is a start, next instr (if exists) is another start - Goto | Goto8 | Goto16 | IfFalse | IfTrue | IfFalse8 | IfTrue8 => { - let jump_target = match instr.imm_operands { - ImmOperand::Label { target } => { - code.iter().position(|x| x.pc == target).unwrap() - } - _ => panic!("branch opcode without a label operand"), - }; + starts.push(jump_target); - starts.push(jump_target); - - if i + 1 < code.len() { - starts.push(i + 1); - } + if i + 1 < code.len() { + starts.push(i + 1); } + } - // If terminal, next instr if exists is a start - Return | ReturnUndef | ReturnAsync | Throw => { - if i + 1 < code.len() { - starts.push(i + 1); - } + // If terminal, next instr (if exists) is a start + if is_terminal_op(instr.op) { + if i + 1 < code.len() { + starts.push(i + 1); } - - _ => {} } } @@ -53,8 +76,75 @@ fn find_basic_blocks(code: &[Instruction]) -> Vec { .map(|(i, &start)| BlockSpan { start, end: starts.get(i + 1).copied().unwrap_or(code.len()), + term: Terminator::Terminal, }) .collect::>(); blocks } + +#[derive(Debug)] +struct SsaInstr { + dsts: Vec, + op: OpCode, + imm: ImmOperand, + args: Vec, +} + +fn ssa_walk(flat_fn: &FlatFn) -> Vec { + //let mut locals = vec![]; + let mut stack = vec![]; + let mut output = vec![]; + let mut next_val = 0; + + let mut fresh_val = || { + let v = next_val; + next_val += 1; + v + }; + + for instr in &flat_fn.code { + let mut ssa_instr = SsaInstr { + dsts: vec![], + op: instr.op, + imm: instr.imm_operands, + args: vec![], + }; + + let info = instr.op.info(); + let n_pop = info.n_pop; + let n_push = info.n_push; + + for _ in 0..n_pop { + let v = stack.pop().unwrap(); + ssa_instr.args.push(v); + } + + for _ in 0..n_push { + let v = fresh_val(); + stack.push(v); + ssa_instr.dsts.push(v); + } + + output.push(ssa_instr); + } + + output +} + +fn compute_terminators(ssa: &[SsaInstr], blocks: &mut [BlockSpan]) { + for instr in ssa { + if is_jump_op(instr.op) {} + } +} + +pub fn convert_ssa(flat_fns: Vec) { + for flat_fn in flat_fns { + let blocks = find_basic_blocks(&flat_fn); + let ssa = ssa_walk(&flat_fn); + + println!("{flat_fn:#?}"); + println!("{blocks:#?}"); + println!("{ssa:#?}"); + } +} diff --git a/docs/FORMAT.md b/docs/FORMAT.md new file mode 100644 index 0000000..7e864a9 --- /dev/null +++ b/docs/FORMAT.md @@ -0,0 +1,502 @@ +# quickjs-ng serialized bytecode — format specification + +Produced by `qjsc -b`, and by `JS_WriteObject()` with `JS_WRITE_OBJ_BYTECODE`. + +Applies to **quickjs-ng v0.16.1, `BC_VERSION 27`**. Hand-maintained against +`quickjs.c`; quickjs-ng raises `BC_VERSION` on any layout change, so re-check +after bumping `third_party/quickjs-ng`. + +- **Part 1** — the wire format, as one declaration. +- **Part 2** — the algorithms it depends on. +- **Part 3** — the C structures behind it, and the enumerations. +- **Appendices** — differences from the original QuickJS; how to verify. + +--- + +## Notation + +The wire format is written as TypeScript type declarations. Two caveats, +since TypeScript is being used for its syntax rather than its semantics: + +- **Field order is the wire order.** TypeScript object types are unordered; + here the declaration order is normative. +- **Widths come from the primitive aliases below**, not from `number`. + +| Alias | Encoding | +|---|---| +| `u8` `u16` `u32` `u64` | Unsigned, fixed width, **native byte order** | +| `leb` | Unsigned LEB128, 7 bits per byte, at most 5 bytes | +| `sleb` | Signed LEB128, zigzag (§2.4) | +| `bit` | One bit inside a packed integer | + +```ts +type u8 = number; type u16 = number; type u32 = number; type u64 = bigint; +type leb = number; type sleb = number; type bit = boolean; +``` + +An optional field written `x?: T` is present only under the condition stated +in its comment. A union of object types is a discriminated union: the shared +literal field is a real field on the wire, and its width is given in the +type's doc comment, since a TypeScript literal type carries no width. + +Fixed-width integers are written with `memcpy`, so a file carries the +**producer's** byte order. Every mainstream target is little-endian; files are +not portable across endianness. + +--- + +# Part 1 — Wire format + +```ts +// ═══ container ═══════════════════════════════════════════════════════════ + +/** Repeats until end of file. The count is not stored. */ +type File = SerializedObject[]; + +type SerializedObject = { + version: u8; // === 27, else reject + checksum: u32; // over bytes 5..end of object, §2.1 + atom_count: leb; // limit 1_000_000 + atoms: AtomEntry[]; // length = atom_count + root: Value; // MODULE, or FUNCTION for a classic script +}; + +// ═══ primitives ══════════════════════════════════════════════════════════ + +type JSString = { + header: leb; // (len << 1) | is_wide_char + data: u8[] | u16[]; // len Latin-1 bytes, or len UTF-16 units +}; + +/** `type` is a u8. */ +type AtomEntry = + | { type: 0; raw: u32 } // a predefined atom number + | { type: 1 | 2 | 3; text: JSString }; // string | global_symbol | symbol + +/** + * One leb. Decoding, where i = v >> 1: + * v & 1 -> tagged integer atom, value i + * i < first_atom -> predefined atom number i + * i >= first_atom -> atoms[i - first_atom] + */ +type AtomRef = leb; + +// ═══ values ══════════════════════════════════════════════════════════════ + +/** `tag` is a u8. */ +type Value = + | { tag: 1 } // NULL + | { tag: 2 } // UNDEFINED + | { tag: 3 } // BOOL_FALSE + | { tag: 4 } // BOOL_TRUE + | { tag: 5; value: sleb } // INT32 + | { tag: 6; bits: u64 } // FLOAT64, IEEE-754 + | { tag: 7; s: JSString } // STRING + | { tag: 8; count: leb; props: [AtomRef, Value][] } // OBJECT + | { tag: 9; count: leb; items: Value[] } // ARRAY + | { tag: 10; len: leb; mag: u8[] } // BIG_INT, two's complement LE + | { tag: 11; count: leb; cooked: Value[]; raw: Value } // TEMPLATE_OBJECT + | { tag: 12; fn: FunctionBytecode } // FUNCTION + | { tag: 13; module: Module } // MODULE + | { tag: 14; kind: u8; len: leb; offset: leb; buffer: Value } // TYPED_ARRAY + | { tag: 15; len: leb; max: leb; data: u8[] } // ARRAY_BUFFER + | { tag: 16; len: leb; max: leb; ptr: u64 } // SHARED_ARRAY_BUFFER, host pointer + | { tag: 17; pattern: JSString; lre_bytecode: JSString } // REGEXP + | { tag: 18; timestamp: Value } // DATE + | { tag: 19; inner: Value } // OBJECT_VALUE + | { tag: 20; index: leb } // OBJECT_REFERENCE + | { tag: 21; count: leb; entries: [Value, Value][] } // MAP + | { tag: 22; count: leb; items: Value[] } // SET + | { tag: 23; description: AtomRef }; // SYMBOL + +// ═══ functions ═══════════════════════════════════════════════════════════ + +type FunctionBytecode = { + flags: FunctionFlags; + is_strict_mode: u8; // not part of flags + func_name: AtomRef; + arg_count: leb; + var_count: leb; + defined_arg_count: leb; // Function.prototype.length + stack_size: leb; // max operand stack depth + var_ref_count: leb; + closure_var_count: leb; + cpool_count: leb; + byte_code_len: leb; // in bytes + vardef_count: leb; // arg_count + var_count, or 0 + vardefs: VarDef[]; // arguments first, then locals + closure_vars: ClosureVar[]; // length = closure_var_count + cpool: Value[]; // length = cpool_count; nested functions live + // here. NOTE: before the code + byte_code: Instruction[]; // byte_code_len bytes in total + debug?: DebugBlock; // present iff flags.has_debug +}; + +/** Packed into one u16, least significant bit first. */ +type FunctionFlags = { + has_prototype: bit; // bit 0 + has_simple_parameter_list: bit; + is_derived_class_constructor: bit; + need_home_object: bit; + func_kind: FuncKind; // bits 4..5 + new_target_allowed: bit; + super_call_allowed: bit; + super_allowed: bit; + arguments_allowed: bit; + backtrace_barrier: bit; + has_debug: bit; // bit 11 +}; + +type VarDef = { + name: AtomRef; + scope_level: leb; // 0 = not lexically scoped; §3.3 + scope_next_plus_1: leb; // int32(v) - 1; -1 none, -2 arg-scope end + flags: VarDefFlags; + var_ref_idx?: leb; // present iff flags.is_captured +}; + +/** Packed into one u8, least significant bit first. */ +type VarDefFlags = { + var_kind: VarKind; // bits 0..3 + is_const: bit; // bit 4 + is_lexical: bit; // bit 5 + is_captured: bit; // bit 6 +}; + +type ClosureVar = { + name: AtomRef; + var_idx: leb; // index in the PARENT frame + flags: ClosureVarFlags; +}; + +/** Packed into one leb, least significant bit first. */ +type ClosureVarFlags = { + closure_type: ClosureType; // bits 0..2 + is_const: bit; // bit 3 + is_lexical: bit; // bit 4 + var_kind: VarKind; // bits 5..8 +}; + +type Instruction = { + opcode: u8; // §2.3 gives its size and operand format + operands: u8[]; // size - 1 bytes, fixed width per format +}; + +type DebugBlock = { + filename: AtomRef; + line_num: leb; // of the function's first character + col_num: leb; + pc2line_len: leb; + pc2line: u8[]; // §2.2 + source_len: leb; // 0 when stripped + source: u8[]; // the function's own text +}; + +// ═══ modules ═════════════════════════════════════════════════════════════ + +type Module = { + module_name: AtomRef; + req_count: leb; + requests: AtomRef[]; // import specifiers + export_count: leb; + exports: ExportEntry[]; + star_count: leb; + star_exports: leb[]; // indexes into requests + import_count: leb; + imports: ImportEntry[]; + has_tla: u8; // top-level await + func_obj: Value; // the body; always a FUNCTION +}; + +/** `export_type` is a u8. */ +type ExportEntry = + | { export_type: 0; var_idx: leb; export_name: AtomRef } + | { export_type: 1; req_module_idx: leb; local_name: AtomRef; export_name: AtomRef }; + +type ImportEntry = { + var_idx: leb; + import_name: AtomRef; + req_module_idx: leb; +}; + +// ═══ enumerations, §3.5 ══════════════════════════════════════════════════ + +const enum FuncKind { normal = 0, generator = 1, async = 2, async_generator = 3 } + +const enum VarKind { + normal = 0, function_decl = 1, new_function_decl = 2, catch_ = 3, + function_name = 4, private_field = 5, private_method = 6, + private_getter = 7, private_setter = 8, private_getter_setter = 9, + using = 10, using_method = 11, +} + +const enum ClosureType { + local = 0, arg = 1, ref = 2, global_ref = 3, + global_decl = 4, global = 5, module_decl = 6, module_import = 7, +} +``` + +## Notes on the above + +Things the types cannot state. + +1. **`first_atom` is not in the file.** It is `JS_ATOM_END` (242) when the file + contains bytecode and 1 otherwise. The engine takes it from the caller's + `JS_READ_OBJ_BYTECODE` flag, so a reader must be told which applies. +2. **Atom operands inside `byte_code` are bare `u32` with no shift.** They use + the same index space as `AtomRef`, but `JS_WriteFunctionBytecode()` writes + them with `bc_atom_to_idx()` rather than `bc_put_atom()`, so the `v >> 1` + step does not apply there. +3. **Functions nest through `cpool`, and only through `cpool`.** A + `FunctionBytecode` appears in exactly two places: `Module.func_obj`, the + module body, and `FunctionBytecode.cpool[]`, a nested function. There is no + list of functions anywhere. Since `cpool` also precedes `byte_code` — so a + reader can resolve a `push_const` operand the moment it decodes it — a + nested function's entire encoding sits inside its parent, ahead of the + parent's first instruction. +4. **`VarDef` is variable-width** because of the optional `var_ref_idx`. + Reading or skipping it unconditionally desynchronises the rest of the table. +5. **Jump operands are signed offsets from the operand field's own position**: + target = `operand_offset + value`. For the `atom_label_*` formats the base + is the label field, 4 bytes into the operands. +6. **`0xffffffff` in `checksum` disables the check.** +7. **Tags 14–16, 18, 21 and 22 never come from compiled JavaScript**; they + arise from serializing live objects. A regexp *literal* compiles to two + `STRING` constants plus a `regexp` opcode, not to tag 17. +8. **`source` is required by `Function.prototype.toString()`**, not merely + diagnostic. `qjsc -s` empties it; a second `-s` clears `has_debug` and + removes `DebugBlock` entirely. + +--- + +# Part 2 — Algorithms + +## 2.1 Checksum + +Over the object's bytes from offset 5 to its end, compared with the `u32` at +offset 1. + +```c +uint32_t bc_csum(const uint8_t *p, size_t n) { + uint32_t a, b, c, h = 0; + size_t i; + for (i = 0; i + 4 < n; i += 4) { /* note: <, not <= */ + h += get_u32(p + i); + h *= 0x9e370001; + } + a = b = c = 0; + switch (n - i) { /* note: no case 4 */ + case 3: c = p[i+2]; /* fallthrough */ + case 2: b = p[i+1]; /* fallthrough */ + case 1: a = p[i]; /* fallthrough */ + case 0: break; + } + h += a | b << 8 | c << 16; + return h * 0x9e370001; +} +``` + +The loop stops while up to four bytes remain and the tail `switch` has no +`case 4`, so a trailing group of exactly four bytes contributes nothing. +Reproduce it exactly or valid files fail verification. + +## 2.2 pc2line decoding + +Produces checkpoints mapping a bytecode offset to a source position. State +starts at `pc = 0`, with `line` and `col` from `DebugBlock`. Constants: +`PC2LINE_BASE = -1`, `PC2LINE_RANGE = 5`, `PC2LINE_OP_FIRST = 1`. + +``` +while bytes remain: + op = read u8 + if op == 0: // escape for large deltas + pc += read leb + line += read sleb + else: + op -= PC2LINE_OP_FIRST + pc += op / PC2LINE_RANGE + line += op % PC2LINE_RANGE + PC2LINE_BASE + col += read sleb // in both branches + emit checkpoint (pc, line, col) +``` + +One byte therefore encodes a small `pc` advance together with a line delta in +the range −1..+3. + +## 2.3 Opcode resolution + +Short opcodes fold an operand into the opcode byte, and their numbers overlap +the compiler's temporary opcodes, which never appear in a file. Resolve as the +engine does: + +```c +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] +``` + +For v0.16.1: `OP_TEMP_START` = 185, temporary block = 19, real opcodes = 252. +Omitting the shift yields both the wrong instruction and the wrong width. +Every opcode, its size and its operand format are in +`tests/report/OPCODES.md`. + +## 2.4 Signed LEB128 + +Zigzag over unsigned LEB128. + +``` +encode: leb( (2 * v) ^ -(v >> 31) ) +decode: (u >> 1) ^ -(u & 1) +``` + +--- + +# Part 3 — Why the format looks like this + +Facts about the engine that explain choices in Part 1. The C structures +themselves are in `quickjs.c`; only what bears on the wire is here. + +## 3.1 Atoms exist only at run time + +A `JSAtom` is a `uint32_t` index into `JSRuntime.atom_array`, shared by +everything in the process and dependent on what was interned and when. It is +meaningless in another process. + +`AtomEntry[]` therefore corresponds to no structure. `JS_WriteObjectAtoms()` +creates it: it walks the object, collects every atom referenced, writes them +as a file-local table, and rewrites each atom field as an index into it. +Indexes below `first_atom` are engine built-ins needing no entry, which is why +only atoms the engine did not already know are written out. + +## 3.2 What is not serialized + +`JSFunctionBytecode` holds a GC header, a `JSContext *realm`, and a pointer to +each of its arrays. None of that is written: pointers become inline data, and +the header and realm are rebuilt on load. `JSModuleDef` is mostly link and +evaluation state — `module_ns`, `status`, `dfs_index`, `cycle_root`, +`promise`, `async_parent_modules` — and `Module` stores only the static shape +the compiler determined. + +Two fields are wire-only, with no structure behind them: + +- **`has_debug`** records the writer's `allow_debug` setting, which is how + `qjsc -s -s` produces a function with no `DebugBlock`. +- **`vardef_count`** is redundant with `arg_count + var_count`; the writer + emits it anyway, and 0 when `vardefs` is absent. + +One field is structure-only: `JSVarDef.is_static_private` is used while +parsing private class fields and never written, which is why `VarDefFlags` +carries seven meaningful bits and not eight. + +## 3.3 The scope chain + +`scope_level` and `scope_next` are a fragment of the compiler's scope tree, +serialized for one consumer: direct `eval`. + +`scope_level` indexes `fd->scopes[]`, an array that is **not** serialized, so +the number carries no meaning of its own — only equality does. Two variables +with the same level were declared in the same block. + +Level 0 is the exception and is universal: it is the function scope, so +`scope_level == 0` means the variable is not lexically scoped — a `var`, a +hoisted function declaration, or a pseudo-variable such as `this`. Level 1 is +`ARG_SCOPE_INDEX`, the argument scope, which is also the body scope unless the +function has parameter expressions. Only then does the body get a scope of its +own, at level 2, and only then can an argument appear twice: once in the +argument half of `vardefs` and once as a lexical copy at level 1. + +`scope_next` is a singly linked list, `-1` at the end. It indexes the +**locals**, that is `vardefs[arg_count + scope_next]`, not `vardefs` directly. +`js_create_function()` rebuilds the chain immediately before the function is +written: the variables of one block link to each other, and the last of them +links to the block outside it. Following `scope_next` from any variable +therefore walks its own block and then the blocks around it, outward. + +```js +function h() { + var v = 1; // level 0 not in the chain + let w = 2; // level 1 next -1 end + { let u = 3; } // level 2 next 1 -> w +} +``` + +`OP_eval` and `OP_apply_eval` hold the entry point: a `u16` operand carrying a +local index biased by +1. At run time `add_closure_variables()` starts there, +follows `scope_next`, and makes a closure variable out of every variable it +reaches, which is how evaluated code sees the caller's locals. Nothing else in +the engine reads either field. A function that contains no direct `eval` still +carries the chain. + +Two cases do not follow the rules above: + +| Where | Value | Meaning | +|---|---|---| +| `scope_next` | `-2`, stored as `0xffffffff` | `ARG_SCOPE_END`: stop here, and do not add the arguments. Written only when the function has parameter expressions — a default value or a destructuring pattern. | +| any entry with `scope_level` 0 | `scope_next` arbitrary | not maintained. The whole argument half of `vardefs` is level 0, and so is every `var`. `JS_DUMP_BYTECODE_FINAL` prints the pair only when `scope_level != 0`, for this reason. | + +Because of `ARG_SCOPE_END` the field must be decoded as a **signed** 32-bit +value before the −1, matching `bc_get_leb128_int()`. Read as unsigned, −2 +becomes 4294967294. + +## 3.4 The capture pairing + +`VarDef` and `ClosureVar` are the two halves of one capture. A parent marks a +local `is_captured` and assigns it a `var_ref_idx`; the child lists the same +variable with `closure_type = local` and a `var_idx` pointing into the +parent's frame. Either side read alone is incomplete. + +`ClosureType.local` and `.arg` index the parent's variables and arguments +respectively; the remaining six index the parent's own closure variables, or +describe a global or module binding. + +## 3.5 Enumeration names + +| TypeScript | C | +|---|---| +| `FuncKind` | `JSFunctionKindEnum` — one bit for generator, one for async, so the four values compose | +| `VarKind` | `JSVarKindEnum` — `catch_` is `JS_VAR_CATCH`, renamed only to avoid the reserved word | +| `ClosureType` | `JSClosureTypeEnum` | +| `ExportEntry.export_type` | `JSExportTypeEnum` | + +`VarKind.using_method` is a hidden local holding the cached dispose method for +the preceding `using` variable, always allocated immediately after it. + +## Appendix A — Differences from the original QuickJS + +Read from Bellard's `quickjs-2026-06-04`, the release current when this was +written. Both projects are active, so date the comparison before trusting it. + +| Aspect | Bellard 2026-06-04 | quickjs-ng v0.16.1 | +|---|---|---| +| `BC_VERSION` | 5 | 27 | +| Header | Version byte, then the atom count | Version byte, then a `u32` checksum | +| Atom entries | String only | Type byte, then a string or a raw number | +| Function body order | Code, debug, then constant pool | Constant pool, then code, then debug | +| Byte after flags | `js_mode` | `is_strict_mode` | +| Tags | 19; no `MAP`, `SET`, `SYMBOL`, `REGEXP` | 23; later tag numbers shifted | +| Debug block | Filename and `pc2line` only | `line_num` and `col_num` as well | +| `VarDef` | No `scope_level`. `var_ref_idx` always written. Extra `has_scope` flag bit | `scope_level` present. `var_ref_idx` only when captured | +| `ClosureVar` flags | `u16` | leb128 | +| Opcodes | 244. `get_var` and its three siblings take a 3-byte `var_ref` index | 252. The same four take a 5-byte atom | + +The pool order is the one that matters to a reader. ng moved it ahead of the +code on purpose — its own comment says *"write constant pool before code so +code can be disassembled on the fly at read time"* — so a `push_const` operand +resolves as soon as it is decoded. + +## Appendix B — Verifying against a file + +The listing tool prints one row per field, in the order given above: + +```sh +cargo run -p qjsdump -- tests/bin/basic.bin +``` + +The parser is written in nom, one function per type in Part 1, so +`crates/qjs-bc/src/{parse,atoms,value,function,module}.rs` reads as an +executable form of this document. Indentation in the listing is depth in +`cpool`: a nested function and its sections are indented one level under the +parent whose constant pool holds them, which is also why a parent's `.code` +appears after, but less indented than, the whole of a child. + +`tests/report/` holds a listing per fixture and `OPCODES.md`; `make` +regenerates both from `tests/src/`. diff --git a/docs/OPCODES.md b/docs/OPCODES.md new file mode 100644 index 0000000..357ba48 --- /dev/null +++ b/docs/OPCODES.md @@ -0,0 +1,626 @@ +# quickjs-ng opcodes + +Generated from `third_party/quickjs-ng` (v0.16.1). Read this beside a listing. + +**#** is the opcode byte. **Size** counts that byte and its operands +together, so it is how far the next instruction is. **Stack** is pop → push, +excluding call arguments, which the operand counts instead. **Format** names +the operand encoding, described in the next table. + +Two things to know about the numbering. Short opcodes such as `get_loc2` fold +an operand into the opcode byte, which is why several names can mean the same +operation at different indexes. And short opcodes share numbers with the +compiler's temporary opcodes, which never reach a file, so a decoder has to +shift the high range past them. + +# Operand formats + +| Format | Bytes after the opcode | +|---|---| +| `none` | No operand. | +| `none_int` | No operand; the integer is folded into the opcode, as in `push_3`. | +| `none_loc` | No operand; the local index is folded into the opcode, as in `get_loc2`. | +| `none_arg` | No operand; the argument index is folded into the opcode. | +| `none_var_ref` | No operand; the closure variable index is folded into the opcode. | +| `u8` | One unsigned byte. | +| `i8` | One signed byte. | +| `loc8` | One byte, a local index. | +| `const8` | One byte, a constant pool index. | +| `label8` | One signed byte, a jump offset from its own position. | +| `u16` | Two bytes, unsigned. | +| `i16` | Two bytes, signed. | +| `label16` | Two signed bytes, a jump offset from its own position. | +| `npop` | Two bytes, an argument count. The arguments are on the stack and are not counted in the stack effect. | +| `npopx` | No operand; the argument count is folded into the opcode, as in `call2`. | +| `npop_u16` | Two bytes of argument count, then two more bytes. | +| `loc` | Two bytes, a local index. | +| `arg` | Two bytes, an argument index. | +| `var_ref` | Two bytes, a closure variable index. | +| `u32` | Four bytes, unsigned. | +| `u32x2` | Two four-byte values. | +| `i32` | Four bytes, signed. | +| `const` | Four bytes, a constant pool index. | +| `label` | Four signed bytes, a jump offset from its own position. | +| `atom` | Four bytes, an atom reference. Not a raw atom number: it is an index in the file's atom space. | +| `atom_u8` | An atom reference then one byte. | +| `atom_u16` | An atom reference then two bytes. | +| `atom_label_u8` | An atom reference, a four-byte jump offset, then one byte. | +| `atom_label_u16` | An atom reference, a four-byte jump offset, then two bytes. | +| `label_u16` | A four-byte jump offset then two bytes. | + +# Common opcodes + +## Function prologue + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `push_this` | 8 | 1 | 0 → 1 | `none` | Push the `this` binding. Only emitted at the start of a function. | +| `special_object` | 12 | 2 | 0 → 1 | `u8` | Push one of the objects a prologue may need, selected by the operand: the arguments object, a mapped arguments object, `this.__proto__`, `new.target`, or the home object. | +| `rest` | 13 | 3 | 0 → 1 | `u16` | Build the rest parameter array from the arguments at or after the given index. | +| `fclosure` | 3 | 5 | 0 → 1 | `const` | Take the function template at the given constant pool index and make a closure of it, capturing the current frame's variables. | +| `fclosure8` | 197 | 2 | 0 → 1 | `const8` | `fclosure` with a one-byte pool index. | +| `init_ctor` | 44 | 1 | 0 → 1 | `none` | Initialise a derived constructor's `this` slot. | + +## Constants + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `push_i32` | 1 | 5 | 0 → 1 | `i32` | Push a 32-bit signed integer given inline. | +| `push_i8` | 194 | 2 | 0 → 1 | `i8` | Push a small signed integer, one byte inline. | +| `push_i16` | 195 | 3 | 0 → 1 | `i16` | Push a signed integer, two bytes inline. | +| `push_const` | 2 | 5 | 0 → 1 | `const` | Push the constant pool entry at the given index. The pool holds nested functions, template objects, and any literal too large to inline. | +| `push_const8` | 196 | 2 | 0 → 1 | `const8` | `push_const` with a one-byte pool index. | +| `push_atom_value` | 4 | 5 | 0 → 1 | `atom` | Push an interned string, named by an atom rather than stored inline. | +| `push_empty_string` | 198 | 1 | 0 → 1 | `none` | Push `""`. | +| `push_bigint_i32` | 177 | 5 | 0 → 1 | `i32` | Push a BigInt small enough to fit in 32 bits. | +| `undefined` | 6 | 1 | 0 → 1 | `none` | Push `undefined`. | +| `null` | 7 | 1 | 0 → 1 | `none` | Push `null`. | +| `push_true` | 10 | 1 | 0 → 1 | `none` | Push `true`. | +| `push_false` | 9 | 1 | 0 → 1 | `none` | Push `false`. | +| `push_minus1` | 185 | 1 | 0 → 1 | `none_int` | Push the integer -1. One of the folded push opcodes, where the value is part of the opcode. | +| `push_0` | 186 | 1 | 0 → 1 | `none_int` | Push the integer 0. `push_0` through `push_7` fold the value into the opcode byte. | +| `object` | 11 | 1 | 0 → 1 | `none` | Push a new empty object, as `{}` does. | +| `regexp` | 52 | 1 | 2 → 1 | `none` | Pop a pattern and its compiled libregexp bytecode, push a new RegExp object. | + +## Locals and arguments + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `get_loc` | 87 | 3 | 0 → 1 | `loc` | Push local variable N. | +| `put_loc` | 88 | 3 | 1 → 0 | `loc` | Pop and store into local variable N. | +| `set_loc` | 89 | 3 | 1 → 1 | `loc` | Store into local variable N, leaving the value on the stack. This is what an assignment used as an expression compiles to. | +| `get_loc8` | 199 | 2 | 0 → 1 | `loc8` | `get_loc` with a one-byte index. | +| `get_loc0` | 203 | 1 | 0 → 1 | `none_loc` | Push local 0. `get_loc0` through `get_loc3` fold the index into the opcode. | +| `get_loc0_loc1` | 202 | 1 | 0 → 2 | `none_loc` | Push local 0 then local 1, in one instruction. | +| `get_arg` | 90 | 3 | 0 → 1 | `arg` | Push argument N. | +| `put_arg` | 91 | 3 | 1 → 0 | `arg` | Pop and store into argument N. | +| `set_arg` | 92 | 3 | 1 → 1 | `arg` | Store into argument N, leaving the value on the stack. | +| `get_arg0` | 215 | 1 | 0 → 1 | `none_arg` | Push argument 0. `get_arg0` through `get_arg3` fold the index into the opcode. | +| `get_var_ref` | 93 | 3 | 0 → 1 | `var_ref` | Push closure variable N, that is, a variable captured from an enclosing function. | +| `put_var_ref` | 94 | 3 | 1 → 0 | `var_ref` | Pop and store into closure variable N. | +| `set_var_ref` | 95 | 3 | 1 → 1 | `var_ref` | Store into closure variable N, leaving the value on the stack. | +| `get_var_ref0` | 227 | 1 | 0 → 1 | `none_var_ref` | Push closure variable 0. Indexes 0 to 3 are folded into the opcode. | +| `set_loc_uninitialized` | 96 | 3 | 0 → 0 | `loc` | Mark a local as being in its temporal dead zone. Reading it before initialisation must throw, and this is what sets that state. | +| `get_loc_check` | 97 | 3 | 0 → 1 | `loc` | Push a `let` or `const` local, throwing a ReferenceError if it is still in its temporal dead zone. | +| `put_loc_check` | 98 | 3 | 1 → 0 | `loc` | Store into a `let` local, throwing if it is still in its temporal dead zone. | +| `put_loc_check_init` | 99 | 3 | 1 → 0 | `loc` | Perform the initialising store to a `let` or `const` local, which is what leaves the dead zone. | +| `get_var_ref_check` | 100 | 3 | 0 → 1 | `var_ref` | Push a captured `let` or `const`, throwing if it is still in its temporal dead zone. | +| `put_var_ref_check` | 101 | 3 | 1 → 0 | `var_ref` | Store into a captured `let`, throwing if it is still in its temporal dead zone. | +| `put_var_ref_check_init` | 102 | 3 | 1 → 0 | `var_ref` | Perform the initialising store to a captured `let` or `const`, which is what leaves the dead zone. | +| `close_loc` | 103 | 3 | 0 → 0 | `loc` | Close over a local, so that a closure keeps it alive after the frame goes away. | +| `inc_loc` | 146 | 2 | 0 → 0 | `loc8` | Increment a local in place, without moving it through the stack. | +| `dec_loc` | 145 | 2 | 0 → 0 | `loc8` | Decrement a local in place. | +| `add_loc` | 147 | 2 | 1 → 0 | `loc8` | Add the top of the stack into a local in place. | + +## Variables by name + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `get_var` | 56 | 5 | 0 → 1 | `atom` | Push a variable looked up by name at run time, throwing a ReferenceError if it does not exist. | +| `get_var_undef` | 55 | 5 | 0 → 1 | `atom` | Like `get_var`, but push `undefined` instead of throwing when the variable does not exist. This is what `typeof x` on an unknown name uses. | +| `put_var` | 57 | 5 | 1 → 0 | `atom` | Store into a variable looked up by name. | +| `put_var_init` | 58 | 5 | 1 → 0 | `atom` | Perform the initialising store to a global lexical variable. | +| `define_var` | 61 | 6 | 0 → 0 | `atom_u8` | Define a `var` or function binding on the global object. | +| `check_define_var` | 62 | 6 | 0 → 0 | `atom_u8` | Check that defining this global name is allowed before `define_var` does it. | +| `define_func` | 63 | 6 | 1 → 0 | `atom_u8` | Define a global function binding from the closure on the stack. | +| `delete_var` | 152 | 5 | 0 → 1 | `atom` | `delete` applied to a bare name. | + +## The `with` statement + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `with_get_var` | 115 | 10 | 1 → 0 | `atom_label_u8` | Read the name from the `with` object, or jump to the label if the object does not have it. | +| `with_put_var` | 116 | 10 | 2 → 1 | `atom_label_u8` | Store into the name on the `with` object, or jump to the label if the object does not have it. | +| `with_delete_var` | 117 | 10 | 1 → 0 | `atom_label_u8` | `delete` the name on the `with` object, or jump to the label if the object does not have it. | +| `with_make_ref` | 118 | 10 | 1 → 0 | `atom_label_u8` | Push the object and the property name as a reference pair, for an assignment target inside `with`. | +| `with_get_ref` | 119 | 10 | 1 → 0 | `atom_label_u8` | Push the object and the named method together, so a call inside `with` gets its receiver. | +| `with_get_ref_undef` | 120 | 10 | 1 → 0 | `atom_label_u8` | `with_get_ref`, but push `undefined` as the receiver instead of the object. | + +## Properties + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `get_field` | 64 | 5 | 1 → 1 | `atom` | Pop an object, push `obj.name`. | +| `get_field2` | 65 | 5 | 1 → 2 | `atom` | Push `obj.name` but keep the object underneath it, so a following call has its receiver. This is the `a.b()` shape. | +| `put_field` | 66 | 5 | 2 → 0 | `atom` | Pop a value and an object, store `obj.name = value`. | +| `get_array_el` | 70 | 1 | 2 → 1 | `none` | Pop a key and an object, push `obj[key]`. | +| `get_array_el2` | 71 | 1 | 2 → 2 | `none` | Push `obj[key]` while keeping the object, the computed-key form of `get_field2`. | +| `put_array_el` | 72 | 1 | 3 → 0 | `none` | Pop a value, a key, and an object, store `obj[key] = value`. | +| `define_field` | 75 | 5 | 2 → 1 | `atom` | Define a named property on an object literal being built. | +| `define_array_el` | 80 | 1 | 3 → 2 | `none` | Define an indexed property on an array literal being built. | +| `append` | 81 | 1 | 3 → 2 | `none` | Append the elements of a spread operand to an array being built, updating its length. | +| `copy_data_properties` | 82 | 2 | 3 → 3 | `u8` | Copy the own enumerable properties of one object to another, for object spread and rest. | +| `delete` | 151 | 1 | 2 → 1 | `none` | `delete obj[key]`, with the key on the stack. | +| `get_length` | 239 | 1 | 1 → 1 | `none` | Push `obj.length`. Common enough to get its own opcode. | +| `set_name` | 76 | 5 | 1 → 1 | `atom` | Give an anonymous function the name it is being assigned to. | +| `set_name_computed` | 77 | 1 | 2 → 2 | `none` | Give an anonymous function a name computed at run time. | +| `set_proto` | 78 | 1 | 2 → 1 | `none` | Set an object's prototype, from `__proto__` in an object literal. | +| `set_home_object` | 79 | 1 | 2 → 2 | `none` | Record a method's home object, which is what `super` resolves against. | +| `to_object` | 112 | 1 | 1 → 1 | `none` | Coerce the top of the stack to an object. | +| `to_propkey` | 113 | 1 | 1 → 1 | `none` | Coerce the top of the stack to a valid property key. | +| `check_object` | 111 | 1 | 1 → 1 | `none` | Throw if the top of the stack is not an object. | + +## Private fields + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `private_symbol` | 5 | 5 | 0 → 1 | `atom` | Push the unique symbol that backs a `#name` private field. | +| `get_private_field` | 67 | 1 | 2 → 1 | `none` | Read a `#name` private field, throwing if the object does not carry it. | +| `put_private_field` | 68 | 1 | 3 → 0 | `none` | Write a `#name` private field. | +| `define_private_field` | 69 | 1 | 3 → 1 | `none` | Install a `#name` private field on a new instance. | +| `private_in` | 176 | 1 | 2 → 1 | `none` | The `#name in obj` test, which reports whether the object carries the field. | +| `check_brand` | 45 | 1 | 2 → 2 | `none` | Check that the receiver really is an instance of the class that owns this private method. | +| `add_brand` | 46 | 1 | 2 → 0 | `none` | Mark a new instance as belonging to a class that has private methods. | + +## Classes + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `define_class` | 85 | 6 | 2 → 2 | `atom_u8` | Build a class: pop the parent and the constructor, push the constructor and its prototype. | +| `define_class_computed` | 86 | 6 | 3 → 3 | `atom_u8` | `define_class` for a class whose name is computed at run time. | +| `define_method` | 83 | 6 | 2 → 1 | `atom_u8` | Define a method on a prototype or object literal. | +| `define_method_computed` | 84 | 2 | 3 → 1 | `u8` | `define_method` with a key computed at run time. | +| `get_super` | 53 | 1 | 1 → 1 | `none` | Push the prototype of the home object, which is what `super` refers to. | +| `get_super_value` | 73 | 1 | 3 → 1 | `none` | Read `super.name`, which needs the receiver as well as the home object. | +| `put_super_value` | 74 | 1 | 4 → 0 | `none` | Write `super.name`. | +| `check_ctor` | 43 | 1 | 0 → 0 | `none` | Throw if a class constructor was called without `new`. | +| `check_ctor_return` | 42 | 1 | 1 → 2 | `none` | Apply the rule that a constructor returning a non-object yields `this` instead. | + +## Calls + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `call` | 34 | 3 | 1 → 1 | `npop` | Call a function with N arguments. The argument count is the operand, and the arguments are not counted in the stack effect. | +| `call0` | 244 | 1 | 1 → 1 | `npopx` | Call with no arguments. `call0` through `call3` fold the count into the opcode. | +| `call_method` | 36 | 3 | 2 → 1 | `npop` | Call a method, which takes a receiver as well as the function. | +| `call_constructor` | 33 | 3 | 2 → 1 | `npop` | Call with `new`, taking the function and `new.target`. | +| `tail_call` | 35 | 3 | 1 → 0 | `npop` | A call in tail position, which reuses the frame instead of growing the stack. | +| `tail_call_method` | 37 | 3 | 2 → 0 | `npop` | A method call in tail position. | +| `apply` | 39 | 3 | 3 → 1 | `u16` | Call with an array of arguments, as `Function.prototype.apply` does. | +| `eval` | 50 | 5 | 1 → 1 | `npop_u16` | A direct `eval` call, which can see the enclosing scope. | +| `apply_eval` | 51 | 3 | 2 → 1 | `u16` | A direct `eval` call whose arguments come from an array. | +| `array_from` | 38 | 3 | 0 → 1 | `npop` | Build an array from N values on the stack. | +| `return` | 40 | 1 | 1 → 0 | `none` | Return the top of the stack. | +| `return_undef` | 41 | 1 | 0 → 0 | `none` | Return `undefined`, which is what a function ending without `return` does. | +| `return_async` | 47 | 1 | 1 → 0 | `none` | Return from an async function, resolving its promise. | + +## Control flow + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `goto` | 106 | 5 | 0 → 0 | `label` | Jump. The operand is a signed offset from its own position, which the listing shows resolved to a label. | +| `goto8` | 242 | 2 | 0 → 0 | `label8` | `goto` with a one-byte offset. | +| `goto16` | 243 | 3 | 0 → 0 | `label16` | `goto` with a two-byte offset. | +| `if_false` | 104 | 5 | 1 → 0 | `label` | Pop a value and jump if it is falsy. | +| `if_true` | 105 | 5 | 1 → 0 | `label` | Pop a value and jump if it is truthy. | +| `if_false8` | 240 | 2 | 1 → 0 | `label8` | `if_false` with a one-byte offset. | +| `if_true8` | 241 | 2 | 1 → 0 | `label8` | `if_true` with a one-byte offset. | +| `catch` | 107 | 5 | 0 → 1 | `label` | Push a catch context pointing at the handler, arming the `try` block that follows. | +| `gosub` | 108 | 5 | 0 → 0 | `label` | Enter a `finally` block, remembering where to resume. | +| `ret` | 109 | 1 | 1 → 0 | `none` | Leave a `finally` block and resume where `gosub` came from. | +| `nip_catch` | 110 | 1 | 2 → 1 | `none` | Drop a catch context from under the value on top, leaving the `try` block. | +| `throw` | 48 | 1 | 1 → 0 | `none` | Throw the value on top of the stack. | +| `throw_error` | 49 | 6 | 0 → 0 | `atom_u8` | Throw a built-in error, named by the operand. Used for the errors the compiler knows about in advance. | +| `nop` | 184 | 1 | 0 → 0 | `none` | Do nothing. Left behind where an optimisation removed an instruction. | +| `invalid` | 0 | 1 | 0 → 0 | `none` | Opcode 0, which upstream marks *never emitted*. Seeing it in a listing means the decode lost its place, not that the compiler produced it. | + +## Iteration + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `for_in_start` | 125 | 1 | 1 → 1 | `none` | Begin a `for...in` loop over the object on the stack. | +| `for_in_next` | 128 | 1 | 1 → 3 | `none` | Take the next key of a `for...in` loop, pushing the key and whether the loop is done. | +| `for_of_start` | 126 | 1 | 1 → 3 | `none` | Begin a `for...of` loop by fetching the iterator of the object on the stack. | +| `for_of_next` | 129 | 2 | 3 → 5 | `u8` | Take the next value of a `for...of` loop. | +| `for_await_of_start` | 127 | 1 | 1 → 3 | `none` | Begin a `for await...of` loop, fetching the async iterator. | +| `iterator_next` | 132 | 1 | 4 → 4 | `none` | Call the iterator's `next` method. | +| `iterator_call` | 133 | 2 | 4 → 5 | `u8` | Call an optional iterator method such as `return` or `throw`, if the iterator has it. | +| `iterator_get_value_done` | 130 | 1 | 1 → 2 | `none` | Split an iterator result object into its `value` and `done` fields. | +| `iterator_close` | 131 | 1 | 3 → 0 | `none` | Close an iterator early, which is what a `break` out of `for...of` must do. | + +## Generators and async + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `initial_yield` | 134 | 1 | 0 → 0 | `none` | Suspend immediately on entry, so a generator body does not run until the first `next` call. | +| `yield` | 135 | 1 | 1 → 2 | `none` | Suspend and hand a value to the caller. | +| `yield_star` | 136 | 1 | 1 → 2 | `none` | `yield*`, delegating to another iterator. | +| `async_yield_star` | 137 | 1 | 1 → 2 | `none` | `yield*` inside an async generator. | +| `await` | 138 | 1 | 1 → 1 | `none` | Suspend until a promise settles. | +| `import` | 54 | 1 | 2 → 1 | `none` | A dynamic `import()`, which yields a promise. | + +## Resource management + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `using_check` | 183 | 2 | 1 → 2 | `u8` | Check that a `using` declaration's value has the disposal method it needs. | +| `using_dispose_init` | 178 | 1 | 0 → 1 | `none` | Start the disposal list for a scope holding `using` declarations. | +| `using_dispose` | 179 | 3 | 1 → 1 | `loc` | Register a value for disposal when the scope ends. | +| `using_dispose_async` | 180 | 3 | 0 → 1 | `loc` | Register a value for `await using` disposal. | +| `using_dispose_merge` | 181 | 1 | 2 → 1 | `none` | Merge an inner scope's disposal list into the enclosing one. | +| `using_dispose_end` | 182 | 1 | 1 → 0 | `none` | Run the disposal list as the scope ends. | + +## Operators + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `add` | 156 | 1 | 2 → 1 | `none` | `a + b`, which is either numeric addition or string concatenation. | +| `sub` | 157 | 1 | 2 → 1 | `none` | `a - b`. | +| `mul` | 153 | 1 | 2 → 1 | `none` | `a * b`. | +| `div` | 154 | 1 | 2 → 1 | `none` | `a / b`. | +| `mod` | 155 | 1 | 2 → 1 | `none` | `a % b`. | +| `pow` | 164 | 1 | 2 → 1 | `none` | `a ** b`. | +| `neg` | 139 | 1 | 1 → 1 | `none` | Unary `-a`. | +| `plus` | 140 | 1 | 1 → 1 | `none` | Unary `+a`, which coerces to a number. | +| `inc` | 142 | 1 | 1 → 1 | `none` | Add one, for the prefix `++a`. | +| `dec` | 141 | 1 | 1 → 1 | `none` | Subtract one, for the prefix `--a`. | +| `post_inc` | 144 | 1 | 1 → 2 | `none` | `a++`: push the old value as well, since that is what the expression evaluates to. | +| `post_dec` | 143 | 1 | 1 → 2 | `none` | `a--`, keeping the old value. | +| `not` | 148 | 1 | 1 → 1 | `none` | Bitwise `~a`. | +| `lnot` | 149 | 1 | 1 → 1 | `none` | Logical `!a`. | +| `typeof` | 150 | 1 | 1 → 1 | `none` | `typeof a`. | +| `shl` | 158 | 1 | 2 → 1 | `none` | `a << b`. | +| `sar` | 159 | 1 | 2 → 1 | `none` | `a >> b`, the sign-propagating shift. | +| `shr` | 160 | 1 | 2 → 1 | `none` | `a >>> b`, the zero-filling shift. | +| `and` | 161 | 1 | 2 → 1 | `none` | Bitwise `a & b`. | +| `or` | 163 | 1 | 2 → 1 | `none` | Bitwise `a \| b`. | +| `xor` | 162 | 1 | 2 → 1 | `none` | Bitwise `a ^ b`. | +| `lt` | 165 | 1 | 2 → 1 | `none` | `a < b`. | +| `lte` | 166 | 1 | 2 → 1 | `none` | `a <= b`. | +| `gt` | 167 | 1 | 2 → 1 | `none` | `a > b`. | +| `gte` | 168 | 1 | 2 → 1 | `none` | `a >= b`. | +| `eq` | 171 | 1 | 2 → 1 | `none` | `a == b`, with coercion. | +| `neq` | 172 | 1 | 2 → 1 | `none` | `a != b`. | +| `strict_eq` | 173 | 1 | 2 → 1 | `none` | `a === b`. | +| `strict_neq` | 174 | 1 | 2 → 1 | `none` | `a !== b`. | +| `instanceof` | 169 | 1 | 2 → 1 | `none` | `a instanceof b`. | +| `in` | 170 | 1 | 2 → 1 | `none` | `a in b`. | +| `is_undefined` | 248 | 1 | 1 → 1 | `none` | Test for `undefined` without a comparison. | +| `is_null` | 249 | 1 | 1 → 1 | `none` | Test for `null` without a comparison. | +| `is_undefined_or_null` | 175 | 1 | 1 → 1 | `none` | The `== null` test, which both `?.` and `??` need. | +| `typeof_is_undefined` | 250 | 1 | 1 → 1 | `none` | The whole of `typeof a === "undefined"` in one instruction. | +| `typeof_is_function` | 251 | 1 | 1 → 1 | `none` | The whole of `typeof a === "function"` in one instruction. | + +## Stack + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `drop` | 14 | 1 | 1 → 0 | `none` | Discard the top of the stack. | +| `dup` | 17 | 1 | 1 → 2 | `none` | Duplicate the top of the stack. | +| `dup1` | 18 | 1 | 2 → 3 | `none` | Duplicate the second item, leaving `a b` as `a a b`. | +| `dup2` | 19 | 1 | 2 → 4 | `none` | Duplicate the top pair. | +| `dup3` | 20 | 1 | 3 → 6 | `none` | Duplicate the top three. | +| `nip` | 15 | 1 | 2 → 1 | `none` | Drop the second item, leaving `a b` as `b`. | +| `nip1` | 16 | 1 | 3 → 2 | `none` | Drop the third item. | +| `swap` | 27 | 1 | 2 → 2 | `none` | Exchange the top two. | +| `swap2` | 28 | 1 | 4 → 4 | `none` | Exchange the top two pairs. | +| `insert2` | 21 | 1 | 2 → 3 | `none` | Copy the top item below the one beneath it, so a receiver survives a call. | +| `insert3` | 22 | 1 | 3 → 4 | `none` | Copy the top item down past two others. | +| `insert4` | 23 | 1 | 4 → 5 | `none` | Copy the top item down past three others. | +| `perm3` | 24 | 1 | 3 → 3 | `none` | Rotate the top three so the deepest comes second. | +| `perm4` | 25 | 1 | 4 → 4 | `none` | Rotate the top four. | +| `perm5` | 26 | 1 | 5 → 5 | `none` | Rotate the top five. | +| `rot3l` | 29 | 1 | 3 → 3 | `none` | Rotate the top three left. | +| `rot3r` | 30 | 1 | 3 → 3 | `none` | Rotate the top three right. | +| `rot4l` | 31 | 1 | 4 → 4 | `none` | Rotate the top four left. | +| `rot5l` | 32 | 1 | 5 → 5 | `none` | Rotate the top five left. | + +## References + +| Opcode | # | Size | Stack | Format | Meaning | +|---|---|---|---|---|---| +| `make_loc_ref` | 121 | 7 | 0 → 2 | `atom_u16` | Build a reference to a local, so it can be written through later. | +| `make_arg_ref` | 122 | 7 | 0 → 2 | `atom_u16` | Build a reference to an argument. | +| `make_var_ref` | 124 | 5 | 0 → 2 | `atom` | Build a reference to a variable named by an atom. | +| `make_var_ref_ref` | 123 | 7 | 0 → 2 | `atom_u16` | Build a reference to a captured variable. | +| `get_ref_value` | 59 | 1 | 2 → 3 | `none` | Read through a reference. | +| `put_ref_value` | 60 | 1 | 3 → 0 | `none` | Write through a reference. | + +# All opcodes + +Every opcode quickjs-ng defines, in numeric order. The comment column is upstream's own, where it has one. + +| # | Name | Size | Pop | Push | Format | Notes | +|---|---|---|---|---|---|---| +| 0 | `invalid` | 1 | 0 | 0 | `none` | explained above | +| 1 | `push_i32` | 5 | 0 | 1 | `i32` | explained above | +| 2 | `push_const` | 5 | 0 | 1 | `const` | explained above | +| 3 | `fclosure` | 5 | 0 | 1 | `const` | explained above | +| 4 | `push_atom_value` | 5 | 0 | 1 | `atom` | explained above | +| 5 | `private_symbol` | 5 | 0 | 1 | `atom` | explained above | +| 6 | `undefined` | 1 | 0 | 1 | `none` | explained above | +| 7 | `null` | 1 | 0 | 1 | `none` | explained above | +| 8 | `push_this` | 1 | 0 | 1 | `none` | explained above | +| 9 | `push_false` | 1 | 0 | 1 | `none` | explained above | +| 10 | `push_true` | 1 | 0 | 1 | `none` | explained above | +| 11 | `object` | 1 | 0 | 1 | `none` | explained above | +| 12 | `special_object` | 2 | 0 | 1 | `u8` | explained above | +| 13 | `rest` | 3 | 0 | 1 | `u16` | explained above | +| 14 | `drop` | 1 | 1 | 0 | `none` | explained above | +| 15 | `nip` | 1 | 2 | 1 | `none` | explained above | +| 16 | `nip1` | 1 | 3 | 2 | `none` | explained above | +| 17 | `dup` | 1 | 1 | 2 | `none` | explained above | +| 18 | `dup1` | 1 | 2 | 3 | `none` | explained above | +| 19 | `dup2` | 1 | 2 | 4 | `none` | explained above | +| 20 | `dup3` | 1 | 3 | 6 | `none` | explained above | +| 21 | `insert2` | 1 | 2 | 3 | `none` | explained above | +| 22 | `insert3` | 1 | 3 | 4 | `none` | explained above | +| 23 | `insert4` | 1 | 4 | 5 | `none` | explained above | +| 24 | `perm3` | 1 | 3 | 3 | `none` | explained above | +| 25 | `perm4` | 1 | 4 | 4 | `none` | explained above | +| 26 | `perm5` | 1 | 5 | 5 | `none` | explained above | +| 27 | `swap` | 1 | 2 | 2 | `none` | explained above | +| 28 | `swap2` | 1 | 4 | 4 | `none` | explained above | +| 29 | `rot3l` | 1 | 3 | 3 | `none` | explained above | +| 30 | `rot3r` | 1 | 3 | 3 | `none` | explained above | +| 31 | `rot4l` | 1 | 4 | 4 | `none` | explained above | +| 32 | `rot5l` | 1 | 5 | 5 | `none` | explained above | +| 33 | `call_constructor` | 3 | 2 | 1 | `npop` | explained above | +| 34 | `call` | 3 | 1 | 1 | `npop` | explained above | +| 35 | `tail_call` | 3 | 1 | 0 | `npop` | explained above | +| 36 | `call_method` | 3 | 2 | 1 | `npop` | explained above | +| 37 | `tail_call_method` | 3 | 2 | 0 | `npop` | explained above | +| 38 | `array_from` | 3 | 0 | 1 | `npop` | explained above | +| 39 | `apply` | 3 | 3 | 1 | `u16` | explained above | +| 40 | `return` | 1 | 1 | 0 | `none` | explained above | +| 41 | `return_undef` | 1 | 0 | 0 | `none` | explained above | +| 42 | `check_ctor_return` | 1 | 1 | 2 | `none` | explained above | +| 43 | `check_ctor` | 1 | 0 | 0 | `none` | explained above | +| 44 | `init_ctor` | 1 | 0 | 1 | `none` | explained above | +| 45 | `check_brand` | 1 | 2 | 2 | `none` | explained above | +| 46 | `add_brand` | 1 | 2 | 0 | `none` | explained above | +| 47 | `return_async` | 1 | 1 | 0 | `none` | explained above | +| 48 | `throw` | 1 | 1 | 0 | `none` | explained above | +| 49 | `throw_error` | 6 | 0 | 0 | `atom_u8` | explained above | +| 50 | `eval` | 5 | 1 | 1 | `npop_u16` | explained above | +| 51 | `apply_eval` | 3 | 2 | 1 | `u16` | explained above | +| 52 | `regexp` | 1 | 2 | 1 | `none` | explained above | +| 53 | `get_super` | 1 | 1 | 1 | `none` | explained above | +| 54 | `import` | 1 | 2 | 1 | `none` | explained above | +| 55 | `get_var_undef` | 5 | 0 | 1 | `atom` | explained above | +| 56 | `get_var` | 5 | 0 | 1 | `atom` | explained above | +| 57 | `put_var` | 5 | 1 | 0 | `atom` | explained above | +| 58 | `put_var_init` | 5 | 1 | 0 | `atom` | explained above | +| 59 | `get_ref_value` | 1 | 2 | 3 | `none` | explained above | +| 60 | `put_ref_value` | 1 | 3 | 0 | `none` | explained above | +| 61 | `define_var` | 6 | 0 | 0 | `atom_u8` | explained above | +| 62 | `check_define_var` | 6 | 0 | 0 | `atom_u8` | explained above | +| 63 | `define_func` | 6 | 1 | 0 | `atom_u8` | explained above | +| 64 | `get_field` | 5 | 1 | 1 | `atom` | explained above | +| 65 | `get_field2` | 5 | 1 | 2 | `atom` | explained above | +| 66 | `put_field` | 5 | 2 | 0 | `atom` | explained above | +| 67 | `get_private_field` | 1 | 2 | 1 | `none` | explained above | +| 68 | `put_private_field` | 1 | 3 | 0 | `none` | explained above | +| 69 | `define_private_field` | 1 | 3 | 1 | `none` | explained above | +| 70 | `get_array_el` | 1 | 2 | 1 | `none` | explained above | +| 71 | `get_array_el2` | 1 | 2 | 2 | `none` | explained above | +| 72 | `put_array_el` | 1 | 3 | 0 | `none` | explained above | +| 73 | `get_super_value` | 1 | 3 | 1 | `none` | explained above | +| 74 | `put_super_value` | 1 | 4 | 0 | `none` | explained above | +| 75 | `define_field` | 5 | 2 | 1 | `atom` | explained above | +| 76 | `set_name` | 5 | 1 | 1 | `atom` | explained above | +| 77 | `set_name_computed` | 1 | 2 | 2 | `none` | explained above | +| 78 | `set_proto` | 1 | 2 | 1 | `none` | explained above | +| 79 | `set_home_object` | 1 | 2 | 2 | `none` | explained above | +| 80 | `define_array_el` | 1 | 3 | 2 | `none` | explained above | +| 81 | `append` | 1 | 3 | 2 | `none` | explained above | +| 82 | `copy_data_properties` | 2 | 3 | 3 | `u8` | explained above | +| 83 | `define_method` | 6 | 2 | 1 | `atom_u8` | explained above | +| 84 | `define_method_computed` | 2 | 3 | 1 | `u8` | explained above | +| 85 | `define_class` | 6 | 2 | 2 | `atom_u8` | explained above | +| 86 | `define_class_computed` | 6 | 3 | 3 | `atom_u8` | explained above | +| 87 | `get_loc` | 3 | 0 | 1 | `loc` | explained above | +| 88 | `put_loc` | 3 | 1 | 0 | `loc` | explained above | +| 89 | `set_loc` | 3 | 1 | 1 | `loc` | explained above | +| 90 | `get_arg` | 3 | 0 | 1 | `arg` | explained above | +| 91 | `put_arg` | 3 | 1 | 0 | `arg` | explained above | +| 92 | `set_arg` | 3 | 1 | 1 | `arg` | explained above | +| 93 | `get_var_ref` | 3 | 0 | 1 | `var_ref` | explained above | +| 94 | `put_var_ref` | 3 | 1 | 0 | `var_ref` | explained above | +| 95 | `set_var_ref` | 3 | 1 | 1 | `var_ref` | explained above | +| 96 | `set_loc_uninitialized` | 3 | 0 | 0 | `loc` | explained above | +| 97 | `get_loc_check` | 3 | 0 | 1 | `loc` | explained above | +| 98 | `put_loc_check` | 3 | 1 | 0 | `loc` | explained above | +| 99 | `put_loc_check_init` | 3 | 1 | 0 | `loc` | explained above | +| 100 | `get_var_ref_check` | 3 | 0 | 1 | `var_ref` | explained above | +| 101 | `put_var_ref_check` | 3 | 1 | 0 | `var_ref` | explained above | +| 102 | `put_var_ref_check_init` | 3 | 1 | 0 | `var_ref` | explained above | +| 103 | `close_loc` | 3 | 0 | 0 | `loc` | explained above | +| 104 | `if_false` | 5 | 1 | 0 | `label` | explained above | +| 105 | `if_true` | 5 | 1 | 0 | `label` | explained above | +| 106 | `goto` | 5 | 0 | 0 | `label` | explained above | +| 107 | `catch` | 5 | 0 | 1 | `label` | explained above | +| 108 | `gosub` | 5 | 0 | 0 | `label` | explained above | +| 109 | `ret` | 1 | 1 | 0 | `none` | explained above | +| 110 | `nip_catch` | 1 | 2 | 1 | `none` | explained above | +| 111 | `check_object` | 1 | 1 | 1 | `none` | explained above | +| 112 | `to_object` | 1 | 1 | 1 | `none` | explained above | +| 113 | `to_propkey` | 1 | 1 | 1 | `none` | explained above | +| 114 | `to_propkey2` | 1 | 2 | 2 | `none` | | +| 115 | `with_get_var` | 10 | 1 | 0 | `atom_label_u8` | explained above | +| 116 | `with_put_var` | 10 | 2 | 1 | `atom_label_u8` | explained above | +| 117 | `with_delete_var` | 10 | 1 | 0 | `atom_label_u8` | explained above | +| 118 | `with_make_ref` | 10 | 1 | 0 | `atom_label_u8` | explained above | +| 119 | `with_get_ref` | 10 | 1 | 0 | `atom_label_u8` | explained above | +| 120 | `with_get_ref_undef` | 10 | 1 | 0 | `atom_label_u8` | explained above | +| 121 | `make_loc_ref` | 7 | 0 | 2 | `atom_u16` | explained above | +| 122 | `make_arg_ref` | 7 | 0 | 2 | `atom_u16` | explained above | +| 123 | `make_var_ref_ref` | 7 | 0 | 2 | `atom_u16` | explained above | +| 124 | `make_var_ref` | 5 | 0 | 2 | `atom` | explained above | +| 125 | `for_in_start` | 1 | 1 | 1 | `none` | explained above | +| 126 | `for_of_start` | 1 | 1 | 3 | `none` | explained above | +| 127 | `for_await_of_start` | 1 | 1 | 3 | `none` | explained above | +| 128 | `for_in_next` | 1 | 1 | 3 | `none` | explained above | +| 129 | `for_of_next` | 2 | 3 | 5 | `u8` | explained above | +| 130 | `iterator_get_value_done` | 1 | 1 | 2 | `none` | explained above | +| 131 | `iterator_close` | 1 | 3 | 0 | `none` | explained above | +| 132 | `iterator_next` | 1 | 4 | 4 | `none` | explained above | +| 133 | `iterator_call` | 2 | 4 | 5 | `u8` | explained above | +| 134 | `initial_yield` | 1 | 0 | 0 | `none` | explained above | +| 135 | `yield` | 1 | 1 | 2 | `none` | explained above | +| 136 | `yield_star` | 1 | 1 | 2 | `none` | explained above | +| 137 | `async_yield_star` | 1 | 1 | 2 | `none` | explained above | +| 138 | `await` | 1 | 1 | 1 | `none` | explained above | +| 139 | `neg` | 1 | 1 | 1 | `none` | explained above | +| 140 | `plus` | 1 | 1 | 1 | `none` | explained above | +| 141 | `dec` | 1 | 1 | 1 | `none` | explained above | +| 142 | `inc` | 1 | 1 | 1 | `none` | explained above | +| 143 | `post_dec` | 1 | 1 | 2 | `none` | explained above | +| 144 | `post_inc` | 1 | 1 | 2 | `none` | explained above | +| 145 | `dec_loc` | 2 | 0 | 0 | `loc8` | explained above | +| 146 | `inc_loc` | 2 | 0 | 0 | `loc8` | explained above | +| 147 | `add_loc` | 2 | 1 | 0 | `loc8` | explained above | +| 148 | `not` | 1 | 1 | 1 | `none` | explained above | +| 149 | `lnot` | 1 | 1 | 1 | `none` | explained above | +| 150 | `typeof` | 1 | 1 | 1 | `none` | explained above | +| 151 | `delete` | 1 | 2 | 1 | `none` | explained above | +| 152 | `delete_var` | 5 | 0 | 1 | `atom` | explained above | +| 153 | `mul` | 1 | 2 | 1 | `none` | explained above | +| 154 | `div` | 1 | 2 | 1 | `none` | explained above | +| 155 | `mod` | 1 | 2 | 1 | `none` | explained above | +| 156 | `add` | 1 | 2 | 1 | `none` | explained above | +| 157 | `sub` | 1 | 2 | 1 | `none` | explained above | +| 158 | `shl` | 1 | 2 | 1 | `none` | explained above | +| 159 | `sar` | 1 | 2 | 1 | `none` | explained above | +| 160 | `shr` | 1 | 2 | 1 | `none` | explained above | +| 161 | `and` | 1 | 2 | 1 | `none` | explained above | +| 162 | `xor` | 1 | 2 | 1 | `none` | explained above | +| 163 | `or` | 1 | 2 | 1 | `none` | explained above | +| 164 | `pow` | 1 | 2 | 1 | `none` | explained above | +| 165 | `lt` | 1 | 2 | 1 | `none` | explained above | +| 166 | `lte` | 1 | 2 | 1 | `none` | explained above | +| 167 | `gt` | 1 | 2 | 1 | `none` | explained above | +| 168 | `gte` | 1 | 2 | 1 | `none` | explained above | +| 169 | `instanceof` | 1 | 2 | 1 | `none` | explained above | +| 170 | `in` | 1 | 2 | 1 | `none` | explained above | +| 171 | `eq` | 1 | 2 | 1 | `none` | explained above | +| 172 | `neq` | 1 | 2 | 1 | `none` | explained above | +| 173 | `strict_eq` | 1 | 2 | 1 | `none` | explained above | +| 174 | `strict_neq` | 1 | 2 | 1 | `none` | explained above | +| 175 | `is_undefined_or_null` | 1 | 1 | 1 | `none` | explained above | +| 176 | `private_in` | 1 | 2 | 1 | `none` | explained above | +| 177 | `push_bigint_i32` | 5 | 0 | 1 | `i32` | explained above | +| 178 | `using_dispose_init` | 1 | 0 | 1 | `none` | explained above | +| 179 | `using_dispose` | 3 | 1 | 1 | `loc` | explained above | +| 180 | `using_dispose_async` | 3 | 0 | 1 | `loc` | explained above | +| 181 | `using_dispose_merge` | 1 | 2 | 1 | `none` | explained above | +| 182 | `using_dispose_end` | 1 | 1 | 0 | `none` | explained above | +| 183 | `using_check` | 2 | 1 | 2 | `u8` | explained above | +| 184 | `nop` | 1 | 0 | 0 | `none` | explained above | +| 185 | `push_minus1` | 1 | 0 | 1 | `none_int` | explained above | +| 186 | `push_0` | 1 | 0 | 1 | `none_int` | explained above | +| 187 | `push_1` | 1 | 0 | 1 | `none_int` | | +| 188 | `push_2` | 1 | 0 | 1 | `none_int` | | +| 189 | `push_3` | 1 | 0 | 1 | `none_int` | | +| 190 | `push_4` | 1 | 0 | 1 | `none_int` | | +| 191 | `push_5` | 1 | 0 | 1 | `none_int` | | +| 192 | `push_6` | 1 | 0 | 1 | `none_int` | | +| 193 | `push_7` | 1 | 0 | 1 | `none_int` | | +| 194 | `push_i8` | 2 | 0 | 1 | `i8` | explained above | +| 195 | `push_i16` | 3 | 0 | 1 | `i16` | explained above | +| 196 | `push_const8` | 2 | 0 | 1 | `const8` | explained above | +| 197 | `fclosure8` | 2 | 0 | 1 | `const8` | explained above | +| 198 | `push_empty_string` | 1 | 0 | 1 | `none` | explained above | +| 199 | `get_loc8` | 2 | 0 | 1 | `loc8` | explained above | +| 200 | `put_loc8` | 2 | 1 | 0 | `loc8` | | +| 201 | `set_loc8` | 2 | 1 | 1 | `loc8` | | +| 202 | `get_loc0_loc1` | 1 | 0 | 2 | `none_loc` | explained above | +| 203 | `get_loc0` | 1 | 0 | 1 | `none_loc` | explained above | +| 204 | `get_loc1` | 1 | 0 | 1 | `none_loc` | | +| 205 | `get_loc2` | 1 | 0 | 1 | `none_loc` | | +| 206 | `get_loc3` | 1 | 0 | 1 | `none_loc` | | +| 207 | `put_loc0` | 1 | 1 | 0 | `none_loc` | | +| 208 | `put_loc1` | 1 | 1 | 0 | `none_loc` | | +| 209 | `put_loc2` | 1 | 1 | 0 | `none_loc` | | +| 210 | `put_loc3` | 1 | 1 | 0 | `none_loc` | | +| 211 | `set_loc0` | 1 | 1 | 1 | `none_loc` | | +| 212 | `set_loc1` | 1 | 1 | 1 | `none_loc` | | +| 213 | `set_loc2` | 1 | 1 | 1 | `none_loc` | | +| 214 | `set_loc3` | 1 | 1 | 1 | `none_loc` | | +| 215 | `get_arg0` | 1 | 0 | 1 | `none_arg` | explained above | +| 216 | `get_arg1` | 1 | 0 | 1 | `none_arg` | | +| 217 | `get_arg2` | 1 | 0 | 1 | `none_arg` | | +| 218 | `get_arg3` | 1 | 0 | 1 | `none_arg` | | +| 219 | `put_arg0` | 1 | 1 | 0 | `none_arg` | | +| 220 | `put_arg1` | 1 | 1 | 0 | `none_arg` | | +| 221 | `put_arg2` | 1 | 1 | 0 | `none_arg` | | +| 222 | `put_arg3` | 1 | 1 | 0 | `none_arg` | | +| 223 | `set_arg0` | 1 | 1 | 1 | `none_arg` | | +| 224 | `set_arg1` | 1 | 1 | 1 | `none_arg` | | +| 225 | `set_arg2` | 1 | 1 | 1 | `none_arg` | | +| 226 | `set_arg3` | 1 | 1 | 1 | `none_arg` | | +| 227 | `get_var_ref0` | 1 | 0 | 1 | `none_var_ref` | explained above | +| 228 | `get_var_ref1` | 1 | 0 | 1 | `none_var_ref` | | +| 229 | `get_var_ref2` | 1 | 0 | 1 | `none_var_ref` | | +| 230 | `get_var_ref3` | 1 | 0 | 1 | `none_var_ref` | | +| 231 | `put_var_ref0` | 1 | 1 | 0 | `none_var_ref` | | +| 232 | `put_var_ref1` | 1 | 1 | 0 | `none_var_ref` | | +| 233 | `put_var_ref2` | 1 | 1 | 0 | `none_var_ref` | | +| 234 | `put_var_ref3` | 1 | 1 | 0 | `none_var_ref` | | +| 235 | `set_var_ref0` | 1 | 1 | 1 | `none_var_ref` | | +| 236 | `set_var_ref1` | 1 | 1 | 1 | `none_var_ref` | | +| 237 | `set_var_ref2` | 1 | 1 | 1 | `none_var_ref` | | +| 238 | `set_var_ref3` | 1 | 1 | 1 | `none_var_ref` | | +| 239 | `get_length` | 1 | 1 | 1 | `none` | explained above | +| 240 | `if_false8` | 2 | 1 | 0 | `label8` | explained above | +| 241 | `if_true8` | 2 | 1 | 0 | `label8` | explained above | +| 242 | `goto8` | 2 | 0 | 0 | `label8` | explained above | +| 243 | `goto16` | 3 | 0 | 0 | `label16` | explained above | +| 244 | `call0` | 1 | 1 | 1 | `npopx` | explained above | +| 245 | `call1` | 1 | 1 | 1 | `npopx` | | +| 246 | `call2` | 1 | 1 | 1 | `npopx` | | +| 247 | `call3` | 1 | 1 | 1 | `npopx` | | +| 248 | `is_undefined` | 1 | 1 | 1 | `none` | explained above | +| 249 | `is_null` | 1 | 1 | 1 | `none` | explained above | +| 250 | `typeof_is_undefined` | 1 | 1 | 1 | `none` | explained above | +| 251 | `typeof_is_function` | 1 | 1 | 1 | `none` | explained above | + +# Temporary opcodes + +These exist only inside the compiler and are removed before anything is written to a file. They are listed because their numbers overlap the short opcodes, starting at `OP_TEMP_START` = 185, and a decoder must account for the 19 of them. + +| Name | Size | Format | +|---|---|---| +| `enter_scope` | 3 | `u16` | +| `leave_scope` | 3 | `u16` | +| `label` | 5 | `label` | +| `scope_get_var_undef` | 7 | `atom_u16` | +| `scope_get_var` | 7 | `atom_u16` | +| `scope_put_var` | 7 | `atom_u16` | +| `scope_delete_var` | 7 | `atom_u16` | +| `scope_make_ref` | 11 | `atom_label_u16` | +| `scope_get_ref` | 7 | `atom_u16` | +| `scope_put_var_init` | 7 | `atom_u16` | +| `scope_get_private_field` | 7 | `atom_u16` | +| `scope_get_private_field2` | 7 | `atom_u16` | +| `scope_put_private_field` | 7 | `atom_u16` | +| `scope_in_private_field` | 7 | `atom_u16` | +| `get_field_opt_chain` | 5 | `atom` | +| `get_array_el_opt_chain` | 1 | `none` | +| `set_class_name` | 5 | `u32` | +| `dispose_scope` | 3 | `u16` | +| `source_loc` | 9 | `u32x2` | + +--- + +252 opcodes, 206 explained above.