From e6335ded8914e1a6cee139db872d71b2e0b746a9 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 26 Apr 2023 19:24:13 +0200 Subject: [PATCH] Issue #2142 - Add FunctionEmitter, FunctionScriptEmitter, and FunctionParamsEmitter with current methods Based-on: m-c 1473796/3, 1473796/4 --- js/src/frontend/BytecodeEmitter.cpp | 742 +++++------------ js/src/frontend/BytecodeEmitter.h | 20 +- js/src/frontend/DestructuringFlavor.h | 31 + js/src/frontend/FunctionEmitter.cpp | 1083 +++++++++++++++++++++++++ js/src/frontend/FunctionEmitter.h | 451 ++++++++++ js/src/moz.build | 1 + 6 files changed, 1755 insertions(+), 573 deletions(-) create mode 100644 js/src/frontend/DestructuringFlavor.h create mode 100644 js/src/frontend/FunctionEmitter.cpp create mode 100644 js/src/frontend/FunctionEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b76d6746a0..72ba7c6ed0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -34,6 +34,7 @@ #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ForOfLoopControl.h" +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter #include "frontend/IfEmitter.h" #include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/NameOpEmitter.h" @@ -2392,133 +2393,46 @@ BytecodeEmitter::emitScript(ParseNode* body) return true; } -bool BytecodeEmitter::emitInitializeInstanceFields() -{ - MOZ_ASSERT(fieldInitializers_.valid); - size_t numFields = fieldInitializers_.numFieldInitializers; - - if (numFields == 0) { - return true; - } - - if (!emitGetName(cx->names().dotInitializers)) { - // [stack] ARRAY - return false; - } - - for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { - if (fieldIndex < numFields - 1) { - // We DUP to keep the array around (it is consumed in the bytecode below) - // for next iterations of this loop, except for the last iteration, which - // avoids an extra POP at the end of the loop. - if (!emit1(JSOP_DUP)) { - // [stack] ARRAY ARRAY - return false; - } - } - - if (!emitNumberOp(fieldIndex)) { - // [stack] ARRAY? ARRAY INDEX - return false; - } - - // Don't use CALLELEM here, because the receiver of the call != the receiver - // of this getelem. (Specifically, the call receiver is `this`, and the - // receiver of this getelem is `.initializers`) - if (!emit1(JSOP_GETELEM)) { - // [stack] ARRAY? FUNC - return false; - } - - // This is guaranteed to run after super(), so we don't need TDZ checks. - if (!emitGetName(cx->names().dotThis)) { - // [stack] ARRAY? FUNC THIS - return false; - } - - if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) { - // [stack] ARRAY? RVAL - return false; - } - - if (!emit1(JSOP_POP)) { - // [stack] ARRAY? - return false; - } - } - - return true; -} - bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { - ParseNode* body = funNode->body(); + ListNode* paramsBody = &funNode->body()->as(); FunctionBox* funbox = sc->asFunctionBox(); - setScriptStartOffsetIfUnset(body->pn_pos.begin); + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); - // The ordering of these EmitterScopes is important. The named lambda - // scope needs to enclose the function scope needs to enclose the extra - // var scope. + // [stack] - Maybe namedLambdaEmitterScope; - if (funbox->namedLambdaBindings()) { - namedLambdaEmitterScope.emplace(this); - if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) - return false; + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; } - /* - * Emit a prologue for run-once scripts which will deoptimize JIT code - * if the script ends up running multiple times via foo.caller related - * shenanigans. - * - * Also mark the script so that initializers created within it may be - * given more precise types. - */ - if (isRunOnceLambda()) { - script->setTreatAsRunOnce(); - MOZ_ASSERT(!script->hasRunOnce()); - - switchToPrologue(); - if (!emit1(JSOP_RUNONCE)) - return false; - switchToMain(); + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; } - setFunctionBodyEndPos(body->pn_pos.end); - if (!emitTree(body)) + if (!fse.prepareForBody()) { + // [stack] return false; - - if (!updateSourceCoordNotes(body->pn_pos.end)) - 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 (!emit1(JSOP_RETRVAL)) - return false; - - if (namedLambdaEmitterScope) { - if (!namedLambdaEmitterScope->leave(this)) - return false; - namedLambdaEmitterScope.reset(); } - if (!JSScript::fullyInitFromEmitter(cx, script, this)) + if (!emitTree(paramsBody->last())) { + // [stack] return false; - - // URL and source map information must be set before firing - // Debugger::onNewScript. Only top-level functions need this, as compiling - // the outer scripts of nested functions already processed the source. - if (emitterMode != LazyFunction && !parent) { - if (!maybeSetDisplayURL() || !maybeSetSourceMap()) - return false; - - tellDebuggerAboutCompiledScript(cx); } + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (!fse.initScript()) + return false; + return true; } @@ -5683,8 +5597,11 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) { FunctionBox* funbox = funNode->funbox(); RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->explicitName()); - MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted()); /* * Set the |wasEmitted| flag in the funbox once the function has been @@ -5692,43 +5609,9 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) * function will be seen by emitFunction in two places. */ if (funbox->wasEmitted) { - // Annex B block-scoped functions are hoisted like any other - // block-scoped function to the top of their scope. When their - // definitions are seen for the second time, we need to emit the - // assignment that assigns the function to the outer 'var' binding. - if (funbox->isAnnexB) { - // 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 lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); - - // If there are parameter expressions, the var name could be a - // parameter. - if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) - lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); - - if (!lhsLoc) { - lhsLoc = Some(NameLocation::DynamicAnnexBVar()); - } else { - MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || - lhsLoc->bindingKind() == BindingKind::FormalParameter || - (lhsLoc->bindingKind() == BindingKind::Let && - sc->asFunctionBox()->hasParameterExprs)); - } - - NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); - if (!noe.prepareForRhs()) { - return false; - } - if (!emitGetName(name)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; + if (!fe.emitAgain()) { + // [stack] + return false; } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); @@ -5736,172 +5619,62 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) return true; } - funbox->wasEmitted = true; - /* - * Mark as singletons any function which will only be executed once, or - * which is inner to a lambda we only expect to run once. In the latter - * case, if the lambda runs multiple times then CloneFunctionObject will - * make a deep clone of its contents. - */ if (fun->isInterpreted()) { - bool singleton = checkRunOnceContext(); - if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) - return false; - - SharedContext* outersc = sc; if (fun->isInterpretedLazy()) { - // We need to update the static scope chain regardless of whether - // the LazyScript has already been initialized, due to the case - // where we previously successfully compiled an inner function's - // lazy script but failed to compile the outer script after the - // fact. If we attempt to compile the outer script again, the - // static scope chain will be newly allocated and will mismatch - // the previously compiled LazyScript's. - ScriptSourceObject* source = &script->sourceObject()->as(); - fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source); - if (emittingRunOnceLambda) - fun->lazyScript()->setTreatAsRunOnce(); - } else { - MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); - - // Inherit most things (principals, version, etc) from the - // parent. Use default values for the rest. - Rooted parent(cx, script); - MOZ_ASSERT(parent->getVersion() == parser->options().version); - MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); - const TransitiveCompileOptions& transitiveOptions = parser->options(); - CompileOptions options(cx, transitiveOptions); - - Rooted sourceObject(cx, script->sourceObject()); - Rooted script(cx, JSScript::Create(cx, options, sourceObject, - funbox->bufStart, funbox->bufEnd, - funbox->toStringStart, - funbox->toStringEnd)); - if (!script) + if (!fe.emitLazy()) { + // [stack] FUN? return false; - - BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, - funNode->pn_pos, emitterMode); - if (!bce2.init()) - return false; - - /* We measured the max scope depth when we parsed the function. */ - if (!bce2.emitFunctionScript(funNode)) - return false; - - if (funbox->isLikelyConstructorWrapper()) - script->setLikelyConstructorWrapper(); - } - - if (outersc->isFunctionBox()) - outersc->asFunctionBox()->setHasInnerFunctions(); - } else { - MOZ_ASSERT(IsAsmJSModule(fun)); - } - - /* Make the function object a literal in the outer script's pool. */ - unsigned index = objectList.add(funNode->funbox()); - - /* Non-hoisted functions simply emit their respective op. */ - if (!funNode->functionIsHoisted()) { - /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ - MOZ_ASSERT(fun->isArrow() == (funNode->syntaxKind() == FunctionSyntaxKind::Arrow)); - if (funbox->isAsync()) { - MOZ_ASSERT(!needsProto); - return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), - fun->isStarGenerator()); - } - - if (fun->isArrow()) { - if (sc->allowNewTarget()) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else { - if (!emit1(JSOP_NULL)) - return false; } + + return true; } - if (needsProto) { - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::DerivedClassConstructor); - return emitIndex32(JSOP_FUNWITHPROTO, index); - } - - // This is a FunctionExpression, ArrowFunctionExpression, or class - // constructor. Emit the single instruction (without location info). - JSOp op = funNode->syntaxKind() == FunctionSyntaxKind::Arrow - ? JSOP_LAMBDA_ARROW - : JSOP_LAMBDA; - return emitIndex32(op, index); - } - - MOZ_ASSERT(!needsProto); - - bool topLevelFunction; - if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { - // No nested functions inside other functions are top-level. - topLevelFunction = false; - } else { - // In sloppy eval scripts, top-level functions in are accessed - // dynamically. In global and module scripts, top-level functions are - // those bound in the var scope. - NameLocation loc = lookupName(name); - topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || - loc.bindingKind() == BindingKind::Var; - } - - if (topLevelFunction) { - if (sc->isModuleContext()) { - // For modules, we record the function and instantiate the binding - // during ModuleInstantiate(), before the script is run. - - RootedModuleObject module(cx, sc->asModuleContext()->module()); - if (!module->noteFunctionDeclaration(cx, name, fun)) - return false; - } else { - MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::Statement); - switchToPrologue(); - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), - fun->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndex32(JSOP_LAMBDA, index)) - return false; - } - if (!emit1(JSOP_DEFFUN)) - return false; - if (!updateSourceCoordNotes(funNode->pn_pos.begin)) - return false; - switchToMain(); - } - } else { - // For functions nested within functions and blocks, make a lambda and - // initialize the binding name of the function in the current scope. - - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { + if (!fe.prepareForNonLazy()) { + // [stack] return false; } - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false, funbox->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndexOp(JSOP_LAMBDA, index)) { - return false; - } + + // Inherit most things (principals, version, etc) from the + // parent. Use default values for the rest. + Rooted parent(cx, script); + MOZ_ASSERT(parent->getVersion() == parser->options().version); + MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); + const TransitiveCompileOptions& transitiveOptions = parser->options(); + CompileOptions options(cx, transitiveOptions); + + Rooted sourceObject(cx, script->sourceObject()); + Rooted script(cx, JSScript::Create(cx, options, sourceObject, + funbox->bufStart, funbox->bufEnd, + funbox->toStringStart, + funbox->toStringEnd)); + if (!script) + return false; + + BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, + funNode->pn_pos, emitterMode); + if (!bce2.init()) + return false; + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) + return false; + + if (funbox->isLikelyConstructorWrapper()) { + script->setLikelyConstructorWrapper(); } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? return false; + } + + return true; + } + + if (!fe.emitAsmJSModule()) { + // [stack] + return false; } return true; @@ -8453,128 +8226,24 @@ BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) return emit1(op); } -bool -BytecodeEmitter::emitFunctionFormalParametersAndBody(ListNode* paramsBody) -{ - MOZ_ASSERT(paramsBody->isKind(PNK_PARAMSBODY)); - - ParseNode* funBody = paramsBody->last(); - FunctionBox* funbox = sc->asFunctionBox(); - - TDZCheckCache tdzCache(this); - - if (funbox->hasParameterExprs) { - EmitterScope funEmitterScope(this); - if (!funEmitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - { - Maybe extraVarEmitterScope; - - if (funbox->hasExtraBodyVarScope()) { - extraVarEmitterScope.emplace(this); - if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) - return false; - - // 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; } - // - RootedAtom name(cx); - if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { - for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { - name = bi.name(); - - // There may not be a var binding of the same name. - if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) - continue; - - // The '.this' and '.generator' function special - // bindings should never appear in the extra var - // scope. 'arguments', however, may. - MOZ_ASSERT(name != cx->names().dotThis && - name != cx->names().dotGenerator); - - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - - NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); - if (!emitGetNameAtLocation(name, paramLoc)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; - } - } - } - - if (!emitFunctionBody(funBody)) - return false; - - if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) - return false; - } - - return funEmitterScope.leave(this); - } - - // No parameter expressions. Enter the function body scope and emit - // everything. - // - // 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. - EmitterScope emitterScope(this); - - switchToPrologue(); - if (!emitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - switchToMain(); - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - if (!emitFunctionBody(funBody)) - return false; - - return emitterScope.leave(this); -} - bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); - EmitterScope* funScope = innermostEmitterScope(); - - bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); - uint16_t argSlot = 0; - for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next, argSlot++) { + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; if (arg->isKind(PNK_ASSIGN)) { bindingElement = arg->as().left(); initializer = arg->as().right(); } + bool hasInitializer = !!initializer; + bool isRest = hasRest && arg->pn_next == funBody; + bool isDestructuring = !bindingElement->isKind(PNK_NAME); // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(PNK_NAME) || @@ -8582,103 +8251,122 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) bindingElement->isKind(PNK_ARRAYCOMP) || bindingElement->isKind(PNK_OBJECT)); - // The rest parameter doesn't have an initializer. - bool isRest = hasRest && arg->pn_next == funBody; - MOZ_ASSERT_IF(isRest, !initializer); + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] - bool isDestructuring = !bindingElement->isKind(PNK_NAME); + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; - // ES 14.1.19 says if BindingElement contains an expression in the - // production FormalParameter : BindingElement, it is evaluated in a - // new var environment. This is needed to prevent vars from escaping - // direct eval in parameter expressions. - Maybe paramExprVarScope; - if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { - paramExprVarScope.emplace(this); - if (!paramExprVarScope->enterParameterExpressionVar(this)) - return false; - } - - // First push the RHS if there is a default expression or if it is - // rest. - - if (initializer) { - // If we have an initializer, emit the initializer and assign it - // to the argument slot. TDZ is taken care of afterwards. - MOZ_ASSERT(hasParameterExprs); - IfEmitter ifUndefined(this); - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; - if (!emit1(JSOP_DUP)) - return false; - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_STRICTEQ)) - return false; - if (!ifUndefined.emitThen()) - return false; - if (!emit1(JSOP_POP)) - return false; - if (!emitInitializer(initializer, bindingElement)) - return false; - if (!ifUndefined.emitEnd()) - return false; - } else if (isRest) { - if (!emit1(JSOP_REST)) - return false; - } - - // Initialize the parameter name. - - if (isDestructuring) { - // If we had an initializer or the rest parameter, the value is - // already on the stack. - if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) - return false; + auto emitDestructuring = [this, &fpe, &bindingElement]() { + // [stack] ARG // If there's an parameter expression var scope, the destructuring // declaration needs to initialize the name in the function scope, // which is not the innermost scope. - if (!emitDestructuringOps(&bindingElement->as(), - paramExprVarScope - ? DestructuringFormalParameterInVarScope - : DestructuringDeclaration)) - { + if (!this->emitDestructuringOps(&bindingElement->as(), + fpe.getDestructuringFlavor())) { + // [stack] ARG return false; } - if (!emit1(JSOP_POP)) - return false; - } else if (hasParameterExprs || isRest) { - RootedAtom paramName(cx, bindingElement->name()); - NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); + return true; + }; - NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - if (hasParameterExprs) { - // If we had an initializer or a rest parameter, the value is - // already on the stack. - if (!initializer && !isRest) { - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; } } - if (!noe.emitAssignment()) { - return false; - } - if (!emit1(JSOP_POP)) { - return false; - } + + continue; } - if (paramExprVarScope) { - if (!paramExprVarScope->leave(this)) + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; } } + return true; } @@ -8687,6 +8375,8 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); + // [stack] + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) { @@ -8696,14 +8386,18 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { + // [stack] return false; } if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS return false; } if (!noe.emitAssignment()) + // [stack] THIS/ARGUMENTS return false; if (!bce->emit1(JSOP_POP)) + // [stack] return false; return true; @@ -8712,6 +8406,7 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasLocalBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) + // [stack] return false; } @@ -8726,66 +8421,6 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() return true; } -bool -BytecodeEmitter::emitFunctionBody(ParseNode* funBody) -{ - FunctionBox* funbox = sc->asFunctionBox(); - - if (funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { - if (!emitInitializeInstanceFields()) - return false; - } - - if (!emitTree(funBody)) - return false; - - if (funbox->needsFinalYield()) { - // If we fall off the end of a generator, do a final yield. - bool needsIteratorResult = funbox->needsIteratorResult(); - if (needsIteratorResult) { - if (!emitPrepareIteratorResult()) - return false; - } - - if (!emit1(JSOP_UNDEFINED)) - return false; - - if (needsIteratorResult) { - if (!emitFinishIteratorResult(true)) - return false; - } - - if (!emit1(JSOP_SETRVAL)) - return false; - - if (!emitGetDotGeneratorInInnermostScope()) - return false; - - // No need to check for finally blocks, etc as in EmitReturn. - if (!emitYieldOp(JSOP_FINALYIELDRVAL)) - 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 (hasTryFinally) { - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - } - } - - if (funbox->isDerivedClassConstructor()) { - if (!emitCheckDerivedClassConstructorReturn()) - return false; - } - - return true; -} - bool BytecodeEmitter::emitLexicalInitialization(NameNode* pn) { @@ -8951,8 +8586,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; case PNK_PARAMSBODY: - if (!emitFunctionFormalParametersAndBody(&pn->as())) - return false; + MOZ_ASSERT_UNREACHABLE("ParamsBody should be handled in emitFunctionScript."); break; case PNK_IF: diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index bbb42f367e..42faa78007 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -14,6 +14,7 @@ #include "jsscript.h" #include "ds/InlineTable.h" +#include "frontend/DestructuringFlavor.h" #include "frontend/JumpList.h" #include "frontend/Parser.h" #include "frontend/SharedContext.h" @@ -424,8 +425,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter // encompasses the entire source. MOZ_MUST_USE bool emitScript(ParseNode* body); - MOZ_MUST_USE bool emitInitializeInstanceFields(); - // Emit function code for the tree rooted at body. MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode); @@ -605,21 +604,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); - enum DestructuringFlavor { - // Destructuring into a declaration. - DestructuringDeclaration, - - // Destructuring into a formal parameter, when the formal parameters - // contain an expression that might be evaluated, and thus require - // this destructuring to assign not into the innermost scope that - // contains the function body's vars, but into its enclosing scope for - // parameter expressions. - DestructuringFormalParameterInVarScope, - - // Destructuring as part of an AssignmentExpression. - DestructuringAssignment - }; - // emitDestructuringLHSRef emits the lhs expression's reference. // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. @@ -794,10 +778,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitBreak(PropertyName* label); MOZ_MUST_USE bool emitContinue(PropertyName* label); - MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody); MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); - MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); MOZ_MUST_USE bool emitLexicalInitialization(NameNode* pn); MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h new file mode 100644 index 0000000000..1a4a20bb3c --- /dev/null +++ b/js/src/frontend/DestructuringFlavor.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef frontend_DestructuringFlavor_h +#define frontend_DestructuringFlavor_h + +namespace js { +namespace frontend { + +enum DestructuringFlavor { + // Destructuring into a declaration. + DestructuringDeclaration, + + // Destructuring into a formal parameter, when the formal parameters + // contain an expression that might be evaluated, and thus require + // this destructuring to assign not into the innermost scope that + // contains the function body's vars, but into its enclosing scope for + // parameter expressions. + DestructuringFormalParameterInVarScope, + + // Destructuring as part of an AssignmentExpression. + DestructuringAssignment +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DestructuringFlavor_h */ \ No newline at end of file diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp new file mode 100644 index 0000000000..f9e8faf714 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.cpp @@ -0,0 +1,1083 @@ +/* -*- 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 "jsscript.h" // JSScript + +#include "builtin/ModuleObject.h" // ModuleObject +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/Parser.h" // BindingIter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "vm/AsyncFunction.h" // AsyncFunctionResolveKind +#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, + bool isHoisted) + : bce_(bce_), + funbox_(funbox), + fun_(bce_->cx, funbox_->function()), + name_(bce_->cx, fun_->explicitName()), + syntaxKind_(syntaxKind), + isHoisted_(isHoisted) +{ + MOZ_ASSERT_IF(fun_->isInterpretedLazy(), fun_->lazyScript()); +} + +bool FunctionEmitter::interpretedCommon() +{ + // Mark as singletons any function which will only be executed once, or + // which is inner to a lambda we only expect to run once. In the latter + // case, if the lambda runs multiple times then CloneFunctionObject will + // make a deep clone of its contents. + bool singleton = bce_->checkRunOnceContext(); + if (!JSFunction::setTypeForScriptedFunction(bce_->cx, fun_, singleton)) + return false; + + SharedContext* outersc = bce_->sc; + if (outersc->isFunctionBox()) + outersc->asFunctionBox()->setHasInnerFunctions(); + + return true; +} + +bool FunctionEmitter::prepareForNonLazy() +{ + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(fun_->isInterpreted()); + MOZ_ASSERT(!fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + MOZ_ASSERT_IF(bce_->sc->strict(), funbox_->strictScript); + +#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(fun_->isInterpreted()); + MOZ_ASSERT(fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + // We need to update the static scope chain regardless of whether + // the LazyScript has already been initialized, due to the case + // where we previously successfully compiled an inner function's + // lazy script but failed to compile the outer script after the + // fact. If we attempt to compile the outer script again, the + // static scope chain will be newly allocated and will mismatch + // the previously compiled LazyScript's. + ScriptSourceObject* source = &bce_->script->sourceObject()->as(); + fun_->lazyScript()->setEnclosingScopeAndSource(bce_->innermostScope(), source); + if (bce_->emittingRunOnceLambda) + fun_->lazyScript()->setTreatAsRunOnce(); + + 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); + MOZ_ASSERT_IF(fun_->hasScript(), fun_->nonLazyScript()); + + // [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 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()->hasExtraBodyVarScope()) + 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()) { + return false; + } + if (!bce_->emitGetName(name_)) { + return false; + } + if (!noe.emitAssignment()) + return false; + if (!bce_->emit1(JSOP_POP)) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAsmJSModule() + { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(!funbox_->wasEmitted); + MOZ_ASSERT(IsAsmJSModule(fun_)); + + // [stack] + + funbox_->wasEmitted = 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. + unsigned index = bce_->objectList.add(funbox_); + + // [stack] + + if (!isHoisted_) { + 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(unsigned index) +{ + // Non-hoisted functions simply emit their respective op. + + // [stack] + + // JSOP_LAMBDA_ARROW is always preceded by a opcode that pushes new.target. + // See below. + MOZ_ASSERT(fun_->isArrow() == (syntaxKind_ == FunctionSyntaxKind::Arrow)); + + bool needsProto = syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor; + + if (funbox_->isAsync()) { + MOZ_ASSERT(!needsProto); + return bce_->emitAsyncWrapper(index, funbox_->needsHomeObject(), fun_->isArrow(), + fun_->isStarGenerator()); + } + + if (fun_->isArrow()) { + if (!emitNewTargetForArrow()) { + // [stack] NEW.TARGET/NULL + return false; + } + } + + if (needsProto) { + // [stack] PROTO + if (!bce_->emitIndex32(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_LAMBDA_ARROW + : JSOP_LAMBDA; + if (!bce_->emitIndex32(op, index)) { + // [stack] FUN + return false; + } + + return true; +} + +bool FunctionEmitter::emitHoisted(unsigned 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 (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, /* needsHomeObject = */ false, + /* isArrow = */ false, funbox_->isStarGenerator())) + { + return false; + } + } else { + if (!bce_->emitIndexOp(JSOP_LAMBDA, index)) { + return false; + } + } + + if (!noe.emitAssignment()) { + // [stack] FUN + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool FunctionEmitter::emitTopLevelFunction(unsigned index) +{ + // [stack] + + if (bce_->sc->isModuleContext()) { + // For modules, we record the function and instantiate the binding + // during ModuleInstantiate(), before the script is run. + + JS::Rooted module(bce_->cx, + bce_->sc->asModuleContext()->module()); + if (!module->noteFunctionDeclaration(bce_->cx, name_, fun_)) + return false; + return true; + } + + MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext()); + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + bce_->switchToPrologue(); + if (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, fun_->isMethod(), fun_->isArrow(), + fun_->isStarGenerator())) + return false; + } else { + if (!bce_->emitIndex32(JSOP_LAMBDA, index)) + return false; + } + if (!bce_->emit1(JSOP_DEFFUN)) { + // [stack] + return false; + } + bce_->switchToMain(); + 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); + + // [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; + } + + /* + * Emit a prologue for run-once scripts which will deoptimize JIT code + * if the script ends up running multiple times via foo.caller related + * shenanigans. + * + * Also mark the script so that initializers created within it may be + * given more precise types. + */ + if (bce_->isRunOnceLambda()) { + bce_->script->setTreatAsRunOnce(); + MOZ_ASSERT(!bce_->script->hasRunOnce()); + + bce_->switchToPrologue(); + if (!bce_->emit1(JSOP_RUNONCE)) + return false; + bce_->switchToMain(); + } + + 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. + bce_->switchToMain(); + } + + if (!functionEmitterScope_->enterFunction(bce_, funbox_)) + return false; + + if (!bce_->emitInitializeFunctionSpecialNames()) { + // [stack] + return false; + } + + if (!funbox_->hasParameterExprs) + bce_->switchToMain(); + +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool FunctionScriptEmitter::prepareForBody() +{ + MOZ_ASSERT(state_ == State::Parameters); + + // [stack] + + if (!emitExtraBodyVarScope()) { + // [stack] + return false; + } + + if (funbox_->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + if (!emitInitializeInstanceFields()) { + // [stack] + return false; + } + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool FunctionScriptEmitter::emitExtraBodyVarScope() +{ + // [stack] + + if (!funbox_->hasExtraBodyVarScope()) { + 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; } + // + JS::Rooted name(bce_->cx); + for (BindingIter bi(*funbox_->functionScopeBindings(), true); bi; bi++) { + name = 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->names().dotThis && + name != bce_->cx->names().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::emitInitializeInstanceFields() +{ + MOZ_ASSERT(bce_->fieldInitializers_.valid); + size_t numFields = bce_->fieldInitializers_.numFieldInitializers; + + if (numFields == 0) { + return true; + } + + if (!bce_->emitGetName(bce_->cx->names().dotInitializers)) { + // [stack] ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + if (fieldIndex < numFields - 1) { + // We DUP to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra POP at the end of the loop. + if (!bce_->emit1(JSOP_DUP)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!bce_->emitNumberOp(fieldIndex)) { + // [stack] ARRAY? ARRAY INDEX + return false; + } + + // Don't use CALLELEM here, because the receiver of the call != the receiver + // of this getelem. (Specifically, the call receiver is `this`, and the + // receiver of this getelem is `.initializers`) + if (!bce_->emit1(JSOP_GETELEM)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!bce_->emitGetName(bce_->cx->names().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!bce_->emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] ARRAY? + 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. + bool needsIteratorResult = funbox_->needsIteratorResult(); + if (needsIteratorResult) { + if (!bce_->emitPrepareIteratorResult()) { + // [stack] RESULT + return false; + } + } + + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] RESULT? UNDEF + return false; + } + + if (needsIteratorResult) { + 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 { + // 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; + } + } + + // Always end the script with a JSOP_RETRVAL. Some other parts of the + // codebase depend on this opcode, + // e.g. InterpreterRegs::setToEndOfScript. + if (!bce_->emit1(JSOP_RETRVAL)) { + // [stack] + return false; + } + + if (namedLambdaEmitterScope_) { + if (!namedLambdaEmitterScope_->leave(bce_)) + return false; + namedLambdaEmitterScope_.reset(); + } + +#ifdef DEBUG + state_ = State::EndBody; +#endif + return true; +} + +bool FunctionScriptEmitter::initScript() +{ + MOZ_ASSERT(state_ == State::EndBody); + + if (!JSScript::fullyInitFromEmitter(bce_->cx, bce_->script, bce_)) { + return false; + } + + bce_->tellDebuggerAboutCompiledScript(bce_->cx); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce_, + FunctionBox* funbox) + : bce_(bce_), + funbox_(funbox), + functionEmitterScope_(bce_->innermostEmitterScope()) {} + +bool FunctionParamsEmitter::emitSimple(JS::Handle 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 (!enterParameterExpressionVarScope()) { + return false; + } + + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDefaultEnd(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuring() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + + 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; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + 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; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::emitRest(JS::Handle 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 (!enterParameterExpressionVarScope()) { + return false; + } + 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; + } + + if (!leaveParameterExpressionVarScope()) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::enterParameterExpressionVarScope() +{ + if (!funbox_->hasDirectEvalInParameterExpr) + return true; + + // ES 14.1.19 says if BindingElement contains an expression in the + // production FormalParameter : BindingElement, it is evaluated in a + // new var environment. This is needed to prevent vars from escaping + // direct eval in parameter expressions. + paramExprVarEmitterScope_.emplace(bce_); + if (!paramExprVarEmitterScope_->enterParameterExpressionVar(bce_)) + return false; + return true; +} + +bool FunctionParamsEmitter::leaveParameterExpressionVarScope() +{ + if (!paramExprVarEmitterScope_) + return true; + + if (!paramExprVarEmitterScope_->leave(bce_)) + return false; + paramExprVarEmitterScope_.reset(); + + 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(JS::Handle 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; +} + +DestructuringFlavor FunctionParamsEmitter::getDestructuringFlavor() +{ + MOZ_ASSERT(state_ == State::Destructuring || + state_ == State::DestructuringDefault || + state_ == State::DestructuringRest); + + return funbox_->hasDirectEvalInParameterExpr + ? DestructuringFormalParameterInVarScope + : DestructuringDeclaration; +} diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h new file mode 100644 index 0000000000..ce068c6b56 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.h @@ -0,0 +1,451 @@ +/* -*- 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/. */ + +#ifndef frontend_FunctionEmitter_h +#define frontend_FunctionEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE + +#include // uint16_t, uint32_t + +#include "jsopcode.h" +#include "jsfun.h" // JSFunction + +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/SharedContext.h" // FunctionBox +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Rooted, JS::Handle +#include "vm/String.h" // JSAtom + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting function declaration, expression, or method etc. +// +// This class handles the enclosing script's part (function object creation, +// declaration, etc). The content of the function script is handled by +// FunctionScriptEmitter and FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f() {}`, non lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.prepareForNonLazy(); +// +// // Emit script with FunctionScriptEmitter here. +// ... +// +// fe.emitNonLazyEnd(); +// +// `function f() {}`, lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitLazy(); +// +// `function f() {}`, emitting hoisted function again +// // See emitAgain comment for more details +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// true); +// fe.emitAgain(); +// +// `function f() { "use asm"; }` +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitAsmJSModule(); +// +class MOZ_STACK_CLASS FunctionEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Function linked from funbox_. + JS::Rooted fun_; + + // Function's explicit name. + JS::Rooted name_; + + FunctionSyntaxKind syntaxKind_; + bool isHoisted_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +-------+ + // | + // | [non-lazy function] + // | prepareForNonLazy +---------+ emitNonLazyEnd +-----+ + // +--------------------->| NonLazy |---------------->+->| End | + // | +---------+ ^ +-----+ + // | | + // | [lazy function] | + // | emitLazy | + // +------------------------------------------------->+ + // | ^ + // | [emitting hoisted function again] | + // | emitAgain | + // +------------------------------------------------->+ + // | ^ + // | [asm.js module] | + // | emitAsmJSModule | + // +--------------------------------------------------+ + // + enum class State { + // The initial state. + Start, + + // After calling prepareForNonLazy. + NonLazy, + + // After calling emitNonLazyEnd, emitLazy, emitAgain, or emitAsmJSModule. + End + }; + State state_ = State::Start; +#endif + + public: + FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, bool isHoisted); + + MOZ_MUST_USE bool prepareForNonLazy(); + MOZ_MUST_USE bool emitNonLazyEnd(); + + MOZ_MUST_USE bool emitLazy(); + + MOZ_MUST_USE bool emitAgain(); + + MOZ_MUST_USE bool emitAsmJSModule(); + + private: + // Common code for non-lazy and lazy functions. + MOZ_MUST_USE bool interpretedCommon(); + + // Emit the function declaration, expression, method etc. + // This leaves function object on the stack for expression etc, + // and doesn't for declaration. + MOZ_MUST_USE bool emitFunction(); + + // Helper methods used by emitFunction for each case. + // `index` is the object index of the function. + MOZ_MUST_USE bool emitNonHoisted(unsigned index); + MOZ_MUST_USE bool emitHoisted(unsigned index); + MOZ_MUST_USE bool emitTopLevelFunction(unsigned index); + MOZ_MUST_USE bool emitNewTargetForArrow(); +}; + +// Class for emitting function script. +// Parameters are handled by FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a) { expr }` +// FunctionScriptEmitter fse(this, funbox_for_f, +// Some(offset_of_opening_paren), +// Some(offset_of_closing_brace)); +// fse.prepareForParameters(); +// +// // Emit parameters with FunctionParamsEmitter here. +// ... +// +// fse.prepareForBody(); +// emit(expr); +// fse.emitEnd(); +// +// // Do NameFunctions operation here if needed. +// +// fse.initScript(); +// +class MOZ_STACK_CLASS FunctionScriptEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Scope for the function name for a named lambda. + // None for anonymous function. + mozilla::Maybe namedLambdaEmitterScope_; + + // Scope for function body. + mozilla::Maybe functionEmitterScope_; + + // Scope for the extra body var. + // None if `funbox_->hasExtraBodyVarScope() == false`. + mozilla::Maybe extraBodyVarEmitterScope_; + + mozilla::Maybe tdzCache_; + + // See the comment for constructor. + mozilla::Maybe paramStart_; + mozilla::Maybe bodyEnd_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForParameters +------------+ + // | Start |---------------------->| Parameters |-+ + // +-------+ +------------+ | + // | + // +--------------------------------------------+ + // | + // | prepareForBody +------+ emitEndBody +---------+ + // +---------------->| Body |------------->| EndBody |-+ + // +------+ +---------+ | + // | + // +-------------------------------------------------+ + // | + // | initScript +-----+ + // +------------>| End | + // +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForParameters. + Parameters, + + // After calling prepareForBody. + Body, + + // After calling emitEndBody. + EndBody, + + // After calling initScript. + End + }; + State state_ = State::Start; +#endif + + public: + // Parameters are the offset in the source code for each character below: + // + // function f(a, b, ...c) { ... } + // ^ ^ + // | | + // paramStart bodyEnd + // + // Can be Nothing() if not available. + FunctionScriptEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + const mozilla::Maybe& paramStart, + const mozilla::Maybe& bodyEnd) + : bce_(bce), + funbox_(funbox), + paramStart_(paramStart), + bodyEnd_(bodyEnd) {} + + MOZ_MUST_USE bool prepareForParameters(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEndBody(); + + // Initialize JSScript for this function. + // WARNING: There shouldn't be any fallible operation for the function + // compilation after `initScript` call. + // See the comment inside JSScript::fullyInitFromEmitter for + // more details. + MOZ_MUST_USE bool initScript(); + + private: + MOZ_MUST_USE bool emitExtraBodyVarScope(); + MOZ_MUST_USE bool emitInitializeInstanceFields(); +}; + +// Class for emitting function parameters. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a, b=10, ...c) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.emitSimple(atom_of_a); +// +// fpe.prepareForDefault(); +// emit(10); +// fpe.emitDefaultEnd(atom_of_b); +// +// fpe.emitRest(atom_of_c); +// +// `function f([a], [b]=[1], ...[c]) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.prepareForDestructuring(); +// emit(destructuring_for_[a]); +// fpe.emitDestructuringEnd(); +// +// fpe.prepareForDestructuringDefaultInitializer(); +// emit([1]); +// fpe.prepareForDestructuringDefault(); +// emit(destructuring_for_[b]); +// fpe.emitDestructuringDefaultEnd(); +// +// fpe.prepareForDestructuringRest(); +// emit(destructuring_for_[c]); +// fpe.emitDestructuringRestEnd(); +// +class MOZ_STACK_CLASS FunctionParamsEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // The pointer to `FunctionScriptEmitter::functionEmitterScope_`, + // passed via `BytecodeEmitter::innermostEmitterScope()`. + EmitterScope* functionEmitterScope_; + + // The slot for the current parameter. + // NOTE: after emitting rest parameter, this isn't incremented. + uint16_t argSlot_ = 0; + + // DefaultEmitter for default parameter. + mozilla::Maybe default_; + + // Scope for each parameter expression. + // Populated only when there's `eval` in parameters. + mozilla::Maybe paramExprVarEmitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +----------------------------------------------------------+ + // | | + // | +-------+ | + // +->| Start |-+ | + // +-------+ | | + // | | + // +------------+ | + // | | + // | [single binding, wihtout default] | + // | emitSimple | + // +--------------------------------------------------------->+ + // | ^ + // | [single binding, with default] | + // | prepareForDefault +---------+ emitDefaultEnd | + // +--------------------->| Default |------------------------>+ + // | +---------+ ^ + // | | + // | [destructuring, without default] | + // | prepareForDestructuring +---------------+ | + // +--------------------------->| Destructuring |-+ | + // | +---------------+ | | + // | | | + // | +-----------------------------------------+ | + // | | | + // | | emitDestructuringEnd | + // | +---------------------------------------------------->+ + // | ^ + // | [destructuring, with default] | + // | prepareForDestructuringDefaultInitializer | + // +---------------------------------------------+ | + // | | | + // | +----------------------------------------+ | + // | | | + // | | +---------------------------------+ | + // | +->| DestructuringDefaultInitializer |-+ | + // | +---------------------------------+ | | + // | | | + // | +------------------------------------+ | + // | | | + // | | prepareForDestructuringDefault | + // | +-------------------------------+ | + // | | | + // | +-----------------------------+ | + // | | | + // | | +----------------------+ | + // | +->| DestructuringDefault |-+ | + // | +----------------------+ | | + // | | | + // | +-------------------------+ | + // | | | + // | | emitDestructuringDefaultEnd | + // | +---------------------------------------------->+ + // | + // | [single binding rest] + // | emitRest +-----+ + // +--------------------------------------------------------->+->| End | + // | ^ +-----+ + // | [destructuring rest] | + // | prepareForDestructuringRest +-------------------+ | + // +-------------------------------->| DestructuringRest |-+ | + // +-------------------+ | | + // | | + // +----------------------------------------------------+ | + // | | + // | emitDestructuringRestEnd | + // +-------------------------------------------------------+ + // + enum class State { + // The initial state, or after emitting non-rest parameter. + Start, + + // After calling prepareForDefault. + Default, + + // After calling prepareForDestructuring. + Destructuring, + + // After calling prepareForDestructuringDefaultInitializer. + DestructuringDefaultInitializer, + + // After calling prepareForDestructuringDefault. + DestructuringDefault, + + // After calling prepareForDestructuringRest. + DestructuringRest, + + // After calling emitRest or emitDestructuringRestEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + FunctionParamsEmitter(BytecodeEmitter* bce, FunctionBox* funbox); + + // paramName is used only when there's at least one expression in the + // paramerters (funbox_->hasParameterExprs == true). + MOZ_MUST_USE bool emitSimple(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitDefaultEnd(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuring(); + MOZ_MUST_USE bool emitDestructuringEnd(); + + MOZ_MUST_USE bool prepareForDestructuringDefaultInitializer(); + MOZ_MUST_USE bool prepareForDestructuringDefault(); + MOZ_MUST_USE bool emitDestructuringDefaultEnd(); + + MOZ_MUST_USE bool emitRest(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuringRest(); + MOZ_MUST_USE bool emitDestructuringRestEnd(); + + MOZ_MUST_USE DestructuringFlavor getDestructuringFlavor(); + + private: + // Enter/leave var scope for `eval` if necessary. + MOZ_MUST_USE bool enterParameterExpressionVarScope(); + MOZ_MUST_USE bool leaveParameterExpressionVarScope(); + + MOZ_MUST_USE bool prepareForInitializer(); + MOZ_MUST_USE bool emitInitializerEnd(); + + MOZ_MUST_USE bool emitRestArray(); + + MOZ_MUST_USE bool emitAssignment(JS::Handle paramName); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FunctionEmitter_h */ diff --git a/js/src/moz.build b/js/src/moz.build index e078efc88a..b12d0a90cc 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -145,6 +145,7 @@ main_deunified_sources = [ 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/ForOfLoopControl.cpp', + 'frontend/FunctionEmitter.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp', 'frontend/LexicalScopeEmitter.cpp',