blob: bb5271c3ef032b11b6aa8742c055132e1cacd749 [file] [log] [blame]
/* -*- 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/. */
#include "jit/IonBuilder.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/SizePrintfMacros.h"
#include "builtin/Eval.h"
#include "builtin/TypedObject.h"
#include "frontend/SourceNotes.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineInspector.h"
#include "jit/Ion.h"
#include "jit/IonOptimizationLevels.h"
#include "jit/JitSpewer.h"
#include "jit/Lowering.h"
#include "jit/MIRGraph.h"
#include "vm/ArgumentsObject.h"
#include "vm/Opcodes.h"
#include "vm/RegExpStatics.h"
#include "vm/TraceLogging.h"
#include "jsopcodeinlines.h"
#include "jsscriptinlines.h"
#include "jit/CompileInfo-inl.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h"
#include "vm/ScopeObject-inl.h"
#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::AssertedCast;
using mozilla::DebugOnly;
using mozilla::Maybe;
using JS::TrackedStrategy;
using JS::TrackedOutcome;
using JS::TrackedTypeSite;
class jit::BaselineFrameInspector
{
public:
TypeSet::Type thisType;
JSObject* singletonScopeChain;
Vector<TypeSet::Type, 4, JitAllocPolicy> argTypes;
Vector<TypeSet::Type, 4, JitAllocPolicy> varTypes;
explicit BaselineFrameInspector(TempAllocator* temp)
: thisType(TypeSet::UndefinedType()),
singletonScopeChain(nullptr),
argTypes(*temp),
varTypes(*temp)
{}
};
BaselineFrameInspector*
jit::NewBaselineFrameInspector(TempAllocator* temp, BaselineFrame* frame, CompileInfo* info)
{
MOZ_ASSERT(frame);
BaselineFrameInspector* inspector = temp->lifoAlloc()->new_<BaselineFrameInspector>(temp);
if (!inspector)
return nullptr;
// Note: copying the actual values into a temporary structure for use
// during compilation could capture nursery pointers, so the values' types
// are recorded instead.
if (frame->isNonEvalFunctionFrame())
inspector->thisType = TypeSet::GetMaybeUntrackedValueType(frame->thisArgument());
if (frame->scopeChain()->isSingleton())
inspector->singletonScopeChain = frame->scopeChain();
JSScript* script = frame->script();
if (script->functionNonDelazifying()) {
if (!inspector->argTypes.reserve(frame->numFormalArgs()))
return nullptr;
for (size_t i = 0; i < frame->numFormalArgs(); i++) {
if (script->formalIsAliased(i)) {
inspector->argTypes.infallibleAppend(TypeSet::UndefinedType());
} else if (!script->argsObjAliasesFormals()) {
TypeSet::Type type =
TypeSet::GetMaybeUntrackedValueType(frame->unaliasedFormal(i));
inspector->argTypes.infallibleAppend(type);
} else if (frame->hasArgsObj()) {
TypeSet::Type type =
TypeSet::GetMaybeUntrackedValueType(frame->argsObj().arg(i));
inspector->argTypes.infallibleAppend(type);
} else {
inspector->argTypes.infallibleAppend(TypeSet::UndefinedType());
}
}
}
if (!inspector->varTypes.reserve(frame->script()->nfixed()))
return nullptr;
for (size_t i = 0; i < frame->script()->nfixed(); i++) {
if (info->isSlotAliasedAtOsr(i + info->firstLocalSlot())) {
inspector->varTypes.infallibleAppend(TypeSet::UndefinedType());
} else {
TypeSet::Type type = TypeSet::GetMaybeUntrackedValueType(frame->unaliasedLocal(i));
inspector->varTypes.infallibleAppend(type);
}
}
return inspector;
}
IonBuilder::IonBuilder(JSContext* analysisContext, CompileCompartment* comp,
const JitCompileOptions& options, TempAllocator* temp,
MIRGraph* graph, CompilerConstraintList* constraints,
BaselineInspector* inspector, CompileInfo* info,
const OptimizationInfo* optimizationInfo,
BaselineFrameInspector* baselineFrame, size_t inliningDepth,
uint32_t loopDepth)
: MIRGenerator(comp, options, temp, graph, info, optimizationInfo),
backgroundCodegen_(nullptr),
actionableAbortScript_(nullptr),
actionableAbortPc_(nullptr),
actionableAbortMessage_(nullptr),
analysisContext(analysisContext),
baselineFrame_(baselineFrame),
constraints_(constraints),
analysis_(*temp, info->script()),
thisTypes(nullptr),
argTypes(nullptr),
typeArray(nullptr),
typeArrayHint(0),
bytecodeTypeMap(nullptr),
loopDepth_(loopDepth),
trackedOptimizationSites_(*temp),
lexicalCheck_(nullptr),
callerResumePoint_(nullptr),
callerBuilder_(nullptr),
cfgStack_(*temp),
loops_(*temp),
switches_(*temp),
labels_(*temp),
iterators_(*temp),
loopHeaders_(*temp),
inspector(inspector),
inliningDepth_(inliningDepth),
inlinedBytecodeLength_(0),
numLoopRestarts_(0),
failedBoundsCheck_(info->script()->failedBoundsCheck()),
failedShapeGuard_(info->script()->failedShapeGuard()),
failedLexicalCheck_(info->script()->failedLexicalCheck()),
nonStringIteration_(false),
lazyArguments_(nullptr),
inlineCallInfo_(nullptr),
maybeFallbackFunctionGetter_(nullptr)
{
script_ = info->script();
scriptHasIonScript_ = script_->hasIonScript();
pc = info->startPC();
abortReason_ = AbortReason_Disable;
MOZ_ASSERT(script()->hasBaselineScript() == (info->analysisMode() != Analysis_ArgumentsUsage));
MOZ_ASSERT(!!analysisContext == (info->analysisMode() == Analysis_DefiniteProperties));
if (!info->isAnalysis())
script()->baselineScript()->setIonCompiledOrInlined();
}
void
IonBuilder::clearForBackEnd()
{
MOZ_ASSERT(!analysisContext);
baselineFrame_ = nullptr;
// The caches below allocate data from the malloc heap. Release this before
// later phases of compilation to avoid leaks, as the top level IonBuilder
// is not explicitly destroyed. Note that builders for inner scripts are
// constructed on the stack and will release this memory on destruction.
gsn.purge();
scopeCoordinateNameCache.purge();
}
bool
IonBuilder::abort(const char* message, ...)
{
// Don't call PCToLineNumber in release builds.
#ifdef JS_JITSPEW
va_list ap;
va_start(ap, message);
abortFmt(message, ap);
va_end(ap);
# ifdef DEBUG
JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(), PCToLineNumber(script(), pc));
# else
JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename());
# endif
#endif
trackActionableAbort(message);
return false;
}
IonBuilder*
IonBuilder::outermostBuilder()
{
IonBuilder* builder = this;
while (builder->callerBuilder_)
builder = builder->callerBuilder_;
return builder;
}
void
IonBuilder::trackActionableAbort(const char* message)
{
if (!isOptimizationTrackingEnabled())
return;
IonBuilder* topBuilder = outermostBuilder();
if (topBuilder->hadActionableAbort())
return;
topBuilder->actionableAbortScript_ = script();
topBuilder->actionableAbortPc_ = pc;
topBuilder->actionableAbortMessage_ = message;
}
void
IonBuilder::spew(const char* message)
{
// Don't call PCToLineNumber in release builds.
#ifdef DEBUG
JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc));
#endif
}
static inline int32_t
GetJumpOffset(jsbytecode* pc)
{
MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP);
return GET_JUMP_OFFSET(pc);
}
IonBuilder::CFGState
IonBuilder::CFGState::If(jsbytecode* join, MTest* test)
{
CFGState state;
state.state = IF_TRUE;
state.stopAt = join;
state.branch.ifFalse = test->ifFalse();
state.branch.test = test;
return state;
}
IonBuilder::CFGState
IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test)
{
MBasicBlock* ifFalse = test->ifFalse();
CFGState state;
// If the end of the false path is the same as the start of the
// false path, then the "else" block is empty and we can devolve
// this to the IF_TRUE case. We handle this here because there is
// still an extra GOTO on the true path and we want stopAt to point
// there, whereas the IF_TRUE case does not have the GOTO.
state.state = (falseEnd == ifFalse->pc())
? IF_TRUE_EMPTY_ELSE
: IF_ELSE_TRUE;
state.stopAt = trueEnd;
state.branch.falseEnd = falseEnd;
state.branch.ifFalse = ifFalse;
state.branch.test = test;
return state;
}
IonBuilder::CFGState
IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs)
{
CFGState state;
state.state = AND_OR;
state.stopAt = join;
state.branch.ifFalse = lhs;
state.branch.test = nullptr;
return state;
}
IonBuilder::CFGState
IonBuilder::CFGState::TableSwitch(jsbytecode* exitpc, MTableSwitch* ins)
{
CFGState state;
state.state = TABLE_SWITCH;
state.stopAt = exitpc;
state.tableswitch.exitpc = exitpc;
state.tableswitch.breaks = nullptr;
state.tableswitch.ins = ins;
state.tableswitch.currentBlock = 0;
return state;
}
JSFunction*
IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes)
{
if (!calleeTypes)
return nullptr;
JSObject* obj = calleeTypes->maybeSingleton();
if (!obj || !obj->is<JSFunction>())
return nullptr;
return &obj->as<JSFunction>();
}
bool
IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
ObjectVector& targets, uint32_t maxTargets)
{
MOZ_ASSERT(targets.empty());
if (!calleeTypes)
return true;
if (calleeTypes->baseFlags() != 0)
return true;
unsigned objCount = calleeTypes->getObjectCount();
if (objCount == 0 || objCount > maxTargets)
return true;
if (!targets.reserve(objCount))
return false;
for (unsigned i = 0; i < objCount; i++) {
JSObject* obj = calleeTypes->getSingleton(i);
if (obj) {
MOZ_ASSERT(obj->isSingleton());
} else {
ObjectGroup* group = calleeTypes->getGroup(i);
if (!group)
continue;
obj = group->maybeInterpretedFunction();
if (!obj) {
targets.clear();
return true;
}
MOZ_ASSERT(!obj->isSingleton());
}
// Don't optimize if the callee is not callable or constructable per
// the manner it is being invoked, so that CallKnown does not have to
// handle these cases (they will always throw).
if (constructing ? !obj->isConstructor() : !obj->isCallable()) {
targets.clear();
return true;
}
targets.infallibleAppend(obj);
}
return true;
}
IonBuilder::InliningDecision
IonBuilder::DontInline(JSScript* targetScript, const char* reason)
{
if (targetScript) {
JitSpew(JitSpew_Inlining, "Cannot inline %s:%" PRIuSIZE ": %s",
targetScript->filename(), targetScript->lineno(), reason);
} else {
JitSpew(JitSpew_Inlining, "Cannot inline: %s", reason);
}
return InliningDecision_DontInline;
}
/*
* |hasCommonInliningPath| determines whether the current inlining path has been
* seen before based on the sequence of scripts in the chain of |IonBuilder|s.
*
* An inlining path for a function |f| is the sequence of functions whose
* inlinings precede |f| up to any previous occurrences of |f|.
* So, if we have the chain of inlinings
*
* f1 -> f2 -> f -> f3 -> f4 -> f5 -> f
* -------- --------------
*
* the inlining paths for |f| are [f2, f1] and [f5, f4, f3].
* When attempting to inline |f|, we find all existing inlining paths for |f|
* and check whether they share a common prefix with the path created were |f|
* inlined.
*
* For example, given mutually recursive functions |f| and |g|, a possible
* inlining is
*
* +---- Inlining stopped here...
* |
* v
* a -> f -> g -> f \ -> g -> f -> g -> ...
*
* where the vertical bar denotes the termination of inlining.
* Inlining is terminated because we have already observed the inlining path
* [f] when inlining function |g|. Note that this will inline recursive
* functions such as |fib| only one level, as |fib| has a zero length inlining
* path which trivially prefixes all inlining paths.
*
*/
bool
IonBuilder::hasCommonInliningPath(const JSScript* scriptToInline)
{
// Find all previous inlinings of the |scriptToInline| and check for common
// inlining paths with the top of the inlining stack.
for (IonBuilder* it = this->callerBuilder_; it; it = it->callerBuilder_) {
if (it->script() != scriptToInline)
continue;
// This only needs to check the top of each stack for a match,
// as a match of length one ensures a common prefix.
IonBuilder* path = it->callerBuilder_;
if (!path || this->script() == path->script())
return true;
}
return false;
}
IonBuilder::InliningDecision
IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo)
{
if (!optimizationInfo().inlineInterpreted()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric);
return InliningDecision_DontInline;
}
if (TraceLogTextIdEnabled(TraceLogger_InlinedScripts)) {
return DontInline(nullptr, "Tracelogging of inlined scripts is enabled"
"but Tracelogger cannot do that yet.");
}
if (!target->isInterpreted()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNotInterpreted);
return DontInline(nullptr, "Non-interpreted target");
}
if (info().analysisMode() != Analysis_DefiniteProperties) {
// If |this| or an argument has an empty resultTypeSet, don't bother
// inlining, as the call is currently unreachable due to incomplete type
// information. This does not apply to the definite properties analysis,
// in that case we want to inline anyway.
if (callInfo.thisArg()->emptyResultTypeSet()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable);
return DontInline(nullptr, "Empty TypeSet for |this|");
}
for (size_t i = 0; i < callInfo.argc(); i++) {
if (callInfo.getArg(i)->emptyResultTypeSet()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable);
return DontInline(nullptr, "Empty TypeSet for argument");
}
}
}
// Allow constructing lazy scripts when performing the definite properties
// analysis, as baseline has not been used to warm the caller up yet.
if (target->isInterpreted() && info().analysisMode() == Analysis_DefiniteProperties) {
RootedScript script(analysisContext, target->getOrCreateScript(analysisContext));
if (!script)
return InliningDecision_Error;
if (!script->hasBaselineScript() && script->canBaselineCompile()) {
MethodStatus status = BaselineCompile(analysisContext, script);
if (status == Method_Error)
return InliningDecision_Error;
if (status != Method_Compiled) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline);
return InliningDecision_DontInline;
}
}
}
if (!target->hasScript()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineLazy);
return DontInline(nullptr, "Lazy script");
}
JSScript* inlineScript = target->nonLazyScript();
if (callInfo.constructing() && !target->isConstructor()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNotConstructor);
return DontInline(inlineScript, "Callee is not a constructor");
}
if (!callInfo.constructing() && target->isClassConstructor()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor);
return DontInline(inlineScript, "Not constructing class constructor");
}
AnalysisMode analysisMode = info().analysisMode();
if (!CanIonCompile(inlineScript, analysisMode)) {
trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon);
return DontInline(inlineScript, "Disabled Ion compilation");
}
// Don't inline functions which don't have baseline scripts.
if (!inlineScript->hasBaselineScript()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline);
return DontInline(inlineScript, "No baseline jitcode");
}
if (TooManyFormalArguments(target->nargs())) {
trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs);
return DontInline(inlineScript, "Too many args");
}
// We check the number of actual arguments against the maximum number of
// formal arguments as we do not want to encode all actual arguments in the
// callerResumePoint.
if (TooManyFormalArguments(callInfo.argc())) {
trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs);
return DontInline(inlineScript, "Too many actual args");
}
if (hasCommonInliningPath(inlineScript)) {
trackOptimizationOutcome(TrackedOutcome::HasCommonInliningPath);
return DontInline(inlineScript, "Common inlining path");
}
if (inlineScript->uninlineable()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric);
return DontInline(inlineScript, "Uninlineable script");
}
if (inlineScript->needsArgsObj()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNeedsArgsObj);
return DontInline(inlineScript, "Script that needs an arguments object");
}
if (inlineScript->isDebuggee()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineDebuggee);
return DontInline(inlineScript, "Script is debuggee");
}
TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target);
if (targetKey->unknownProperties()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps);
return DontInline(inlineScript, "Target type has unknown properties");
}
return InliningDecision_Inline;
}
void
IonBuilder::popCfgStack()
{
if (cfgStack_.back().isLoop())
loops_.popBack();
if (cfgStack_.back().state == CFGState::LABEL)
labels_.popBack();
cfgStack_.popBack();
}
bool
IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end)
{
// The phi inputs at the loop head only reflect types for variables that
// were present at the start of the loop. If the variable changes to a new
// type within the loop body, and that type is carried around to the loop
// head, then we need to know about the new type up front.
//
// Since SSA information hasn't been constructed for the loop body yet, we
// need a separate analysis to pick out the types that might flow around
// the loop header. This is a best-effort analysis that may either over-
// or under-approximate the set of such types.
//
// Over-approximating the types may lead to inefficient generated code, and
// under-approximating the types will cause the loop body to be analyzed
// multiple times as the correct types are deduced (see finishLoop).
// If we restarted processing of an outer loop then get loop header types
// directly from the last time we have previously processed this loop. This
// both avoids repeated work from the bytecode traverse below, and will
// also pick up types discovered while previously building the loop body.
for (size_t i = 0; i < loopHeaders_.length(); i++) {
if (loopHeaders_[i].pc == start) {
MBasicBlock* oldEntry = loopHeaders_[i].header;
// If this block has been discarded, its resume points will have
// already discarded their operands.
if (!oldEntry->isDead()) {
MResumePoint* oldEntryRp = oldEntry->entryResumePoint();
size_t stackDepth = oldEntryRp->stackDepth();
for (size_t slot = 0; slot < stackDepth; slot++) {
MDefinition* oldDef = oldEntryRp->getOperand(slot);
if (!oldDef->isPhi()) {
MOZ_ASSERT(oldDef->block()->id() < oldEntry->id());
MOZ_ASSERT(oldDef == entry->getSlot(slot));
continue;
}
MPhi* oldPhi = oldDef->toPhi();
MPhi* newPhi = entry->getSlot(slot)->toPhi();
if (!newPhi->addBackedgeType(oldPhi->type(), oldPhi->resultTypeSet()))
return false;
}
}
// Update the most recent header for this loop encountered, in case
// new types flow to the phis and the loop is processed at least
// three times.
loopHeaders_[i].header = entry;
return true;
}
}
if (!loopHeaders_.append(LoopHeader(start, entry)))
return false;
jsbytecode* last = nullptr;
jsbytecode* earlier = nullptr;
for (jsbytecode* pc = start; pc != end; earlier = last, last = pc, pc += GetBytecodeLength(pc)) {
uint32_t slot;
if (*pc == JSOP_SETLOCAL)
slot = info().localSlot(GET_LOCALNO(pc));
else if (*pc == JSOP_SETARG)
slot = info().argSlotUnchecked(GET_ARGNO(pc));
else
continue;
if (slot >= info().firstStackSlot())
continue;
if (!analysis().maybeInfo(pc))
continue;
if (!last)
continue;
MPhi* phi = entry->getSlot(slot)->toPhi();
if (*last == JSOP_POS)
last = earlier;
if (CodeSpec[*last].format & JOF_TYPESET) {
TemporaryTypeSet* typeSet = bytecodeTypes(last);
if (!typeSet->empty()) {
MIRType type = typeSet->getKnownMIRType();
if (!phi->addBackedgeType(type, typeSet))
return false;
}
} else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) {
uint32_t slot = (*last == JSOP_GETLOCAL)
? info().localSlot(GET_LOCALNO(last))
: info().argSlotUnchecked(GET_ARGNO(last));
if (slot < info().firstStackSlot()) {
MPhi* otherPhi = entry->getSlot(slot)->toPhi();
if (otherPhi->hasBackedgeType()) {
if (!phi->addBackedgeType(otherPhi->type(), otherPhi->resultTypeSet()))
return false;
}
}
} else {
MIRType type = MIRType_None;
switch (*last) {
case JSOP_VOID:
case JSOP_UNDEFINED:
type = MIRType_Undefined;
break;
case JSOP_GIMPLICITTHIS:
if (!script()->hasNonSyntacticScope())
type = MIRType_Undefined;
break;
case JSOP_NULL:
type = MIRType_Null;
break;
case JSOP_ZERO:
case JSOP_ONE:
case JSOP_INT8:
case JSOP_INT32:
case JSOP_UINT16:
case JSOP_UINT24:
case JSOP_BITAND:
case JSOP_BITOR:
case JSOP_BITXOR:
case JSOP_BITNOT:
case JSOP_RSH:
case JSOP_LSH:
case JSOP_URSH:
type = MIRType_Int32;
break;
case JSOP_FALSE:
case JSOP_TRUE:
case JSOP_EQ:
case JSOP_NE:
case JSOP_LT:
case JSOP_LE:
case JSOP_GT:
case JSOP_GE:
case JSOP_NOT:
case JSOP_STRICTEQ:
case JSOP_STRICTNE:
case JSOP_IN:
case JSOP_INSTANCEOF:
type = MIRType_Boolean;
break;
case JSOP_DOUBLE:
type = MIRType_Double;
break;
case JSOP_STRING:
case JSOP_TOSTRING:
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
type = MIRType_String;
break;
case JSOP_SYMBOL:
type = MIRType_Symbol;
break;
case JSOP_ADD:
case JSOP_SUB:
case JSOP_MUL:
case JSOP_DIV:
case JSOP_MOD:
case JSOP_NEG:
type = inspector->expectedResultType(last);
default:
break;
}
if (type != MIRType_None) {
if (!phi->addBackedgeType(type, nullptr))
return false;
}
}
}
return true;
}
bool
IonBuilder::pushLoop(CFGState::State initial, jsbytecode* stopAt, MBasicBlock* entry, bool osr,
jsbytecode* loopHead, jsbytecode* initialPc,
jsbytecode* bodyStart, jsbytecode* bodyEnd,
jsbytecode* exitpc, jsbytecode* continuepc)
{
ControlFlowInfo loop(cfgStack_.length(), continuepc);
if (!loops_.append(loop))
return false;
CFGState state;
state.state = initial;
state.stopAt = stopAt;
state.loop.bodyStart = bodyStart;
state.loop.bodyEnd = bodyEnd;
state.loop.exitpc = exitpc;
state.loop.continuepc = continuepc;
state.loop.entry = entry;
state.loop.osr = osr;
state.loop.successor = nullptr;
state.loop.breaks = nullptr;
state.loop.continues = nullptr;
state.loop.initialState = initial;
state.loop.initialPc = initialPc;
state.loop.initialStopAt = stopAt;
state.loop.loopHead = loopHead;
return cfgStack_.append(state);
}
bool
IonBuilder::init()
{
if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray))
return false;
if (inlineCallInfo_) {
// If we're inlining, the actual this/argument types are not necessarily
// a subset of the script's observed types. |argTypes| is never accessed
// for inlined scripts, so we just null it.
thisTypes = inlineCallInfo_->thisArg()->resultTypeSet();
argTypes = nullptr;
}
if (!analysis().init(alloc(), gsn))
return false;
// The baseline script normally has the bytecode type map, but compute
// it ourselves if we do not have a baseline script.
if (script()->hasBaselineScript()) {
bytecodeTypeMap = script()->baselineScript()->bytecodeTypeMap();
} else {
bytecodeTypeMap = alloc_->lifoAlloc()->newArrayUninitialized<uint32_t>(script()->nTypeSets());
if (!bytecodeTypeMap)
return false;
FillBytecodeTypeMap(script(), bytecodeTypeMap);
}
return true;
}
bool
IonBuilder::build()
{
if (!init())
return false;
if (script()->hasBaselineScript())
script()->baselineScript()->resetMaxInliningDepth();
if (!setCurrentAndSpecializePhis(newBlock(pc)))
return false;
if (!current)
return false;
#ifdef JS_JITSPEW
if (info().isAnalysis()) {
JitSpew(JitSpew_IonScripts, "Analyzing script %s:%" PRIuSIZE " (%p) %s",
script()->filename(), script()->lineno(), (void*)script(),
AnalysisModeString(info().analysisMode()));
} else {
JitSpew(JitSpew_IonScripts, "%sompiling script %s:%" PRIuSIZE " (%p) (warmup-counter=%" PRIu32 ", level=%s)",
(script()->hasIonScript() ? "Rec" : "C"),
script()->filename(), script()->lineno(), (void*)script(),
script()->getWarmUpCount(), OptimizationLevelString(optimizationInfo().level()));
}
#endif
initParameters();
initLocals();
// Initialize something for the scope chain. We can bail out before the
// start instruction, but the snapshot is encoded *at* the start
// instruction, which means generating any code that could load into
// registers is illegal.
MInstruction* scope = MConstant::New(alloc(), UndefinedValue());
current->add(scope);
current->initSlot(info().scopeChainSlot(), scope);
// Initialize the return value.
MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
current->add(returnValue);
current->initSlot(info().returnValueSlot(), returnValue);
// Initialize the arguments object slot to undefined if necessary.
if (info().hasArguments()) {
MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
current->add(argsObj);
current->initSlot(info().argsObjSlot(), argsObj);
}
// Emit the start instruction, so we can begin real instructions.
current->add(MStart::New(alloc(), MStart::StartType_Default));
// Guard against over-recursion. Do this before we start unboxing, since
// this will create an OSI point that will read the incoming argument
// values, which is nice to do before their last real use, to minimize
// register/stack pressure.
MCheckOverRecursed* check = MCheckOverRecursed::New(alloc());
current->add(check);
MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy)
return false;
check->setResumePoint(entryRpCopy);
// Parameters have been checked to correspond to the typeset, now we unbox
// what we can in an infallible manner.
rewriteParameters();
// Check for redeclaration errors for global scripts.
if (!info().funMaybeLazy() && !info().module() &&
script()->bindings.numBodyLevelLocals() > 0)
{
MGlobalNameConflictsCheck* redeclCheck = MGlobalNameConflictsCheck::New(alloc());
current->add(redeclCheck);
MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy)
return false;
redeclCheck->setResumePoint(entryRpCopy);
}
// It's safe to start emitting actual IR, so now build the scope chain.
if (!initScopeChain())
return false;
if (info().needsArgsObj() && !initArgumentsObject())
return false;
// The type analysis phase attempts to insert unbox operations near
// definitions of values. It also attempts to replace uses in resume points
// with the narrower, unboxed variants. However, we must prevent this
// replacement from happening on values in the entry snapshot. Otherwise we
// could get this:
//
// v0 = MParameter(0)
// v1 = MParameter(1)
// -- ResumePoint(v2, v3)
// v2 = Unbox(v0, INT32)
// v3 = Unbox(v1, INT32)
//
// So we attach the initial resume point to each parameter, which the type
// analysis explicitly checks (this is the same mechanism used for
// effectful operations).
for (uint32_t i = 0; i < info().endArgSlot(); i++) {
MInstruction* ins = current->getEntrySlot(i)->toInstruction();
if (ins->type() != MIRType_Value)
continue;
MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy)
return false;
ins->setResumePoint(entryRpCopy);
}
// lazyArguments should never be accessed in |argsObjAliasesFormals| scripts.
if (info().hasArguments() && !info().argsObjAliasesFormals()) {
lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
current->add(lazyArguments_);
}
insertRecompileCheck();
if (!traverseBytecode())
return false;
// Discard unreferenced & pre-allocated resume points.
replaceMaybeFallbackFunctionGetter(nullptr);
if (script_->hasBaselineScript() &&
inlinedBytecodeLength_ > script_->baselineScript()->inlinedBytecodeLength())
{
script_->baselineScript()->setInlinedBytecodeLength(inlinedBytecodeLength_);
}
if (!maybeAddOsrTypeBarriers())
return false;
if (!processIterators())
return false;
if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) {
abortReason_ = AbortReason_PreliminaryObjects;
return false;
}
if (shouldForceAbort()) {
abortReason_ = AbortReason_Disable;
return false;
}
MOZ_ASSERT(loopDepth_ == 0);
abortReason_ = AbortReason_NoAbort;
return true;
}
bool
IonBuilder::processIterators()
{
// Find phis that must directly hold an iterator live.
Vector<MPhi*, 0, SystemAllocPolicy> worklist;
for (size_t i = 0; i < iterators_.length(); i++) {
MInstruction* ins = iterators_[i];
for (MUseDefIterator iter(ins); iter; iter++) {
if (iter.def()->isPhi()) {
if (!worklist.append(iter.def()->toPhi()))
return false;
}
}
}
// Propagate the iterator and live status of phis to all other connected
// phis.
while (!worklist.empty()) {
MPhi* phi = worklist.popCopy();
phi->setIterator();
phi->setImplicitlyUsedUnchecked();
for (MUseDefIterator iter(phi); iter; iter++) {
if (iter.def()->isPhi()) {
MPhi* other = iter.def()->toPhi();
if (!other->isIterator() && !worklist.append(other))
return false;
}
}
}
return true;
}
bool
IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint,
CallInfo& callInfo)
{
inlineCallInfo_ = &callInfo;
if (!init())
return false;
JitSpew(JitSpew_IonScripts, "Inlining script %s:%" PRIuSIZE " (%p)",
script()->filename(), script()->lineno(), (void*)script());
callerBuilder_ = callerBuilder;
callerResumePoint_ = callerResumePoint;
if (callerBuilder->failedBoundsCheck_)
failedBoundsCheck_ = true;
if (callerBuilder->failedShapeGuard_)
failedShapeGuard_ = true;
if (callerBuilder->failedLexicalCheck_)
failedLexicalCheck_ = true;
safeForMinorGC_ = callerBuilder->safeForMinorGC_;
// Generate single entrance block.
if (!setCurrentAndSpecializePhis(newBlock(pc)))
return false;
if (!current)
return false;
current->setCallerResumePoint(callerResumePoint);
// Connect the entrance block to the last block in the caller's graph.
MBasicBlock* predecessor = callerBuilder->current;
MOZ_ASSERT(predecessor == callerResumePoint->block());
predecessor->end(MGoto::New(alloc(), current));
if (!current->addPredecessorWithoutPhis(predecessor))
return false;
// Initialize scope chain slot to Undefined. It's set later by |initScopeChain|.
MInstruction* scope = MConstant::New(alloc(), UndefinedValue());
current->add(scope);
current->initSlot(info().scopeChainSlot(), scope);
// Initialize |return value| slot.
MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
current->add(returnValue);
current->initSlot(info().returnValueSlot(), returnValue);
// Initialize |arguments| slot.
if (info().hasArguments()) {
MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
current->add(argsObj);
current->initSlot(info().argsObjSlot(), argsObj);
}
// Initialize |this| slot.
current->initSlot(info().thisSlot(), callInfo.thisArg());
JitSpew(JitSpew_Inlining, "Initializing %u arg slots", info().nargs());
// NB: Ion does not inline functions which |needsArgsObj|. So using argSlot()
// instead of argSlotUnchecked() below is OK
MOZ_ASSERT(!info().needsArgsObj());
// Initialize actually set arguments.
uint32_t existing_args = Min<uint32_t>(callInfo.argc(), info().nargs());
for (size_t i = 0; i < existing_args; ++i) {
MDefinition* arg = callInfo.getArg(i);
current->initSlot(info().argSlot(i), arg);
}
// Pass Undefined for missing arguments
for (size_t i = callInfo.argc(); i < info().nargs(); ++i) {
MConstant* arg = MConstant::New(alloc(), UndefinedValue());
current->add(arg);
current->initSlot(info().argSlot(i), arg);
}
// Initialize the scope chain now that args are initialized.
if (!initScopeChain(callInfo.fun()))
return false;
JitSpew(JitSpew_Inlining, "Initializing %u local slots; fixed lexicals begin at %u",
info().nlocals(), info().fixedLexicalBegin());
initLocals();
JitSpew(JitSpew_Inlining, "Inline entry block MResumePoint %p, %u stack slots",
(void*) current->entryResumePoint(), current->entryResumePoint()->stackDepth());
// +2 for the scope chain and |this|, maybe another +1 for arguments object slot.
MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots());
if (script_->argumentsHasVarBinding()) {
lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
current->add(lazyArguments_);
}
insertRecompileCheck();
if (!traverseBytecode())
return false;
// Discard unreferenced & pre-allocated resume points.
replaceMaybeFallbackFunctionGetter(nullptr);
if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) {
abortReason_ = AbortReason_PreliminaryObjects;
return false;
}
if (shouldForceAbort()) {
abortReason_ = AbortReason_Disable;
return false;
}
return true;
}
void
IonBuilder::rewriteParameter(uint32_t slotIdx, MDefinition* param, int32_t argIndex)
{
MOZ_ASSERT(param->isParameter() || param->isGetArgumentsObjectArg());
TemporaryTypeSet* types = param->resultTypeSet();
MDefinition* actual = ensureDefiniteType(param, types->getKnownMIRType());
if (actual == param)
return;
// Careful! We leave the original MParameter in the entry resume point. The
// arguments still need to be checked unless proven otherwise at the call
// site, and these checks can bailout. We can end up:
// v0 = Parameter(0)
// v1 = Unbox(v0, INT32)
// -- ResumePoint(v0)
//
// As usual, it would be invalid for v1 to be captured in the initial
// resume point, rather than v0.
current->rewriteSlot(slotIdx, actual);
}
// Apply Type Inference information to parameters early on, unboxing them if
// they have a definitive type. The actual guards will be emitted by the code
// generator, explicitly, as part of the function prologue.
void
IonBuilder::rewriteParameters()
{
MOZ_ASSERT(info().scopeChainSlot() == 0);
if (!info().funMaybeLazy())
return;
for (uint32_t i = info().startArgSlot(); i < info().endArgSlot(); i++) {
MDefinition* param = current->getSlot(i);
rewriteParameter(i, param, param->toParameter()->index());
}
}
void
IonBuilder::initParameters()
{
if (!info().funMaybeLazy())
return;
// If we are doing OSR on a frame which initially executed in the
// interpreter and didn't accumulate type information, try to use that OSR
// frame to determine possible initial types for 'this' and parameters.
if (thisTypes->empty() && baselineFrame_) {
TypeSet::Type type = baselineFrame_->thisType;
if (type.isSingletonUnchecked())
checkNurseryObject(type.singleton());
thisTypes->addType(type, alloc_->lifoAlloc());
}
MParameter* param = MParameter::New(alloc(), MParameter::THIS_SLOT, thisTypes);
current->add(param);
current->initSlot(info().thisSlot(), param);
for (uint32_t i = 0; i < info().nargs(); i++) {
TemporaryTypeSet* types = &argTypes[i];
if (types->empty() && baselineFrame_ &&
!script_->baselineScript()->modifiesArguments())
{
TypeSet::Type type = baselineFrame_->argTypes[i];
if (type.isSingletonUnchecked())
checkNurseryObject(type.singleton());
types->addType(type, alloc_->lifoAlloc());
}
param = MParameter::New(alloc(), i, types);
current->add(param);
current->initSlot(info().argSlotUnchecked(i), param);
}
}
void
IonBuilder::initLocals()
{
if (info().nlocals() == 0)
return;
MConstant* undef = nullptr;
if (info().fixedLexicalBegin() > 0) {
undef = MConstant::New(alloc(), UndefinedValue());
current->add(undef);
}
MConstant* uninitLexical = nullptr;
if (info().fixedLexicalBegin() < info().nlocals()) {
uninitLexical = MConstant::New(alloc(), MagicValue(JS_UNINITIALIZED_LEXICAL));
current->add(uninitLexical);
}
for (uint32_t i = 0; i < info().nlocals(); i++) {
current->initSlot(info().localSlot(i), (i < info().fixedLexicalBegin()
? undef
: uninitLexical));
}
}
bool
IonBuilder::initScopeChain(MDefinition* callee)
{
MInstruction* scope = nullptr;
// If the script doesn't use the scopechain, then it's already initialized
// from earlier. However, always make a scope chain when |needsArgsObj| is true
// for the script, since arguments object construction requires the scope chain
// to be passed in.
if (!info().needsArgsObj() && !analysis().usesScopeChain())
return true;
// The scope chain is only tracked in scripts that have NAME opcodes which
// will try to access the scope. For other scripts, the scope instructions
// will be held live by resume points and code will still be generated for
// them, so just use a constant undefined value.
if (JSFunction* fun = info().funMaybeLazy()) {
if (!callee) {
MCallee* calleeIns = MCallee::New(alloc());
current->add(calleeIns);
callee = calleeIns;
}
scope = MFunctionEnvironment::New(alloc(), callee);
current->add(scope);
// This reproduce what is done in CallObject::createForFunction. Skip
// this for analyses, as the script might not have a baseline script
// with template objects yet.
if (fun->needsCallObject() && !info().isAnalysis()) {
if (fun->isNamedLambda()) {
scope = createDeclEnvObject(callee, scope);
if (!scope)
return false;
}
scope = createCallObject(callee, scope);
if (!scope)
return false;
}
} else if (ModuleObject* module = info().module()) {
// Modules use a pre-created scope object.
scope = constant(ObjectValue(module->initialEnvironment()));
} else {
// For global scripts without a non-syntactic global scope, the scope
// chain is the global lexical scope.
MOZ_ASSERT(!script()->isForEval());
MOZ_ASSERT(!script()->hasNonSyntacticScope());
scope = constant(ObjectValue(script()->global().lexicalScope()));
}
current->setScopeChain(scope);
return true;
}
bool
IonBuilder::initArgumentsObject()
{
JitSpew(JitSpew_IonMIR, "%s:%" PRIuSIZE " - Emitting code to initialize arguments object! block=%p",
script()->filename(), script()->lineno(), current);
MOZ_ASSERT(info().needsArgsObj());
MCreateArgumentsObject* argsObj = MCreateArgumentsObject::New(alloc(), current->scopeChain());
current->add(argsObj);
current->setArgumentsObject(argsObj);
return true;
}
bool
IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_,
MIRType type, TemporaryTypeSet* typeSet)
{
MInstruction*& def = *def_;
MBasicBlock* osrBlock = def->block();
// Clear bogus type information added in newOsrPreheader().
def->setResultType(MIRType_Value);
def->setResultTypeSet(nullptr);
if (typeSet && !typeSet->unknown()) {
MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet);
osrBlock->insertBefore(osrBlock->lastIns(), barrier);
osrBlock->rewriteSlot(slot, barrier);
def = barrier;
} else if (type == MIRType_Null ||
type == MIRType_Undefined ||
type == MIRType_MagicOptimizedArguments)
{
// No unbox instruction will be added below, so check the type by
// adding a type barrier for a singleton type set.
TypeSet::Type ntype = TypeSet::PrimitiveType(ValueTypeFromMIRType(type));
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, ntype);
if (!typeSet)
return false;
MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet);
osrBlock->insertBefore(osrBlock->lastIns(), barrier);
osrBlock->rewriteSlot(slot, barrier);
def = barrier;
}
switch (type) {
case MIRType_Boolean:
case MIRType_Int32:
case MIRType_Double:
case MIRType_String:
case MIRType_Symbol:
case MIRType_Object:
if (type != def->type()) {
MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible);
osrBlock->insertBefore(osrBlock->lastIns(), unbox);
osrBlock->rewriteSlot(slot, unbox);
def = unbox;
}
break;
case MIRType_Null:
{
MConstant* c = MConstant::New(alloc(), NullValue());
osrBlock->insertBefore(osrBlock->lastIns(), c);
osrBlock->rewriteSlot(slot, c);
def = c;
break;
}
case MIRType_Undefined:
{
MConstant* c = MConstant::New(alloc(), UndefinedValue());
osrBlock->insertBefore(osrBlock->lastIns(), c);
osrBlock->rewriteSlot(slot, c);
def = c;
break;
}
case MIRType_MagicOptimizedArguments:
MOZ_ASSERT(lazyArguments_);
osrBlock->rewriteSlot(slot, lazyArguments_);
def = lazyArguments_;
break;
default:
break;
}
MOZ_ASSERT(def == osrBlock->getSlot(slot));
return true;
}
bool
IonBuilder::maybeAddOsrTypeBarriers()
{
if (!info().osrPc())
return true;
// The loop has successfully been processed, and the loop header phis
// have their final type. Add unboxes and type barriers in the OSR
// block to check that the values have the appropriate type, and update
// the types in the preheader.
MBasicBlock* osrBlock = graph().osrBlock();
if (!osrBlock) {
// Because IonBuilder does not compile catch blocks, it's possible to
// end up without an OSR block if the OSR pc is only reachable via a
// break-statement inside the catch block. For instance:
//
// for (;;) {
// try {
// throw 3;
// } catch(e) {
// break;
// }
// }
// while (..) { } // <= OSR here, only reachable via catch block.
//
// For now we just abort in this case.
MOZ_ASSERT(graph().hasTryBlock());
return abort("OSR block only reachable through catch block");
}
MBasicBlock* preheader = osrBlock->getSuccessor(0);
MBasicBlock* header = preheader->getSuccessor(0);
static const size_t OSR_PHI_POSITION = 1;
MOZ_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock);
MResumePoint* headerRp = header->entryResumePoint();
size_t stackDepth = headerRp->stackDepth();
MOZ_ASSERT(stackDepth == osrBlock->stackDepth());
for (uint32_t slot = info().startArgSlot(); slot < stackDepth; slot++) {
// Aliased slots are never accessed, since they need to go through
// the callobject. The typebarriers are added there and can be
// discarded here.
if (info().isSlotAliasedAtOsr(slot))
continue;
MInstruction* def = osrBlock->getSlot(slot)->toInstruction();
MPhi* preheaderPhi = preheader->getSlot(slot)->toPhi();
MPhi* headerPhi = headerRp->getOperand(slot)->toPhi();
MIRType type = headerPhi->type();
TemporaryTypeSet* typeSet = headerPhi->resultTypeSet();
if (!addOsrValueTypeBarrier(slot, &def, type, typeSet))
return false;
preheaderPhi->replaceOperand(OSR_PHI_POSITION, def);
preheaderPhi->setResultType(type);
preheaderPhi->setResultTypeSet(typeSet);
}
return true;
}
// We try to build a control-flow graph in the order that it would be built as
// if traversing the AST. This leads to a nice ordering and lets us build SSA
// in one pass, since the bytecode is structured.
//
// We traverse the bytecode iteratively, maintaining a current basic block.
// Each basic block has a mapping of local slots to instructions, as well as a
// stack depth. As we encounter instructions we mutate this mapping in the
// current block.
//
// Things get interesting when we encounter a control structure. This can be
// either an IFEQ, downward GOTO, or a decompiler hint stashed away in source
// notes. Once we encounter such an opcode, we recover the structure of the
// control flow (its branches and bounds), and push it on a stack.
//
// As we continue traversing the bytecode, we look for points that would
// terminate the topmost control flow path pushed on the stack. These are:
// (1) The bounds of the current structure (end of a loop or join/edge of a
// branch).
// (2) A "return", "break", or "continue" statement.
//
// For (1), we expect that there is a current block in the progress of being
// built, and we complete the necessary edges in the CFG. For (2), we expect
// that there is no active block.
//
// For normal diamond join points, we construct Phi nodes as we add
// predecessors. For loops, care must be taken to propagate Phi nodes back
// through uses in the loop body.
bool
IonBuilder::traverseBytecode()
{
for (;;) {
MOZ_ASSERT(pc < info().limitPC());
for (;;) {
if (!alloc().ensureBallast())
return false;
// Check if we've hit an expected join point or edge in the bytecode.
// Leaving one control structure could place us at the edge of another,
// thus |while| instead of |if| so we don't skip any opcodes.
if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) {
ControlStatus status = processCfgStack();
if (status == ControlStatus_Error)
return false;
if (status == ControlStatus_Abort)
return abort("Aborted while processing control flow");
if (!current)
return true;
continue;
}
// Some opcodes need to be handled early because they affect control
// flow, terminating the current basic block and/or instructing the
// traversal algorithm to continue from a new pc.
//
// (1) If the opcode does not affect control flow, then the opcode
// is inspected and transformed to IR. This is the process_opcode
// label.
// (2) A loop could be detected via a forward GOTO. In this case,
// we don't want to process the GOTO, but the following
// instruction.
// (3) A RETURN, STOP, BREAK, or CONTINUE may require processing the
// CFG stack to terminate open branches.
//
// Similar to above, snooping control flow could land us at another
// control flow point, so we iterate until it's time to inspect a real
// opcode.
ControlStatus status;
if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus_None)
break;
if (status == ControlStatus_Error)
return false;
if (status == ControlStatus_Abort)
return abort("Aborted while processing control flow");
if (!current)
return true;
}
#ifdef DEBUG
// In debug builds, after compiling this op, check that all values
// popped by this opcode either:
//
// (1) Have the ImplicitlyUsed flag set on them.
// (2) Have more uses than before compiling this op (the value is
// used as operand of a new MIR instruction).
//
// This is used to catch problems where IonBuilder pops a value without
// adding any SSA uses and doesn't call setImplicitlyUsedUnchecked on it.
Vector<MDefinition*, 4, JitAllocPolicy> popped(alloc());
Vector<size_t, 4, JitAllocPolicy> poppedUses(alloc());
unsigned nuses = GetUseCount(script_, script_->pcToOffset(pc));
for (unsigned i = 0; i < nuses; i++) {
MDefinition* def = current->peek(-int32_t(i + 1));
if (!popped.append(def) || !poppedUses.append(def->defUseCount()))
return false;
}
#endif
// Nothing in inspectOpcode() is allowed to advance the pc.
JSOp op = JSOp(*pc);
if (!inspectOpcode(op))
return false;
#ifdef DEBUG
for (size_t i = 0; i < popped.length(); i++) {
switch (op) {
case JSOP_POP:
case JSOP_POPN:
case JSOP_DUPAT:
case JSOP_DUP:
case JSOP_DUP2:
case JSOP_PICK:
case JSOP_SWAP:
case JSOP_SETARG:
case JSOP_SETLOCAL:
case JSOP_INITLEXICAL:
case JSOP_SETRVAL:
case JSOP_VOID:
// Don't require SSA uses for values popped by these ops.
break;
case JSOP_POS:
case JSOP_TOID:
case JSOP_TOSTRING:
// These ops may leave their input on the stack without setting
// the ImplicitlyUsed flag. If this value will be popped immediately,
// we may replace it with |undefined|, but the difference is
// not observable.
MOZ_ASSERT(i == 0);
if (current->peek(-1) == popped[0])
break;
// FALL THROUGH
default:
MOZ_ASSERT(popped[i]->isImplicitlyUsed() ||
// MNewDerivedTypedObject instances are
// often dead unless they escape from the
// fn. See IonBuilder::loadTypedObjectData()
// for more details.
popped[i]->isNewDerivedTypedObject() ||
popped[i]->defUseCount() > poppedUses[i]);
break;
}
}
#endif
pc += CodeSpec[op].length;
current->updateTrackedSite(bytecodeSite(pc));
}
return true;
}
IonBuilder::ControlStatus
IonBuilder::snoopControlFlow(JSOp op)
{
switch (op) {
case JSOP_NOP:
return maybeLoop(op, info().getNote(gsn, pc));
case JSOP_POP:
return maybeLoop(op, info().getNote(gsn, pc));
case JSOP_RETURN:
case JSOP_RETRVAL:
return processReturn(op);
case JSOP_THROW:
return processThrow();
case JSOP_GOTO:
{
jssrcnote* sn = info().getNote(gsn, pc);
switch (sn ? SN_TYPE(sn) : SRC_NULL) {
case SRC_BREAK:
case SRC_BREAK2LABEL:
return processBreak(op, sn);
case SRC_CONTINUE:
return processContinue(op);
case SRC_SWITCHBREAK:
return processSwitchBreak(op);
case SRC_WHILE:
case SRC_FOR_IN:
case SRC_FOR_OF:
// while (cond) { }
return whileOrForInLoop(sn);
default:
// Hard assert for now - make an error later.
MOZ_CRASH("unknown goto case");
}
break;
}
case JSOP_TABLESWITCH:
return tableSwitch(op, info().getNote(gsn, pc));
case JSOP_IFNE:
// We should never reach an IFNE, it's a stopAt point, which will
// trigger closing the loop.
MOZ_CRASH("we should never reach an ifne!");
default:
break;
}
return ControlStatus_None;
}
bool
IonBuilder::inspectOpcode(JSOp op)
{
MOZ_ASSERT(analysis_.maybeInfo(pc), "Compiling unreachable op");
switch (op) {
case JSOP_NOP:
case JSOP_LINENO:
case JSOP_LOOPENTRY:
return true;
case JSOP_LABEL:
return jsop_label();
case JSOP_UNDEFINED:
// If this ever changes, change what JSOP_GIMPLICITTHIS does too.
return pushConstant(UndefinedValue());
case JSOP_IFEQ:
return jsop_ifeq(JSOP_IFEQ);
case JSOP_TRY:
return jsop_try();
case JSOP_CONDSWITCH:
return jsop_condswitch();
case JSOP_BITNOT:
return jsop_bitnot();
case JSOP_BITAND:
case JSOP_BITOR:
case JSOP_BITXOR:
case JSOP_LSH:
case JSOP_RSH:
case JSOP_URSH:
return jsop_bitop(op);
case JSOP_ADD:
case JSOP_SUB:
case JSOP_MUL:
case JSOP_DIV:
case JSOP_MOD:
return jsop_binary_arith(op);
case JSOP_POW:
return jsop_pow();
case JSOP_POS:
return jsop_pos();
case JSOP_NEG:
return jsop_neg();
case JSOP_TOSTRING:
return jsop_tostring();
case JSOP_AND:
case JSOP_OR:
return jsop_andor(op);
case JSOP_DEFVAR:
return jsop_defvar(GET_UINT32_INDEX(pc));
case JSOP_DEFLET:
case JSOP_DEFCONST:
return jsop_deflexical(GET_UINT32_INDEX(pc));
case JSOP_DEFFUN:
return jsop_deffun(GET_UINT32_INDEX(pc));
case JSOP_EQ:
case JSOP_NE:
case JSOP_STRICTEQ:
case JSOP_STRICTNE:
case JSOP_LT:
case JSOP_LE:
case JSOP_GT:
case JSOP_GE:
return jsop_compare(op);
case JSOP_DOUBLE:
return pushConstant(info().getConst(pc));
case JSOP_STRING:
return pushConstant(StringValue(info().getAtom(pc)));
case JSOP_SYMBOL: {
unsigned which = GET_UINT8(pc);
JS::Symbol* sym = compartment->runtime()->wellKnownSymbols().get(which);
return pushConstant(SymbolValue(sym));
}
case JSOP_ZERO:
return pushConstant(Int32Value(0));
case JSOP_ONE:
return pushConstant(Int32Value(1));
case JSOP_NULL:
return pushConstant(NullValue());
case JSOP_VOID:
current->pop();
return pushConstant(UndefinedValue());
case JSOP_HOLE:
return pushConstant(MagicValue(JS_ELEMENTS_HOLE));
case JSOP_FALSE:
return pushConstant(BooleanValue(false));
case JSOP_TRUE:
return pushConstant(BooleanValue(true));
case JSOP_ARGUMENTS:
return jsop_arguments();
case JSOP_RUNONCE:
return jsop_runonce();
case JSOP_REST:
return jsop_rest();
case JSOP_GETARG:
if (info().argsObjAliasesFormals()) {
MGetArgumentsObjectArg* getArg = MGetArgumentsObjectArg::New(alloc(),
current->argumentsObject(),
GET_ARGNO(pc));
current->add(getArg);
current->push(getArg);
} else {
current->pushArg(GET_ARGNO(pc));
}
return true;
case JSOP_SETARG:
return jsop_setarg(GET_ARGNO(pc));
case JSOP_GETLOCAL:
current->pushLocal(GET_LOCALNO(pc));
return true;
case JSOP_SETLOCAL:
current->setLocal(GET_LOCALNO(pc));
return true;
case JSOP_THROWSETCONST:
case JSOP_THROWSETALIASEDCONST:
return jsop_throwsetconst();
case JSOP_CHECKLEXICAL:
return jsop_checklexical();
case JSOP_INITLEXICAL:
current->setLocal(GET_LOCALNO(pc));
return true;
case JSOP_INITGLEXICAL: {
MOZ_ASSERT(!script()->hasNonSyntacticScope());
MDefinition* value = current->pop();
current->push(constant(ObjectValue(script()->global().lexicalScope())));
current->push(value);
return jsop_setprop(info().getAtom(pc)->asPropertyName());
}
case JSOP_CHECKALIASEDLEXICAL:
return jsop_checkaliasedlet(ScopeCoordinate(pc));
case JSOP_INITALIASEDLEXICAL:
return jsop_setaliasedvar(ScopeCoordinate(pc));
case JSOP_UNINITIALIZED:
return pushConstant(MagicValue(JS_UNINITIALIZED_LEXICAL));
case JSOP_POP:
current->pop();
// POP opcodes frequently appear where values are killed, e.g. after
// SET* opcodes. Place a resume point afterwards to avoid capturing
// the dead value in later snapshots, except in places where that
// resume point is obviously unnecessary.
if (pc[JSOP_POP_LENGTH] == JSOP_POP)
return true;
return maybeInsertResume();
case JSOP_POPN:
for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++)
current->pop();
return true;
case JSOP_DUPAT:
current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc));
return true;
case JSOP_NEWINIT:
if (GET_UINT8(pc) == JSProto_Array)
return jsop_newarray(0);
return jsop_newobject();
case JSOP_NEWARRAY:
return jsop_newarray(GET_UINT32(pc));
case JSOP_NEWARRAY_COPYONWRITE:
return jsop_newarray_copyonwrite();
case JSOP_NEWOBJECT:
return jsop_newobject();
case JSOP_INITELEM:
case JSOP_INITHIDDENELEM:
return jsop_initelem();
case JSOP_INITELEM_ARRAY:
return jsop_initelem_array();
case JSOP_INITPROP:
case JSOP_INITLOCKEDPROP:
case JSOP_INITHIDDENPROP:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_initprop(name);
}
case JSOP_MUTATEPROTO:
{
return jsop_mutateproto();
}
case JSOP_INITPROP_GETTER:
case JSOP_INITHIDDENPROP_GETTER:
case JSOP_INITPROP_SETTER:
case JSOP_INITHIDDENPROP_SETTER: {
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_initprop_getter_setter(name);
}
case JSOP_INITELEM_GETTER:
case JSOP_INITHIDDENELEM_GETTER:
case JSOP_INITELEM_SETTER:
case JSOP_INITHIDDENELEM_SETTER:
return jsop_initelem_getter_setter();
case JSOP_FUNCALL:
return jsop_funcall(GET_ARGC(pc));
case JSOP_FUNAPPLY:
return jsop_funapply(GET_ARGC(pc));
case JSOP_CALL:
case JSOP_CALLITER:
case JSOP_NEW:
case JSOP_SUPERCALL:
return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL);
case JSOP_EVAL:
case JSOP_STRICTEVAL:
return jsop_eval(GET_ARGC(pc));
case JSOP_INT8:
return pushConstant(Int32Value(GET_INT8(pc)));
case JSOP_UINT16:
return pushConstant(Int32Value(GET_UINT16(pc)));
case JSOP_GETGNAME:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
if (!script()->hasNonSyntacticScope())
return jsop_getgname(name);
return jsop_getname(name);
}
case JSOP_SETGNAME:
case JSOP_STRICTSETGNAME:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
JSObject* obj = nullptr;
if (!script()->hasNonSyntacticScope())
obj = testGlobalLexicalBinding(name);
if (obj)
return setStaticName(obj, name);
return jsop_setprop(name);
}
case JSOP_GETNAME:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_getname(name);
}
case JSOP_GETINTRINSIC:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_intrinsic(name);
}
case JSOP_GETIMPORT:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_getimport(name);
}
case JSOP_BINDGNAME:
if (!script()->hasNonSyntacticScope()) {
if (JSObject* scope = testGlobalLexicalBinding(info().getName(pc)))
return pushConstant(ObjectValue(*scope));
}
// Fall through to JSOP_BINDNAME
case JSOP_BINDNAME:
return jsop_bindname(info().getName(pc));
case JSOP_DUP:
current->pushSlot(current->stackDepth() - 1);
return true;
case JSOP_DUP2:
return jsop_dup2();
case JSOP_SWAP:
current->swapAt(-1);
return true;
case JSOP_PICK:
current->pick(-GET_INT8(pc));
return true;
case JSOP_GETALIASEDVAR:
return jsop_getaliasedvar(ScopeCoordinate(pc));
case JSOP_SETALIASEDVAR:
return jsop_setaliasedvar(ScopeCoordinate(pc));
case JSOP_UINT24:
return pushConstant(Int32Value(GET_UINT24(pc)));
case JSOP_INT32:
return pushConstant(Int32Value(GET_INT32(pc)));
case JSOP_LOOPHEAD:
// JSOP_LOOPHEAD is handled when processing the loop header.
MOZ_CRASH("JSOP_LOOPHEAD outside loop");
case JSOP_GETELEM:
case JSOP_CALLELEM:
if (!jsop_getelem())
return false;
if (op == JSOP_CALLELEM && !improveThisTypesForCall())
return false;
return true;
case JSOP_SETELEM:
case JSOP_STRICTSETELEM:
return jsop_setelem();
case JSOP_LENGTH:
return jsop_length();
case JSOP_NOT:
return jsop_not();
case JSOP_FUNCTIONTHIS:
return jsop_functionthis();
case JSOP_GLOBALTHIS:
return jsop_globalthis();
case JSOP_CALLEE: {
MDefinition* callee = getCallee();
current->push(callee);
return true;
}
case JSOP_GETPROP:
case JSOP_CALLPROP:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
if (!jsop_getprop(name))
return false;
if (op == JSOP_CALLPROP && !improveThisTypesForCall())
return false;
return true;
}
case JSOP_SETPROP:
case JSOP_STRICTSETPROP:
case JSOP_SETNAME:
case JSOP_STRICTSETNAME:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_setprop(name);
}
case JSOP_DELPROP:
case JSOP_STRICTDELPROP:
{
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_delprop(name);
}
case JSOP_DELELEM:
case JSOP_STRICTDELELEM:
return jsop_delelem();
case JSOP_REGEXP:
return jsop_regexp(info().getRegExp(pc));
case JSOP_CALLSITEOBJ:
return pushConstant(ObjectValue(*(info().getObject(pc))));
case JSOP_OBJECT:
return jsop_object(info().getObject(pc));
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
return jsop_typeof();
case JSOP_TOID:
return jsop_toid();
case JSOP_LAMBDA:
return jsop_lambda(info().getFunction(pc));
case JSOP_LAMBDA_ARROW:
return jsop_lambda_arrow(info().getFunction(pc));
case JSOP_ITER:
return jsop_iter(GET_INT8(pc));
case JSOP_MOREITER:
return jsop_itermore();
case JSOP_ISNOITER:
return jsop_isnoiter();
case JSOP_ENDITER:
return jsop_iterend();
case JSOP_IN:
return jsop_in();
case JSOP_SETRVAL:
MOZ_ASSERT(!script()->noScriptRval());
current->setSlot(info().returnValueSlot(), current->pop());
return true;
case JSOP_INSTANCEOF:
return jsop_instanceof();
case JSOP_DEBUGLEAVEBLOCK:
return true;
case JSOP_DEBUGGER:
return jsop_debugger();
case JSOP_GIMPLICITTHIS:
if (!script()->hasNonSyntacticScope())
return pushConstant(UndefinedValue());
// Just fall through to the unsupported bytecode case.
break;
case JSOP_NEWTARGET:
return jsop_newtarget();
case JSOP_CHECKOBJCOERCIBLE:
return jsop_checkobjcoercible();
#ifdef DEBUG
case JSOP_PUSHBLOCKSCOPE:
case JSOP_FRESHENBLOCKSCOPE:
case JSOP_POPBLOCKSCOPE:
// These opcodes are currently unhandled by Ion, but in principle
// there's no reason they couldn't be. Whenever this happens, OSR will
// have to consider that JSOP_FRESHENBLOCK mutates the scope chain --
// right now it caches the scope chain in MBasicBlock::scopeChain().
// That stale value will have to be updated when JSOP_FRESHENBLOCK is
// encountered.
#endif
default:
break;
}
// Track a simpler message, since the actionable abort message is a
// static string, and the internal opcode name isn't an actionable
// thing anyways.
trackActionableAbort("Unsupported bytecode");
#ifdef DEBUG
return abort("Unsupported opcode: %s", CodeName[op]);
#else
return abort("Unsupported opcode: %d", op);
#endif
}
// Given that the current control flow structure has ended forcefully,
// via a return, break, or continue (rather than joining), propagate the
// termination up. For example, a return nested 5 loops deep may terminate
// every outer loop at once, if there are no intervening conditionals:
//
// for (...) {
// for (...) {
// return x;
// }
// }
//
// If |current| is nullptr when this function returns, then there is no more
// control flow to be processed.
IonBuilder::ControlStatus
IonBuilder::processControlEnd()
{
MOZ_ASSERT(!current);
if (cfgStack_.empty()) {
// If there is no more control flow to process, then this is the
// last return in the function.
return ControlStatus_Ended;
}
return processCfgStack();
}
// Processes the top of the CFG stack. This is used from two places:
// (1) processControlEnd(), whereby a break, continue, or return may interrupt
// an in-progress CFG structure before reaching its actual termination
// point in the bytecode.
// (2) traverseBytecode(), whereby we reach the last instruction in a CFG
// structure.
IonBuilder::ControlStatus
IonBuilder::processCfgStack()
{
ControlStatus status = processCfgEntry(cfgStack_.back());
// If this terminated a CFG structure, act like processControlEnd() and
// keep propagating upward.
while (status == ControlStatus_Ended) {
popCfgStack();
if (cfgStack_.empty())
return status;
status = processCfgEntry(cfgStack_.back());
}
// If some join took place, the current structure is finished.
if (status == ControlStatus_Joined)
popCfgStack();
return status;
}
IonBuilder::ControlStatus
IonBuilder::processCfgEntry(CFGState& state)
{
switch (state.state) {
case CFGState::IF_TRUE:
case CFGState::IF_TRUE_EMPTY_ELSE:
return processIfEnd(state);
case CFGState::IF_ELSE_TRUE:
return processIfElseTrueEnd(state);
case CFGState::IF_ELSE_FALSE:
return processIfElseFalseEnd(state);
case CFGState::DO_WHILE_LOOP_BODY:
return processDoWhileBodyEnd(state);
case CFGState::DO_WHILE_LOOP_COND:
return processDoWhileCondEnd(state);
case CFGState::WHILE_LOOP_COND:
return processWhileCondEnd(state);
case CFGState::WHILE_LOOP_BODY:
return processWhileBodyEnd(state);
case CFGState::FOR_LOOP_COND:
return processForCondEnd(state);
case CFGState::FOR_LOOP_BODY:
return processForBodyEnd(state);
case CFGState::FOR_LOOP_UPDATE:
return processForUpdateEnd(state);
case CFGState::TABLE_SWITCH:
return processNextTableSwitchCase(state);
case CFGState::COND_SWITCH_CASE:
return processCondSwitchCase(state);
case CFGState::COND_SWITCH_BODY:
return processCondSwitchBody(state);
case CFGState::AND_OR:
return processAndOrEnd(state);
case CFGState::LABEL:
return processLabelEnd(state);
case CFGState::TRY:
return processTryEnd(state);
default:
MOZ_CRASH("unknown cfgstate");
}
}
IonBuilder::ControlStatus
IonBuilder::processIfEnd(CFGState& state)
{
bool thenBranchTerminated = !current;
if (!thenBranchTerminated) {
// Here, the false block is the join point. Create an edge from the
// current block to the false block. Note that a RETURN opcode
// could have already ended the block.
current->end(MGoto::New(alloc(), state.branch.ifFalse));
if (!state.branch.ifFalse->addPredecessor(alloc(), current))
return ControlStatus_Error;
}
if (!setCurrentAndSpecializePhis(state.branch.ifFalse))
return ControlStatus_Error;
graph().moveBlockToEnd(current);
pc = current->pc();
if (thenBranchTerminated) {
// If we can't reach here via the then-branch, we can filter the types
// after the if-statement based on the if-condition.
MTest* test = state.branch.test;
if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
return ControlStatus_Error;
}
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processIfElseTrueEnd(CFGState& state)
{
// We've reached the end of the true branch of an if-else. Don't
// create an edge yet, just transition to parsing the false branch.
state.state = CFGState::IF_ELSE_FALSE;
state.branch.ifTrue = current;
state.stopAt = state.branch.falseEnd;
pc = state.branch.ifFalse->pc();
if (!setCurrentAndSpecializePhis(state.branch.ifFalse))
return ControlStatus_Error;
graph().moveBlockToEnd(current);
MTest* test = state.branch.test;
if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
return ControlStatus_Error;
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processIfElseFalseEnd(CFGState& state)
{
// Update the state to have the latest block from the false path.
state.branch.ifFalse = current;
// To create the join node, we need an incoming edge that has not been
// terminated yet.
MBasicBlock* pred = state.branch.ifTrue
? state.branch.ifTrue
: state.branch.ifFalse;
MBasicBlock* other = (pred == state.branch.ifTrue) ? state.branch.ifFalse : state.branch.ifTrue;
if (!pred)
return ControlStatus_Ended;
// Create a new block to represent the join.
MBasicBlock* join = newBlock(pred, state.branch.falseEnd);
if (!join)
return ControlStatus_Error;
// Create edges from the true and false blocks as needed.
pred->end(MGoto::New(alloc(), join));
if (other) {
other->end(MGoto::New(alloc(), join));
if (!join->addPredecessor(alloc(), other))
return ControlStatus_Error;
}
// Ignore unreachable remainder of false block if existent.
if (!setCurrentAndSpecializePhis(join))
return ControlStatus_Error;
pc = current->pc();
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processBrokenLoop(CFGState& state)
{
MOZ_ASSERT(!current);
MOZ_ASSERT(loopDepth_);
loopDepth_--;
// A broken loop is not a real loop (it has no header or backedge), so
// reset the loop depth.
for (MBasicBlockIterator i(graph().begin(state.loop.entry)); i != graph().end(); i++) {
if (i->loopDepth() > loopDepth_)
i->setLoopDepth(i->loopDepth() - 1);
}
// If the loop started with a condition (while/for) then even if the
// structure never actually loops, the condition itself can still fail and
// thus we must resume at the successor, if one exists.
if (!setCurrentAndSpecializePhis(state.loop.successor))
return ControlStatus_Error;
if (current) {
MOZ_ASSERT(current->loopDepth() == loopDepth_);
graph().moveBlockToEnd(current);
}
// Join the breaks together and continue parsing.
if (state.loop.breaks) {
MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
if (!block)
return ControlStatus_Error;
if (current) {
current->end(MGoto::New(alloc(), block));
if (!block->addPredecessor(alloc(), current))
return ControlStatus_Error;
}
if (!setCurrentAndSpecializePhis(block))
return ControlStatus_Error;
}
// If the loop is not gated on a condition, and has only returns, we'll
// reach this case. For example:
// do { ... return; } while ();
if (!current)
return ControlStatus_Ended;
// Otherwise, the loop is gated on a condition and/or has breaks so keep
// parsing at the successor.
pc = current->pc();
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::finishLoop(CFGState& state, MBasicBlock* successor)
{
MOZ_ASSERT(current);
MOZ_ASSERT(loopDepth_);
loopDepth_--;
MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_);
// Compute phis in the loop header and propagate them throughout the loop,
// including the successor.
AbortReason r = state.loop.entry->setBackedge(current);
if (r == AbortReason_Alloc)
return ControlStatus_Error;
if (r == AbortReason_Disable) {
// If there are types for variables on the backedge that were not
// present at the original loop header, then uses of the variables'
// phis may have generated incorrect nodes. The new types have been
// incorporated into the header phis, so remove all blocks for the
// loop body and restart with the new types.
return restartLoop(state);
}
if (successor) {
graph().moveBlockToEnd(successor);
successor->inheritPhis(state.loop.entry);
}
if (state.loop.breaks) {
// Propagate phis placed in the header to individual break exit points.
DeferredEdge* edge = state.loop.breaks;
while (edge) {
edge->block->inheritPhis(state.loop.entry);
edge = edge->next;
}
// Create a catch block to join all break exits.
MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc);
if (!block)
return ControlStatus_Error;
if (successor) {
// Finally, create an unconditional edge from the successor to the
// catch block.
successor->end(MGoto::New(alloc(), block));
if (!block->addPredecessor(alloc(), successor))
return ControlStatus_Error;
}
successor = block;
}
if (!setCurrentAndSpecializePhis(successor))
return ControlStatus_Error;
// An infinite loop (for (;;) { }) will not have a successor.
if (!current)
return ControlStatus_Ended;
pc = current->pc();
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::restartLoop(CFGState state)
{
spew("New types at loop header, restarting loop body");
if (JitOptions.limitScriptSize) {
if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS)
return ControlStatus_Abort;
}
MBasicBlock* header = state.loop.entry;
// Discard unreferenced & pre-allocated resume points.
replaceMaybeFallbackFunctionGetter(nullptr);
// Remove all blocks in the loop body other than the header, which has phis
// of the appropriate type and incoming edges to preserve.
graph().removeBlocksAfter(header);
// Remove all instructions from the header itself, and all resume points
// except the entry resume point.
header->discardAllInstructions();
header->discardAllResumePoints(/* discardEntry = */ false);
header->setStackDepth(header->getPredecessor(0)->stackDepth());
popCfgStack();
loopDepth_++;
if (!pushLoop(state.loop.initialState, state.loop.initialStopAt, header, state.loop.osr,
state.loop.loopHead, state.loop.initialPc,
state.loop.bodyStart, state.loop.bodyEnd,
state.loop.exitpc, state.loop.continuepc))
{
return ControlStatus_Error;
}
CFGState& nstate = cfgStack_.back();
nstate.loop.condpc = state.loop.condpc;
nstate.loop.updatepc = state.loop.updatepc;
nstate.loop.updateEnd = state.loop.updateEnd;
// Don't specializePhis(), as the header has been visited before and the
// phis have already had their type set.
setCurrent(header);
if (!jsop_loophead(nstate.loop.loopHead))
return ControlStatus_Error;
pc = nstate.loop.initialPc;
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processDoWhileBodyEnd(CFGState& state)
{
if (!processDeferredContinues(state))
return ControlStatus_Error;
// No current means control flow cannot reach the condition, so this will
// never loop.
if (!current)
return processBrokenLoop(state);
MBasicBlock* header = newBlock(current, state.loop.updatepc);
if (!header)
return ControlStatus_Error;
current->end(MGoto::New(alloc(), header));
state.state = CFGState::DO_WHILE_LOOP_COND;
state.stopAt = state.loop.updateEnd;
pc = state.loop.updatepc;
if (!setCurrentAndSpecializePhis(header))
return ControlStatus_Error;
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processDoWhileCondEnd(CFGState& state)
{
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
// We're guaranteed a |current|, it's impossible to break or return from
// inside the conditional expression.
MOZ_ASSERT(current);
// Pop the last value, and create the successor block.
MDefinition* vins = current->pop();
MBasicBlock* successor = newBlock(current, GetNextPc(pc), loopDepth_ - 1);
if (!successor)
return ControlStatus_Error;
// Test for do {} while(false) and don't create a loop in that case.
if (vins->isConstantValue() && !vins->constantValue().isMagic()) {
if (!vins->constantToBoolean()) {
current->end(MGoto::New(alloc(), successor));
current = nullptr;
state.loop.successor = successor;
return processBrokenLoop(state);
}
}
// Create the test instruction and end the current block.
MTest* test = newTest(vins, state.loop.entry, successor);
current->end(test);
return finishLoop(state, successor);
}
IonBuilder::ControlStatus
IonBuilder::processWhileCondEnd(CFGState& state)
{
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
// Balance the stack past the IFNE.
MDefinition* ins = current->pop();
// Create the body and successor blocks.
MBasicBlock* body = newBlock(current, state.loop.bodyStart);
state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
if (!body || !state.loop.successor)
return ControlStatus_Error;
MTest* test;
if (JSOp(*pc) == JSOP_IFNE)
test = newTest(ins, body, state.loop.successor);
else
test = newTest(ins, state.loop.successor, body);
current->end(test);
state.state = CFGState::WHILE_LOOP_BODY;
state.stopAt = state.loop.bodyEnd;
pc = state.loop.bodyStart;
if (!setCurrentAndSpecializePhis(body))
return ControlStatus_Error;
// Filter the types in the loop body.
if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test))
return ControlStatus_Error;
// If this is a for-in loop, unbox the current value as string if possible.
if (ins->isIsNoIter()) {
MIteratorMore* iterMore = ins->toIsNoIter()->input()->toIteratorMore();
jsbytecode* iterMorePc = iterMore->resumePoint()->pc();
MOZ_ASSERT(*iterMorePc == JSOP_MOREITER);
if (!nonStringIteration_ && !inspector->hasSeenNonStringIterMore(iterMorePc)) {
MDefinition* val = current->peek(-1);
MOZ_ASSERT(val == iterMore);
MInstruction* ins = MUnbox::New(alloc(), val, MIRType_String, MUnbox::Fallible,
Bailout_NonStringInputInvalidate);
current->add(ins);
current->rewriteAtDepth(-1, ins);
}
}
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processWhileBodyEnd(CFGState& state)
{
if (!processDeferredContinues(state))
return ControlStatus_Error;
if (!current)
return processBrokenLoop(state);
current->end(MGoto::New(alloc(), state.loop.entry));
return finishLoop(state, state.loop.successor);
}
IonBuilder::ControlStatus
IonBuilder::processForCondEnd(CFGState& state)
{
MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE);
// Balance the stack past the IFNE.
MDefinition* ins = current->pop();
// Create the body and successor blocks.
MBasicBlock* body = newBlock(current, state.loop.bodyStart);
state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
if (!body || !state.loop.successor)
return ControlStatus_Error;
MTest* test = newTest(ins, body, state.loop.successor);
current->end(test);
state.state = CFGState::FOR_LOOP_BODY;
state.stopAt = state.loop.bodyEnd;
pc = state.loop.bodyStart;
if (!setCurrentAndSpecializePhis(body))
return ControlStatus_Error;
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processForBodyEnd(CFGState& state)
{
if (!processDeferredContinues(state))
return ControlStatus_Error;
// If there is no updatepc, just go right to processing what would be the
// end of the update clause. Otherwise, |current| might be nullptr; if this is
// the case, the udpate is unreachable anyway.
if (!state.loop.updatepc || !current)
return processForUpdateEnd(state);
pc = state.loop.updatepc;
state.state = CFGState::FOR_LOOP_UPDATE;
state.stopAt = state.loop.updateEnd;
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processForUpdateEnd(CFGState& state)
{
// If there is no current, we couldn't reach the loop edge and there was no
// update clause.
if (!current)
return processBrokenLoop(state);
current->end(MGoto::New(alloc(), state.loop.entry));
return finishLoop(state, state.loop.successor);
}
IonBuilder::DeferredEdge*
IonBuilder::filterDeadDeferredEdges(DeferredEdge* edge)
{
DeferredEdge* head = edge;
DeferredEdge* prev = nullptr;
while (edge) {
if (edge->block->isDead()) {
if (prev)
prev->next = edge->next;
else
head = edge->next;
} else {
prev = edge;
}
edge = edge->next;
}
// There must be at least one deferred edge from a block that was not
// deleted; blocks are deleted when restarting processing of a loop, and
// the final version of the loop body will have edges from live blocks.
MOZ_ASSERT(head);
return head;
}
bool
IonBuilder::processDeferredContinues(CFGState& state)
{
// If there are any continues for this loop, and there is an update block,
// then we need to create a new basic block to house the update.
if (state.loop.continues) {
DeferredEdge* edge = filterDeadDeferredEdges(state.loop.continues);
MBasicBlock* update = newBlock(edge->block, loops_.back().continuepc);
if (!update)
return false;
if (current) {
current->end(MGoto::New(alloc(), update));
if (!update->addPredecessor(alloc(), current))
return false;
}
// No need to use addPredecessor for first edge,
// because it is already predecessor.
edge->block->end(MGoto::New(alloc(), update));
edge = edge->next;
// Remaining edges
while (edge) {
edge->block->end(MGoto::New(alloc(), update));
if (!update->addPredecessor(alloc(), edge->block))
return false;
edge = edge->next;
}
state.loop.continues = nullptr;
if (!setCurrentAndSpecializePhis(update))
return ControlStatus_Error;
}
return true;
}
MBasicBlock*
IonBuilder::createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc)
{
edge = filterDeadDeferredEdges(edge);
// Create block, using the first break statement as predecessor
MBasicBlock* successor = newBlock(edge->block, pc);
if (!successor)
return nullptr;
// No need to use addPredecessor for first edge,
// because it is already predecessor.
edge->block->end(MGoto::New(alloc(), successor));
edge = edge->next;
// Finish up remaining breaks.
while (edge) {
edge->block->end(MGoto::New(alloc(), successor));
if (!successor->addPredecessor(alloc(), edge->block))
return nullptr;
edge = edge->next;
}
return successor;
}
IonBuilder::ControlStatus
IonBuilder::processNextTableSwitchCase(CFGState& state)
{
MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH);
state.tableswitch.currentBlock++;
// Test if there are still unprocessed successors (cases/default)
if (state.tableswitch.currentBlock >= state.tableswitch.ins->numBlocks())
return processSwitchEnd(state.tableswitch.breaks, state.tableswitch.exitpc);
// Get the next successor
MBasicBlock* successor = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock);
// Add current block as predecessor if available.
// This means the previous case didn't have a break statement.
// So flow will continue in this block.
if (current) {
current->end(MGoto::New(alloc(), successor));
if (!successor->addPredecessor(alloc(), current))
return ControlStatus_Error;
} else {
// If this is an actual case statement, optimize by replacing the
// input to the switch case with the actual number of the case.
// This constant has been emitted when creating the case blocks.
if (state.tableswitch.ins->getDefault() != successor) {
MConstant* constant = successor->begin()->toConstant();
for (uint32_t j = 0; j < successor->stackDepth(); j++) {
MDefinition* ins = successor->getSlot(j);
if (ins != state.tableswitch.ins->getOperand(0))
continue;
constant->setDependency(state.tableswitch.ins);
successor->setSlot(j, constant);
}
}
}
// Insert successor after the current block, to maintain RPO.
graph().moveBlockToEnd(successor);
// If this is the last successor the block should stop at the end of the tableswitch
// Else it should stop at the start of the next successor
if (state.tableswitch.currentBlock+1 < state.tableswitch.ins->numBlocks())
state.stopAt = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock+1)->pc();
else
state.stopAt = state.tableswitch.exitpc;
if (!setCurrentAndSpecializePhis(successor))
return ControlStatus_Error;
pc = current->pc();
return ControlStatus_Jumped;
}
IonBuilder::ControlStatus
IonBuilder::processAndOrEnd(CFGState& state)
{
MOZ_ASSERT(current);
MBasicBlock* lhs = state.branch.ifFalse;
// Create a new block to represent the join.
MBasicBlock* join = newBlock(current, state.stopAt);
if (!join)
return ControlStatus_Error;
// End the rhs.
current->end(MGoto::New(alloc(), join));
// End the lhs.
lhs->end(MGoto::New(alloc(), join));
if (!join->addPredecessor(alloc(), state.branch.ifFalse))
return ControlStatus_Error;
// Set the join path as current path.
if (!setCurrentAndSpecializePhis(join))
return ControlStatus_Error;
pc = current->pc();
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processLabelEnd(CFGState& state)
{
MOZ_ASSERT(state.state == CFGState::LABEL);
// If there are no breaks and no current, controlflow is terminated.
if (!state.label.breaks && !current)
return ControlStatus_Ended;
// If there are no breaks to this label, there's nothing to do.
if (!state.label.breaks)
return ControlStatus_Joined;
MBasicBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt);
if (!successor)
return ControlStatus_Error;
if (current) {
current->end(MGoto::New(alloc(), successor));
if (!successor->addPredecessor(alloc(), current))
return ControlStatus_Error;
}
pc = state.stopAt;
if (!setCurrentAndSpecializePhis(successor))
return ControlStatus_Error;
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processTryEnd(CFGState& state)
{
MOZ_ASSERT(state.state == CFGState::TRY);
if (!state.try_.successor) {
MOZ_ASSERT(!current);
return ControlStatus_Ended;
}
if (current) {
current->end(MGoto::New(alloc(), state.try_.successor));
if (!state.try_.successor->addPredecessor(alloc(), current))
return ControlStatus_Error;
}
// Start parsing the code after this try-catch statement.
if (!setCurrentAndSpecializePhis(state.try_.successor))
return ControlStatus_Error;
graph().moveBlockToEnd(current);
pc = current->pc();
return ControlStatus_Joined;
}
IonBuilder::ControlStatus
IonBuilder::processBreak(JSOp op, jssrcnote* sn)
{
MOZ_ASSERT(op == JSOP_GOTO);
MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK ||
SN_TYPE(sn) == SRC_BREAK2LABEL);
// Find the break target.
jsbytecode* target = pc + GetJumpOffset(pc);
DebugOnly<bool> found = false;
if (SN_TYPE(sn) == SRC_BREAK2LABEL) {
for (size_t i = labels_.length() - 1; i < labels_.length(); i--) {
CFGState& cfg = cfgStack_[labels_[i].cfgEntry];
MOZ_ASSERT(cfg.state == CFGState::LABEL);
if (cfg.stopAt == target) {
cfg.label.breaks = new(alloc()) DeferredEdge(current, cfg.label.breaks);
found = true;
break;
}
}
} else {
for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
CFGState& cfg = cfgStack_[loops_[i].cfgEntry];
MOZ_ASSERT(cfg.isLoop());
if (cfg.loop.exitpc == target) {
cfg.loop.breaks = new(alloc()) DeferredEdge(current, cfg.loop.breaks);
found = true;
break;
}
}
}
MOZ_ASSERT(found);
setCurrent(nullptr);
pc += CodeSpec[op].length;
return processControlEnd();
}
static inline jsbytecode*
EffectiveContinue(jsbytecode* pc)
{
if (JSOp(*pc) == JSOP_GOTO)
return pc + GetJumpOffset(pc);
return pc;
}
IonBuilder::ControlStatus
IonBuilder::processContinue(JSOp op)
{
MOZ_ASSERT(op == JSOP_GOTO);
// Find the target loop.
CFGState* found = nullptr;
jsbytecode* target = pc + GetJumpOffset(pc);
for (size_t i = loops_.length() - 1; i < loops_.length(); i--) {
if (loops_[i].continuepc == target ||
EffectiveContinue(loops_[i].continuepc) == target)
{
found = &cfgStack_[loops_[i].cfgEntry];
break;
}
}
// There must always be a valid target loop structure. If not, there's
// probably an off-by-something error in which pc we track.
MOZ_ASSERT(found);
CFGState& state = *found;
state.loop.continues = new(alloc()) DeferredEdge(current, state.loop.continues);
setCurrent(nullptr);
pc += CodeSpec[op].length;
return processControlEnd();
}
IonBuilder::ControlStatus
IonBuilder::processSwitchBreak(JSOp op)
{
MOZ_ASSERT(op == JSOP_GOTO);
// Find the target switch.
CFGState* found = nullptr;
jsbytecode* target = pc + GetJumpOffset(pc);
for (size_t i = switches_.length() - 1; i < switches_.length(); i--) {
if (switches_[i].continuepc == target) {
found = &cfgStack_[switches_[i].cfgEntry];
break;
}
}
// There must always be a valid target loop structure. If not, there's
// probably an off-by-something error in which pc we track.
MOZ_ASSERT(found);
CFGState& state = *found;
DeferredEdge** breaks = nullptr;
switch (state.state) {
case CFGState::TABLE_SWITCH:
breaks = &state.tableswitch.breaks;
break;
case CFGState::COND_SWITCH_BODY:
breaks = &state.condswitch.breaks;
break;
default:
MOZ_CRASH("Unexpected switch state.");
}
*breaks = new(alloc()) DeferredEdge(current, *breaks);
setCurrent(nullptr);
pc += CodeSpec[op].length;
return processControlEnd();
}
IonBuilder::ControlStatus
IonBuilder::processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc)
{
// No break statements, no current.
// This means that control flow is cut-off from this point
// (e.g. all cases have return statements).
if (!breaks && !current)
return ControlStatus_Ended;
// Create successor block.
// If there are breaks, create block with breaks as predecessor
// Else create a block with current as predecessor
MBasicBlock* successor = nullptr;
if (breaks)
successor = createBreakCatchBlock(breaks, exitpc);
else
successor = newBlock(current, exitpc);
if (!successor)
return ControlStatus_Ended;
// If there is current, the current block flows into this one.
// So current is also a predecessor to this block
if (current) {
current->end(MGoto::New(alloc(), successor));
if (breaks) {
if (!successor->addPredecessor(alloc(), current))
return ControlStatus_Error;
}
}
pc = exitpc;
if (!setCurrentAndSpecializePhis(successor))
return ControlStatus_Error;
return ControlStatus_Joined;
}
IonBuilder::