diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/vm/Opcodes.h | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/Opcodes.h')
-rw-r--r-- | js/src/vm/Opcodes.h | 3632 |
1 files changed, 3632 insertions, 0 deletions
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h new file mode 100644 index 0000000000..d26ea4de39 --- /dev/null +++ b/js/src/vm/Opcodes.h @@ -0,0 +1,3632 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=2 et tw=0 ft=c: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_Opcodes_h +#define vm_Opcodes_h + +#include <stddef.h> +#include <stdint.h> + +#include "js/TypeDecls.h" + +// clang-format off +/* + * [SMDOC] Bytecode Definitions + * + * SpiderMonkey bytecode instructions. + * + * To use this header, define a macro of the form: + * + * #define MACRO(op, op_snake, token, length, nuses, ndefs, format) ... + * + * Then `FOR_EACH_OPCODE(MACRO)` invokes `MACRO` for every opcode. + * + * Field Description + * ----- ----------- + * op UpperCamelCase form of opcode id + * op_snake snake_case form of opcode id + * token Pretty-printer string, or null if ugly + * length Number of bytes including any immediate operands + * nuses Number of stack slots consumed by bytecode, -1 if variadic + * ndefs Number of stack slots produced by bytecode + * format JOF_ flags describing instruction operand layout, etc. + * + * For more about `format`, see the comments on the `JOF_` constants defined in + * BytecodeUtil.h. + * + * + * [SMDOC] Bytecode Invariants + * + * Creating scripts that do not follow the rules can lead to undefined + * behavior. Bytecode has many consumers, not just the interpreter: JITs, + * analyses, the debugger. That's why the rules below apply even to code that + * can't be reached in ordinary execution (such as code after an infinite loop + * or inside an `if (false)` block). + * + * The `code()` of a script must be a packed (not aligned) sequence of valid + * instructions from start to end. Each instruction has a single byte opcode + * followed by a number of operand bytes based on the opcode. + * + * ## Jump instructions + * + * Operands named `offset`, `forwardOffset`, or `defaultOffset` are jump + * offsets, the distance in bytes from the start of the current instruction to + * the start of another instruction in the same script. Operands named + * `forwardOffset` or `defaultOffset` must be positive. + * + * Forward jumps must jump to a `JSOp::JumpTarget` instruction. Backward jumps, + * indicated by negative offsets, must jump to a `JSOp::LoopHead` instruction. + * Jump offsets can't be zero. + * + * Needless to say, scripts must not contain overlapping instruction sequences + * (in the sense of <https://en.wikipedia.org/wiki/Overlapping_gene>). + * + * A script's `trynotes` and `scopeNotes` impose further constraints. Each try + * note and each scope note marks a region of the bytecode where some invariant + * holds, or some cleanup behavior is needed--that there's a for-in iterator in + * a particular stack slot, for instance, which must be closed on error. All + * paths into the span must establish that invariant. In practice, this means + * other code never jumps into the span: the only way in is to execute the + * bytecode instruction that sets up the invariant (in our example, + * `JSOp::Iter`). + * + * If a script's `trynotes` (see "Try Notes" in JSScript.h) contain a + * `JSTRY_CATCH` or `JSTRY_FINALLY` span, there must be a `JSOp::Try` + * instruction immediately before the span and a `JSOp::JumpTarget immediately + * after it. Instructions must not jump to this `JSOp::JumpTarget`. (The VM puts + * us there on exception.) Furthermore, the instruction sequence immediately + * following a `JSTRY_CATCH` span must read `JumpTarget; Exception` or, in + * non-function scripts, `JumpTarget; Undefined; SetRval; Exception`. (These + * instructions run with an exception pending; other instructions aren't + * designed to handle that.) + * + * Unreachable instructions are allowed, but they have to follow all the rules. + * + * Control must not reach the end of a script. (Currently, the last instruction + * is always JSOp::RetRval.) + * + * ## Other operands + * + * Operands named `nameIndex` or `atomIndex` (which appear on instructions that + * have `JOF_ATOM` in the `format` field) must be valid indexes into + * `script->atoms()`. + * + * Operands named `argc` (`JOF_ARGC`) are argument counts for call + * instructions. `argc` must be small enough that the instruction's nuses is <= + * the current stack depth (see "Stack depth" below). + * + * Operands named `argno` (`JOF_QARG`) refer to an argument of the current + * function. `argno` must be in the range `0..script->function()->nargs()`. + * Instructions with these operands must appear only in function scripts. + * + * Operands named `localno` (`JOF_LOCAL`) refer to a local variable stored in + * the stack frame. `localno` must be in the range `0..script->nfixed()`. + * + * Operands named `resumeIndex` (`JOF_RESUMEINDEX`) refer to a resume point in + * the current script. `resumeIndex` must be a valid index into + * `script->resumeOffsets()`. + * + * Operands named `hops` and `slot` (`JOF_ENVCOORD`) refer a slot in an + * `EnvironmentObject`. At run time, they must point to a fixed slot in an + * object on the current environment chain. See `EnvironmentCoordinates`. + * + * Operands with the following names must be valid indexes into + * `script->gcthings()`, and the pointer in the vector must point to the right + * type of thing: + * + * - `objectIndex` (`JOF_OBJECT`): `PlainObject*` or `ArrayObject*` + * - `baseobjIndex` (`JOF_OBJECT`): `PlainObject*` + * - `funcIndex` (`JOF_OBJECT`): `JSFunction*` + * - `regexpIndex` (`JOF_REGEXP`): `RegExpObject*` + * - `shapeIndex` (`JOF_SHAPE`): `Shape*` + * - `scopeIndex` (`JOF_SCOPE`): `Scope*` + * - `lexicalScopeIndex` (`JOF_SCOPE`): `LexicalScope*` + * - `classBodyScopeIndex` (`JOF_SCOPE`): `ClassBodyScope*` + * - `withScopeIndex` (`JOF_SCOPE`): `WithScope*` + * - `bigIntIndex` (`JOF_BIGINT`): `BigInt*` + * + * Operands named `icIndex` (`JOF_ICINDEX`) must be exactly the number of + * preceding instructions in the script that have the JOF_IC flag. + * (Rationale: Each JOF_IC instruction has a unique entry in + * `script->jitScript()->icEntries()`. At run time, in the bytecode + * interpreter, we have to find that entry. We could store the IC index as an + * operand to each JOF_IC instruction, but it's more memory-efficient to use a + * counter and reset the counter to `icIndex` after each jump.) + * + * ## Stack depth + * + * Each instruction has a compile-time stack depth, the number of values on the + * interpreter stack just before executing the instruction. It isn't explicitly + * present in the bytecode itself, but (for reachable instructions, anyway) + * it's a function of the bytecode. + * + * - The first instruction has stack depth 0. + * + * - Each successor of an instruction X has a stack depth equal to + * + * X's stack depth - `js::StackUses(X)` + `js::StackDefs(X)` + * + * except for `JSOp::Case` (below). + * + * X's "successors" are: the next instruction in the script, if + * `js::FlowsIntoNext(op)` is true for X's opcode; one or more + * `JSOp::JumpTarget`s elsewhere, if X is a forward jump or + * `JSOp::TableSwitch`; and/or a `JSOp::LoopHead` if it's a backward jump. + * + * - `JSOp::Case` is a special case because its stack behavior is eccentric. + * The formula above is correct for the next instruction. The jump target + * has a stack depth that is 1 less. + * + * - The `JSOp::JumpTarget` instruction immediately following a `JSTRY_CATCH` + * or `JSTRY_FINALLY` span has the same stack depth as the `JSOp::Try` + * instruction that precedes the span. + * + * Every instruction covered by the `JSTRY_CATCH` or `JSTRY_FINALLY` span + * must have a stack depth >= that value, so that error recovery is + * guaranteed to find enough values on the stack to resume there. + * + * - `script->nslots() - script->nfixed()` must be >= the maximum stack + * depth of any instruction in `script`. (The stack frame must be big + * enough to run the code.) + * + * `BytecodeParser::parse()` computes stack depths for every reachable + * instruction in a script. + * + * ## Scopes and environments + * + * As with stack depth, each instruction has a static scope, which is a + * compile-time characterization of the eventual run-time environment chain + * when that instruction executes. Just as every instruction has a stack budget + * (nuses/ndefs), every instruction either pushes a scope, pops a scope, or + * neither. The same successor relation applies as above. + * + * Every scope used in a script is stored in the `JSScript::gcthings()` vector. + * They can be accessed using `getScope(index)` if you know what `index` to + * pass. + * + * The scope of every instruction (that's reachable via the successor relation) + * is given in two independent ways: by the bytecode itself and by the scope + * notes. The two sources must agree. + * + * ## Further rules + * + * All reachable instructions must be reachable without taking any backward + * edges. + * + * Instructions with the `JOF_CHECKSLOPPY` flag must not be used in strict mode + * code. `JOF_CHECKSTRICT` instructions must not be used in nonstrict code. + * + * Many instructions have their own additional rules. These are documented on + * the various opcodes below (look for the word "must"). + */ +// clang-format on + +// clang-format off +/* + * SpiderMonkey bytecode categorization (as used in generated documentation): + * + * [Index] + * [Constants] + * [Compound primitives] + * Record literals + * Tuple literals + * [Expressions] + * Unary operators + * Binary operators + * Conversions + * Other expressions + * [Objects] + * Creating objects + * Defining properties + * Accessing properties + * Super + * Enumeration + * Iteration + * SetPrototype + * Array literals + * RegExp literals + * Built-in objects + * [Functions] + * Creating functions + * Creating constructors + * Calls + * Generators and async functions + * [Control flow] + * Jump targets + * Jumps + * Return + * Exceptions + * [Variables and scopes] + * Initialization + * Looking up bindings + * Getting binding values + * Setting binding values + * Entering and leaving environments + * Creating and deleting bindings + * Function environment setup + * [Stack operations] + * [Other] + */ +// clang-format on + +// clang-format off +#define FOR_EACH_OPCODE(MACRO) \ + /* + * Push `undefined`. + * + * Category: Constants + * Operands: + * Stack: => undefined + */ \ + MACRO(Undefined, undefined, "", 1, 0, 1, JOF_BYTE) \ + /* + * Push `null`. + * + * Category: Constants + * Operands: + * Stack: => null + */ \ + MACRO(Null, null, "null", 1, 0, 1, JOF_BYTE) \ + /* + * Push a boolean constant. + * + * Category: Constants + * Operands: + * Stack: => true/false + */ \ + MACRO(False, false_, "false", 1, 0, 1, JOF_BYTE) \ + MACRO(True, true_, "true", 1, 0, 1, JOF_BYTE) \ + /* + * Push the `int32_t` immediate operand as an `Int32Value`. + * + * `JSOp::Zero`, `JSOp::One`, `JSOp::Int8`, `JSOp::Uint16`, and `JSOp::Uint24` + * are all compact encodings for `JSOp::Int32`. + * + * Category: Constants + * Operands: int32_t val + * Stack: => val + */ \ + MACRO(Int32, int32, NULL, 5, 0, 1, JOF_INT32) \ + /* + * Push the number `0`. + * + * Category: Constants + * Operands: + * Stack: => 0 + */ \ + MACRO(Zero, zero, "0", 1, 0, 1, JOF_BYTE) \ + /* + * Push the number `1`. + * + * Category: Constants + * Operands: + * Stack: => 1 + */ \ + MACRO(One, one, "1", 1, 0, 1, JOF_BYTE) \ + /* + * Push the `int8_t` immediate operand as an `Int32Value`. + * + * Category: Constants + * Operands: int8_t val + * Stack: => val + */ \ + MACRO(Int8, int8, NULL, 2, 0, 1, JOF_INT8) \ + /* + * Push the `uint16_t` immediate operand as an `Int32Value`. + * + * Category: Constants + * Operands: uint16_t val + * Stack: => val + */ \ + MACRO(Uint16, uint16, NULL, 3, 0, 1, JOF_UINT16) \ + /* + * Push the `uint24_t` immediate operand as an `Int32Value`. + * + * Category: Constants + * Operands: uint24_t val + * Stack: => val + */ \ + MACRO(Uint24, uint24, NULL, 4, 0, 1, JOF_UINT24) \ + /* + * Push the 64-bit floating-point immediate operand as a `DoubleValue`. + * + * If the operand is a NaN, it must be the canonical NaN (see + * `JS::detail::CanonicalizeNaN`). + * + * Category: Constants + * Operands: double val + * Stack: => val + */ \ + MACRO(Double, double_, NULL, 9, 0, 1, JOF_DOUBLE) \ + /* + * Push the BigInt constant `script->getBigInt(bigIntIndex)`. + * + * Category: Constants + * Operands: uint32_t bigIntIndex + * Stack: => bigint + */ \ + MACRO(BigInt, big_int, NULL, 5, 0, 1, JOF_BIGINT) \ + /* + * Push the string constant `script->getAtom(atomIndex)`. + * + * Category: Constants + * Operands: uint32_t atomIndex + * Stack: => string + */ \ + MACRO(String, string, NULL, 5, 0, 1, JOF_STRING) \ + /* + * Push a well-known symbol. + * + * `symbol` must be in range for `JS::SymbolCode`. + * + * Category: Constants + * Operands: uint8_t symbol (the JS::SymbolCode of the symbol to use) + * Stack: => symbol + */ \ + MACRO(Symbol, symbol, NULL, 2, 0, 1, JOF_UINT8) \ + /* + * Pop the top value on the stack, discard it, and push `undefined`. + * + * Implements: [The `void` operator][1], step 3. + * + * [1]: https://tc39.es/ecma262/#sec-void-operator + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => undefined + */ \ + MACRO(Void, void_, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * [The `typeof` operator][1]. + * + * Infallible. The result is always a string that depends on the [type][2] + * of `val`. + * + * `JSOp::Typeof` and `JSOp::TypeofExpr` are the same except + * that--amazingly--`JSOp::Typeof` affects the behavior of an immediately + * *preceding* `JSOp::GetName` or `JSOp::GetGName` instruction! This is how + * we implement [`typeof`][1] step 2, making `typeof nonExistingVariable` + * return `"undefined"` instead of throwing a ReferenceError. + * + * In a global scope: + * + * - `typeof x` compiles to `GetGName "x"; Typeof`. + * - `typeof (0, x)` compiles to `GetGName "x"; TypeofExpr`. + * + * Emitting the same bytecode for these two expressions would be a bug. + * Per spec, the latter throws a ReferenceError if `x` doesn't exist. + * + * [1]: https://tc39.es/ecma262/#sec-typeof-operator + * [2]: https://tc39.es/ecma262/#sec-ecmascript-language-types + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => (typeof val) + */ \ + MACRO(Typeof, typeof_, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + MACRO(TypeofExpr, typeof_expr, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [The unary `+` operator][1]. + * + * `+val` doesn't do any actual math. It just calls [ToNumber][2](val). + * + * The conversion can call `.toString()`/`.valueOf()` methods and can + * throw. The result on success is always a Number. (Per spec, unary `-` + * supports BigInts, but unary `+` does not.) + * + * [1]: https://tc39.es/ecma262/#sec-unary-plus-operator + * [2]: https://tc39.es/ecma262/#sec-tonumber + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => (+val) + */ \ + MACRO(Pos, pos, "+ ", 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [The unary `-` operator][1]. + * + * Convert `val` to a numeric value, then push `-val`. The conversion can + * call `.toString()`/`.valueOf()` methods and can throw. The result on + * success is always numeric. + * + * [1]: https://tc39.es/ecma262/#sec-unary-minus-operator + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => (-val) + */ \ + MACRO(Neg, neg, "- ", 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [The bitwise NOT operator][1] (`~`). + * + * `val` is converted to an integer, then bitwise negated. The conversion + * can call `.toString()`/`.valueOf()` methods and can throw. The result on + * success is always an Int32 or BigInt value. + * + * [1]: https://tc39.es/ecma262/#sec-bitwise-not-operator + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => (~val) + */ \ + MACRO(BitNot, bit_not, "~", 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [The logical NOT operator][1] (`!`). + * + * `val` is first converted with [ToBoolean][2], then logically + * negated. The result is always a boolean value. This does not call + * user-defined methods and can't throw. + * + * [1]: https://tc39.es/ecma262/#sec-logical-not-operator + * [2]: https://tc39.es/ecma262/#sec-toboolean + * + * Category: Expressions + * Type: Unary operators + * Operands: + * Stack: val => (!val) + */ \ + MACRO(Not, not_, "!", 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [Binary bitwise operations][1] (`|`, `^`, `&`). + * + * The arguments are converted to integers first. The conversion can call + * `.toString()`/`.valueOf()` methods and can throw. The result on success + * is always an Int32 or BigInt Value. + * + * [1]: https://tc39.es/ecma262/#sec-binary-bitwise-operators + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(BitOr, bit_or, "|", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(BitXor, bit_xor, "^", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(BitAnd, bit_and, "&", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Loose equality operators (`==` and `!=`). + * + * Pop two values, compare them, and push the boolean result. The + * comparison may perform conversions that call `.toString()`/`.valueOf()` + * methods and can throw. + * + * Implements: [Abstract Equality Comparison][1]. + * + * [1]: https://tc39.es/ecma262/#sec-abstract-equality-comparison + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(Eq, eq, "==", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Ne, ne, "!=", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Strict equality operators (`===` and `!==`). + * + * Pop two values, check whether they're equal, and push the boolean + * result. This does not call user-defined methods and can't throw + * (except possibly due to OOM while flattening a string). + * + * Implements: [Strict Equality Comparison][1]. + * + * [1]: https://tc39.es/ecma262/#sec-strict-equality-comparison + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(StrictEq, strict_eq, "===", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(StrictNe, strict_ne, "!==", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Relative operators (`<`, `>`, `<=`, `>=`). + * + * Pop two values, compare them, and push the boolean result. The + * comparison may perform conversions that call `.toString()`/`.valueOf()` + * methods and can throw. + * + * Implements: [Relational Operators: Evaluation][1]. + * + * [1]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(Lt, lt, "<", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Gt, gt, ">", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Le, le, "<=", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Ge, ge, ">=", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [The `instanceof` operator][1]. + * + * This throws a `TypeError` if `target` is not an object. It calls + * `target[Symbol.hasInstance](value)` if the method exists. On success, + * the result is always a boolean value. + * + * [1]: https://tc39.es/ecma262/#sec-instanceofoperator + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: value, target => (value instanceof target) + */ \ + MACRO(Instanceof, instanceof, "instanceof", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [The `in` operator][1]. + * + * Push `true` if `obj` has a property with the key `id`. Otherwise push `false`. + * + * This throws a `TypeError` if `obj` is not an object. This can fire + * proxy hooks and can throw. On success, the result is always a boolean + * value. + * + * [1]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: id, obj => (id in obj) + */ \ + MACRO(In, in_, "in", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [Bitwise shift operators][1] (`<<`, `>>`, `>>>`). + * + * Pop two values, convert them to integers, perform a bitwise shift, and + * push the result. + * + * Conversion can call `.toString()`/`.valueOf()` methods and can throw. + * The result on success is always an Int32 or BigInt Value. + * + * [1]: https://tc39.es/ecma262/#sec-bitwise-shift-operators + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(Lsh, lsh, "<<", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Rsh, rsh, ">>", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Ursh, ursh, ">>>", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [The binary `+` operator][1]. + * + * Pop two values, convert them to primitive values, add them, and push the + * result. If both values are numeric, add them; if either is a + * string, do string concatenation instead. + * + * The conversion can call `.toString()`/`.valueOf()` methods and can throw. + * + * [1]: https://tc39.es/ecma262/#sec-addition-operator-plus-runtime-semantics-evaluation + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval + rval) + */ \ + MACRO(Add, add, "+", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [The binary `-` operator][1]. + * + * Pop two values, convert them to numeric values, subtract the top value + * from the other one, and push the result. + * + * The conversion can call `.toString()`/`.valueOf()` methods and can + * throw. On success, the result is always numeric. + * + * [1]: https://tc39.es/ecma262/#sec-subtraction-operator-minus-runtime-semantics-evaluation + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval - rval) + */ \ + MACRO(Sub, sub, "-", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Add or subtract 1. + * + * `val` must already be a numeric value, such as the result of + * `JSOp::ToNumeric`. + * + * Implements: [The `++` and `--` operators][1], step 3 of each algorithm. + * + * [1]: https://tc39.es/ecma262/#sec-postfix-increment-operator + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: val => (val +/- 1) + */ \ + MACRO(Inc, inc, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + MACRO(Dec, dec, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * [The multiplicative operators][1] (`*`, `/`, `%`). + * + * Pop two values, convert them to numeric values, do math, and push the + * result. + * + * The conversion can call `.toString()`/`.valueOf()` methods and can + * throw. On success, the result is always numeric. + * + * [1]: https://tc39.es/ecma262/#sec-multiplicative-operators-runtime-semantics-evaluation + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval OP rval) + */ \ + MACRO(Mul, mul, "*", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Div, div, "/", 1, 2, 1, JOF_BYTE|JOF_IC) \ + MACRO(Mod, mod, "%", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * [The exponentiation operator][1] (`**`). + * + * Pop two values, convert them to numeric values, do exponentiation, and + * push the result. The top value is the exponent. + * + * The conversion can call `.toString()`/`.valueOf()` methods and can + * throw. This throws a RangeError if both values are BigInts and the + * exponent is negative. + * + * [1]: https://tc39.es/ecma262/#sec-exp-operator + * + * Category: Expressions + * Type: Binary operators + * Operands: + * Stack: lval, rval => (lval ** rval) + */ \ + MACRO(Pow, pow, "**", 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Convert a value to a property key. + * + * Implements: [ToPropertyKey][1], except that if the result would be the + * string representation of some integer in the range 0..2^31, we push the + * corresponding Int32 value instead. This is because the spec insists that + * array indices are strings, whereas for us they are integers. + * + * This is used for code like `++obj[index]`, which must do both a + * `JSOp::GetElem` and a `JSOp::SetElem` with the same property key. Both + * instructions would convert `index` to a property key for us, but the + * spec says to convert it only once. + * + * The conversion can call `.toString()`/`.valueOf()` methods and can + * throw. + * + * [1]: https://tc39.es/ecma262/#sec-topropertykey + * + * Category: Expressions + * Type: Conversions + * Operands: + * Stack: propertyNameValue => propertyKey + */ \ + MACRO(ToPropertyKey, to_property_key, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * Convert a value to a numeric value (a Number or BigInt). + * + * Implements: [ToNumeric][1](val). + * + * Note: This is used to implement [`++` and `--`][2]. Surprisingly, it's + * not possible to get the right behavior using `JSOp::Add` and `JSOp::Sub` + * alone. For one thing, `JSOp::Add` sometimes does string concatenation, + * while `++` always does numeric addition. More fundamentally, the result + * of evaluating `x--` is ToNumeric(old value of `x`), a value that the + * sequence `GetLocal "x"; One; Sub; SetLocal "x"` does not give us. + * + * [1]: https://tc39.es/ecma262/#sec-tonumeric + * [2]: https://tc39.es/ecma262/#sec-postfix-increment-operator + * + * Category: Expressions + * Type: Conversions + * Operands: + * Stack: val => ToNumeric(val) + */ \ + MACRO(ToNumeric, to_numeric, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * Convert a value to a string. + * + * Implements: [ToString][1](val). + * + * Note: This is used in code for template literals, like `${x}${y}`. Each + * substituted value must be converted using ToString. `JSOp::Add` by itself + * would do a slightly wrong kind of conversion (hint="number" rather than + * hint="string"). + * + * [1]: https://tc39.es/ecma262/#sec-tostring + * + * Category: Expressions + * Type: Conversions + * Stack: val => ToString(val) + */ \ + MACRO(ToString, to_string, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Test whether the value on top of the stack is `NullValue` or + * `UndefinedValue` and push the boolean result. + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: val => val, IsNullOrUndefined(val) + */ \ + MACRO(IsNullOrUndefined, is_null_or_undefined, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Push the global `this` value. Not to be confused with the `globalThis` + * property on the global. + * + * This must be used only in scopes where `this` refers to the global + * `this`. + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: => this + */ \ + MACRO(GlobalThis, global_this, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Push the global `this` value for non-syntactic scope. Not to be confused + * with the `globalThis` property on the global. + * + * This must be used only in scopes where `this` refers to the global + * `this`. + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: => this + */ \ + MACRO(NonSyntacticGlobalThis, non_syntactic_global_this, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Push the value of `new.target`. + * + * The result is a constructor or `undefined`. + * + * This must be used only in non-arrow function scripts. + * + * Implements: [GetNewTarget][1]. + * + * [1]: https://tc39.es/ecma262/#sec-getnewtarget + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: => new.target + */ \ + MACRO(NewTarget, new_target, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Dynamic import of the module specified by the string value on the top of + * the stack. + * + * Implements: [Import Calls][1]. + * + * [1]: https://tc39.es/ecma262/#sec-import-calls + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: moduleId, options => promise + */ \ + MACRO(DynamicImport, dynamic_import, NULL, 1, 2, 1, JOF_BYTE) \ + /* + * Push the `import.meta` object. + * + * This must be used only in module code. + * + * Category: Expressions + * Type: Other expressions + * Operands: + * Stack: => import.meta + */ \ + MACRO(ImportMeta, import_meta, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Create and push a new object with no properties. + * + * Category: Objects + * Type: Creating objects + * Operands: + * Stack: => obj + */ \ + MACRO(NewInit, new_init, NULL, 1, 0, 1, JOF_BYTE|JOF_IC) \ + /* + * Create and push a new object of a predetermined shape. + * + * The new object has the shape `script->getShape(shapeIndex)`. + * Subsequent `InitProp` instructions must fill in all slots of the new + * object before it is used in any other way. + * + * Category: Objects + * Type: Creating objects + * Operands: uint32_t shapeIndex + * Stack: => obj + */ \ + MACRO(NewObject, new_object, NULL, 5, 0, 1, JOF_SHAPE|JOF_IC) \ + /* + * Push a preconstructed object. + * + * Going one step further than `JSOp::NewObject`, this instruction doesn't + * just reuse the shape--it actually pushes the preconstructed object + * `script->getObject(objectIndex)` right onto the stack. The object must + * be a singleton `PlainObject` or `ArrayObject`. + * + * The spec requires that an *ObjectLiteral* or *ArrayLiteral* creates a + * new object every time it's evaluated, so this instruction must not be + * used anywhere it might be executed more than once. + * + * This may only be used in non-function run-once scripts. Care also must + * be taken to not emit in loops or other constructs where it could run + * more than once. + * + * Category: Objects + * Type: Creating objects + * Operands: uint32_t objectIndex + * Stack: => obj + */ \ + MACRO(Object, object, NULL, 5, 0, 1, JOF_OBJECT) \ + /* + * Create and push a new ordinary object with the provided [[Prototype]]. + * + * This is used to create the `.prototype` object for derived classes. + * + * Category: Objects + * Type: Creating objects + * Operands: + * Stack: proto => obj + */ \ + MACRO(ObjWithProto, obj_with_proto, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Define a data property on an object. + * + * `obj` must be an object. + * + * Implements: [CreateDataPropertyOrThrow][1] as used in + * [PropertyDefinitionEvaluation][2] of regular and shorthand + * *PropertyDefinition*s. + * + * [1]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow + * [2]: https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-propertydefinitionevaluation + * + * Category: Objects + * Type: Defining properties + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + MACRO(InitProp, init_prop, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT|JOF_IC) \ + /* + * Like `JSOp::InitProp`, but define a non-enumerable property. + * + * This is used to define class methods. + * + * Implements: [PropertyDefinitionEvaluation][1] for methods, steps 3 and + * 4, when *enumerable* is false. + * + * [1]: https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation + * + * Category: Objects + * Type: Defining properties + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + MACRO(InitHiddenProp, init_hidden_prop, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT|JOF_IC) \ + /* + * Like `JSOp::InitProp`, but define a non-enumerable, non-writable, + * non-configurable property. + * + * This is used to define the `.prototype` property on classes. + * + * Implements: [MakeConstructor][1], step 8, when *writablePrototype* is + * false. + * + * [1]: https://tc39.es/ecma262/#sec-makeconstructor + * + * Category: Objects + * Type: Defining properties + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + MACRO(InitLockedProp, init_locked_prop, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT|JOF_IC) \ + /* + * Define a data property on `obj` with property key `id` and value `val`. + * + * `obj` must be an object. + * + * Implements: [CreateDataPropertyOrThrow][1]. This instruction is used for + * object literals like `{0: val}` and `{[id]: val}`, and methods like + * `*[Symbol.iterator]() {}`. + * + * `JSOp::InitHiddenElem` is the same but defines a non-enumerable property, + * for class methods. + * `JSOp::InitLockedElem` is the same but defines a non-enumerable, non-writable, non-configurable property, + * for private class methods. + * + * [1]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow + * + * Category: Objects + * Type: Defining properties + * Operands: + * Stack: obj, id, val => obj + */ \ + MACRO(InitElem, init_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \ + MACRO(InitHiddenElem, init_hidden_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \ + MACRO(InitLockedElem, init_locked_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \ + /* + * Define an accessor property on `obj` with the given `getter`. + * `nameIndex` gives the property name. + * + * `obj` must be an object and `getter` must be a function. + * + * `JSOp::InitHiddenPropGetter` is the same but defines a non-enumerable + * property, for getters in classes. + * + * Category: Objects + * Type: Defining properties + * Operands: uint32_t nameIndex + * Stack: obj, getter => obj + */ \ + MACRO(InitPropGetter, init_prop_getter, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT) \ + MACRO(InitHiddenPropGetter, init_hidden_prop_getter, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT) \ + /* + * Define an accessor property on `obj` with property key `id` and the given `getter`. + * + * This is used to implement getters like `get [id]() {}` or `get 0() {}`. + * + * `obj` must be an object and `getter` must be a function. + * + * `JSOp::InitHiddenElemGetter` is the same but defines a non-enumerable + * property, for getters in classes. + * + * Category: Objects + * Type: Defining properties + * Operands: + * Stack: obj, id, getter => obj + */ \ + MACRO(InitElemGetter, init_elem_getter, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT) \ + MACRO(InitHiddenElemGetter, init_hidden_elem_getter, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT) \ + /* + * Define an accessor property on `obj` with the given `setter`. + * + * This is used to implement ordinary setters like `set foo(v) {}`. + * + * `obj` must be an object and `setter` must be a function. + * + * `JSOp::InitHiddenPropSetter` is the same but defines a non-enumerable + * property, for setters in classes. + * + * Category: Objects + * Type: Defining properties + * Operands: uint32_t nameIndex + * Stack: obj, setter => obj + */ \ + MACRO(InitPropSetter, init_prop_setter, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT) \ + MACRO(InitHiddenPropSetter, init_hidden_prop_setter, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPINIT) \ + /* + * Define an accesssor property on `obj` with property key `id` and the + * given `setter`. + * + * This is used to implement setters with computed property keys or numeric + * keys. + * + * `JSOp::InitHiddenElemSetter` is the same but defines a non-enumerable + * property, for setters in classes. + * + * Category: Objects + * Type: Defining properties + * Operands: + * Stack: obj, id, setter => obj + */ \ + MACRO(InitElemSetter, init_elem_setter, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT) \ + MACRO(InitHiddenElemSetter, init_hidden_elem_setter, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPINIT) \ + /* + * Get the value of the property `obj.name`. This can call getters and + * proxy traps. + * + * Implements: [GetV][1], [GetValue][2] step 5. + * + * [1]: https://tc39.es/ecma262/#sec-getv + * [2]: https://tc39.es/ecma262/#sec-getvalue + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: obj => obj[name] + */ \ + MACRO(GetProp, get_prop, NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_IC) \ + /* + * Get the value of the property `obj[key]`. + * + * Implements: [GetV][1], [GetValue][2] step 5. + * + * [1]: https://tc39.es/ecma262/#sec-getv + * [2]: https://tc39.es/ecma262/#sec-getvalue + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: obj, key => obj[key] + */ \ + MACRO(GetElem, get_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_IC) \ + /* + * Non-strict assignment to a property, `obj.name = val`. + * + * This throws a TypeError if `obj` is null or undefined. If it's a + * primitive value, the property is set on ToObject(`obj`), typically with + * no effect. + * + * Implements: [PutValue][1] step 6 for non-strict code. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: obj, val => val + */ \ + MACRO(SetProp, set_prop, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPSET|JOF_CHECKSLOPPY|JOF_IC) \ + /* + * Like `JSOp::SetProp`, but for strict mode code. Throw a TypeError if + * `obj[key]` exists but is non-writable, if it's an accessor property with + * no setter, or if `obj` is a primitive value. + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: obj, val => val + */ \ + MACRO(StrictSetProp, strict_set_prop, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_PROPSET|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Non-strict assignment to a property, `obj[key] = val`. + * + * Implements: [PutValue][1] step 6 for non-strict code. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: obj, key, val => val + */ \ + MACRO(SetElem, set_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSLOPPY|JOF_IC) \ + /* + * Like `JSOp::SetElem`, but for strict mode code. Throw a TypeError if + * `obj[key]` exists but is non-writable, if it's an accessor property with + * no setter, or if `obj` is a primitive value. + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: obj, key, val => val + */ \ + MACRO(StrictSetElem, strict_set_elem, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Delete a property from `obj`. Push true on success, false if the + * property existed but could not be deleted. This implements `delete + * obj.name` in non-strict code. + * + * Throws if `obj` is null or undefined. Can call proxy traps. + * + * Implements: [`delete obj.propname`][1] step 5 in non-strict code. + * + * [1]: https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: obj => succeeded + */ \ + MACRO(DelProp, del_prop, NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_CHECKSLOPPY) \ + /* + * Like `JSOp::DelProp`, but for strict mode code. Push `true` on success, + * else throw a TypeError. + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: obj => succeeded + */ \ + MACRO(StrictDelProp, strict_del_prop, NULL, 5, 1, 1, JOF_ATOM|JOF_PROP|JOF_CHECKSTRICT) \ + /* + * Delete the property `obj[key]` and push `true` on success, `false` + * if the property existed but could not be deleted. + * + * This throws if `obj` is null or undefined. Can call proxy traps. + * + * Implements: [`delete obj[key]`][1] step 5 in non-strict code. + * + * [1]: https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: obj, key => succeeded + */ \ + MACRO(DelElem, del_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_CHECKSLOPPY) \ + /* + * Like `JSOp::DelElem, but for strict mode code. Push `true` on success, + * else throw a TypeError. + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: obj, key => succeeded + */ \ + MACRO(StrictDelElem, strict_del_elem, NULL, 1, 2, 1, JOF_BYTE|JOF_ELEM|JOF_CHECKSTRICT) \ + /* + * Push true if `obj` has an own property `id`. + * + * Note that `obj` is the top value, like `JSOp::In`. + * + * This opcode is not used for normal JS. Self-hosted code uses it by + * calling the intrinsic `hasOwn(id, obj)`. For example, + * `Object.prototype.hasOwnProperty` is implemented this way (see + * js/src/builtin/Object.js). + * + * Category: Objects + * Type: Accessing properties + * Operands: + * Stack: id, obj => (obj.hasOwnProperty(id)) + */ \ + MACRO(HasOwn, has_own, NULL, 1, 2, 1, JOF_BYTE|JOF_IC) \ + /* + * Push a bool representing the presence of private field id on obj. + * May throw, depending on the ThrowCondition. + * + * Two arguments: + * - throwCondition: One of the ThrowConditions defined in + * ThrowMsgKind.h. Determines why (or if) this op will throw. + * - msgKind: One of the ThrowMsgKinds defined in ThrowMsgKind.h, which + * maps to one of the messages in js.msg. Note: It's not possible to + * pass arguments to the message at the moment. + * + * Category: Objects + * Type: Accessing properties + * Operands: ThrowCondition throwCondition, ThrowMsgKind msgKind + * Stack: obj, key => obj, key, (obj.hasOwnProperty(id)) + */ \ + MACRO(CheckPrivateField, check_private_field, NULL, 3, 2, 3, JOF_TWO_UINT8|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Push a new private name. + * + * Category: Objects + * Type: Accessing properties + * Operands: uint32_t nameIndex + * Stack: => private_name + */ \ + MACRO(NewPrivateName, new_private_name, NULL, 5, 0, 1, JOF_ATOM) \ + /* + * Push the SuperBase of the method `callee`. The SuperBase is + * `callee.[[HomeObject]].[[GetPrototypeOf]]()`, the object where `super` + * property lookups should begin. + * + * `callee` must be a function that has a HomeObject that's an object, + * typically produced by `JSOp::Callee` or `JSOp::EnvCallee`. + * + * Implements: [GetSuperBase][1], except that instead of the environment, + * the argument supplies the callee. + * + * [1]: https://tc39.es/ecma262/#sec-getsuperbase + * + * Category: Objects + * Type: Super + * Operands: + * Stack: callee => superBase + */ \ + MACRO(SuperBase, super_base, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Get the value of `receiver.name`, starting the property search at `obj`. + * In spec terms, `obj.[[Get]](name, receiver)`. + * + * Implements: [GetValue][1] for references created by [`super.name`][2]. + * The `receiver` is `this` and `obj` is the SuperBase of the enclosing + * method. + * + * [1]: https://tc39.es/ecma262/#sec-getvalue + * [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + * + * Category: Objects + * Type: Super + * Operands: uint32_t nameIndex + * Stack: receiver, obj => super.name + */ \ + MACRO(GetPropSuper, get_prop_super, NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_IC) \ + /* + * Get the value of `receiver[key]`, starting the property search at `obj`. + * In spec terms, `obj.[[Get]](key, receiver)`. + * + * Implements: [GetValue][1] for references created by [`super[key]`][2] + * (where the `receiver` is `this` and `obj` is the SuperBase of the enclosing + * method); [`Reflect.get(obj, key, receiver)`][3]. + * + * [1]: https://tc39.es/ecma262/#sec-getvalue + * [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + * [3]: https://tc39.es/ecma262/#sec-reflect.get + * + * Category: Objects + * Type: Super + * Operands: + * Stack: receiver, key, obj => super[key] + */ \ + MACRO(GetElemSuper, get_elem_super, NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_IC) \ + /* + * Assign `val` to `receiver.name`, starting the search for an existing + * property at `obj`. In spec terms, `obj.[[Set]](name, val, receiver)`. + * + * Implements: [PutValue][1] for references created by [`super.name`][2] in + * non-strict code. The `receiver` is `this` and `obj` is the SuperBase of + * the enclosing method. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + * + * Category: Objects + * Type: Super + * Operands: uint32_t nameIndex + * Stack: receiver, obj, val => val + */ \ + MACRO(SetPropSuper, set_prop_super, NULL, 5, 3, 1, JOF_ATOM|JOF_PROP|JOF_PROPSET|JOF_CHECKSLOPPY) \ + /* + * Like `JSOp::SetPropSuper`, but for strict mode code. + * + * Category: Objects + * Type: Super + * Operands: uint32_t nameIndex + * Stack: receiver, obj, val => val + */ \ + MACRO(StrictSetPropSuper, strict_set_prop_super, NULL, 5, 3, 1, JOF_ATOM|JOF_PROP|JOF_PROPSET|JOF_CHECKSTRICT) \ + /* + * Assign `val` to `receiver[key]`, strating the search for an existing + * property at `obj`. In spec terms, `obj.[[Set]](key, val, receiver)`. + * + * Implements: [PutValue][1] for references created by [`super[key]`][2] in + * non-strict code. The `receiver` is `this` and `obj` is the SuperBase of + * the enclosing method. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * [2]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + * + * Category: Objects + * Type: Super + * Operands: + * Stack: receiver, key, obj, val => val + */ \ + MACRO(SetElemSuper, set_elem_super, NULL, 1, 4, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSLOPPY) \ + /* + * Like `JSOp::SetElemSuper`, but for strict mode code. + * + * Category: Objects + * Type: Super + * Operands: + * Stack: receiver, key, obj, val => val + */ \ + MACRO(StrictSetElemSuper, strict_set_elem_super, NULL, 1, 4, 1, JOF_BYTE|JOF_ELEM|JOF_PROPSET|JOF_CHECKSTRICT) \ + /* + * Set up a for-in loop by pushing a `PropertyIteratorObject` over the + * enumerable properties of `val`. + * + * Implements: [ForIn/OfHeadEvaluation][1] step 6, + * [EnumerateObjectProperties][1]. (The spec refers to an "Iterator object" + * with a `next` method, but notes that it "is never directly accessible" + * to scripts. The object we use for this has no public methods.) + * + * If `val` is null or undefined, this pushes an empty iterator. + * + * The `iter` object pushed by this instruction must not be used or removed + * from the stack except by `JSOp::MoreIter` and `JSOp::EndIter`, or by error + * handling. + * + * The script's `JSScript::trynotes()` must mark the body of the `for-in` + * loop, i.e. exactly those instructions that begin executing with `iter` + * on the stack, starting with the next instruction (always + * `JSOp::LoopHead`). Code must not jump into or out of this region: control + * can enter only by executing `JSOp::Iter` and can exit only by executing a + * `JSOp::EndIter` or by exception unwinding. (A `JSOp::EndIter` is always + * emitted at the end of the loop, and extra copies are emitted on "exit + * slides", where a `break`, `continue`, or `return` statement exits the + * loop.) + * + * Typically a single try note entry marks the contiguous chunk of bytecode + * from the instruction after `JSOp::Iter` to `JSOp::EndIter` (inclusive); + * but if that range contains any instructions on exit slides, after a + * `JSOp::EndIter`, then those must be correctly noted as *outside* the + * loop. + * + * [1]: https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofheadevaluation-tdznames-expr-iterationkind + * [2]: https://tc39.es/ecma262/#sec-enumerate-object-properties + * + * Category: Objects + * Type: Enumeration + * Operands: + * Stack: val => iter + */ \ + MACRO(Iter, iter, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * Get the next property name for a for-in loop. + * + * `iter` must be a `PropertyIteratorObject` produced by `JSOp::Iter`. This + * pushes the property name for the next loop iteration, or + * `MagicValue(JS_NO_ITER_VALUE)` if there are no more enumerable + * properties to iterate over. The magic value must be used only by + * `JSOp::IsNoIter` and `JSOp::EndIter`. + * + * Category: Objects + * Type: Enumeration + * Operands: + * Stack: iter => iter, name + */ \ + MACRO(MoreIter, more_iter, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Test whether the value on top of the stack is + * `MagicValue(JS_NO_ITER_VALUE)` and push the boolean result. + * + * Category: Objects + * Type: Enumeration + * Operands: + * Stack: val => val, done + */ \ + MACRO(IsNoIter, is_no_iter, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Exit a for-in loop, closing the iterator. + * + * `iter` must be a `PropertyIteratorObject` pushed by `JSOp::Iter`. + * + * Category: Objects + * Type: Enumeration + * Operands: + * Stack: iter, iterval => + */ \ + MACRO(EndIter, end_iter, NULL, 1, 2, 0, JOF_BYTE) \ + /* + * If the iterator object on top of the stack has a `return` method, + * call that method. If the method exists but does not return an object, + * and `kind` is not `CompletionKind::Throw`, throw a TypeError. (If + * `kind` is `Throw`, the error we are already throwing takes precedence.) + * + * `iter` must be an object conforming to the [Iterator][1] interface. + * + * Implements: [IteratorClose][2] + * + * [1]: https://tc39.es/ecma262/#sec-iterator-interface + * [2]: https://tc39.es/ecma262/#sec-iteratorclose + * Category: Objects + * Type: Iteration + * Operands: CompletionKind kind + * Stack: iter => + */ \ + MACRO(CloseIter, close_iter, NULL, 2, 1, 0, JOF_UINT8|JOF_IC) \ + /* + * Check that the top value on the stack is an object, and throw a + * TypeError if not. `kind` is used only to generate an appropriate error + * message. + * + * Implements: [GetIterator][1] step 5, [IteratorNext][2] step 3. Both + * operations call a JS method which scripts can define however they want, + * so they check afterwards that the method returned an object. + * + * [1]: https://tc39.es/ecma262/#sec-getiterator + * [2]: https://tc39.es/ecma262/#sec-iteratornext + * + * Category: Objects + * Type: Iteration + * Operands: CheckIsObjectKind kind + * Stack: result => result + */ \ + MACRO(CheckIsObj, check_is_obj, NULL, 2, 1, 1, JOF_UINT8) \ + /* + * Throw a TypeError if `val` is `null` or `undefined`. + * + * Implements: [RequireObjectCoercible][1]. But most instructions that + * require an object will perform this check for us, so of the dozens of + * calls to RequireObjectCoercible in the spec, we need this instruction + * only for [destructuring assignment][2] and [initialization][3]. + * + * [1]: https://tc39.es/ecma262/#sec-requireobjectcoercible + * [2]: https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation + * [3]: https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-bindinginitialization + * + * Category: Objects + * Type: Iteration + * Operands: + * Stack: val => val + */ \ + MACRO(CheckObjCoercible, check_obj_coercible, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Create and push an async iterator wrapping the sync iterator `iter`. + * `next` should be `iter`'s `.next` method. + * + * Implements: [CreateAsyncToSyncIterator][1]. The spec says this operation + * takes one argument, but that argument is a Record with two relevant + * fields, `[[Iterator]]` and `[[NextMethod]]`. + * + * Used for `for await` loops. + * + * [1]: https://tc39.es/ecma262/#sec-createasyncfromsynciterator + * + * Category: Objects + * Type: Iteration + * Operands: + * Stack: iter, next => asynciter + */ \ + MACRO(ToAsyncIter, to_async_iter, NULL, 1, 2, 1, JOF_BYTE) \ + /* + * Set the prototype of `obj`. + * + * `obj` must be an object. + * + * Implements: [B.3.1 __proto__ Property Names in Object Initializers][1], step 7.a. + * + * [1]: https://tc39.es/ecma262/#sec-__proto__-property-names-in-object-initializers + * + * Category: Objects + * Type: SetPrototype + * Operands: + * Stack: obj, protoVal => obj + */ \ + MACRO(MutateProto, mutate_proto, NULL, 1, 2, 1, JOF_BYTE) \ + /* + * Create and push a new Array object with the given `length`, + * preallocating enough memory to hold that many elements. + * + * Category: Objects + * Type: Array literals + * Operands: uint32_t length + * Stack: => array + */ \ + MACRO(NewArray, new_array, NULL, 5, 0, 1, JOF_UINT32|JOF_IC) \ + /* + * Initialize an array element `array[index]` with value `val`. + * + * `val` may be `MagicValue(JS_ELEMENTS_HOLE)` pushed by `JSOp::Hole`. + * + * This never calls setters or proxy traps. + * + * `array` must be an Array object created by `JSOp::NewArray` with length > + * `index`, and never used except by `JSOp::InitElemArray`. + * + * Implements: [ArrayAccumulation][1], the third algorithm, step 4, in the + * common case where *nextIndex* is known. + * + * [1]: https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation + * + * Category: Objects + * Type: Array literals + * Operands: uint32_t index + * Stack: array, val => array + */ \ + MACRO(InitElemArray, init_elem_array, NULL, 5, 2, 1, JOF_UINT32|JOF_ELEM|JOF_PROPINIT) \ + /* + * Initialize an array element `array[index++]` with value `val`. + * + * `val` may be `MagicValue(JS_ELEMENTS_HOLE)` pushed by `JSOp::Hole`. If it + * is, no element is defined, but the array length and the stack value + * `index` are still incremented. + * + * This never calls setters or proxy traps. + * + * `array` must be an Array object created by `JSOp::NewArray` and never used + * except by `JSOp::InitElemArray` and `JSOp::InitElemInc`. + * + * `index` must be an integer, `0 <= index <= INT32_MAX`. If `index` is + * `INT32_MAX`, this throws a RangeError. Unlike `InitElemArray`, it is not + * necessary that the `array` length > `index`. + * + * This instruction is used when an array literal contains a + * *SpreadElement*. In `[a, ...b, c]`, `InitElemArray 0` is used to put + * `a` into the array, but `InitElemInc` is used for the elements of `b` + * and for `c`. + * + * Implements: Several steps in [ArrayAccumulation][1] that call + * CreateDataProperty, set the array length, and/or increment *nextIndex*. + * + * [1]: https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation + * + * Category: Objects + * Type: Array literals + * Operands: + * Stack: array, index, val => array, (index + 1) + */ \ + MACRO(InitElemInc, init_elem_inc, NULL, 1, 3, 2, JOF_BYTE|JOF_ELEM|JOF_PROPINIT|JOF_IC) \ + /* + * Push `MagicValue(JS_ELEMENTS_HOLE)`, representing an *Elision* in an + * array literal (like the missing property 0 in the array `[, 1]`). + * + * This magic value must be used only by `JSOp::InitElemArray` or + * `JSOp::InitElemInc`. + * + * Category: Objects + * Type: Array literals + * Operands: + * Stack: => hole + */ \ + MACRO(Hole, hole, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Clone and push a new RegExp object. + * + * Implements: [Evaluation for *RegularExpressionLiteral*][1]. + * + * [1]: https://tc39.es/ecma262/#sec-regular-expression-literals-runtime-semantics-evaluation + * + * Category: Objects + * Type: RegExp literals + * Operands: uint32_t regexpIndex + * Stack: => regexp + */ \ + MACRO(RegExp, reg_exp, NULL, 5, 0, 1, JOF_REGEXP) \ + /* + * Initialize a new record, preallocating `length` memory slots. `length` can still grow + * if needed, for example when using the spread operator. + * + * Implements: [RecordLiteral Evaluation][1] step 1. + * + * [1]: https://tc39.es/proposal-record-tuple/#sec-record-initializer-runtime-semantics-evaluation + * + * Category: Compound primitives + * Type: Record literals + * Operands: uint32_t length + * Stack: => rval + */ \ + IF_RECORD_TUPLE(MACRO(InitRecord, init_record, NULL, 5, 0, 1, JOF_UINT32)) \ + /* + * Add the last element in the stack to the preceding tuple. + * + * Implements: [AddPropertyIntoRecordEntriesList][1]. + * + * [1]: https://tc39.es/proposal-record-tuple/#sec-addpropertyintorecordentrieslist + * + * Category: Compound primitives + * Type: Record literals + * Operands: + * Stack: record, key, value => record + */ \ + IF_RECORD_TUPLE(MACRO(AddRecordProperty, add_record_property, NULL, 1, 3, 1, JOF_BYTE)) \ + /* + * Add the last element in the stack to the preceding tuple. + * + * Implements: [RecordPropertyDefinitionEvaluation][1] for + * RecordPropertyDefinition : ... AssignmentExpression + * + * [1]: https://tc39.es/proposal-record-tuple/#sec-addpropertyintorecordentrieslist + * + * Category: Compound primitives + * Type: Record literals + * Operands: + * Stack: record, value => record + */ \ + IF_RECORD_TUPLE(MACRO(AddRecordSpread, add_record_spread, NULL, 1, 2, 1, JOF_BYTE)) \ + /* + * Mark a record as "initialized", going from "write-only" mode to + * "read-only" mode. + * + * Category: Compound primitives + * Type: Record literals + * Operands: + * Stack: record => record + */ \ + IF_RECORD_TUPLE(MACRO(FinishRecord, finish_record, NULL, 1, 1, 1, JOF_BYTE)) \ + /* + * Initialize a new tuple, preallocating `length` memory slots. `length` can still grow + * if needed, for example when using the spread operator. + * + * Implements: [TupleLiteral Evaluation][1] step 1. + * + * [1]: https://tc39.es/proposal-record-tuple/#sec-tuple-initializer-runtime-semantics-evaluation + * + * Category: Compound primitives + * Type: Tuple literals + * Operands: uint32_t length + * Stack: => rval + */ \ + IF_RECORD_TUPLE(MACRO(InitTuple, init_tuple, NULL, 5, 0, 1, JOF_UINT32)) \ + /* + * Add the last element in the stack to the preceding tuple. + * + * Implements: [AddValueToTupleSequenceList][1]. + * + * [1]: https://tc39.es/proposal-record-tuple/#sec-addvaluetotuplesequencelist + * + * Category: Compound primitives + * Type: Tuple literals + * Operands: + * Stack: tuple, element => tuple + */ \ + IF_RECORD_TUPLE(MACRO(AddTupleElement, add_tuple_element, NULL, 1, 2, 1, JOF_BYTE)) \ + /* + * Mark a tuple as "initialized", going from "write-only" mode to + * "read-only" mode. + * + * Category: Compound primitives + * Type: Tuple literals + * Operands: + * Stack: tuple => tuple + */ \ + IF_RECORD_TUPLE(MACRO(FinishTuple, finish_tuple, NULL, 1, 1, 1, JOF_BYTE)) \ + /* + * Push a new function object. + * + * The new function inherits the current environment chain. + * + * Used to create most JS functions. Notable exceptions are derived or + * default class constructors. + * + * Implements: [InstantiateFunctionObject][1], [Evaluation for + * *FunctionExpression*][2], and so on. + * + * [1]: https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-instantiatefunctionobject + * [2]: https://tc39.es/ecma262/#sec-function-definitions-runtime-semantics-evaluation + * + * Category: Functions + * Type: Creating functions + * Operands: uint32_t funcIndex + * Stack: => fn + */ \ + MACRO(Lambda, lambda, NULL, 5, 0, 1, JOF_OBJECT|JOF_USES_ENV) \ + /* + * Set the name of a function. + * + * `fun` must be a function object. `name` must be a string, Int32 value, + * or symbol (like the result of `JSOp::ToId`). + * + * Implements: [SetFunctionName][1], used e.g. to name methods with + * computed property names. + * + * [1]: https://tc39.es/ecma262/#sec-setfunctionname + * + * Category: Functions + * Type: Creating functions + * Operands: FunctionPrefixKind prefixKind + * Stack: fun, name => fun + */ \ + MACRO(SetFunName, set_fun_name, NULL, 2, 2, 1, JOF_UINT8) \ + /* + * Initialize the home object for functions with super bindings. + * + * `fun` must be a method, getter, or setter, so that it has a + * [[HomeObject]] slot. `homeObject` must be a plain object or (for static + * methods) a constructor. + * + * Category: Functions + * Type: Creating functions + * Operands: + * Stack: fun, homeObject => fun + */ \ + MACRO(InitHomeObject, init_home_object, NULL, 1, 2, 1, JOF_BYTE) \ + /* + * Throw a TypeError if `baseClass` isn't either `null` or a constructor. + * + * Implements: [ClassDefinitionEvaluation][1] step 6.f. + * + * [1]: https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation + * + * Category: Functions + * Type: Creating constructors + * Operands: + * Stack: baseClass => baseClass + */ \ + MACRO(CheckClassHeritage, check_class_heritage, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Like `JSOp::Lambda`, but using `proto` as the new function's + * `[[Prototype]]` (or `%FunctionPrototype%` if `proto` is `null`). + * + * `proto` must be either a constructor or `null`. We use + * `JSOp::CheckClassHeritage` to check. + * + * This is used to create the constructor for a derived class. + * + * Implements: [ClassDefinitionEvaluation][1] steps 6.e.ii, 6.g.iii, and + * 12 for derived classes. + * + * [1]: https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation + * + * Category: Functions + * Type: Creating constructors + * Operands: uint32_t funcIndex + * Stack: proto => obj + */ \ + MACRO(FunWithProto, fun_with_proto, NULL, 5, 1, 1, JOF_OBJECT|JOF_USES_ENV) \ + /* + * Pushes the current global's %BuiltinObject%. + * + * `kind` must be a valid `BuiltinObjectKind` (and must not be + * `BuiltinObjectKind::None`). + * + * Category: Objects + * Type: Built-in objects + * Operands: uint8_t kind + * Stack: => %BuiltinObject% + */ \ + MACRO(BuiltinObject, builtin_object, NULL, 2, 0, 1, JOF_UINT8) \ + /* + * Invoke `callee` with `this` and `args`, and push the return value. Throw + * a TypeError if `callee` isn't a function. + * + * `JSOp::CallContent` is for `callContentFunction` in self-hosted JS, and + * this is for handling it differently in debugger's `onNativeCall` hook. + * `onNativeCall` hook disables all JITs, and `JSOp::CallContent` is + * treated exactly the same as `JSOP::Call` in JIT. + * + * `JSOp::CallIter` is used for implicit calls to @@iterator methods, to + * ensure error messages are formatted with `JSMSG_NOT_ITERABLE` ("x is not + * iterable") rather than `JSMSG_NOT_FUNCTION` ("x[Symbol.iterator] is not + * a function"). The `argc` operand must be 0 for this variation. + * + * `JSOp::CallContentIter` is `JSOp::CallContent` variant of + * `JSOp::CallIter`. + * + * `JSOp::CallIgnoresRv` hints to the VM that the return value is ignored. + * This allows alternate faster implementations to be used that avoid + * unnecesary allocations. + * + * Implements: [EvaluateCall][1] steps 4, 5, and 7. + * + * [1]: https://tc39.es/ecma262/#sec-evaluatecall + * + * Category: Functions + * Type: Calls + * Operands: uint16_t argc + * Stack: callee, this, args[0], ..., args[argc-1] => rval + */ \ + MACRO(Call, call, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_IC) \ + MACRO(CallContent, call_content, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_IC) \ + MACRO(CallIter, call_iter, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_IC) \ + MACRO(CallContentIter, call_content_iter, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_IC) \ + MACRO(CallIgnoresRv, call_ignores_rv, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_IC) \ + /* + * Like `JSOp::Call`, but the arguments are provided in an array rather than + * a span of stack slots. Used to implement spread-call syntax: + * `f(...args)`. + * + * `args` must be an Array object containing the actual arguments. The + * array must be packed (dense and free of holes; see IsPackedArray). + * This can be ensured by creating the array with `JSOp::NewArray` and + * populating it using `JSOp::InitElemArray`. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: callee, this, args => rval + */ \ + MACRO(SpreadCall, spread_call, NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_SPREAD|JOF_IC) \ + /* + * Push an array object that can be passed directly as the `args` argument + * to `JSOp::SpreadCall`. If the operation can't be optimized, push + * `undefined` instead. + * + * This instruction and the branch around the iterator loop are emitted + * only when `iterable` is the sole argument in a call, as in `f(...arr)`. + * + * See `js::OptimizeSpreadCall`. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: iterable => array_or_undefined + */ \ + MACRO(OptimizeSpreadCall, optimize_spread_call, NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \ + /* + * Perform a direct eval in the current environment if `callee` is the + * builtin `eval` function, otherwise follow same behaviour as `JSOp::Call`. + * + * All direct evals use one of the JSOp::*Eval instructions here and these + * opcodes are only used when the syntactic conditions for a direct eval + * are met. If the builtin `eval` function is called though other means, it + * becomes an indirect eval. + * + * Direct eval causes all bindings in *enclosing* non-global scopes to be + * marked "aliased". The optimization that puts bindings in stack slots has + * to prove that the bindings won't need to be captured by closures or + * accessed using `JSOp::{Get,Bind,Set,Del}Name` instructions. Direct eval + * makes that analysis impossible. + * + * The instruction immediately following any `JSOp::*Eval` instruction must + * be `JSOp::Lineno`. + * + * Implements: [Function Call Evaluation][1], steps 5-7 and 9, when the + * syntactic critera for direct eval in step 6 are all met. + * + * [1]: https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation + * + * Category: Functions + * Type: Calls + * Operands: uint16_t argc + * Stack: callee, this, args[0], ..., args[argc-1] => rval + */ \ + MACRO(Eval, eval, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_CHECKSLOPPY|JOF_IC) \ + /* + * Spread-call variant of `JSOp::Eval`. + * + * See `JSOp::SpreadCall` for restrictions on `args`. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: callee, this, args => rval + */ \ + MACRO(SpreadEval, spread_eval, NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_SPREAD|JOF_CHECKSLOPPY|JOF_IC) \ + /* + * Like `JSOp::Eval`, but for strict mode code. + * + * Category: Functions + * Type: Calls + * Operands: uint16_t argc + * Stack: evalFn, this, args[0], ..., args[argc-1] => rval + */ \ + MACRO(StrictEval, strict_eval, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Spread-call variant of `JSOp::StrictEval`. + * + * See `JSOp::SpreadCall` for restrictions on `args`. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: callee, this, args => rval + */ \ + MACRO(StrictSpreadEval, strict_spread_eval, NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_SPREAD|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Push the implicit `this` value for an unqualified function call, like + * `foo()`. `nameIndex` gives the name of the function we're calling. + * + * The result is always `undefined` except when the name refers to a `with` + * binding. For example, in `with (date) { getFullYear(); }`, the + * implicit `this` passed to `getFullYear` is `date`, not `undefined`. + * + * This walks the run-time environment chain looking for the environment + * record that contains the function. If the function call definitely + * refers to a local binding, use `JSOp::Undefined`. + * + * Implements: [EvaluateCall][1] step 1.b. But not entirely correctly. + * See [bug 1166408][2]. + * + * [1]: https://tc39.es/ecma262/#sec-evaluatecall + * [2]: https://bugzilla.mozilla.org/show_bug.cgi?id=1166408 + * + * Category: Functions + * Type: Calls + * Operands: uint32_t nameIndex + * Stack: => this + */ \ + MACRO(ImplicitThis, implicit_this, "", 5, 0, 1, JOF_ATOM|JOF_USES_ENV) \ + /* + * Push the call site object for a tagged template call. + * + * `script->getObject(objectIndex)` is the call site object. + * + * The call site object will already have the `.raw` property defined on it + * and will be frozen. + * + * Category: Functions + * Type: Calls + * Operands: uint32_t objectIndex + * Stack: => callSiteObj + */ \ + MACRO(CallSiteObj, call_site_obj, NULL, 5, 0, 1, JOF_OBJECT) \ + /* + * Push `MagicValue(JS_IS_CONSTRUCTING)`. + * + * This magic value is a required argument to the `JSOp::New` and + * `JSOp::SuperCall` instructions and must not be used any other way. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: => JS_IS_CONSTRUCTING + */ \ + MACRO(IsConstructing, is_constructing, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Invoke `callee` as a constructor with `args` and `newTarget`, and push + * the return value. Throw a TypeError if `callee` isn't a constructor. + * + * `isConstructing` must be the value pushed by `JSOp::IsConstructing`. + * + * `JSOp::SuperCall` behaves exactly like `JSOp::New`, but is used for + * *SuperCall* expressions, to allow JITs to distinguish them from `new` + * expressions. + * + * `JSOp::NewContent` is for `constructContentFunction` in self-hosted JS. + * See the comment for `JSOp::CallContent` for more details. + * + * Implements: [EvaluateConstruct][1] steps 7 and 8. + * + * [1]: https://tc39.es/ecma262/#sec-evaluatenew + * + * Category: Functions + * Type: Calls + * Operands: uint16_t argc + * Stack: callee, isConstructing, args[0], ..., args[argc-1], newTarget => rval + */ \ + MACRO(New, new_, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_CONSTRUCT|JOF_IC) \ + MACRO(NewContent, new_content, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_CONSTRUCT|JOF_IC) \ + MACRO(SuperCall, super_call, NULL, 3, -1, 1, JOF_ARGC|JOF_INVOKE|JOF_CONSTRUCT|JOF_IC) \ + /* + * Spread-call variant of `JSOp::New`. + * + * Invokes `callee` as a constructor with `args` and `newTarget`, and + * pushes the return value onto the stack. + * + * `isConstructing` must be the value pushed by `JSOp::IsConstructing`. + * See `JSOp::SpreadCall` for restrictions on `args`. + * + * `JSOp::SpreadSuperCall` behaves exactly like `JSOp::SpreadNew`, but is + * used for *SuperCall* expressions. + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: callee, isConstructing, args, newTarget => rval + */ \ + MACRO(SpreadNew, spread_new, NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_CONSTRUCT|JOF_SPREAD|JOF_IC) \ + MACRO(SpreadSuperCall, spread_super_call, NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_CONSTRUCT|JOF_SPREAD|JOF_IC) \ + /* + * Push the prototype of `callee` in preparation for calling `super()`. + * + * `callee` must be a derived class constructor. + * + * Implements: [GetSuperConstructor][1], steps 4-7. + * + * [1]: https://tc39.es/ecma262/#sec-getsuperconstructor + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: callee => superFun + */ \ + MACRO(SuperFun, super_fun, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Throw a ReferenceError if `thisval` is not + * `MagicValue(JS_UNINITIALIZED_LEXICAL)`. Used in derived class + * constructors to prohibit calling `super` more than once. + * + * Implements: [BindThisValue][1], step 3. + * + * [1]: https://tc39.es/ecma262/#sec-bindthisvalue + * + * Category: Functions + * Type: Calls + * Operands: + * Stack: thisval => thisval + */ \ + MACRO(CheckThisReinit, check_this_reinit, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Create and push a generator object for the current frame. + * + * This instruction must appear only in scripts for generators, async + * functions, and async generators. There must not already be a generator + * object for the current frame (that is, this instruction must execute at + * most once per generator or async call). + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: => gen + */ \ + MACRO(Generator, generator, NULL, 1, 0, 1, JOF_BYTE|JOF_USES_ENV) \ + /* + * Suspend the current generator and return to the caller. + * + * When a generator is called, its script starts running, like any other JS + * function, because [FunctionDeclarationInstantation][1] and other + * [generator object setup][2] are implemented mostly in bytecode. However, + * the *FunctionBody* of the generator is not supposed to start running + * until the first `.next()` call, so after setup the script suspends + * itself: the "initial yield". + * + * Later, when resuming execution, `rval`, `gen` and `resumeKind` will + * receive the values passed in by `JSOp::Resume`. `resumeKind` is the + * `GeneratorResumeKind` stored as an Int32 value. + * + * This instruction must appear only in scripts for generators and async + * generators. `gen` must be the generator object for the current frame. It + * must not have been previously suspended. The resume point indicated by + * `resumeIndex` must be the next instruction in the script, which must be + * `AfterYield`. + * + * Implements: [GeneratorStart][3], steps 4-7. + * + * [1]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + * [2]: https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluatebody + * [3]: https://tc39.es/ecma262/#sec-generatorstart + * + * Category: Functions + * Type: Generators and async functions + * Operands: uint24_t resumeIndex + * Stack: gen => rval, gen, resumeKind + */ \ + MACRO(InitialYield, initial_yield, NULL, 4, 1, 3, JOF_RESUMEINDEX) \ + /* + * Bytecode emitted after `yield` expressions. This is useful for the + * Debugger and `AbstractGeneratorObject::isAfterYieldOrAwait`. It's + * treated as jump target op so that the Baseline Interpreter can + * efficiently restore the frame's interpreterICEntry when resuming a + * generator. + * + * The preceding instruction in the script must be `Yield`, `InitialYield`, + * or `Await`. + * + * Category: Functions + * Type: Generators and async functions + * Operands: uint32_t icIndex + * Stack: => + */ \ + MACRO(AfterYield, after_yield, NULL, 5, 0, 0, JOF_ICINDEX) \ + /* + * Suspend and close the current generator, async function, or async + * generator. + * + * `gen` must be the generator object for the current frame. + * + * If the current function is a non-async generator, then the value in the + * frame's return value slot is returned to the caller. It should be an + * object of the form `{value: returnValue, done: true}`. + * + * If the current function is an async function or async generator, the + * frame's return value slot must contain the current frame's result + * promise, which must already be resolved or rejected. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: gen => + */ \ + MACRO(FinalYieldRval, final_yield_rval, NULL, 1, 1, 0, JOF_BYTE) \ + /* + * Suspend execution of the current generator or async generator, returning + * `rval1`. + * + * For non-async generators, `rval1` should be an object of the form + * `{value: valueToYield, done: true}`. For async generators, `rval1` + * should be the value to yield, and the caller is responsible for creating + * the iterator result object (under `js::AsyncGeneratorYield`). + * + * This instruction must appear only in scripts for generators and async + * generators. `gen` must be the generator object for the current stack + * frame. The resume point indicated by `resumeIndex` must be the next + * instruction in the script, which must be `AfterYield`. + * + * When resuming execution, `rval2`, `gen` and `resumeKind` receive the + * values passed in by `JSOp::Resume`. + * + * Implements: [GeneratorYield][1] and [AsyncGeneratorYield][2]. + * + * [1]: https://tc39.es/ecma262/#sec-generatoryield + * [2]: https://tc39.es/ecma262/#sec-asyncgeneratoryield + * + * Category: Functions + * Type: Generators and async functions + * Operands: uint24_t resumeIndex + * Stack: rval1, gen => rval2, gen, resumeKind + */ \ + MACRO(Yield, yield, NULL, 4, 2, 3, JOF_RESUMEINDEX) \ + /* + * Pushes a boolean indicating whether the top of the stack is + * `MagicValue(JS_GENERATOR_CLOSING)`. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: val => val, res + */ \ + MACRO(IsGenClosing, is_gen_closing, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Arrange for this async function to resume asynchronously when `value` + * becomes resolved. + * + * This is the last thing an async function does before suspending for an + * `await` expression. It coerces the awaited `value` to a promise and + * effectively calls `.then()` on it, passing handler functions that will + * resume this async function call later. See `js::AsyncFunctionAwait`. + * + * This instruction must appear only in non-generator async function + * scripts. `gen` must be the internal generator object for the current + * frame. After this instruction, the script should suspend itself with + * `Await` (rather than exiting any other way). + * + * The result `promise` is the async function's result promise, + * `gen->as<AsyncFunctionGeneratorObject>().promise()`. + * + * Implements: [Await][1], steps 2-9. + * + * [1]: https://tc39.github.io/ecma262/#await + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: value, gen => promise + */ \ + MACRO(AsyncAwait, async_await, NULL, 1, 2, 1, JOF_BYTE) \ + /* + * Resolve or reject the current async function's result promise with + * 'valueOrReason'. + * + * This instruction must appear only in non-generator async function + * scripts. `gen` must be the internal generator object for the current + * frame. This instruction must run at most once per async function call, + * as resolving/rejecting an already resolved/rejected promise is not + * permitted. + * + * The result `promise` is the async function's result promise, + * `gen->as<AsyncFunctionGeneratorObject>().promise()`. + * + * Implements: [AsyncFunctionStart][1], step 4.d.i. and 4.e.i. + * + * [1]: https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start + * + * Category: Functions + * Type: Generators and async functions + * Operands: AsyncFunctionResolveKind fulfillOrReject + * Stack: valueOrReason, gen => promise + */ \ + MACRO(AsyncResolve, async_resolve, NULL, 2, 2, 1, JOF_UINT8) \ + /* + * Suspend the current frame for an `await` expression. + * + * This instruction must appear only in scripts for async functions and + * async generators. `gen` must be the internal generator object for the + * current frame. + * + * This returns `promise` to the caller. Later, when this async call is + * resumed, `resolved`, `gen` and `resumeKind` receive the values passed in + * by `JSOp::Resume`, and execution continues at the next instruction, + * which must be `AfterYield`. + * + * This instruction is used in two subtly different ways. + * + * 1. In async functions: + * + * ... # valueToAwait + * GetAliasedVar ".generator" # valueToAwait gen + * AsyncAwait # resultPromise + * GetAliasedVar ".generator" # resultPromise gen + * Await # resolved gen resumeKind + * AfterYield + * + * `AsyncAwait` arranges for this frame to be resumed later and pushes + * its result promise. `Await` then suspends the frame and removes it + * from the stack, returning the result promise to the caller. (If this + * async call hasn't awaited before, the caller may be user code. + * Otherwise, the caller is self-hosted code using `resumeGenerator`.) + * + * 2. In async generators: + * + * ... # valueToAwait + * GetAliasedVar ".generator" # valueToAwait gen + * Await # resolved gen resumeKind + * AfterYield + * + * `AsyncAwait` is not used, so (1) the value returned to the caller by + * `Await` is `valueToAwait`, not `resultPromise`; and (2) the caller + * is responsible for doing the async-generator equivalent of + * `AsyncAwait` (namely, `js::AsyncGeneratorAwait`, called from + * `js::AsyncGeneratorResume` after `js::CallSelfHostedFunction` + * returns). + * + * Implements: [Await][1], steps 10-12. + * + * [1]: https://tc39.es/ecma262/#await + * + * Category: Functions + * Type: Generators and async functions + * Operands: uint24_t resumeIndex + * Stack: promise, gen => resolved, gen, resumeKind + */ \ + MACRO(Await, await, NULL, 4, 2, 3, JOF_RESUMEINDEX) \ + /* + * Test if the re-entry to the microtask loop may be skipped. + * + * This is part of an optimization for `await` expressions. Programs very + * often await values that aren't promises, or promises that are already + * resolved. We can then sometimes skip suspending the current frame and + * returning to the microtask loop. If the circumstances permit the + * optimization, `CanSkipAwait` pushes true if the optimization is allowed, + * and false otherwise. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: value => value, can_skip + */ \ + MACRO(CanSkipAwait, can_skip_await, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Potentially extract an awaited value, if the await is skippable + * + * If re-entering the microtask loop is skippable (as checked by CanSkipAwait) + * if can_skip is true, `MaybeExtractAwaitValue` replaces `value` with the result of the + * `await` expression (unwrapping the resolved promise, if any). Otherwise, value remains + * as is. + * + * In both cases, can_skip remains the same. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: value, can_skip => value_or_resolved, can_skip + */ \ + MACRO(MaybeExtractAwaitValue, maybe_extract_await_value, NULL, 1, 2, 2, JOF_BYTE) \ + /* + * Pushes one of the GeneratorResumeKind values as Int32Value. + * + * Category: Functions + * Type: Generators and async functions + * Operands: GeneratorResumeKind resumeKind (encoded as uint8_t) + * Stack: => resumeKind + */ \ + MACRO(ResumeKind, resume_kind, NULL, 2, 0, 1, JOF_UINT8) \ + /* + * Handle Throw and Return resumption. + * + * `gen` must be the generator object for the current frame. `resumeKind` + * must be a `GeneratorResumeKind` stored as an `Int32` value. If it is + * `Next`, continue to the next instruction. If `resumeKind` is `Throw` or + * `Return`, these completions are handled by throwing an exception. See + * `GeneratorThrowOrReturn`. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: rval, gen, resumeKind => rval + */ \ + MACRO(CheckResumeKind, check_resume_kind, NULL, 1, 3, 1, JOF_BYTE) \ + /* + * Resume execution of a generator, async function, or async generator. + * + * This behaves something like a call instruction. It pushes a stack frame + * (the one saved when `gen` was suspended, rather than a fresh one) and + * runs instructions in it. Once `gen` returns or yields, its return value + * is pushed to this frame's stack and execution continues in this script. + * + * This instruction is emitted only for the `resumeGenerator` self-hosting + * intrinsic. It is used in the implementation of + * `%GeneratorPrototype%.next`, `.throw`, and `.return`. + * + * `gen` must be a suspended generator object. `resumeKind` must be in + * range for `GeneratorResumeKind`. + * + * Category: Functions + * Type: Generators and async functions + * Operands: + * Stack: gen, val, resumeKind => rval + */ \ + MACRO(Resume, resume, NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE) \ + /* + * No-op instruction marking the target of a jump instruction. + * + * This instruction and a few others (see `js::BytecodeIsJumpTarget`) are + * jump target instructions. The Baseline Interpreter uses these + * instructions to sync the frame's `interpreterICEntry` after a jump. Ion + * uses them to find block boundaries when translating bytecode to MIR. + * + * Category: Control flow + * Type: Jump targets + * Operands: uint32_t icIndex + * Stack: => + */ \ + MACRO(JumpTarget, jump_target, NULL, 5, 0, 0, JOF_ICINDEX) \ + /* + * Marks the target of the backwards jump for some loop. + * + * This is a jump target instruction (see `JSOp::JumpTarget`). Additionally, + * it checks for interrupts and handles JIT tiering. + * + * The `depthHint` operand is a loop depth hint for Ion. It starts at 1 and + * deeply nested loops all have the same value. + * + * For the convenience of the JITs, scripts must not start with this + * instruction. See bug 1602390. + * + * Category: Control flow + * Type: Jump targets + * Operands: uint32_t icIndex, uint8_t depthHint + * Stack: => + */ \ + MACRO(LoopHead, loop_head, NULL, 6, 0, 0, JOF_LOOPHEAD) \ + /* + * Jump to a 32-bit offset from the current bytecode. + * + * See "Jump instructions" above for details. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t offset + * Stack: => + */ \ + MACRO(Goto, goto_, NULL, 5, 0, 0, JOF_JUMP) \ + /* + * If ToBoolean(`cond`) is false, jumps to a 32-bit offset from the current + * instruction. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: cond => + */ \ + MACRO(JumpIfFalse, jump_if_false, NULL, 5, 1, 0, JOF_JUMP|JOF_IC) \ + /* + * If ToBoolean(`cond`) is true, jump to a 32-bit offset from the current + * instruction. + * + * `offset` may be positive or negative. This is the instruction used at the + * end of a do-while loop to jump back to the top. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t offset + * Stack: cond => + */ \ + MACRO(JumpIfTrue, jump_if_true, NULL, 5, 1, 0, JOF_JUMP|JOF_IC) \ + /* + * Short-circuit for logical AND. + * + * If ToBoolean(`cond`) is false, jump to a 32-bit offset from the current + * instruction. The value remains on the stack. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: cond => cond + */ \ + MACRO(And, and_, NULL, 5, 1, 1, JOF_JUMP|JOF_IC) \ + /* + * Short-circuit for logical OR. + * + * If ToBoolean(`cond`) is true, jump to a 32-bit offset from the current + * instruction. The value remains on the stack. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: cond => cond + */ \ + MACRO(Or, or_, NULL, 5, 1, 1, JOF_JUMP|JOF_IC) \ + /* + * Short-circuiting for nullish coalescing. + * + * If `val` is not null or undefined, jump to a 32-bit offset from the + * current instruction. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: val => val + */ \ + MACRO(Coalesce, coalesce, NULL, 5, 1, 1, JOF_JUMP) \ + /* + * Like `JSOp::JumpIfTrue`, but if the branch is taken, pop and discard an + * additional stack value. + * + * This is used to implement `switch` statements when the + * `JSOp::TableSwitch` optimization is not possible. The switch statement + * + * switch (expr) { + * case A: stmt1; + * case B: stmt2; + * } + * + * compiles to this bytecode: + * + * # dispatch code - evaluate expr, check it against each `case`, + * # jump to the right place in the body or to the end. + * <expr> + * Dup; <A>; StrictEq; Case L1; JumpTarget + * Dup; <B>; StrictEq; Case L2; JumpTarget + * Default LE + * + * # body code + * L1: JumpTarget; <stmt1> + * L2: JumpTarget; <stmt2> + * LE: JumpTarget + * + * This opcode is weird: it's the only one whose ndefs varies depending on + * which way a conditional branch goes. We could implement switch + * statements using `JSOp::JumpIfTrue` and `JSOp::Pop`, but that would also + * be awkward--putting the `JSOp::Pop` inside the `switch` body would + * complicate fallthrough. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: val, cond => val (if !cond) + */ \ + MACRO(Case, case_, NULL, 5, 2, 1, JOF_JUMP) \ + /* + * Like `JSOp::Goto`, but pop and discard an additional stack value. + * + * This appears after all cases for a non-optimized `switch` statement. If + * there's a `default:` label, it jumps to that point in the body; + * otherwise it jumps to the next statement. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t forwardOffset + * Stack: lval => + */ \ + MACRO(Default, default_, NULL, 5, 1, 0, JOF_JUMP) \ + /* + * Optimized switch-statement dispatch, used when all `case` labels are + * small integer constants. + * + * If `low <= i <= high`, jump to the instruction at the offset given by + * `script->resumeOffsets()[firstResumeIndex + i - low]`, in bytes from the + * start of the current script's bytecode. Otherwise, jump to the + * instruction at `defaultOffset` from the current instruction. All of + * these offsets must be in range for the current script and must point to + * `JSOp::JumpTarget` instructions. + * + * The following inequalities must hold: `low <= high` and + * `firstResumeIndex + high - low < resumeOffsets().size()`. + * + * Category: Control flow + * Type: Jumps + * Operands: int32_t defaultOffset, int32_t low, int32_t high, + * uint24_t firstResumeIndex + * Stack: i => + */ \ + MACRO(TableSwitch, table_switch, NULL, 16, 1, 0, JOF_TABLESWITCH) \ + /* + * Return `rval`. + * + * This must not be used in derived class constructors. Instead use + * `JSOp::SetRval`, `JSOp::CheckReturn`, and `JSOp::RetRval`. + * + * Category: Control flow + * Type: Return + * Operands: + * Stack: rval => + */ \ + MACRO(Return, return_, NULL, 1, 1, 0, JOF_BYTE) \ + /* + * Push the current stack frame's `returnValue`. If no `JSOp::SetRval` + * instruction has been executed in this stack frame, this is `undefined`. + * + * Every stack frame has a `returnValue` slot, used by top-level scripts, + * generators, async functions, and derived class constructors. Plain + * functions usually use `JSOp::Return` instead. + * + * Category: Control flow + * Type: Return + * Operands: + * Stack: => rval + */ \ + MACRO(GetRval, get_rval, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Store `rval` in the current stack frame's `returnValue` slot. + * + * This instruction must not be used in a toplevel script compiled with the + * `noScriptRval` option. + * + * Category: Control flow + * Type: Return + * Operands: + * Stack: rval => + */ \ + MACRO(SetRval, set_rval, NULL, 1, 1, 0, JOF_BYTE) \ + /* + * Stop execution and return the current stack frame's `returnValue`. If no + * `JSOp::SetRval` instruction has been executed in this stack frame, this + * is `undefined`. + * + * Also emitted at end of every script so consumers don't need to worry + * about running off the end. + * + * If the current script is a derived class constructor, `returnValue` must + * be an object. The script can use `JSOp::CheckReturn` to ensure this. + * + * Category: Control flow + * Type: Return + * Operands: + * Stack: => + */ \ + MACRO(RetRval, ret_rval, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Check the return value in a derived class constructor. + * + * - If the current stack frame's `returnValue` is an object, push + * `returnValue` onto the stack. + * + * - Otherwise, if the `returnValue` is undefined and `thisval` is an + * object, push `thisval` onto the stack. + * + * - Otherwise, throw a TypeError. + * + * This is exactly what has to happen when a derived class constructor + * returns. `thisval` should be the current value of `this`, or + * `MagicValue(JS_UNINITIALIZED_LEXICAL)` if `this` is uninitialized. + * + * Implements: [The [[Construct]] internal method of JS functions][1], + * steps 13 and 15. + * + * [1]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget + * + * Category: Control flow + * Type: Return + * Operands: + * Stack: thisval => rval + */ \ + MACRO(CheckReturn, check_return, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Throw `exc`. (ノಠ益ಠ)ノ彡┴──┴ + * + * This sets the pending exception to `exc` and jumps to error-handling + * code. If we're in a `try` block, error handling adjusts the stack and + * environment chain and resumes execution at the top of the `catch` or + * `finally` block. Otherwise it starts unwinding the stack. + * + * Implements: [*ThrowStatement* Evaluation][1], step 3. + * + * This is also used in for-of loops. If the body of the loop throws an + * exception, we catch it, close the iterator, then use `JSOp::Throw` to + * rethrow. + * + * [1]: https://tc39.es/ecma262/#sec-throw-statement-runtime-semantics-evaluation + * + * Category: Control flow + * Type: Exceptions + * Operands: + * Stack: exc => + */ \ + MACRO(Throw, throw_, NULL, 1, 1, 0, JOF_BYTE) \ + /* + * Create and throw an Error object. + * + * Sometimes we know at emit time that an operation always throws. For + * example, `delete super.prop;` is allowed in methods, but always throws a + * ReferenceError. + * + * `msgNumber` determines the `.message` and [[Prototype]] of the new Error + * object. It must be an error number in js/public/friend/ErrorNumbers.msg. + * The number of arguments in the error message must be 0. + * + * Category: Control flow + * Type: Exceptions + * Operands: ThrowMsgKind msgNumber + * Stack: => + */ \ + MACRO(ThrowMsg, throw_msg, NULL, 2, 0, 0, JOF_UINT8) \ + /* + * Throws a runtime TypeError for invalid assignment to a `const` binding. + * + * Category: Control flow + * Type: Exceptions + * Operands: uint32_t nameIndex + * Stack: + */ \ + MACRO(ThrowSetConst, throw_set_const, NULL, 5, 0, 0, JOF_ATOM|JOF_NAME) \ + /* + * No-op instruction that marks the top of the bytecode for a + * *TryStatement*. + * + * Location information for catch/finally blocks is stored in a side table, + * `script->trynotes()`. + * + * Category: Control flow + * Type: Exceptions + * Operands: + * Stack: => + */ \ + MACRO(Try, try_, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * No-op instruction used by the exception unwinder to determine the + * correct environment to unwind to when performing IteratorClose due to + * destructuring. + * + * This instruction must appear immediately before each + * `JSTRY_DESTRUCTURING` span in a script's try notes. + * + * Category: Control flow + * Type: Exceptions + * Operands: + * Stack: => + */ \ + MACRO(TryDestructuring, try_destructuring, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Push and clear the pending exception. ┬──┬◡ノ(° -°ノ) + * + * This must be used only in the fixed sequence of instructions following a + * `JSTRY_CATCH` span (see "Bytecode Invariants" above), as that's the only + * way instructions would run with an exception pending. + * + * Used to implement catch-blocks, including the implicit ones generated as + * part of for-of iteration. + * + * Category: Control flow + * Type: Exceptions + * Operands: + * Stack: => exception + */ \ + MACRO(Exception, exception, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * No-op instruction that marks the start of a `finally` block. + * + * Category: Control flow + * Type: Exceptions + * Operands: + * Stack: => + */ \ + MACRO(Finally, finally, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Push `MagicValue(JS_UNINITIALIZED_LEXICAL)`, a magic value used to mark + * a binding as uninitialized. + * + * This magic value must be used only by `JSOp::InitLexical`. + * + * Category: Variables and scopes + * Type: Initialization + * Operands: + * Stack: => uninitialized + */ \ + MACRO(Uninitialized, uninitialized, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Initialize an optimized local lexical binding; or mark it as + * uninitialized. + * + * This stores the value `v` in the fixed slot `localno` in the current + * stack frame. If `v` is the magic value produced by `JSOp::Uninitialized`, + * this marks the binding as uninitialized. Otherwise this initializes the + * binding with value `v`. + * + * Implements: [CreateMutableBinding][1] step 3, substep "record that it is + * uninitialized", and [InitializeBinding][2], for optimized locals. (Note: + * this is how `const` bindings are initialized.) + * + * [1]: https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d + * [2]: https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v + * + * Category: Variables and scopes + * Type: Initialization + * Operands: uint24_t localno + * Stack: v => v + */ \ + MACRO(InitLexical, init_lexical, NULL, 4, 1, 1, JOF_LOCAL|JOF_NAME) \ + /* + * Initialize a global lexical binding. + * + * The binding must already have been created by + * `GlobalOrEvalDeclInstantiation` and must be uninitialized. + * + * Like `JSOp::InitLexical` but for global lexicals. Unlike `InitLexical` + * this can't be used to mark a binding as uninitialized. + * + * Category: Variables and scopes + * Type: Initialization + * Operands: uint32_t nameIndex + * Stack: val => val + */ \ + MACRO(InitGLexical, init_g_lexical, NULL, 5, 1, 1, JOF_ATOM|JOF_NAME|JOF_PROPINIT|JOF_GNAME|JOF_IC) \ + /* + * Initialize an aliased lexical binding; or mark it as uninitialized. + * + * Like `JSOp::InitLexical` but for aliased bindings. + * + * Note: There is no even-less-optimized `InitName` instruction because JS + * doesn't need it. We always know statically which binding we're + * initializing. + * + * `hops` is usually 0, but in `function f(a=eval("var b;")) { }`, the + * argument `a` is initialized from inside a nested scope, so `hops == 1`. + * + * Category: Variables and scopes + * Type: Initialization + * Operands: uint8_t hops, uint24_t slot + * Stack: v => v + */ \ + MACRO(InitAliasedLexical, init_aliased_lexical, NULL, 5, 1, 1, JOF_ENVCOORD|JOF_NAME|JOF_PROPINIT) \ + /* + * Throw a ReferenceError if the value on top of the stack is uninitialized. + * + * Typically used after `JSOp::GetLocal` with the same `localno`. + * + * Implements: [GetBindingValue][1] step 3 and [SetMutableBinding][2] step + * 4 for declarative Environment Records. + * + * [1]: https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s + * [2]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s + * + * Category: Variables and scopes + * Type: Initialization + * Operands: uint24_t localno + * Stack: v => v + */ \ + MACRO(CheckLexical, check_lexical, NULL, 4, 1, 1, JOF_LOCAL|JOF_NAME) \ + /* + * Like `JSOp::CheckLexical` but for aliased bindings. + * + * Typically used after `JSOp::GetAliasedVar` with the same hops/slot. + * + * Note: There are no `CheckName` or `CheckGName` instructions because + * they're unnecessary. `JSOp::{Get,Set}{Name,GName}` all check for + * uninitialized lexicals and throw if needed. + * + * Category: Variables and scopes + * Type: Initialization + * Operands: uint8_t hops, uint24_t slot + * Stack: v => v + */ \ + MACRO(CheckAliasedLexical, check_aliased_lexical, NULL, 5, 1, 1, JOF_ENVCOORD|JOF_NAME) \ + /* + * Throw a ReferenceError if the value on top of the stack is + * `MagicValue(JS_UNINITIALIZED_LEXICAL)`. Used in derived class + * constructors to check `this` (which needs to be initialized before use, + * by calling `super()`). + * + * Implements: [GetThisBinding][1] step 3. + * + * [1]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + * + * Category: Variables and scopes + * Type: Initialization + * Operands: + * Stack: this => this + */ \ + MACRO(CheckThis, check_this, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Look up a name on the global lexical environment's chain and push the + * environment which contains a binding for that name. If no such binding + * exists, push the global lexical environment. + * + * Category: Variables and scopes + * Type: Looking up bindings + * Operands: uint32_t nameIndex + * Stack: => global + */ \ + MACRO(BindGName, bind_g_name, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_GNAME|JOF_IC) \ + /* + * Look up a name on the environment chain and push the environment which + * contains a binding for that name. If no such binding exists, push the + * global lexical environment. + * + * Category: Variables and scopes + * Type: Looking up bindings + * Operands: uint32_t nameIndex + * Stack: => env + */ \ + MACRO(BindName, bind_name, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_IC|JOF_USES_ENV) \ + /* + * Find a binding on the environment chain and push its value. + * + * If the binding is an uninitialized lexical, throw a ReferenceError. If + * no such binding exists, throw a ReferenceError unless the next + * instruction is `JSOp::Typeof`, in which case push `undefined`. + * + * Implements: [ResolveBinding][1] followed by [GetValue][2] + * (adjusted hackily for `typeof`). + * + * This is the fallback `Get` instruction that handles all unoptimized + * cases. Optimized instructions follow. + * + * [1]: https://tc39.es/ecma262/#sec-resolvebinding + * [2]: https://tc39.es/ecma262/#sec-getvalue + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint32_t nameIndex + * Stack: => val + */ \ + MACRO(GetName, get_name, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_IC|JOF_USES_ENV) \ + /* + * Find a global binding and push its value. + * + * This searches the global lexical environment and, failing that, the + * global object. (Unlike most declarative environments, the global lexical + * environment can gain more bindings after compilation, possibly shadowing + * global object properties.) + * + * This is an optimized version of `JSOp::GetName` that skips all local + * scopes, for use when the name doesn't refer to any local binding. + * `NonSyntacticVariablesObject`s break this optimization, so if the + * current script has a non-syntactic global scope, use `JSOp::GetName` + * instead. + * + * Like `JSOp::GetName`, this throws a ReferenceError if no such binding is + * found (unless the next instruction is `JSOp::Typeof`) or if the binding + * is an uninitialized lexical. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint32_t nameIndex + * Stack: => val + */ \ + MACRO(GetGName, get_g_name, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_GNAME|JOF_IC) \ + /* + * Push the value of an argument that is stored in the stack frame + * or in an `ArgumentsObject`. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint16_t argno + * Stack: => arguments[argno] + */ \ + MACRO(GetArg, get_arg, NULL, 3, 0, 1, JOF_QARG|JOF_NAME) \ + /* + * Push the value of an argument that is stored in the stack frame. Like + * `JSOp::GetArg`, but ignores the frame's `ArgumentsObject` and doesn't + * assert the argument is unaliased. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint16_t argno + * Stack: => arguments[argno] + */ \ + MACRO(GetFrameArg, get_frame_arg, NULL, 3, 0, 1, JOF_QARG|JOF_NAME) \ + /* + * Push the value of an optimized local variable. + * + * If the variable is an uninitialized lexical, push + * `MagicValue(JS_UNINIITALIZED_LEXICAL)`. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint24_t localno + * Stack: => val + */ \ + MACRO(GetLocal, get_local, NULL, 4, 0, 1, JOF_LOCAL|JOF_NAME) \ + /* + * Push the number of actual arguments as Int32Value. + * + * This is emitted for the ArgumentsLength() intrinsic in self-hosted code. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: + * Stack: => arguments.length + */ \ + MACRO(ArgumentsLength, arguments_length, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Push the value of an argument that is stored in the stack frame. The + * value on top of the stack must be an Int32Value storing the index. The + * index must be less than the number of actual arguments. + * + * This is emitted for the GetArgument(i) intrinsic in self-hosted code. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: + * Stack: index => arguments[index] + */ \ + MACRO(GetActualArg, get_actual_arg, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Push the value of an aliased binding. + * + * Local bindings that aren't closed over or dynamically accessed are + * stored in stack slots. Global and `with` bindings are object properties. + * All other bindings are called "aliased" and stored in + * `EnvironmentObject`s. + * + * Where possible, `Aliased` instructions are used to access aliased + * bindings. (There's no difference in meaning between `AliasedVar` and + * `AliasedLexical`.) Each of these instructions has operands `hops` and + * `slot` that encode an [`EnvironmentCoordinate`][1], directions to the + * binding from the current environment object. + * + * `Aliased` instructions can't be used when there's a dynamic scope (due + * to non-strict `eval` or `with`) that might shadow the aliased binding. + * + * [1]: https://searchfox.org/mozilla-central/search?q=symbol:T_js%3A%3AEnvironmentCoordinate + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint8_t hops, uint24_t slot + * Stack: => aliasedVar + */ \ + MACRO(GetAliasedVar, get_aliased_var, NULL, 5, 0, 1, JOF_ENVCOORD|JOF_NAME|JOF_USES_ENV) \ + /* + * Push the value of an aliased binding, which may have to bypass a DebugEnvironmentProxy + * on the environment chain. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint8_t hops, uint24_t slot + * Stack: => aliasedVar + */ \ + MACRO(GetAliasedDebugVar, get_aliased_debug_var, NULL, 5, 0, 1, JOF_DEBUGCOORD|JOF_NAME) \ + /* + * Get the value of a module import by name and pushes it onto the stack. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint32_t nameIndex + * Stack: => val + */ \ + MACRO(GetImport, get_import, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME) \ + /* + * Get the value of a binding from the environment `env`. If the name is + * not bound in `env`, throw a ReferenceError. + * + * `env` must be an environment currently on the environment chain, pushed + * by `JSOp::BindName` or `JSOp::BindVar`. + * + * Note: `JSOp::BindName` and `JSOp::GetBoundName` are the two halves of the + * `JSOp::GetName` operation: finding and reading a variable. This + * decomposed version is needed to implement the compound assignment and + * increment/decrement operators, which get and then set a variable. The + * spec says the variable lookup is done only once. If we did the lookup + * twice, there would be observable bugs, thanks to dynamic scoping. We + * could set the wrong variable or call proxy traps incorrectly. + * + * Implements: [GetValue][1] steps 4 and 6. + * + * [1]: https://tc39.es/ecma262/#sec-getvalue + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint32_t nameIndex + * Stack: env => v + */ \ + MACRO(GetBoundName, get_bound_name, NULL, 5, 1, 1, JOF_ATOM|JOF_NAME|JOF_IC) \ + /* + * Push the value of an intrinsic onto the stack. + * + * Non-standard. Intrinsics are slots in the intrinsics holder object (see + * `GlobalObject::getIntrinsicsHolder`), which is used in lieu of global + * bindings in self-hosting code. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint32_t nameIndex + * Stack: => intrinsic[name] + */ \ + MACRO(GetIntrinsic, get_intrinsic, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_IC) \ + /* + * Pushes the currently executing function onto the stack. + * + * The current script must be a function script. + * + * Used to implement `super`. This is also used sometimes as a minor + * optimization when a named function expression refers to itself by name: + * + * f = function fac(n) { ... fac(n - 1) ... }; + * + * This lets us optimize away a lexical environment that contains only the + * binding for `fac`, unless it's otherwise observable (via `with`, `eval`, + * or a nested closure). + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: + * Stack: => callee + */ \ + MACRO(Callee, callee, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Load the callee stored in a CallObject on the environment chain. The + * `numHops` operand is the number of environment objects to skip on the + * environment chain. The environment chain element indicated by `numHops` + * must be a CallObject. + * + * Category: Variables and scopes + * Type: Getting binding values + * Operands: uint8_t numHops + * Stack: => callee + */ \ + MACRO(EnvCallee, env_callee, NULL, 2, 0, 1, JOF_UINT8) \ + /* + * Assign `val` to the binding in `env` with the name given by `nameIndex`. + * Throw a ReferenceError if the binding is an uninitialized lexical. + * This can call setters and/or proxy traps. + * + * `env` must be an environment currently on the environment chain, + * pushed by `JSOp::BindName` or `JSOp::BindVar`. + * + * This is the fallback `Set` instruction that handles all unoptimized + * cases. Optimized instructions follow. + * + * Implements: [PutValue][1] steps 5 and 7 for unoptimized bindings. + * + * Note: `JSOp::BindName` and `JSOp::SetName` are the two halves of simple + * assignment: finding and setting a variable. They are two separate + * instructions because, per spec, the "finding" part happens before + * evaluating the right-hand side of the assignment, and the "setting" part + * after. Optimized cases don't need a `Bind` instruction because the + * "finding" is done statically. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint32_t nameIndex + * Stack: env, val => val + */ \ + MACRO(SetName, set_name, NULL, 5, 2, 1, JOF_ATOM|JOF_NAME|JOF_PROPSET|JOF_CHECKSLOPPY|JOF_IC|JOF_USES_ENV) \ + /* + * Like `JSOp::SetName`, but throw a TypeError if there is no binding for + * the specified name in `env`, or if the binding is immutable (a `const` + * or read-only property). + * + * Implements: [PutValue][1] steps 5 and 7 for strict mode code. + * + * [1]: https://tc39.es/ecma262/#sec-putvalue + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint32_t nameIndex + * Stack: env, val => val + */ \ + MACRO(StrictSetName, strict_set_name, NULL, 5, 2, 1, JOF_ATOM|JOF_NAME|JOF_PROPSET|JOF_CHECKSTRICT|JOF_IC|JOF_USES_ENV) \ + /* + * Like `JSOp::SetName`, but for assigning to globals. `env` must be an + * environment pushed by `JSOp::BindGName`. + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint32_t nameIndex + * Stack: env, val => val + */ \ + MACRO(SetGName, set_g_name, NULL, 5, 2, 1, JOF_ATOM|JOF_NAME|JOF_PROPSET|JOF_GNAME|JOF_CHECKSLOPPY|JOF_IC) \ + /* + * Like `JSOp::StrictSetGName`, but for assigning to globals. `env` must be + * an environment pushed by `JSOp::BindGName`. + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint32_t nameIndex + * Stack: env, val => val + */ \ + MACRO(StrictSetGName, strict_set_g_name, NULL, 5, 2, 1, JOF_ATOM|JOF_NAME|JOF_PROPSET|JOF_GNAME|JOF_CHECKSTRICT|JOF_IC) \ + /* + * Assign `val` to an argument binding that's stored in the stack frame or + * in an `ArgumentsObject`. + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint16_t argno + * Stack: val => val + */ \ + MACRO(SetArg, set_arg, NULL, 3, 1, 1, JOF_QARG|JOF_NAME) \ + /* + * Assign to an optimized local binding. + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint24_t localno + * Stack: v => v + */ \ + MACRO(SetLocal, set_local, NULL, 4, 1, 1, JOF_LOCAL|JOF_NAME) \ + /* + * Assign to an aliased binding. + * + * Implements: [SetMutableBinding for declarative Environment Records][1], + * in certain cases where it's known that the binding exists, is mutable, + * and has been initialized. + * + * [1]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint8_t hops, uint24_t slot + * Stack: val => val + */ \ + MACRO(SetAliasedVar, set_aliased_var, NULL, 5, 1, 1, JOF_ENVCOORD|JOF_NAME|JOF_PROPSET|JOF_USES_ENV) \ + /* + * Assign to an intrinsic. + * + * Nonstandard. Intrinsics are used in lieu of global bindings in self- + * hosted code. The value is actually stored in the intrinsics holder + * object, `GlobalObject::getIntrinsicsHolder`. (Self-hosted code doesn't + * have many global `var`s, but it has many `function`s.) + * + * Category: Variables and scopes + * Type: Setting binding values + * Operands: uint32_t nameIndex + * Stack: val => val + */ \ + MACRO(SetIntrinsic, set_intrinsic, NULL, 5, 1, 1, JOF_ATOM|JOF_NAME) \ + /* + * Push a lexical environment onto the environment chain. + * + * The `LexicalScope` indicated by `lexicalScopeIndex` determines the shape + * of the new `BlockLexicalEnvironmentObject`. All bindings in the new + * environment are marked as uninitialized. + * + * Implements: [Evaluation of *Block*][1], steps 1-4. + * + * #### Fine print for environment chain instructions + * + * The following rules for `JSOp::{Push,Pop}LexicalEnv` also apply to + * `JSOp::PushClassBodyEnv`, `JSOp::PushVarEnv`, and + * `JSOp::{Enter,Leave}With`. + * + * Each `JSOp::PopLexicalEnv` instruction matches a particular + * `JSOp::PushLexicalEnv` instruction in the same script and must have the + * same scope and stack depth as the instruction immediately after that + * `PushLexicalEnv`. + * + * `JSOp::PushLexicalEnv` enters a scope that extends to some set of + * instructions in the script. Code must not jump into or out of this + * region: control can enter only by executing `PushLexicalEnv` and can + * exit only by executing a `PopLexicalEnv` or by exception unwinding. (A + * `JSOp::PopLexicalEnv` is always emitted at the end of the block, and + * extra copies are emitted on "exit slides", where a `break`, `continue`, + * or `return` statement exits the scope.) + * + * The script's `JSScript::scopeNotes()` must identify exactly which + * instructions begin executing in this scope. Typically this means a + * single entry marking the contiguous chunk of bytecode from the + * instruction after `JSOp::PushLexicalEnv` to `JSOp::PopLexicalEnv` + * (inclusive); but if that range contains any instructions on exit slides, + * after a `JSOp::PopLexicalEnv`, then those must be correctly noted as + * *outside* the scope. + * + * [1]: https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t lexicalScopeIndex + * Stack: => + */ \ + MACRO(PushLexicalEnv, push_lexical_env, NULL, 5, 0, 0, JOF_SCOPE|JOF_USES_ENV) \ + /* + * Pop a lexical or class-body environment from the environment chain. + * + * See `JSOp::PushLexicalEnv` for the fine print. + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: + * Stack: => + */ \ + MACRO(PopLexicalEnv, pop_lexical_env, NULL, 1, 0, 0, JOF_BYTE|JOF_USES_ENV) \ + /* + * No-op instruction that indicates leaving an optimized lexical scope. + * + * If all bindings in a lexical scope are optimized into stack slots, then + * the runtime environment objects for that scope are optimized away. No + * `JSOp::{Push,Pop}LexicalEnv` instructions are emitted. However, the + * debugger still needs to be notified when control exits a scope; that's + * what this instruction does. + * + * The last instruction in a lexical or class-body scope, as indicated by + * scope notes, must be either this instruction (if the scope is optimized) + * or `JSOp::PopLexicalEnv` (if not). + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: + * Stack: => + */ \ + MACRO(DebugLeaveLexicalEnv, debug_leave_lexical_env, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Replace the current block on the environment chain with a fresh block + * with uninitialized bindings. This implements the behavior of inducing a + * fresh lexical environment for every iteration of a for-in/of loop whose + * loop-head declares lexical variables that may be captured. + * + * The current environment must be a BlockLexicalEnvironmentObject. + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t lexicalScopeIndex + * Stack: => + */ \ + MACRO(RecreateLexicalEnv, recreate_lexical_env, NULL, 5, 0, 0, JOF_SCOPE) \ + /* + * Like `JSOp::RecreateLexicalEnv`, but the values of all the bindings are + * copied from the old block to the new one. This is used for C-style + * `for(let ...; ...; ...)` loops. + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t lexicalScopeIndex + * Stack: => + */ \ + MACRO(FreshenLexicalEnv, freshen_lexical_env, NULL, 5, 0, 0, JOF_SCOPE) \ + /* + * Push a ClassBody environment onto the environment chain. + * + * Like `JSOp::PushLexicalEnv`, but pushes a `ClassBodyEnvironmentObject` + * rather than a `BlockLexicalEnvironmentObject`. `JSOp::PopLexicalEnv` is + * used to pop class-body environments as well as lexical environments. + * + * See `JSOp::PushLexicalEnv` for the fine print. + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t lexicalScopeIndex + * Stack: => + */ \ + MACRO(PushClassBodyEnv, push_class_body_env, NULL, 5, 0, 0, JOF_SCOPE) \ + /* + * Push a var environment onto the environment chain. + * + * Like `JSOp::PushLexicalEnv`, but pushes a `VarEnvironmentObject` rather + * than a `BlockLexicalEnvironmentObject`. The difference is that + * non-strict direct `eval` can add bindings to a var environment; see + * `VarScope` in Scope.h. + * + * See `JSOp::PushLexicalEnv` for the fine print. + * + * There is no corresponding `JSOp::PopVarEnv` operation, because a + * `VarEnvironmentObject` is never popped from the environment chain. + * + * Implements: Places in the spec where the VariableEnvironment is set: + * + * - The bit in [PerformEval][1] where, in strict direct eval, the new + * eval scope is taken as *varEnv* and becomes "*runningContext*'s + * VariableEnvironment". + * + * - The weird scoping rules for functions with default parameter + * expressions, as specified in [FunctionDeclarationInstantiation][2] + * step 28 ("NOTE: A separate Environment Record is needed..."). + * + * Note: The spec also pushes a new VariableEnvironment on entry to every + * function, but the VM takes care of that as part of pushing the stack + * frame, before the function script starts to run, so `JSOp::PushVarEnv` is + * not needed. + * + * [1]: https://tc39.es/ecma262/#sec-performeval + * [2]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t scopeIndex + * Stack: => + */ \ + MACRO(PushVarEnv, push_var_env, NULL, 5, 0, 0, JOF_SCOPE|JOF_USES_ENV) \ + /* + * Push a `WithEnvironmentObject` wrapping ToObject(`val`) to the + * environment chain. + * + * Implements: [Evaluation of `with` statements][1], steps 2-6. + * + * Operations that may need to consult a WithEnvironment can't be correctly + * implemented using optimized instructions like `JSOp::GetLocal`. A script + * must use the deoptimized `JSOp::GetName`, `BindName`, `SetName`, and + * `DelName` instead. Since those instructions don't work correctly with + * optimized locals and arguments, all bindings in scopes enclosing a + * `with` statement are marked as "aliased" and deoptimized too. + * + * See `JSOp::PushLexicalEnv` for the fine print. + * + * [1]: https://tc39.es/ecma262/#sec-with-statement-runtime-semantics-evaluation + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: uint32_t staticWithIndex + * Stack: val => + */ \ + MACRO(EnterWith, enter_with, NULL, 5, 1, 0, JOF_SCOPE) \ + /* + * Pop a `WithEnvironmentObject` from the environment chain. + * + * See `JSOp::PushLexicalEnv` for the fine print. + * + * Implements: [Evaluation of `with` statements][1], step 8. + * + * [1]: https://tc39.es/ecma262/#sec-with-statement-runtime-semantics-evaluation + * + * Category: Variables and scopes + * Type: Entering and leaving environments + * Operands: + * Stack: => + */ \ + MACRO(LeaveWith, leave_with, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Push the current VariableEnvironment (the environment on the environment + * chain designated to receive new variables). + * + * Implements: [Annex B.3.3.1, changes to FunctionDeclarationInstantiation + * for block-level functions][1], step 1.a.ii.3.a, and similar steps in + * other Annex B.3.3 algorithms, when setting the function's second binding + * can't be optimized. + * + * [1]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation + * + * Category: Variables and scopes + * Type: Creating and deleting bindings + * Operands: + * Stack: => env + */ \ + MACRO(BindVar, bind_var, NULL, 1, 0, 1, JOF_BYTE|JOF_USES_ENV) \ + /* + * Check for conflicting bindings and then initialize them in global or + * sloppy eval scripts. This is required for global scripts with any + * top-level bindings, or any sloppy-eval scripts with any non-lexical + * top-level bindings. + * + * Implements: [GlobalDeclarationInstantiation][1] and + * [EvalDeclarationInstantiation][2] (except step 12). + * + * The `lastFun` argument is a GCThingIndex of the last hoisted top-level + * function that is part of top-level script initialization. The gcthings + * from index `0` thru `lastFun` contain only scopes and hoisted functions. + * + * [1]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation + * [2]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation + * + * Category: Variables and scopes + * Type: Creating and deleting bindings + * Operands: uint32_t lastFun + * Stack: => + */ \ + MACRO(GlobalOrEvalDeclInstantiation, global_or_eval_decl_instantiation, NULL, 5, 0, 0, JOF_GCTHING|JOF_USES_ENV) \ + /* + * Look up a variable on the environment chain and delete it. Push `true` + * on success (if a binding was deleted, or if no such binding existed in + * the first place), `false` otherwise (most kinds of bindings can't be + * deleted). + * + * Implements: [`delete` *Identifier*][1], which [is a SyntaxError][2] in + * strict mode code. + * + * [1]: https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation + * [2]: https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors + * + * Category: Variables and scopes + * Type: Creating and deleting bindings + * Operands: uint32_t nameIndex + * Stack: => succeeded + */ \ + MACRO(DelName, del_name, NULL, 5, 0, 1, JOF_ATOM|JOF_NAME|JOF_CHECKSLOPPY|JOF_USES_ENV) \ + /* + * Create and push the `arguments` object for the current function activation. + * + * When it exists, `arguments` is stored in an ordinary local variable. + * `JSOp::Arguments` is used in function preludes, to populate that variable + * before the function body runs, *not* each time `arguments` appears in a + * function. + * + * If a function clearly doesn't use `arguments`, we optimize it away when + * emitting bytecode. The function's script won't use `JSOp::Arguments` at + * all. + * + * The current script must be a function script. This instruction must + * execute at most once per function activation. + * + * Category: Variables and scopes + * Type: Function environment setup + * Operands: + * Stack: => arguments + */ \ + MACRO(Arguments, arguments, NULL, 1, 0, 1, JOF_BYTE|JOF_USES_ENV) \ + /* + * Create and push the rest parameter array for current function call. + * + * This must appear only in a script for a function that has a rest + * parameter. + * + * Category: Variables and scopes + * Type: Function environment setup + * Operands: + * Stack: => rest + */ \ + MACRO(Rest, rest, NULL, 1, 0, 1, JOF_BYTE|JOF_IC) \ + /* + * Determines the `this` value for current function frame and pushes it + * onto the stack. + * + * In functions, `this` is stored in a local variable. This instruction is + * used in the function prologue to get the value to initialize that + * variable. (This doesn't apply to arrow functions, becauses they don't + * have a `this` binding; also, `this` is optimized away if it's unused.) + * + * Functions that have a `this` binding have a local variable named + * `".this"`, which is initialized using this instruction in the function + * prologue. + * + * In non-strict functions, `this` is always an object. Undefined/null + * `this` is converted into the global `this` value. Other primitive values + * are boxed. See `js::BoxNonStrictThis`. + * + * Category: Variables and scopes + * Type: Function environment setup + * Operands: + * Stack: => this + */ \ + MACRO(FunctionThis, function_this, NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Pop the top value from the stack and discard it. + * + * Category: Stack operations + * Operands: + * Stack: v => + */ \ + MACRO(Pop, pop, NULL, 1, 1, 0, JOF_BYTE) \ + /* + * Pop the top `n` values from the stack. `n` must be <= the current stack + * depth. + * + * Category: Stack operations + * Operands: uint16_t n + * Stack: v[n-1], ..., v[1], v[0] => + */ \ + MACRO(PopN, pop_n, NULL, 3, -1, 0, JOF_UINT16) \ + /* + * Push a copy of the top value on the stack. + * + * Category: Stack operations + * Operands: + * Stack: v => v, v + */ \ + MACRO(Dup, dup, NULL, 1, 1, 2, JOF_BYTE) \ + /* + * Duplicate the top two values on the stack. + * + * Category: Stack operations + * Operands: + * Stack: v1, v2 => v1, v2, v1, v2 + */ \ + MACRO(Dup2, dup2, NULL, 1, 2, 4, JOF_BYTE) \ + /* + * Push a copy of the nth value from the top of the stack. + * + * `n` must be less than the current stack depth. + * + * Category: Stack operations + * Operands: uint24_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => + * v[n], v[n-1], ..., v[1], v[0], v[n] + */ \ + MACRO(DupAt, dup_at, NULL, 4, 0, 1, JOF_UINT24) \ + /* + * Swap the top two values on the stack. + * + * Category: Stack operations + * Operands: + * Stack: v1, v2 => v2, v1 + */ \ + MACRO(Swap, swap, NULL, 1, 2, 2, JOF_BYTE) \ + /* + * Pick the nth element from the stack and move it to the top of the stack. + * + * Category: Stack operations + * Operands: uint8_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => v[n-1], ..., v[1], v[0], v[n] + */ \ + MACRO(Pick, pick, NULL, 2, 0, 0, JOF_UINT8) \ + /* + * Move the top of the stack value under the `n`th element of the stack. + * `n` must not be 0. + * + * Category: Stack operations + * Operands: uint8_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1] + */ \ + MACRO(Unpick, unpick, NULL, 2, 0, 0, JOF_UINT8) \ + /* + * Do nothing. This is used when we need distinct bytecode locations for + * various mechanisms. + * + * Category: Other + * Operands: + * Stack: => + */ \ + MACRO(Nop, nop, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * No-op instruction emitted immediately after `JSOp::*Eval` so that direct + * eval does not have to do slow pc-to-line mapping. + * + * The `lineno` operand should agree with this script's source notes about + * the line number of the preceding `*Eval` instruction. + * + * Category: Other + * Operands: uint32_t lineno + * Stack: => + */ \ + MACRO(Lineno, lineno, NULL, 5, 0, 0, JOF_UINT32) \ + /* + * No-op instruction to hint that the top stack value is uninteresting. + * + * This affects only debug output and some error messages. + * In array destructuring, we emit bytecode that is roughly equivalent to + * `result.done ? undefined : result.value`. + * `NopDestructuring` is emitted after the `undefined`, so that the + * expression decompiler and disassembler know to casually ignore the + * possibility of `undefined`, and render the result of the conditional + * expression simply as "`result.value`". + * + * Category: Other + * Operands: + * Stack: => + */ \ + MACRO(NopDestructuring, nop_destructuring, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * No-op instruction only emitted in some self-hosted functions. Not + * handled by the JITs or Baseline Interpreter so the script always runs in + * the C++ interpreter. + * + * Category: Other + * Operands: + * Stack: => + */ \ + MACRO(ForceInterpreter, force_interpreter, NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Examine the top stack value, asserting that it's either a self-hosted + * function or a self-hosted intrinsic. This does nothing in a non-debug + * build. + * + * Category: Other + * Operands: + * Stack: checkVal => checkVal + */ \ + MACRO(DebugCheckSelfHosted, debug_check_self_hosted, NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Break in the debugger, if one is attached. Otherwise this is a no-op. + * + * The [`Debugger` API][1] offers a way to hook into this instruction. + * + * Implements: [Evaluation for *DebuggerStatement*][2]. + * + * [1]: https://developer.mozilla.org/en-US/docs/Tools/Debugger-API/Debugger + * [2]: https://tc39.es/ecma262/#sec-debugger-statement-runtime-semantics-evaluation + * + * Category: Other + * Operands: + * Stack: => + */ \ + MACRO(Debugger, debugger, NULL, 1, 0, 0, JOF_BYTE) + +// clang-format on + +/* + * In certain circumstances it may be useful to "pad out" the opcode space to + * a power of two. Use this macro to do so. + */ +#define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \ + IF_RECORD_TUPLE(/* empty */, MACRO(230)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(231)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(232)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(233)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(234)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(235)) \ + IF_RECORD_TUPLE(/* empty */, MACRO(236)) \ + MACRO(237) \ + MACRO(238) \ + MACRO(239) \ + MACRO(240) \ + MACRO(241) \ + MACRO(242) \ + MACRO(243) \ + MACRO(244) \ + MACRO(245) \ + MACRO(246) \ + MACRO(247) \ + MACRO(248) \ + MACRO(249) \ + MACRO(250) \ + MACRO(251) \ + MACRO(252) \ + MACRO(253) \ + MACRO(254) \ + MACRO(255) + +namespace js { + +// Sanity check that opcode values and trailing unused opcodes completely cover +// the [0, 256) range. Avert your eyes! You don't want to know how the +// sausage gets made. + +// clang-format off +#define PLUS_ONE(...) \ + + 1 +constexpr int JSOP_LIMIT = 0 FOR_EACH_OPCODE(PLUS_ONE); +#undef PLUS_ONE + +#define TRAILING_VALUE_AND_VALUE_PLUS_ONE(val) \ + val) && (val + 1 == +static_assert((JSOP_LIMIT == + FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_VALUE_AND_VALUE_PLUS_ONE) + 256), + "trailing unused opcode values monotonically increase " + "from JSOP_LIMIT to 255"); +#undef TRAILING_VALUE_AND_VALUE_PLUS_ONE +// clang-format on + +// Define JSOpLength_* constants for all ops. +#define DEFINE_LENGTH_CONSTANT(op, op_snake, image, len, ...) \ + constexpr size_t JSOpLength_##op = len; +FOR_EACH_OPCODE(DEFINE_LENGTH_CONSTANT) +#undef DEFINE_LENGTH_CONSTANT + +} // namespace js + +/* + * JS operation bytecodes. + */ +enum class JSOp : uint8_t { +#define ENUMERATE_OPCODE(op, ...) op, + FOR_EACH_OPCODE(ENUMERATE_OPCODE) +#undef ENUMERATE_OPCODE +}; + +#endif // vm_Opcodes_h |