diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/frontend/FunctionEmitter.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
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.cpp | 1017 |
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; +} |