Initial SSA impl + docs

This commit is contained in:
2026-09-02 13:16:05 +00:00
parent a8acb7706c
commit c7d44584d9
6 changed files with 1255 additions and 33 deletions
+502
View File
@@ -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/`.
+626
View File
@@ -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.