/* -*- 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;
}
