https://github.com/mozilla/gecko-dev
Tip revision: 02b4ae79b24aae2346b1338e2bf095a571192061 authored by Ryan VanderMeulen on 21 October 2019, 16:13:46 UTC
Bug 1590150 - Turn off ESR60 cron jobs. r=tomprince, a=release DONTBUILD
Bug 1590150 - Turn off ESR60 cron jobs. r=tomprince, a=release DONTBUILD
Tip revision: 02b4ae7
BytecodeEmitter.cpp
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/*
* JS bytecode generation.
*/
#include "frontend/BytecodeEmitter.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
#include <string.h>
#include "jsapi.h"
#include "jsnum.h"
#include "jstypes.h"
#include "jsutil.h"
#include "ds/Nestable.h"
#include "frontend/Parser.h"
#include "frontend/TokenStream.h"
#include "vm/BytecodeUtil.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/Stack.h"
#include "wasm/AsmJS.h"
#include "frontend/ParseNode-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::gc;
using namespace js::frontend;
using mozilla::AssertedCast;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::NumberIsInt32;
using mozilla::PodCopy;
using mozilla::Some;
using mozilla::Unused;
class BreakableControl;
class LabelControl;
class LoopControl;
class ForOfLoopControl;
class TryFinallyControl;
static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) {
// The few node types listed below are exceptions to the usual
// location-source-note-emitting code in BytecodeEmitter::emitTree().
// Single-line `while` loops and C-style `for` loops require careful
// handling to avoid strange stepping behavior.
// Functions usually shouldn't have location information (bug 1431202).
ParseNodeKind kind = pn->getKind();
return kind == ParseNodeKind::While || kind == ParseNodeKind::For ||
kind == ParseNodeKind::Function;
}
// A cache that tracks superfluous TDZ checks.
//
// Each basic block should have a TDZCheckCache in scope. Some NestableControl
// subclasses contain a TDZCheckCache.
class BytecodeEmitter::TDZCheckCache
: public Nestable<BytecodeEmitter::TDZCheckCache> {
PooledMapPtr<CheckTDZMap> cache_;
MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
return cache_ || cache_.acquire(bce->cx);
}
public:
explicit TDZCheckCache(BytecodeEmitter* bce)
: Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
cache_(bce->cx->frontendCollectionPool()) {}
Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name);
MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name,
MaybeCheckTDZ check);
};
class BytecodeEmitter::NestableControl
: public Nestable<BytecodeEmitter::NestableControl> {
StatementKind kind_;
// The innermost scope when this was pushed.
EmitterScope* emitterScope_;
protected:
NestableControl(BytecodeEmitter* bce, StatementKind kind)
: Nestable<NestableControl>(&bce->innermostNestableControl),
kind_(kind),
emitterScope_(bce->innermostEmitterScopeNoCheck()) {}
public:
using Nestable<NestableControl>::enclosing;
using Nestable<NestableControl>::findNearest;
StatementKind kind() const { return kind_; }
EmitterScope* emitterScope() const { return emitterScope_; }
template <typename T>
bool is() const;
template <typename T>
T& as() {
MOZ_ASSERT(this->is<T>());
return static_cast<T&>(*this);
}
};
// Template specializations are disallowed in different namespaces; specialize
// all the NestableControl subtypes up front.
namespace js {
namespace frontend {
template <>
bool BytecodeEmitter::NestableControl::is<BreakableControl>() const {
return StatementKindIsUnlabeledBreakTarget(kind_) ||
kind_ == StatementKind::Label;
}
template <>
bool BytecodeEmitter::NestableControl::is<LabelControl>() const {
return kind_ == StatementKind::Label;
}
template <>
bool BytecodeEmitter::NestableControl::is<LoopControl>() const {
return StatementKindIsLoop(kind_);
}
template <>
bool BytecodeEmitter::NestableControl::is<ForOfLoopControl>() const {
return kind_ == StatementKind::ForOfLoop;
}
template <>
bool BytecodeEmitter::NestableControl::is<TryFinallyControl>() const {
return kind_ == StatementKind::Try || kind_ == StatementKind::Finally;
}
} // namespace frontend
} // namespace js
class BreakableControl : public BytecodeEmitter::NestableControl {
public:
// Offset of the last break.
JumpList breaks;
BreakableControl(BytecodeEmitter* bce, StatementKind kind)
: NestableControl(bce, kind) {
MOZ_ASSERT(is<BreakableControl>());
}
MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) {
return bce->emitJumpTargetAndPatch(breaks);
}
};
class LabelControl : public BreakableControl {
RootedAtom label_;
// The code offset when this was pushed. Used for effectfulness checking.
ptrdiff_t startOffset_;
public:
LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset)
: BreakableControl(bce, StatementKind::Label),
label_(bce->cx, label),
startOffset_(startOffset) {}
HandleAtom label() const { return label_; }
ptrdiff_t startOffset() const { return startOffset_; }
};
class LoopControl : public BreakableControl {
// Loops' children are emitted in dominance order, so they can always
// have a TDZCheckCache.
BytecodeEmitter::TDZCheckCache tdzCache_;
// Stack depth when this loop was pushed on the control stack.
int32_t stackDepth_;
// The loop nesting depth. Used as a hint to Ion.
uint32_t loopDepth_;
// Can we OSR into Ion from here? True unless there is non-loop state on the
// stack.
bool canIonOsr_;
public:
// The target of continue statement jumps, e.g., the update portion of a
// for(;;) loop.
JumpTarget continueTarget;
// Offset of the last continue in the loop.
JumpList continues;
LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
: BreakableControl(bce, loopKind), tdzCache_(bce), continueTarget({-1}) {
MOZ_ASSERT(is<LoopControl>());
LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
stackDepth_ = bce->stackDepth;
loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1;
int loopSlots;
if (loopKind == StatementKind::Spread) {
// The iterator next method, the iterator, the result array, and
// the current array index are on the stack.
loopSlots = 4;
} else if (loopKind == StatementKind::ForOfLoop) {
// The iterator next method, the iterator, and the current value
// are on the stack.
loopSlots = 3;
} else if (loopKind == StatementKind::ForInLoop) {
// The iterator and the current value are on the stack.
loopSlots = 2;
} else {
// No additional loop values are on the stack.
loopSlots = 0;
}
MOZ_ASSERT(loopSlots <= stackDepth_);
if (enclosingLoop) {
canIonOsr_ = (enclosingLoop->canIonOsr_ &&
stackDepth_ == enclosingLoop->stackDepth_ + loopSlots);
} else {
canIonOsr_ = stackDepth_ == loopSlots;
}
}
uint32_t loopDepth() const { return loopDepth_; }
bool canIonOsr() const { return canIonOsr_; }
MOZ_MUST_USE bool emitSpecialBreakForDone(BytecodeEmitter* bce) {
// This doesn't pop stack values, nor handle any other controls.
// Should be called on the toplevel of the loop.
MOZ_ASSERT(bce->stackDepth == stackDepth_);
MOZ_ASSERT(bce->innermostNestableControl == this);
if (!bce->newSrcNote(SRC_BREAK)) return false;
if (!bce->emitJump(JSOP_GOTO, &breaks)) return false;
return true;
}
MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) {
MOZ_ASSERT(continueTarget.offset != -1);
if (!patchBreaks(bce)) return false;
bce->patchJumpsToTarget(continues, continueTarget);
return true;
}
};
class TryFinallyControl : public BytecodeEmitter::NestableControl {
bool emittingSubroutine_;
public:
// The subroutine when emitting a finally block.
JumpList gosubs;
TryFinallyControl(BytecodeEmitter* bce, StatementKind kind)
: NestableControl(bce, kind), emittingSubroutine_(false) {
MOZ_ASSERT(is<TryFinallyControl>());
}
void setEmittingSubroutine() { emittingSubroutine_ = true; }
bool emittingSubroutine() const { return emittingSubroutine_; }
};
static inline void MarkAllBindingsClosedOver(LexicalScope::Data& data) {
BindingName* names = data.names;
for (uint32_t i = 0; i < data.length; i++)
names[i] = BindingName(names[i].name(), true);
}
// A scope that introduces bindings.
class BytecodeEmitter::EmitterScope
: public Nestable<BytecodeEmitter::EmitterScope> {
// The cache of bound names that may be looked up in the
// scope. Initially populated as the set of names this scope binds. As
// names are looked up in enclosing scopes, they are cached on the
// current scope.
PooledMapPtr<NameLocationMap> nameCache_;
// If this scope's cache does not include free names, such as the
// global scope, the NameLocation to return.
Maybe<NameLocation> fallbackFreeNameLocation_;
// True if there is a corresponding EnvironmentObject on the environment
// chain, false if all bindings are stored in frame slots on the stack.
bool hasEnvironment_;
// The number of enclosing environments. Used for error checking.
uint8_t environmentChainLength_;
// The next usable slot on the frame for not-closed over bindings.
//
// The initial frame slot when assigning slots to bindings is the
// enclosing scope's nextFrameSlot. For the first scope in a frame,
// the initial frame slot is 0.
uint32_t nextFrameSlot_;
// The index in the BytecodeEmitter's interned scope vector, otherwise
// ScopeNote::NoScopeIndex.
uint32_t scopeIndex_;
// If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
// block scope note list. Otherwise ScopeNote::NoScopeNote.
uint32_t noteIndex_;
MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
return nameCache_.acquire(bce->cx);
}
template <typename BindingIter>
MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce,
const BindingIter& bi) {
if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) {
bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
return false;
}
return true;
}
MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) {
uint32_t hops;
if (EmitterScope* emitterScope = enclosing(&bce))
hops = emitterScope->environmentChainLength_;
else
hops = bce->sc->compilationEnclosingScope()->environmentChainLength();
if (hops >= ENVCOORD_HOPS_LIMIT - 1) {
bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
return false;
}
environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
return true;
}
void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) {
nextFrameSlot_ = bi.nextFrameSlot();
if (nextFrameSlot_ > bce->maxFixedSlots)
bce->maxFixedSlots = nextFrameSlot_;
MOZ_ASSERT_IF(
bce->sc->isFunctionBox() && (bce->sc->asFunctionBox()->isGenerator() ||
bce->sc->asFunctionBox()->isAsync()),
bce->maxFixedSlots == 0);
}
MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name,
NameLocation loc) {
NameLocationMap& cache = *nameCache_;
NameLocationMap::AddPtr p = cache.lookupForAdd(name);
MOZ_ASSERT(!p);
if (!cache.add(p, name, loc)) {
ReportOutOfMemory(bce->cx);
return false;
}
return true;
}
Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name) {
if (NameLocationMap::Ptr p = nameCache_->lookup(name))
return Some(p->value().wrapped);
if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name))
return fallbackFreeNameLocation_;
return Nothing();
}
friend bool BytecodeEmitter::needsImplicitThis();
EmitterScope* enclosing(BytecodeEmitter** bce) const {
// There is an enclosing scope with access to the same frame.
if (EmitterScope* inFrame = enclosingInFrame()) return inFrame;
// We are currently compiling the enclosing script, look in the
// enclosing BCE.
if ((*bce)->parent) {
*bce = (*bce)->parent;
return (*bce)->innermostEmitterScopeNoCheck();
}
return nullptr;
}
Scope* enclosingScope(BytecodeEmitter* bce) const {
if (EmitterScope* es = enclosing(&bce)) return es->scope(bce);
// The enclosing script is already compiled or the current script is the
// global script.
return bce->sc->compilationEnclosingScope();
}
static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
// '.generator' cannot be accessed by name.
return name != bce->cx->names().dotGenerator;
}
static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope,
uint8_t hops);
NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
template <typename ScopeCreator>
MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
template <typename ScopeCreator>
MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce,
ScopeCreator createScope);
MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce,
uint32_t slotStart,
uint32_t slotEnd);
public:
explicit EmitterScope(BytecodeEmitter* bce)
: Nestable<EmitterScope>(&bce->innermostEmitterScope_),
nameCache_(bce->cx->frontendCollectionPool()),
hasEnvironment_(false),
environmentChainLength_(0),
nextFrameSlot_(0),
scopeIndex_(ScopeNote::NoScopeIndex),
noteIndex_(ScopeNote::NoScopeNoteIndex) {}
void dump(BytecodeEmitter* bce);
MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
Handle<LexicalScope::Data*> bindings);
MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce,
FunctionBox* funbox);
MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce,
GlobalSharedContext* globalsc);
MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
MOZ_MUST_USE bool enterModule(BytecodeEmitter* module,
ModuleSharedContext* modulesc);
MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
uint32_t index() const {
MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex,
"Did you forget to intern a Scope?");
return scopeIndex_;
}
uint32_t noteIndex() const { return noteIndex_; }
Scope* scope(const BytecodeEmitter* bce) const {
return bce->scopeList.vector[index()];
}
bool hasEnvironment() const { return hasEnvironment_; }
// The first frame slot used.
uint32_t frameSlotStart() const {
if (EmitterScope* inFrame = enclosingInFrame())
return inFrame->nextFrameSlot_;
return 0;
}
// The last frame slot used + 1.
uint32_t frameSlotEnd() const { return nextFrameSlot_; }
uint32_t numFrameSlots() const { return frameSlotEnd() - frameSlotStart(); }
EmitterScope* enclosingInFrame() const {
return Nestable<EmitterScope>::enclosing();
}
NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) {
if (Maybe<NameLocation> loc = lookupInCache(bce, name)) return *loc;
return searchAndCache(bce, name);
}
Maybe<NameLocation> locationBoundInScope(JSAtom* name, EmitterScope* target);
};
void BytecodeEmitter::EmitterScope::dump(BytecodeEmitter* bce) {
fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()),
this);
for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
const NameLocation& l = r.front().value();
JSAutoByteString bytes;
if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes)) return;
if (l.kind() != NameLocation::Kind::Dynamic)
fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()),
bytes.ptr());
else
fprintf(stdout, " %s ", bytes.ptr());
switch (l.kind()) {
case NameLocation::Kind::Dynamic:
fprintf(stdout, "dynamic\n");
break;
case NameLocation::Kind::Global:
fprintf(stdout, "global\n");
break;
case NameLocation::Kind::Intrinsic:
fprintf(stdout, "intrinsic\n");
break;
case NameLocation::Kind::NamedLambdaCallee:
fprintf(stdout, "named lambda callee\n");
break;
case NameLocation::Kind::Import:
fprintf(stdout, "import\n");
break;
case NameLocation::Kind::ArgumentSlot:
fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
break;
case NameLocation::Kind::FrameSlot:
fprintf(stdout, "frame slot=%u\n", l.frameSlot());
break;
case NameLocation::Kind::EnvironmentCoordinate:
fprintf(stdout, "environment hops=%u slot=%u\n",
l.environmentCoordinate().hops(),
l.environmentCoordinate().slot());
break;
case NameLocation::Kind::DynamicAnnexBVar:
fprintf(stdout, "dynamic annex b var\n");
break;
}
}
fprintf(stdout, "\n");
}
template <typename ScopeCreator>
bool BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce,
ScopeCreator createScope) {
RootedScope enclosing(bce->cx, enclosingScope(bce));
Scope* scope = createScope(bce->cx, enclosing);
if (!scope) return false;
hasEnvironment_ = scope->hasEnvironment();
scopeIndex_ = bce->scopeList.length();
return bce->scopeList.append(scope);
}
template <typename ScopeCreator>
bool BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce,
ScopeCreator createScope) {
MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX,
"There can be only one body scope");
bce->bodyScopeIndex = bce->scopeList.length();
return internScope(bce, createScope);
}
bool BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce) {
MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
"Scope notes are not needed for body-level scopes.");
noteIndex_ = bce->scopeNoteList.length();
return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
enclosingInFrame()
? enclosingInFrame()->noteIndex()
: ScopeNote::NoScopeNoteIndex);
}
#ifdef DEBUG
static bool NameIsOnEnvironment(Scope* scope, JSAtom* name) {
for (BindingIter bi(scope); bi; bi++) {
// If found, the name must already be on the environment or an import,
// or else there is a bug in the closed-over name analysis in the
// Parser.
if (bi.name() == name) {
BindingLocation::Kind kind = bi.location().kind();
if (bi.hasArgumentSlot()) {
JSScript* script = scope->as<FunctionScope>().script();
if (!script->strict() && !script->functionHasParameterExprs()) {
// Check for duplicate positional formal parameters.
for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
if (bi2.name() == name) kind = bi2.location().kind();
}
}
}
return kind == BindingLocation::Kind::Global ||
kind == BindingLocation::Kind::Environment ||
kind == BindingLocation::Kind::Import;
}
}
// If not found, assume it's on the global or dynamically accessed.
return true;
}
#endif
/* static */ NameLocation BytecodeEmitter::EmitterScope::searchInEnclosingScope(
JSAtom* name, Scope* scope, uint8_t hops) {
for (ScopeIter si(scope); si; si++) {
MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
bool hasEnv = si.hasSyntacticEnvironment();
switch (si.kind()) {
case ScopeKind::Function:
if (hasEnv) {
JSScript* script = si.scope()->as<FunctionScope>().script();
if (script->funHasExtensibleScope()) return NameLocation::Dynamic();
for (BindingIter bi(si.scope()); bi; bi++) {
if (bi.name() != name) continue;
BindingLocation bindLoc = bi.location();
if (bi.hasArgumentSlot() && !script->strict() &&
!script->functionHasParameterExprs()) {
// Check for duplicate positional formal parameters.
for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
if (bi2.name() == name) bindLoc = bi2.location();
}
}
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
return NameLocation::EnvironmentCoordinate(bi.kind(), hops,
bindLoc.slot());
}
}
break;
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar:
case ScopeKind::Lexical:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
if (hasEnv) {
for (BindingIter bi(si.scope()); bi; bi++) {
if (bi.name() != name) continue;
// The name must already have been marked as closed
// over. If this assertion is hit, there is a bug in the
// name analysis.
BindingLocation bindLoc = bi.location();
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
return NameLocation::EnvironmentCoordinate(bi.kind(), hops,
bindLoc.slot());
}
}
break;
case ScopeKind::Module:
if (hasEnv) {
for (BindingIter bi(si.scope()); bi; bi++) {
if (bi.name() != name) continue;
BindingLocation bindLoc = bi.location();
// Imports are on the environment but are indirect
// bindings and must be accessed dynamically instead of
// using an EnvironmentCoordinate.
if (bindLoc.kind() == BindingLocation::Kind::Import) {
MOZ_ASSERT(si.kind() == ScopeKind::Module);
return NameLocation::Import();
}
MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
return NameLocation::EnvironmentCoordinate(bi.kind(), hops,
bindLoc.slot());
}
}
break;
case ScopeKind::Eval:
case ScopeKind::StrictEval:
// As an optimization, if the eval doesn't have its own var
// environment and its immediate enclosing scope is a global
// scope, all accesses are global.
if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>())
return NameLocation::Global(BindingKind::Var);
return NameLocation::Dynamic();
case ScopeKind::Global:
return NameLocation::Global(BindingKind::Var);
case ScopeKind::With:
case ScopeKind::NonSyntactic:
return NameLocation::Dynamic();
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("No direct eval inside wasm functions");
}
if (hasEnv) {
MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
hops++;
}
}
MOZ_CRASH("Malformed scope chain");
}
NameLocation BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce,
JSAtom* name) {
Maybe<NameLocation> loc;
uint8_t hops = hasEnvironment() ? 1 : 0;
DebugOnly<bool> inCurrentScript = enclosingInFrame();
// Start searching in the current compilation.
for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
loc = es->lookupInCache(bce, name);
if (loc) {
if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate)
*loc = loc->addHops(hops);
break;
}
if (es->hasEnvironment()) hops++;
#ifdef DEBUG
if (!es->enclosingInFrame()) inCurrentScript = false;
#endif
}
// If the name is not found in the current compilation, walk the Scope
// chain encompassing the compilation.
if (!loc) {
inCurrentScript = false;
loc = Some(searchInEnclosingScope(
name, bce->sc->compilationEnclosingScope(), hops));
}
// Each script has its own frame. A free name that is accessed
// from an inner script must not be a frame slot access. If this
// assertion is hit, it is a bug in the free name analysis in the
// parser.
MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
// It is always correct to not cache the location. Ignore OOMs to make
// lookups infallible.
if (!putNameInCache(bce, name, *loc)) bce->cx->recoverFromOutOfMemory();
return *loc;
}
Maybe<NameLocation> BytecodeEmitter::EmitterScope::locationBoundInScope(
JSAtom* name, EmitterScope* target) {
// The target scope must be an intra-frame enclosing scope of this
// one. Count the number of extra hops to reach it.
uint8_t extraHops = 0;
for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
if (es->hasEnvironment()) extraHops++;
}
// Caches are prepopulated with bound names. So if the name is bound in a
// particular scope, it must already be in the cache. Furthermore, don't
// consult the fallback location as we only care about binding names.
Maybe<NameLocation> loc;
if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) {
NameLocation l = p->value().wrapped;
if (l.kind() == NameLocation::Kind::EnvironmentCoordinate)
loc = Some(l.addHops(extraHops));
else
loc = Some(l);
}
return loc;
}
bool BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce,
uint32_t slotStart,
uint32_t slotEnd) {
// Lexical bindings throw ReferenceErrors if they are used before
// initialization. See ES6 8.1.1.1.6.
//
// For completeness, lexical bindings are initialized in ES6 by calling
// InitializeBinding, after which touching the binding will no longer
// throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
// 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
if (slotStart != slotEnd) {
if (!bce->emit1(JSOP_UNINITIALIZED)) return false;
for (uint32_t slot = slotStart; slot < slotEnd; slot++) {
if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot)) return false;
}
if (!bce->emit1(JSOP_POP)) return false;
}
return true;
}
bool BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) {
return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
}
bool BytecodeEmitter::EmitterScope::enterLexical(
BytecodeEmitter* bce, ScopeKind kind,
Handle<LexicalScope::Data*> bindings) {
MOZ_ASSERT(kind != ScopeKind::NamedLambda &&
kind != ScopeKind::StrictNamedLambda);
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
if (!ensureCache(bce)) return false;
// Marks all names as closed over if the context requires it. This
// cannot be done in the Parser as we may not know if the context requires
// all bindings to be closed over until after parsing is finished. For
// example, legacy generators require all bindings to be closed over but
// it is unknown if a function is a legacy generator until the first
// 'yield' expression is parsed.
//
// This is not a problem with other scopes, as all other scopes with
// bindings are body-level. At the time of their creation, whether or not
// the context requires all bindings to be closed over is already known.
if (bce->sc->allBindingsClosedOver()) MarkAllBindingsClosedOver(*bindings);
// Resolve bindings.
TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
uint32_t firstFrameSlot = frameSlotStart();
BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
for (; bi; bi++) {
if (!checkSlotLimits(bce, bi)) return false;
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
if (!putNameInCache(bce, bi.name(), loc)) return false;
if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) return false;
}
updateFrameFixedSlots(bce, bi);
// Create and intern the VM scope.
auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx,
HandleScope enclosing) {
return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
};
if (!internScope(bce, createScope)) return false;
if (ScopeKindIsInBody(kind) && hasEnvironment()) {
// After interning the VM scope we can get the scope index.
if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV)) return false;
}
// Lexical scopes need notes to be mapped from a pc.
if (!appendScopeNote(bce)) return false;
// Put frame slots in TDZ. Environment slots are poisoned during
// environment creation.
//
// This must be done after appendScopeNote to be considered in the extent
// of the scope.
if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd()))
return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce,
FunctionBox* funbox) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
MOZ_ASSERT(funbox->namedLambdaBindings());
if (!ensureCache(bce)) return false;
// See comment in enterLexical about allBindingsClosedOver.
if (funbox->allBindingsClosedOver())
MarkAllBindingsClosedOver(*funbox->namedLambdaBindings());
BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT,
/* isNamedLambda = */ true);
MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
// The lambda name, if not closed over, is accessed via JSOP_CALLEE and
// not a frame slot. Do not update frame slot information.
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
if (!putNameInCache(bce, bi.name(), loc)) return false;
bi++;
MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
ScopeKind scopeKind = funbox->strict() ? ScopeKind::StrictNamedLambda
: ScopeKind::NamedLambda;
return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
LOCALNO_LIMIT, enclosing);
};
if (!internScope(bce, createScope)) return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::enterParameterExpressionVar(
BytecodeEmitter* bce) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
if (!ensureCache(bce)) return false;
// Parameter expressions var scopes have no pre-set bindings and are
// always extensible, as they are needed for eval.
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
// Create and intern the VM scope.
uint32_t firstFrameSlot = frameSlotStart();
auto createScope = [firstFrameSlot](JSContext* cx, HandleScope enclosing) {
return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
/* data = */ nullptr, firstFrameSlot,
/* needsEnvironment = */ true, enclosing);
};
if (!internScope(bce, createScope)) return false;
MOZ_ASSERT(hasEnvironment());
if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) return false;
// The extra var scope needs a note to be mapped from a pc.
if (!appendScopeNote(bce)) return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce,
FunctionBox* funbox) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
// If there are parameter expressions, there is an extra var scope.
if (!funbox->hasExtraBodyVarScope()) bce->setVarEmitterScope(this);
if (!ensureCache(bce)) return false;
// Resolve body-level bindings, if there are any.
auto bindings = funbox->functionScopeBindings();
Maybe<uint32_t> lastLexicalSlot;
if (bindings) {
NameLocationMap& cache = *nameCache_;
BindingIter bi(*bindings, funbox->hasParameterExprs);
for (; bi; bi++) {
if (!checkSlotLimits(bce, bi)) return false;
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
// The only duplicate bindings that occur are simple formal
// parameters, in which case the last position counts, so update the
// location.
if (p) {
MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter);
MOZ_ASSERT(!funbox->hasDestructuringArgs);
MOZ_ASSERT(!funbox->hasRest());
p->value() = loc;
continue;
}
if (!cache.add(p, bi.name(), loc)) {
ReportOutOfMemory(bce->cx);
return false;
}
}
updateFrameFixedSlots(bce, bi);
} else {
nextFrameSlot_ = 0;
}
// If the function's scope may be extended at runtime due to sloppy direct
// eval and there is no extra var scope, any names beyond the function
// scope must be accessed dynamically as we don't know if the name will
// become a 'var' binding due to direct eval.
if (!funbox->hasParameterExprs && funbox->hasExtensibleScope())
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
// In case of parameter expressions, the parameters are lexical
// bindings and have TDZ.
if (funbox->hasParameterExprs && nextFrameSlot_) {
uint32_t paramFrameSlotEnd = 0;
for (BindingIter bi(*bindings, true); bi; bi++) {
if (!BindingKindIsLexical(bi.kind())) break;
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
if (loc.kind() == NameLocation::Kind::FrameSlot) {
MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
paramFrameSlotEnd = loc.frameSlot() + 1;
}
}
if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) return false;
}
// Create and intern the VM scope.
auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
RootedFunction fun(cx, funbox->function());
return FunctionScope::create(
cx, funbox->functionScopeBindings(), funbox->hasParameterExprs,
funbox->needsCallObjectRegardlessOfBindings(), fun, enclosing);
};
if (!internBodyScope(bce, createScope)) return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(
BytecodeEmitter* bce, FunctionBox* funbox) {
MOZ_ASSERT(funbox->hasParameterExprs);
MOZ_ASSERT(funbox->extraVarScopeBindings() ||
funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
// The extra var scope is never popped once it's entered. It replaces the
// function scope as the var emitter scope.
bce->setVarEmitterScope(this);
if (!ensureCache(bce)) return false;
// Resolve body-level bindings, if there are any.
uint32_t firstFrameSlot = frameSlotStart();
if (auto bindings = funbox->extraVarScopeBindings()) {
BindingIter bi(*bindings, firstFrameSlot);
for (; bi; bi++) {
if (!checkSlotLimits(bce, bi)) return false;
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
if (!putNameInCache(bce, bi.name(), loc)) return false;
}
updateFrameFixedSlots(bce, bi);
} else {
nextFrameSlot_ = firstFrameSlot;
}
// If the extra var scope may be extended at runtime due to sloppy
// direct eval, any names beyond the var scope must be accessed
// dynamically as we don't know if the name will become a 'var' binding
// due to direct eval.
if (funbox->hasExtensibleScope())
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
// Create and intern the VM scope.
auto createScope = [funbox, firstFrameSlot](JSContext* cx,
HandleScope enclosing) {
return VarScope::create(
cx, ScopeKind::FunctionBodyVar, funbox->extraVarScopeBindings(),
firstFrameSlot,
funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), enclosing);
};
if (!internScope(bce, createScope)) return false;
if (hasEnvironment()) {
if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) return false;
}
// The extra var scope needs a note to be mapped from a pc.
if (!appendScopeNote(bce)) return false;
return checkEnvironmentChainLength(bce);
}
class DynamicBindingIter : public BindingIter {
public:
explicit DynamicBindingIter(GlobalSharedContext* sc)
: BindingIter(*sc->bindings) {}
explicit DynamicBindingIter(EvalSharedContext* sc)
: BindingIter(*sc->bindings, /* strict = */ false) {
MOZ_ASSERT(!sc->strict());
}
JSOp bindingOp() const {
switch (kind()) {
case BindingKind::Var:
return JSOP_DEFVAR;
case BindingKind::Let:
return JSOP_DEFLET;
case BindingKind::Const:
return JSOP_DEFCONST;
default:
MOZ_CRASH("Bad BindingKind");
}
}
};
bool BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce,
GlobalSharedContext* globalsc) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
bce->setVarEmitterScope(this);
if (!ensureCache(bce)) return false;
if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
// In self-hosting, it is incorrect to consult the global scope because
// self-hosted scripts are cloned into their target compartments before
// they are run. Instead of Global, Intrinsic is used for all names.
//
// Intrinsic lookups are redirected to the special intrinsics holder
// in the global object, into which any missing values are cloned
// lazily upon first access.
fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic());
auto createScope = [](JSContext* cx, HandleScope enclosing) {
MOZ_ASSERT(!enclosing);
return &cx->global()->emptyGlobalScope();
};
return internBodyScope(bce, createScope);
}
// Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops.
if (globalsc->bindings) {
for (DynamicBindingIter bi(globalsc); bi; bi++) {
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
JSAtom* name = bi.name();
if (!putNameInCache(bce, name, loc)) return false;
// Define the name in the prologue. Do not emit DEFVAR for
// functions that we'll emit DEFFUN for.
if (bi.isTopLevelFunction()) continue;
if (!bce->emitAtomOp(name, bi.bindingOp())) return false;
}
}
// Note that to save space, we don't add free names to the cache for
// global scopes. They are assumed to be global vars in the syntactic
// global scope, dynamic accesses under non-syntactic global scope.
if (globalsc->scopeKind() == ScopeKind::Global)
fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
else
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) {
MOZ_ASSERT(!enclosing);
return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
};
return internBodyScope(bce, createScope);
}
bool BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce,
EvalSharedContext* evalsc) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
bce->setVarEmitterScope(this);
if (!ensureCache(bce)) return false;
// For simplicity, treat all free name lookups in eval scripts as dynamic.
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
// Create the `var` scope. Note that there is also a lexical scope, created
// separately in emitScript().
auto createScope = [evalsc](JSContext* cx, HandleScope enclosing) {
ScopeKind scopeKind =
evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing);
};
if (!internBodyScope(bce, createScope)) return false;
if (hasEnvironment()) {
if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV)) return false;
} else {
// Resolve binding names and emit DEFVAR prologue ops if we don't have
// an environment (i.e., a sloppy eval not in a parameter expression).
// Eval scripts always have their own lexical scope, but non-strict
// scopes may introduce 'var' bindings to the nearest var scope.
//
// TODO: We may optimize strict eval bindings in the future to be on
// the frame. For now, handle everything dynamically.
if (!hasEnvironment() && evalsc->bindings) {
for (DynamicBindingIter bi(evalsc); bi; bi++) {
MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR);
if (bi.isTopLevelFunction()) continue;
if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR)) return false;
}
}
// As an optimization, if the eval does not have its own var
// environment and is directly enclosed in a global scope, then all
// free name lookups are global.
if (scope(bce)->enclosing()->is<GlobalScope>())
fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
}
return true;
}
bool BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce,
ModuleSharedContext* modulesc) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
bce->setVarEmitterScope(this);
if (!ensureCache(bce)) return false;
// Resolve body-level bindings, if there are any.
TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
Maybe<uint32_t> firstLexicalFrameSlot;
if (ModuleScope::Data* bindings = modulesc->bindings) {
BindingIter bi(*bindings);
for (; bi; bi++) {
if (!checkSlotLimits(bce, bi)) return false;
NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
if (!putNameInCache(bce, bi.name(), loc)) return false;
if (BindingKindIsLexical(bi.kind())) {
if (loc.kind() == NameLocation::Kind::FrameSlot &&
!firstLexicalFrameSlot)
firstLexicalFrameSlot = Some(loc.frameSlot());
if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) return false;
}
}
updateFrameFixedSlots(bce, bi);
} else {
nextFrameSlot_ = 0;
}
// Modules are toplevel, so any free names are global.
fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
// Put lexical frame slots in TDZ. Environment slots are poisoned during
// environment creation.
if (firstLexicalFrameSlot) {
if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd()))
return false;
}
// Create and intern the VM scope.
auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) {
return ModuleScope::create(cx, modulesc->bindings, modulesc->module(),
enclosing);
};
if (!internBodyScope(bce, createScope)) return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce) {
MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
if (!ensureCache(bce)) return false;
// 'with' make all accesses dynamic and unanalyzable.
fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
auto createScope = [](JSContext* cx, HandleScope enclosing) {
return WithScope::create(cx, enclosing);
};
if (!internScope(bce, createScope)) return false;
if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH)) return false;
if (!appendScopeNote(bce)) return false;
return checkEnvironmentChainLength(bce);
}
bool BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) {
// If we aren't leaving the scope due to a non-local jump (e.g., break),
// we must be the innermost scope.
MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck());
ScopeKind kind = scope(bce)->kind();
switch (kind) {
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV
: JSOP_DEBUGLEAVELEXICALENV))
return false;
break;
case ScopeKind::With:
if (!bce->emit1(JSOP_LEAVEWITH)) return false;
break;
case ScopeKind::ParameterExpressionVar:
MOZ_ASSERT(hasEnvironment());
if (!bce->emit1(JSOP_POPVARENV)) return false;
break;
case ScopeKind::Function:
case ScopeKind::FunctionBodyVar:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
case ScopeKind::Eval:
case ScopeKind::StrictEval:
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
case ScopeKind::Module:
break;
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("No wasm function scopes in JS");
}
// Finish up the scope if we are leaving it in LIFO fashion.
if (!nonLocal) {
// Popping scopes due to non-local jumps generate additional scope
// notes. See NonLocalExitControl::prepareForNonLocalJump.
if (ScopeKindIsInBody(kind)) {
// The extra function var scope is never popped once it's pushed,
// so its scope note extends until the end of any possible code.
uint32_t offset =
kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset();
bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue());
}
}
return true;
}
Maybe<MaybeCheckTDZ> BytecodeEmitter::TDZCheckCache::needsTDZCheck(
BytecodeEmitter* bce, JSAtom* name) {
if (!ensureCache(bce)) return Nothing();
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
if (p) return Some(p->value().wrapped);
MaybeCheckTDZ rv = CheckTDZ;
for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
if (it->cache_) {
if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
rv = p2->value();
break;
}
}
}
if (!cache_->add(p, name, rv)) {
ReportOutOfMemory(bce->cx);
return Nothing();
}
return Some(rv);
}
bool BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce,
JSAtom* name,
MaybeCheckTDZ check) {
if (!ensureCache(bce)) return false;
CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
if (p) {
MOZ_ASSERT(
!check,
"TDZ only needs to be checked once per binding per basic block.");
p->value() = check;
} else {
if (!cache_->add(p, name, check)) return false;
}
return true;
}
class MOZ_STACK_CLASS TryEmitter {
public:
enum Kind { TryCatch, TryCatchFinally, TryFinally };
enum ShouldUseRetVal { UseRetVal, DontUseRetVal };
enum ShouldUseControl {
UseControl,
DontUseControl,
};
private:
BytecodeEmitter* bce_;
Kind kind_;
ShouldUseRetVal retValKind_;
// Track jumps-over-catches and gosubs-to-finally for later fixup.
//
// When a finally block is active, non-local jumps (including
// jumps-over-catches) result in a GOSUB being written into the bytecode
// stream and fixed-up later.
//
// If ShouldUseControl is DontUseControl, all that handling is skipped.
// DontUseControl is used by yield* and the internal try-catch around
// IteratorClose. These internal uses must:
// * have only one catch block
// * have JSOP_GOTO at the end of catch-block
// * have no non-local-jump
// * don't use finally block for normal completion of try-block and
// catch-block
//
// Additionally, a finally block may be emitted when ShouldUseControl is
// DontUseControl, even if the kind is not TryCatchFinally or TryFinally,
// because GOSUBs are not emitted. This internal use shares the
// requirements as above.
Maybe<TryFinallyControl> controlInfo_;
int depth_;
unsigned noteIndex_;
ptrdiff_t tryStart_;
JumpList catchAndFinallyJump_;
JumpTarget tryEnd_;
JumpTarget finallyStart_;
enum State { Start, Try, TryEnd, Catch, CatchEnd, Finally, FinallyEnd, End };
State state_;
bool hasCatch() const {
return kind_ == TryCatch || kind_ == TryCatchFinally;
}
bool hasFinally() const {
return kind_ == TryCatchFinally || kind_ == TryFinally;
}
public:
TryEmitter(BytecodeEmitter* bce, Kind kind,
ShouldUseRetVal retValKind = UseRetVal,
ShouldUseControl controlKind = UseControl)
: bce_(bce),
kind_(kind),
retValKind_(retValKind),
depth_(0),
noteIndex_(0),
tryStart_(0),
state_(Start) {
if (controlKind == UseControl)
controlInfo_.emplace(
bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
finallyStart_.offset = 0;
}
bool emitJumpOverCatchAndFinally() {
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) return false;
return true;
}
bool emitTry() {
MOZ_ASSERT(state_ == Start);
// Since an exception can be thrown at any place inside the try block,
// we need to restore the stack and the scope chain before we transfer
// the control to the exception handler.
//
// For that we store in a try note associated with the catch or
// finally block the stack depth upon the try entry. The interpreter
// uses this depth to properly unwind the stack and the scope chain.
depth_ = bce_->stackDepth;
// Record the try location, then emit the try block.
if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) return false;
if (!bce_->emit1(JSOP_TRY)) return false;
tryStart_ = bce_->offset();
state_ = Try;
return true;
}
private:
bool emitTryEnd() {
MOZ_ASSERT(state_ == Try);
MOZ_ASSERT(depth_ == bce_->stackDepth);
// GOSUB to finally, if present.
if (hasFinally() && controlInfo_) {
if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) return false;
}
// Source note points to the jump at the end of the try block.
if (!bce_->setSrcNoteOffset(noteIndex_, 0,
bce_->offset() - tryStart_ + JSOP_TRY_LENGTH))
return false;
// Emit jump over catch and/or finally.
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) return false;
if (!bce_->emitJumpTarget(&tryEnd_)) return false;
return true;
}
public:
bool emitCatch() {
MOZ_ASSERT(state_ == Try);
if (!emitTryEnd()) return false;
MOZ_ASSERT(bce_->stackDepth == depth_);
if (retValKind_ == UseRetVal) {
// Clear the frame's return value that might have been set by the
// try block:
//
// eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
if (!bce_->emit1(JSOP_UNDEFINED)) return false;
if (!bce_->emit1(JSOP_SETRVAL)) return false;
}
state_ = Catch;
return true;
}
private:
bool emitCatchEnd() {
MOZ_ASSERT(state_ == Catch);
if (!controlInfo_) return true;
// gosub <finally>, if required.
if (hasFinally()) {
if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) return false;
MOZ_ASSERT(bce_->stackDepth == depth_);
// Jump over the finally block.
if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) return false;
}
return true;
}
public:
bool emitFinally(const Maybe<uint32_t>& finallyPos = Nothing()) {
// If we are using controlInfo_ (i.e., emitting a syntactic try
// blocks), we must have specified up front if there will be a finally
// close. For internal try blocks, like those emitted for yield* and
// IteratorClose inside for-of loops, we can emitFinally even without
// specifying up front, since the internal try blocks emit no GOSUBs.
if (!controlInfo_) {
if (kind_ == TryCatch) kind_ = TryCatchFinally;
} else {
MOZ_ASSERT(hasFinally());
}
if (state_ == Try) {
if (!emitTryEnd()) return false;
} else {
MOZ_ASSERT(state_ == Catch);
if (!emitCatchEnd()) return false;
}
MOZ_ASSERT(bce_->stackDepth == depth_);
if (!bce_->emitJumpTarget(&finallyStart_)) return false;
if (controlInfo_) {
// Fix up the gosubs that might have been emitted before non-local
// jumps to the finally code.
bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
// Indicate that we're emitting a subroutine body.
controlInfo_->setEmittingSubroutine();
}
if (finallyPos) {
if (!bce_->updateSourceCoordNotes(finallyPos.value())) return false;
}
if (!bce_->emit1(JSOP_FINALLY)) return false;
if (retValKind_ == UseRetVal) {
if (!bce_->emit1(JSOP_GETRVAL)) return false;
// Clear the frame's return value to make break/continue return
// correct value even if there's no other statement before them:
//
// eval("x: try { 1 } finally { break x; }"); // undefined, not 1
if (!bce_->emit1(JSOP_UNDEFINED)) return false;
if (!bce_->emit1(JSOP_SETRVAL)) return false;
}
state_ = Finally;
return true;
}
private:
bool emitFinallyEnd() {
MOZ_ASSERT(state_ == Finally);
if (retValKind_ == UseRetVal) {
if (!bce_->emit1(JSOP_SETRVAL)) return false;
}
if (!bce_->emit1(JSOP_RETSUB)) return false;
bce_->hasTryFinally = true;
return true;
}
public:
bool emitEnd() {
if (state_ == Catch) {
MOZ_ASSERT(!hasFinally());
if (!emitCatchEnd()) return false;
} else {
MOZ_ASSERT(state_ == Finally);
MOZ_ASSERT(hasFinally());
if (!emitFinallyEnd()) return false;
}
MOZ_ASSERT(bce_->stackDepth == depth_);
// ReconstructPCStack needs a NOP here to mark the end of the last
// catch block.
if (!bce_->emit1(JSOP_NOP)) return false;
// Fix up the end-of-try/catch jumps to come here.
if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) return false;
// Add the try note last, to let post-order give us the right ordering
// (first to last for a given nesting level, inner to outer by level).
if (hasCatch()) {
if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_,
tryEnd_.offset))
return false;
}
// If we've got a finally, mark try+catch region with additional
// trynote to catch exceptions (re)thrown from a catch block or
// for the try{}finally{} case.
if (hasFinally()) {
if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_,
finallyStart_.offset))
return false;
}
state_ = End;
return true;
}
};
class MOZ_STACK_CLASS IfThenElseEmitter {
BytecodeEmitter* bce_;
JumpList jumpAroundThen_;
JumpList jumpsAroundElse_;
unsigned noteIndex_;
int32_t thenDepth_;
#ifdef DEBUG
int32_t pushed_;
bool calculatedPushed_;
#endif
enum State { Start, If, Cond, IfElse, Else, End };
State state_;
public:
explicit IfThenElseEmitter(BytecodeEmitter* bce)
: bce_(bce),
noteIndex_(-1),
thenDepth_(0),
#ifdef DEBUG
pushed_(0),
calculatedPushed_(false),
#endif
state_(Start) {
}
~IfThenElseEmitter() {}
private:
bool emitIf(State nextState) {
MOZ_ASSERT(state_ == Start || state_ == Else);
MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond);
// Clear jumpAroundThen_ offset that points previous JSOP_IFEQ.
if (state_ == Else) jumpAroundThen_ = JumpList();
// Emit an annotated branch-if-false around the then part.
SrcNoteType type =
nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND;
if (!bce_->newSrcNote(type, ¬eIndex_)) return false;
if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) return false;
// To restore stack depth in else part, save depth of the then part.
#ifdef DEBUG
// If DEBUG, this is also necessary to calculate |pushed_|.
thenDepth_ = bce_->stackDepth;
#else
if (nextState == IfElse || nextState == Cond) thenDepth_ = bce_->stackDepth;
#endif
state_ = nextState;
return true;
}
public:
bool emitIf() { return emitIf(If); }
bool emitCond() { return emitIf(Cond); }
bool emitIfElse() { return emitIf(IfElse); }
bool emitElse() {
MOZ_ASSERT(state_ == IfElse || state_ == Cond);
calculateOrCheckPushed();
// Emit a jump from the end of our then part around the else part. The
// patchJumpsToTarget call at the bottom of this function will fix up
// the offset with jumpsAroundElse value.
if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) return false;
// Ensure the branch-if-false comes here, then emit the else.
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) return false;
// Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to
// jump, for IonMonkey's benefit. We can't just "back up" from the pc
// of the else clause, because we don't know whether an extended
// jump was required to leap from the end of the then clause over
// the else clause.
if (!bce_->setSrcNoteOffset(
noteIndex_, 0, jumpsAroundElse_.offset - jumpAroundThen_.offset)) {
return false;
}
// Restore stack depth of the then part.
bce_->stackDepth = thenDepth_;
state_ = Else;
return true;
}
bool emitEnd() {
MOZ_ASSERT(state_ == If || state_ == Else);
calculateOrCheckPushed();
if (state_ == If) {
// No else part, fixup the branch-if-false to come here.
if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) return false;
}
// Patch all the jumps around else parts.
if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) return false;
state_ = End;
return true;
}
void calculateOrCheckPushed() {
#ifdef DEBUG
if (!calculatedPushed_) {
pushed_ = bce_->stackDepth - thenDepth_;
calculatedPushed_ = true;
} else {
MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_);
}
#endif
}
#ifdef DEBUG
int32_t pushed() const { return pushed_; }
int32_t popped() const { return -pushed_; }
#endif
};
class ForOfLoopControl : public LoopControl {
using EmitterScope = BytecodeEmitter::EmitterScope;
// The stack depth of the iterator.
int32_t iterDepth_;
// for-of loops, when throwing from non-iterator code (i.e. from the body
// or from evaluating the LHS of the loop condition), need to call
// IteratorClose. This is done by enclosing non-iterator code with
// try-catch and call IteratorClose in `catch` block.
// If IteratorClose itself throws, we must not re-call IteratorClose. Since
// non-local jumps like break and return call IteratorClose, whenever a
// non-local jump is emitted, we must tell catch block not to perform
// IteratorClose.
//
// for (x of y) {
// // Operations for iterator (IteratorNext etc) are outside of
// // try-block.
// try {
// ...
// if (...) {
// // Before non-local jump, clear iterator on the stack to tell
// // catch block not to perform IteratorClose.
// tmpIterator = iterator;
// iterator = undefined;
// IteratorClose(tmpIterator, { break });
// break;
// }
// ...
// } catch (e) {
// // Just throw again when iterator is cleared by non-local jump.
// if (iterator === undefined)
// throw e;
// IteratorClose(iterator, { throw, e });
// }
// }
Maybe<TryEmitter> tryCatch_;
// Used to track if any yields were emitted between calls to to
// emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose.
uint32_t numYieldsAtBeginCodeNeedingIterClose_;
bool allowSelfHosted_;
IteratorKind iterKind_;
public:
ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth,
bool allowSelfHosted, IteratorKind iterKind)
: LoopControl(bce, StatementKind::ForOfLoop),
iterDepth_(iterDepth),
numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
allowSelfHosted_(allowSelfHosted),
iterKind_(iterKind) {}
bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
TryEmitter::DontUseControl);
if (!tryCatch_->emitTry()) return false;
MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
numYieldsAtBeginCodeNeedingIterClose_ =
bce->yieldAndAwaitOffsetList.numYields;
return true;
}
bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
if (!tryCatch_->emitCatch()) // ITER ...
return false;
if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION
return false;
unsigned slotFromTop = bce->stackDepth - iterDepth_;
if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
return false;
// If ITER is undefined, it means the exception is thrown by
// IteratorClose for non-local jump, and we should't perform
// IteratorClose again here.
if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF
return false;
if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE
return false;
IfThenElseEmitter ifIteratorIsNotClosed(bce);
if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION
return false;
MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_));
if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER
return false;
if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Throw))
return false; // ITER ... EXCEPTION
if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION
return false;
if (!bce->emit1(JSOP_THROW)) // ITER ...
return false;
// If any yields were emitted, then this for-of loop is inside a star
// generator and must handle the case of Generator.return. Like in
// yield*, it is handled with a finally block.
uint32_t numYieldsEmitted = bce->yieldAndAwaitOffsetList.numYields;
if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
if (!tryCatch_->emitFinally()) return false;
IfThenElseEmitter ifGeneratorClosing(bce);
if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING
return false;
if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE
return false;
if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER
return false;
if (!emitIteratorCloseInInnermostScope(bce, CompletionKind::Normal))
return false; // ITER ... FTYPE FVALUE
if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE
return false;
}
if (!tryCatch_->emitEnd()) return false;
tryCatch_.reset();
numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;
return true;
}
bool emitIteratorCloseInInnermostScope(
BytecodeEmitter* bce,
CompletionKind completionKind = CompletionKind::Normal) {
return emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(),
completionKind);
}
bool emitIteratorCloseInScope(
BytecodeEmitter* bce, EmitterScope& currentScope,
CompletionKind completionKind = CompletionKind::Normal) {
ptrdiff_t start = bce->offset();
if (!bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
allowSelfHosted_)) {
return false;
}
ptrdiff_t end = bce->offset();
return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end);
}
bool emitPrepareForNonLocalJumpFromScope(BytecodeEmitter* bce,
EmitterScope& currentScope,
bool isTarget) {
// Pop unnecessary value from the stack. Effectively this means
// leaving try-catch block. However, the performing IteratorClose can
// reach the depth for try-catch, and effectively re-enter the
// try-catch block.
if (!bce->emit1(JSOP_POP)) // NEXT ITER
return false;
// Pop the iterator's next method.
if (!bce->emit1(JSOP_SWAP)) // ITER NEXT
return false;
if (!bce->emit1(JSOP_POP)) // ITER
return false;
// Clear ITER slot on the stack to tell catch block to avoid performing
// IteratorClose again.
if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF
return false;
if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER
return false;
if (!emitIteratorCloseInScope(bce, currentScope,
CompletionKind::Normal)) // UNDEF
return false;
if (isTarget) {
// At the level of the target block, there's bytecode after the
// loop that will pop the next method, the iterator, and the
// value, so push two undefineds to balance the stack.
if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF
return false;
if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF
return false;
} else {
if (!bce->emit1(JSOP_POP)) //
return false;
}
return true;
}
};
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
const EitherParser<FullParseHandler>& parser,
SharedContext* sc, HandleScript script,
Handle<LazyScript*> lazyScript,
uint32_t lineNum, EmitterMode emitterMode)
: sc(sc),
cx(sc->context),
parent(parent),
script(cx, script),
lazyScript(cx, lazyScript),
prologue(cx, lineNum),
main(cx, lineNum),
current(&main),
parser(parser),
atomIndices(cx->frontendCollectionPool()),
firstLine(lineNum),
maxFixedSlots(0),
maxStackDepth(0),
stackDepth(0),
emitLevel(0),
bodyScopeIndex(UINT32_MAX),
varEmitterScope(nullptr),
innermostNestableControl(nullptr),
innermostEmitterScope_(nullptr),
innermostTDZCheckCache(nullptr),
#ifdef DEBUG
unstableEmitterScope(false),
#endif
constList(cx),
scopeList(cx),
tryNoteList(cx),
scopeNoteList(cx),
yieldAndAwaitOffsetList(cx),
typesetCount(0),
hasSingletons(false),
hasTryFinally(false),
emittingRunOnceLambda(false),
emitterMode(emitterMode),
functionBodyEndPosSet(false) {
MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
}
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
const EitherParser<FullParseHandler>& parser,
SharedContext* sc, HandleScript script,
Handle<LazyScript*> lazyScript,
TokenPos bodyPosition, EmitterMode emitterMode)
: BytecodeEmitter(
parent, parser, sc, script, lazyScript,
parser.tokenStream().srcCoords.lineNum(bodyPosition.begin),
emitterMode) {
setFunctionBodyEndPos(bodyPosition);
}
bool BytecodeEmitter::init() { return atomIndices.acquire(cx); }
template <typename Predicate /* (NestableControl*) -> bool */>
BytecodeEmitter::NestableControl* BytecodeEmitter::findInnermostNestableControl(
Predicate predicate) const {
return NestableControl::findNearest(innermostNestableControl, predicate);
}
template <typename T>
T* BytecodeEmitter::findInnermostNestableControl() const {
return NestableControl::findNearest<T>(innermostNestableControl);
}
template <typename T, typename Predicate /* (T*) -> bool */>
T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const {
return NestableControl::findNearest<T>(innermostNestableControl, predicate);
}
NameLocation BytecodeEmitter::lookupName(JSAtom* name) {
return innermostEmitterScope()->lookup(this, name);
}
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
JSAtom* name, EmitterScope* target) {
return innermostEmitterScope()->locationBoundInScope(name, target);
}
Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInFunctionScope(
JSAtom* name, EmitterScope* source) {
EmitterScope* funScope = source;
while (!funScope->scope(this)->is<FunctionScope>())
funScope = funScope->enclosingInFrame();
return source->locationBoundInScope(name, funScope);
}
bool BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset) {
size_t oldLength = code().length();
*offset = ptrdiff_t(oldLength);
size_t newLength = oldLength + size_t(delta);
if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) {
ReportAllocationOverflow(cx);
return false;
}
if (!code().growBy(delta)) {
return false;
}
return true;
}
void BytecodeEmitter::updateDepth(ptrdiff_t target) {
jsbytecode* pc = code(target);
int nuses = StackUses(pc);
int ndefs = StackDefs(pc);
stackDepth -= nuses;
MOZ_ASSERT(stackDepth >= 0);
stackDepth += ndefs;
if ((uint32_t)stackDepth > maxStackDepth) maxStackDepth = stackDepth;
}
#ifdef DEBUG
bool BytecodeEmitter::checkStrictOrSloppy(JSOp op) {
if (IsCheckStrictOp(op) && !sc->strict()) return false;
if (IsCheckSloppyOp(op) && sc->strict()) return false;
return true;
}
#endif
bool BytecodeEmitter::emit1(JSOp op) {
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t offset;
if (!emitCheck(1, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) {
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t offset;
if (!emitCheck(2, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
code[1] = jsbytecode(op1);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) {
MOZ_ASSERT(checkStrictOrSloppy(op));
/* These should filter through emitVarOp. */
MOZ_ASSERT(!IsArgOp(op));
MOZ_ASSERT(!IsLocalOp(op));
ptrdiff_t offset;
if (!emitCheck(3, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
code[1] = op1;
code[2] = op2;
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emitN(JSOp op, size_t extra, ptrdiff_t* offset) {
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t length = 1 + ptrdiff_t(extra);
ptrdiff_t off;
if (!emitCheck(length, &off)) return false;
jsbytecode* code = this->code(off);
code[0] = jsbytecode(op);
/* The remaining |extra| bytes are set by the caller */
/*
* Don't updateDepth if op's use-count comes from the immediate
* operand yet to be stored in the extra bytes after op.
*/
if (CodeSpec[op].nuses >= 0) updateDepth(off);
if (offset) *offset = off;
return true;
}
bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) {
ptrdiff_t off = offset();
// Alias consecutive jump targets.
if (off == current->lastTarget.offset + ptrdiff_t(JSOP_JUMPTARGET_LENGTH)) {
target->offset = current->lastTarget.offset;
return true;
}
target->offset = off;
current->lastTarget.offset = off;
if (!emit1(JSOP_JUMPTARGET)) return false;
return true;
}
void JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset) {
SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset);
offset = jumpOffset;
}
void JumpList::patchAll(jsbytecode* code, JumpTarget target) {
ptrdiff_t delta;
for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) {
jsbytecode* pc = &code[jumpOffset];
MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL);
delta = GET_JUMP_OFFSET(pc);
MOZ_ASSERT(delta < 0);
ptrdiff_t span = target.offset - jumpOffset;
SET_JUMP_OFFSET(pc, span);
}
}
bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) {
ptrdiff_t offset;
if (!emitCheck(5, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
MOZ_ASSERT(-1 <= jump->offset && jump->offset < offset);
jump->push(this->code(0), offset);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) {
if (!emitJumpNoFallthrough(op, jump)) return false;
if (BytecodeFallsThrough(op)) {
JumpTarget fallthrough;
if (!emitJumpTarget(&fallthrough)) return false;
}
return true;
}
bool BytecodeEmitter::emitBackwardJump(JSOp op, JumpTarget target,
JumpList* jump,
JumpTarget* fallthrough) {
if (!emitJumpNoFallthrough(op, jump)) return false;
patchJumpsToTarget(*jump, target);
// Unconditionally create a fallthrough for closing iterators, and as a
// target for break statements.
if (!emitJumpTarget(fallthrough)) return false;
return true;
}
void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) {
MOZ_ASSERT(-1 <= jump.offset && jump.offset <= offset());
MOZ_ASSERT(0 <= target.offset && target.offset <= offset());
MOZ_ASSERT_IF(jump.offset != -1 && target.offset + 4 <= offset(),
BytecodeIsJumpTarget(JSOp(*code(target.offset))));
jump.patchAll(code(0), target);
}
bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) {
if (jump.offset == -1) return true;
JumpTarget target;
if (!emitJumpTarget(&target)) return false;
patchJumpsToTarget(jump, target);
return true;
}
bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) {
if (pn && !updateSourceCoordNotes(pn->pn_pos.begin)) return false;
return emit3(op, ARGC_LO(argc), ARGC_HI(argc));
}
bool BytecodeEmitter::emitDupAt(unsigned slotFromTop) {
MOZ_ASSERT(slotFromTop < unsigned(stackDepth));
if (slotFromTop == 0) return emit1(JSOP_DUP);
if (slotFromTop >= JS_BIT(24)) {
reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
return false;
}
ptrdiff_t off;
if (!emitN(JSOP_DUPAT, 3, &off)) return false;
jsbytecode* pc = code(off);
SET_UINT24(pc, slotFromTop);
return true;
}
bool BytecodeEmitter::emitPopN(unsigned n) {
MOZ_ASSERT(n != 0);
if (n == 1) return emit1(JSOP_POP);
// 2 JSOP_POPs (2 bytes) are shorter than JSOP_POPN (3 bytes).
if (n == 2) return emit1(JSOP_POP) && emit1(JSOP_POP);
return emitUint16Operand(JSOP_POPN, n);
}
bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) {
return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
}
bool BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) {
return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind));
}
static inline unsigned LengthOfSetLine(unsigned line) {
return 1 /* SRC_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
}
/* Updates line number notes, not column notes. */
bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) {
TokenStreamAnyChars* ts = &parser.tokenStream();
bool onThisLine;
if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine)) {
ts->reportErrorNoOffset(JSMSG_OUT_OF_MEMORY);
return false;
}
if (!onThisLine) {
unsigned line = ts->srcCoords.lineNum(offset);
unsigned delta = line - currentLine();
/*
* Encode any change in the current source line number by using
* either several SRC_NEWLINE notes or just one SRC_SETLINE note,
* whichever consumes less space.
*
* NB: We handle backward line number deltas (possible with for
* loops where the update part is emitted after the body, but its
* line number is <= any line number in the body) here by letting
* unsigned delta_ wrap to a very large number, which triggers a
* SRC_SETLINE.
*/
current->currentLine = line;
current->lastColumn = 0;
if (delta >= LengthOfSetLine(line)) {
if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line))) return false;
} else {
do {
if (!newSrcNote(SRC_NEWLINE)) return false;
} while (--delta != 0);
}
}
return true;
}
/* Updates the line number and column number information in the source notes. */
bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) {
if (!updateLineNumberNotes(offset)) return false;
uint32_t columnIndex = parser.tokenStream().srcCoords.columnIndex(offset);
ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn);
if (colspan != 0) {
// If the column span is so large that we can't store it, then just
// discard this information. This can happen with minimized or otherwise
// machine-generated code. Even gigantic column numbers are still
// valuable if you have a source map to relate them to something real;
// but it's better to fail soft here.
if (!SN_REPRESENTABLE_COLSPAN(colspan)) return true;
if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) return false;
current->lastColumn = columnIndex;
}
return true;
}
bool BytecodeEmitter::emitLoopHead(ParseNode* nextpn, JumpTarget* top) {
if (nextpn) {
/*
* Try to give the JSOP_LOOPHEAD the same line number as the next
* instruction. nextpn is often a block, in which case the next
* instruction typically comes from the first statement inside.
*/
if (nextpn->isKind(ParseNodeKind::LexicalScope))
nextpn = nextpn->scopeBody();
MOZ_ASSERT_IF(nextpn->isKind(ParseNodeKind::StatementList),
nextpn->isArity(PN_LIST));
if (nextpn->isKind(ParseNodeKind::StatementList) && nextpn->pn_head)
nextpn = nextpn->pn_head;
if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) return false;
}
*top = {offset()};
return emit1(JSOP_LOOPHEAD);
}
bool BytecodeEmitter::emitLoopEntry(ParseNode* nextpn, JumpList entryJump) {
if (nextpn) {
/* Update the line number, as for LOOPHEAD. */
if (nextpn->isKind(ParseNodeKind::LexicalScope))
nextpn = nextpn->scopeBody();
MOZ_ASSERT_IF(nextpn->isKind(ParseNodeKind::StatementList),
nextpn->isArity(PN_LIST));
if (nextpn->isKind(ParseNodeKind::StatementList) && nextpn->pn_head)
nextpn = nextpn->pn_head;
if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) return false;
}
JumpTarget entry{offset()};
patchJumpsToTarget(entryJump, entry);
LoopControl& loopInfo = innermostNestableControl->as<LoopControl>();
MOZ_ASSERT(loopInfo.loopDepth() > 0);
uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(
loopInfo.loopDepth(), loopInfo.canIonOsr());
return emit2(JSOP_LOOPENTRY, loopDepthAndFlags);
}
void BytecodeEmitter::checkTypeSet(JSOp op) {
if (CodeSpec[op].format & JOF_TYPESET) {
if (typesetCount < UINT16_MAX) typesetCount++;
}
}
bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) {
MOZ_ASSERT(operand <= UINT16_MAX);
if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) return false;
checkTypeSet(op);
return true;
}
bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) {
ptrdiff_t off;
if (!emitN(op, 4, &off)) return false;
SET_UINT32(code(off), operand);
checkTypeSet(op);
return true;
}
namespace {
class NonLocalExitControl {
public:
enum Kind {
// IteratorClose is handled especially inside the exception unwinder.
Throw,
// A 'continue' statement does not call IteratorClose for the loop it
// is continuing, i.e. excluding the target loop.
Continue,
// A 'break' or 'return' statement does call IteratorClose for the
// loop it is breaking out of or returning from, i.e. including the
// target loop.
Break,
Return
};
private:
BytecodeEmitter* bce_;
const uint32_t savedScopeNoteIndex_;
const int savedDepth_;
uint32_t openScopeNoteIndex_;
Kind kind_;
NonLocalExitControl(const NonLocalExitControl&) = delete;
MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope);
public:
NonLocalExitControl(BytecodeEmitter* bce, Kind kind)
: bce_(bce),
savedScopeNoteIndex_(bce->scopeNoteList.length()),
savedDepth_(bce->stackDepth),
openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()),
kind_(kind) {}
~NonLocalExitControl() {
for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length();
n++)
bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue());
bce_->stackDepth = savedDepth_;
}
MOZ_MUST_USE bool prepareForNonLocalJump(
BytecodeEmitter::NestableControl* target);
MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() {
return prepareForNonLocalJump(nullptr);
}
};
bool NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es) {
if (!es->leave(bce_, /* nonLocal = */ true)) return false;
// As we pop each scope due to the non-local jump, emit notes that
// record the extent of the enclosing scope. These notes will have
// their ends recorded in ~NonLocalExitControl().
uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex;
if (es->enclosingInFrame())
enclosingScopeIndex = es->enclosingInFrame()->index();
if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(),
bce_->inPrologue(), openScopeNoteIndex_))
return false;
openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1;
return true;
}
/*
* Emit additional bytecode(s) for non-local jumps.
*/
bool NonLocalExitControl::prepareForNonLocalJump(
BytecodeEmitter::NestableControl* target) {
using NestableControl = BytecodeEmitter::NestableControl;
using EmitterScope = BytecodeEmitter::EmitterScope;
EmitterScope* es = bce_->innermostEmitterScope();
int npops = 0;
AutoCheckUnstableEmitterScope cues(bce_);
// For 'continue', 'break', and 'return' statements, emit IteratorClose
// bytecode inline. 'continue' statements do not call IteratorClose for
// the loop they are continuing.
bool emitIteratorClose =
kind_ == Continue || kind_ == Break || kind_ == Return;
bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue;
auto flushPops = [&npops](BytecodeEmitter* bce) {
if (npops && !bce->emitPopN(npops)) return false;
npops = 0;
return true;
};
// Walk the nestable control stack and patch jumps.
for (NestableControl* control = bce_->innermostNestableControl;
control != target; control = control->enclosing()) {
// Walk the scope stack and leave the scopes we entered. Leaving a scope
// may emit administrative ops like JSOP_POPLEXICALENV but never anything
// that manipulates the stack.
for (; es != control->emitterScope(); es = es->enclosingInFrame()) {
if (!leaveScope(es)) return false;
}
switch (control->kind()) {
case StatementKind::Finally: {
TryFinallyControl& finallyControl = control->as<TryFinallyControl>();
if (finallyControl.emittingSubroutine()) {
/*
* There's a [exception or hole, retsub pc-index] pair and the
* possible return value on the stack that we need to pop.
*/
npops += 3;
} else {
if (!flushPops(bce_)) return false;
if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ...
return false;
}
break;
}
case StatementKind::ForOfLoop:
if (emitIteratorClose) {
if (!flushPops(bce_)) return false;
ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>();
if (!loopinfo.emitPrepareForNonLocalJumpFromScope(
bce_, *es,
/* isTarget = */ false)) {
// [stack] ...
return false;
}
} else {
// The iterator next method, the iterator, and the current
// value are on the stack.
npops += 3;
}
break;
case StatementKind::ForInLoop:
if (!flushPops(bce_)) return false;
// The iterator and the current value are on the stack.
if (!bce_->emit1(JSOP_POP)) // ... ITER
return false;
if (!bce_->emit1(JSOP_ENDITER)) // ...
return false;
break;
default:
break;
}
}
if (!flushPops(bce_)) return false;
if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) {
ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>();
if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es,
/* isTarget = */ true)) {
// [stack] ... UNDEF UNDEF UNDEF
return false;
}
}
EmitterScope* targetEmitterScope =
target ? target->emitterScope() : bce_->varEmitterScope;
for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
if (!leaveScope(es)) return false;
}
return true;
}
} // anonymous namespace
bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist,
SrcNoteType noteType) {
NonLocalExitControl nle(this, noteType == SRC_CONTINUE
? NonLocalExitControl::Continue
: NonLocalExitControl::Break);
if (!nle.prepareForNonLocalJump(target)) return false;
if (noteType != SRC_NULL) {
if (!newSrcNote(noteType)) return false;
}
return emitJump(JSOP_GOTO, jumplist);
}
Scope* BytecodeEmitter::innermostScope() const {
return innermostEmitterScope()->scope(this);
}
bool BytecodeEmitter::emitIndex32(JSOp op, uint32_t index) {
MOZ_ASSERT(checkStrictOrSloppy(op));
const size_t len = 1 + UINT32_INDEX_LEN;
MOZ_ASSERT(len == size_t(CodeSpec[op].length));
ptrdiff_t offset;
if (!emitCheck(len, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
SET_UINT32_INDEX(code, index);
checkTypeSet(op);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) {
MOZ_ASSERT(checkStrictOrSloppy(op));
const size_t len = CodeSpec[op].length;
MOZ_ASSERT(len >= 1 + UINT32_INDEX_LEN);
ptrdiff_t offset;
if (!emitCheck(len, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = jsbytecode(op);
SET_UINT32_INDEX(code, index);
checkTypeSet(op);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) {
MOZ_ASSERT(atom);
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
// .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
// JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
// It's safe to emit .this lookups though because |with| objects skip
// those.
MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
atom != cx->names().dotGenerator);
if (op == JSOP_GETPROP && atom == cx->names().length) {
/* Specialize length accesses for the interpreter. */
op = JSOP_LENGTH;
}
uint32_t index;
if (!makeAtomIndex(atom, &index)) return false;
return emitIndexOp(op, index);
}
bool BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op) {
MOZ_ASSERT(pn->pn_atom != nullptr);
return emitAtomOp(pn->pn_atom, op);
}
bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
MOZ_ASSERT(index < scopeList.length());
return emitIndex32(op, index);
}
bool BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
MOZ_ASSERT(index < objectList.length);
return emitIndex32(op, index);
}
bool BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) {
return emitInternedObjectOp(objectList.add(objbox), op);
}
bool BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2,
JSOp op) {
uint32_t index = objectList.add(objbox1);
objectList.add(objbox2);
return emitInternedObjectOp(index, op);
}
bool BytecodeEmitter::emitRegExp(uint32_t index) {
return emitIndex32(JSOP_REGEXP, index);
}
bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) {
MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
MOZ_ASSERT(IsLocalOp(op));
ptrdiff_t off;
if (!emitN(op, LOCALNO_LEN, &off)) return false;
SET_LOCALNO(code(off), slot);
return true;
}
bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) {
MOZ_ASSERT(IsArgOp(op));
ptrdiff_t off;
if (!emitN(op, ARGNO_LEN, &off)) return false;
SET_ARGNO(code(off), slot);
return true;
}
bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) {
MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD);
unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length);
ptrdiff_t off;
if (!emitN(op, n, &off)) return false;
jsbytecode* pc = code(off);
SET_ENVCOORD_HOPS(pc, ec.hops());
pc += ENVCOORD_HOPS_LEN;
SET_ENVCOORD_SLOT(pc, ec.slot());
pc += ENVCOORD_SLOT_LEN;
checkTypeSet(op);
return true;
}
static JSOp GetIncDecInfo(ParseNodeKind kind, bool* post) {
MOZ_ASSERT(kind == ParseNodeKind::PostIncrement ||
kind == ParseNodeKind::PreIncrement ||
kind == ParseNodeKind::PostDecrement ||
kind == ParseNodeKind::PreDecrement);
*post = kind == ParseNodeKind::PostIncrement ||
kind == ParseNodeKind::PostDecrement;
return (kind == ParseNodeKind::PostIncrement ||
kind == ParseNodeKind::PreIncrement)
? JSOP_ADD
: JSOP_SUB;
}
JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) {
switch (op) {
case JSOP_SETNAME:
if (sc->strict()) op = JSOP_STRICTSETNAME;
break;
case JSOP_SETGNAME:
if (sc->strict()) op = JSOP_STRICTSETGNAME;
break;
default:;
}
return op;
}
bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) {
if (!CheckRecursionLimit(cx)) return false;
restart:
switch (pn->getKind()) {
// Trivial cases with no side effects.
case ParseNodeKind::EmptyStatement:
case ParseNodeKind::String:
case ParseNodeKind::TemplateString:
case ParseNodeKind::RegExp:
case ParseNodeKind::True:
case ParseNodeKind::False:
case ParseNodeKind::Null:
case ParseNodeKind::RawUndefined:
case ParseNodeKind::Elision:
case ParseNodeKind::Generator:
case ParseNodeKind::Number:
case ParseNodeKind::ObjectPropertyName:
MOZ_ASSERT(pn->isArity(PN_NULLARY));
*answer = false;
return true;
// |this| can throw in derived class constructors, including nested arrow
// functions or eval.
case ParseNodeKind::This:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = sc->needsThisTDZChecks();
return true;
// Trivial binary nodes with more token pos holders.
case ParseNodeKind::NewTarget:
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::PosHolder));
*answer = false;
return true;
case ParseNodeKind::Break:
case ParseNodeKind::Continue:
case ParseNodeKind::Debugger:
MOZ_ASSERT(pn->isArity(PN_NULLARY));
*answer = true;
return true;
// Watch out for getters!
case ParseNodeKind::Dot:
MOZ_ASSERT(pn->isArity(PN_NAME));
*answer = true;
return true;
// Unary cases with side effects only if the child has them.
case ParseNodeKind::TypeOfExpr:
case ParseNodeKind::Void:
case ParseNodeKind::Not:
MOZ_ASSERT(pn->isArity(PN_UNARY));
return checkSideEffects(pn->pn_kid, answer);
// Even if the name expression is effect-free, performing ToPropertyKey on
// it might not be effect-free:
//
// RegExp.prototype.toString = () => { throw 42; };
// ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42
//
// function Q() {
// ({ [new.target]: 0 });
// }
// Q.toString = () => { throw 17; };
// new Q; // new.target will be Q, ToPropertyKey(Q) throws 17
case ParseNodeKind::ComputedName:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// Looking up or evaluating the associated name could throw.
case ParseNodeKind::TypeOfName:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// This unary case has side effects on the enclosing object, sure. But
// that's not the question this function answers: it's whether the
// operation may have a side effect on something *other* than the result
// of the overall operation in which it's embedded. The answer to that
// is no, because an object literal having a mutated prototype only
// produces a value, without affecting anything else.
case ParseNodeKind::MutateProto:
MOZ_ASSERT(pn->isArity(PN_UNARY));
return checkSideEffects(pn->pn_kid, answer);
// Unary cases with obvious side effects.
case ParseNodeKind::PreIncrement:
case ParseNodeKind::PostIncrement:
case ParseNodeKind::PreDecrement:
case ParseNodeKind::PostDecrement:
case ParseNodeKind::Throw:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// These might invoke valueOf/toString, even with a subexpression without
// side effects! Consider |+{ valueOf: null, toString: null }|.
case ParseNodeKind::BitNot:
case ParseNodeKind::Pos:
case ParseNodeKind::Neg:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// This invokes the (user-controllable) iterator protocol.
case ParseNodeKind::Spread:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
case ParseNodeKind::InitialYield:
case ParseNodeKind::YieldStar:
case ParseNodeKind::Yield:
case ParseNodeKind::Await:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// Deletion generally has side effects, even if isolated cases have none.
case ParseNodeKind::DeleteName:
case ParseNodeKind::DeleteProp:
case ParseNodeKind::DeleteElem:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// Deletion of a non-Reference expression has side effects only through
// evaluating the expression.
case ParseNodeKind::DeleteExpr: {
MOZ_ASSERT(pn->isArity(PN_UNARY));
ParseNode* expr = pn->pn_kid;
return checkSideEffects(expr, answer);
}
case ParseNodeKind::ExpressionStatement:
MOZ_ASSERT(pn->isArity(PN_UNARY));
return checkSideEffects(pn->pn_kid, answer);
// Binary cases with obvious side effects.
case ParseNodeKind::Assign:
case ParseNodeKind::AddAssign:
case ParseNodeKind::SubAssign:
case ParseNodeKind::BitOrAssign:
case ParseNodeKind::BitXorAssign:
case ParseNodeKind::BitAndAssign:
case ParseNodeKind::LshAssign:
case ParseNodeKind::RshAssign:
case ParseNodeKind::UrshAssign:
case ParseNodeKind::MulAssign:
case ParseNodeKind::DivAssign:
case ParseNodeKind::ModAssign:
case ParseNodeKind::PowAssign:
case ParseNodeKind::SetThis:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
case ParseNodeKind::StatementList:
// Strict equality operations and logical operators are well-behaved and
// perform no conversions.
case ParseNodeKind::Or:
case ParseNodeKind::And:
case ParseNodeKind::StrictEq:
case ParseNodeKind::StrictNe:
// Any subexpression of a comma expression could be effectful.
case ParseNodeKind::Comma:
MOZ_ASSERT(pn->pn_count > 0);
MOZ_FALLTHROUGH;
// Subcomponents of a literal may be effectful.
case ParseNodeKind::Array:
case ParseNodeKind::Object:
MOZ_ASSERT(pn->isArity(PN_LIST));
for (ParseNode* item = pn->pn_head; item; item = item->pn_next) {
if (!checkSideEffects(item, answer)) return false;
if (*answer) return true;
}
return true;
// Most other binary operations (parsed as lists in SpiderMonkey) may
// perform conversions triggering side effects. Math operations perform
// ToNumber and may fail invoking invalid user-defined toString/valueOf:
// |5 < { toString: null }|. |instanceof| throws if provided a
// non-object constructor: |null instanceof null|. |in| throws if given
// a non-object RHS: |5 in null|.
case ParseNodeKind::BitOr:
case ParseNodeKind::BitXor:
case ParseNodeKind::BitAnd:
case ParseNodeKind::Eq:
case ParseNodeKind::Ne:
case ParseNodeKind::Lt:
case ParseNodeKind::Le:
case ParseNodeKind::Gt:
case ParseNodeKind::Ge:
case ParseNodeKind::InstanceOf:
case ParseNodeKind::In:
case ParseNodeKind::Lsh:
case ParseNodeKind::Rsh:
case ParseNodeKind::Ursh:
case ParseNodeKind::Add:
case ParseNodeKind::Sub:
case ParseNodeKind::Star:
case ParseNodeKind::Div:
case ParseNodeKind::Mod:
case ParseNodeKind::Pow:
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_count >= 2);
*answer = true;
return true;
case ParseNodeKind::Colon:
case ParseNodeKind::Case:
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (!checkSideEffects(pn->pn_left, answer)) return false;
if (*answer) return true;
return checkSideEffects(pn->pn_right, answer);
// More getters.
case ParseNodeKind::Elem:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
// These affect visible names in this code, or in other code.
case ParseNodeKind::Import:
case ParseNodeKind::ExportFrom:
case ParseNodeKind::ExportDefault:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
// Likewise.
case ParseNodeKind::Export:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
// Every part of a loop might be effect-free, but looping infinitely *is*
// an effect. (Language lawyer trivia: C++ says threads can be assumed
// to exit or have side effects, C++14 [intro.multithread]p27, so a C++
// implementation's equivalent of the below could set |*answer = false;|
// if all loop sub-nodes set |*answer = false|!)
case ParseNodeKind::DoWhile:
case ParseNodeKind::While:
case ParseNodeKind::For:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
// Declarations affect the name set of the relevant scope.
case ParseNodeKind::Var:
case ParseNodeKind::Const:
case ParseNodeKind::Let:
MOZ_ASSERT(pn->isArity(PN_LIST));
*answer = true;
return true;
case ParseNodeKind::If:
case ParseNodeKind::Conditional:
MOZ_ASSERT(pn->isArity(PN_TERNARY));
if (!checkSideEffects(pn->pn_kid1, answer)) return false;
if (*answer) return true;
if (!checkSideEffects(pn->pn_kid2, answer)) return false;
if (*answer) return true;
if ((pn = pn->pn_kid3)) goto restart;
return true;
// Function calls can invoke non-local code.
case ParseNodeKind::New:
case ParseNodeKind::Call:
case ParseNodeKind::TaggedTemplate:
case ParseNodeKind::SuperCall:
MOZ_ASSERT(pn->isArity(PN_LIST));
*answer = true;
return true;
case ParseNodeKind::Pipeline:
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_count >= 2);
*answer = true;
return true;
// Classes typically introduce names. Even if no name is introduced,
// the heritage and/or class body (through computed property names)
// usually have effects.
case ParseNodeKind::Class:
MOZ_ASSERT(pn->isArity(PN_TERNARY));
*answer = true;
return true;
// |with| calls |ToObject| on its expression and so throws if that value
// is null/undefined.
case ParseNodeKind::With:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
case ParseNodeKind::Return:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
case ParseNodeKind::Name:
MOZ_ASSERT(pn->isArity(PN_NAME));
*answer = true;
return true;
// Shorthands could trigger getters: the |x| in the object literal in
// |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers
// one. (Of course, it isn't necessary to use |with| for a shorthand to
// trigger a getter.)
case ParseNodeKind::Shorthand:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
case ParseNodeKind::Function:
MOZ_ASSERT(pn->isArity(PN_CODE));
/*
* A named function, contrary to ES3, is no longer effectful, because
* we bind its name lexically (using JSOP_CALLEE) instead of creating
* an Object instance and binding a readonly, permanent property in it
* (the object and binding can be detected and hijacked or captured).
* This is a bug fix to ES3; it is fixed in ES3.1 drafts.
*/
*answer = false;
return true;
case ParseNodeKind::Module:
*answer = false;
return true;
case ParseNodeKind::Try:
MOZ_ASSERT(pn->isArity(PN_TERNARY));
if (!checkSideEffects(pn->pn_kid1, answer)) return false;
if (*answer) return true;
if (ParseNode* catchScope = pn->pn_kid2) {
MOZ_ASSERT(catchScope->isKind(ParseNodeKind::LexicalScope));
if (!checkSideEffects(catchScope, answer)) return false;
if (*answer) return true;
}
if (ParseNode* finallyBlock = pn->pn_kid3) {
if (!checkSideEffects(finallyBlock, answer)) return false;
}
return true;
case ParseNodeKind::Catch:
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (ParseNode* name = pn->pn_left) {
if (!checkSideEffects(name, answer)) return false;
if (*answer) return true;
}
return checkSideEffects(pn->pn_right, answer);
case ParseNodeKind::Switch:
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (!checkSideEffects(pn->pn_left, answer)) return false;
return *answer || checkSideEffects(pn->pn_right, answer);
case ParseNodeKind::Label:
MOZ_ASSERT(pn->isArity(PN_NAME));
return checkSideEffects(pn->expr(), answer);
case ParseNodeKind::LexicalScope:
MOZ_ASSERT(pn->isArity(PN_SCOPE));
return checkSideEffects(pn->scopeBody(), answer);
// We could methodically check every interpolated expression, but it's
// probably not worth the trouble. Treat template strings as effect-free
// only if they don't contain any substitutions.
case ParseNodeKind::TemplateStringList:
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_count > 0);
MOZ_ASSERT((pn->pn_count % 2) == 1,
"template strings must alternate template and substitution "
"parts");
*answer = pn->pn_count > 1;
return true;
// This should be unreachable but is left as-is for now.
case ParseNodeKind::ParamsBody:
*answer = true;
return true;
case ParseNodeKind::ForIn: // by ParseNodeKind::For
case ParseNodeKind::ForOf: // by ParseNodeKind::For
case ParseNodeKind::ForHead: // by ParseNodeKind::For
case ParseNodeKind::ClassMethod: // by ParseNodeKind::Class
case ParseNodeKind::ClassNames: // by ParseNodeKind::Class
case ParseNodeKind::ClassMethodList: // by ParseNodeKind::Class
case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import
case ParseNodeKind::ExportBatchSpec: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export
case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate
case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget
case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others
MOZ_CRASH("handled by parent nodes");
case ParseNodeKind::Limit: // invalid sentinel value
MOZ_CRASH("invalid node kind");
}
MOZ_CRASH(
"invalid, unenumerated ParseNodeKind value encountered in "
"BytecodeEmitter::checkSideEffects");
}
bool BytecodeEmitter::isInLoop() {
return findInnermostNestableControl<LoopControl>();
}
bool BytecodeEmitter::checkSingletonContext() {
if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop())
return false;
hasSingletons = true;
return true;
}
bool BytecodeEmitter::checkRunOnceContext() {
return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
}
bool BytecodeEmitter::needsImplicitThis() {
// Short-circuit if there is an enclosing 'with' scope.
if (sc->inWith()) return true;
// Otherwise see if the current point is under a 'with'.
for (EmitterScope* es = innermostEmitterScope(); es;
es = es->enclosingInFrame()) {
if (es->scope(this)->kind() == ScopeKind::With) return true;
}
return false;
}
bool BytecodeEmitter::maybeSetDisplayURL() {
if (tokenStream().hasDisplayURL()) {
if (!parser.ss()->setDisplayURL(cx, tokenStream().displayURL()))
return false;
}
return true;
}
bool BytecodeEmitter::maybeSetSourceMap() {
if (tokenStream().hasSourceMapURL()) {
MOZ_ASSERT(!parser.ss()->hasSourceMapURL());
if (!parser.ss()->setSourceMapURL(cx, tokenStream().sourceMapURL()))
return false;
}
/*
* Source map URLs passed as a compile option (usually via a HTTP source map
* header) override any source map urls passed as comment pragmas.
*/
if (parser.options().sourceMapURL()) {
// Warn about the replacement, but use the new one.
if (parser.ss()->hasSourceMapURL()) {
if (!parser.warningNoOffset(JSMSG_ALREADY_HAS_PRAGMA,
parser.ss()->filename(),
"//# sourceMappingURL")) {
return false;
}
}
if (!parser.ss()->setSourceMapURL(cx, parser.options().sourceMapURL()))
return false;
}
return true;
}
void BytecodeEmitter::tellDebuggerAboutCompiledScript(JSContext* cx) {
// Note: when parsing off thread the resulting scripts need to be handed to
// the debugger after rejoining to the active thread.
if (cx->helperThread()) return;
// Lazy scripts are never top level (despite always being invoked with a
// nullptr parent), and so the hook should never be fired.
if (emitterMode != LazyFunction && !parent) Debugger::onNewScript(cx, script);
}
inline TokenStreamAnyChars& BytecodeEmitter::tokenStream() {
return parser.tokenStream();
}
void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) {
TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos;
va_list args;
va_start(args, errorNumber);
ErrorMetadata metadata;
if (parser.computeErrorMetadata(&metadata, pos.begin))
ReportCompileError(cx, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber,
args);
va_end(args);
}
bool BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber,
...) {
TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos;
va_list args;
va_start(args, errorNumber);
bool result = parser.reportExtraWarningErrorNumberVA(nullptr, pos.begin,
errorNumber, &args);
va_end(args);
return result;
}
bool BytecodeEmitter::emitNewInit(JSProtoKey key) {
const size_t len = 1 + UINT32_INDEX_LEN;
ptrdiff_t offset;
if (!emitCheck(len, &offset)) return false;
jsbytecode* code = this->code(offset);
code[0] = JSOP_NEWINIT;
code[1] = jsbytecode(key);
code[2] = 0;
code[3] = 0;
code[4] = 0;
checkTypeSet(JSOP_NEWINIT);
updateDepth(offset);
return true;
}
bool BytecodeEmitter::iteratorResultShape(unsigned* shape) {
// No need to do any guessing for the object kind, since we know exactly how
// many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(2);
RootedPlainObject obj(
cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj) return false;
Rooted<jsid> value_id(cx, NameToId(cx->names().value));
Rooted<jsid> done_id(cx, NameToId(cx->names().done));
if (!NativeDefineDataProperty(cx, obj, value_id, UndefinedHandleValue,
JSPROP_ENUMERATE))
return false;
if (!NativeDefineDataProperty(cx, obj, done_id, UndefinedHandleValue,
JSPROP_ENUMERATE))
return false;
ObjectBox* objbox = parser.newObjectBox(obj);
if (!objbox) return false;
*shape = objectList.add(objbox);
return true;
}
bool BytecodeEmitter::emitPrepareIteratorResult() {
unsigned shape;
if (!iteratorResultShape(&shape)) return false;
return emitIndex32(JSOP_NEWOBJECT, shape);
}
bool BytecodeEmitter::emitFinishIteratorResult(bool done) {
uint32_t value_id;
if (!makeAtomIndex(cx->names().value, &value_id)) return false;
uint32_t done_id;
if (!makeAtomIndex(cx->names().done, &done_id)) return false;
if (!emitIndex32(JSOP_INITPROP, value_id)) return false;
if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) return false;
if (!emitIndex32(JSOP_INITPROP, done_id)) return false;
return true;
}
bool BytecodeEmitter::emitGetNameAtLocation(JSAtom* name,
const NameLocation& loc,
bool callContext) {
switch (loc.kind()) {
case NameLocation::Kind::Dynamic:
if (!emitAtomOp(name, JSOP_GETNAME)) return false;
break;
case NameLocation::Kind::Global:
if (!emitAtomOp(name, JSOP_GETGNAME)) return false;
break;
case NameLocation::Kind::Intrinsic:
if (!emitAtomOp(name, JSOP_GETINTRINSIC)) return false;
break;
case NameLocation::Kind::NamedLambdaCallee:
if (!emit1(JSOP_CALLEE)) return false;
break;
case NameLocation::Kind::Import:
if (!emitAtomOp(name, JSOP_GETIMPORT)) return false;
break;
case NameLocation::Kind::ArgumentSlot:
if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) return false;
break;
case NameLocation::Kind::FrameSlot:
if (loc.isLexical()) {
if (!emitTDZCheckIfNeeded(name, loc)) return false;
}
if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) return false;
break;
case NameLocation::Kind::EnvironmentCoordinate:
if (loc.isLexical()) {
if (!emitTDZCheckIfNeeded(name, loc)) return false;
}
if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate()))
return false;
break;
case NameLocation::Kind::DynamicAnnexBVar:
MOZ_CRASH(
"Synthesized vars for Annex B.3.3 should only be used in "
"initialization");
}
// Need to provide |this| value for call.
if (callContext) {
switch (loc.kind()) {
case NameLocation::Kind::Dynamic: {
JSOp thisOp =
needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
if (!emitAtomOp(name, thisOp)) return false;
break;
}
case NameLocation::Kind::Global:
if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) return false;
break;
case NameLocation::Kind::Intrinsic:
case NameLocation::Kind::NamedLambdaCallee:
case NameLocation::Kind::Import:
case NameLocation::Kind::ArgumentSlot:
case NameLocation::Kind::FrameSlot:
case NameLocation::Kind::EnvironmentCoordinate:
if (!emit1(JSOP_UNDEFINED)) return false;
break;
case NameLocation::Kind::DynamicAnnexBVar:
MOZ_CRASH(
"Synthesized vars for Annex B.3.3 should only be used in "
"initialization");
}
}
return true;
}
bool BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext) {
return emitGetName(pn->name(), callContext);
}
template <typename RHSEmitter>
bool BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name,
const NameLocation& loc,
RHSEmitter emitRhs,
bool initialize) {
bool emittedBindOp = false;
switch (loc.kind()) {
case NameLocation::Kind::Dynamic:
case NameLocation::Kind::Import:
case NameLocation::Kind::DynamicAnnexBVar: {
uint32_t atomIndex;
if (!makeAtomIndex(name, &atomIndex)) return false;
if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
// Annex B vars always go on the nearest variable environment,
// even if lexical environments in between contain same-named
// bindings.
if (!emit1(JSOP_BINDVAR)) return false;
} else {
if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) return false;
}
emittedBindOp = true;
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex))
return false;
break;
}
case NameLocation::Kind::Global: {
JSOp op;
uint32_t atomIndex;
if (!makeAtomIndex(name, &atomIndex)) return false;
if (loc.isLexical() && initialize) {
// INITGLEXICAL always gets the global lexical scope. It doesn't
// need a BINDGNAME.
MOZ_ASSERT(innermostScope()->is<GlobalScope>());
op = JSOP_INITGLEXICAL;
} else {
if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) return false;
emittedBindOp = true;
op = strictifySetNameOp(JSOP_SETGNAME);
}
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (!emitIndexOp(op, atomIndex)) return false;
break;
}
case NameLocation::Kind::Intrinsic:
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (!emitAtomOp(name, JSOP_SETINTRINSIC)) return false;
break;
case NameLocation::Kind::NamedLambdaCallee:
if (!emitRhs(this, loc, emittedBindOp)) return false;
// Assigning to the named lambda is a no-op in sloppy mode but
// throws in strict mode.
if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) return false;
break;
case NameLocation::Kind::ArgumentSlot: {
// If we assign to a positional formal parameter and the arguments
// object is unmapped (strict mode or function with
// default/rest/destructing args), parameters do not alias
// arguments[i], and to make the arguments object reflect initial
// parameter values prior to any mutation we create it eagerly
// whenever parameters are (or might, in the case of calls to eval)
// assigned.
FunctionBox* funbox = sc->asFunctionBox();
if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj())
funbox->setDefinitelyNeedsArgsObj();
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) return false;
break;
}
case NameLocation::Kind::FrameSlot: {
JSOp op = JSOP_SETLOCAL;
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (loc.isLexical()) {
if (initialize) {
op = JSOP_INITLEXICAL;
} else {
if (loc.isConst()) op = JSOP_THROWSETCONST;
if (!emitTDZCheckIfNeeded(name, loc)) return false;
}
}
if (!emitLocalOp(op, loc.frameSlot())) return false;
if (op == JSOP_INITLEXICAL) {
if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
return false;
}
break;
}
case NameLocation::Kind::EnvironmentCoordinate: {
JSOp op = JSOP_SETALIASEDVAR;
if (!emitRhs(this, loc, emittedBindOp)) return false;
if (loc.isLexical()) {
if (initialize) {
op = JSOP_INITALIASEDLEXICAL;
} else {
if (loc.isConst()) op = JSOP_THROWSETALIASEDCONST;
if (!emitTDZCheckIfNeeded(name, loc)) return false;
}
}
if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
// Assigning to the named lambda is a no-op in sloppy mode and throws
// in strict mode.
op = JSOP_THROWSETALIASEDCONST;
if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate()))
return false;
} else {
if (!emitEnvCoordOp(op, loc.environmentCoordinate())) return false;
}
if (op == JSOP_INITALIASEDLEXICAL) {
if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
return false;
}
break;
}
}
return true;
}
bool BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name,
const NameLocation& loc) {
// Dynamic accesses have TDZ checks built into their VM code and should
// never emit explicit TDZ checks.
MOZ_ASSERT(loc.hasKnownSlot());
MOZ_ASSERT(loc.isLexical());
Maybe<MaybeCheckTDZ> check =
innermostTDZCheckCache->needsTDZCheck(this, name);
if (!check) return false;
// We've already emitted a check in this basic block.
if (*check == DontCheckTDZ) return true;
if (loc.kind() == NameLocation::Kind::FrameSlot) {
if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot())) return false;
} else {
if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate()))
return false;
}
return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ);
}
bool BytecodeEmitter::emitPropLHS(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::Dot));
MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper());
ParseNode* pn2 = pn->pn_expr;
/*
* If the object operand is also a dotted property reference, reverse the
* list linked via pn_expr temporarily so we can iterate over it from the
* bottom up (reversing again as we go), to avoid excessive recursion.
*/
if (pn2->isKind(ParseNodeKind::Dot) && !pn2->as<PropertyAccess>().isSuper()) {
ParseNode* pndot = pn2;
ParseNode* pnup = nullptr;
ParseNode* pndown;
for (;;) {
/* Reverse pndot->pn_expr to point up, not down. */
pndown = pndot->pn_expr;
pndot->pn_expr = pnup;
if (!pndown->isKind(ParseNodeKind::Dot) ||
pndown->as<PropertyAccess>().isSuper())
break;
pnup = pndot;
pndot = pndown;
}
/* pndown is a primary expression, not a dotted property reference. */
if (!emitTree(pndown)) return false;
do {
/* Walk back up the list, emitting annotated name ops. */
if (!emitAtomOp(pndot, JSOP_GETPROP)) return false;
/* Reverse the pn_expr link again. */
pnup = pndot->pn_expr;
pndot->pn_expr = pndown;
pndown = pndot;
} while ((pndot = pnup) != nullptr);
return true;
}
// The non-optimized case.
return emitTree(pn2);
}
bool BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall) {
if (!emitGetThisForSuperBase(superBase)) return false;
if (isCall && !emit1(JSOP_DUP)) return false;
if (!emit1(JSOP_SUPERBASE)) return false;
return true;
}
bool BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op) {
MOZ_ASSERT(pn->isArity(PN_NAME));
if (!emitPropLHS(pn)) return false;
if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) return false;
if (!emitAtomOp(pn, op)) return false;
if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) return false;
return true;
}
bool BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall) {
ParseNode* base = &pn->as<PropertyAccess>().expression();
if (!emitSuperPropLHS(base, isCall)) return false;
if (!emitAtomOp(pn, op)) return false;
if (isCall && !emit1(JSOP_SWAP)) return false;
return true;
}
bool BytecodeEmitter::emitPropIncDec(ParseNode* pn) {
MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Dot));
bool post;
bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper();
JSOp binop = GetIncDecInfo(pn->getKind(), &post);
if (isSuper) {
ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression();
if (!emitSuperPropLHS(base)) // THIS OBJ
return false;
if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ
return false;
} else {
if (!emitPropLHS(pn->pn_kid)) // OBJ
return false;
if (!emit1(JSOP_DUP)) // OBJ OBJ
return false;
}
if (!emitAtomOp(pn->pn_kid,
isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V
return false;
if (!emit1(JSOP_POS)) // OBJ N
return false;
if (post && !emit1(JSOP_DUP)) // OBJ N? N
return false;
if (!emit1(JSOP_ONE)) // OBJ N? N 1
return false;
if (!emit1(binop)) // OBJ N? N+1
return false;
if (post) {
if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ
return false;
if (!emit1(JSOP_SWAP)) // N? OBJ N+1
return false;
if (isSuper) {
if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ
return false;
if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1
return false;
}
}
JSOp setOp =
isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
: sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1
return false;
if (post && !emit1(JSOP_POP)) // RESULT
return false;
return true;
}
bool BytecodeEmitter::emitGetNameAtLocationForCompoundAssignment(
JSAtom* name, const NameLocation& loc) {
if (loc.kind() == NameLocation::Kind::Dynamic) {
// For dynamic accesses we need to emit GETBOUNDNAME instead of
// GETNAME for correctness: looking up @@unscopables on the
// environment chain (due to 'with' environments) must only happen
// once.
//
// GETBOUNDNAME uses the environment already pushed on the stack from
// the earlier BINDNAME.
if (!emit1(JSOP_DUP)) // ENV ENV
return false;
if (!emitAtomOp(name, JSOP_GETBOUNDNAME)) // ENV V
return false;
} else {
if (!emitGetNameAtLocation(name, loc)) // ENV? V
return false;
}
return true;
}
bool BytecodeEmitter::emitNameIncDec(ParseNode* pn) {
MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Name));
bool post;
JSOp binop = GetIncDecInfo(pn->getKind(), &post);
auto emitRhs = [pn, post, binop](BytecodeEmitter* bce,
const NameLocation& loc,
bool emittedBindOp) {
JSAtom* name = pn->pn_kid->name();
if (!bce->emitGetNameAtLocationForCompoundAssignment(name, loc)) // ENV? V
return false;
if (!bce->emit1(JSOP_POS)) // ENV? N
return false;
if (post && !bce->emit1(JSOP_DUP)) // ENV? N? N
return false;
if (!bce->emit1(JSOP_ONE)) // ENV? N? N 1
return false;
if (!bce->emit1(binop)) // ENV? N? N+1
return false;
if (post && emittedBindOp) {
if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 ENV?
return false;
if (!bce->emit1(JSOP_SWAP)) // N? ENV? N+1
return false;
}
return true;
};
if (!emitSetName(pn->pn_kid, emitRhs)) return false;
if (post && !emit1(JSOP_POP)) return false;
return true;
}
bool BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts) {
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (!emitTree(pn->pn_left)) return false;
if (opts == EmitElemOption::IncDec) {
if (!emit1(JSOP_CHECKOBJCOERCIBLE)) return false;
} else if (opts == EmitElemOption::Call) {
if (!emit1(JSOP_DUP)) return false;
}
if (!emitTree(pn->pn_right)) return false;
if (opts == EmitElemOption::Set) {
if (!emit2(JSOP_PICK, 2)) return false;
} else if (opts == EmitElemOption::IncDec ||
opts == EmitElemOption::CompoundAssign) {
if (!emit1(JSOP_TOID)) return false;
}
return true;
}
bool BytecodeEmitter::emitSuperElemOperands(ParseNode* pn,
EmitElemOption opts) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::Elem) &&
pn->as<PropertyByValue>().isSuper());
// The ordering here is somewhat screwy. We need to evaluate the propval
// first, by spec. Do a little dance to not emit more than one JSOP_THIS.
// Since JSOP_THIS might throw in derived class constructors, we cannot
// just push it earlier as the receiver. We have to swap it down instead.
if (!emitTree(pn->pn_right)) return false;
// We need to convert the key to an object id first, so that we do not do
// it inside both the GETELEM and the SETELEM.
if (opts == EmitElemOption::IncDec ||
opts == EmitElemOption::CompoundAssign) {
if (!emit1(JSOP_TOID)) return false;
}
if (!emitGetThisForSuperBase(pn->pn_left)) return false;
if (opts == EmitElemOption::Call) {
if (!emit1(JSOP_SWAP)) return false;
// We need another |this| on top, also
if (!emitDupAt(1)) return false;
}
if (!emit1(JSOP_SUPERBASE)) return false;
if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3)) return false;
return true;
}
bool BytecodeEmitter::emitElemOpBase(JSOp op) {
if (!emit1(op)) return false;
checkTypeSet(op);
return true;
}
bool BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op) {
EmitElemOption opts = EmitElemOption::Get;
if (op == JSOP_CALLELEM)
opts = EmitElemOption::Call;
else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM)
opts = EmitElemOption::Set;
return emitElemOperands(pn, opts) && emitElemOpBase(op);
}
bool BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) {
EmitElemOption opts = EmitElemOption::Get;
if (isCall)
opts = EmitElemOption::Call;
else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER)
opts = EmitElemOption::Set;
if (!emitSuperElemOperands(pn, opts)) return false;
if (!emitElemOpBase(op)) return false;
if (isCall && !emit1(JSOP_SWAP)) return false;
return true;
}
bool BytecodeEmitter::emitElemIncDec(ParseNode* pn) {
MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Elem));
bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper();
// We need to convert the key to an object id first, so that we do not do
// it inside both the GETELEM and the SETELEM. This is done by
// emit(Super)ElemOperands.
if (isSuper) {
if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec))
return false;
} else {
if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) return false;
}
bool post;
JSOp binop = GetIncDecInfo(pn->getKind(), &post);
JSOp getOp;
if (isSuper) {
// There's no such thing as JSOP_DUP3, so we have to be creative.
// Note that pushing things again is no fewer JSOps.
if (!emitDupAt(2)) // KEY THIS OBJ KEY
return false;
if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS
return false;
if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ
return false;
getOp = JSOP_GETELEM_SUPER;
} else {
// OBJ KEY
if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY
return false;
getOp = JSOP_GETELEM;
}
if (!emitElemOpBase(getOp)) // OBJ KEY V
return false;
if (!emit1(JSOP_POS)) // OBJ KEY N
return false;
if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N
return false;
if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1
return false;
if (!emit1(binop)) // OBJ KEY N? N+1
return false;
if (post) {
if (isSuper) {
// We have one more value to rotate around, because of |this|
// on the stack
if (!emit2(JSOP_PICK, 4)) return false;
}
if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ
return false;
if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY
return false;
if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1
return false;
}
JSOp setOp =
isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
: (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
if (!emitElemOpBase(setOp)) // N? N+1
return false;
if (post && !emit1(JSOP_POP)) // RESULT
return false;
return true;
}
bool BytecodeEmitter::emitCallIncDec(ParseNode* incDec) {
MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrement) ||
incDec->isKind(ParseNodeKind::PostIncrement) ||
incDec->isKind(ParseNodeKind::PreDecrement) ||
incDec->isKind(ParseNodeKind::PostDecrement));
MOZ_ASSERT(incDec->pn_kid->isKind(ParseNodeKind::Call));
ParseNode* call = incDec->pn_kid;
if (!emitTree(call)) // CALLRESULT
return false;
if (!emit1(JSOP_POS)) // N
return false;
// The increment/decrement has no side effects, so proceed to throw for
// invalid assignment target.
return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
}
bool BytecodeEmitter::emitNumberOp(double dval) {
int32_t ival;
if (NumberIsInt32(dval, &ival)) {
if (ival == 0) return emit1(JSOP_ZERO);
if (ival == 1) return emit1(JSOP_ONE);
if ((int)(int8_t)ival == ival)
return emit2(JSOP_INT8, uint8_t(int8_t(ival)));
uint32_t u = uint32_t(ival);
if (u < JS_BIT(16)) {
if (!emitUint16Operand(JSOP_UINT16, u)) return false;
} else if (u < JS_BIT(24)) {
ptrdiff_t off;
if (!emitN(JSOP_UINT24, 3, &off)) return false;
SET_UINT24(code(off), u);
} else {
ptrdiff_t off;
if (!emitN(JSOP_INT32, 4, &off)) return false;
SET_INT32(code(off), ival);
}
return true;
}
if (!constList.append(DoubleValue(dval))) return false;
return emitIndex32(JSOP_DOUBLE, constList.length() - 1);
}
/*
* Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
* LLVM is deciding to inline this function which uses a lot of stack space
* into emitTree which is recursive and uses relatively little stack space.
*/
MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(ParseNode* pn) {
ParseNode* cases = pn->pn_right;
MOZ_ASSERT(cases->isKind(ParseNodeKind::LexicalScope) ||
cases->isKind(ParseNodeKind::StatementList));
// Emit code for the discriminant.
if (!emitTree(pn->pn_left)) return false;
// Enter the scope before pushing the switch BreakableControl since all
// breaks are under this scope.
Maybe<TDZCheckCache> tdzCache;
Maybe<EmitterScope> emitterScope;
if (cases->isKind(ParseNodeKind::LexicalScope)) {
if (!cases->isEmptyScope()) {
tdzCache.emplace(this);
emitterScope.emplace(this);
if (!emitterScope->enterLexical(this, ScopeKind::Lexical,
cases->scopeBindings()))
return false;
}
// Advance |cases| to refer to the switch case list.
cases = cases->scopeBody();
// A switch statement may contain hoisted functions inside its
// cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
// bodies of the cases to the case list.
if (cases->pn_xflags & PNX_FUNCDEFS) {
MOZ_ASSERT(emitterScope);
for (ParseNode* caseNode = cases->pn_head; caseNode;
caseNode = caseNode->pn_next) {
if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
if (!emitHoistedFunctionsInList(caseNode->pn_right)) return false;
}
}
}
}
// After entering the scope, push the switch control.
BreakableControl controlInfo(this, StatementKind::Switch);
ptrdiff_t top = offset();
// Switch bytecodes run from here till end of final case.
uint32_t caseCount = cases->pn_count;
if (caseCount > JS_BIT(16)) {
parser.reportError(JSMSG_TOO_MANY_CASES);
return false;
}
// Try for most optimal, fall back if not dense ints.
JSOp switchOp = JSOP_TABLESWITCH;
uint32_t tableLength = 0;
int32_t low, high;
bool hasDefault = false;
CaseClause* firstCase =
cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
if (caseCount == 0 ||
(caseCount == 1 && (hasDefault = firstCase->isDefault()))) {
caseCount = 0;
low = 0;
high = -1;
} else {
Vector<jsbitmap, 128, SystemAllocPolicy> intmap;
int32_t intmapBitLength = 0;
low = JSVAL_INT_MAX;
high = JSVAL_INT_MIN;
for (CaseClause* caseNode = firstCase; caseNode;
caseNode = caseNode->next()) {
if (caseNode->isDefault()) {
hasDefault = true;
caseCount--; // one of the "cases" was the default
continue;
}
if (switchOp == JSOP_CONDSWITCH) continue;
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
ParseNode* caseValue = caseNode->caseExpression();
if (caseValue->getKind() != ParseNodeKind::Number) {
switchOp = JSOP_CONDSWITCH;
continue;
}
int32_t i;
if (!NumberIsInt32(caseValue->pn_dval, &i)) {
switchOp = JSOP_CONDSWITCH;
continue;
}
if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
switchOp = JSOP_CONDSWITCH;
continue;
}
if (i < low) low = i;
if (i > high) high = i;
// Check for duplicates, which require a JSOP_CONDSWITCH.
// We bias i by 65536 if it's negative, and hope that's a rare
// case (because it requires a malloc'd bitmap).
if (i < 0) i += JS_BIT(16);
if (i >= intmapBitLength) {
size_t newLength = (i / JS_BITMAP_NBITS) + 1;
if (!intmap.resize(newLength)) return false;
intmapBitLength = newLength * JS_BITMAP_NBITS;
}
if (JS_TEST_BIT(intmap, i)) {
switchOp = JSOP_CONDSWITCH;
continue;
}
JS_SET_BIT(intmap, i);
}
// Compute table length and select condswitch instead if overlarge or
// more than half-sparse.
if (switchOp == JSOP_TABLESWITCH) {
tableLength = uint32_t(high - low + 1);
if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount)
switchOp = JSOP_CONDSWITCH;
}
}
// The note has one or two offsets: first tells total switch code length;
// second (if condswitch) tells offset to first JSOP_CASE.
unsigned noteIndex;
size_t switchSize;
if (switchOp == JSOP_CONDSWITCH) {
// 0 bytes of immediate for unoptimized switch.
switchSize = 0;
if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex)) return false;
} else {
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
// 3 offsets (len, low, high) before the table, 1 per entry.
switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength));
if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex)) return false;
}
// Emit switchOp followed by switchSize bytes of jump or lookup table.
MOZ_ASSERT(top == offset());
if (!emitN(switchOp, switchSize)) return false;
Vector<CaseClause*, 32, SystemAllocPolicy> table;
JumpList condSwitchDefaultOff;
if (switchOp == JSOP_CONDSWITCH) {
unsigned caseNoteIndex;
bool beforeCases = true;
ptrdiff_t lastCaseOffset = -1;
// The case conditions need their own TDZ cache since they might not
// all execute.
TDZCheckCache tdzCache(this);
// Emit code for evaluating cases and jumping to case statements.
for (CaseClause* caseNode = firstCase; caseNode;
caseNode = caseNode->next()) {
ParseNode* caseValue = caseNode->caseExpression();
// If the expression is a literal, suppress line number emission so
// that debugging works more naturally.
if (caseValue) {
if (!emitTree(
caseValue, ValueUsage::WantValue,
caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) {
return false;
}
}
if (!beforeCases) {
// prevCase is the previous JSOP_CASE's bytecode offset.
if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
return false;
}
if (!caseValue) {
// This is the default clause.
continue;
}
if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex)) return false;
// The case clauses are produced before any of the case body. The
// JumpList is saved on the parsed tree, then later restored and
// patched when generating the cases body.
JumpList caseJump;
if (!emitJump(JSOP_CASE, &caseJump)) return false;
caseNode->setOffset(caseJump.offset);
lastCaseOffset = caseJump.offset;
if (beforeCases) {
// Switch note's second offset is to first JSOP_CASE.
unsigned noteCount = notes().length();
if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top)) return false;
unsigned noteCountDelta = notes().length() - noteCount;
if (noteCountDelta != 0) caseNoteIndex += noteCountDelta;
beforeCases = false;
}
}
// If we didn't have an explicit default (which could fall in between
// cases, preventing us from fusing this setSrcNoteOffset with the call
// in the loop above), link the last case to the implicit default for
// the benefit of IonBuilder.
if (!hasDefault && !beforeCases &&
!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset)) {
return false;
}
// Emit default even if no explicit default statement.
if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff)) return false;
} else {
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
// skip default offset.
jsbytecode* pc = code(top + JUMP_OFFSET_LEN);
// Fill in switch bounds, which we know fit in 16-bit offsets.
SET_JUMP_OFFSET(pc, low);
pc += JUMP_OFFSET_LEN;
SET_JUMP_OFFSET(pc, high);
pc += JUMP_OFFSET_LEN;
if (tableLength != 0) {
if (!table.growBy(tableLength)) return false;
for (CaseClause* caseNode = firstCase; caseNode;
caseNode = caseNode->next()) {
if (ParseNode* caseValue = caseNode->caseExpression()) {
MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
int32_t i = int32_t(caseValue->pn_dval);
MOZ_ASSERT(double(i) == caseValue->pn_dval);
i -= low;
MOZ_ASSERT(uint32_t(i) < tableLength);
MOZ_ASSERT(!table[i]);
table[i] = caseNode;
}
}
}
}
JumpTarget defaultOffset{-1};
// Emit code for each case's statements.
for (CaseClause* caseNode = firstCase; caseNode;
caseNode = caseNode->next()) {
if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) {
// The case offset got saved in the caseNode structure after
// emitting the JSOP_CASE jump instruction above.
JumpList caseCond;
caseCond.offset = caseNode->offset();
if (!emitJumpTargetAndPatch(caseCond)) return false;
}
JumpTarget here;
if (!emitJumpTarget(&here)) return false;
if (caseNode->isDefault()) defaultOffset = here;
// If this is emitted as a TABLESWITCH, we'll need to know this case's
// offset later when emitting the table. Store it in the node's
// pn_offset (giving the field a different meaning vs. how we used it
// on the immediately preceding line of code).
caseNode->setOffset(here.offset);
TDZCheckCache tdzCache(this);
if (!emitTree(caseNode->statementList())) return false;
}
if (!hasDefault) {
// If no default case, offset for default is to end of switch.
if (!emitJumpTarget(&defaultOffset)) return false;
}
MOZ_ASSERT(defaultOffset.offset != -1);
// Set the default offset (to end of switch if no default).
jsbytecode* pc;
if (switchOp == JSOP_CONDSWITCH) {
pc = nullptr;
patchJumpsToTarget(condSwitchDefaultOff, defaultOffset);
} else {
MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
pc = code(top);
SET_JUMP_OFFSET(pc, defaultOffset.offset - top);
pc += JUMP_OFFSET_LEN;
}
// Set the SRC_SWITCH note's offset operand to tell end of switch.
if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top))
return false;
if (switchOp == JSOP_TABLESWITCH) {
// Skip over the already-initialized switch bounds.
pc += 2 * JUMP_OFFSET_LEN;
// Fill in the jump table, if there is one.
for (uint32_t i = 0; i < tableLength; i++) {
CaseClause* caseNode = table[i];
ptrdiff_t off = caseNode ? caseNode->offset() - top : 0;
SET_JUMP_OFFSET(pc, off);
pc += JUMP_OFFSET_LEN;
}
}
// Patch breaks before leaving the scope, as all breaks are under the
// lexical scope if it exists.
if (!controlInfo.patchBreaks(this)) return false;
if (emitterScope && !emitterScope->leave(this)) return false;
return true;
}
bool BytecodeEmitter::isRunOnceLambda() {
// The run once lambda flags set by the parser are approximate, and we look
// at properties of the function itself before deciding to emit a function
// as a run once lambda.
if (!(parent && parent->emittingRunOnceLambda) &&
(emitterMode != LazyFunction || !lazyScript->treatAsRunOnce())) {
return false;
}
FunctionBox* funbox = sc->asFunctionBox();
return !funbox->argumentsHasLocalBinding() && !funbox->isGenerator() &&
!funbox->isAsync() && !funbox->function()->explicitName();
}
bool BytecodeEmitter::emitYieldOp(JSOp op) {
if (op == JSOP_FINALYIELDRVAL) return emit1(JSOP_FINALYIELDRVAL);
MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD || op == JSOP_AWAIT);
ptrdiff_t off;
if (!emitN(op, 3, &off)) return false;
uint32_t yieldAndAwaitIndex = yieldAndAwaitOffsetList.length();
if (yieldAndAwaitIndex >= JS_BIT(24)) {
reportError(nullptr, JSMSG_TOO_MANY_YIELDS);
return false;
}
if (op == JSOP_AWAIT)
yieldAndAwaitOffsetList.numAwaits++;
else
yieldAndAwaitOffsetList.numYields++;
SET_UINT24(code(off), yieldAndAwaitIndex);
if (!yieldAndAwaitOffsetList.append(offset())) return false;
return emit1(JSOP_DEBUGAFTERYIELD);
}
bool BytecodeEmitter::emitSetThis(ParseNode* pn) {
// ParseNodeKind::SetThis is used to update |this| after a super() call
// in a derived class constructor.
MOZ_ASSERT(pn->isKind(ParseNodeKind::SetThis));
MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::Name));
RootedAtom name(cx, pn->pn_left->name());
auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) {
// Emit the new |this| value.
if (!bce->emitTree(pn->pn_right)) return false;
// Get the original |this| and throw if we already initialized
// it. Do *not* use the NameLocation argument, as that's the special
// lexical location below to deal with super() semantics.
if (!bce->emitGetName(name)) return false;
if (!bce->emit1(JSOP_CHECKTHISREINIT)) return false;
if (!bce->emit1(JSOP_POP)) return false;
return true;
};
// The 'this' binding is not lexical, but due to super() semantics this
// initialization needs to be treated as a lexical one.
NameLocation loc = lookupName(name);
NameLocation lexicalLoc;
if (loc.kind() == NameLocation::Kind::FrameSlot) {
lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot());
} else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
EnvironmentCoordinate coord = loc.environmentCoordinate();
uint8_t hops = AssertedCast<uint8_t>(coord.hops());
lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops,
coord.slot());
} else {
MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic);
lexicalLoc = loc;
}
return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true);
}
bool BytecodeEmitter::emitScript(ParseNode* body) {
AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, tokenStream(),
body);
TDZCheckCache tdzCache(this);
EmitterScope emitterScope(this);
if (sc->isGlobalContext()) {
switchToPrologue();
if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) return false;
switchToMain();
} else if (sc->isEvalContext()) {
switchToPrologue();
if (!emitterScope.enterEval(this, sc->asEvalContext())) return false;
switchToMain();
} else {
MOZ_ASSERT(sc->isModuleContext());
if (!emitterScope.enterModule(this, sc->asModuleContext())) return false;
}
setFunctionBodyEndPos(body->pn_pos);
if (sc->isEvalContext() && !sc->strict() &&
body->isKind(ParseNodeKind::LexicalScope) && !body->isEmptyScope()) {
// Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is
// an immediately enclosed lexical scope, we need to enter the lexical
// scope in the prologue for the DEFFUNs to pick up the right
// environment chain.
EmitterScope lexicalEmitterScope(this);
switchToPrologue();
if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical,
body->scopeBindings()))
return false;
switchToMain();
if (!emitLexicalScopeBody(body->scopeBody())) return false;
if (!lexicalEmitterScope.leave(this)) return false;
} else {
if (!emitTree(body)) return false;
}
if (!updateSourceCoordNotes(body->pn_pos.end)) return false;
if (!emit1(JSOP_RETRVAL)) return false;
if (!emitterScope.leave(this)) return false;
if (!JSScript::fullyInitFromEmitter(cx, script, this)) return false;
// URL and source map information must be set before firing
// Debugger::onNewScript.
if (!maybeSetDisplayURL() || !maybeSetSourceMap()) return false;
tellDebuggerAboutCompiledScript(cx);
return true;
}
bool BytecodeEmitter::emitFunctionScript(ParseNode* body) {
FunctionBox* funbox = sc->asFunctionBox();
AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, tokenStream(),
funbox);
// The ordering of these EmitterScopes is important. The named lambda
// scope needs to enclose the function scope needs to enclose the extra
// var scope.
Maybe<EmitterScope> namedLambdaEmitterScope;
if (funbox->namedLambdaBindings()) {
namedLambdaEmitterScope.emplace(this);
if (!namedLambdaEmitterScope->enterNamedLambda(this, 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 (isRunOnceLambda()) {
script->setTreatAsRunOnce();
MOZ_ASSERT(!script->hasRunOnce());
switchToPrologue();
if (!emit1(JSOP_RUNONCE)) return false;
switchToMain();
}
setFunctionBodyEndPos(body->pn_pos);
if (!emitTree(body)) 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)) 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);
}
return true;
}
template <typename NameEmitter>
bool BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern,
NameEmitter emitName) {
if (pattern->isKind(ParseNodeKind::Array)) {
for (ParseNode* element = pattern->pn_head; element;
element = element->pn_next) {
if (element->isKind(ParseNodeKind::Elision)) continue;
ParseNode* target = element;
if (element->isKind(ParseNodeKind::Spread)) {
target = element->pn_kid;
}
if (target->isKind(ParseNodeKind::Assign)) target = target->pn_left;
if (target->isKind(ParseNodeKind::Name)) {
if (!emitName(this, target)) return false;
} else {
if (!emitDestructuringDeclsWithEmitter(target, emitName)) return false;
}
}
return true;
}
MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
MOZ_ASSERT(member->isKind(ParseNodeKind::MutateProto) ||
member->isKind(ParseNodeKind::Colon) ||
member->isKind(ParseNodeKind::Shorthand));
ParseNode* target = member->isKind(ParseNodeKind::MutateProto)
? member->pn_kid
: member->pn_right;
if (target->isKind(ParseNodeKind::Assign)) target = target->pn_left;
if (target->isKind(ParseNodeKind::Name)) {
if (!emitName(this, target)) return false;
} else {
if (!emitDestructuringDeclsWithEmitter(target, emitName)) return false;
}
}
return true;
}
bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target,
size_t* emitted) {
*emitted = 0;
if (target->isKind(ParseNodeKind::Spread))
target = target->pn_kid;
else if (target->isKind(ParseNodeKind::Assign))
target = target->pn_left;
// No need to recur into ParseNodeKind::Array and
// ParseNodeKind::Object subpatterns here, since
// emitSetOrInitializeDestructuring does the recursion when
// setting or initializing value. Getting reference doesn't recur.
if (target->isKind(ParseNodeKind::Name) ||
target->isKind(ParseNodeKind::Array) ||
target->isKind(ParseNodeKind::Object)) {
return true;
}
#ifdef DEBUG
int depth = stackDepth;
#endif
switch (target->getKind()) {
case ParseNodeKind::Dot: {
if (target->as<PropertyAccess>().isSuper()) {
if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression()))
return false;
*emitted = 2;
} else {
if (!emitTree(target->pn_expr)) return false;
*emitted = 1;
}
break;
}
case ParseNodeKind::Elem: {
if (target->as<PropertyByValue>().isSuper()) {
if (!emitSuperElemOperands(target, EmitElemOption::Ref)) return false;
*emitted = 3;
} else {
if (!emitElemOperands(target, EmitElemOption::Ref)) return false;
*emitted = 2;
}
break;
}
case ParseNodeKind::Call:
MOZ_ASSERT_UNREACHABLE(
"Parser::reportIfNotValidSimpleAssignmentTarget "
"rejects function calls as assignment "
"targets in destructuring assignments");
break;
default:
MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind");
}
MOZ_ASSERT(stackDepth == depth + int(*emitted));
return true;
}
bool BytecodeEmitter::emitSetOrInitializeDestructuring(
ParseNode* target, DestructuringFlavor flav) {
// Now emit the lvalue opcode sequence. If the lvalue is a nested
// destructuring initialiser-form, call ourselves to handle it, then pop
// the matched value. Otherwise emit an lvalue bytecode sequence followed
// by an assignment op.
if (target->isKind(ParseNodeKind::Spread))
target = target->pn_kid;
else if (target->isKind(ParseNodeKind::Assign))
target = target->pn_left;
if (target->isKind(ParseNodeKind::Array) ||
target->isKind(ParseNodeKind::Object)) {
if (!emitDestructuringOps(target, flav)) return false;
// Per its post-condition, emitDestructuringOps has left the
// to-be-destructured value on top of the stack.
if (!emit1(JSOP_POP)) return false;
} else {
switch (target->getKind()) {
case ParseNodeKind::Name: {
auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
bool emittedBindOp) {
if (emittedBindOp) {
// This is like ordinary assignment, but with one
// difference.
//
// In `a = b`, we first determine a binding for `a` (using
// JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`,
// then a JSOP_SETNAME instruction.
//
// In `[a] = [b]`, per spec, `b` is evaluated first, then
// we determine a binding for `a`. Then we need to do
// assignment-- but the operands are on the stack in the
// wrong order for JSOP_SETPROP, so we have to add a
// JSOP_SWAP.
//
// In the cases where we are emitting a name op, emit a
// swap because of this.
return bce->emit1(JSOP_SWAP);
}
// In cases of emitting a frame slot or environment slot,
// nothing needs be done.
return true;
};
RootedAtom name(cx, target->name());
switch (flav) {
case DestructuringDeclaration:
if (!emitInitializeName(name, emitSwapScopeAndRhs)) return false;
break;
case DestructuringFormalParameterInVarScope: {
// If there's an parameter expression var scope, the
// destructuring declaration needs to initialize the name in
// the function scope. The innermost scope is the var scope,
// and its enclosing scope is the function scope.
EmitterScope* funScope =
innermostEmitterScope()->enclosingInFrame();
NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope);
if (!emitSetOrInitializeNameAtLocation(name, paramLoc,
emitSwapScopeAndRhs, true))
return false;
break;
}
case DestructuringAssignment:
if (!emitSetName(name, emitSwapScopeAndRhs)) return false;
break;
}
break;
}
case ParseNodeKind::Dot: {
// The reference is already pushed by emitDestructuringLHSRef.
JSOp setOp;
if (target->as<PropertyAccess>().isSuper())
setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
else
setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
if (!emitAtomOp(target, setOp)) return false;
break;
}
case ParseNodeKind::Elem: {
// The reference is already pushed by emitDestructuringLHSRef.
if (target->as<PropertyByValue>().isSuper()) {
JSOp setOp =
sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
// emitDestructuringLHSRef already did emitSuperElemOperands
// part of emitSuperElemOp. Perform remaining part here.
if (!emitElemOpBase(setOp)) return false;
} else {
JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!emitElemOpBase(setOp)) return false;
}
break;
}
case ParseNodeKind::Call:
MOZ_ASSERT_UNREACHABLE(
"Parser::reportIfNotValidSimpleAssignmentTarget "
"rejects function calls as assignment "
"targets in destructuring assignments");
break;
default:
MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind");
}
// Pop the assigned value.
if (!emit1(JSOP_POP)) return false;
}
return true;
}
bool BytecodeEmitter::emitIteratorNext(
ParseNode* pn, IteratorKind iterKind /* = IteratorKind::Sync */,
bool allowSelfHosted /* = false */) {
MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
".next() iteration is prohibited in self-hosted code because it "
"can run user-modifiable iteration code");
// [stack] ... NEXT ITER
MOZ_ASSERT(this->stackDepth >= 2);
if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT
return false;
if (iterKind == IteratorKind::Async) {
if (!emitAwaitInInnermostScope()) // ... RESULT
return false;
}
if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT
return false;
checkTypeSet(JSOP_CALL);
return true;
}
bool BytecodeEmitter::emitPushNotUndefinedOrNull() {
// [stack] V
MOZ_ASSERT(this->stackDepth > 0);
if (!emit1(JSOP_DUP)) // V V
return false;
if (!emit1(JSOP_UNDEFINED)) // V V UNDEFINED
return false;
if (!emit1(JSOP_STRICTNE)) // V ?NEQL
return false;
JumpList undefinedOrNullJump;
if (!emitJump(JSOP_AND, &undefinedOrNullJump)) // V ?NEQL
return false;
if (!emit1(JSOP_POP)) // V
return false;
if (!emit1(JSOP_DUP)) // V V
return false;
if (!emit1(JSOP_NULL)) // V V NULL
return false;
if (!emit1(JSOP_STRICTNE)) // V ?NEQL
return false;
if (!emitJumpTargetAndPatch(undefinedOrNullJump)) // V NOT-UNDEF-OR-NULL
return false;
return true;
}
bool BytecodeEmitter::emitIteratorCloseInScope(
EmitterScope& currentScope,
IteratorKind iterKind /* = IteratorKind::Sync */,
CompletionKind completionKind /* = CompletionKind::Normal */,
bool allowSelfHosted /* = false */) {
MOZ_ASSERT(
allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
".close() on iterators is prohibited in self-hosted code because it "
"can run user-modifiable iteration code");
// Generate inline logic corresponding to IteratorClose (ES 7.4.6).
//
// Callers need to ensure that the iterator object is at the top of the
// stack.
if (!emit1(JSOP_DUP)) // ... ITER ITER
return false;
// Step 3.
//
// Get the "return" method.
if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET
return false;
// Step 4.
//
// Do nothing if "return" is undefined or null.
IfThenElseEmitter ifReturnMethodIsDefined(this);
if (!emitPushNotUndefinedOrNull()) // ... ITER RET NOT-UNDEF-OR-NULL
return false;
if (!ifReturnMethodIsDefined.emitIfElse()) // ... ITER RET
return false;
if (completionKind == CompletionKind::Throw) {
// 7.4.6 IteratorClose ( iterator, completion )
// ...
// 3. Let return be ? GetMethod(iterator, "return").
// 4. If return is undefined, return Completion(completion).
// 5. Let innerResult be Call(return, iterator, « »).
// 6. If completion.[[Type]] is throw, return Completion(completion).
// 7. If innerResult.[[Type]] is throw, return
// Completion(innerResult).
//
// For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET
// is callable, and throws if not. Since step 6 doesn't match and
// error handling in step 3 and step 7 can be merged.
//
// For CompletionKind::Throw case, an error thrown by JSOP_CALL for
// step 5 is ignored by try-catch. So we should check if RET is
// callable here, outside of try-catch, and the throw immediately if
// not.
CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn;
if (!emitCheckIsCallable(kind)) // ... ITER RET
return false;
}
// Steps 5, 8.
//
// Call "return" if it is not undefined or null, and check that it returns
// an Object.
if (!emit1(JSOP_SWAP)) // ... RET ITER
return false;
Maybe<TryEmitter> tryCatch;
if (completionKind == CompletionKind::Throw) {
tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal,
TryEmitter::DontUseControl);
// Mutate stack to balance stack for try-catch.
if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF
return false;
if (!tryCatch->emitTry()) // ... RET ITER UNDEF
return false;
if (!emitDupAt(2)) // ... RET ITER UNDEF RET
return false;
if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER
return false;
}
if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT
return false;
checkTypeSet(JSOP_CALL);
if (iterKind == IteratorKind::Async) {
if (completionKind != CompletionKind::Throw) {
// Await clobbers rval, so save the current rval.
if (!emit1(JSOP_GETRVAL)) // ... ... RESULT RVAL
return false;
if (!emit1(JSOP_SWAP)) // ... ... RVAL RESULT
return false;
}
if (!emitAwaitInScope(currentScope)) // ... ... RVAL? RESULT
return false;
}
if (completionKind == CompletionKind::Throw) {
if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF
return false;
if (!emit1(JSOP_POP)) // ... RET ITER RESULT
return false;
if (!tryCatch->emitCatch()) // ... RET ITER RESULT
return false;
// Just ignore the exception thrown by call and await.
if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC
return false;
if (!emit1(JSOP_POP)) // ... RET ITER RESULT
return false;
if (!tryCatch->emitEnd()) // ... RET ITER RESULT
return false;
// Restore stack.
if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER
return false;
if (!emitPopN(2)) // ... RESULT
return false;
} else {
if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RVAL? RESULT
return false;
if (iterKind == IteratorKind::Async) {
if (!emit1(JSOP_SWAP)) // ... RESULT RVAL
return false;
if (!emit1(JSOP_SETRVAL)) // ... RESULT
return false;
}
}
if (!ifReturnMethodIsDefined.emitElse()) // ... ITER RET
return false;
if (!emit1(JSOP_POP)) // ... ITER
return false;
if (!ifReturnMethodIsDefined.emitEnd()) return false;
return emit1(JSOP_POP);
// [stack] ...
}
template <typename InnerEmitter>
bool BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(
int32_t iterDepth, InnerEmitter emitter) {
MOZ_ASSERT(this->stackDepth >= iterDepth);
// Pad a nop at the beginning of the bytecode covered by the trynote so
// that when unwinding environments, we may unwind to the scope
// corresponding to the pc *before* the start, in case the first bytecode
// emitted by |emitter| is the start of an inner scope. See comment above
// UnwindEnvironmentToTryPc.
if (!emit1(JSOP_TRY_DESTRUCTURING_ITERCLOSE)) return false;
ptrdiff_t start = offset();
if (!emitter(this)) return false;
ptrdiff_t end = offset();
if (start != end)
return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start,
end);
return true;
}
bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) {
if (!emit1(JSOP_DUP)) // VALUE VALUE
return false;
if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED
return false;
if (!emit1(JSOP_STRICTEQ)) // VALUE EQL?
return false;
// Emit source note to enable ion compilation.
if (!newSrcNote(SRC_IF)) return false;
JumpList jump;
if (!emitJump(JSOP_IFEQ, &jump)) // VALUE
return false;
if (!emit1(JSOP_POP)) // .
return false;
if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE
return false;
if (!emitJumpTargetAndPatch(jump)) return false;
return true;
}
bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name,
FunctionPrefixKind prefixKind) {
if (maybeFun->isKind(ParseNodeKind::Function)) {
// Function doesn't have 'name' property at this point.
// Set function's name at compile time.
JSFunction* fun = maybeFun->pn_funbox->function();
// Single node can be emitted multiple times if it appears in
// array destructuring default. If function already has a name,
// just return.
if (fun->hasCompileTimeName()) {
#ifdef DEBUG
RootedFunction rootedFun(cx, fun);
JSAtom* funName = NameToFunctionName(cx, name, prefixKind);
if (!funName) return false;
MOZ_ASSERT(funName == rootedFun->compileTimeName());
#endif
return true;
}
fun->setCompileTimeName(name);
return true;
}
uint32_t nameIndex;
if (!makeAtomIndex(name, &nameIndex)) return false;
if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME
return false;
uint8_t kind = uint8_t(prefixKind);
if (!emit2(JSOP_SETFUNNAME, kind)) // FUN
return false;
return true;
}
bool BytecodeEmitter::emitInitializer(ParseNode* initializer,
ParseNode* pattern) {
if (!emitTree(initializer)) return false;
if (!pattern->isInParens() && pattern->isKind(ParseNodeKind::Name) &&
initializer->isDirectRHSAnonFunction()) {
RootedAtom name(cx, pattern->name());
if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None))
return false;
}
return true;
}
bool BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer,
ParseNode* pattern) {
TDZCheckCache tdzCache(this);
return emitInitializer(initializer, pattern);
}
bool BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern,
DestructuringFlavor flav) {
MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array));
MOZ_ASSERT(pattern->isArity(PN_LIST));
MOZ_ASSERT(this->stackDepth != 0);
// Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
//
// Lines that are annotated "covered by trynote" mean that upon throwing
// an exception, IteratorClose is called on iter only if done is false.
//
// let x, y;
// let a, b, c, d;
// let iter, next, lref, result, done, value; // stack values
//
// iter = x[Symbol.iterator]();
// next = iter.next;
//
// // ==== emitted by loop for a ====
// lref = GetReference(a); // covered by trynote
//
// result = Call(next, iter);
// done = result.done;
//
// if (done)
// value = undefined;
// else
// value = result.value;
//
// SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for b ====
// lref = GetReference(b); // covered by trynote
//
// if (done) {
// value = undefined;
// } else {
// result = Call(next, iter);
// done = result.done;
// if (done)
// value = undefined;
// else
// value = result.value;
// }
//
// SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for elision ====
// if (done) {
// value = undefined;
// } else {
// result = Call(next, iter);
// done = result.done;
// if (done)
// value = undefined;
// else
// value = result.value;
// }
//
// // ==== emitted by loop for c ====
// lref = GetReference(c); // covered by trynote
//
// if (done) {
// value = undefined;
// } else {
// result = Call(next, iter);
// done = result.done;
// if (done)
// value = undefined;
// else
// value = result.value;
// }
//
// if (value === undefined)
// value = y; // covered by trynote
//
// SetOrInitialize(lref, value); // covered by trynote
//
// // ==== emitted by loop for d ====
// lref = GetReference(d); // covered by trynote
//
// if (done)
// value = [];
// else
// value = [...iter];
//
// SetOrInitialize(lref, value); // covered by trynote
//
// // === emitted after loop ===
// if (!done)
// IteratorClose(iter);
// Use an iterator to destructure the RHS, instead of index lookup. We
// must leave the *original* value on the stack.
if (!emit1(JSOP_DUP)) // ... OBJ OBJ
return false;
if (!emitIterator()) // ... OBJ NEXT ITER
return false;
// For an empty pattern [], call IteratorClose unconditionally. Nothing
// else needs to be done.
if (!pattern->pn_head) {
if (!emit1(JSOP_SWAP)) // ... OBJ ITER NEXT
return false;
if (!emit1(JSOP_POP)) // ... OBJ ITER
return false;
return emitIteratorCloseInInnermostScope();
// [stack] ... OBJ
}
// Push an initial FALSE value for DONE.
if (!emit1(JSOP_FALSE)) // ... OBJ NEXT ITER FALSE
return false;
// JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
// to be the second to top and the top of the stack, respectively.
// IteratorClose is called upon exception only if done is false.
int32_t tryNoteDepth = stackDepth;
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
bool isFirst = member == pattern->pn_head;
DebugOnly<bool> hasNext = !!member->pn_next;
size_t emitted = 0;
// Spec requires LHS reference to be evaluated first.
ParseNode* lhsPattern = member;
if (lhsPattern->isKind(ParseNodeKind::Assign))
lhsPattern = lhsPattern->pn_left;
bool isElision = lhsPattern->isKind(ParseNodeKind::Elision);
if (!isElision) {
auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
return bce->emitDestructuringLHSRef(lhsPattern, &emitted);
// [stack] ... OBJ NEXT ITER DONE LREF*
};
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef))
return false;
}
// Pick the DONE value to the top of the stack.
if (emitted) {
if (!emit2(JSOP_PICK, emitted)) // ... OBJ NEXT ITER *LREF DONE
return false;
}
if (isFirst) {
// If this element is the first, DONE is always FALSE, so pop it.
//
// Non-first elements should emit if-else depending on the
// member pattern, below.
if (!emit1(JSOP_POP)) // ... OBJ NEXT ITER *LREF
return false;
}
if (member->isKind(ParseNodeKind::Spread)) {
IfThenElseEmitter ifThenElse(this);
if (!isFirst) {
// If spread is not the first element of the pattern,
// iterator can already be completed.
// ... OBJ NEXT ITER *LREF DONE
if (!ifThenElse.emitIfElse()) // ... OBJ NEXT ITER *LREF
return false;
if (!emitUint32Operand(JSOP_NEWARRAY,
0)) // ... OBJ NEXT ITER *LREF ARRAY
return false;
if (!ifThenElse.emitElse()) // ... OBJ NEXT ITER *LREF
return false;
}
// If iterator is not completed, create a new array with the rest
// of the iterator.
if (!emitDupAt(emitted + 1)) // ... OBJ NEXT ITER *LREF NEXT
return false;
if (!emitDupAt(emitted + 1)) // ... OBJ NEXT ITER *LREF NEXT ITER
return false;
if (!emitUint32Operand(JSOP_NEWARRAY,
0)) // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY
return false;
if (!emitNumberOp(0)) // ... OBJ NEXT ITER *LREF NEXT ITER ARRAY INDEX
return false;
if (!emitSpread()) // ... OBJ NEXT ITER *LREF ARRAY INDEX
return false;
if (!emit1(JSOP_POP)) // ... OBJ NEXT ITER *LREF ARRAY
return false;
if (!isFirst) {
if (!ifThenElse.emitEnd()) return false;
MOZ_ASSERT(ifThenElse.pushed() == 1);
}
// At this point the iterator is done. Unpick a TRUE value for DONE above
// ITER.
if (!emit1(JSOP_TRUE)) // ... OBJ NEXT ITER *LREF ARRAY TRUE
return false;
if (!emit2(JSOP_UNPICK,
emitted + 1)) // ... OBJ NEXT ITER TRUE *LREF ARRAY
return false;
auto emitAssignment = [member, flav](BytecodeEmitter* bce) {
return bce->emitSetOrInitializeDestructuring(member, flav);
// [stack] ... OBJ NEXT ITER TRUE
};
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth,
emitAssignment))
return false;
MOZ_ASSERT(!hasNext);
break;
}
ParseNode* pndefault = nullptr;
if (member->isKind(ParseNodeKind::Assign)) pndefault = member->pn_right;
MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread));
IfThenElseEmitter ifAlreadyDone(this);
if (!isFirst) {
// ... OBJ NEXT ITER *LREF DONE
if (!ifAlreadyDone.emitIfElse()) // ... OBJ NEXT ITER *LREF
return false;
if (!emit1(JSOP_UNDEFINED)) // ... OBJ NEXT ITER *LREF UNDEF
return false;
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ NEXT ITER *LREF UNDEF
return false;
// The iterator is done. Unpick a TRUE value for DONE above ITER.
if (!emit1(JSOP_TRUE)) // ... OBJ NEXT ITER *LREF UNDEF TRUE
return false;
if (!emit2(JSOP_UNPICK,
emitted + 1)) // ... OBJ NEXT ITER TRUE *LREF UNDEF
return false;
if (!ifAlreadyDone.emitElse()) // ... OBJ NEXT ITER *LREF
return false;
}
if (!emitDupAt(emitted + 1)) // ... OBJ NEXT ITER *LREF NEXT
return false;
if (!emitDupAt(emitted + 1)) // ... OBJ NEXT ITER *LREF NEXT ITER
return false;
if (!emitIteratorNext(pattern)) // ... OBJ NEXT ITER *LREF RESULT
return false;
if (!emit1(JSOP_DUP)) // ... OBJ NEXT ITER *LREF RESULT RESULT
return false;
if (!emitAtomOp(cx->names().done,
JSOP_GETPROP)) // ... OBJ NEXT ITER *LREF RESULT DONE
return false;
if (!emit1(JSOP_DUP)) // ... OBJ NEXT ITER *LREF RESULT DONE DONE
return false;
if (!emit2(JSOP_UNPICK,
emitted + 2)) // ... OBJ NEXT ITER DONE *LREF RESULT DONE
return false;
IfThenElseEmitter ifDone(this);
if (!ifDone.emitIfElse()) // ... OBJ NEXT ITER DONE *LREF RESULT
return false;
if (!emit1(JSOP_POP)) // ... OBJ NEXT ITER DONE *LREF
return false;
if (!emit1(JSOP_UNDEFINED)) // ... OBJ NEXT ITER DONE *LREF UNDEF
return false;
if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ NEXT ITER DONE *LREF UNDEF
return false;
if (!ifDone.emitElse()) // ... OBJ NEXT ITER DONE *LREF RESULT
return false;
if (!emitAtomOp(cx->names().value,
JSOP_GETPROP)) // ... OBJ NEXT ITER DONE *LREF VALUE
return false;
if (!ifDone.emitEnd()) return false;
MOZ_ASSERT(ifDone.pushed() == 0);
if (!isFirst) {
if (!ifAlreadyDone.emitEnd()) return false;
MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
}
if (pndefault) {
auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
return bce->emitDefault(pndefault, lhsPattern);
// [stack] ... OBJ NEXT ITER DONE LREF* VALUE
};
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault))
return false;
}
if (!isElision) {
auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
return bce->emitSetOrInitializeDestructuring(lhsPattern, flav);
// [stack] ... OBJ NEXT ITER DONE
};
if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth,
emitAssignment))
return false;
} else {
if (!emit1(JSOP_POP)) // ... OBJ NEXT ITER DONE
return false;
}
}
// The last DONE value is on top of the stack. If not DONE, call
// IteratorClose.
// ... OBJ NEXT ITER DONE
IfThenElseEmitter ifDone(this);
if (!ifDone.emitIfElse()) // ... OBJ NEXT ITER
return false;
if (!emitPopN(2)) // ... OBJ
return false;
if (!ifDone.emitElse()) // ... OBJ NEXT ITER
return false;
if (!emit1(JSOP_SWAP)) // ... OBJ ITER NEXT
return false;
if (!emit1(JSOP_POP)) // ... OBJ ITER
return false;
if (!emitIteratorCloseInInnermostScope()) // ... OBJ
return false;
if (!ifDone.emitEnd()) return false;
return true;
}
bool BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName) {
MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName));
return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID);
}
bool BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern,
DestructuringFlavor flav) {
MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
MOZ_ASSERT(pattern->isArity(PN_LIST));
// [stack] ... RHS
MOZ_ASSERT(this->stackDepth > 0);
if (!emit1(JSOP_CHECKOBJCOERCIBLE)) // ... RHS
return false;
bool needsRestPropertyExcludedSet =
pattern->pn_count > 1 && pattern->last()->isKind(ParseNodeKind::Spread);
if (needsRestPropertyExcludedSet) {
if (!emitDestructuringObjRestExclusionSet(pattern)) // ... RHS SET
return false;
if (!emit1(JSOP_SWAP)) // ... SET RHS
return false;
}
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
ParseNode* subpattern;
if (member->isKind(ParseNodeKind::MutateProto) ||
member->isKind(ParseNodeKind::Spread)) {
subpattern = member->pn_kid;
} else {
subpattern = member->pn_right;
}
ParseNode* lhs = subpattern;
MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread),
!lhs->isKind(ParseNodeKind::Assign));
if (lhs->isKind(ParseNodeKind::Assign)) lhs = lhs->pn_left;
size_t emitted;
if (!emitDestructuringLHSRef(lhs, &emitted)) // ... *SET RHS *LREF
return false;
// Duplicate the value being destructured to use as a reference base.
if (!emitDupAt(emitted)) // ... *SET RHS *LREF RHS
return false;
if (member->isKind(ParseNodeKind::Spread)) {
if (!updateSourceCoordNotes(member->pn_pos.begin)) return false;
if (!emitNewInit(JSProto_Object)) // ... *SET RHS *LREF RHS TARGET
return false;
if (!emit1(JSOP_DUP)) // ... *SET RHS *LREF RHS TARGET TARGET
return false;
if (!emit2(JSOP_PICK, 2)) // ... *SET RHS *LREF TARGET TARGET RHS
return false;
if (needsRestPropertyExcludedSet) {
if (!emit2(JSOP_PICK,
emitted + 4)) // ... RHS *LREF TARGET TARGET RHS SET
return false;
}
CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered
: CopyOption::Unfiltered;
if (!emitCopyDataProperties(option)) // ... RHS *LREF TARGET
return false;
// Destructure TARGET per this member's lhs.
if (!emitSetOrInitializeDestructuring(lhs, flav)) // ... RHS
return false;
MOZ_ASSERT(member == pattern->last(), "Rest property is always last");
break;
}
// Now push the property name currently being matched, which is the
// current property name "label" on the left of a colon in the object
// initialiser.
bool needsGetElem = true;
if (member->isKind(ParseNodeKind::MutateProto)) {
if (!emitAtomOp(cx->names().proto,
JSOP_GETPROP)) // ... *SET RHS *LREF PROP
return false;
needsGetElem = false;
} else {
MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) ||
member->isKind(ParseNodeKind::Shorthand));
ParseNode* key = member->pn_left;
if (key->isKind(ParseNodeKind::Number)) {
if (!emitNumberOp(key->pn_dval)) // ... *SET RHS *LREF RHS KEY
return false;
} else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
key->isKind(ParseNodeKind::String)) {
if (!emitAtomOp(key->pn_atom, JSOP_GETPROP)) // ... *SET RHS *LREF PROP
return false;
needsGetElem = false;
} else {
if (!emitComputedPropertyName(key)) // ... *SET RHS *LREF RHS KEY
return false;
// Add the computed property key to the exclusion set.
if (needsRestPropertyExcludedSet) {
if (!emitDupAt(emitted + 3)) // ... SET RHS *LREF RHS KEY SET
return false;
if (!emitDupAt(1)) // ... SET RHS *LREF RHS KEY SET KEY
return false;
if (!emit1(JSOP_UNDEFINED)) // ... SET RHS *LREF RHS KEY SET KEY
// UNDEFINED
return false;
if (!emit1(JSOP_INITELEM)) // ... SET RHS *LREF RHS KEY SET
return false;
if (!emit1(JSOP_POP)) // ... SET RHS *LREF RHS KEY
return false;
}
}
}
// Get the property value if not done already.
if (needsGetElem &&
!emitElemOpBase(JSOP_GETELEM)) // ... *SET RHS *LREF PROP
return false;
if (subpattern->isKind(ParseNodeKind::Assign)) {
if (!emitDefault(subpattern->pn_right, lhs)) // ... *SET RHS *LREF VALUE
return false;
}
// Destructure PROP per this member's lhs.
if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... *SET RHS
return false;
}
return true;
}
bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ParseNode* pattern) {
MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
MOZ_ASSERT(pattern->isArity(PN_LIST));
MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread));
ptrdiff_t offset = this->offset();
if (!emitNewInit(JSProto_Object)) return false;
// Try to construct the shape of the object as we go, so we can emit a
// JSOP_NEWOBJECT with the final shape instead.
// In the case of computed property names and indices, we cannot fix the
// shape at bytecode compile time. When the shape cannot be determined,
// |obj| is nulled out.
// No need to do any guessing for the object kind, since we know the upper
// bound of how many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(pattern->pn_count - 1);
RootedPlainObject obj(
cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj) return false;
RootedAtom pnatom(cx);
for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
if (member->isKind(ParseNodeKind::Spread)) break;
bool isIndex = false;
if (member->isKind(ParseNodeKind::MutateProto)) {
pnatom.set(cx->names().proto);
} else {
ParseNode* key = member->pn_left;
if (key->isKind(ParseNodeKind::Number)) {
if (!emitNumberOp(key->pn_dval)) return false;
isIndex = true;
} else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
key->isKind(ParseNodeKind::String)) {
pnatom.set(key->pn_atom);
} else {
// Otherwise this is a computed property name which needs to
// be added dynamically.
obj.set(nullptr);
continue;
}
}
// Initialize elements with |undefined|.
if (!emit1(JSOP_UNDEFINED)) return false;
if (isIndex) {
obj.set(nullptr);
if (!emit1(JSOP_INITELEM)) return false;
} else {
uint32_t index;
if (!makeAtomIndex(pnatom, &index)) return false;
if (obj) {
MOZ_ASSERT(!obj->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(pnatom));
if (!NativeDefineDataProperty(cx, obj, id, UndefinedHandleValue,
JSPROP_ENUMERATE))
return false;
if (obj->inDictionaryMode()) obj.set(nullptr);
}
if (!emitIndex32(JSOP_INITPROP, index)) return false;
}
}
if (obj) {
// The object survived and has a predictable shape: update the
// original bytecode.
if (!replaceNewInitWithNewObject(obj, offset)) return false;
}
return true;
}
bool BytecodeEmitter::emitDestructuringOps(ParseNode* pattern,
DestructuringFlavor flav) {
if (pattern->isKind(ParseNodeKind::Array))
return emitDestructuringOpsArray(pattern, flav);
return emitDestructuringOpsObject(pattern, flav);
}
bool BytecodeEmitter::emitTemplateString(ParseNode* pn) {
MOZ_ASSERT(pn->isArity(PN_LIST));
bool pushedString = false;
for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) {
bool isString = (pn2->getKind() == ParseNodeKind::String ||
pn2->getKind() == ParseNodeKind::TemplateString);
// Skip empty strings. These are very common: a template string like
// `${a}${b}` has three empty strings and without this optimization
// we'd emit four JSOP_ADD operations instead of just one.
if (isString && pn2->pn_atom->empty()) continue;
if (!isString) {
// We update source notes before emitting the expression
if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false;
}
if (!emitTree(pn2)) return false;
if (!isString) {
// We need to convert the expression to a string
if (!emit1(JSOP_TOSTRING)) return false;
}
if (pushedString) {
// We've pushed two strings onto the stack. Add them together, leaving
// just one.
if (!emit1(JSOP_ADD)) return false;
} else {
pushedString = true;
}
}
if (!pushedString) {
// All strings were empty, this can happen for something like `${""}`.
// Just push an empty string.
if (!emitAtomOp(cx->names().empty, JSOP_STRING)) return false;
}
return true;
}
bool BytecodeEmitter::emitDeclarationList(ParseNode* declList) {
MOZ_ASSERT(declList->isArity(PN_LIST));
MOZ_ASSERT(declList->isOp(JSOP_NOP));
ParseNode* next;
for (ParseNode* decl = declList->pn_head; decl; decl = next) {
if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false;
next = decl->pn_next;
if (decl->isKind(ParseNodeKind::Assign)) {
MOZ_ASSERT(decl->isOp(JSOP_NOP));
ParseNode* pattern = decl->pn_left;
MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array) ||
pattern->isKind(ParseNodeKind::Object));
if (!emitTree(decl->pn_right)) return false;
if (!emitDestructuringOps(pattern, DestructuringDeclaration))
return false;
if (!emit1(JSOP_POP)) return false;
} else {
if (!emitSingleDeclaration(declList, decl, decl->expr())) return false;
}
}
return true;
}
bool BytecodeEmitter::emitSingleDeclaration(ParseNode* declList,
ParseNode* decl,
ParseNode* initializer) {
MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
// Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
if (!initializer && declList->isKind(ParseNodeKind::Var)) return true;
auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce,
const NameLocation&, bool) {
if (!initializer) {
// Lexical declarations are initialized to undefined without an
// initializer.
MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
"var declarations without initializers handled above, "
"and const declarations must have initializers");
Unused
<< declList; // silence clang -Wunused-lambda-capture in opt builds
return bce->emit1(JSOP_UNDEFINED);
}
MOZ_ASSERT(initializer);
return bce->emitInitializer(initializer, decl);
};
if (!emitInitializeName(decl, emitRhs)) return false;
// Pop the RHS.
return emit1(JSOP_POP);
}
static bool EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs,
uint8_t offset) {
// If there is a RHS tree, emit the tree.
if (rhs) return bce->emitTree(rhs);
// Otherwise the RHS value to assign is already on the stack, i.e., the
// next enumeration value in a for-in or for-of loop. Depending on how
// many other values have been pushed on the stack, we need to get the
// already-pushed RHS value.
if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1)) return false;
return true;
}
static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) {
switch (pnk) {
case ParseNodeKind::Assign:
return JSOP_NOP;
case ParseNodeKind::AddAssign:
return JSOP_ADD;
case ParseNodeKind::SubAssign:
return JSOP_SUB;
case ParseNodeKind::BitOrAssign:
return JSOP_BITOR;
case ParseNodeKind::BitXorAssign:
return JSOP_BITXOR;
case ParseNodeKind::BitAndAssign:
return JSOP_BITAND;
case ParseNodeKind::LshAssign:
return JSOP_LSH;
case ParseNodeKind::RshAssign:
return JSOP_RSH;
case ParseNodeKind::UrshAssign:
return JSOP_URSH;
case ParseNodeKind::MulAssign:
return JSOP_MUL;
case ParseNodeKind::DivAssign:
return JSOP_DIV;
case ParseNodeKind::ModAssign:
return JSOP_MOD;
case ParseNodeKind::PowAssign:
return JSOP_POW;
default:
MOZ_CRASH("unexpected compound assignment op");
}
}
bool BytecodeEmitter::emitAssignment(ParseNode* lhs, ParseNodeKind pnk,
ParseNode* rhs) {
JSOp op = CompoundAssignmentParseNodeKindToJSOp(pnk);
// Name assignments are handled separately because choosing ops and when
// to emit BINDNAME is involved and should avoid duplication.
if (lhs->isKind(ParseNodeKind::Name)) {
auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce,
const NameLocation& lhsLoc,
bool emittedBindOp) {
// For compound assignments, first get the LHS value, then emit
// the RHS and the op.
if (op != JSOP_NOP) {
if (!bce->emitGetNameAtLocationForCompoundAssignment(lhs->name(),
lhsLoc))
return false;
}
// Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
// the top of the stack and we need to pick the right RHS value.
if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) return false;
if (!lhs->isInParens() && op == JSOP_NOP && rhs &&
rhs->isDirectRHSAnonFunction()) {
RootedAtom name(bce->cx, lhs->name());
if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None))
return false;
}
// Emit the compound assignment op if there is one.
if (op != JSOP_NOP) {
if (!bce->emit1(op)) return false;
}
return true;
};
return emitSetName(lhs, emitRhs);
}
// Deal with non-name assignments.
uint32_t atomIndex = (uint32_t)-1;
uint8_t offset = 1;
switch (lhs->getKind()) {
case ParseNodeKind::Dot:
if (lhs->as<PropertyAccess>().isSuper()) {
if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression()))
return false;
offset += 2;
} else {
if (!emitTree(lhs->expr())) return false;
offset += 1;
}
if (!makeAtomIndex(lhs->pn_atom, &atomIndex)) return false;
break;
case ParseNodeKind::Elem: {
MOZ_ASSERT(lhs->isArity(PN_BINARY));
EmitElemOption opt =
op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign;
if (lhs->as<PropertyByValue>().isSuper()) {
if (!emitSuperElemOperands(lhs, opt)) return false;
offset += 3;
} else {
if (!emitElemOperands(lhs, opt)) return false;
offset += 2;
}
break;
}
case ParseNodeKind::Array:
case ParseNodeKind::Object:
break;
case ParseNodeKind::Call:
if (!emitTree(lhs)) return false;
// Assignment to function calls is forbidden, but we have to make the
// call first. Now we can throw.
if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS))
return false;
// Rebalance the stack to placate stack-depth assertions.
if (!emit1(JSOP_POP)) return false;
break;
default:
MOZ_ASSERT(0);
}
if (op != JSOP_NOP) {
MOZ_ASSERT(rhs);
switch (lhs->getKind()) {
case ParseNodeKind::Dot: {
JSOp getOp;
if (lhs->as<PropertyAccess>().isSuper()) {
if (!emit1(JSOP_DUP2)) return false;
getOp = JSOP_GETPROP_SUPER;
} else {
if (!emit1(JSOP_DUP)) return false;
bool isLength = (lhs->pn_atom == cx->names().length);
getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
}
if (!emitIndex32(getOp, atomIndex)) return false;
break;
}
case ParseNodeKind::Elem: {
JSOp elemOp;
if (lhs->as<PropertyByValue>().isSuper()) {
if (!emitDupAt(2)) return false;
if (!emitDupAt(2)) return false;
if (!emitDupAt(2)) return false;
elemOp = JSOP_GETELEM_SUPER;
} else {
if (!emit1(JSOP_DUP2)) return false;
elemOp = JSOP_GETELEM;
}
if (!emitElemOpBase(elemOp)) return false;
break;
}
case ParseNodeKind::Call:
// We just emitted a JSOP_THROWMSG and popped the call's return
// value. Push a random value to make sure the stack depth is
// correct.
if (!emit1(JSOP_NULL)) return false;
break;
default:;
}
}
if (!EmitAssignmentRhs(this, rhs, offset)) return false;
/* If += etc., emit the binary operator with a source note. */
if (op != JSOP_NOP) {
if (!newSrcNote(SRC_ASSIGNOP)) return false;
if (!emit1(op)) return false;
}
/* Finally, emit the specialized assignment bytecode. */
switch (lhs->getKind()) {
case ParseNodeKind::Dot: {
JSOp setOp =
lhs->as<PropertyAccess>().isSuper()
? (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER)
: (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP);
if (!emitIndexOp(setOp, atomIndex)) return false;
break;
}
case ParseNodeKind::Call:
// We threw above, so nothing to do here.
break;
case ParseNodeKind::Elem: {
JSOp setOp =
lhs->as<PropertyByValue>().isSuper()
? sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
: sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
if (!emit1(setOp)) return false;
break;
}
case ParseNodeKind::Array:
case ParseNodeKind::Object:
if (!emitDestructuringOps(lhs, DestructuringAssignment)) return false;
break;
default:
MOZ_ASSERT(0);
}
return true;
}
bool ParseNode::getConstantValue(JSContext* cx,
AllowConstantObjects allowObjects,
MutableHandleValue vp, Value* compare,
size_t ncompare, NewObjectKind newKind) {
MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject);
switch (getKind()) {
case ParseNodeKind::Number:
vp.setNumber(pn_dval);
return true;
case ParseNodeKind::TemplateString:
case ParseNodeKind::String:
vp.setString(pn_atom);
return true;
case ParseNodeKind::True:
vp.setBoolean(true);
return true;
case ParseNodeKind::False:
vp.setBoolean(false);
return true;
case ParseNodeKind::Null:
vp.setNull();
return true;
case ParseNodeKind::RawUndefined:
vp.setUndefined();
return true;
case ParseNodeKind::CallSiteObj:
case ParseNodeKind::Array: {
unsigned count;
ParseNode* pn;
if (allowObjects == DontAllowObjects) {
vp.setMagic(JS_GENERIC_MAGIC);
return true;
}
ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal;
if (allowObjects == ForCopyOnWriteArray) {
arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite;
allowObjects = DontAllowObjects;
}
if (getKind() == ParseNodeKind::CallSiteObj) {
count = pn_count - 1;
pn = pn_head->pn_next;
} else {
MOZ_ASSERT(!(pn_xflags & PNX_NONCONST));
count = pn_count;
pn = pn_head;
}
AutoValueVector values(cx);
if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count)) return false;
size_t idx;
for (idx = 0; pn; idx++, pn = pn->pn_next) {
if (!pn->getConstantValue(cx, allowObjects, values[idx], values.begin(),
idx))
return false;
if (values[idx].isMagic(JS_GENERIC_MAGIC)) {
vp.setMagic(JS_GENERIC_MAGIC);
return true;
}
}
MOZ_ASSERT(idx == count);
ArrayObject* obj = ObjectGroup::newArrayObject(
cx, values.begin(), values.length(), newKind, arrayKind);
if (!obj) return false;
if (!CombineArrayElementTypes(cx, obj, compare, ncompare)) return false;
vp.setObject(*obj);
return true;
}
case ParseNodeKind::Object: {
MOZ_ASSERT(!(pn_xflags & PNX_NONCONST));
if (allowObjects == DontAllowObjects) {
vp.setMagic(JS_GENERIC_MAGIC);
return true;
}
MOZ_ASSERT(allowObjects == AllowObjects);
Rooted<IdValueVector> properties(cx, IdValueVector(cx));
RootedValue value(cx), idvalue(cx);
for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) {
if (!pn->pn_right->getConstantValue(cx, allowObjects, &value))
return false;
if (value.isMagic(JS_GENERIC_MAGIC)) {
vp.setMagic(JS_GENERIC_MAGIC);
return true;
}
ParseNode* pnid = pn->pn_left;
if (pnid->isKind(ParseNodeKind::Number)) {
idvalue = NumberValue(pnid->pn_dval);
} else {
MOZ_ASSERT(pnid->isKind(ParseNodeKind::ObjectPropertyName) ||
pnid->isKind(ParseNodeKind::String));
MOZ_ASSERT(pnid->pn_atom != cx->names().proto);
idvalue = StringValue(pnid->pn_atom);
}
RootedId id(cx);
if (!ValueToId<CanGC>(cx, idvalue, &id)) return false;
if (!properties.append(IdValuePair(id, value))) return false;
}
JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(),
properties.length(), newKind);
if (!obj) return false;
if (!CombinePlainObjectPropertyTypes(cx, obj, compare, ncompare))
return false;
vp.setObject(*obj);
return true;
}
default:
MOZ_CRASH("Unexpected node");
}
return false;
}
bool BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn) {
NewObjectKind newKind = (pn->getKind() == ParseNodeKind::Object)
? SingletonObject
: TenuredObject;
RootedValue value(cx);
if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0,
newKind))
return false;
MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton());
ObjectBox* objbox = parser.newObjectBox(&value.toObject());
if (!objbox) return false;
return emitObjectOp(objbox, JSOP_OBJECT);
}
bool BytecodeEmitter::emitCallSiteObject(ParseNode* pn) {
RootedValue value(cx);
if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value)) return false;
MOZ_ASSERT(value.isObject());
ObjectBox* objbox1 = parser.newObjectBox(&value.toObject());
if (!objbox1) return false;
if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value)) return false;
MOZ_ASSERT(value.isObject());
ObjectBox* objbox2 = parser.newObjectBox(&value.toObject());
if (!objbox2) return false;
return emitObjectPairOp(objbox1, objbox2, JSOP_CALLSITEOBJ);
}
/* See the SRC_FOR source note offsetBias comments later in this file. */
JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1);
JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1);
namespace {
class EmitLevelManager {
BytecodeEmitter* bce;
public:
explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) {
bce->emitLevel++;
}
~EmitLevelManager() { bce->emitLevel--; }
};
} /* anonymous namespace */
bool BytecodeEmitter::emitCatch(ParseNode* pn) {
// We must be nested under a try-finally statement.
MOZ_ASSERT(innermostNestableControl->is<TryFinallyControl>());
/* Pick up the pending exception and bind it to the catch variable. */
if (!emit1(JSOP_EXCEPTION)) return false;
ParseNode* pn2 = pn->pn_left;
if (!pn2) {
// Catch parameter was omitted; just discard the exception.
if (!emit1(JSOP_POP)) return false;
} else {
switch (pn2->getKind()) {
case ParseNodeKind::Array:
case ParseNodeKind::Object:
if (!emitDestructuringOps(pn2, DestructuringDeclaration)) return false;
if (!emit1(JSOP_POP)) return false;
break;
case ParseNodeKind::Name:
if (!emitLexicalInitialization(pn2)) return false;
if (!emit1(JSOP_POP)) return false;
break;
default:
MOZ_ASSERT(0);
}
}
/* Emit the catch body. */
return emitTree(pn->pn_right);
}
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the
// comment on EmitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(ParseNode* pn) {
ParseNode* catchScope = pn->pn_kid2;
ParseNode* finallyNode = pn->pn_kid3;
TryEmitter::Kind kind;
if (catchScope) {
if (finallyNode)
kind = TryEmitter::TryCatchFinally;
else
kind = TryEmitter::TryCatch;
} else {
MOZ_ASSERT(finallyNode);
kind = TryEmitter::TryFinally;
}
TryEmitter tryCatch(this, kind);
if (!tryCatch.emitTry()) return false;
if (!emitTree(pn->pn_kid1)) return false;
// If this try has a catch block, emit it.
if (catchScope) {
// The emitted code for a catch block looks like:
//
// [pushlexicalenv] only if any local aliased
// exception
// setlocal 0; pop assign or possibly destructure exception
// < catch block contents >
// debugleaveblock
// [poplexicalenv] only if any local aliased
// if there is a finally block:
// gosub <finally>
// goto <after finally>
if (!tryCatch.emitCatch()) return false;
// Emit the lexical scope and catch body.
MOZ_ASSERT(catchScope->isKind(ParseNodeKind::LexicalScope));
if (!emitTree(catchScope)) return false;
}
// Emit the finally handler, if there is one.
if (finallyNode) {
if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) return false;
if (!emitTree(finallyNode)) return false;
}
if (!tryCatch.emitEnd()) return false;
return true;
}
bool BytecodeEmitter::emitIf(ParseNode* pn) {
IfThenElseEmitter ifThenElse(this);
if_again:
/* Emit code for the condition before pushing stmtInfo. */
if (!emitTreeInBranch(pn->pn_kid1)) return false;
ParseNode* elseNode = pn->pn_kid3;
if (elseNode) {
if (!ifThenElse.emitIfElse()) return false;
} else {
if (!ifThenElse.emitIf()) return false;
}
/* Emit code for the then part. */
if (!emitTreeInBranch(pn->pn_kid2)) return false;
if (elseNode) {
if (!ifThenElse.emitElse()) return false;
if (elseNode->isKind(ParseNodeKind::If)) {
pn = elseNode;
goto if_again;
}
/* Emit code for the else part. */
if (!emitTreeInBranch(elseNode)) return false;
}
if (!ifThenElse.emitEnd()) return false;
return true;
}
bool BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list) {
MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
ParseNode* maybeFun = pn;
if (!sc->strict()) {
while (maybeFun->isKind(ParseNodeKind::Label))
maybeFun = maybeFun->as<LabeledStatement>().statement();
}
if (maybeFun->isKind(ParseNodeKind::Function) &&
maybeFun->functionIsHoisted()) {
if (!emitTree(maybeFun)) return false;
}
}
return true;
}
bool BytecodeEmitter::emitLexicalScopeBody(ParseNode* body,
EmitLineNumberNote emitLineNote) {
if (body->isKind(ParseNodeKind::StatementList) &&
body->pn_xflags & PNX_FUNCDEFS) {
// This block contains function statements whose definitions are
// hoisted to the top of the block. Emit these as a separate pass
// before the rest of the block.
if (!emitHoistedFunctionsInList(body)) return false;
}
// Line notes were updated by emitLexicalScope.
return emitTree(body, ValueUsage::WantValue, emitLineNote);
}
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::LexicalScope));
TDZCheckCache tdzCache(this);
ParseNode* body = pn->scopeBody();
if (pn->isEmptyScope()) return emitLexicalScopeBody(body);
// We are about to emit some bytecode for what the spec calls "declaration
// instantiation". Assign these instructions to the opening `{` of the
// block. (Using the location of each declaration we're instantiating is
// too weird when stepping in the debugger.)
if (!ParseNodeRequiresSpecialLineNumberNotes(body)) {
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
}
EmitterScope emitterScope(this);
ScopeKind kind;
if (body->isKind(ParseNodeKind::Catch)) {
kind = (!body->pn_left || body->pn_left->isKind(ParseNodeKind::Name))
? ScopeKind::SimpleCatch
: ScopeKind::Catch;
} else {
kind = ScopeKind::Lexical;
}
if (!emitterScope.enterLexical(this, kind, pn->scopeBindings())) return false;
if (body->isKind(ParseNodeKind::For)) {
// for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are
// lexical declarations in the head. Signal this by passing a
// non-nullptr lexical scope.
if (!emitFor(body, &emitterScope)) return false;
} else {
if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) return false;
}
return emitterScope.leave(this);
}
bool BytecodeEmitter::emitWith(ParseNode* pn) {
if (!emitTree(pn->pn_left)) return false;
EmitterScope emitterScope(this);
if (!emitterScope.enterWith(this)) return false;
if (!emitTree(pn->pn_right)) return false;
return emitterScope.leave(this);
}
bool BytecodeEmitter::emitCopyDataProperties(CopyOption option) {
DebugOnly<int32_t> depth = this->stackDepth;
uint32_t argc;
if (option == CopyOption::Filtered) {
MOZ_ASSERT(depth > 2);
// [stack] TARGET SOURCE SET
argc = 3;
if (!emitAtomOp(cx->names().CopyDataProperties, JSOP_GETINTRINSIC)) {
// [stack] TARGET SOURCE SET COPYDATAPROPERTIES
return false;
}
} else {
MOZ_ASSERT(depth > 1);
// [stack] TARGET SOURCE
argc = 2;
if (!emitAtomOp(cx->names().CopyDataPropertiesUnfiltered,
JSOP_GETINTRINSIC)) {
// [stack] TARGET SOURCE COPYDATAPROPERTIES
return false;
}
}
if (!emit1(
JSOP_UNDEFINED)) // TARGET SOURCE *SET COPYDATAPROPERTIES UNDEFINED
return false;
if (!emit2(JSOP_PICK,
argc + 1)) // SOURCE *SET COPYDATAPROPERTIES UNDEFINED TARGET
return false;
if (!emit2(JSOP_PICK,
argc + 1)) // *SET COPYDATAPROPERTIES UNDEFINED TARGET SOURCE
return false;
if (option == CopyOption::Filtered) {
if (!emit2(JSOP_PICK,
argc + 1)) // COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET
return false;
}
if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) // IGNORED
return false;
checkTypeSet(JSOP_CALL_IGNORES_RV);
if (!emit1(JSOP_POP)) // -
return false;
MOZ_ASSERT(depth - int(argc) == this->stackDepth);
return true;
}
bool BytecodeEmitter::emitIterator() {
// Convert iterable to iterator.
if (!emit1(JSOP_DUP)) // OBJ OBJ
return false;
if (!emit2(JSOP_SYMBOL,
uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR
return false;
if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN
return false;
if (!emit1(JSOP_SWAP)) // ITERFN OBJ
return false;
if (!emitCall(JSOP_CALLITER, 0)) // ITER
return false;
checkTypeSet(JSOP_CALLITER);
if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER
return false;
if (!emit1(JSOP_DUP)) // ITER ITER
return false;
if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) // ITER NEXT
return false;
if (!emit1(JSOP_SWAP)) // NEXT ITER
return false;
return true;
}
bool BytecodeEmitter::emitAsyncIterator() {
// Convert iterable to iterator.
if (!emit1(JSOP_DUP)) // OBJ OBJ
return false;
if (!emit2(
JSOP_SYMBOL,
uint8_t(JS::SymbolCode::asyncIterator))) // OBJ OBJ @@ASYNCITERATOR
return false;
if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN
return false;
IfThenElseEmitter ifAsyncIterIsUndefined(this);
if (!emitPushNotUndefinedOrNull()) // OBJ ITERFN !UNDEF-OR-NULL
return false;
if (!emit1(JSOP_NOT)) // OBJ ITERFN UNDEF-OR-NULL
return false;
if (!ifAsyncIterIsUndefined.emitIfElse()) // OBJ ITERFN
return false;
if (!emit1(JSOP_POP)) // OBJ
return false;
if (!emit1(JSOP_DUP)) // OBJ OBJ
return false;
if (!emit2(JSOP_SYMBOL,
uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR
return false;
if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN
return false;
if (!emit1(JSOP_SWAP)) // ITERFN OBJ
return false;
if (!emitCall(JSOP_CALLITER, 0)) // ITER
return false;
checkTypeSet(JSOP_CALLITER);
if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER
return false;
if (!emit1(JSOP_DUP)) // ITER ITER
return false;
if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) // ITER SYNCNEXT
return false;
if (!emit1(JSOP_TOASYNCITER)) // ITER
return false;
if (!ifAsyncIterIsUndefined.emitElse()) // OBJ ITERFN
return false;
if (!emit1(JSOP_SWAP)) // ITERFN OBJ
return false;
if (!emitCall(JSOP_CALLITER, 0)) // ITER
return false;
checkTypeSet(JSOP_CALLITER);
if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER
return false;
if (!ifAsyncIterIsUndefined.emitEnd()) // ITER
return false;
if (!emit1(JSOP_DUP)) // ITER ITER
return false;
if (!emitAtomOp(cx->names().next, JSOP_GETPROP)) // ITER NEXT
return false;
if (!emit1(JSOP_SWAP)) // NEXT ITER
return false;
return true;
}
bool BytecodeEmitter::emitSpread(bool allowSelfHosted) {
LoopControl loopInfo(this, StatementKind::Spread);
// Jump down to the loop condition to minimize overhead assuming at least
// one iteration, as the other loop forms do. Annotate so IonMonkey can
// find the loop-closing jump.
unsigned noteIndex;
if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false;
// Jump down to the loop condition to minimize overhead, assuming at least
// one iteration. (This is also what we do for loops; whether this
// assumption holds for spreads is an unanswered question.)
JumpList initialJump;
if (!emitJump(JSOP_GOTO, &initialJump)) // NEXT ITER ARR I (during the goto)
return false;
JumpTarget top{-1};
if (!emitLoopHead(nullptr, &top)) // NEXT ITER ARR I
return false;
// When we enter the goto above, we have NEXT ITER ARR I on the stack. But
// when we reach this point on the loop backedge (if spreading produces at
// least one value), we've additionally pushed a RESULT iteration value.
// Increment manually to reflect this.
this->stackDepth++;
JumpList beq;
JumpTarget breakTarget{-1};
{
#ifdef DEBUG
auto loopDepth = this->stackDepth;
#endif
// Emit code to assign result.value to the iteration variable.
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // NEXT ITER ARR I VALUE
return false;
if (!emit1(JSOP_INITELEM_INC)) // NEXT ITER ARR (I+1)
return false;
MOZ_ASSERT(this->stackDepth == loopDepth - 1);
// Spread operations can't contain |continue|, so don't bother setting loop
// and enclosing "update" offsets, as we do with for-loops.
// COME FROM the beginning of the loop to here.
if (!emitLoopEntry(nullptr, initialJump)) // NEXT ITER ARR I
return false;
if (!emitDupAt(3)) // NEXT ITER ARR I NEXT
return false;
if (!emitDupAt(3)) // NEXT ITER ARR I NEXT ITER
return false;
if (!emitIteratorNext(nullptr, IteratorKind::Sync,
allowSelfHosted)) // ITER ARR I RESULT
return false;
if (!emit1(JSOP_DUP)) // NEXT ITER ARR I RESULT RESULT
return false;
if (!emitAtomOp(cx->names().done,
JSOP_GETPROP)) // NEXT ITER ARR I RESULT DONE
return false;
if (!emitBackwardJump(JSOP_IFEQ, top, &beq,
&breakTarget)) // NEXT ITER ARR I RESULT
return false;
MOZ_ASSERT(this->stackDepth == loopDepth);
}
// Let Ion know where the closing jump of this loop is.
if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
return false;
// No breaks or continues should occur in spreads.
MOZ_ASSERT(loopInfo.breaks.offset == -1);
MOZ_ASSERT(loopInfo.continues.offset == -1);
if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset,
breakTarget.offset))
return false;
if (!emit2(JSOP_PICK, 4)) // ITER ARR FINAL_INDEX RESULT NEXT
return false;
if (!emit2(JSOP_PICK, 4)) // ARR FINAL_INDEX RESULT NEXT ITER
return false;
return emitPopN(3);
// [stack] ARR FINAL_INDEX
}
bool BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead) {
MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) ||
forHead->isKind(ParseNodeKind::ForOf));
MOZ_ASSERT(forHead->isArity(PN_TERNARY));
MOZ_ASSERT(this->stackDepth >= 1,
"must have a per-iteration value for initializing");
ParseNode* target = forHead->pn_kid1;
MOZ_ASSERT(!forHead->pn_kid2);
// If the for-in/of loop didn't have a variable declaration, per-loop
// initialization is just assigning the iteration value to a target
// expression.
if (!parser.isDeclarationList(target))
return emitAssignment(target, ParseNodeKind::Assign,
nullptr); // ... ITERVAL
// Otherwise, per-loop initialization is (possibly) declaration
// initialization. If the declaration is a lexical declaration, it must be
// initialized. If the declaration is a variable declaration, an
// assignment to that name (which does *not* necessarily assign to the
// variable!) must be generated.
if (!updateSourceCoordNotes(target->pn_pos.begin)) return false;
MOZ_ASSERT(target->isForLoopDeclaration());
target = parser.singleBindingFromDeclaration(target);
if (target->isKind(ParseNodeKind::Name)) {
auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
bool emittedBindOp) {
if (emittedBindOp) {
// Per-iteration initialization in for-in/of loops computes the
// iteration value *before* initializing. Thus the
// initializing value may be buried under a bind-specific value
// on the stack. Swap it to the top of the stack.
MOZ_ASSERT(bce->stackDepth >= 2);
return bce->emit1(JSOP_SWAP);
}
// In cases of emitting a frame slot or environment slot,
// nothing needs be done.
MOZ_ASSERT(bce->stackDepth >= 1);
return true;
};
// The caller handles removing the iteration value from the stack.
return emitInitializeName(target, emitSwapScopeAndRhs);
}
MOZ_ASSERT(
!target->isKind(ParseNodeKind::Assign),
"for-in/of loop destructuring declarations can't have initializers");
MOZ_ASSERT(target->isKind(ParseNodeKind::Array) ||
target->isKind(ParseNodeKind::Object));
return emitDestructuringOps(target, DestructuringDeclaration);
}
bool BytecodeEmitter::emitForOf(ParseNode* forOfLoop,
EmitterScope* headLexicalEmitterScope) {
MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::For));
MOZ_ASSERT(forOfLoop->isArity(PN_BINARY));
ParseNode* forOfHead = forOfLoop->pn_left;
MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf));
MOZ_ASSERT(forOfHead->isArity(PN_TERNARY));
unsigned iflags = forOfLoop->pn_iflags;
IteratorKind iterKind =
(iflags & JSITER_FORAWAITOF) ? IteratorKind::Async : IteratorKind::Sync;
MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asFunctionBox());
MOZ_ASSERT_IF(iterKind == IteratorKind::Async,
sc->asFunctionBox()->isAsync());
ParseNode* forHeadExpr = forOfHead->pn_kid3;
// Certain builtins (e.g. Array.from) are implemented in self-hosting
// as for-of loops.
bool allowSelfHostedIter = false;
if (emitterMode == BytecodeEmitter::SelfHosting &&
forHeadExpr->isKind(ParseNodeKind::Call) &&
forHeadExpr->pn_head->name() == cx->names().allowContentIter) {
allowSelfHostedIter = true;
}
// Evaluate the expression being iterated. The forHeadExpr should use a
// distinct TDZCheckCache to evaluate since (abstractly) it runs in its own
// LexicalEnvironment.
if (!emitTreeInBranch(forHeadExpr)) // ITERABLE
return false;
if (iterKind == IteratorKind::Async) {
if (!emitAsyncIterator()) // NEXT ITER
return false;
} else {
if (!emitIterator()) // NEXT ITER
return false;
}
int32_t iterDepth = stackDepth;
// For-of loops have the iterator next method, the iterator itself, and
// the result.value on the stack.
// Push an undefined to balance the stack.
if (!emit1(JSOP_UNDEFINED)) // NEXT ITER UNDEF
return false;
ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter, iterKind);
// Annotate so IonMonkey can find the loop-closing jump.
unsigned noteIndex;
if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false;
JumpList initialJump;
if (!emitJump(JSOP_GOTO, &initialJump)) // NEXT ITER UNDEF
return false;
JumpTarget top{-1};
if (!emitLoopHead(nullptr, &top)) // NEXT ITER UNDEF
return false;
// If the loop had an escaping lexical declaration, replace the current
// environment with an dead zoned one to implement TDZ semantics.
if (headLexicalEmitterScope) {
// The environment chain only includes an environment for the for-of
// loop head *if* a scope binding is captured, thereby requiring
// recreation each iteration. If a lexical scope exists for the head,
// it must be the innermost one. If that scope has closed-over
// bindings inducing an environment, recreate the current environment.
DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1;
MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::Let) ||
forOfTarget->isKind(ParseNodeKind::Const));
MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() ==
ScopeKind::Lexical);
if (headLexicalEmitterScope->hasEnvironment()) {
if (!emit1(JSOP_RECREATELEXICALENV)) // NEXT ITER UNDEF
return false;
}
// For uncaptured bindings, put them back in TDZ.
if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) return false;
}
JumpList beq;
JumpTarget breakTarget{-1};
{
#ifdef DEBUG
auto loopDepth = this->stackDepth;
#endif
// Make sure this code is attributed to the "for".
if (!updateSourceCoordNotes(forOfHead->pn_pos.begin)) return false;
if (!emit1(JSOP_POP)) // NEXT ITER
return false;
if (!emit1(JSOP_DUP2)) // NEXT ITER NEXT ITER
return false;
if (!emitIteratorNext(forOfHead, iterKind, allowSelfHostedIter))
return false; // NEXT ITER RESULT
if (!emit1(JSOP_DUP)) // NEXT ITER RESULT RESULT
return false;
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // NEXT ITER RESULT DONE
return false;
IfThenElseEmitter ifDone(this);
if (!ifDone.emitIf()) // NEXT ITER RESULT
return false;
// Remove RESULT from the stack to release it.
if (!emit1(JSOP_POP)) // NEXT ITER
return false;
if (!emit1(JSOP_UNDEFINED)) // NEXT ITER UNDEF
return false;
// If the iteration is done, leave loop here, instead of the branch at
// the end of the loop.
if (!loopInfo.emitSpecialBreakForDone(this)) // NEXT ITER UNDEF
return false;
if (!ifDone.emitEnd()) // NEXT ITER RESULT
return false;
// Emit code to assign result.value to the iteration variable.
//
// Note that ES 13.7.5.13, step 5.c says getting result.value does not
// call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP.
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // NEXT ITER VALUE
return false;
if (!loopInfo.emitBeginCodeNeedingIteratorClose(this)) return false;
if (!emitInitializeForInOrOfTarget(forOfHead)) // NEXT ITER VALUE
return false;
MOZ_ASSERT(stackDepth == loopDepth,
"the stack must be balanced around the initializing "
"operation");
// Remove VALUE from the stack to release it.
if (!emit1(JSOP_POP)) // NEXT ITER
return false;
if (!emit1(JSOP_UNDEFINED)) // NEXT ITER UNDEF
return false;
// Perform the loop body.
ParseNode* forBody = forOfLoop->pn_right;
if (!emitTree(forBody)) // NEXT ITER UNDEF
return false;
MOZ_ASSERT(stackDepth == loopDepth,
"the stack must be balanced around the for-of body");
if (!loopInfo.emitEndCodeNeedingIteratorClose(this)) return false;
// Set offset for continues.
loopInfo.continueTarget = {offset()};
if (!emitLoopEntry(forHeadExpr, initialJump)) // NEXT ITER UNDEF
return false;
if (!emit1(JSOP_FALSE)) // NEXT ITER UNDEF FALSE
return false;
if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
return false; // NEXT ITER UNDEF
MOZ_ASSERT(this->stackDepth == loopDepth);
}
// Let Ion know where the closing jump of this loop is.
if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
return false;
if (!loopInfo.patchBreaksAndContinues(this)) return false;
if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset,
breakTarget.offset))
return false;
return emitPopN(3); //
}
bool BytecodeEmitter::emitForIn(ParseNode* forInLoop,
EmitterScope* headLexicalEmitterScope) {
MOZ_ASSERT(forInLoop->isKind(ParseNodeKind::For));
MOZ_ASSERT(forInLoop->isArity(PN_BINARY));
MOZ_ASSERT(forInLoop->isOp(JSOP_ITER));
ParseNode* forInHead = forInLoop->pn_left;
MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn));
MOZ_ASSERT(forInHead->isArity(PN_TERNARY));
// Annex B: Evaluate the var-initializer expression if present.
// |for (var i = initializer in expr) { ... }|
ParseNode* forInTarget = forInHead->pn_kid1;
if (parser.isDeclarationList(forInTarget)) {
ParseNode* decl = parser.singleBindingFromDeclaration(forInTarget);
if (decl->isKind(ParseNodeKind::Name)) {
if (ParseNode* initializer = decl->expr()) {
MOZ_ASSERT(
forInTarget->isKind(ParseNodeKind::Var),
"for-in initializers are only permitted for |var| declarations");
if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false;
auto emitRhs = [decl, initializer](BytecodeEmitter* bce,
const NameLocation&, bool) {
return bce->emitInitializer(initializer, decl);
};
if (!emitInitializeName(decl, emitRhs)) return false;
// Pop the initializer.
if (!emit1(JSOP_POP)) return false;
}
}
}
// Evaluate the expression being iterated.
ParseNode* expr = forInHead->pn_kid3;
if (!emitTreeInBranch(expr)) // EXPR
return false;
MOZ_ASSERT(forInLoop->pn_iflags == 0);
if (!emit1(JSOP_ITER)) // ITER
return false;
// For-in loops have both the iterator and the value on the stack. Push
// undefined to balance the stack.
if (!emit1(JSOP_UNDEFINED)) // ITER ITERVAL
return false;
LoopControl loopInfo(this, StatementKind::ForInLoop);
/* Annotate so IonMonkey can find the loop-closing jump. */
unsigned noteIndex;
if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) return false;
// Jump down to the loop condition to minimize overhead (assuming at least
// one iteration, just like the other loop forms).
JumpList initialJump;
if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ITERVAL
return false;
JumpTarget top{-1};
if (!emitLoopHead(nullptr, &top)) // ITER ITERVAL
return false;
// If the loop had an escaping lexical declaration, replace the current
// environment with an dead zoned one to implement TDZ semantics.
if (headLexicalEmitterScope) {
// The environment chain only includes an environment for the for-in
// loop head *if* a scope binding is captured, thereby requiring
// recreation each iteration. If a lexical scope exists for the head,
// it must be the innermost one. If that scope has closed-over
// bindings inducing an environment, recreate the current environment.
MOZ_ASSERT(forInTarget->isKind(ParseNodeKind::Let) ||
forInTarget->isKind(ParseNodeKind::Const));
MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() ==
ScopeKind::Lexical);
if (headLexicalEmitterScope->hasEnvironment()) {
if (!emit1(JSOP_RECREATELEXICALENV)) // ITER ITERVAL
return false;
}
// For uncaptured bindings, put them back in TDZ.
if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) return false;
}
{
#ifdef DEBUG
auto loopDepth = this->stackDepth;
#endif
MOZ_ASSERT(loopDepth >= 2);
if (!emit1(JSOP_ITERNEXT)) // ITER ITERVAL
return false;
if (!emitInitializeForInOrOfTarget(forInHead)) // ITER ITERVAL
return false;
MOZ_ASSERT(this->stackDepth == loopDepth,
"iterator and iterval must be left on the stack");
}
// Perform the loop body.
ParseNode* forBody = forInLoop->pn_right;
if (!emitTree(forBody)) // ITER ITERVAL
return false;
// Set offset for continues.
loopInfo.continueTarget = {offset()};
// Make sure this code is attributed to the "for".
if (!updateSourceCoordNotes(forInHead->pn_pos.begin)) return false;
if (!emitLoopEntry(nullptr, initialJump)) // ITER ITERVAL
return false;
if (!emit1(JSOP_POP)) // ITER
return false;
if (!emit1(JSOP_MOREITER)) // ITER NEXTITERVAL?
return false;
if (!emit1(JSOP_ISNOITER)) // ITER NEXTITERVAL? ISNOITER
return false;
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
return false; // ITER NEXTITERVAL
// Set the srcnote offset so we can find the closing jump.
if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
return false;
if (!loopInfo.patchBreaksAndContinues(this)) return false;
// Pop the enumeration value.
if (!emit1(JSOP_POP)) // ITER
return false;
if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, offset()))
return false;
return emit1(JSOP_ENDITER); //
}
/* C-style `for (init; cond; update) ...` loop. */
bool BytecodeEmitter::emitCStyleFor(ParseNode* pn,
EmitterScope* headLexicalEmitterScope) {
LoopControl loopInfo(this, StatementKind::ForLoop);
ParseNode* forHead = pn->pn_left;
ParseNode* forBody = pn->pn_right;
// If the head of this for-loop declared any lexical variables, the parser
// wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope
// representing the implicit scope of those variables. By the time we get
// here, we have already entered that scope. So far, so good.
//
// ### Scope freshening
//
// Each iteration of a `for (let V...)` loop creates a fresh loop variable
// binding for V, even if the loop is a C-style `for(;;)` loop:
//
// var funcs = [];
// for (let i = 0; i < 2; i++)
// funcs.push(function() { return i; });
// assertEq(funcs[0](), 0); // the two closures capture...
// assertEq(funcs[1](), 1); // ...two different `i` bindings
//
// This is implemented by "freshening" the implicit block -- changing the
// scope chain to a fresh clone of the instantaneous block object -- each
// iteration, just before evaluating the "update" in for(;;) loops.
//
// No freshening occurs in `for (const ...;;)` as there's no point: you
// can't reassign consts. This is observable through the Debugger API. (The
// ES6 spec also skips cloning the environment in this case.)
bool forLoopRequiresFreshening = false;
if (ParseNode* init = forHead->pn_kid1) {
// Emit the `init` clause, whether it's an expression or a variable
// declaration. (The loop variables were hoisted into an enclosing
// scope, but we still need to emit code for the initializers.)
if (!updateSourceCoordNotes(init->pn_pos.begin)) return false;
if (init->isForLoopDeclaration()) {
if (!emitTree(init)) return false;
} else {
// 'init' is an expression, not a declaration. emitTree left its
// value on the stack.
if (!emitTree(init, ValueUsage::IgnoreValue)) return false;
if (!emit1(JSOP_POP)) return false;
}
// ES 13.7.4.8 step 2. The initial freshening.
//
// If an initializer let-declaration may be captured during loop iteration,
// the current scope has an environment. If so, freshen the current
// environment to expose distinct bindings for each loop iteration.
forLoopRequiresFreshening =
init->isKind(ParseNodeKind::Let) && headLexicalEmitterScope;
if (forLoopRequiresFreshening) {
// The environment chain only includes an environment for the for(;;)
// loop head's let-declaration *if* a scope binding is captured, thus
// requiring a fresh environment each iteration. If a lexical scope
// exists for the head, it must be the innermost one. If that scope
// has closed-over bindings inducing an environment, recreate the
// current environment.
MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() ==
ScopeKind::Lexical);
if (headLexicalEmitterScope->hasEnvironment()) {
if (!emit1(JSOP_FRESHENLEXICALENV)) return false;
}
}
}
/*
* NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
* Use tmp to hold the biased srcnote "top" offset, which differs
* from the top local variable by the length of the JSOP_GOTO
* emitted in between tmp and top if this loop has a condition.
*/
unsigned noteIndex;
if (!newSrcNote(SRC_FOR, ¬eIndex)) return false;
if (!emit1(JSOP_NOP)) return false;
ptrdiff_t tmp = offset();
JumpList jmp;
if (forHead->pn_kid2) {
/* Goto the loop condition, which branches back to iterate. */
if (!emitJump(JSOP_GOTO, &jmp)) return false;
}
/* Emit code for the loop body. */
JumpTarget top{-1};
if (!emitLoopHead(forBody, &top)) return false;
if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) return false;
if (!emitTreeInBranch(forBody)) return false;
// Set loop and enclosing "update" offsets, for continue. Note that we
// continue to immediately *before* the block-freshening: continuing must
// refresh the block.
if (!emitJumpTarget(&loopInfo.continueTarget)) return false;
// ES 13.7.4.8 step 3.e. The per-iteration freshening.
if (forLoopRequiresFreshening) {
MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() ==
ScopeKind::Lexical);
if (headLexicalEmitterScope->hasEnvironment()) {
if (!emit1(JSOP_FRESHENLEXICALENV)) return false;
}
}
// Check for update code to do before the condition (if any).
// The update code may not be executed at all; it needs its own TDZ cache.
if (ParseNode* update = forHead->pn_kid3) {
TDZCheckCache tdzCache(this);
if (!updateSourceCoordNotes(update->pn_pos.begin)) return false;
if (!emitTree(update, ValueUsage::IgnoreValue)) return false;
if (!emit1(JSOP_POP)) return false;
/* Restore the absolute line number for source note readers. */
uint32_t lineNum = parser.tokenStream().srcCoords.lineNum(pn->pn_pos.end);
if (currentLine() != lineNum) {
if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum))) return false;
current->currentLine = lineNum;
current->lastColumn = 0;
}
}
ptrdiff_t tmp3 = offset();
if (forHead->pn_kid2) {
/* Fix up the goto from top to target the loop condition. */
MOZ_ASSERT(jmp.offset >= 0);
if (!emitLoopEntry(forHead->pn_kid2, jmp)) return false;
if (!emitTree(forHead->pn_kid2)) return false;
} else if (!forHead->pn_kid3) {
// If there is no condition clause and no update clause, mark
// the loop-ending "goto" with the location of the "for".
// This ensures that the debugger will stop on each loop
// iteration.
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
}
/* Set the first note offset so we can find the loop condition. */
if (!setSrcNoteOffset(noteIndex, 0, tmp3 - tmp)) return false;
if (!setSrcNoteOffset(noteIndex, 1, loopInfo.continueTarget.offset - tmp))
return false;
/* If no loop condition, just emit a loop-closing jump. */
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO, top, &beq,
&breakTarget))
return false;
/* The third note offset helps us find the loop-closing jump. */
if (!setSrcNoteOffset(noteIndex, 2, beq.offset - tmp)) return false;
if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset,
breakTarget.offset))
return false;
if (!loopInfo.patchBreaksAndContinues(this)) return false;
return true;
}
bool BytecodeEmitter::emitFor(ParseNode* pn,
EmitterScope* headLexicalEmitterScope) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::For));
if (pn->pn_left->isKind(ParseNodeKind::ForHead))
return emitCStyleFor(pn, headLexicalEmitterScope);
if (!updateLineNumberNotes(pn->pn_pos.begin)) return false;
if (pn->pn_left->isKind(ParseNodeKind::ForIn))
return emitForIn(pn, headLexicalEmitterScope);
MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::ForOf));
return emitForOf(pn, headLexicalEmitterScope);
}
MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(ParseNode* pn,
bool needsProto) {
FunctionBox* funbox = pn->pn_funbox;
RootedFunction fun(cx, funbox->function());
RootedAtom name(cx, fun->explicitName());
MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
// Set the |wasEmitted| flag in the funbox once the function has been
// emitted. Function definitions that need hoisting to the top of the
// 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) {
auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) {
// The RHS is the value of the lexically bound name in the
// innermost scope.
return bce->emitGetName(name);
};
// 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));
}
if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false))
return false;
if (!emit1(JSOP_POP)) return false;
}
MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
MOZ_ASSERT(pn->functionIsHoisted());
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->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, pn->pn_pos, emitterMode);
if (!bce2.init()) return false;
/* We measured the max scope depth when we parsed the function. */
if (!bce2.emitFunctionScript(pn->pn_body)) 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(pn->pn_funbox);
// Non-hoisted functions simply emit their respective op.
if (!pn->functionIsHoisted()) {
// JSOP_LAMBDA_ARROW is always preceded by a new.target
MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
if (funbox->isAsync()) {
MOZ_ASSERT(!needsProto);
return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(),
fun->isGenerator());
}
if (fun->isArrow()) {
if (sc->allowNewTarget()) {
if (!emit1(JSOP_NEWTARGET)) return false;
} else {
if (!emit1(JSOP_NULL)) return false;
}
}
if (needsProto) {
MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA);
pn->setOp(JSOP_FUNWITHPROTO);
}
if (pn->getOp() == JSOP_DEFFUN) {
if (!emitIndex32(JSOP_LAMBDA, index)) return false;
return emit1(JSOP_DEFFUN);
}
// This is a FunctionExpression, ArrowFunctionExpression, or class
// constructor. Emit the single instruction (without location info).
return emitIndex32(pn->getOp(), 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(pn->getOp() == JSOP_NOP);
switchToPrologue();
if (funbox->isAsync()) {
if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(),
fun->isGenerator())) {
return false;
}
} else {
if (!emitIndex32(JSOP_LAMBDA, index)) return false;
}
if (!emit1(JSOP_DEFFUN)) 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.
bool isAsync = funbox->isAsync();
bool isGenerator = funbox->isGenerator();
auto emitLambda = [index, isAsync, isGenerator](BytecodeEmitter* bce,
const NameLocation&, bool) {
if (isAsync) {
return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
/* isArrow = */ false, isGenerator);
}
return bce->emitIndexOp(JSOP_LAMBDA, index);
};
if (!emitInitializeName(name, emitLambda)) return false;
if (!emit1(JSOP_POP)) return false;
}
return true;
}
bool BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) {
if (isArrow) {
if (sc->allowNewTarget()) {
if (!emit1(JSOP_NEWTARGET)) return false;
} else {
if (!emit1(JSOP_NULL)) return false;
}
if (!emitIndex32(JSOP_LAMBDA_ARROW, index)) return false;
} else {
if (!emitIndex32(JSOP_LAMBDA, index)) return false;
}
return true;
}
bool BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject,
bool isArrow, bool isGenerator) {
// needsHomeObject can be true for propertyList for extended class.
// In that case push both unwrapped and wrapped function, in order to
// initialize home object of unwrapped function, and set wrapped function
// as a property.
//
// lambda // unwrapped
// dup // unwrapped unwrapped
// toasync // unwrapped wrapped
//
// Emitted code is surrounded by the following code.
//
// // classObj classCtor classProto
// (emitted code) // classObj classCtor classProto unwrapped wrapped
// swap // classObj classCtor classProto wrapped unwrapped
// inithomeobject 1 // classObj classCtor classProto wrapped unwrapped
// // initialize the home object of unwrapped
// // with classProto here
// pop // classObj classCtor classProto wrapped
// inithiddenprop // classObj classCtor classProto wrapped
// // initialize the property of the classProto
// // with wrapped function here
// pop // classObj classCtor classProto
//
// needsHomeObject is false for other cases, push wrapped function only.
if (!emitAsyncWrapperLambda(index, isArrow)) return false;
if (needsHomeObject) {
if (!emit1(JSOP_DUP)) return false;
}
if (isGenerator) {
if (!emit1(JSOP_TOASYNCGEN)) return false;
} else {
if (!emit1(JSOP_TOASYNC)) return false;
}
return true;
}
bool BytecodeEmitter::emitDo(ParseNode* pn) {
/* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */
unsigned noteIndex;
if (!newSrcNote(SRC_WHILE, ¬eIndex)) return false;
if (!emit1(JSOP_NOP)) return false;
unsigned noteIndex2;
if (!newSrcNote(SRC_WHILE, ¬eIndex2)) return false;
/* Compile the loop body. */
JumpTarget top;
if (!emitLoopHead(pn->pn_left, &top)) return false;
LoopControl loopInfo(this, StatementKind::DoLoop);
JumpList empty;
if (!emitLoopEntry(nullptr, empty)) return false;
if (!emitTree(pn->pn_left)) return false;
// Set the offset for continues.
if (!emitJumpTarget(&loopInfo.continueTarget)) return false;
/* Compile the loop condition, now that continues know where to go. */
if (!emitTree(pn->pn_right)) return false;
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) return false;
if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset,
breakTarget.offset))
return false;
/*
* Update the annotations with the update and back edge positions, for
* IonBuilder.
*
* Be careful: We must set noteIndex2 before noteIndex in case the noteIndex
* note gets bigger.
*/
if (!setSrcNoteOffset(noteIndex2, 0, beq.offset - top.offset)) return false;
if (!setSrcNoteOffset(noteIndex, 0,
1 + (loopInfo.continueTarget.offset - top.offset)))
return false;
if (!loopInfo.patchBreaksAndContinues(this)) return false;
return true;
}
bool BytecodeEmitter::emitWhile(ParseNode* pn) {
/*
* Minimize bytecodes issued for one or more iterations by jumping to
* the condition below the body and closing the loop if the condition
* is true with a backward branch. For iteration count i:
*
* i test at the top test at the bottom
* = =============== ==================
* 0 ifeq-pass goto; ifne-fail
* 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail
* 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail
* . . .
* N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail
*/
// If we have a single-line while, like "while (x) ;", we want to
// emit the line note before the initial goto, so that the
// debugger sees a single entry point. This way, if there is a
// breakpoint on the line, it will only fire once; and "next"ing
// will skip the whole loop. However, for the multi-line case we
// want to emit the line note after the initial goto, so that
// "cont" stops on each iteration -- but without a stop before the
// first iteration.
if (parser.tokenStream().srcCoords.lineNum(pn->pn_pos.begin) ==
parser.tokenStream().srcCoords.lineNum(pn->pn_pos.end)) {
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
}
JumpTarget top{-1};
if (!emitJumpTarget(&top)) return false;
LoopControl loopInfo(this, StatementKind::WhileLoop);
loopInfo.continueTarget = top;
unsigned noteIndex;
if (!newSrcNote(SRC_WHILE, ¬eIndex)) return false;
JumpList jmp;
if (!emitJump(JSOP_GOTO, &jmp)) return false;
if (!emitLoopHead(pn->pn_right, &top)) return false;
if (!emitTreeInBranch(pn->pn_right)) return false;
if (!emitLoopEntry(pn->pn_left, jmp)) return false;
if (!emitTree(pn->pn_left)) return false;
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) return false;
if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset,
breakTarget.offset))
return false;
if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) return false;
if (!loopInfo.patchBreaksAndContinues(this)) return false;
return true;
}
bool BytecodeEmitter::emitBreak(PropertyName* label) {
BreakableControl* target;
SrcNoteType noteType;
if (label) {
// Any statement with the matching label may be the break target.
auto hasSameLabel = [label](LabelControl* labelControl) {
return labelControl->label() == label;
};
target = findInnermostNestableControl<LabelControl>(hasSameLabel);
noteType = SRC_BREAK2LABEL;
} else {
auto isNotLabel = [](BreakableControl* control) {
return !control->is<LabelControl>();
};
target = findInnermostNestableControl<BreakableControl>(isNotLabel);
noteType =
(target->kind() == StatementKind::Switch) ? SRC_SWITCHBREAK : SRC_BREAK;
}
return emitGoto(target, &target->breaks, noteType);
}
bool BytecodeEmitter::emitContinue(PropertyName* label) {
LoopControl* target = nullptr;
if (label) {
// Find the loop statement enclosed by the matching label.
NestableControl* control = innermostNestableControl;
while (!control->is<LabelControl>() ||
control->as<LabelControl>().label() != label) {
if (control->is<LoopControl>()) target = &control->as<LoopControl>();
control = control->enclosing();
}
} else {
target = findInnermostNestableControl<LoopControl>();
}
return emitGoto(target, &target->continues, SRC_CONTINUE);
}
bool BytecodeEmitter::emitGetFunctionThis(ParseNode* pn) {
MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function);
MOZ_ASSERT(pn->isKind(ParseNodeKind::Name));
MOZ_ASSERT(pn->name() == cx->names().dotThis);
if (!emitTree(pn)) return false;
if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) return false;
return true;
}
bool BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::SuperBase));
return emitGetFunctionThis(pn->pn_kid);
}
bool BytecodeEmitter::emitThisLiteral(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::This));
if (ParseNode* thisName = pn->pn_kid) return emitGetFunctionThis(thisName);
if (sc->thisBinding() == ThisBinding::Module) return emit1(JSOP_UNDEFINED);
MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
return emit1(JSOP_GLOBALTHIS);
}
bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() {
MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot());
if (!emitGetName(cx->names().dotThis)) return false;
if (!emit1(JSOP_CHECKRETURN)) return false;
return true;
}
bool BytecodeEmitter::emitReturn(ParseNode* pn) {
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
bool needsIteratorResult =
sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult();
if (needsIteratorResult) {
if (!emitPrepareIteratorResult()) return false;
}
/* Push a return value */
if (ParseNode* pn2 = pn->pn_kid) {
if (!emitTree(pn2)) return false;
bool isAsyncGenerator =
sc->asFunctionBox()->isAsync() && sc->asFunctionBox()->isGenerator();
if (isAsyncGenerator) {
if (!emitAwaitInInnermostScope()) return false;
}
} else {
/* No explicit return value provided */
if (!emit1(JSOP_UNDEFINED)) return false;
}
if (needsIteratorResult) {
if (!emitFinishIteratorResult(true)) return false;
}
// We know functionBodyEndPos is set because "return" is only
// valid in a function, and so we've passed through
// emitFunctionScript.
MOZ_ASSERT(functionBodyEndPosSet);
if (!updateSourceCoordNotes(functionBodyEndPos)) return false;
/*
* EmitNonLocalJumpFixup may add fixup bytecode to close open try
* blocks having finally clauses and to exit intermingled let blocks.
* We can't simply transfer control flow to our caller in that case,
* because we must gosub to those finally clauses from inner to outer,
* with the correct stack pointer (i.e., after popping any with,
* for/in, etc., slots nested inside the finally's try).
*
* In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an
* extra JSOP_RETRVAL after the fixups.
*/
ptrdiff_t top = offset();
bool needsFinalYield =
sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield();
bool isDerivedClassConstructor =
sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor();
if (!emit1((needsFinalYield || isDerivedClassConstructor) ? JSOP_SETRVAL
: JSOP_RETURN))
return false;
// Make sure that we emit this before popping the blocks in
// prepareForNonLocalJump, to ensure that the error is thrown while the
// scope-chain is still intact.
if (isDerivedClassConstructor) {
if (!emitCheckDerivedClassConstructorReturn()) return false;
}
NonLocalExitControl nle(this, NonLocalExitControl::Return);
if (!nle.prepareForNonLocalJumpToOutermost()) return false;
if (needsFinalYield) {
// We know that .generator is on the function scope, as we just exited
// all nested scopes.
NameLocation loc = *locationOfNameBoundInFunctionScope(
cx->names().dotGenerator, varEmitterScope);
if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) return false;
if (!emitYieldOp(JSOP_FINALYIELDRVAL)) return false;
} else if (isDerivedClassConstructor) {
MOZ_ASSERT(code()[top] == JSOP_SETRVAL);
if (!emit1(JSOP_RETRVAL)) return false;
} else if (top + static_cast<ptrdiff_t>(JSOP_RETURN_LENGTH) != offset()) {
code()[top] = JSOP_SETRVAL;
if (!emit1(JSOP_RETRVAL)) return false;
}
return true;
}
bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) {
NameLocation loc = *locationOfNameBoundInFunctionScope(
cx->names().dotGenerator, ¤tScope);
return emitGetNameAtLocation(cx->names().dotGenerator, loc);
}
bool BytecodeEmitter::emitInitialYield(ParseNode* pn) {
if (!emitTree(pn->pn_kid)) return false;
if (!emitYieldOp(JSOP_INITIALYIELD)) return false;
if (!emit1(JSOP_POP)) return false;
return true;
}
bool BytecodeEmitter::emitYield(ParseNode* pn) {
MOZ_ASSERT(sc->isFunctionBox());
MOZ_ASSERT(pn->isKind(ParseNodeKind::Yield));
bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult();
if (needsIteratorResult) {
if (!emitPrepareIteratorResult()) return false;
}
if (pn->pn_kid) {
if (!emitTree(pn->pn_kid)) return false;
} else {
if (!emit1(JSOP_UNDEFINED)) return false;
}
// 11.4.3.7 AsyncGeneratorYield step 5.
bool isAsyncGenerator = sc->asFunctionBox()->isAsync();
if (isAsyncGenerator) {
if (!emitAwaitInInnermostScope()) // RESULT
return false;
}
if (needsIteratorResult) {
if (!emitFinishIteratorResult(false)) return false;
}
if (!emitGetDotGeneratorInInnermostScope()) return false;
if (!emitYieldOp(JSOP_YIELD)) return false;
return true;
}
bool BytecodeEmitter::emitAwaitInInnermostScope(ParseNode* pn) {
MOZ_ASSERT(sc->isFunctionBox());
MOZ_ASSERT(pn->isKind(ParseNodeKind::Await));
if (!emitTree(pn->pn_kid)) return false;
return emitAwaitInInnermostScope();
}
bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) {
if (!emitGetDotGeneratorInScope(currentScope)) return false;
if (!emitYieldOp(JSOP_AWAIT)) return false;
return true;
}
bool BytecodeEmitter::emitYieldStar(ParseNode* iter) {
MOZ_ASSERT(sc->isFunctionBox());
MOZ_ASSERT(sc->asFunctionBox()->isGenerator());
bool isAsyncGenerator = sc->asFunctionBox()->isAsync();
if (!emitTree(iter)) // ITERABLE
return false;
if (isAsyncGenerator) {
if (!emitAsyncIterator()) // NEXT ITER
return false;
} else {
if (!emitIterator()) // NEXT ITER
return false;
}
// Initial send value is undefined.
if (!emit1(JSOP_UNDEFINED)) // NEXT ITER RECEIVED
return false;
int32_t savedDepthTemp;
int32_t startDepth = stackDepth;
MOZ_ASSERT(startDepth >= 3);
TryEmitter tryCatch(this, TryEmitter::TryCatchFinally,
TryEmitter::DontUseRetVal, TryEmitter::DontUseControl);
if (!tryCatch.emitJumpOverCatchAndFinally()) // NEXT ITER RESULT
return false;
JumpTarget tryStart{offset()};
if (!tryCatch.emitTry()) // NEXT ITER RESULT
return false;
MOZ_ASSERT(this->stackDepth == startDepth);
// 11.4.3.7 AsyncGeneratorYield step 5.
if (isAsyncGenerator) {
if (!emitAwaitInInnermostScope()) // NEXT ITER RESULT
return false;
}
// Load the generator object.
if (!emitGetDotGeneratorInInnermostScope()) // NEXT ITER RESULT GENOBJ
return false;
// Yield RESULT as-is, without re-boxing.
if (!emitYieldOp(JSOP_YIELD)) // NEXT ITER RECEIVED
return false;
if (!tryCatch.emitCatch()) // NEXT ITER RESULT
return false;
stackDepth = startDepth; // NEXT ITER RESULT
if (!emit1(JSOP_EXCEPTION)) // NEXT ITER RESULT EXCEPTION
return false;
if (!emitDupAt(2)) // NEXT ITER RESULT EXCEPTION ITER
return false;
if (!emit1(JSOP_DUP)) // NEXT ITER RESULT EXCEPTION ITER ITER
return false;
if (!emitAtomOp(cx->names().throw_,
JSOP_CALLPROP)) // NEXT ITER RESULT EXCEPTION ITER THROW
return false;
if (!emit1(JSOP_DUP)) // NEXT ITER RESULT EXCEPTION ITER THROW THROW
return false;
if (!emit1(JSOP_UNDEFINED)) // NEXT ITER RESULT EXCEPTION ITER THROW THROW
// UNDEFINED
return false;
if (!emit1(JSOP_EQ)) // NEXT ITER RESULT EXCEPTION ITER THROW ?EQL
return false;
IfThenElseEmitter ifThrowMethodIsNotDefined(this);
if (!ifThrowMethodIsNotDefined
.emitIf()) // NEXT ITER RESULT EXCEPTION ITER THROW
return false;
savedDepthTemp = stackDepth;
if (!emit1(JSOP_POP)) // NEXT ITER RESULT EXCEPTION ITER
return false;
// ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2
//
// If the iterator does not have a "throw" method, it calls IteratorClose
// and then throws a TypeError.
IteratorKind iterKind =
isAsyncGenerator ? IteratorKind::Async : IteratorKind::Sync;
if (!emitIteratorCloseInInnermostScope(
iterKind)) // NEXT ITER RESULT EXCEPTION
return false;
if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw
return false;
stackDepth = savedDepthTemp;
if (!ifThrowMethodIsNotDefined
.emitEnd()) // NEXT ITER OLDRESULT EXCEPTION ITER THROW
return false;
// ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4.
// RESULT = ITER.throw(EXCEPTION) // NEXT ITER OLDRESULT
// EXCEPTION ITER THROW
if (!emit1(JSOP_SWAP)) // NEXT ITER OLDRESULT EXCEPTION THROW ITER
return false;
if (!emit2(JSOP_PICK, 2)) // NEXT ITER OLDRESULT THROW ITER EXCEPTION
return false;
if (!emitCall(JSOP_CALL, 1, iter)) // NEXT ITER OLDRESULT RESULT
return false;
checkTypeSet(JSOP_CALL);
if (isAsyncGenerator) {
if (!emitAwaitInInnermostScope()) // NEXT ITER OLDRESULT RESULT
return false;
}
if (!emitCheckIsObj(
CheckIsObjectKind::IteratorThrow)) // NEXT ITER OLDRESULT RESULT
return false;
if (!emit1(JSOP_SWAP)) // NEXT ITER RESULT OLDRESULT
return false;
if (!emit1(JSOP_POP)) // NEXT ITER RESULT
return false;
MOZ_ASSERT(this->stackDepth == startDepth);
JumpList checkResult;
// ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii.
//
// Note that there is no GOSUB to the finally block here. If the iterator has
// a "throw" method, it does not perform IteratorClose.
if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult
return false;
if (!tryCatch.emitFinally()) return false;
// ES 14.4.13, yield * AssignmentExpression, step 5.c
//
// Call iterator.return() for receiving a "forced return" completion from
// the generator.
IfThenElseEmitter ifGeneratorClosing(this);
if (!emit1(JSOP_ISGENCLOSING)) // NEXT ITER RESULT FTYPE FVALUE CLOSING
return false;
if (!ifGeneratorClosing.emitIf()) // NEXT ITER RESULT FTYPE FVALUE
return false;
// Step ii.
//
// Get the "return" method.
if (!emitDupAt(3)) // NEXT ITER RESULT FTYPE FVALUE ITER
return false;
if (!emit1(JSOP_DUP)) // NEXT ITER RESULT FTYPE FVALUE ITER ITER
return false;
if (!emitAtomOp(cx->names().return_,
JSOP_CALLPROP)) // NEXT ITER RESULT FTYPE FVALUE ITER RET
return false;
// Step iii.
//
// Do nothing if "return" is undefined or null.
IfThenElseEmitter ifReturnMethodIsDefined(this);
if (!emitPushNotUndefinedOrNull()) // NEXT ITER RESULT FTYPE FVALUE ITER RET
// NOT-UNDEF-OR-NULL
return false;
// Step iv.
//
// Call "return" with the argument passed to Generator.prototype.return,
// which is currently in rval.value.
if (!ifReturnMethodIsDefined
.emitIfElse()) // NEXT ITER OLDRESULT FTYPE FVALUE ITER RET
return false;
if (!emit1(JSOP_SWAP)) // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER
return false;
if (!emit1(JSOP_GETRVAL)) // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER RVAL
return false;
if (!emitAtomOp(
cx->names().value,
JSOP_GETPROP)) // NEXT ITER OLDRESULT FTYPE FVALUE RET ITER VALUE
return false;
if (!emitCall(JSOP_CALL, 1)) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
return false;
checkTypeSet(JSOP_CALL);
if (iterKind == IteratorKind::Async) {
if (!emitAwaitInInnermostScope()) // ... FTYPE FVALUE RESULT
return false;
}
// Step v.
if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // NEXT ITER
// OLDRESULT FTYPE
// FVALUE RESULT
return false;
// Steps vi-viii.
//
// Check if the returned object from iterator.return() is done. If not,
// continuing yielding.
IfThenElseEmitter ifReturnDone(this);
if (!emit1(JSOP_DUP)) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT RESULT
return false;
if (!emitAtomOp(
cx->names().done,
JSOP_GETPROP)) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT DONE
return false;
if (!ifReturnDone.emitIfElse()) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
return false;
if (!emitAtomOp(cx->names().value,
JSOP_GETPROP)) // NEXT ITER OLDRESULT FTYPE FVALUE VALUE
return false;
if (!emitPrepareIteratorResult()) // NEXT ITER OLDRESULT FTYPE FVALUE VALUE
// RESULT
return false;
if (!emit1(JSOP_SWAP)) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT VALUE
return false;
if (!emitFinishIteratorResult(
true)) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
return false;
if (!emit1(JSOP_SETRVAL)) // NEXT ITER OLDRESULT FTYPE FVALUE
return false;
savedDepthTemp = this->stackDepth;
if (!ifReturnDone.emitElse()) // NEXT ITER OLDRESULT FTYPE FVALUE RESULT
return false;
if (!emit2(JSOP_UNPICK, 3)) // NEXT ITER RESULT OLDRESULT FTYPE FVALUE
return false;
if (!emitPopN(3)) // NEXT ITER RESULT
return false;
{
// goto tryStart;
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq,
&breakTarget)) // NEXT ITER RESULT
return false;
}
this->stackDepth = savedDepthTemp;
if (!ifReturnDone.emitEnd()) return false;
if (!ifReturnMethodIsDefined
.emitElse()) // NEXT ITER RESULT FTYPE FVALUE ITER RET
return false;
if (!emitPopN(2)) // NEXT ITER RESULT FTYPE FVALUE
return false;
if (!ifReturnMethodIsDefined.emitEnd()) return false;
if (!ifGeneratorClosing.emitEnd()) return false;
if (!tryCatch.emitEnd()) return false;
// [stack] NEXT ITER RECEIVED
// After the try-catch-finally block: send the received value to the iterator.
// result = iter.next(received) // NEXT ITER
// RECEIVED
if (!emit2(JSOP_UNPICK, 2)) // RECEIVED NEXT ITER
return false;
if (!emit1(JSOP_DUP2)) // RECEIVED NEXT ITER NEXT ITER
return false;
if (!emit2(JSOP_PICK, 4)) // NEXT ITER NEXT ITER RECEIVED
return false;
if (!emitCall(JSOP_CALL, 1, iter)) // NEXT ITER RESULT
return false;
checkTypeSet(JSOP_CALL);
if (isAsyncGenerator) {
if (!emitAwaitInInnermostScope()) // NEXT ITER RESULT RESULT
return false;
}
if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // NEXT ITER RESULT
return false;
MOZ_ASSERT(this->stackDepth == startDepth);
if (!emitJumpTargetAndPatch(checkResult)) // checkResult:
return false;
// if (!result.done) goto tryStart; // NEXT ITER
// RESULT
if (!emit1(JSOP_DUP)) // NEXT ITER RESULT RESULT
return false;
if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // NEXT ITER RESULT DONE
return false;
// if (!DONE) goto tryStart;
{
JumpList beq;
JumpTarget breakTarget{-1};
if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq,
&breakTarget)) // NEXT ITER RESULT
return false;
}
// result.value
if (!emit2(JSOP_UNPICK, 2)) // RESULT NEXT ITER
return false;
if (!emitPopN(2)) // RESULT
return false;
if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE
return false;
MOZ_ASSERT(this->stackDepth == startDepth - 2);
return true;
}
bool BytecodeEmitter::emitStatementList(ParseNode* pn) {
MOZ_ASSERT(pn->isArity(PN_LIST));
for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
if (!emitTree(pn2)) return false;
}
return true;
}
bool BytecodeEmitter::emitExpressionStatement(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStatement));
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
/*
* Top-level or called-from-a-native JS_Execute/EvaluateScript,
* debugger, and eval frames may need the value of the ultimate
* expression statement as the script's result, despite the fact
* that it appears useless to the compiler.
*
* API users may also set the JSOPTION_NO_SCRIPT_RVAL option when
* calling JS_Compile* to suppress JSOP_SETRVAL.
*/
bool wantval = false;
bool useful = false;
if (sc->isFunctionBox())
MOZ_ASSERT(!script->noScriptRval());
else
useful = wantval = !script->noScriptRval();
/* Don't eliminate expressions with side effects. */
ParseNode* expr = pn->pn_kid;
if (!useful) {
if (!checkSideEffects(expr, &useful)) return false;
/*
* Don't eliminate apparently useless expressions if they are labeled
* expression statements. The startOffset() test catches the case
* where we are nesting in emitTree for a labeled compound statement.
*/
if (innermostNestableControl &&
innermostNestableControl->is<LabelControl>() &&
innermostNestableControl->as<LabelControl>().startOffset() >=
offset()) {
useful = true;
}
}
if (useful) {
JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP;
ValueUsage valueUsage =
wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue;
MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::Assign), expr->isOp(JSOP_NOP));
if (!emitTree(expr, valueUsage)) return false;
if (!emit1(op)) return false;
} else if (pn->isDirectivePrologueMember()) {
// Don't complain about directive prologue members; just don't emit
// their code.
} else {
if (JSAtom* atom = pn->isStringExprStatement()) {
// Warn if encountering a non-directive prologue member string
// expression statement, that is inconsistent with the current
// directive prologue. That is, a script *not* starting with
// "use strict" should warn for any "use strict" statements seen
// later in the script, because such statements are misleading.
const char* directive = nullptr;
if (atom == cx->names().useStrict) {
if (!sc->strictScript) directive = js_useStrict_str;
} else if (atom == cx->names().useAsm) {
if (sc->isFunctionBox()) {
if (IsAsmJSModule(sc->asFunctionBox()->function()))
directive = js_useAsm_str;
}
}
if (directive) {
if (!reportExtraWarning(expr, JSMSG_CONTRARY_NONDIRECTIVE, directive))
return false;
}
} else {
if (!reportExtraWarning(expr, JSMSG_USELESS_EXPR)) return false;
}
}
return true;
}
bool BytecodeEmitter::emitDeleteName(ParseNode* node) {
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteName));
MOZ_ASSERT(node->isArity(PN_UNARY));
ParseNode* nameExpr = node->pn_kid;
MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name));
return emitAtomOp(nameExpr, JSOP_DELNAME);
}
bool BytecodeEmitter::emitDeleteProperty(ParseNode* node) {
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteProp));
MOZ_ASSERT(node->isArity(PN_UNARY));
ParseNode* propExpr = node->pn_kid;
MOZ_ASSERT(propExpr->isKind(ParseNodeKind::Dot));
if (propExpr->as<PropertyAccess>().isSuper()) {
// Still have to calculate the base, even though we are are going
// to throw unconditionally, as calculating the base could also
// throw.
if (!emit1(JSOP_SUPERBASE)) return false;
return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER);
}
JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
return emitPropOp(propExpr, delOp);
}
bool BytecodeEmitter::emitDeleteElement(ParseNode* node) {
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteElem));
MOZ_ASSERT(node->isArity(PN_UNARY));
ParseNode* elemExpr = node->pn_kid;
MOZ_ASSERT(elemExpr->isKind(ParseNodeKind::Elem));
if (elemExpr->as<PropertyByValue>().isSuper()) {
// Still have to calculate everything, even though we're gonna throw
// since it may have side effects
if (!emitTree(elemExpr->pn_right)) return false;
if (!emit1(JSOP_SUPERBASE)) return false;
if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER))
return false;
// Another wrinkle: Balance the stack from the emitter's point of view.
// Execution will not reach here, as the last bytecode threw.
return emit1(JSOP_POP);
}
JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
return emitElemOp(elemExpr, delOp);
}
bool BytecodeEmitter::emitDeleteExpression(ParseNode* node) {
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteExpr));
MOZ_ASSERT(node->isArity(PN_UNARY));
ParseNode* expression = node->pn_kid;
// If useless, just emit JSOP_TRUE; otherwise convert |delete <expr>| to
// effectively |<expr>, true|.
bool useful = false;
if (!checkSideEffects(expression, &useful)) return false;
if (useful) {
if (!emitTree(expression)) return false;
if (!emit1(JSOP_POP)) return false;
}
return emit1(JSOP_TRUE);
}
static const char* SelfHostedCallFunctionName(JSAtom* name, JSContext* cx) {
if (name == cx->names().callFunction) return "callFunction";
if (name == cx->names().callContentFunction) return "callContentFunction";
if (name == cx->names().constructContentFunction)
return "constructContentFunction";
MOZ_CRASH("Unknown self-hosted call function name");
}
bool BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) {
// Special-casing of callFunction to emit bytecode that directly
// invokes the callee with the correct |this| object and arguments.
// callFunction(fun, thisArg, arg0, arg1) thus becomes:
// - emit lookup for fun
// - emit lookup for thisArg
// - emit lookups for arg0, arg1
//
// argc is set to the amount of actually emitted args and the
// emitting of args below is disabled by setting emitArgs to false.
ParseNode* pn2 = pn->pn_head;
const char* errorName = SelfHostedCallFunctionName(pn2->name(), cx);
if (pn->pn_count < 3) {
reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s");
return false;
}
JSOp callOp = pn->getOp();
if (callOp != JSOP_CALL) {
reportError(pn, JSMSG_NOT_CONSTRUCTOR, errorName);
return false;
}
bool constructing = pn2->name() == cx->names().constructContentFunction;
ParseNode* funNode = pn2->pn_next;
if (constructing) {
callOp = JSOP_NEW;
} else if (funNode->getKind() == ParseNodeKind::Name &&
funNode->name() == cx->names().std_Function_apply) {
callOp = JSOP_FUNAPPLY;
}
if (!emitTree(funNode)) return false;
#ifdef DEBUG
if (emitterMode == BytecodeEmitter::SelfHosting &&
pn2->name() == cx->names().callFunction) {
if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) return false;
}
#endif
ParseNode* thisOrNewTarget = funNode->pn_next;
if (constructing) {
// Save off the new.target value, but here emit a proper |this| for a
// constructing call.
if (!emit1(JSOP_IS_CONSTRUCTING)) return false;
} else {
// It's |this|, emit it.
if (!emitTree(thisOrNewTarget)) return false;
}
for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn;
argpn = argpn->pn_next) {
if (!emitTree(argpn)) return false;
}
if (constructing) {
if (!emitTree(thisOrNewTarget)) return false;
}
uint32_t argc = pn->pn_count - 3;
if (!emitCall(callOp, argc)) return false;
checkTypeSet(callOp);
return true;
}
bool BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn) {
// Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return')
if (pn->pn_count != 4) {
reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s");
return false;
}
ParseNode* funNode = pn->pn_head; // The resumeGenerator node.
ParseNode* genNode = funNode->pn_next;
if (!emitTree(genNode)) return false;
ParseNode* valNode = genNode->pn_next;
if (!emitTree(valNode)) return false;
ParseNode* kindNode = valNode->pn_next;
MOZ_ASSERT(kindNode->isKind(ParseNodeKind::String));
uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->pn_atom);
MOZ_ASSERT(!kindNode->pn_next);
if (!emitCall(JSOP_RESUME, operand)) return false;
return true;
}
bool BytecodeEmitter::emitSelfHostedForceInterpreter() {
if (!emit1(JSOP_FORCEINTERPRETER)) return false;
if (!emit1(JSOP_UNDEFINED)) return false;
return true;
}
bool BytecodeEmitter::emitSelfHostedAllowContentIter(ParseNode* pn) {
if (pn->pn_count != 2) {
reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentIter", "1", "");
return false;
}
// We're just here as a sentinel. Pass the value through directly.
return emitTree(pn->pn_head->pn_next);
}
bool BytecodeEmitter::emitSelfHostedDefineDataProperty(ParseNode* pn) {
// Only optimize when 3 arguments are passed (we use 4 to include |this|).
MOZ_ASSERT(pn->pn_count == 4);
ParseNode* funNode = pn->pn_head; // The _DefineDataProperty node.
ParseNode* objNode = funNode->pn_next;
if (!emitTree(objNode)) return false;
ParseNode* idNode = objNode->pn_next;
if (!emitTree(idNode)) return false;
ParseNode* valNode = idNode->pn_next;
if (!emitTree(valNode)) return false;
// This will leave the object on the stack instead of pushing |undefined|,
// but that's fine because the self-hosted code doesn't use the return
// value.
return emit1(JSOP_INITELEM);
}
bool BytecodeEmitter::emitSelfHostedHasOwn(ParseNode* pn) {
if (pn->pn_count != 3) {
reportError(pn, JSMSG_MORE_ARGS_NEEDED, "hasOwn", "2", "");
return false;
}
ParseNode* funNode = pn->pn_head; // The hasOwn node.
ParseNode* idNode = funNode->pn_next;
if (!emitTree(idNode)) return false;
ParseNode* objNode = idNode->pn_next;
if (!emitTree(objNode)) return false;
return emit1(JSOP_HASOWN);
}
bool BytecodeEmitter::emitSelfHostedGetPropertySuper(ParseNode* pn) {
if (pn->pn_count != 4) {
reportError(pn, JSMSG_MORE_ARGS_NEEDED, "getPropertySuper", "3", "");
return false;
}
ParseNode* funNode = pn->pn_head; // The getPropertySuper node.
ParseNode* objNode = funNode->pn_next;
ParseNode* idNode = objNode->pn_next;
ParseNode* receiverNode = idNode->pn_next;
if (!emitTree(idNode)) return false;
if (!emitTree(receiverNode)) return false;
if (!emitTree(objNode)) return false;
return emitElemOpBase(JSOP_GETELEM_SUPER);
}
bool BytecodeEmitter::isRestParameter(ParseNode* pn) {
if (!sc->isFunctionBox()) return false;
FunctionBox* funbox = sc->asFunctionBox();
RootedFunction fun(cx, funbox->function());
if (!funbox->hasRest()) return false;
if (!pn->isKind(ParseNodeKind::Name)) {
if (emitterMode == BytecodeEmitter::SelfHosting &&
pn->isKind(ParseNodeKind::Call)) {
ParseNode* pn2 = pn->pn_head;
if (pn2->getKind() == ParseNodeKind::Name &&
pn2->name() == cx->names().allowContentIter) {
return isRestParameter(pn2->pn_next);
}
}
return false;
}
JSAtom* name = pn->name();
Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name);
if (paramLoc && lookupName(name) == *paramLoc) {
FunctionScope::Data* bindings = funbox->functionScopeBindings();
if (bindings->nonPositionalFormalStart > 0) {
// |paramName| can be nullptr when the rest destructuring syntax is
// used: `function f(...[]) {}`.
JSAtom* paramName =
bindings->names[bindings->nonPositionalFormalStart - 1].name();
return paramName && name == paramName;
}
}
return false;
}
bool BytecodeEmitter::emitCallee(ParseNode* callee, ParseNode* call,
bool* callop) {
switch (callee->getKind()) {
case ParseNodeKind::Name:
if (!emitGetName(callee, *callop)) return false;
break;
case ParseNodeKind::Dot:
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
if (callee->as<PropertyAccess>().isSuper()) {
if (!emitSuperPropOp(callee, JSOP_GETPROP_SUPER,
/* isCall = */ *callop))
return false;
} else {
if (!emitPropOp(callee, *callop ? JSOP_CALLPROP : JSOP_GETPROP))
return false;
}
break;
case ParseNodeKind::Elem:
MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
if (callee->as<PropertyByValue>().isSuper()) {
if (!emitSuperElemOp(callee, JSOP_GETELEM_SUPER,
/* isCall = */ *callop))
return false;
} else {
if (!emitElemOp(callee, *callop ? JSOP_CALLELEM : JSOP_GETELEM))
return false;
if (*callop) {
if (!emit1(JSOP_SWAP)) return false;
}
}
break;
case ParseNodeKind::Function:
/*
* Top level lambdas which are immediately invoked should be
* treated as only running once. Every time they execute we will
* create new types and scripts for their contents, to increase
* the quality of type information within them and enable more
* backend optimizations. Note that this does not depend on the
* lambda being invoked at most once (it may be named or be
* accessed via foo.caller indirection), as multiple executions
* will just cause the inner scripts to be repeatedly cloned.
*/
MOZ_ASSERT(!emittingRunOnceLambda);
if (checkRunOnceContext()) {
emittingRunOnceLambda = true;
if (!emitTree(callee)) return false;
emittingRunOnceLambda = false;
} else {
if (!emitTree(callee)) return false;
}
*callop = false;
break;
case ParseNodeKind::SuperBase:
MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall));
MOZ_ASSERT(parser.isSuperBase(callee));
if (!emit1(JSOP_SUPERFUN)) return false;
break;
default:
if (!emitTree(callee)) return false;
*callop = false; /* trigger JSOP_UNDEFINED after */
break;
}
return true;
}
bool BytecodeEmitter::emitPipeline(ParseNode* pn) {
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_count >= 2);
if (!emitTree(pn->pn_head)) return false;
ParseNode* callee = pn->pn_head->pn_next;
do {
bool callop = true;
if (!emitCallee(callee, pn, &callop)) return false;
// Emit room for |this|
if (!callop) {
if (!emit1(JSOP_UNDEFINED)) return false;
}
if (!emit2(JSOP_PICK, 2)) return false;
if (!emitCall(JSOP_CALL, 1, pn)) return false;
checkTypeSet(JSOP_CALL);
} while ((callee = callee->pn_next));
return true;
}
bool BytecodeEmitter::emitCallOrNew(
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) {
bool callop = pn->isKind(ParseNodeKind::Call) ||
pn->isKind(ParseNodeKind::TaggedTemplate);
/*
* Emit callable invocation or operator new (constructor call) code.
* First, emit code for the left operand to evaluate the callable or
* constructable object expression.
*
* For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc.
* This is necessary to interpose the lambda-initialized method read
* barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by
* JSOP_{SET,INIT}PROP.
*
* Then (or in a call case that has no explicit reference-base
* object) we emit JSOP_UNDEFINED to produce the undefined |this|
* value required for calls (which non-strict mode functions
* will box into the global object).
*/
uint32_t argc = pn->pn_count - 1;
if (argc >= ARGC_LIMIT) {
parser.reportError(callop ? JSMSG_TOO_MANY_FUN_ARGS
: JSMSG_TOO_MANY_CON_ARGS);
return false;
}
ParseNode* pn2 = pn->pn_head;
bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
if (pn2->isKind(ParseNodeKind::Name) &&
emitterMode == BytecodeEmitter::SelfHosting && !spread) {
// Calls to "forceInterpreter", "callFunction",
// "callContentFunction", or "resumeGenerator" in self-hosted
// code generate inline bytecode.
if (pn2->name() == cx->names().callFunction ||
pn2->name() == cx->names().callContentFunction ||
pn2->name() == cx->names().constructContentFunction) {
return emitSelfHostedCallFunction(pn);
}
if (pn2->name() == cx->names().resumeGenerator)
return emitSelfHostedResumeGenerator(pn);
if (pn2->name() == cx->names().forceInterpreter)
return emitSelfHostedForceInterpreter();
if (pn2->name() == cx->names().allowContentIter)
return emitSelfHostedAllowContentIter(pn);
if (pn2->name() == cx->names().defineDataPropertyIntrinsic &&
pn->pn_count == 4)
return emitSelfHostedDefineDataProperty(pn);
if (pn2->name() == cx->names().hasOwn) return emitSelfHostedHasOwn(pn);
if (pn2->name() == cx->names().getPropertySuper)
return emitSelfHostedGetPropertySuper(pn);
// Fall through
}
if (!emitCallee(pn2, pn, &callop)) return false;
bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW ||
pn->getOp() == JSOP_SUPERCALL ||
pn->getOp() == JSOP_SPREADSUPERCALL;
// Emit room for |this|.
if (!callop) {
if (isNewOp) {
if (!emit1(JSOP_IS_CONSTRUCTING)) return false;
} else {
if (!emit1(JSOP_UNDEFINED)) return false;
}
}
/*
* Emit code for each argument in order, then emit the JSOP_*CALL or
* JSOP_NEW bytecode with a two-byte immediate telling how many args
* were pushed on the operand stack.
*/
if (!spread) {
for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
if (!emitTree(pn3)) return false;
}
if (isNewOp) {
if (pn->isKind(ParseNodeKind::SuperCall)) {
if (!emit1(JSOP_NEWTARGET)) return false;
} else {
// Repush the callee as new.target
if (!emitDupAt(argc + 1)) return false;
}
}
} else {
ParseNode* args = pn2->pn_next;
bool emitOptCode = (argc == 1) && isRestParameter(args->pn_kid);
IfThenElseEmitter ifNotOptimizable(this);
if (emitOptCode) {
// Emit a preparation code to optimize the spread call with a rest
// parameter:
//
// function f(...args) {
// g(...args);
// }
//
// If the spread operand is a rest parameter and it's optimizable
// array, skip spread operation and pass it directly to spread call
// operation. See the comment in OptimizeSpreadCall in
// Interpreter.cpp for the optimizable conditons.
if (!emitTree(args->pn_kid)) return false;
if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) return false;
if (!emit1(JSOP_NOT)) return false;
if (!ifNotOptimizable.emitIf()) return false;
if (!emit1(JSOP_POP)) return false;
}
if (!emitArray(args, argc)) return false;
if (emitOptCode) {
if (!ifNotOptimizable.emitEnd()) return false;
}
if (isNewOp) {
if (pn->isKind(ParseNodeKind::SuperCall)) {
if (!emit1(JSOP_NEWTARGET)) return false;
} else {
if (!emitDupAt(2)) return false;
}
}
}
if (!spread) {
if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
if (!emitCall(JSOP_CALL_IGNORES_RV, argc, pn)) return false;
checkTypeSet(JSOP_CALL_IGNORES_RV);
} else {
if (!emitCall(pn->getOp(), argc, pn)) return false;
checkTypeSet(pn->getOp());
}
} else {
if (!emit1(pn->getOp())) return false;
checkTypeSet(pn->getOp());
}
if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_STRICTEVAL) ||
pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_STRICTSPREADEVAL)) {
uint32_t lineNum = parser.tokenStream().srcCoords.lineNum(pn->pn_pos.begin);
if (!emitUint32Operand(JSOP_LINENO, lineNum)) return false;
}
return true;
}
static const JSOp ParseNodeKindToJSOp[] = {
// JSOP_NOP is for pipeline operator which does not emit its own JSOp
// but has highest precedence in binary operators
JSOP_NOP, JSOP_OR, JSOP_AND, JSOP_BITOR, JSOP_BITXOR,
JSOP_BITAND, JSOP_STRICTEQ, JSOP_EQ, JSOP_STRICTNE, JSOP_NE,
JSOP_LT, JSOP_LE, JSOP_GT, JSOP_GE, JSOP_INSTANCEOF,
JSOP_IN, JSOP_LSH, JSOP_RSH, JSOP_URSH, JSOP_ADD,
JSOP_SUB, JSOP_MUL, JSOP_DIV, JSOP_MOD, JSOP_POW};
static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) {
MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst);
MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast);
return ParseNodeKindToJSOp[size_t(pnk) - size_t(ParseNodeKind::BinOpFirst)];
}
bool BytecodeEmitter::emitRightAssociative(ParseNode* pn) {
// ** is the only right-associative operator.
MOZ_ASSERT(pn->isKind(ParseNodeKind::Pow));
MOZ_ASSERT(pn->isArity(PN_LIST));
// Right-associative operator chain.
for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) {
if (!emitTree(subexpr)) return false;
}
for (uint32_t i = 0; i < pn->pn_count - 1; i++) {
if (!emit1(JSOP_POW)) return false;
}
return true;
}
bool BytecodeEmitter::emitLeftAssociative(ParseNode* pn) {
MOZ_ASSERT(pn->isArity(PN_LIST));
// Left-associative operator chain.
if (!emitTree(pn->pn_head)) return false;
JSOp op = BinaryOpParseNodeKindToJSOp(pn->getKind());
ParseNode* nextExpr = pn->pn_head->pn_next;
do {
if (!emitTree(nextExpr)) return false;
if (!emit1(op)) return false;
} while ((nextExpr = nextExpr->pn_next));
return true;
}
bool BytecodeEmitter::emitLogical(ParseNode* pn) {
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->isKind(ParseNodeKind::Or) || pn->isKind(ParseNodeKind::And));
/*
* JSOP_OR converts the operand on the stack to boolean, leaves the original
* value on the stack and jumps if true; otherwise it falls into the next
* bytecode, which pops the left operand and then evaluates the right operand.
* The jump goes around the right operand evaluation.
*
* JSOP_AND converts the operand on the stack to boolean and jumps if false;
* otherwise it falls into the right operand's bytecode.
*/
TDZCheckCache tdzCache(this);
/* Left-associative operator chain: avoid too much recursion. */
ParseNode* pn2 = pn->pn_head;
if (!emitTree(pn2)) return false;
JSOp op = pn->isKind(ParseNodeKind::Or) ? JSOP_OR : JSOP_AND;
JumpList jump;
if (!emitJump(op, &jump)) return false;
if (!emit1(JSOP_POP)) return false;
/* Emit nodes between the head and the tail. */
while ((pn2 = pn2->pn_next)->pn_next) {
if (!emitTree(pn2)) return false;
if (!emitJump(op, &jump)) return false;
if (!emit1(JSOP_POP)) return false;
}
if (!emitTree(pn2)) return false;
if (!emitJumpTargetAndPatch(jump)) return false;
return true;
}
bool BytecodeEmitter::emitSequenceExpr(
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) {
for (ParseNode* child = pn->pn_head;; child = child->pn_next) {
if (!updateSourceCoordNotes(child->pn_pos.begin)) return false;
if (!emitTree(child, child->pn_next ? ValueUsage::IgnoreValue : valueUsage))
return false;
if (!child->pn_next) break;
if (!emit1(JSOP_POP)) return false;
}
return true;
}
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(ParseNode* pn) {
switch (pn->pn_kid->getKind()) {
case ParseNodeKind::Dot:
return emitPropIncDec(pn);
case ParseNodeKind::Elem:
return emitElemIncDec(pn);
case ParseNodeKind::Call:
return emitCallIncDec(pn);
default:
return emitNameIncDec(pn);
}
}
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement(
const LabeledStatement* pn) {
/*
* Emit a JSOP_LABEL instruction. The argument is the offset to the statement
* following the labeled statement.
*/
uint32_t index;
if (!makeAtomIndex(pn->label(), &index)) return false;
JumpList top;
if (!emitJump(JSOP_LABEL, &top)) return false;
/* Emit code for the labeled statement. */
LabelControl controlInfo(this, pn->label(), offset());
if (!emitTree(pn->statement())) return false;
/* Patch the JSOP_LABEL offset. */
JumpTarget brk{lastNonJumpTargetOffset()};
patchJumpsToTarget(top, brk);
if (!controlInfo.patchBreaks(this)) return false;
return true;
}
bool BytecodeEmitter::emitConditionalExpression(
ConditionalExpression& conditional,
ValueUsage valueUsage /* = ValueUsage::WantValue */) {
/* Emit the condition, then branch if false to the else part. */
if (!emitTree(&conditional.condition())) return false;
IfThenElseEmitter ifThenElse(this);
if (!ifThenElse.emitCond()) return false;
if (!emitTreeInBranch(&conditional.thenExpression(), valueUsage))
return false;
if (!ifThenElse.emitElse()) return false;
if (!emitTreeInBranch(&conditional.elseExpression(), valueUsage))
return false;
if (!ifThenElse.emitEnd()) return false;
MOZ_ASSERT(ifThenElse.pushed() == 1);
return true;
}
bool BytecodeEmitter::emitPropertyList(ParseNode* pn,
MutableHandlePlainObject objp,
PropListType type) {
for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
if (!updateSourceCoordNotes(propdef->pn_pos.begin)) return false;
// Handle __proto__: v specially because *only* this form, and no other
// involving "__proto__", performs [[Prototype]] mutation.
if (propdef->isKind(ParseNodeKind::MutateProto)) {
MOZ_ASSERT(type == ObjectLiteral);
if (!emitTree(propdef->pn_kid)) return false;
objp.set(nullptr);
if (!emit1(JSOP_MUTATEPROTO)) return false;
continue;
}
if (propdef->isKind(ParseNodeKind::Spread)) {
MOZ_ASSERT(type == ObjectLiteral);
if (!emit1(JSOP_DUP)) return false;
if (!emitTree(propdef->pn_kid)) return false;
if (!emitCopyDataProperties(CopyOption::Unfiltered)) return false;
objp.set(nullptr);
continue;
}
bool extraPop = false;
if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) {
extraPop = true;
if (!emit1(JSOP_DUP2)) return false;
if (!emit1(JSOP_POP)) return false;
}
/* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
ParseNode* key = propdef->pn_left;
bool isIndex = false;
if (key->isKind(ParseNodeKind::Number)) {
if (!emitNumberOp(key->pn_dval)) return false;
isIndex = true;
} else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
key->isKind(ParseNodeKind::String)) {
// EmitClass took care of constructor already.
if (type == ClassBody && key->pn_atom == cx->names().constructor &&
!propdef->as<ClassMethod>().isStatic()) {
continue;
}
} else {
if (!emitComputedPropertyName(key)) return false;
isIndex = true;
}
/* Emit code for the property initializer. */
if (!emitTree(propdef->pn_right)) return false;
JSOp op = propdef->getOp();
MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER ||
op == JSOP_INITPROP_SETTER);
FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER
? FunctionPrefixKind::Get
: op == JSOP_INITPROP_SETTER
? FunctionPrefixKind::Set
: FunctionPrefixKind::None;
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
objp.set(nullptr);
if (propdef->pn_right->isKind(ParseNodeKind::Function) &&
propdef->pn_right->pn_funbox->needsHomeObject()) {
MOZ_ASSERT(
propdef->pn_right->pn_funbox->function()->allowSuperProperty());
bool isAsync = propdef->pn_right->pn_funbox->isAsync();
if (isAsync) {
if (!emit1(JSOP_SWAP)) return false;
}
if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) return false;
if (isAsync) {
if (!emit1(JSOP_POP)) return false;
}
}
// Class methods are not enumerable.
if (type == ClassBody) {
switch (op) {
case JSOP_INITPROP:
op = JSOP_INITHIDDENPROP;
break;
case JSOP_INITPROP_GETTER:
op = JSOP_INITHIDDENPROP_GETTER;
break;
case JSOP_INITPROP_SETTER:
op = JSOP_INITHIDDENPROP_SETTER;
break;
default:
MOZ_CRASH("Invalid op");
}
}
if (isIndex) {
objp.set(nullptr);
switch (op) {
case JSOP_INITPROP:
op = JSOP_INITELEM;
break;
case JSOP_INITHIDDENPROP:
op = JSOP_INITHIDDENELEM;
break;
case JSOP_INITPROP_GETTER:
op = JSOP_INITELEM_GETTER;
break;
case JSOP_INITHIDDENPROP_GETTER:
op = JSOP_INITHIDDENELEM_GETTER;
break;
case JSOP_INITPROP_SETTER:
op = JSOP_INITELEM_SETTER;
break;
case JSOP_INITHIDDENPROP_SETTER:
op = JSOP_INITHIDDENELEM_SETTER;
break;
default:
MOZ_CRASH("Invalid op");
}
if (propdef->pn_right->isDirectRHSAnonFunction()) {
if (!emitDupAt(1)) return false;
if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) return false;
}
if (!emit1(op)) return false;
} else {
MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) ||
key->isKind(ParseNodeKind::String));
uint32_t index;
if (!makeAtomIndex(key->pn_atom, &index)) return false;
if (objp) {
MOZ_ASSERT(type == ObjectLiteral);
MOZ_ASSERT(!IsHiddenInitOp(op));
MOZ_ASSERT(!objp->inDictionaryMode());
Rooted<jsid> id(cx, AtomToId(key->pn_atom));
if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue,
JSPROP_ENUMERATE)) {
return false;
}
if (objp->inDictionaryMode()) objp.set(nullptr);
}
if (propdef->pn_right->isDirectRHSAnonFunction()) {
RootedAtom keyName(cx, key->pn_atom);
if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind))
return false;
}
if (!emitIndex32(op, index)) return false;
}
if (extraPop) {
if (!emit1(JSOP_POP)) return false;
}
}
return true;
}
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ParseNode* pn) {
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext())
return emitSingletonInitialiser(pn);
/*
* Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
* a new object and defining (in source order) each property on the object
* (or mutating the object's [[Prototype]], in the case of __proto__).
*/
ptrdiff_t offset = this->offset();
if (!emitNewInit(JSProto_Object)) return false;
// Try to construct the shape of the object as we go, so we can emit a
// JSOP_NEWOBJECT with the final shape instead.
// In the case of computed property names and indices, we cannot fix the
// shape at bytecode compile time. When the shape cannot be determined,
// |obj| is nulled out.
// No need to do any guessing for the object kind, since we know the upper
// bound of how many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count);
RootedPlainObject obj(
cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
if (!obj) return false;
if (!emitPropertyList(pn, &obj, ObjectLiteral)) return false;
if (obj) {
// The object survived and has a predictable shape: update the original
// bytecode.
if (!replaceNewInitWithNewObject(obj, offset)) return false;
}
return true;
}
bool BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj,
ptrdiff_t offset) {
ObjectBox* objbox = parser.newObjectBox(obj);
if (!objbox) return false;
static_assert(
JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
"newinit and newobject must have equal length to edit in-place");
uint32_t index = objectList.add(objbox);
jsbytecode* code = this->code(offset);
MOZ_ASSERT(code[0] == JSOP_NEWINIT);
code[0] = JSOP_NEWOBJECT;
SET_UINT32(code, index);
return true;
}
bool BytecodeEmitter::emitArrayLiteral(ParseNode* pn) {
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) {
if (checkSingletonContext()) {
// Bake in the object entirely if it will only be created once.
return emitSingletonInitialiser(pn);
}
// If the array consists entirely of primitive values, make a
// template object with copy on write elements that can be reused
// every time the initializer executes. Don't do this if the array is
// small: copying the elements lazily is not worth it in that case.
static const size_t MinElementsForCopyOnWrite = 5;
if (emitterMode != BytecodeEmitter::SelfHosting &&
pn->pn_count >= MinElementsForCopyOnWrite) {
RootedValue value(cx);
if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value))
return false;
if (!value.isMagic(JS_GENERIC_MAGIC)) {
// Note: the group of the template object might not yet reflect
// that the object has copy on write elements. When the
// interpreter or JIT compiler fetches the template, it should
// use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the
// group for the template is accurate. We don't do this here as we
// want to use ObjectGroup::allocationSiteGroup, which requires a
// finished script.
JSObject* obj = &value.toObject();
MOZ_ASSERT(obj->is<ArrayObject>() &&
obj->as<ArrayObject>().denseElementsAreCopyOnWrite());
ObjectBox* objbox = parser.newObjectBox(obj);
if (!objbox) return false;
return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE);
}
}
}
return emitArray(pn->pn_head, pn->pn_count);
}
bool BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count) {
/*
* Emit code for [a, b, c] that is equivalent to constructing a new
* array and in source order evaluating each element value and adding
* it to the array, without invoking latent setters. We use the
* JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and
* to avoid dup'ing and popping the array as each element is added, as
* JSOP_SETELEM/JSOP_SETPROP would do.
*/
uint32_t nspread = 0;
for (ParseNode* elt = pn; elt; elt = elt->pn_next) {
if (elt->isKind(ParseNodeKind::Spread)) nspread++;
}
// Array literal's length is limited to NELEMENTS_LIMIT in parser.
static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX,
"array literals' maximum length must not exceed limits "
"required by BaselineCompiler::emit_JSOP_NEWARRAY, "
"BaselineCompiler::emit_JSOP_INITELEM_ARRAY, "
"and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY");
MOZ_ASSERT(count >= nspread);
MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT,
"the parser must throw an error if the array exceeds maximum "
"length");
// For arrays with spread, this is a very pessimistic allocation, the
// minimum possible final size.
if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) // ARRAY
return false;
ParseNode* pn2 = pn;
uint32_t index;
bool afterSpread = false;
for (index = 0; pn2; index++, pn2 = pn2->pn_next) {
if (!afterSpread && pn2->isKind(ParseNodeKind::Spread)) {
afterSpread = true;
if (!emitNumberOp(index)) // ARRAY INDEX
return false;
}
if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false;
bool allowSelfHostedIter = false;
if (pn2->isKind(ParseNodeKind::Elision)) {
if (!emit1(JSOP_HOLE)) return false;
} else {
ParseNode* expr;
if (pn2->isKind(ParseNodeKind::Spread)) {
expr = pn2->pn_kid;
if (emitterMode == BytecodeEmitter::SelfHosting &&
expr->isKind(ParseNodeKind::Call) &&
expr->pn_head->name() == cx->names().allowContentIter) {
allowSelfHostedIter = true;
}
} else {
expr = pn2;
}
if (!emitTree(expr)) // ARRAY INDEX? VALUE
return false;
}
if (pn2->isKind(ParseNodeKind::Spread)) {
if (!emitIterator()) // ARRAY INDEX NEXT ITER
return false;
if (!emit2(JSOP_PICK, 3)) // INDEX NEXT ITER ARRAY
return false;
if (!emit2(JSOP_PICK, 3)) // NEXT ITER ARRAY INDEX
return false;
if (!emitSpread(allowSelfHostedIter)) // ARRAY INDEX
return false;
} else if (afterSpread) {
if (!emit1(JSOP_INITELEM_INC)) return false;
} else {
if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index)) return false;
}
}
MOZ_ASSERT(index == count);
if (afterSpread) {
if (!emit1(JSOP_POP)) // ARRAY
return false;
}
return true;
}
static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) {
switch (pnk) {
case ParseNodeKind::Throw:
return JSOP_THROW;
case ParseNodeKind::Void:
return JSOP_VOID;
case ParseNodeKind::Not:
return JSOP_NOT;
case ParseNodeKind::BitNot:
return JSOP_BITNOT;
case ParseNodeKind::Pos:
return JSOP_POS;
case ParseNodeKind::Neg:
return JSOP_NEG;
default:
MOZ_CRASH("unexpected unary op");
}
}
bool BytecodeEmitter::emitUnary(ParseNode* pn) {
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
if (!emitTree(pn->pn_kid)) return false;
return emit1(UnaryOpParseNodeKindToJSOp(pn->getKind()));
}
bool BytecodeEmitter::emitTypeof(ParseNode* node, JSOp op) {
MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR);
if (!updateSourceCoordNotes(node->pn_pos.begin)) return false;
if (!emitTree(node->pn_kid)) return false;
return emit1(op);
}
bool BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ParamsBody));
ParseNode* funBody = pn->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(pn)) 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);
NameLocation paramLoc =
*locationOfNameBoundInScope(name, &funEmitterScope);
auto emitRhs = [&name, ¶mLoc](BytecodeEmitter* bce,
const NameLocation&, bool) {
return bce->emitGetNameAtLocation(name, paramLoc);
};
if (!emitInitializeName(name, emitRhs)) 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(pn)) return false;
if (!emitFunctionBody(funBody)) return false;
return emitterScope.leave(this);
}
bool BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) {
ParseNode* funBody = pn->last();
FunctionBox* funbox = sc->asFunctionBox();
EmitterScope* funScope = innermostEmitterScope();
bool hasParameterExprs = funbox->hasParameterExprs;
bool hasRest = funbox->hasRest();
uint16_t argSlot = 0;
for (ParseNode *arg = pn->pn_head; arg != funBody;
arg = arg->pn_next, argSlot++) {
ParseNode* bindingElement = arg;
ParseNode* initializer = nullptr;
if (arg->isKind(ParseNodeKind::Assign)) {
bindingElement = arg->pn_left;
initializer = arg->pn_right;
}
// Left-hand sides are either simple names or destructuring patterns.
MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) ||
bindingElement->isKind(ParseNodeKind::Array) ||
bindingElement->isKind(ParseNodeKind::Object));
// The rest parameter doesn't have an initializer.
bool isRest = hasRest && arg->pn_next == funBody;
MOZ_ASSERT_IF(isRest, !initializer);
bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name);
// 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);
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;
// Emit source note to enable Ion compilation.
if (!newSrcNote(SRC_IF)) return false;
JumpList jump;
if (!emitJump(JSOP_IFEQ, &jump)) return false;
if (!emit1(JSOP_POP)) return false;
if (!emitInitializerInBranch(initializer, bindingElement)) return false;
if (!emitJumpTargetAndPatch(jump)) return false;
} else if (isRest) {
if (!emit1(JSOP_REST)) return false;
checkTypeSet(JSOP_REST);
}
// 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;
// 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,
paramExprVarScope
? DestructuringFormalParameterInVarScope
: DestructuringDeclaration)) {
return false;
}
if (!emit1(JSOP_POP)) return false;
} else {
RootedAtom paramName(cx, bindingElement->name());
NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
if (hasParameterExprs) {
auto emitRhs = [argSlot, initializer, isRest](
BytecodeEmitter* bce, const NameLocation&, bool) {
// If we had an initializer or a rest parameter, the value is
// already on the stack.
if (!initializer && !isRest)
return bce->emitArgOp(JSOP_GETARG, argSlot);
return true;
};
if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs,
true))
return false;
if (!emit1(JSOP_POP)) return false;
} else if (isRest) {
// The rest value is already on top of the stack.
auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
return true;
};
if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true))
return false;
if (!emit1(JSOP_POP)) return false;
}
}
if (paramExprVarScope) {
if (!paramExprVarScope->leave(this)) return false;
}
}
return true;
}
bool BytecodeEmitter::emitInitializeFunctionSpecialNames() {
FunctionBox* funbox = sc->asFunctionBox();
auto emitInitializeFunctionSpecialName =
[](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) {
// A special name must be slotful, either on the frame or on the
// call environment.
MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&,
bool) { return bce->emit1(op); };
if (!bce->emitInitializeName(name, emitInitial)) return false;
if (!bce->emit1(JSOP_POP)) return false;
return true;
};
// Do nothing if the function doesn't have an arguments binding.
if (funbox->argumentsHasLocalBinding()) {
if (!emitInitializeFunctionSpecialName(this, cx->names().arguments,
JSOP_ARGUMENTS))
return false;
}
// Do nothing if the function doesn't have a this-binding (this
// happens for instance if it doesn't use this/eval or if it's an
// arrow function).
if (funbox->hasThisBinding()) {
if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis,
JSOP_FUNCTIONTHIS))
return false;
}
return true;
}
bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) {
FunctionBox* funbox = sc->asFunctionBox();
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(ParseNode* pn) {
// The caller has pushed the RHS to the top of the stack. Assert that the
// name is lexical and no BIND[G]NAME ops were emitted.
auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc,
bool emittedBindOp) {
MOZ_ASSERT(loc.isLexical());
MOZ_ASSERT(!emittedBindOp);
return true;
};
return emitInitializeName(pn, assertLexical);
}
// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
// (BindingClassDeclarationEvaluation).
bool BytecodeEmitter::emitClass(ParseNode* pn) {
ClassNode& classNode = pn->as<ClassNode>();
ClassNames* names = classNode.names();
ParseNode* heritageExpression = classNode.heritage();
ParseNode* classMethods = classNode.methodList();
ParseNode* constructor = nullptr;
for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) {
ClassMethod& method = mn->as<ClassMethod>();
ParseNode& methodName = method.name();
if (!method.isStatic() &&
(methodName.isKind(ParseNodeKind::ObjectPropertyName) ||
methodName.isKind(ParseNodeKind::String)) &&
methodName.pn_atom == cx->names().constructor) {
constructor = &method.method();
break;
}
}
bool savedStrictness = sc->setLocalStrictMode(true);
Maybe<TDZCheckCache> tdzCache;
Maybe<EmitterScope> emitterScope;
if (names) {
tdzCache.emplace(this);
emitterScope.emplace(this);
if (!emitterScope->enterLexical(this, ScopeKind::Lexical,
classNode.scopeBindings()))
return false;
}
// Pseudocode for class declarations:
//
// class extends BaseExpression {
// constructor() { ... }
// ...
// }
//
//
// if defined <BaseExpression> {
// let heritage = BaseExpression;
//
// if (heritage !== null) {
// funProto = heritage;
// objProto = heritage.prototype;
// } else {
// funProto = %FunctionPrototype%;
// objProto = null;
// }
// } else {
// objProto = %ObjectPrototype%;
// }
//
// let homeObject = ObjectCreate(objProto);
//
// if defined <constructor> {
// if defined <BaseExpression> {
// cons = DefineMethod(<constructor>, proto=homeObject,
// funProto=funProto);
// } else {
// cons = DefineMethod(<constructor>, proto=homeObject);
// }
// } else {
// if defined <BaseExpression> {
// cons = DefaultDerivedConstructor(proto=homeObject,
// funProto=funProto);
// } else {
// cons = DefaultConstructor(proto=homeObject);
// }
// }
//
// cons.prototype = homeObject;
// homeObject.constructor = cons;
//
// EmitPropertyList(...)
// This is kind of silly. In order to the get the home object defined on
// the constructor, we have to make it second, but we want the prototype
// on top for EmitPropertyList, because we expect static properties to be
// rarer. The result is a few more swaps than we would like. Such is life.
if (heritageExpression) {
IfThenElseEmitter ifThenElse(this);
if (!emitTree(heritageExpression)) // ... HERITAGE
return false;
// Heritage must be null or a non-generator constructor
if (!emit1(JSOP_CHECKCLASSHERITAGE)) // ... HERITAGE
return false;
// [IF] (heritage !== null)
if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE
return false;
if (!emit1(JSOP_NULL)) // ... HERITAGE HERITAGE NULL
return false;
if (!emit1(JSOP_STRICTNE)) // ... HERITAGE NE
return false;
// [THEN] funProto = heritage, objProto = heritage.prototype
if (!ifThenElse.emitIfElse()) return false;
if (!emit1(JSOP_DUP)) // ... HERITAGE HERITAGE
return false;
if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) // ... HERITAGE PROTO
return false;
// [ELSE] funProto = %FunctionPrototype%, objProto = null
if (!ifThenElse.emitElse()) return false;
if (!emit1(JSOP_POP)) // ...
return false;
if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) // ... PROTO
return false;
if (!emit1(JSOP_NULL)) // ... PROTO NULL
return false;
// [ENDIF]
if (!ifThenElse.emitEnd()) return false;
if (!emit1(JSOP_OBJWITHPROTO)) // ... HERITAGE HOMEOBJ
return false;
if (!emit1(JSOP_SWAP)) // ... HOMEOBJ HERITAGE
return false;
} else {
if (!emitNewInit(JSProto_Object)) // ... HOMEOBJ
return false;
}
// Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
// is not used, an implicit value of %FunctionPrototype% is implied.
if (constructor) {
if (!emitFunction(constructor,
!!heritageExpression)) // ... HOMEOBJ CONSTRUCTOR
return false;
if (constructor->pn_funbox->needsHomeObject()) {
if (!emit2(JSOP_INITHOMEOBJECT, 0)) // ... HOMEOBJ CONSTRUCTOR
return false;
}
} else {
// In the case of default class constructors, emit the start and end
// offsets in the source buffer as source notes so that when we
// actually make the constructor during execution, we can give it the
// correct toString output.
ptrdiff_t classStart = ptrdiff_t(pn->pn_pos.begin);
ptrdiff_t classEnd = ptrdiff_t(pn->pn_pos.end);
if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) return false;
JSAtom* name = names ? names->innerBinding()->pn_atom : cx->names().empty;
if (heritageExpression) {
if (!emitAtomOp(name,
JSOP_DERIVEDCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR
return false;
} else {
if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) // ... HOMEOBJ CONSTRUCTOR
return false;
}
}
if (!emit1(JSOP_SWAP)) // ... CONSTRUCTOR HOMEOBJ
return false;
if (!emit1(JSOP_DUP2)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
return false;
if (!emitAtomOp(cx->names().prototype,
JSOP_INITLOCKEDPROP)) // ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
return false;
if (!emitAtomOp(cx->names().constructor,
JSOP_INITHIDDENPROP)) // ... CONSTRUCTOR HOMEOBJ
return false;
RootedPlainObject obj(cx);
if (!emitPropertyList(classMethods, &obj,
ClassBody)) // ... CONSTRUCTOR HOMEOBJ
return false;
if (!emit1(JSOP_POP)) // ... CONSTRUCTOR
return false;
if (names) {
ParseNode* innerName = names->innerBinding();
if (!emitLexicalInitialization(innerName)) // ... CONSTRUCTOR
return false;
// Pop the inner scope.
if (!emitterScope->leave(this)) return false;
emitterScope.reset();
ParseNode* outerName = names->outerBinding();
if (outerName) {
if (!emitLexicalInitialization(outerName)) // ... CONSTRUCTOR
return false;
// Only class statements make outer bindings, and they do not leave
// themselves on the stack.
if (!emit1(JSOP_POP)) // ...
return false;
}
}
// The CONSTRUCTOR is left on stack if this is an expression.
MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
return true;
}
bool BytecodeEmitter::emitExportDefault(ParseNode* pn) {
if (!emitTree(pn->pn_left)) return false;
if (pn->pn_right) {
if (!emitLexicalInitialization(pn->pn_right)) return false;
if (pn->pn_left->isDirectRHSAnonFunction()) {
HandlePropertyName name = cx->names().default_;
if (!setOrEmitSetFunName(pn->pn_left, name, FunctionPrefixKind::None))
return false;
}
if (!emit1(JSOP_POP)) return false;
}
return true;
}
bool BytecodeEmitter::emitTree(
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) {
if (!CheckRecursionLimit(cx)) return false;
EmitLevelManager elm(this);
/* Emit notes to tell the current bytecode's source line number.
However, a couple trees require special treatment; see the
relevant emitter functions for details. */
if (emitLineNote == EMIT_LINENOTE &&
!ParseNodeRequiresSpecialLineNumberNotes(pn)) {
if (!updateLineNumberNotes(pn->pn_pos.begin)) return false;
}
switch (pn->getKind()) {
case ParseNodeKind::Function:
if (!emitFunction(pn)) return false;
break;
case ParseNodeKind::ParamsBody:
if (!emitFunctionFormalParametersAndBody(pn)) return false;
break;
case ParseNodeKind::If:
if (!emitIf(pn)) return false;
break;
case ParseNodeKind::Switch:
if (!emitSwitch(pn)) return false;
break;
case ParseNodeKind::While:
if (!emitWhile(pn)) return false;
break;
case ParseNodeKind::DoWhile:
if (!emitDo(pn)) return false;
break;
case ParseNodeKind::For:
if (!emitFor(pn)) return false;
break;
case ParseNodeKind::Break:
if (!emitBreak(pn->as<BreakStatement>().label())) return false;
break;
case ParseNodeKind::Continue:
if (!emitContinue(pn->as<ContinueStatement>().label())) return false;
break;
case ParseNodeKind::With:
if (!emitWith(pn)) return false;
break;
case ParseNodeKind::Try:
if (!emitTry(pn)) return false;
break;
case ParseNodeKind::Catch:
if (!emitCatch(pn)) return false;
break;
case ParseNodeKind::Var:
if (!emitDeclarationList(pn)) return false;
break;
case ParseNodeKind::Return:
if (!emitReturn(pn)) return false;
break;
case ParseNodeKind::YieldStar:
if (!emitYieldStar(pn->pn_kid)) return false;
break;
case ParseNodeKind::Generator:
if (!emit1(JSOP_GENERATOR)) return false;
break;
case ParseNodeKind::InitialYield:
if (!emitInitialYield(pn)) return false;
break;
case ParseNodeKind::Yield:
if (!emitYield(pn)) return false;
break;
case ParseNodeKind::Await:
if (!emitAwaitInInnermostScope(pn)) return false;
break;
case ParseNodeKind::StatementList:
if (!emitStatementList(pn)) return false;
break;
case ParseNodeKind::EmptyStatement:
break;
case ParseNodeKind::ExpressionStatement:
if (!emitExpressionStatement(pn)) return false;
break;
case ParseNodeKind::Label:
if (!emitLabeledStatement(&pn->as<LabeledStatement>())) return false;
break;
case ParseNodeKind::Comma:
if (!emitSequenceExpr(pn, valueUsage)) return false;
break;
case ParseNodeKind::Assign:
case ParseNodeKind::AddAssign:
case ParseNodeKind::SubAssign:
case ParseNodeKind::BitOrAssign:
case ParseNodeKind::BitXorAssign:
case ParseNodeKind::BitAndAssign:
case ParseNodeKind::LshAssign:
case ParseNodeKind::RshAssign:
case ParseNodeKind::UrshAssign:
case ParseNodeKind::MulAssign:
case ParseNodeKind::DivAssign:
case ParseNodeKind::ModAssign:
case ParseNodeKind::PowAssign:
if (!emitAssignment(pn->pn_left, pn->getKind(), pn->pn_right))
return false;
break;
case ParseNodeKind::Conditional:
if (!emitConditionalExpression(pn->as<ConditionalExpression>(),
valueUsage))
return false;
break;
case ParseNodeKind::Or:
case ParseNodeKind::And:
if (!emitLogical(pn)) return false;
break;
case ParseNodeKind::Add:
case ParseNodeKind::Sub:
case ParseNodeKind::BitOr:
case ParseNodeKind::BitXor:
case ParseNodeKind::BitAnd:
case ParseNodeKind::StrictEq:
case ParseNodeKind::Eq:
case ParseNodeKind::StrictNe:
case ParseNodeKind::Ne:
case ParseNodeKind::Lt:
case ParseNodeKind::Le:
case ParseNodeKind::Gt:
case ParseNodeKind::Ge:
case ParseNodeKind::In:
case ParseNodeKind::InstanceOf:
case ParseNodeKind::Lsh:
case ParseNodeKind::Rsh:
case ParseNodeKind::Ursh:
case ParseNodeKind::Star:
case ParseNodeKind::Div:
case ParseNodeKind::Mod:
if (!emitLeftAssociative(pn)) return false;
break;
case ParseNodeKind::Pow:
if (!emitRightAssociative(pn)) return false;
break;
case ParseNodeKind::Pipeline:
if (!emitPipeline(pn)) return false;
break;
case ParseNodeKind::TypeOfName:
if (!emitTypeof(pn, JSOP_TYPEOF)) return false;
break;
case ParseNodeKind::TypeOfExpr:
if (!emitTypeof(pn, JSOP_TYPEOFEXPR)) return false;
break;
case ParseNodeKind::Throw:
case ParseNodeKind::Void:
case ParseNodeKind::Not:
case ParseNodeKind::BitNot:
case ParseNodeKind::Pos:
case ParseNodeKind::Neg:
if (!emitUnary(pn)) return false;
break;
case ParseNodeKind::PreIncrement:
case ParseNodeKind::PreDecrement:
case ParseNodeKind::PostIncrement:
case ParseNodeKind::PostDecrement:
if (!emitIncOrDec(pn)) return false;
break;
case ParseNodeKind::DeleteName:
if (!emitDeleteName(pn)) return false;
break;
case ParseNodeKind::DeleteProp:
if (!emitDeleteProperty(pn)) return false;
break;
case ParseNodeKind::DeleteElem:
if (!emitDeleteElement(pn)) return false;
break;
case ParseNodeKind::DeleteExpr:
if (!emitDeleteExpression(pn)) return false;
break;
case ParseNodeKind::Dot:
if (pn->as<PropertyAccess>().isSuper()) {
if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) return false;
} else {
if (!emitPropOp(pn, JSOP_GETPROP)) return false;
}
break;
case ParseNodeKind::Elem:
if (pn->as<PropertyByValue>().isSuper()) {
if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER)) return false;
} else {
if (!emitElemOp(pn, JSOP_GETELEM)) return false;
}
break;
case ParseNodeKind::New:
case ParseNodeKind::TaggedTemplate:
case ParseNodeKind::Call:
case ParseNodeKind::SuperCall:
if (!emitCallOrNew(pn, valueUsage)) return false;
break;
case ParseNodeKind::LexicalScope:
if (!emitLexicalScope(pn)) return false;
break;
case ParseNodeKind::Const:
case ParseNodeKind::Let:
if (!emitDeclarationList(pn)) return false;
break;
case ParseNodeKind::Import:
MOZ_ASSERT(sc->isModuleContext());
break;
case ParseNodeKind::Export:
MOZ_ASSERT(sc->isModuleContext());
if (pn->pn_kid->getKind() != ParseNodeKind::ExportSpecList) {
if (!emitTree(pn->pn_kid)) return false;
}
break;
case ParseNodeKind::ExportDefault:
MOZ_ASSERT(sc->isModuleContext());
if (!emitExportDefault(pn)) return false;
break;
case ParseNodeKind::ExportFrom:
MOZ_ASSERT(sc->isModuleContext());
break;
case ParseNodeKind::CallSiteObj:
if (!emitCallSiteObject(pn)) return false;
break;
case ParseNodeKind::Array:
if (!emitArrayLiteral(pn)) return false;
break;
case ParseNodeKind::Object:
if (!emitObject(pn)) return false;
break;
case ParseNodeKind::Name:
if (!emitGetName(pn)) return false;
break;
case ParseNodeKind::TemplateStringList:
if (!emitTemplateString(pn)) return false;
break;
case ParseNodeKind::TemplateString:
case ParseNodeKind::String:
if (!emitAtomOp(pn, JSOP_STRING)) return false;
break;
case ParseNodeKind::Number:
if (!emitNumberOp(pn->pn_dval)) return false;
break;
case ParseNodeKind::RegExp:
if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox())))
return false;
break;
case ParseNodeKind::True:
case ParseNodeKind::False:
case ParseNodeKind::Null:
case ParseNodeKind::RawUndefined:
if (!emit1(pn->getOp())) return false;
break;
case ParseNodeKind::This:
if (!emitThisLiteral(pn)) return false;
break;
case ParseNodeKind::Debugger:
if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false;
if (!emit1(JSOP_DEBUGGER)) return false;
break;
case ParseNodeKind::Class:
if (!emitClass(pn)) return false;
break;
case ParseNodeKind::NewTarget:
if (!emit1(JSOP_NEWTARGET)) return false;
break;
case ParseNodeKind::SetThis:
if (!emitSetThis(pn)) return false;
break;
case ParseNodeKind::PosHolder:
MOZ_FALLTHROUGH_ASSERT(
"Should never try to emit ParseNodeKind::PosHolder");
default:
MOZ_ASSERT(0);
}
/* bce->emitLevel == 1 means we're last on the stack, so finish up. */
if (emitLevel == 1) {
if (!updateSourceCoordNotes(pn->pn_pos.end)) return false;
}
return true;
}
bool BytecodeEmitter::emitTreeInBranch(
ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) {
// Code that may be conditionally executed always need their own TDZ
// cache.
TDZCheckCache tdzCache(this);
return emitTree(pn, valueUsage);
}
static bool AllocSrcNote(JSContext* cx, SrcNotesVector& notes,
unsigned* index) {
size_t oldLength = notes.length();
if (MOZ_UNLIKELY(oldLength + 1 > MaxSrcNotesLength)) {
ReportAllocationOverflow(cx);
return false;
}
if (!notes.growBy(1)) {
return false;
}
*index = oldLength;
return true;
}
bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) {
SrcNotesVector& notes = this->notes();
unsigned index;
if (!AllocSrcNote(cx, notes, &index)) return false;
/*
* Compute delta from the last annotated bytecode's offset. If it's too
* big to fit in sn, allocate one or more xdelta notes and reset sn.
*/
ptrdiff_t offset = this->offset();
ptrdiff_t delta = offset - lastNoteOffset();
current->lastNoteOffset = offset;
if (delta >= SN_DELTA_LIMIT) {
do {
ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK);
SN_MAKE_XDELTA(¬es[index], xdelta);
delta -= xdelta;
if (!AllocSrcNote(cx, notes, &index)) return false;
} while (delta >= SN_DELTA_LIMIT);
}
/*
* Initialize type and delta, then allocate the minimum number of notes
* needed for type's arity. Usually, we won't need more, but if an offset
* does take two bytes, setSrcNoteOffset will grow notes.
*/
SN_MAKE_NOTE(¬es[index], type, delta);
for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) {
if (!newSrcNote(SRC_NULL)) return false;
}
if (indexp) *indexp = index;
return true;
}
bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset,
unsigned* indexp) {
unsigned index;
if (!newSrcNote(type, &index)) return false;
if (!setSrcNoteOffset(index, 0, offset)) return false;
if (indexp) *indexp = index;
return true;
}
bool BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1,
ptrdiff_t offset2, unsigned* indexp) {
unsigned index;
if (!newSrcNote(type, &index)) return false;
if (!setSrcNoteOffset(index, 0, offset1)) return false;
if (!setSrcNoteOffset(index, 1, offset2)) return false;
if (indexp) *indexp = index;
return true;
}
bool BytecodeEmitter::addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta) {
/*
* Called only from finishTakingSrcNotes to add to main script note
* deltas, and only by a small positive amount.
*/
MOZ_ASSERT(current == &main);
MOZ_ASSERT((unsigned)delta < (unsigned)SN_XDELTA_LIMIT);
ptrdiff_t base = SN_DELTA(sn);
ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT;
ptrdiff_t newdelta = base + delta;
if (newdelta < limit) {
SN_SET_DELTA(sn, newdelta);
} else {
jssrcnote xdelta;
SN_MAKE_XDELTA(&xdelta, delta);
if (!main.notes.insert(sn, xdelta)) return false;
}
return true;
}
bool BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which,
ptrdiff_t offset) {
if (!SN_REPRESENTABLE_OFFSET(offset)) {
parser.reportError(JSMSG_NEED_DIET, js_script_str);
return false;
}
SrcNotesVector& notes = this->notes();
/* Find the offset numbered which (i.e., skip exactly which offsets). */
jssrcnote* sn = ¬es[index];
MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
MOZ_ASSERT((int)which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
for (sn++; which; sn++, which--) {
if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3;
}
/*
* See if the new offset requires four bytes either by being too big or if
* the offset has already been inflated (in which case, we need to stay big
* to not break the srcnote encoding if this isn't the last srcnote).
*/
if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK ||
(*sn & SN_4BYTE_OFFSET_FLAG)) {
/* Maybe this offset was already set to a four-byte value. */
if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
/* Insert three dummy bytes that will be overwritten shortly. */
if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) {
ReportAllocationOverflow(cx);
return false;
}
jssrcnote dummy = 0;
if (!(sn = notes.insert(sn, dummy)) || !(sn = notes.insert(sn, dummy)) ||
!(sn = notes.insert(sn, dummy))) {
return false;
}
}
*sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24));
*sn++ = (jssrcnote)(offset >> 16);
*sn++ = (jssrcnote)(offset >> 8);
}
*sn = (jssrcnote)offset;
return true;
}
bool BytecodeEmitter::finishTakingSrcNotes(uint32_t* out) {
MOZ_ASSERT(current == &main);
unsigned prologueCount = prologue.notes.length();
if (prologueCount && prologue.currentLine != firstLine) {
switchToPrologue();
if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(firstLine))) return false;
switchToMain();
} else {
/*
* Either no prologue srcnotes, or no line number change over prologue.
* We don't need a SRC_SETLINE, but we may need to adjust the offset
* of the first main note, by adding to its delta and possibly even
* prepending SRC_XDELTA notes to it to account for prologue bytecodes
* that came at and after the last annotated bytecode.
*/
ptrdiff_t offset = prologueOffset() - prologue.lastNoteOffset;
MOZ_ASSERT(offset >= 0);
if (offset > 0 && main.notes.length() != 0) {
/* NB: Use as much of the first main note's delta as we can. */
jssrcnote* sn = main.notes.begin();
ptrdiff_t delta = SN_IS_XDELTA(sn)
? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK)
: SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
if (offset < delta) delta = offset;
for (;;) {
if (!addToSrcNoteDelta(sn, delta)) return false;
offset -= delta;
if (offset == 0) break;
delta = Min(offset, SN_XDELTA_MASK);
sn = main.notes.begin();
}
}
}
// The prologue count might have changed, so we can't reuse prologueCount.
// The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended
// when the notes are copied to their final destination by copySrcNotes.
*out = prologue.notes.length() + main.notes.length() + 1;
return true;
}
void BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) {
unsigned prologueCount = prologue.notes.length();
unsigned mainCount = main.notes.length();
unsigned totalCount = prologueCount + mainCount;
MOZ_ASSERT(totalCount == nsrcnotes - 1);
if (prologueCount)
PodCopy(destination, prologue.notes.begin(), prologueCount);
PodCopy(destination + prologueCount, main.notes.begin(), mainCount);
SN_MAKE_TERMINATOR(&destination[totalCount]);
}
void CGConstList::finish(ConstArray* array) {
MOZ_ASSERT(length() == array->length);
for (unsigned i = 0; i < length(); i++) array->vector[i] = list[i];
}
/*
* Find the index of the given object for code generator.
*
* Since the emitter refers to each parsed object only once, for the index we
* use the number of already indexed objects. We also add the object to a list
* to convert the list to a fixed-size array when we complete code generation,
* see js::CGObjectList::finish below.
*/
unsigned CGObjectList::add(ObjectBox* objbox) {
MOZ_ASSERT(!objbox->emitLink);
objbox->emitLink = lastbox;
lastbox = objbox;
return length++;
}
unsigned CGObjectList::indexOf(JSObject* obj) {
MOZ_ASSERT(length > 0);
unsigned index = length - 1;
for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink)
index--;
return index;
}
void CGObjectList::finish(ObjectArray* array) {
MOZ_ASSERT(length <= INDEX_LIMIT);
MOZ_ASSERT(length == array->length);
js::GCPtrObject* cursor = array->vector + array->length;
ObjectBox* objbox = lastbox;
do {
--cursor;
MOZ_ASSERT(!*cursor);
MOZ_ASSERT(objbox->object->isTenured());
*cursor = objbox->object;
} while ((objbox = objbox->emitLink) != nullptr);
MOZ_ASSERT(cursor == array->vector);
}
ObjectBox* CGObjectList::find(uint32_t index) {
MOZ_ASSERT(index < length);
ObjectBox* box = lastbox;
for (unsigned n = length - 1; n > index; n--) box = box->emitLink;
return box;
}
void CGScopeList::finish(ScopeArray* array) {
MOZ_ASSERT(length() <= INDEX_LIMIT);
MOZ_ASSERT(length() == array->length);
for (uint32_t i = 0; i < length(); i++) array->vector[i].init(vector[i]);
}
bool CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth,
size_t start, size_t end) {
MOZ_ASSERT(start <= end);
MOZ_ASSERT(size_t(uint32_t(start)) == start);
MOZ_ASSERT(size_t(uint32_t(end)) == end);
JSTryNote note;
note.kind = kind;
note.stackDepth = stackDepth;
note.start = uint32_t(start);
note.length = uint32_t(end - start);
return list.append(note);
}
void CGTryNoteList::finish(TryNoteArray* array) {
MOZ_ASSERT(length() == array->length);
for (unsigned i = 0; i < length(); i++) array->vector[i] = list[i];
}
bool CGScopeNoteList::append(uint32_t scopeIndex, uint32_t offset,
bool inPrologue, uint32_t parent) {
CGScopeNote note;
mozilla::PodZero(¬e);
note.index = scopeIndex;
note.start = offset;
note.parent = parent;
note.startInPrologue = inPrologue;
return list.append(note);
}
void CGScopeNoteList::recordEnd(uint32_t index, uint32_t offset,
bool inPrologue) {
MOZ_ASSERT(index < length());
MOZ_ASSERT(list[index].length == 0);
list[index].end = offset;
list[index].endInPrologue = inPrologue;
}
void CGScopeNoteList::finish(ScopeNoteArray* array, uint32_t prologueLength) {
MOZ_ASSERT(length() == array->length);
for (unsigned i = 0; i < length(); i++) {
if (!list[i].startInPrologue) list[i].start += prologueLength;
if (!list[i].endInPrologue && list[i].end != UINT32_MAX)
list[i].end += prologueLength;
MOZ_ASSERT(list[i].end >= list[i].start);
list[i].length = list[i].end - list[i].start;
array->vector[i] = list[i];
}
}
void CGYieldAndAwaitOffsetList::finish(YieldAndAwaitOffsetArray& array,
uint32_t prologueLength) {
MOZ_ASSERT(length() == array.length());
for (unsigned i = 0; i < length(); i++) array[i] = prologueLength + list[i];
}
/*
* We should try to get rid of offsetBias (always 0 or 1, where 1 is
* JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR.
*/
const JSSrcNoteSpec js_SrcNoteSpec[] = {
#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) {name, arity},
FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC)
#undef DEFINE_SRC_NOTE_SPEC
};
static int SrcNoteArity(jssrcnote* sn) {
MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST);
return js_SrcNoteSpec[SN_TYPE(sn)].arity;
}
JS_FRIEND_API unsigned js::SrcNoteLength(jssrcnote* sn) {
unsigned arity;
jssrcnote* base;
arity = SrcNoteArity(sn);
for (base = sn++; arity; sn++, arity--) {
if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3;
}
return sn - base;
}
JS_FRIEND_API ptrdiff_t js::GetSrcNoteOffset(jssrcnote* sn, unsigned which) {
/* Find the offset numbered which (i.e., skip exactly which offsets). */
MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
MOZ_ASSERT((int)which < SrcNoteArity(sn));
for (sn++; which; sn++, which--) {
if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3;
}
if (*sn & SN_4BYTE_OFFSET_FLAG) {
return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) |
(sn[1] << 16) | (sn[2] << 8) | sn[3]);
}
return (ptrdiff_t)*sn;
}