Issue #2142 - Add FunctionEmitter, FunctionScriptEmitter, and FunctionParamsEmitter with current methods

Based-on: m-c 1473796/3, 1473796/4
This commit is contained in:
Martok 2023-04-26 19:24:13 +02:00 • committed by roytam1
commit e6335ded89
6 changed files with 1742 additions and 560 deletions

View file

@ -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<ListNode>();
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<EmitterScope> 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<NameLocation> 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<ScriptSourceObject>();
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<JSScript*> 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<JSObject*> sourceObject(cx, script->sourceObject());
Rooted<JSScript*> 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<JSScript*> 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<JSObject*> sourceObject(cx, script->sourceObject());
Rooted<JSScript*> 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<EmitterScope> 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<AssignmentNode>().left();
initializer = arg->as<AssignmentNode>().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<EmitterScope> 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<ListNode>(),
paramExprVarScope
? DestructuringFormalParameterInVarScope
: DestructuringDeclaration))
{
if (!this->emitDestructuringOps(&bindingElement->as<ListNode>(),
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<NameNode>().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<NameNode>().name());
if (!fpe.emitDefaultEnd(paramName)) {
// [stack]
return false;
}
continue;
}
RootedAtom paramName(cx, bindingElement->as<NameNode>().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<ListNode>()))
return false;
MOZ_ASSERT_UNREACHABLE("ParamsBody should be handled in emitFunctionScript.");
break;
case PNK_IF:

View file

@ -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);

View file

@ -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 */

File diff suppressed because it is too large Load diff

View file

@ -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 <stdint.h> // 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<JSFunction*> fun_;
// Function's explicit name.
JS::Rooted<JSAtom*> 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<EmitterScope> namedLambdaEmitterScope_;
// Scope for function body.
mozilla::Maybe<EmitterScope> functionEmitterScope_;
// Scope for the extra body var.
// None if `funbox_->hasExtraBodyVarScope() == false`.
mozilla::Maybe<EmitterScope> extraBodyVarEmitterScope_;
mozilla::Maybe<TDZCheckCache> tdzCache_;
// See the comment for constructor.
mozilla::Maybe<uint32_t> paramStart_;
mozilla::Maybe<uint32_t> 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<uint32_t>& paramStart,
const mozilla::Maybe<uint32_t>& 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<DefaultEmitter> default_;
// Scope for each parameter expression.
// Populated only when there's `eval` in parameters.
mozilla::Maybe<EmitterScope> 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<JSAtom*> paramName);
MOZ_MUST_USE bool prepareForDefault();
MOZ_MUST_USE bool emitDefaultEnd(JS::Handle<JSAtom*> 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<JSAtom*> 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<JSAtom*> paramName);
};
} /* namespace frontend */
} /* namespace js */
#endif /* frontend_FunctionEmitter_h */

View file

@ -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',