--- 136f05c1bbc46466c3334240f0a2f6ac2c657c25/js/src/frontend/BytecodeEmitter.cpp
+++ 443b10f2927593c3c882e0c4c5ab44e13fbb7eb8/js/src/frontend/BytecodeEmitter.cpp
@@ -2379,16 +2379,86 @@ bool BytecodeEmitter::defineHoistedTopLe
if (!body->as<ListNode>().hasTopLevelFunctionDeclarations()) {
return true;
}
return emitHoistedFunctionsInList(&body->as<ListNode>());
}
+// For Global and sloppy-Eval scripts, this performs most of the steps of the
+// spec's [GlobalDeclarationInstantiation] and [EvalDeclarationInstantiation]
+// operations.
+//
+// Note that while strict-Eval is handled in the same part of the spec, it never
+// fails for global-redeclaration checks so those scripts initialize directly in
+// their bytecode.
+bool BytecodeEmitter::emitDeclarationInstantiation(ParseNode* body) {
+ if (sc->isModuleContext()) {
+ // ES Modules have dedicated variable and lexial environments and therefore
+ // do not have to perform redeclaration checks. We initialize their bindings
+ // elsewhere in bytecode.
+ return true;
+ }
+
+ if (sc->isEvalContext() && sc->strict()) {
+ // Strict Eval has a dedicated variables (and lexical) environment and
+ // therefore does not have to perform redeclaration checks. We initialize
+ // their bindings elsewhere in the bytecode.
+ return true;
+ }
+
+ // If we have no variables bindings, then we are done!
+ if (sc->isGlobalContext()) {
+ if (!sc->asGlobalContext()->bindings) {
+ return true;
+ }
+ } else {
+ MOZ_ASSERT(sc->isEvalContext());
+
+ if (!sc->asEvalContext()->bindings) {
+ return true;
+ }
+ }
+
+#if DEBUG
+ // There should be no emitted functions yet.
+ for (const auto& thing : perScriptData().gcThingList().objects()) {
+ MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope());
+ }
+#endif
+
+ // Emit the hoisted functions to gc-things list. There is no bytecode
+ // generated yet to bind them.
+ if (!defineHoistedTopLevelFunctions(body)) {
+ return false;
+ }
+
+ // Save the last GCThingIndex emitted. The hoisted functions are contained in
+ // the gc-things list up until this point. This set of gc-things also contain
+ // initial scopes (of which there must be at least one).
+ MOZ_ASSERT(perScriptData().gcThingList().length() > 0);
+ GCThingIndex lastFun =
+ GCThingIndex(perScriptData().gcThingList().length() - 1);
+
+#if DEBUG
+ for (const auto& thing : perScriptData().gcThingList().objects()) {
+ MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope() ||
+ thing.isFunction());
+ }
+#endif
+
+ // Check for declaration conflicts and initialize the bindings.
+ if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) {
+ return false;
+ }
+
+ return true;
+}
+
bool BytecodeEmitter::emitScript(ParseNode* body) {
AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission,
parser->errorReporter(), body);
setScriptStartOffsetIfUnset(body->pn_pos.begin);
MOZ_ASSERT(inPrologue());
@@ -2409,29 +2479,33 @@ bool BytecodeEmitter::emitScript(ParseNo
}
}
setFunctionBodyEndPos(body->pn_pos.end);
bool isSloppyEval = sc->isEvalContext() && !sc->strict();
if (isSloppyEval && body->is<LexicalScopeNode>() &&
!body->as<LexicalScopeNode>().isEmptyScope()) {
- // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is
- // an immediately enclosed lexical scope, we need to enter the lexical
- // scope in the prologue for the DEFFUNs to pick up the right
- // environment chain.
+ // Sloppy eval scripts may emit hoisted functions bindings with a
+ // `JSOp::GlobalOrEvalDeclInstantiation` opcode below. If this eval needs a
+ // top-level lexical environment, we must ensure that environment is created
+ // before those functions are created and bound.
+ //
+ // This differs from the global-script case below because the global-lexical
+ // environment exists outside the script itself. In the case of strict eval
+ // scripts, the `emitterScope` above is already sufficient.
EmitterScope lexicalEmitterScope(this);
LexicalScopeNode* scope = &body->as<LexicalScopeNode>();
if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical,
scope->scopeBindings())) {
return false;
}
- if (!defineHoistedTopLevelFunctions(scope->scopeBody())) {
+ if (!emitDeclarationInstantiation(scope->scopeBody())) {
return false;
}
if (!switchToMain()) {
return false;
}
ParseNode* scopeBody = scope->scopeBody();
@@ -2442,20 +2516,18 @@ bool BytecodeEmitter::emitScript(ParseNo
if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) {
return false;
}
if (!lexicalEmitterScope.leave(this)) {
return false;
}
} else {
- if (sc->isGlobalContext() || isSloppyEval) {
- if (!defineHoistedTopLevelFunctions(body)) {
- return false;
- }
+ if (!emitDeclarationInstantiation(body)) {
+ return false;
}
if (!switchToMain()) {
return false;
}
if (!emitTree(body)) {
return false;
--- 394718eef5059ae3cffdb30a8b91b1c2032cd188/js/src/frontend/EmitterScope.cpp
+++ a25a8624af260e4e193be8f67206cd132cb2b1dd/js/src/frontend/EmitterScope.cpp
@@ -770,40 +770,16 @@ bool EmitterScope::enterFunctionExtraBod
// The extra var scope needs a note to be mapped from a pc.
if (!appendScopeNote(bce)) {
return false;
}
return checkEnvironmentChainLength(bce);
}
-class DynamicBindingIter : public ParserBindingIter {
- public:
- explicit DynamicBindingIter(GlobalSharedContext* sc)
- : ParserBindingIter(*sc->bindings) {}
-
- explicit DynamicBindingIter(EvalSharedContext* sc)
- : ParserBindingIter(*sc->bindings, /* strict = */ false) {
- MOZ_ASSERT(!sc->strict());
- }
-
- JSOp bindingOp() const {
- switch (kind()) {
- case BindingKind::Var:
- return JSOp::DefVar;
- case BindingKind::Let:
- return JSOp::DefLet;
- case BindingKind::Const:
- return JSOp::DefConst;
- default:
- MOZ_CRASH("Bad BindingKind");
- }
- }
-};
-
bool EmitterScope::enterGlobal(BytecodeEmitter* bce,
GlobalSharedContext* globalsc) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
// TODO-Stencil
// This is another snapshot-sensitive location.
// The incoming atoms from the global scope object should be snapshotted.
// For now, converting them to ParserAtoms here individually.
@@ -838,39 +814,27 @@ bool EmitterScope::enterGlobal(BytecodeE
if (!internBodyScopeCreationData(bce, createScope)) {
return false;
}
// See: JSScript::outermostScope.
MOZ_ASSERT(bce->bodyScopeIndex == GCThingIndex::outermostScopeIndex(),
"Global scope must be index 0");
- // Resolve binding names and emit Def{Var,Let,Const} prologue ops.
+ // Resolve binding names.
+ //
+ // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the
+ // redeclaration check and initialize these bindings.
if (globalsc->bindings) {
- // Check for declaration conflicts before the Def* ops.
- if (!bce->emit1(JSOp::CheckGlobalOrEvalDecl)) {
- return false;
- }
-
- for (DynamicBindingIter bi(globalsc); bi; bi++) {
+ for (ParserBindingIter bi(*globalsc->bindings); bi; bi++) {
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
const ParserAtom* name = bi.name();
if (!putNameInCache(bce, name, loc)) {
return false;
}
-
- // Define the name in the prologue. Do not emit DefVar for
- // functions that we'll emit DefFun for.
- if (bi.isTopLevelFunction()) {
- continue;
- }
-
- if (!bce->emitAtomOp(bi.bindingOp(), name)) {
- return false;
- }
}
}
// Note that to save space, we don't add free names to the cache for
// global scopes. They are assumed to be global vars in the syntactic
// global scope, dynamic accesses under non-syntactic global scope.
if (globalsc->scopeKind() == ScopeKind::Global) {
fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
@@ -908,41 +872,19 @@ bool EmitterScope::enterEval(BytecodeEmi
return false;
}
if (hasEnvironment()) {
if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) {
return false;
}
} else {
- // Resolve binding names and emit DefVar prologue ops if we don't have
- // an environment (i.e., a sloppy eval).
- // Eval scripts always have their own lexical scope, but non-strict
- // scopes may introduce 'var' bindings to the nearest var scope.
- //
- // TODO: We may optimize strict eval bindings in the future to be on
- // the frame. For now, handle everything dynamically.
- if (!hasEnvironment() && evalsc->bindings) {
- // Check for declaration conflicts before the DefVar ops.
- if (!bce->emit1(JSOp::CheckGlobalOrEvalDecl)) {
- return false;
- }
-
- for (DynamicBindingIter bi(evalsc); bi; bi++) {
- MOZ_ASSERT(bi.bindingOp() == JSOp::DefVar);
-
- if (bi.isTopLevelFunction()) {
- continue;
- }
-
- if (!bce->emitAtomOp(JSOp::DefVar, bi.name())) {
- return false;
- }
- }
- }
+ // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the
+ // redeclaration check and initialize these bindings for sloppy
+ // eval.
// As an optimization, if the eval does not have its own var
// environment and is directly enclosed in a global scope, then all
// free name lookups are global.
if (scope(bce).enclosing().is<GlobalScope>()) {
fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
}
}
--- f15e01260d1c0c06467bc2a81195e538d371890f/js/src/frontend/FunctionEmitter.cpp
+++ 443b10f2927593c3c882e0c4c5ab44e13fbb7eb8/js/src/frontend/FunctionEmitter.cpp
@@ -2,16 +2,17 @@
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
#include "frontend/FunctionEmitter.h"
#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Unused.h"
#include "builtin/ModuleObject.h" // ModuleObject
#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/ModuleSharedContext.h" // ModuleSharedContext
#include "frontend/NameAnalysisTypes.h" // NameLocation
#include "frontend/NameOpEmitter.h" // NameOpEmitter
#include "frontend/ParseContext.h" // BindingIter
@@ -302,24 +303,21 @@ bool FunctionEmitter::emitTopLevelFuncti
return bce_->sc->asModuleContext()->builder.noteFunctionDeclaration(
bce_->cx, index);
}
MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext());
MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement);
MOZ_ASSERT(bce_->inPrologue());
- if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) {
- // [stack] FUN
- return false;
- }
- if (!bce_->emit1(JSOp::DefFun)) {
- // [stack]
- return false;
- }
+ // NOTE: The `index` is not directly stored as an opcode, but we collect the
+ // range of indices in `BytecodeEmitter::emitDeclarationInstantiation` instead
+ // of discrete indices.
+ mozilla::Unused << index;
+
return true;
}
bool FunctionEmitter::emitNewTargetForArrow() {
// [stack]
if (bce_->sc->allowNewTarget()) {
if (!bce_->emit1(JSOp::NewTarget)) {
--- 3a3723ac01526070c0a5f4bfac33e9d7329b3e18/js/src/vm/Opcodes.h
+++ 3fc26b5eba8401b97142b679b2ea1d7480f76c3e/js/src/vm/Opcodes.h
@@ -2743,18 +2743,18 @@
* 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 `DefLet` or `DefConst` and
- * must be uninitialized.
+ * 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
@@ -3322,97 +3322,37 @@
*
* Category: Variables and scopes
* Type: Creating and deleting bindings
* Operands:
* Stack: => env
*/ \
MACRO(BindVar, bind_var, NULL, 1, 0, 1, JOF_BYTE) \
/*
- * Create a new binding on the current VariableEnvironment (the environment
- * on the environment chain designated to receive new variables).
- *
- * `JSOp::Def{Var,Let,Const,Fun}` instructions must appear in the script
- * before anything else that might add bindings to the environment, and
- * only once per binding. There must be a correct entry for the new binding
- * in `script->bodyScope()`. (All this ensures that at run time, there is
- * no existing conflicting binding. This is checked by the
- * `JSOp::CheckGlobalOrEvalDecl` bytecode instruction that must appear
- * before `JSOp::Def{Var,Let,Const,Fun}`.)
- *
- * Throw a SyntaxError if the current VariableEnvironment is the global
- * environment and a binding with the same name exists on the global
- * lexical environment.
- *
- * This is used for global scripts and also in some cases for function
- * scripts where use of dynamic scoping inhibits optimization.
- *
- * Category: Variables and scopes
- * Type: Creating and deleting bindings
- * Operands: uint32_t nameIndex
- * Stack: =>
- */ \
- MACRO(DefVar, def_var, NULL, 5, 0, 0, JOF_ATOM) \
- /*
- * Create a new binding for the given function on the current scope.
- *
- * `fun` must be a function object with an explicit name. The new
- * variable's name is `fun->explicitName()`, and its value is `fun`. In
- * global scope, this creates a new property on the global object.
- *
- * Implements: The body of the loop in [GlobalDeclarationInstantiation][1]
- * step 17 ("For each Parse Node *f* in *functionsToInitialize*...") and
- * the corresponding loop in [EvalDeclarationInstantiation][2].
- *
- * [1]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
- * [2]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
- *
- * Category: Variables and scopes
- * Type: Creating and deleting bindings
- * Operands:
- * Stack: fun =>
- */ \
- MACRO(DefFun, def_fun, NULL, 1, 1, 0, JOF_BYTE) \
- /*
- * Create a new uninitialized mutable binding in the global lexical
- * environment. Throw a SyntaxError if a binding with the same name already
- * exists on that environment, or if a var binding with the same name
- * exists on the global.
- *
- * Category: Variables and scopes
- * Type: Creating and deleting bindings
- * Operands: uint32_t nameIndex
- * Stack: =>
- */ \
- MACRO(DefLet, def_let, NULL, 5, 0, 0, JOF_ATOM) \
- /*
- * Like `DefLet`, but create an uninitialized constant binding.
- *
- * Category: Variables and scopes
- * Type: Creating and deleting bindings
- * Operands: uint32_t nameIndex
- * Stack: =>
- */ \
- MACRO(DefConst, def_const, NULL, 5, 0, 0, JOF_ATOM) \
- /*
- * Check for conflicting bindings before `JSOp::Def{Var,Let,Const,Fun}` in
- * global or sloppy eval scripts.
- *
- * Implements: [GlobalDeclarationInstantiation][1] steps 5, 6, 10 and 12,
- * and [EvalDeclarationInstantiation][2] steps 5 and 8.
+ * 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:
+ * Operands: uint32_t lastFun
* Stack: =>
*/ \
- MACRO(CheckGlobalOrEvalDecl, check_global_or_eval_decl, NULL, 1, 0, 0, JOF_BYTE) \
+ MACRO(GlobalOrEvalDeclInstantiation, global_or_eval_decl_instantiation, NULL, 5, 0, 0, JOF_GCTHING) \
/*
* 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.
@@ -3674,16 +3614,20 @@
// 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) \
+ MACRO(235) \
+ MACRO(236) \
+ MACRO(237) \
+ MACRO(238) \
MACRO(239) \
MACRO(240) \
MACRO(241) \
MACRO(242) \
MACRO(243) \
MACRO(244) \
MACRO(245) \
MACRO(246) \
Files
/js/src/frontend/BytecodeEmitter.cpp
/js/src/frontend/BytecodeEmitter.h
/js/src/frontend/EmitterScope.cpp
/js/src/frontend/FunctionEmitter.cpp
/js/src/vm/BytecodeUtil.cpp
/js/src/vm/Opcodes.h
Changesets
Diffs
/js/src/frontend/BytecodeEmitter.cpp
/js/src/frontend/BytecodeEmitter.h
/js/src/frontend/EmitterScope.cpp
/js/src/frontend/FunctionEmitter.cpp
/js/src/vm/BytecodeUtil.cpp
/js/src/vm/Opcodes.h