summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/FunctionEmitter.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/frontend/FunctionEmitter.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/frontend/FunctionEmitter.cpp')
-rw-r--r--js/src/frontend/FunctionEmitter.cpp954
1 files changed, 954 insertions, 0 deletions
diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp
new file mode 100644
index 0000000000..66d78fa721
--- /dev/null
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -0,0 +1,954 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/AsyncEmitter.h" // AsyncEmitter
+#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
+#include "frontend/PropOpEmitter.h" // PropOpEmitter
+#include "frontend/SharedContext.h" // SharedContext
+#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind
+#include "vm/JSScript.h" // JSScript
+#include "vm/ModuleBuilder.h" // ModuleBuilder
+#include "vm/Opcodes.h" // JSOp
+#include "vm/Scope.h" // BindingKind
+#include "wasm/AsmJS.h" // IsAsmJSModule
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+using mozilla::Some;
+
+FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox,
+ FunctionSyntaxKind syntaxKind,
+ IsHoisted isHoisted)
+ : bce_(bce),
+ funbox_(funbox),
+ name_(funbox_->explicitName()),
+ syntaxKind_(syntaxKind),
+ isHoisted_(isHoisted) {}
+
+bool FunctionEmitter::prepareForNonLazy() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ MOZ_ASSERT(funbox_->isInterpreted());
+ MOZ_ASSERT(funbox_->emitBytecode);
+ MOZ_ASSERT(!funbox_->wasEmitted());
+
+ // [stack]
+
+ funbox_->setWasEmitted(true);
+
+#ifdef DEBUG
+ state_ = State::NonLazy;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitNonLazyEnd() {
+ MOZ_ASSERT(state_ == State::NonLazy);
+
+ // [stack]
+
+ if (!emitFunction()) {
+ // [stack] FUN?
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitLazy() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ MOZ_ASSERT(funbox_->isInterpreted());
+ MOZ_ASSERT(!funbox_->emitBytecode);
+ MOZ_ASSERT(!funbox_->wasEmitted());
+
+ // [stack]
+
+ funbox_->setWasEmitted(true);
+
+ // Prepare to update the inner lazy script now that it's parent is fully
+ // compiled. These updates will be applied in UpdateEmittedInnerFunctions().
+ funbox_->setEnclosingScopeForInnerLazyFunction(bce_->innermostScopeIndex());
+
+ if (!emitFunction()) {
+ // [stack] FUN?
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitAgain() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(funbox_->wasEmitted());
+
+ // [stack]
+
+ // Annex B block-scoped functions are hoisted like any other assignment
+ // that assigns the function to the outer 'var' binding.
+ if (!funbox_->isAnnexB) {
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+ }
+
+ // Get the location of the 'var' binding in the body scope. The
+ // name must be found, else there is a bug in the Annex B handling
+ // in Parser.
+ //
+ // In sloppy eval contexts, this location is dynamic.
+ Maybe<NameLocation> lhsLoc =
+ bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope);
+
+ // If there are parameter expressions, the var name could be a
+ // parameter.
+ if (!lhsLoc && bce_->sc->isFunctionBox() &&
+ bce_->sc->asFunctionBox()->functionHasExtraBodyVarScope()) {
+ lhsLoc = bce_->locationOfNameBoundInScope(
+ name_, bce_->varEmitterScope->enclosingInFrame());
+ }
+
+ if (!lhsLoc) {
+ lhsLoc = Some(NameLocation::DynamicAnnexBVar());
+ } else {
+ MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var ||
+ lhsLoc->bindingKind() == BindingKind::FormalParameter ||
+ (lhsLoc->bindingKind() == BindingKind::Let &&
+ bce_->sc->asFunctionBox()->hasParameterExprs));
+ }
+
+ NameOpEmitter noe(bce_, name_, *lhsLoc,
+ NameOpEmitter::Kind::SimpleAssignment);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGetName(name_)) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitAsmJSModule() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ MOZ_ASSERT(!funbox_->wasEmitted());
+ MOZ_ASSERT(funbox_->isAsmJSModule());
+
+ // [stack]
+
+ funbox_->setWasEmitted(true);
+
+ if (!emitFunction()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitFunction() {
+ // Make the function object a literal in the outer script's pool.
+ GCThingIndex index;
+ if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) {
+ return false;
+ }
+
+ // [stack]
+
+ if (isHoisted_ == IsHoisted::No) {
+ return emitNonHoisted(index);
+ // [stack] FUN?
+ }
+
+ bool topLevelFunction;
+ if (bce_->sc->isFunctionBox() ||
+ (bce_->sc->isEvalContext() && bce_->sc->strict())) {
+ // No nested functions inside other functions are top-level.
+ topLevelFunction = false;
+ } else {
+ // In sloppy eval scripts, top-level functions are accessed dynamically.
+ // In global and module scripts, top-level functions are those bound in
+ // the var scope.
+ NameLocation loc = bce_->lookupName(name_);
+ topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic ||
+ loc.bindingKind() == BindingKind::Var;
+ }
+
+ if (topLevelFunction) {
+ return emitTopLevelFunction(index);
+ // [stack]
+ }
+
+ return emitHoisted(index);
+ // [stack]
+}
+
+bool FunctionEmitter::emitNonHoisted(GCThingIndex index) {
+ // Non-hoisted functions simply emit their respective op.
+
+ // [stack]
+
+ // JSOp::LambdaArrow is always preceded by a opcode that pushes new.target.
+ // See below.
+ MOZ_ASSERT(funbox_->isArrow() == (syntaxKind_ == FunctionSyntaxKind::Arrow));
+
+ if (funbox_->isArrow()) {
+ if (!emitNewTargetForArrow()) {
+ // [stack] NEW.TARGET/NULL
+ return false;
+ }
+ }
+
+ if (syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor) {
+ // [stack] PROTO
+ if (!bce_->emitGCIndexOp(JSOp::FunWithProto, index)) {
+ // [stack] FUN
+ return false;
+ }
+ return true;
+ }
+
+ // This is a FunctionExpression, ArrowFunctionExpression, or class
+ // constructor. Emit the single instruction (without location info).
+ JSOp op = syntaxKind_ == FunctionSyntaxKind::Arrow ? JSOp::LambdaArrow
+ : JSOp::Lambda;
+ if (!bce_->emitGCIndexOp(op, index)) {
+ // [stack] FUN
+ return false;
+ }
+
+ return true;
+}
+
+bool FunctionEmitter::emitHoisted(GCThingIndex index) {
+ MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement);
+
+ // [stack]
+
+ // For functions nested within functions and blocks, make a lambda and
+ // initialize the binding name of the function in the current scope.
+
+ NameOpEmitter noe(bce_, name_, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool FunctionEmitter::emitTopLevelFunction(GCThingIndex index) {
+ // [stack]
+
+ if (bce_->sc->isModuleContext()) {
+ // For modules, we record the function and instantiate the binding
+ // during ModuleInstantiate(), before the script is run.
+ 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());
+
+ // 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)) {
+ // [stack] NEW.TARGET
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOp::Null)) {
+ // [stack] NULL
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FunctionScriptEmitter::prepareForParameters() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->inPrologue());
+
+ // [stack]
+
+ if (paramStart_) {
+ bce_->setScriptStartOffsetIfUnset(*paramStart_);
+ }
+
+ // The ordering of these EmitterScopes is important. The named lambda
+ // scope needs to enclose the function scope needs to enclose the extra
+ // var scope.
+
+ if (funbox_->namedLambdaBindings()) {
+ namedLambdaEmitterScope_.emplace(bce_);
+ if (!namedLambdaEmitterScope_->enterNamedLambda(bce_, funbox_)) {
+ return false;
+ }
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ asyncEmitter_.emplace(bce_);
+ }
+
+ if (bodyEnd_) {
+ bce_->setFunctionBodyEndPos(*bodyEnd_);
+ }
+
+ if (paramStart_) {
+ if (!bce_->updateLineNumberNotes(*paramStart_)) {
+ return false;
+ }
+ }
+
+ tdzCache_.emplace(bce_);
+ functionEmitterScope_.emplace(bce_);
+
+ if (funbox_->hasParameterExprs) {
+ // There's parameter exprs, emit them in the main section.
+ //
+ // One caveat is that Debugger considers ops in the prologue to be
+ // unreachable (i.e. cannot set a breakpoint on it). If there are no
+ // parameter exprs, any unobservable environment ops (like pushing the
+ // call object, setting '.this', etc) need to go in the prologue, else it
+ // messes up breakpoint tests.
+ if (!bce_->switchToMain()) {
+ return false;
+ }
+ }
+
+ if (!functionEmitterScope_->enterFunction(bce_, funbox_)) {
+ return false;
+ }
+
+ if (!bce_->emitInitializeFunctionSpecialNames()) {
+ // [stack]
+ return false;
+ }
+
+ if (!funbox_->hasParameterExprs) {
+ if (!bce_->switchToMain()) {
+ return false;
+ }
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ if (funbox_->hasParameterExprs) {
+ if (!asyncEmitter_->prepareForParamsWithExpression()) {
+ return false;
+ }
+ } else {
+ if (!asyncEmitter_->prepareForParamsWithoutExpression()) {
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Parameters;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::prepareForBody() {
+ MOZ_ASSERT(state_ == State::Parameters);
+
+ // [stack]
+
+ if (funbox_->needsPromiseResult()) {
+ if (!asyncEmitter_->emitParamsEpilogue()) {
+ return false;
+ }
+ }
+
+ if (!emitExtraBodyVarScope()) {
+ // [stack]
+ return false;
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ if (!asyncEmitter_->prepareForBody()) {
+ return false;
+ }
+ }
+
+ if (funbox_->isClassConstructor()) {
+ if (!funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitInitializeInstanceMembers()) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::emitExtraBodyVarScope() {
+ // [stack]
+
+ if (!funbox_->functionHasExtraBodyVarScope()) {
+ return true;
+ }
+
+ extraBodyVarEmitterScope_.emplace(bce_);
+ if (!extraBodyVarEmitterScope_->enterFunctionExtraBodyVar(bce_, funbox_)) {
+ return false;
+ }
+
+ if (!funbox_->extraVarScopeBindings() || !funbox_->functionScopeBindings()) {
+ return true;
+ }
+
+ // After emitting expressions for all parameters, copy over any formal
+ // parameters which have been redeclared as vars. For example, in the
+ // following, the var y in the body scope is 42:
+ //
+ // function f(x, y = 42) { var y; }
+ //
+ const ParserAtom* name = nullptr;
+ for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi;
+ bi++) {
+ name = bce_->compilationState.getParserAtomAt(bce_->cx, bi.name());
+
+ // There may not be a var binding of the same name.
+ if (!bce_->locationOfNameBoundInScope(name,
+ extraBodyVarEmitterScope_.ptr())) {
+ continue;
+ }
+
+ // The '.this' and '.generator' function special
+ // bindings should never appear in the extra var
+ // scope. 'arguments', however, may.
+ MOZ_ASSERT(name != bce_->cx->parserNames().dotThis &&
+ name != bce_->cx->parserNames().dotGenerator);
+
+ NameOpEmitter noe(bce_, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ NameLocation paramLoc =
+ *bce_->locationOfNameBoundInScope(name, functionEmitterScope_.ptr());
+ if (!bce_->emitGetNameAtLocation(name, paramLoc)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FunctionScriptEmitter::emitEndBody() {
+ MOZ_ASSERT(state_ == State::Body);
+ // [stack]
+
+ if (funbox_->needsFinalYield()) {
+ // If we fall off the end of a generator, do a final yield.
+ if (funbox_->needsIteratorResult()) {
+ MOZ_ASSERT(!funbox_->needsPromiseResult());
+ // Emit final yield bytecode for generators, for example:
+ // function gen * () { ... }
+ if (!bce_->emitPrepareIteratorResult()) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] RESULT? UNDEF
+ return false;
+ }
+
+ if (!bce_->emitFinishIteratorResult(true)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ // No need to check for finally blocks, etc as in EmitReturn.
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ // [stack]
+ return false;
+ }
+ } else if (funbox_->needsPromiseResult()) {
+ // Emit final yield bytecode for async functions, for example:
+ // async function deferred() { ... }
+ if (!asyncEmitter_->emitEnd()) {
+ return false;
+ }
+ } else {
+ // Emit final yield bytecode for async generators, for example:
+ // async function asyncgen * () { ... }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] RESULT? UNDEF
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ // No need to check for finally blocks, etc as in EmitReturn.
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ // [stack]
+ return false;
+ }
+ }
+ } else {
+ // Non-generator functions just return |undefined|. The
+ // JSOp::RetRval emitted below will do that, except if the
+ // script has a finally block: there can be a non-undefined
+ // value in the return value slot. Make sure the return value
+ // is |undefined|.
+ if (bce_->hasTryFinally) {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+ if (funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitCheckDerivedClassConstructorReturn()) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (extraBodyVarEmitterScope_) {
+ if (!extraBodyVarEmitterScope_->leave(bce_)) {
+ return false;
+ }
+
+ extraBodyVarEmitterScope_.reset();
+ }
+
+ if (!functionEmitterScope_->leave(bce_)) {
+ return false;
+ }
+ functionEmitterScope_.reset();
+ tdzCache_.reset();
+
+ if (bodyEnd_) {
+ if (!bce_->updateSourceCoordNotes(*bodyEnd_)) {
+ return false;
+ }
+ }
+
+ // We only want to mark the end of a function as a breakable position if
+ // there is token there that the user can easily associate with the function
+ // as a whole. Since arrow function single-expression bodies have no closing
+ // curly bracket, we do not place a breakpoint at their end position.
+ if (!funbox_->hasExprBody()) {
+ if (!bce_->markSimpleBreakpoint()) {
+ return false;
+ }
+ }
+
+ // Always end the script with a JSOp::RetRval. Some other parts of the
+ // codebase depend on this opcode,
+ // e.g. InterpreterRegs::setToEndOfScript.
+ if (!bce_->emitReturnRval()) {
+ // [stack]
+ return false;
+ }
+
+ if (namedLambdaEmitterScope_) {
+ if (!namedLambdaEmitterScope_->leave(bce_)) {
+ return false;
+ }
+ namedLambdaEmitterScope_.reset();
+ }
+
+#ifdef DEBUG
+ state_ = State::EndBody;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::intoStencil() {
+ MOZ_ASSERT(state_ == State::EndBody);
+
+ if (!bce_->intoScriptStencil(funbox_->index())) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+
+ return true;
+}
+
+FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce,
+ FunctionBox* funbox)
+ : bce_(bce),
+ funbox_(funbox),
+ functionEmitterScope_(bce_->innermostEmitterScope()) {}
+
+bool FunctionParamsEmitter::emitSimple(const ParserAtom* paramName) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (funbox_->hasParameterExprs) {
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ argSlot_++;
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDefault() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!prepareForInitializer()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Default;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDefaultEnd(const ParserAtom* paramName) {
+ MOZ_ASSERT(state_ == State::Default);
+
+ // [stack] DEFAULT
+
+ if (!emitInitializerEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuring() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Destructuring;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringEnd() {
+ MOZ_ASSERT(state_ == State::Destructuring);
+
+ // [stack] ARG
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!prepareForInitializer()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringDefaultInitializer;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringDefault() {
+ MOZ_ASSERT(state_ == State::DestructuringDefaultInitializer);
+
+ // [stack] DEFAULT
+
+ if (!emitInitializerEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringDefault;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringDefaultEnd() {
+ MOZ_ASSERT(state_ == State::DestructuringDefault);
+
+ // [stack] ARG/DEFAULT
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitRest(const ParserAtom* paramName) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!emitRestArray()) {
+ // [stack] REST
+ return false;
+ }
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringRest() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!emitRestArray()) {
+ // [stack] REST
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringRest;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringRestEnd() {
+ MOZ_ASSERT(state_ == State::DestructuringRest);
+
+ // [stack] REST
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForInitializer() {
+ // [stack]
+
+ // If we have an initializer, emit the initializer and assign it
+ // to the argument slot. TDZ is taken care of afterwards.
+ MOZ_ASSERT(funbox_->hasParameterExprs);
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+ default_.emplace(bce_);
+ if (!default_->prepareForDefault()) {
+ // [stack]
+ return false;
+ }
+ return true;
+}
+
+bool FunctionParamsEmitter::emitInitializerEnd() {
+ // [stack] DEFAULT
+
+ if (!default_->emitEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+ default_.reset();
+ return true;
+}
+
+bool FunctionParamsEmitter::emitRestArray() {
+ // [stack]
+
+ if (!bce_->emit1(JSOp::Rest)) {
+ // [stack] REST
+ return false;
+ }
+ return true;
+}
+
+bool FunctionParamsEmitter::emitAssignment(const ParserAtom* paramName) {
+ // [stack] ARG
+
+ NameLocation paramLoc =
+ *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_);
+
+ // RHS is already pushed in the caller side.
+ // Make sure prepareForRhs doesn't touch stack.
+ MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot ||
+ paramLoc.kind() == NameLocation::Kind::FrameSlot ||
+ paramLoc.kind() == NameLocation::Kind::EnvironmentCoordinate);
+
+ NameOpEmitter noe(bce_, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}