Initial SSA impl + docs
This commit is contained in:
+502
@@ -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
@@ -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.
|
||||
Reference in New Issue
Block a user