summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/FunctionEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend/FunctionEmitter.cpp')
-rw-r--r--js/src/frontend/FunctionEmitter.cpp1017
1 files changed, 1017 insertions, 0 deletions
diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp
new file mode 100644
index 0000000000..fbff6cf1c5
--- /dev/null
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -0,0 +1,1017 @@
+/* -*- 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 "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/SharedContext.h" // SharedContext
+#include "vm/ModuleBuilder.h" // ModuleBuilder
+#include "vm/Opcodes.h" // JSOp
+#include "vm/Scope.h" // BindingKind
+
+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_->wasEmittedByEnclosingScript());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(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_->wasEmittedByEnclosingScript());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(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_->wasEmittedByEnclosingScript());
+
+ // [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_->wasEmittedByEnclosingScript());
+ MOZ_ASSERT(funbox_->isAsmJSModule());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(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]
+
+ 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).
+ if (!bce_->emitGCIndexOp(JSOp::Lambda, 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_->fc, 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.
+ (void)index;
+
+ 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 (!functionEmitterScope_->enterFunction(bce_, funbox_)) {
+ return false;
+ }
+
+ if (!emitInitializeClosedOverArgumentBindings()) {
+ // [stack]
+ return false;
+ }
+
+ 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.
+ bce_->switchToMain();
+ }
+
+ if (!bce_->emitInitializeFunctionSpecialNames()) {
+ // [stack]
+ return false;
+ }
+
+ if (!funbox_->hasParameterExprs) {
+ bce_->switchToMain();
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ if (funbox_->hasParameterExprs || funbox_->hasDestructuringArgs) {
+ if (!asyncEmitter_->prepareForParamsWithExpressionOrDestructuring()) {
+ return false;
+ }
+ } else {
+ if (!asyncEmitter_->prepareForParamsWithoutExpressionOrDestructuring()) {
+ return false;
+ }
+ }
+ }
+
+ if (funbox_->isClassConstructor()) {
+ if (!funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitInitializeInstanceMembers(false)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Parameters;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::emitInitializeClosedOverArgumentBindings() {
+ // Initialize CallObject slots for closed-over arguments. If the function has
+ // parameter expressions, these are lexical bindings and we initialize the
+ // slots to the magic TDZ value. If the function doesn't have parameter
+ // expressions, we copy the frame's arguments.
+
+ MOZ_ASSERT(bce_->inPrologue());
+
+ auto* bindings = funbox_->functionScopeBindings();
+ if (!bindings) {
+ return true;
+ }
+
+ const bool hasParameterExprs = funbox_->hasParameterExprs;
+
+ bool pushedUninitialized = false;
+ for (ParserPositionalFormalParameterIter fi(*bindings, hasParameterExprs); fi;
+ fi++) {
+ if (!fi.closedOver()) {
+ continue;
+ }
+
+ if (hasParameterExprs) {
+ NameLocation nameLoc = bce_->lookupName(fi.name());
+ if (!pushedUninitialized) {
+ if (!bce_->emit1(JSOp::Uninitialized)) {
+ // [stack] UNINITIALIZED
+ return false;
+ }
+ pushedUninitialized = true;
+ }
+ if (!bce_->emitEnvCoordOp(JSOp::InitAliasedLexical,
+ nameLoc.environmentCoordinate())) {
+ // [stack] UNINITIALIZED
+ return false;
+ }
+ } else {
+ NameOpEmitter noe(bce_, fi.name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitArgOp(JSOp::GetFrameArg, fi.argumentSlot())) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+ if (pushedUninitialized) {
+ MOZ_ASSERT(hasParameterExprs);
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ 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;
+ }
+ }
+
+#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; }
+ //
+ for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi;
+ bi++) {
+ auto name = bi.name();
+
+ // There may not be a var binding of the same name.
+ if (!bce_->locationOfNameBoundInScope(name,
+ extraBodyVarEmitterScope_.ptr())) {
+ continue;
+ }
+
+ // The '.this', '.newTarget', and '.generator' function special binding
+ // should never appear in the extra var scope. 'arguments', however, may.
+ MOZ_ASSERT(name != TaggedParserAtomIndex::WellKnown::dotThis() &&
+ name != TaggedParserAtomIndex::WellKnown::dotNewTarget() &&
+ name != TaggedParserAtomIndex::WellKnown::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 (bodyEnd_) {
+ if (!bce_->updateSourceCoordNotes(*bodyEnd_)) {
+ return false;
+ }
+ }
+
+ if (funbox_->needsFinalYield()) {
+ // If we fall off the end of a generator or async function, we
+ // do a final yield with an |undefined| payload. We put all
+ // the code to do this in one place, both to reduce bytecode
+ // size and to prevent any OOM or debugger exception that occurs
+ // at this point from being caught inside the function.
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ // Return statements in the body of the function will jump here
+ // with the return payload in rval.
+ if (!bce_->emitJumpTargetAndPatch(bce_->finalYields)) {
+ // [stack]
+ return false;
+ }
+
+ 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::GetRval)) {
+ // [stack] RESULT RVAL
+ return false;
+ }
+
+ if (!bce_->emitFinishIteratorResult(true)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ } else if (funbox_->needsPromiseResult()) {
+ // Emit final yield bytecode for async functions, for example:
+ // async function deferred() { ... }
+ if (!bce_->emit1(JSOp::GetRval)) {
+ // [stack] RVAL
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] RVAL GEN
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::AsyncResolve,
+ uint8_t(AsyncFunctionResolveKind::Fulfill))) {
+ // [stack] PROMISE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ // Emit the final yield.
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ return false;
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ // Emit the reject catch block.
+ if (!asyncEmitter_->emitEndFunction()) {
+ 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;
+ }
+ }
+ }
+
+ // Execute |CheckReturn| right before exiting the class constructor.
+ if (funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitJumpTargetAndPatch(bce_->endOfDerivedClassConstructorBody)) {
+ return false;
+ }
+
+ 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();
+
+ // 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;
+ }
+ }
+
+ // Emit JSOp::RetRval except for sync arrow function with expression body
+ // which always ends with JSOp::Return. Other parts of the codebase depend
+ // on these opcodes being the last opcode.
+ // See JSScript::lastPC and BaselineCompiler::emitBody.
+ if (!funbox_->hasExprBody() || funbox_->isAsync()) {
+ 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(TaggedParserAtomIndex 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(TaggedParserAtomIndex 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(TaggedParserAtomIndex 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(TaggedParserAtomIndex 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;
+}