Documentation
¶
Overview ¶
Package bytecode defines the moejs instruction set and the compiled-function template shared between the compiler and the engine's interpreter.
See doc.go for the ISA reference and opcodes.go for the encoding. The types in this file are the data model: nothing in a Function is mutated after compilation (but for the name index an EvalLevel caches, see EvalLevel.Slot), so one *Function is shared by every realm (and every goroutine) that instantiates the same module. Per-realm state (inline caches, materialized constants) lives in engine side tables keyed by *Function.
Package bytecode: instruction set reference.
Machine model ¶
moejs runs a register machine. A function executes in a window of NumRegs registers R[0..NumRegs-1] carved out of one contiguous per-realm value stack. Arguments arrive in R[0..argc-1]; missing parameters are undefined; when HasRest is set the surplus arguments are collected into an array in R[NumParams] before any other instruction runs, and when HasArguments is set an unmapped arguments object holding every argument is created in the next register (R[NumParams], or R[NumParams+1] after a rest array). `this` is held in the frame, not in a register. Registers above the parameters hold locals (un-captured let/const/var/function bindings) and expression temporaries; the compiler allocates them stack-like so that a call block (callee, this, args) is always the highest live group of registers.
Captured bindings never live in registers: they live in closure environments (Env). env^0 is the innermost environment; env^d is its d-th parent. A function whose CaptureLayout has n > 0 slots creates its own Env of n slots at entry (with the parameter registers listed in the layout copied in); otherwise it runs directly in the environment captured by its closure. Block scopes with captured bindings push and pop nested Envs.
Every instruction is one 32-bit word: op:8 | A:8 | B:8 | C:8, or op:8 | A:8 | Bx:16 (unsigned) / sBx:16 (signed). Ops marked +X below are followed by one ExtraArg word (a raw uint32); GetEnvChkW and GetImportW have two. Jump offsets are relative to the pc of the following word. Constants K[i] are Function.Consts; F[i] are Function.Children.
Exceptions unwind through Function.Handlers without panic/recover: the interpreter takes the first row whose [Start, End) covers the faulting pc, pops closure environments down to StackDepth, stores the thrown value as described by Kind, and resumes at Handler. Rows without a match propagate the error to the caller's frame. Interrupts (Realm.Interrupt) are checked at function entry and at every backward Jmp and are not catchable.
Opcodes ¶
Loads and moves
LoadConst A Bx R[A] = K[Bx] (number, string or bigint constant) LoadInt A sBx R[A] = sBx (small integer) LoadUndef A R[A] = undefined LoadNull A R[A] = null LoadTrue A R[A] = true LoadFalse A R[A] = false LoadHole A R[A] = hole (the TDZ marker; never observable) LoadThis A R[A] = this (undefined at module top level; lexical in arrows) LoadCallee A R[A] = the function object being executed (named function expressions) Move A B R[A] = R[B] UndefRange A B R[A .. A+B-1] = undefined (var bindings at entry)
Closure environments
GetEnv A B C R[A] = env^B[C] GetEnvChk A B C +X R[A] = env^B[C]; ReferenceError "K[X] is not defined" if hole SetEnv A B C env^B[C] = R[A] GetEnvW A B +X R[A] = env^B[X] (slot >= 256) GetEnvChkW A B +X +X R[A] = env^B[X1] with TDZ check; X2 = name constant SetEnvW A B +X env^B[X] = R[A] PushEnv Bx env = new Env(env, Bx slots initialized to undefined) PopEnv env = env.parent CopyEnv env = copy of env with the same parent (per-iteration let bindings) CheckTDZ A +X ReferenceError "Cannot access 'K[X]' before initialization" if R[A] is hole
Module imports (env^B[C] holds a reference to the exporting module's binding, filled in when the module graph is instantiated)
GetImport A B C +X R[A] = the binding env^B[C] refers to; ReferenceError if hole; X = name constant GetImportW A B +X +X same with slot X1 (slot >= 256); X2 = name constant
Dynamic import and import.meta (off the interpreter's jump table, after the async ops; the compiler sets Function.ScriptOrModule on the root of code that uses them, and of a script or module holding a direct eval)
ImportCall A B R[A] = a new promise for import(R[B]): ToString(R[B]), then the host's module for
it in the running root's script or module, linked and evaluated in the realm's module
map, fulfils it with the module's namespace; any failure rejects it
ImportMeta A R[A] = import.meta of the running root module: a null-prototype object created, and
filled by the host, on the first read in the realm
Globals (X = name constant:16 | inline cache:16)
GetGlobal A +X R[A] = globalThis[name]; ReferenceError if absent GetGlobalOrUndef A +X R[A] = globalThis[name] or undefined (typeof operand) SetGlobal A +X globalThis[name] = R[A]; ReferenceError if absent (strict)
A global name resolves first in the realm's global declarative environment (the let, const and class declarations of scripts), then on the global object. Scripts reach their own top-level declarations by name too: GlobalDeclarationInstantiation (Realm.RunScript) creates them from Function.Extra.Globals before the body runs.
Properties
GetProp A B +X R[A] = R[B].name (X = name:16 | ic:16; TypeError on null/undefined base)
GetLen A B +X R[A] = R[B].length (array and string fast paths; X = ic in the high half)
SetProp A B +X R[A].name = R[B] (strict PutValue)
GetElem A B C R[A] = R[B][R[C]] (dense array and string index fast paths)
GetElemRef A B C +X R[C] = the key R[X], converted by ToPropertyKey when it is an object and
the base R[B] is neither undefined nor null; then GetElem A B C: the read of a
compound assignment or update, whose key GetValue converts once, after ToObject
of the base, and PutValue reuses (X is a register, C or another)
SetElem A B C R[A][R[B]] = R[C]
DelProp A B +X R[A] = delete R[B].name (strict: TypeError when not configurable)
DelElem A B C R[A] = delete R[B][R[C]]
In A B C R[A] = R[B] in R[C]
InstanceOf A B C R[A] = R[B] instanceof R[C]
Literals
NewObject A Bx R[A] = {} (ordinary object, Bx = property count hint)
NewArray A Bx R[A] = [] (Bx = capacity hint)
DefineField A B +X CreateDataProperty(R[A], K[name], R[B]) (X = name:16 | ic:16; the IC caches the shape transition)
DefineElem A B C CreateDataProperty(R[A], ToPropertyKey(R[B]), R[C])
DefineMethod A B C DefineElem for an object-literal method with a computed key: SetFunctionName(R[C], key) first
DefineAccessor A B C +X DefinePropertyOrThrow(R[A], ToPropertyKey(R[B]), {[[Get]] or [[Set]]: R[C], [[Enumerable]], [[Configurable]]: true}),
merging with an existing accessor half; X = AccessorSetter | AccessorEnumerable | AccessorNameKey
(AccessorNameKey: SetFunctionName(R[C], key, "get"/"set") first, for computed keys)
SetProto A B if R[B] is an object or null, R[A].[[Prototype]] = R[B] (the __proto__: v literal form)
CopyDataProps A B copy own enumerable properties of R[B] into R[A] (object spread)
CopyDataPropsEx A B C same, skipping the keys listed in array R[C] (object rest pattern)
ArrayPush A B append R[B] to array R[A]
ArrayHole A append a hole to array R[A] (length += 1)
AppendSpread A B append every value produced by iterating R[B] to array R[A]
Closure A Bx R[A] = new function object over F[Bx] closing over env (and this for arrows)
NewRegExp A Bx R[A] = new RegExp(K[Bx].pattern, K[Bx].flags)
GetTemplate A +X R[A] = the template object of K[X lo] (GetTemplateObject: frozen cooked strings with a frozen raw array), created on the site's first evaluation in the realm and cached in IC slot X hi
Arithmetic (number fast paths; BigInt operands take the slow path)
Add A B C R[A] = R[B] + R[C] (numbers, string concatenation, ToPrimitive) Sub A B C R[A] = R[B] - R[C] Mul A B C R[A] = R[B] * R[C] Div A B C R[A] = R[B] / R[C] Mod A B C R[A] = R[B] % R[C] Exp A B C R[A] = R[B] ** R[C] AddImm A B C R[A] = R[B] + int8(C) (Add semantics: strings concatenate) SubImm A B C R[A] = R[B] - int8(C) Inc A B R[A] = ToNumeric(R[B]) + 1 (++; never concatenates) Dec A B R[A] = ToNumeric(R[B]) - 1 (--) Neg A B R[A] = -R[B] Plus A B R[A] = +R[B] (ToNumber) Not A B R[A] = !ToBoolean(R[B]) BitNot A B R[A] = ~R[B] ToNumeric A B R[A] = ToNumeric(R[B]) (postfix update result) RequireObjectCoercible A throw TypeError when R[A] is null or undefined (empty or rest-only destructuring pattern) ToStr A B R[A] = ToString(R[B]) (template substitutions) Concat A B C R[A] = R[B] + R[C], both already strings (rope above the threshold) Typeof A B R[A] = typeof R[B] TypeofIs A B C R[A] = typeof R[B] === TypeNames[C] BitAnd A B C R[A] = R[B] & R[C] BitOr A B C R[A] = R[B] | R[C] BitXor A B C R[A] = R[B] ^ R[C] Shl A B C R[A] = R[B] << R[C] Shr A B C R[A] = R[B] >> R[C] UShr A B C R[A] = R[B] >>> R[C]
Comparison
Eq A B C R[A] = R[B] == R[C] Ne A B C R[A] = R[B] != R[C] StrictEq A B C R[A] = R[B] === R[C] StrictNe A B C R[A] = R[B] !== R[C] Lt A B C R[A] = R[B] < R[C] Le A B C R[A] = R[B] <= R[C] Gt A B C R[A] = R[B] > R[C] Ge A B C R[A] = R[B] >= R[C]
Control flow (offsets relative to the next pc)
Jmp sBx pc += sBx; a negative offset is a loop back-edge and checks the interrupt flag
JmpT A sBx if ToBoolean(R[A]) pc += sBx
JmpF A sBx if !ToBoolean(R[A]) pc += sBx
JmpNullish A sBx if R[A] is null or undefined pc += sBx
JmpNotNullish A sBx if R[A] is neither null nor undefined pc += sBx
JmpNotUndef A sBx if R[A] is not undefined pc += sBx
JmpLt A sBx +X if R[A] < R[X] pc += sBx (offset relative to the X word); JmpLe, JmpGt, JmpGe
likewise with <=, >, >=. The compiler emits them for a comparison whose value
only decides a conditional jump; they convert and throw as Lt .. Ge do
JmpNLt A sBx +X if !(R[A] < R[X]) pc += sBx; JmpNLe, JmpNGt, JmpNGe likewise (NaN jumps)
JmpStrictEq A sBx +X if R[A] === R[X] pc += sBx; JmpStrictNe with !==
Calls
Call A B R[A] = R[A](this = R[A+1], args R[A+2 .. A+1+B]); TypeError if not callable
CallSpread A R[A] = R[A](this = R[A+1], ...array R[A+2])
New A B R[A] = new R[A](R[A+2 .. A+1+B]); TypeError if not a constructor
NewSpread A R[A] = new R[A](...array R[A+2])
Ret A return R[A]
RetUndef return undefined
Throw A throw R[A]
ThrowConstAssign +X throw TypeError("Assignment to constant variable.") (K[X] names the binding)
Iteration (for-of, spread and array destructuring share GetIterator and IteratorStepValue; R[A], R[A+1] hold the Iterator Record: the iterator and its next method, or, while the protocol is unobservable, the array or string itself and the next index; R[A] is undefined once done or closed)
IterInit A B R[A], R[A+1] = GetIterator(R[B]); TypeError if not iterable IterNext A sBx R[A+2] = next value; when exhausted pc += sBx IterValue A B R[B] = next value or undefined when exhausted (destructuring) IterRest A B R[B] = array of the remaining values (rest element) IterClose A sBx IteratorClose(R[A], normal completion) unless done; pc += sBx IterThrow A B IteratorClose(R[A], throw completion) unless done; throw R[B] ForInInit A B R[A] = enumerable string keys of R[B] (prototype chain, deduplicated), R[A+1] = 0, R[A+2] = the object ForInNext A sBx R[A+3] = next key still present as a property; when exhausted pc += sBx
Classes, super, new.target and private names (off the interpreter's jump table: they only run while classes are defined and in class code)
CtorEntry A R[A] = new.target; TypeError "Class constructor X cannot be invoked without 'new'" in a [[Call]] (first op of every class constructor)
LoadNewTarget A R[A] = new.target, undefined in a [[Call]] (first op of a function with NewTarget set)
LoadHome A R[A] = [[HomeObject]] of the running function
SetHome A B [[HomeObject]] of function R[A] = R[B]
CreateClass A B C finish class constructor R[A] (the Closure of a class-constructor template):
R[A+1] = the new prototype object, R[A].prototype = R[A+1] (read-only),
R[A+1].constructor = R[A] (non-enumerable), [[HomeObject]] of R[A] = R[A+1];
C&1: R[B] is the heritage (null, or a constructor whose prototype is an object or null);
C&2: first name R[A] after property key R[A+1] (an anonymous class field value)
DefineClassMethod A B C R[A][R[B]] := R[C] non-enumerable, [[HomeObject]] of R[C] = R[A];
an unnamed function is named after the key
ToPropertyKey A B R[A] = ToPropertyKey(R[B]) (computed field keys)
GetProtoOf A B R[A] = R[B].[[GetPrototypeOf]]() or null: the super base of home object R[B],
or the super constructor of active function R[B]
GetSuper A B C R[A] = super property R[C] of base R[B] read with receiver R[B+1];
TypeError if the base is null (A may equal B)
SetSuper A B C set super property R[B] of base R[A] to R[C] with receiver R[A+1]; TypeError if refused
SuperCall A B R[A] = Construct(R[A], args R[A+2 .. A+1+B], new.target R[A+1]); TypeError if not a constructor
SuperCallSpread A same with the argument array R[A+2] (AppendSpread-built, or the default constructor's rest array)
CheckSuper A ReferenceError "Super constructor may only be called once" unless R[A] is hole
DerivedResult A B R[A] = the value a derived constructor returns for return value R[A] and raw `this` R[B]:
an object as is; undefined gives `this` (ReferenceError if still hole); otherwise TypeError
NewPrivateName A Bx R[A] = a new private name described by K[Bx] ("#x", or the class name for a brand)
SetPrivateMethod A B C +X make R[B] the method, getter or setter of private name R[A], checked against
the brand R[C] (or, with PrivateStatic, the class constructor R[C]); X = Private* flags
GetPrivate A B C R[A] = R[B].#R[C]; TypeError when R[B] lacks it
SetPrivate A B C R[A].#R[B] = R[C]; TypeError when R[A] lacks it or it is a method
DefPrivate A B C add private field #R[B] = R[C] to object R[A]; TypeError if already present
AddBrand A B add class brand R[B] to object R[A]; TypeError if already present
InPrivate A B C R[A] = #R[B] in R[C]; TypeError if R[C] is not an object
ThrowError A Bx throw a new TypeError (A=ThrowTypeError) or ReferenceError (A=ThrowReferenceError) with message K[Bx]
Sloppy mode, script globals and with (off the interpreter's jump table, after the async ops: only sloppy code, script top levels, with statements and strict assignments of an undeclared name from a value that may run code use them)
SetGlobalSloppy A +X PutValue of an identifier resolved against the global environment in sloppy code:
an unresolvable name becomes a global object property; a rejected write is
ignored (X = name:16 | ic:16, the SetGlobal cache)
ResolveGlobal A +X R[A] = HasBinding(K[X.lo]) of the global environment: ResolveBinding of an
undeclared name in strict code, before the value assigned to it (X = name:16 | ic:16)
SetGlobalRef A B +X PutValue of R[A] to the reference ResolveGlobal resolved: ReferenceError unless
R[B] (its result) is true, else SetMutableBinding(K[X.lo], R[A], strict), whose
HasProperty throws a ReferenceError for a binding gone since (X = ResolveGlobal's)
InitGlobal A +X initialize the script's global lexical binding K[X.lo] to R[A] (X.hi = 1: const)
SetGlobalVar A +X evaluation of a sloppy block function declaration at script level (Annex B.3.2.2):
globalThis[K[X]] = R[A] as SetGlobalSloppy, skipped when a global lexical binding K[X] exists
DelGlobal A +X R[A] = delete K[X] for a name resolved against the global environment:
false for a global lexical binding, else globalThis.[[Delete]](K[X])
SetPropSloppy A B +X R[A].name = R[B], ignoring a rejected assignment (X = name:16 | ic:16)
SetElemSloppy A B C R[A][R[B]] = R[C], ignoring a rejected assignment
DelPropSloppy A B +X R[A] = delete R[B].name; false instead of a TypeError when not configurable
DelElemSloppy A B C R[A] = delete R[B][R[C]]; false instead of a TypeError when not configurable
SetSuperSloppy A B C set super property R[B] of base R[A] to R[C] with receiver R[A+1], ignoring a
rejected assignment
ToObject A B R[A] = ToObject(R[B]); TypeError on null or undefined
JmpWith A sBx +X pc += sBx if the object environment of with object R[A] has binding K[X]:
HasProperty, then a truthy @@unscopables entry blocks it; falls through when R[A]
is not an object (a function's %evalvars before a direct eval declares a var)
WithGet A B +X R[A] = GetBindingValue(K[X.lo]) of the object environment of R[B]: undefined,
or with X.hi = 1 (strict) a ReferenceError, when the property is gone
WithSet A B +X SetMutableBinding(K[X.lo], R[B]) of the object environment of R[A]
(X.hi = 1: strict, a ReferenceError when the property is gone and a TypeError when rejected)
CoerceThis this = the global object when undefined or null, else ToObject(this)
(the entry of a sloppy function that uses this)
MapArguments A make the fresh arguments object R[A] a mapped one: its indices below both the argument
count and NumParams alias the parameters' slots of the function's own Env (CaptureLayout
entries below NumParams) and callee is the running function
CallEval A B C +X R[A] = Call as with Call (C = 1: CallSpread) of callee R[A], this R[A+1]; when the
callee is the realm's %eval% it is a direct eval (PerformEval) of the first argument
in the running Env, compiled against the call site's scope Extra.Evals[X]
Index ¶
- Constants
- Variables
- func DecodeA(w uint32) uint8
- func DecodeB(w uint32) uint8
- func DecodeBx(w uint32) uint16
- func DecodeC(w uint32) uint8
- func DecodeSBx(w uint32) int16
- func Disassemble(fn *Function) string
- func EncodeABC(op Op, a, b, c uint8) uint32
- func EncodeABx(op Op, a uint8, bx uint16) uint32
- func EncodeAsBx(op Op, a uint8, sbx int16) uint32
- func ExtraArg(lo, hi uint16) uint32
- func ExtraHi(x uint32) uint16
- func ExtraLo(x uint32) uint16
- func TypeNameIndex(name string) (uint8, bool)
- type CaptureLayout
- type Const
- type ConstKind
- type EvalLevel
- type EvalScope
- type Export
- type Extra
- type Format
- type Function
- type GlobalNames
- type Handler
- type HandlerKind
- type Import
- type Kind
- type LineEntry
- type Links
- type Module
- type Op
- type Reexport
- type Request
- type SourceInfo
- type Star
Constants ¶
const ( TypeUndefined uint8 = iota TypeObject TypeBoolean TypeNumber TypeString TypeSymbol TypeBigInt TypeFunction )
TypeName indices used by TypeofIs (operand C).
const ( AccessorSetter uint32 = 1 << iota // define the setter half (otherwise the getter) AccessorEnumerable // the property is enumerable AccessorNameKey // name the function "get <key>"/"set <key>" at run time (computed keys) )
DefineAccessor flags (its ExtraArg word). Object literals set AccessorEnumerable; class accessors do not.
const ( PrivateGetter uint32 = 1 << iota // the function is the getter of an accessor PrivateSetter // the function is the setter of an accessor PrivateStatic // checked against the class constructor instead of a brand )
SetPrivateMethod flags (its ExtraArg word).
const ( ThrowTypeError uint8 = iota ThrowReferenceError )
ThrowError kinds (operand A).
const ( MaxRegister = 255 // registers are 8-bit operands MaxBx = 0xFFFF // unsigned 16-bit operand MaxSBx = 0x7FFF MinSBx = -0x8000 )
Instruction word layout constants.
const NoRegister uint16 = 0xFFFF
NoRegister marks a CaptureLayout slot that is not initialized from a parameter register.
const OpCount = int(opCount)
OpCount is the number of defined opcodes.
const UndefinedCooked = "\xff"
UndefinedCooked is the Const.Cooked entry of a tagged template chunk with an invalid escape, whose cooked value is undefined. The byte 0xFF never occurs in WTF-8.
Variables ¶
var DescribeCallee func(f *Function, pc uint32) string
DescribeCallee, when non-nil, returns the source form of the callee of the Call, CallSpread, New or NewSpread instruction at pc of f ("obj.foo", "f(...)"), or "" when it cannot tell. Package compiler installs it; the engine calls it only to word the TypeError of a call or construction of a value that is not callable, so no call site carries any extra data.
var TypeNames = [typeNameCount]string{"undefined", "object", "boolean", "number", "string", "symbol", "bigint", "function"}
TypeNames spells the TypeofIs operand.
Functions ¶
func Disassemble ¶
Disassemble renders fn (and, indented, its children) in a stable text form for tests and debugging.
func EncodeAsBx ¶
EncodeAsBx packs an AsBx instruction.
func TypeNameIndex ¶
TypeNameIndex returns the TypeofIs operand for a typeof result string.
Types ¶
type CaptureLayout ¶
type CaptureLayout []uint16
CaptureLayout describes the function's own closure environment: its length is the number of Env slots created at function entry (zero means the function creates no Env and runs in its closure's environment), and entry i is the parameter register whose value is boxed into slot i at entry, or NoRegister for slots initialized to undefined.
type Const ¶
type Const struct {
Kind ConstKind
Num float64 // ConstNumber
Str string // ConstString (WTF-8), ConstBigInt (decimal or 0x hex), ConstRegExp (pattern)
IsASCII bool // ConstString: every byte of Str is < 0x80
Units []uint16 // ConstString: UTF-16 code units when !IsASCII (nil otherwise)
Key bool // ConstString: used as a property name (the engine interns it)
Flags string // ConstRegExp flags
Cooked []string // ConstTemplate cooked strings (WTF-8, or UndefinedCooked)
Raw []string // ConstTemplate raw strings (UTF-8)
Index int // ConstFunction: index into Function.Children
}
Const is a constant-pool entry. Only the fields relevant to Kind are set.
type ConstKind ¶
type ConstKind uint8
ConstKind tags a constant-pool entry.
const ( // ConstNumber is a float64 literal (Num). ConstNumber ConstKind = iota // ConstString is a string literal (Str, IsASCII, Units). ConstString // ConstBigInt is a BigInt literal stored as text (Str): decimal, or hex // after "0x" (see syntax.BigIntLit). ConstBigInt // ConstRegExp is a regular-expression literal (Str = pattern, Flags). ConstRegExp // ConstTemplate is a tagged template's strings (Cooked, Raw); untagged // templates are lowered to Concat. ConstTemplate // ConstFunction is an index into Function.Children (Index). Reserved; // Closure indexes Children directly. ConstFunction )
type EvalLevel ¶
type EvalLevel struct {
Kind uint8 // syntax.ScopeKind of the scope owning the Env
Names []string
Kinds []uint8
TDZ []bool
// Outer is the next Env up the chain, nil for the outermost.
Outer *EvalLevel
// contains filtered or unexported fields
}
EvalLevel is one Env of an EvalScope: its binding names in slot order ("" for a slot no binding owns), with the syntax.BindKind and whether the binding has a temporal dead zone for each.
func (*EvalLevel) Slot ¶
Slot returns the slot of the binding named name, or -1 when the Env has none. A large level indexes its names on first use, so that eval code using a few of them does not pay for all: the index is a cache every goroutine would build the same, stored atomically, and the one thing written to a Function after compilation.
type EvalScope ¶
type EvalScope struct {
// Env is the level of the running Env of the call site, nil when there
// is none; its Outer chain are the Envs outwards: level i is the Env i
// hops up the chain from the running Env. The script's global bindings
// are not levels; they are resolved through the global object and the
// global lexical environment.
Env *EvalLevel
// Var is the level holding the variable environment of the calling
// function (its %evalvars object), or -1 for the global one.
Var int
// This is the level of the Env of the function supplying this,
// new.target and super (the nearest non-arrow function), or -1 when
// the call site is not inside a function.
This int
// Fields is the level holding the %fields binding of the class of a
// derived constructor supplying this (super() in the eval code runs its
// field initializers), or -1.
Fields int
// FuncKind is the syntax.FuncKind of that function and Derived marks a
// derived class constructor.
FuncKind uint8
Derived bool
// Strict is the strictness of the calling code; InParams marks a call
// in the parameter list of a function whose body has its own variable
// environment; Module marks a call in module code.
Strict, InParams, Module bool
}
EvalScope describes the environment chain visible at a direct eval call site so that the compiler can resolve the eval code against it at run time. It is opaque to the engine, which hands it to the compile hook together with the source text. The call sites of a compilation share their EvalScopes and EvalLevels where they can: sites in one scope have the same EvalScope, and the chains of sites in nested scopes share their outer levels.
type Extra ¶
type Extra struct {
// Globals lists the names a script or global eval code declares at top
// level (nil when it declares none) for GlobalDeclarationInstantiation
// or EvalDeclarationInstantiation, which check and create them before
// the body runs.
Globals *GlobalNames
// Evals describes the scope of each direct eval call site (CallEval's
// X operand indexes it).
Evals []*EvalScope
}
Extra is the rarely needed part of a Function.
type Function ¶
type Function struct {
Name string // "" for anonymous functions
Length int // formal parameter count exposed as .length
Strict bool // strict mode code (modules, classes, "use strict"); else sloppy
Kind Kind
NumRegs uint16 // register window size
NumParams uint16 // declared parameters (excluding rest)
HasRest bool // the rest array is collected into R[NumParams] at entry
// HasArguments makes the entry create an unmapped arguments object in
// the register after the parameters (and the rest array); the prologue
// of a sloppy function with simple parameters maps it (MapArguments).
HasArguments bool
// NewTarget is set when the function reads new.target (LoadNewTarget):
// [[Construct]] then passes it through the realm. Class constructors
// always receive it.
NewTarget bool
// Generator marks a generator function (of kind KindMethod: not
// constructible); its body starts with GenStart.
Generator bool
// Async marks an async function, async generator function or module
// with top-level await (see compiler/async.go).
Async bool
// ScriptOrModule is set on the root template of a script or module
// whose code uses import() or import.meta: they need the script or
// module record the host gave for it at run time
// (GetActiveScriptOrModule), which a realm keeps only for such roots.
// It is set too on the root of a script or module holding a direct
// eval call: the code a realm compiles from a string shares the record
// of the code that evaluates it.
ScriptOrModule bool
// ICCount is the number of inline-cache slots this function needs. Their
// indices are assigned per realm on the function's first call there
// (engine.Realm.AllocIC); the template is never mutated.
ICCount uint32
Code []uint32
Consts []Const
Children []*Function
Handlers []Handler
CaptureLayout CaptureLayout
LineTable []LineEntry
Source *SourceInfo
// Module is the module record of a KindModule template (nil for any
// other kind).
Module *Module
// Extra holds the rarely needed data of scripts, eval code and
// functions containing a direct eval (nil for everything else, so
// ordinary functions pay one pointer word).
Extra *Extra
}
Function is an immutable compiled-function template. A single *Function is shared by every realm that instantiates the same module, so nothing in it may be mutated after compilation; per-realm state (inline caches) lives in engine.Realm side tables keyed by *Function.
func (*Function) Position ¶
Position returns the source line and column of the instruction at pc, or (0, 0) when the function has no line table.
func (*Function) SourceText ¶
SourceText returns the function's source slice for Function.prototype.toString, or "" when unknown.
type GlobalNames ¶
type GlobalNames struct {
Lexical []string // let, const and class names
Function []string // function declaration names, each once
Var []string // var names that are not also function names
// AnnexB lists the names only block-level function declarations of
// sloppy code declare at top level (Annex B.3.2.2): each becomes a var
// unless a global lexical binding or a non-extensible global object
// prevents it.
AnnexB []string
// Eval marks the declarations of sloppy eval code, whose global
// bindings are created configurable (EvalDeclarationInstantiation) and
// which never declares global lexical bindings.
Eval bool
}
GlobalNames are the top-level declarations of a script, each list in source order.
type Handler ¶
type Handler struct {
Start uint32
End uint32
Handler uint32
StackDepth uint16
Kind HandlerKind
Reg uint16 // register receiving the exception (see HandlerKind)
}
Handler is one exception-table row: [Start, End) is the protected pc range, Handler is the pc to resume at and StackDepth the closure-environment depth (number of PushEnv levels relative to function entry) to unwind to. Rows are ordered innermost first; the interpreter takes the first row whose range covers the faulting pc.
type HandlerKind ¶
type HandlerKind uint8
HandlerKind distinguishes catch and finally rows of the handler table.
const ( // HandlerCatch receives the thrown value in R[Reg]. HandlerCatch HandlerKind = iota // HandlerFinally receives the completion kind (1 = throw) in R[Reg] and // the thrown value in R[Reg+1]. HandlerFinally )
type Import ¶
type Import struct {
Request int // index into Links.Requests
Name string // the imported name (unset for a namespace import)
Namespace bool
Slot int
Line, Col int32
}
Import is an import binding.
type Kind ¶
type Kind uint8
Kind classifies a compiled function template.
const ( // KindNormal is an ordinary function declaration or expression. KindNormal Kind = iota // KindArrow is an arrow function (lexical this, not constructible). KindArrow // KindMethod is a concise object-literal method (not constructible). KindMethod // KindModule is the module top-level body. KindModule // KindScript is a script top-level body (Realm.RunScript): its // top-level declarations are global bindings reached by name. KindScript // KindClassCtor is a base class constructor: [[Construct]] creates // `this` from new.target and [[Call]] throws (CtorEntry). The class // kinds come last so IsClassCtor is one comparison. KindClassCtor // KindDerivedCtor is the constructor of a class with an extends clause: // `this` stays uninitialized until super() returns. KindDerivedCtor )
func (Kind) IsClassCtor ¶
IsClassCtor reports whether k is a class constructor kind.
type Links ¶
type Links struct {
// Requests are the specifiers, each once ([[RequestedModules]]).
Requests []Request
// Imports are the import bindings: each Env slot holds a reference to
// the exporting binding (GetImport), or the namespace object when
// Namespace is set (`import * as ns`, read with GetEnv).
Imports []Import
// Reexports are the indirect exports: `export {x as y} from`, `export *
// as y from` (All), and `export {x as y}` of an import binding x.
Reexports []Reexport
// Stars are the requests of the `export * from` entries.
Stars []Star
}
Links are the module requests of a module and the entries that name them, each in source order. Line and Col (1-based, the column in code points) locate the specifier or the entry for the errors of linking.
type Module ¶
type Module struct {
// Exports are the local exports sorted by name.
Exports []Export
// BodyPC is the pc of the body after the prologue that writes the TDZ
// markers and creates the hoisted functions: linking a graph runs the
// prologue of every module before any body runs (InitializeEnvironment).
BodyPC int
// Links is nil when the module requests no other module and uses
// neither import() nor import.meta, which then evaluates without a
// module graph (a direct eval in its code alone sets ScriptOrModule
// and leaves Links nil); Requests is empty when it only uses those.
Links *Links
}
Module is what a module template records for the host and for linking (ECMA-262 §16.2.1.7 Source Text Module Records). The module Env is the top-level function's own Env, so every local binding, and every exported one, is a CaptureLayout slot.
type Op ¶
type Op uint8
Op is an opcode. Opcodes are dense so that the interpreter's switch compiles to a jump table. See doc.go for the semantics of every op.
const ( // --- loads and moves --- LoadConst Op = iota // ABx R[A] = K[Bx] LoadInt // AsBx R[A] = sBx LoadUndef // A R[A] = undefined LoadNull // A R[A] = null LoadTrue // A R[A] = true LoadFalse // A R[A] = false LoadHole // A R[A] = hole (TDZ marker) LoadThis // A R[A] = this LoadCallee // A R[A] = the running function object Move // AB R[A] = R[B] UndefRange // AB R[A .. A+B-1] = undefined // --- closure environments --- GetEnv // ABC R[A] = env^B[C] GetEnvChk // ABC+X R[A] = env^B[C]; ReferenceError if hole; X = name const SetEnv // ABC env^B[C] = R[A] GetEnvW // AB+X R[A] = env^B[X] GetEnvChkW // AB+X+X R[A] = env^B[X1]; ReferenceError if hole; X2 = name const SetEnvW // AB+X env^B[X] = R[A] PushEnv // ABx env = new Env(parent env, Bx slots) PopEnv // - env = env.parent CopyEnv // - env = shallow copy of env (per-iteration bindings) CheckTDZ // A+X ReferenceError "Cannot access 'K[X]' before initialization" if R[A] is hole // --- globals --- GetGlobal // A+X R[A] = global[name]; ReferenceError if absent; X = name:16 | ic:16 GetGlobalOrUndef // A+X R[A] = global[name] or undefined (typeof) SetGlobal // A+X global[name] = R[A]; ReferenceError if absent // --- properties --- GetProp // AB+X R[A] = R[B].name; X = name:16 | ic:16 GetLen // AB+X R[A] = R[B].length; X = ic:16 SetProp // AB+X R[A].name = R[B]; X = name:16 | ic:16 GetElem // ABC R[A] = R[B][R[C]] SetElem // ABC R[A][R[B]] = R[C] DelProp // AB+X R[A] = delete R[B].name; X = name const DelElem // ABC R[A] = delete R[B][R[C]] In // ABC R[A] = R[B] in R[C] InstanceOf // ABC R[A] = R[B] instanceof R[C] // --- literals --- NewObject // ABx R[A] = {} with room for Bx properties NewArray // ABx R[A] = [] with capacity Bx DefineField // AB+X R[A].name := R[B] (CreateDataProperty); X = name const DefineElem // ABC R[A][R[B]] := R[C] (CreateDataProperty) DefineMethod // ABC R[A][R[B]] := R[C], naming the function R[C] after the key (computed-key method) DefineAccessor // ABC+X define R[C] as the getter or setter of R[A][R[B]]; X = Accessor* flags SetProto // AB R[A].[[Prototype]] = R[B] when object or null (__proto__: v) CopyDataProps // AB copy own enumerable properties of R[B] into R[A] CopyDataPropsEx // ABC same, excluding the keys listed in array R[C] ArrayPush // AB append R[B] to array R[A] ArrayHole // A append a hole to array R[A] AppendSpread // AB append every element of iterable R[B] to array R[A] Closure // ABx R[A] = new function object from Children[Bx] NewRegExp // ABx R[A] = new RegExp(K[Bx].pattern, K[Bx].flags) GetTemplate // A+X R[A] = template object of K[X] (GetTemplateObject); X = const:16 | ic:16 // --- arithmetic --- Add // ABC R[A] = R[B] + R[C] Sub // ABC Mul // ABC Div // ABC Mod // ABC Exp // ABC R[A] = R[B] ** R[C] AddImm // ABC R[A] = R[B] + int8(C) SubImm // ABC R[A] = R[B] - int8(C) Inc // AB R[A] = ToNumeric(R[B]) + 1 Dec // AB R[A] = ToNumeric(R[B]) - 1 Neg // AB R[A] = -R[B] Plus // AB R[A] = +R[B] (ToNumber) Not // AB R[A] = !R[B] BitNot // AB R[A] = ~R[B] ToNumeric // AB R[A] = ToNumeric(R[B]) // RequireObjectCoercible throws TypeError when R[A] is null or undefined // (destructuring through a pattern with no properties to read). RequireObjectCoercible // A ToStr // AB R[A] = ToString(R[B]) Concat // ABC R[A] = R[B] concatenated with R[C] (both strings) Typeof // AB R[A] = typeof R[B] TypeofIs // ABC R[A] = typeof R[B] === TypeName(C) BitAnd // ABC BitOr // ABC BitXor // ABC Shl // ABC Shr // ABC signed >> UShr // ABC >>> // --- comparison --- Eq // ABC R[A] = R[B] == R[C] Ne // ABC StrictEq // ABC StrictNe // ABC Lt // ABC Le // ABC Gt // ABC Ge // ABC // --- control flow --- Jmp // sBx pc += sBx (negative offsets are back-edges: interrupt check) JmpT // AsBx if ToBoolean(R[A]) pc += sBx JmpF // AsBx if !ToBoolean(R[A]) pc += sBx JmpNullish // AsBx if R[A] is null or undefined JmpNotNullish // AsBx JmpNotUndef // AsBx // --- calls --- Call // AB R[A] = R[A].call(this = R[A+1], R[A+2 .. A+1+B]) CallSpread // A R[A] = R[A].call(this = R[A+1], ...R[A+2]) New // AB R[A] = new R[A](R[A+2 .. A+1+B]) NewSpread // A R[A] = new R[A](...R[A+2]) Ret // A return R[A] RetUndef // - return undefined Throw // A throw R[A] ThrowConstAssign // +X throw TypeError("Assignment to constant variable."); X = name const // --- iteration --- IterInit // AB R[A], R[A+1] = iterator state over R[B] IterNext // AsBx R[A+2] = next value of iterator R[A]; when done pc += sBx IterValue // AB R[B] = next value of iterator R[A], or undefined when done IterRest // AB R[B] = array of the remaining values of iterator R[A] IterClose // AsBx IteratorClose(R[A], normal); pc += sBx IterThrow // AB IteratorClose(R[A], throw R[B]); throw R[B] ForInInit // AB R[A] = key list of R[B], R[A+1] = 0, R[A+2] = ToObject(R[B]) (or undefined) ForInNext // AsBx R[A+3] = next key still present on R[A+2]; when done pc += sBx // --- references (on the jump table, numbered after the ops above so // that code without them keeps its encoding) --- GetElemRef // ABC+X R[C] = R[X], converted once; R[A] = R[B][R[C]]; X = register // --- classes (dispatched off the interpreter's jump table) --- CtorEntry // A R[A] = new.target; TypeError when a class constructor is called LoadNewTarget // A R[A] = new.target, or undefined in a [[Call]] LoadHome // A R[A] = [[HomeObject]] of the running function SetHome // AB [[HomeObject]] of function R[A] = R[B] CreateClass // ABC make R[A] a class constructor, R[A+1] = its prototype; C&1: R[B] is the heritage, C&2: named after key R[A+1] DefineClassMethod // ABC R[A][R[B]] := R[C] non-enumerable, [[HomeObject]] R[A] ToPropertyKey // AB R[A] = ToPropertyKey(R[B]) GetProtoOf // AB R[A] = R[B].[[GetPrototypeOf]]() (super base, super constructor) GetSuper // ABC R[A] = R[B][R[C]] with receiver R[B+1] SetSuper // ABC R[A][R[B]] = R[C] with receiver R[A+1] SuperCall // AB R[A] = Construct(R[A], R[A+2 .. A+1+B], new.target R[A+1]) SuperCallSpread // A R[A] = Construct(R[A], ...R[A+2], new.target R[A+1]) CheckSuper // A ReferenceError unless R[A] (the raw `this` binding) is hole DerivedResult // AB R[A] = result of a derived constructor returning R[A] with `this` R[B] NewPrivateName // ABx R[A] = new private name described by K[Bx] SetPrivateMethod // ABC+X private name R[A] names method R[B] owned by brand/class R[C]; X = Private* flags GetPrivate // ABC R[A] = R[B].#R[C] SetPrivate // ABC R[A].#R[B] = R[C] DefPrivate // ABC add private field #R[B] = R[C] to R[A] AddBrand // AB add class brand R[B] to R[A] InPrivate // ABC R[A] = #R[B] in R[C] ThrowError // ABx throw a new error of kind A (Throw* kinds) with message K[Bx] // --- generators (dispatched off the jump table after the class ops) --- GenFunc // A make the fresh closure R[A] a generator function GenStart // A R[A] = new generator with `this` R[A]; suspends it, and the call returns it Yield // ABC generator R[B] suspends yielding R[A] (C=1: R[A] is a result object, yielded as is); // resumed with the sent value in R[A] and the mode in R[A+1]: undefined next, true throw, false return GenIter // AB R[A] = GetIterator(R[B]) (never a fast mode), R[A+1] = its next method Delegate // AB one yield* step on the record R[B..B+1] with the value R[A] and mode R[A+1] (as Yield): // --- async functions and iteration (dispatched after the generator ops) --- AsyncFunc // A make the fresh closure R[A] an async function or async generator function AsyncStart // A R[A] = the state of an async function call with `this` R[A]; the call returns its promise AsyncGenStart // A R[A] = new async generator with `this` R[A]; suspends it, and the call returns it Await // AB R[B] (the async state) awaits R[A]; resumed with R[A] = the result, R[A+1] = undefined (fulfilled) or true (rejected) AsyncReturn // AB resolve the promise of async function R[B] with R[A]; R[A] = the promise AsyncThrow // AB reject the promise of async function R[B] with R[A]; R[A] = the promise AsyncYield // AB async generator R[B] settles its head request with {value: R[A], done: false}; resumed as Yield AsyncIterInit // AB R[A] = GetIterator(R[B], async), R[A+1] = its next method AsyncIterNext // A R[A+2] = Call(R[A+1], R[A]) AsyncIterResult // AsBx TypeError unless R[A+2] is an object; when done pc += sBx, else R[A+2] = its value AsyncIterClose // AsBx R[A] = undefined; pc += sBx if the iterator has no return method, else R[A+2] = Call(return, iterator) AsyncIterClosed // A TypeError unless the awaited return result R[A+2] is an object AsyncDelegate // ABC one async yield* step on the record R[B..B+2] with value R[A] and mode R[A+1] (C=0 call, C=1 result) // --- modules --- GetImport // ABC+X R[A] = the binding env^B[C] refers to; ReferenceError if hole; X = name const GetImportW // AB+X+X R[A] = the binding env^B[X1] refers to; ReferenceError if hole; X2 = name const ImportCall // AB R[A] = import(R[B]): a promise for the namespace of the module the host loads for it ImportMeta // A R[A] = import.meta of the running script or module's root // --- sloppy mode, script globals and with (dispatched after the async ops) --- SetGlobalSloppy // A+X global[name] = R[A] in sloppy mode: creates an unresolvable name, a rejected write is ignored; X = name:16 | ic:16 ResolveGlobal // A+X R[A] = whether the global environment has a binding name (strict, before the value of an assignment); X = name:16 | ic:16 SetGlobalRef // AB+X global binding name = R[A] (strict) when R[B], ResolveGlobal's result, else ReferenceError; X = name:16 | ic:16 (ResolveGlobal's) InitGlobal // A+X initialize the script's global lexical binding name to R[A]; X = name:16 | const<<16 SetGlobalVar // A+X Annex B.3.2.2: global[name] = R[A] (sloppy) unless a global lexical binding name exists; X = name const DelGlobal // A+X R[A] = delete of the global binding name (false for a global lexical); X = name const SetPropSloppy // AB+X R[A].name = R[B], a rejected assignment ignored; X = name:16 | ic:16 SetElemSloppy // ABC R[A][R[B]] = R[C], a rejected assignment ignored DelPropSloppy // AB+X R[A] = delete R[B].name, false when rejected; X = name const DelElemSloppy // ABC R[A] = delete R[B][R[C]], false when rejected SetSuperSloppy // ABC R[A][R[B]] = R[C] with receiver R[A+1], a rejected assignment ignored ToObject // AB R[A] = ToObject(R[B]) (the with statement's object) JmpWith // AsBx+X pc += sBx if the with object R[A] has a binding name (HasProperty, not @@unscopables-blocked); X = name const WithGet // AB+X R[A] = binding name of the with object R[B]; X = name:16 | strict<<16 WithSet // AB+X set binding name of the with object R[A] to R[B]; X = name:16 | strict<<16 CoerceThis // - this = the global object if undefined or null, else ToObject(this) (sloppy function entry) MapArguments // A make the arguments object R[A] mapped to the parameters in the function's own Env CallEval // ABC+X R[A] = call of callee R[A] with this R[A+1] and args R[A+2..] (B = argc, C = 1: one spread array), a direct eval when the callee is %eval%; X = index into Extra.Evals // --- compare and branch (a comparison whose result only feeds a // conditional jump; numbered last so that code without them keeps its // encoding). The offset is relative to the X word. --- JmpLt // AsBx+X if R[A] < R[X] pc += sBx JmpLe // AsBx+X if R[A] <= R[X] pc += sBx JmpGt // AsBx+X if R[A] > R[X] pc += sBx JmpGe // AsBx+X if R[A] >= R[X] pc += sBx JmpNLt // AsBx+X if !(R[A] < R[X]) pc += sBx JmpNLe // AsBx+X if !(R[A] <= R[X]) pc += sBx JmpNGt // AsBx+X if !(R[A] > R[X]) pc += sBx JmpNGe // AsBx+X if !(R[A] >= R[X]) pc += sBx JmpStrictEq // AsBx+X if R[A] === R[X] pc += sBx JmpStrictNe // AsBx+X if R[A] !== R[X] pc += sBx )
Instruction formats. Every instruction is one 32-bit word:
ABC : op:8 | A:8 | B:8 | C:8 ABx : op:8 | A:8 | Bx:16 (Bx unsigned) AsBx: op:8 | A:8 | sBx:16 (sBx signed, jump offset relative to the next pc)
Ops marked "+X" in doc.go are followed by one ExtraArg word (a raw uint32) whose layout is given per op; a few use two.
func CompareJump ¶
CompareJump returns the op that jumps when the comparison op (Lt, Le, Gt, Ge, StrictEq or StrictNe) of R[A] and R[X] is jumpIf, and false for any other op.
func (Op) ExtraWords ¶
ExtraWords returns how many ExtraArg words follow an instruction of op.
type Reexport ¶
type Reexport struct {
Name string // the exported name
Request int
Import string // the imported name (unset when All)
All bool
Line, Col int32
}
Reexport is an indirect export.
type SourceInfo ¶
type SourceInfo struct {
Name string // file name used in stack traces
Src string // full module/script source text
Start int // byte offset of the function's first character
End int // byte offset one past the function's last character
}
SourceInfo locates a function template inside its source file. Function.prototype.toString slices Src[Start:End].