Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/IonBuilder.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ScopeExit.h"
#include <algorithm>
#include "builtin/Eval.h"
#include "builtin/TypedObject.h"
#include "frontend/SourceNotes.h"
#include "jit/BaselineFrame.h"
#include "jit/BaselineInspector.h"
#include "jit/CacheIR.h"
#include "jit/Ion.h"
#include "jit/IonOptimizationLevels.h"
#include "jit/JitSpewer.h"
#include "jit/Lowering.h"
#include "jit/MIRGraph.h"
#include "util/CheckedArithmetic.h"
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"
#include "vm/EnvironmentObject.h"
#include "vm/Instrumentation.h"
#include "vm/Opcodes.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/RegExpStatics.h"
#include "vm/SelfHosting.h"
#include "vm/TraceLogging.h"
#include "gc/Nursery-inl.h"
#include "jit/CompileInfo-inl.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/BytecodeLocation-inl.h"
#include "vm/BytecodeUtil-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::AssertedCast;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
class jit::BaselineFrameInspector {
public:
TypeSet::Type thisType;
JSObject* singletonEnvChain;
Vector<TypeSet::Type, 4, JitAllocPolicy> argTypes;
Vector<TypeSet::Type, 4, JitAllocPolicy> varTypes;
explicit BaselineFrameInspector(TempAllocator* temp)
: thisType(TypeSet::UndefinedType()),
singletonEnvChain(nullptr),
argTypes(*temp),
varTypes(*temp) {}
};
BaselineFrameInspector* jit::NewBaselineFrameInspector(TempAllocator* temp,
BaselineFrame* frame,
uint32_t frameSize) {
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->isFunctionFrame()) {
inspector->thisType =
TypeSet::GetMaybeUntrackedValueType(frame->thisArgument());
}
if (frame->environmentChain()->isSingleton()) {
inspector->singletonEnvChain = frame->environmentChain();
}
JSScript* script = frame->script();
if (script->function()) {
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());
}
}
}
uint32_t numValueSlots = frame->numValueSlots(frameSize);
if (!inspector->varTypes.reserve(numValueSlots)) {
return nullptr;
}
for (size_t i = 0; i < numValueSlots; i++) {
TypeSet::Type type =
TypeSet::GetMaybeUntrackedValueType(*frame->valueSlot(i));
inspector->varTypes.infallibleAppend(type);
}
return inspector;
}
IonBuilder::IonBuilder(JSContext* analysisContext, MIRGenerator& mirGen,
CompileInfo* info, CompilerConstraintList* constraints,
BaselineInspector* inspector,
BaselineFrameInspector* baselineFrame,
size_t inliningDepth, uint32_t loopDepth)
: actionableAbortScript_(nullptr),
actionableAbortPc_(nullptr),
actionableAbortMessage_(nullptr),
analysisContext(analysisContext),
baselineFrame_(baselineFrame),
constraints_(constraints),
mirGen_(mirGen),
tiOracle_(this, constraints),
realm(mirGen.realm),
info_(info),
optimizationInfo_(&mirGen.optimizationInfo()),
alloc_(&mirGen.alloc()),
graph_(&mirGen.graph()),
thisTypes(nullptr),
argTypes(nullptr),
typeArray(nullptr),
typeArrayHint(0),
bytecodeTypeMap(nullptr),
loopDepth_(loopDepth),
loopStack_(*alloc_),
trackedOptimizationSites_(*alloc_),
abortedPreliminaryGroups_(*alloc_),
callerResumePoint_(nullptr),
callerBuilder_(nullptr),
iterators_(*alloc_),
loopHeaders_(*alloc_),
inspector(inspector),
inliningDepth_(inliningDepth),
inlinedBytecodeLength_(0),
numLoopRestarts_(0),
failedBoundsCheck_(info_->script()->failedBoundsCheck()),
failedShapeGuard_(info_->script()->failedShapeGuard()),
failedLexicalCheck_(info_->script()->failedLexicalCheck()),
#ifdef DEBUG
hasLazyArguments_(false),
#endif
inlineCallInfo_(nullptr),
maybeFallbackFunctionGetter_(nullptr) {
script_ = info_->script();
pc = info_->startPC();
// The script must have a JitScript. Compilation requires a BaselineScript
// too.
MOZ_ASSERT(script_->hasJitScript());
MOZ_ASSERT_IF(!info_->isAnalysis(), script_->hasBaselineScript());
MOZ_ASSERT(!!analysisContext ==
(info_->analysisMode() == Analysis_DefiniteProperties));
MOZ_ASSERT(script_->numBytecodeTypeSets() < JSScript::MaxBytecodeTypeSets);
if (!info_->isAnalysis()) {
script()->jitScript()->setIonCompiledOrInlined();
}
}
mozilla::GenericErrorResult<AbortReason> IonBuilder::abort(AbortReason r) {
auto res = mirGen_.abort(r);
[[maybe_unused]] unsigned line, column;
#ifdef DEBUG
line = PCToLineNumber(script(), pc, &column);
#else
line = script()->lineno();
column = script()->column();
#endif
JitSpew(JitSpew_IonAbort, "aborted @ %s:%u:%u", script()->filename(), line,
column);
return res;
}
mozilla::GenericErrorResult<AbortReason> IonBuilder::abort(AbortReason r,
const char* message,
...) {
// Don't call PCToLineNumber in release builds.
va_list ap;
va_start(ap, message);
auto res = mirGen_.abortFmt(r, message, ap);
va_end(ap);
[[maybe_unused]] unsigned line, column;
#ifdef DEBUG
line = PCToLineNumber(script(), pc, &column);
#else
line = script()->lineno();
column = script()->column();
#endif
JitSpew(JitSpew_IonAbort, "aborted @ %s:%u:%u", script()->filename(), line,
column);
return res;
}
IonBuilder* IonBuilder::outermostBuilder() {
IonBuilder* builder = this;
while (builder->callerBuilder_) {
builder = builder->callerBuilder_;
}
return builder;
}
void IonBuilder::spew(const char* message) {
// Don't call PCToLineNumber in release builds.
#ifdef DEBUG
JitSpew(JitSpew_IonMIR, "%s @ %s:%u", message, script()->filename(),
PCToLineNumber(script(), pc));
#endif
}
JSFunction* IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes) {
if (!calleeTypes) {
return nullptr;
}
TemporaryTypeSet::ObjectKey* key = calleeTypes->maybeSingleObject();
if (!key || key->clasp() != &JSFunction::class_) {
return nullptr;
}
if (key->isSingleton()) {
return &key->singleton()->as<JSFunction>();
}
if (JSFunction* fun = key->group()->maybeInterpretedFunction()) {
return fun;
}
return nullptr;
}
AbortReasonOr<Ok> IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes,
bool constructing,
InliningTargets& targets,
uint32_t maxTargets) {
MOZ_ASSERT(targets.empty());
if (!calleeTypes) {
return Ok();
}
if (calleeTypes->baseFlags() != 0) {
return Ok();
}
unsigned objCount = calleeTypes->getObjectCount();
if (objCount == 0 || objCount > maxTargets) {
return Ok();
}
if (!targets.reserve(objCount)) {
return abort(AbortReason::Alloc);
}
for (unsigned i = 0; i < objCount; i++) {
JSObject* obj = calleeTypes->getSingleton(i);
ObjectGroup* group = nullptr;
if (obj) {
MOZ_ASSERT(obj->isSingleton());
} else {
group = calleeTypes->getGroup(i);
if (!group) {
continue;
}
obj = group->maybeInterpretedFunction();
if (!obj) {
targets.clear();
return Ok();
}
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 Ok();
}
targets.infallibleAppend(InliningTarget(obj, group));
}
return Ok();
}
IonBuilder::InliningDecision IonBuilder::DontInline(JSScript* targetScript,
const char* reason) {
if (targetScript) {
JitSpew(JitSpew_Inlining, "Cannot inline %s:%u:%u %s",
targetScript->filename(), targetScript->lineno(),
targetScript->column(), 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()) {
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()) {
return DontInline(nullptr, "Non-interpreted target");
}
// Never inline scripted cross-realm calls.
if (target->realm() != script()->realm()) {
return DontInline(nullptr, "Cross-realm call");
}
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()) {
return DontInline(nullptr, "Empty TypeSet for |this|");
}
for (size_t i = 0; i < callInfo.argc(); i++) {
if (callInfo.getArg(i)->emptyResultTypeSet()) {
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) {
RootedFunction fun(analysisContext, target);
RootedScript script(analysisContext,
JSFunction::getOrCreateScript(analysisContext, fun));
if (!script) {
return InliningDecision_Error;
}
if (CanBaselineInterpretScript(script)) {
AutoKeepJitScripts keepJitScript(analysisContext);
if (!script->ensureHasJitScript(analysisContext, keepJitScript)) {
return InliningDecision_Error;
}
}
}
if (!target->hasBytecode()) {
return DontInline(nullptr, "Lazy script");
}
JSScript* inlineScript = target->nonLazyScript();
if (callInfo.constructing()) {
if (!target->isConstructor()) {
return DontInline(inlineScript, "Callee is not a constructor");
}
// Don't inline if creating |this| for this target is complicated, for
// example when the newTarget.prototype lookup may be effectful.
if (!target->constructorNeedsUninitializedThis() &&
callInfo.getNewTarget() != callInfo.callee()) {
JSFunction* newTargetFun =
getSingleCallTarget(callInfo.getNewTarget()->resultTypeSet());
if (!newTargetFun) {
return DontInline(inlineScript, "Constructing with unknown newTarget");
}
if (!newTargetFun->hasNonConfigurablePrototypeDataProperty()) {
return DontInline(inlineScript,
"Constructing with effectful newTarget.prototype");
}
} else {
// At this point, the target is either a function that requires an
// uninitialized-this (bound function or derived class constructor) or a
// scripted constructor with a non-configurable .prototype data property
// (self-hosted built-in constructor, non-self-hosted scripted function).
MOZ_ASSERT(target->constructorNeedsUninitializedThis() ||
target->hasNonConfigurablePrototypeDataProperty());
}
}
if (!callInfo.constructing() && target->isClassConstructor()) {
return DontInline(inlineScript, "Not constructing class constructor");
}
if (!CanIonInlineScript(inlineScript)) {
return DontInline(inlineScript, "Disabled Ion compilation");
}
if (info().isAnalysis()) {
// Analysis requires only a JitScript.
if (!inlineScript->hasJitScript()) {
return DontInline(inlineScript, "No JitScript");
}
} else {
// Compilation requires a BaselineScript.
if (!inlineScript->hasBaselineScript()) {
return DontInline(inlineScript, "No baseline jitcode");
}
}
// Don't inline functions with a higher optimization level.
if (!isHighestOptimizationLevel()) {
OptimizationLevel level = optimizationLevel();
if (inlineScript->hasIonScript() &&
(inlineScript->ionScript()->isRecompiling() ||
inlineScript->ionScript()->optimizationLevel() > level)) {
return DontInline(inlineScript, "More optimized");
}
if (IonOptimizations.levelForScript(inlineScript, nullptr) > level) {
return DontInline(inlineScript, "Should be more optimized");
}
}
if (TooManyFormalArguments(target->nargs())) {
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())) {
return DontInline(inlineScript, "Too many actual args");
}
if (hasCommonInliningPath(inlineScript)) {
return DontInline(inlineScript, "Common inlining path");
}
if (inlineScript->uninlineable()) {
return DontInline(inlineScript, "Uninlineable script");
}
if (inlineScript->needsArgsObj()) {
return DontInline(inlineScript, "Script that needs an arguments object");
}
if (inlineScript->isDebuggee()) {
return DontInline(inlineScript, "Script is debuggee");
}
return InliningDecision_Inline;
}
AbortReasonOr<Ok> IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry) {
MOZ_ASSERT(!entry->isDead());
MOZ_ASSERT(JSOp(*pc) == JSOp::LoopHead);
// 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.
bool foundEntry = false;
for (size_t i = 0; i < loopHeaders_.length(); i++) {
if (loopHeaders_[i].pc == pc) {
MBasicBlock* oldEntry = loopHeaders_[i].header;
// If this block has been discarded, its resume points will have
// already discarded their operands.
if (oldEntry->isDead()) {
loopHeaders_[i].header = entry;
foundEntry = true;
break;
}
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(alloc(), oldPhi->type(),
oldPhi->resultTypeSet())) {
return abort(AbortReason::Alloc);
}
}
// 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 Ok();
}
}
if (!foundEntry) {
if (!loopHeaders_.append(LoopHeader(pc, entry))) {
return abort(AbortReason::Alloc);
}
}
// Get the start and end bytecode locations.
BytecodeLocation start(script_, pc);
BytecodeLocation end(script_, script_->codeEnd());
// Iterate the bytecode quickly to seed possible types in the loopheader.
Maybe<BytecodeLocation> last;
Maybe<BytecodeLocation> earlier;
for (auto it : BytecodeLocationRange(start, end)) {
if (IsBackedgeForLoopHead(it.toRawBytecode(), pc)) {
break;
}
MOZ_TRY(analyzeNewLoopTypesForLocation(entry, it, last, earlier));
earlier = last;
last = mozilla::Some(it);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::analyzeNewLoopTypesForLocation(
MBasicBlock* entry, const BytecodeLocation loc,
const Maybe<BytecodeLocation>& last_,
const Maybe<BytecodeLocation>& earlier) {
// Unfortunately Maybe<> cannot be passed as by-value argument so make a copy
// here.
Maybe<BytecodeLocation> last = last_;
// We're only interested in JSOp::SetLocal and JSOp::SetArg.
uint32_t slot;
if (loc.is(JSOp::SetLocal)) {
slot = info().localSlot(loc.local());
} else if (loc.is(JSOp::SetArg)) {
slot = info().argSlotUnchecked(loc.arg());
} else {
return Ok();
}
if (slot >= info().firstStackSlot()) {
return Ok();
}
// Ensure there is a |last| instruction.
if (!last) {
return Ok();
}
MOZ_ASSERT(last->isValid(script_));
// Analyze the |last| bytecode instruction to try to dermine the type of this
// local/argument.
MPhi* phi = entry->getSlot(slot)->toPhi();
auto addPhiBackedgeType =
[&](MIRType type, TemporaryTypeSet* typeSet) -> AbortReasonOr<Ok> {
if (!phi->addBackedgeType(alloc(), type, typeSet)) {
return abort(AbortReason::Alloc);
}
return Ok();
};
// If it's a JSOp::Pos or JSOp::ToNumeric, use its operand instead.
if (last->is(JSOp::Pos) || last->is(JSOp::ToNumeric)) {
MOZ_ASSERT(earlier);
last = earlier;
}
// If the |last| op had a TypeSet, use it.
if (last->opHasTypeSet()) {
TemporaryTypeSet* typeSet = bytecodeTypes(last->toRawBytecode());
if (typeSet->empty()) {
return Ok();
}
return addPhiBackedgeType(typeSet->getKnownMIRType(), typeSet);
}
// If the |last| op was a JSOp::GetLocal or JSOp::GetArg, use that slot's
// type.
if (last->is(JSOp::GetLocal) || last->is(JSOp::GetArg)) {
uint32_t slot = (last->is(JSOp::GetLocal))
? info().localSlot(last->local())
: info().argSlotUnchecked(last->arg());
if (slot >= info().firstStackSlot()) {
return Ok();
}
MPhi* otherPhi = entry->getSlot(slot)->toPhi();
if (!otherPhi->hasBackedgeType()) {
return Ok();
}
return addPhiBackedgeType(otherPhi->type(), otherPhi->resultTypeSet());
}
// If the |last| op has a known type (determined statically or from
// BaselineInspector), use that.
MIRType type = MIRType::None;
switch (last->getOp()) {
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::ResumeIndex:
type = MIRType::Int32;
break;
case JSOp::BitAnd:
case JSOp::BitOr:
case JSOp::BitXor:
case JSOp::BitNot:
case JSOp::Rsh:
case JSOp::Lsh:
type = inspector->expectedResultType(last->toRawBytecode());
break;
case JSOp::Ursh:
// Unsigned right shift is not applicable to BigInts, so we don't need
// to query the baseline inspector for the possible result types.
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:
case JSOp::HasOwn:
type = MIRType::Boolean;
break;
case JSOp::Double:
type = MIRType::Double;
break;
case JSOp::IterNext:
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:
case JSOp::Inc:
case JSOp::Dec:
type = inspector->expectedResultType(last->toRawBytecode());
break;
case JSOp::BigInt:
type = MIRType::BigInt;
break;
default:
break;
}
if (type == MIRType::None) {
return Ok();
}
return addPhiBackedgeType(type, /* typeSet = */ nullptr);
}
AbortReasonOr<Ok> IonBuilder::init() {
{
LifoAlloc::AutoFallibleScope fallibleAllocator(alloc().lifoAlloc());
if (!JitScript::FreezeTypeSets(constraints(), script(), &thisTypes,
&argTypes, &typeArray)) {
return abort(AbortReason::Alloc);
}
}
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
{
JSContext* cx = TlsContext.get();
RootedScript rootedScript(cx, script());
if (!rootedScript->jitScript()->ensureHasCachedIonData(cx, rootedScript)) {
return abort(AbortReason::Error);
}
}
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;
}
bytecodeTypeMap = script()->jitScript()->bytecodeTypeMap();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::build() {
// Spew IC info for inlined script, but only when actually compiling,
// not when analyzing it.
#ifdef JS_STRUCTURED_SPEW
if (!info().isAnalysis()) {
JitSpewBaselineICStats(script(), "To-Be-Compiled");
}
#endif
MOZ_TRY(init());
// The JitScript-based inlining heuristics only affect the highest
// optimization level. Other levels do almost no inlining and we don't want to
// overwrite data from the highest optimization tier.
if (isHighestOptimizationLevel()) {
script()->jitScript()->resetMaxInliningDepth();
}
MBasicBlock* entry;
MOZ_TRY_VAR(entry, newBlock(info().firstStackSlot(), pc));
MOZ_TRY(setCurrentAndSpecializePhis(entry));
#ifdef JS_JITSPEW
if (info().isAnalysis()) {
JitSpew(JitSpew_IonScripts, "Analyzing script %s:%u:%u (%p) %s",
script()->filename(), script()->lineno(), script()->column(),
(void*)script(), AnalysisModeString(info().analysisMode()));
} else {
JitSpew(JitSpew_IonScripts,
"%sompiling script %s:%u:%u (%p) (warmup-counter=%" PRIu32
", level=%s)",
(script()->hasIonScript() ? "Rec" : "C"), script()->filename(),
script()->lineno(), script()->column(), (void*)script(),
script()->getWarmUpCount(),
OptimizationLevelString(optimizationLevel()));
}
#endif
MOZ_TRY(initParameters());
initLocals();
// Initialize something for the env 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* env = MConstant::New(alloc(), UndefinedValue());
current->add(env);
current->initSlot(info().environmentChainSlot(), env);
// 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()));
// 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 abort(AbortReason::Alloc);
}
check->setResumePoint(entryRpCopy);
// Parameters have been checked to correspond to the typeset, now we unbox
// what we can in an infallible manner.
MOZ_TRY(rewriteParameters());
// It's safe to start emitting actual IR, so now build the env chain.
MOZ_TRY(initEnvironmentChain());
if (info().needsArgsObj()) {
initArgumentsObject();
}
// 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 abort(AbortReason::Alloc);
}
ins->setResumePoint(entryRpCopy);
}
#ifdef DEBUG
// lazyArguments should never be accessed in |argsObjAliasesFormals| scripts.
if (info().hasArguments() && !info().argsObjAliasesFormals()) {
hasLazyArguments_ = true;
}
#endif
insertRecompileCheck(pc);
auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
// Discard unreferenced & pre-allocated resume points.
replaceMaybeFallbackFunctionGetter(nullptr);
});
MOZ_TRY(traverseBytecode());
if (isHighestOptimizationLevel() &&
inlinedBytecodeLength_ > script_->jitScript()->inlinedBytecodeLength()) {
script_->jitScript()->setInlinedBytecodeLength(inlinedBytecodeLength_);
}
MOZ_TRY(maybeAddOsrTypeBarriers());
if (!MPhi::markIteratorPhis(iterators_)) {
return abort(AbortReason::Alloc);
}
if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) {
return abort(AbortReason::PreliminaryObjects);
}
MOZ_ASSERT(loopDepth_ == 0);
MOZ_ASSERT(loopStack_.empty());
return Ok();
}
AbortReasonOr<Ok> IonBuilder::buildInline(IonBuilder* callerBuilder,
MResumePoint* callerResumePoint,
CallInfo& callInfo) {
inlineCallInfo_ = &callInfo;
// Spew IC info for inlined script, but only when actually compiling,
// not when analyzing it.
#ifdef JS_STRUCTURED_SPEW
if (!info().isAnalysis()) {
JitSpewBaselineICStats(script(), "To-Be-Inlined");
}
#endif
MOZ_TRY(init());
JitSpew(JitSpew_IonScripts, "Inlining script %s:%u:%u (%p)",
script()->filename(), script()->lineno(), script()->column(),
(void*)script());
callerBuilder_ = callerBuilder;
callerResumePoint_ = callerResumePoint;
if (callerBuilder->failedBoundsCheck_) {
failedBoundsCheck_ = true;
}
if (callerBuilder->failedShapeGuard_) {
failedShapeGuard_ = true;
}
if (callerBuilder->failedLexicalCheck_) {
failedLexicalCheck_ = true;
}
// Generate single entrance block.
MBasicBlock* entry;
MOZ_TRY_VAR(entry, newBlock(info().firstStackSlot(), pc));
MOZ_TRY(setCurrentAndSpecializePhis(entry));
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 abort(AbortReason::Alloc);
}
// Initialize env chain slot to Undefined. It's set later by
// |initEnvironmentChain|.
MInstruction* env = MConstant::New(alloc(), UndefinedValue());
current->add(env);
current->initSlot(info().environmentChainSlot(), env);
// 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 = std::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);
}
JitSpew(JitSpew_Inlining, "Initializing %u locals", info().nlocals());
initLocals();
JitSpew(JitSpew_Inlining,
"Inline entry block MResumePoint %p, %u stack slots",
(void*)current->entryResumePoint(),
current->entryResumePoint()->stackDepth());
// +2 for the env chain and |this|, maybe another +1 for arguments object
// slot.
MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots());
#ifdef DEBUG
if (script_->argumentsHasVarBinding()) {
hasLazyArguments_ = true;
}
#endif
insertRecompileCheck(pc);
// Initialize the env chain now that all resume points operands are
// initialized.
MOZ_TRY(initEnvironmentChain(callInfo.callee()));
auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
// Discard unreferenced & pre-allocated resume points.
replaceMaybeFallbackFunctionGetter(nullptr);
});
MOZ_TRY(traverseBytecode());
MOZ_ASSERT(iterators_.empty(), "Iterators should be added to outer builder");
if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) {
return abort(AbortReason::PreliminaryObjects);
}
return Ok();
}
void IonBuilder::rewriteParameter(uint32_t slotIdx, MDefinition* param) {
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.
AbortReasonOr<Ok> IonBuilder::rewriteParameters() {
MOZ_ASSERT(info().environmentChainSlot() == 0);
// If this JSScript is not the code of a function, then skip the
// initialization of function parameters.
if (!info().funMaybeLazy()) {
return Ok();
}
for (uint32_t i = info().startArgSlot(); i < info().endArgSlot(); i++) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
MDefinition* param = current->getSlot(i);
rewriteParameter(i, param);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::initParameters() {
// If this JSScript is not the code of a function, then skip the
// initialization of function parameters.
if (!info().funMaybeLazy()) {
return Ok();
}
// 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_->jitScript()->modifiesArguments()) {
TypeSet::Type type = baselineFrame_->argTypes[i];
if (type.isSingletonUnchecked()) {
checkNurseryObject(type.singleton());
}
types->addType(type, alloc_->lifoAlloc());
}
param = MParameter::New(alloc().fallible(), i, types);
if (!param) {
return abort(AbortReason::Alloc);
}
current->add(param);
current->initSlot(info().argSlotUnchecked(i), param);
}
return Ok();
}
void IonBuilder::initLocals() {
// Initialize all frame slots to undefined. Lexical bindings are temporal
// dead zoned in bytecode.
if (info().nlocals() == 0) {
return;
}
MConstant* undef = MConstant::New(alloc(), UndefinedValue());
current->add(undef);
for (uint32_t i = 0; i < info().nlocals(); i++) {
current->initSlot(info().localSlot(i), undef);
}
}
bool IonBuilder::usesEnvironmentChain() {
return script()->jitScript()->usesEnvironmentChain();
}
AbortReasonOr<Ok> IonBuilder::initEnvironmentChain(MDefinition* callee) {
MInstruction* env = nullptr;
// If the script doesn't use the envchain, then it's already initialized
// from earlier. However, always make a env chain when |needsArgsObj| is true
// for the script, since arguments object construction requires the env chain
// to be passed in.
if (!info().needsArgsObj() && !usesEnvironmentChain()) {
return Ok();
}
// The env chain is only tracked in scripts that have NAME opcodes which
// will try to access the env. For other scripts, the env 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;
}
env = MFunctionEnvironment::New(alloc(), callee);
current->add(env);
// This reproduce what is done in CallObject::createForFunction. Skip
// this for the arguments analysis, as the script might not have a
// baseline script with template objects yet.
if (fun->needsSomeEnvironmentObject() &&
info().analysisMode() != Analysis_ArgumentsUsage) {
if (fun->needsNamedLambdaEnvironment()) {
env = createNamedLambdaObject(callee, env);
}
// TODO: Parameter expression-induced extra var environment not
// yet handled.
if (fun->needsExtraBodyVarEnvironment()) {
return abort(AbortReason::Disable, "Extra var environment unsupported");
}
if (fun->needsCallObject()) {
MOZ_TRY_VAR(env, createCallObject(callee, env));
}
}
} else if (ModuleObject* module = info().module()) {
// Modules use a pre-created env object.
env = constant(ObjectValue(module->initialEnvironment()));
} else {
// For global scripts without a non-syntactic global scope, the env
// chain is the global lexical env.
MOZ_ASSERT(!script()->isForEval());
MOZ_ASSERT(!script()->hasNonSyntacticScope());
env = constant(ObjectValue(script()->global().lexicalEnvironment()));
}
// Update the environment slot from UndefinedValue only after initial
// environment is created so that bailout doesn't see a partial env.
// See: |InitFromBailout|
current->setEnvironmentChain(env);
return Ok();
}
void IonBuilder::initArgumentsObject() {
JitSpew(JitSpew_IonMIR,
"%s:%u:%u - Emitting code to initialize arguments object! block=%p",
script()->filename(), script()->lineno(), script()->column(),
current);
MOZ_ASSERT(info().needsArgsObj());
bool mapped = script()->hasMappedArgsObj();
ArgumentsObject* templateObj =
script()->realm()->maybeArgumentsTemplateObject(mapped);
MCreateArgumentsObject* argsObj = MCreateArgumentsObject::New(
alloc(), current->environmentChain(), templateObj);
current->add(argsObj);
current->setArgumentsObject(argsObj);
}
AbortReasonOr<Ok> 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;
// If the TypeSet is more precise than |type|, adjust |type| for the
// code below.
if (type == MIRType::Value) {
type = barrier->type();
}
} 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(type);
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, ntype);
if (!typeSet) {
return abort(AbortReason::Alloc);
}
MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet);
osrBlock->insertBefore(osrBlock->lastIns(), barrier);
osrBlock->rewriteSlot(slot, barrier);
def = barrier;
}
// The following guards aren't directly linked into the usedef chain,
// however in the OSR block we need to ensure they're not optimized out, so we
// mark them as implicitly used.
switch (type) {
case MIRType::Null:
case MIRType::Undefined:
case MIRType::MagicOptimizedArguments:
def->setImplicitlyUsed();
break;
default:
break;
}
// Unbox the OSR value to the type expected by the loop header.
//
// The only specialized types that can show up here are MIRTypes with a
// corresponding TypeSet::Type because NewBaselineFrameInspector and
// newPendingLoopHeader use TypeSet::Type for Values from the BaselineFrame.
// This means magic values other than MagicOptimizedArguments are represented
// as UnknownType() and MIRType::Value. See also TypeSet::IsUntrackedValue.
switch (type) {
case MIRType::Boolean:
case MIRType::Int32:
case MIRType::Double:
case MIRType::String:
case MIRType::Symbol:
case MIRType::BigInt:
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::Value:
// Nothing to do.
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(hasLazyArguments_);
MConstant* lazyArg =
MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
osrBlock->insertBefore(osrBlock->lastIns(), lazyArg);
osrBlock->rewriteSlot(slot, lazyArg);
def = lazyArg;
break;
}
default:
MOZ_CRASH("Unexpected type");
}
MOZ_ASSERT(def == osrBlock->getSlot(slot));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::maybeAddOsrTypeBarriers() {
if (!info().osrPc()) {
return Ok();
}
// 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();
MOZ_ASSERT(osrBlock);
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().isSlotAliased(slot)) {
continue;
}
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
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();
MOZ_TRY(addOsrValueTypeBarrier(slot, &def, type, typeSet));
preheaderPhi->replaceOperand(OSR_PHI_POSITION, def);
preheaderPhi->setResultType(type);
preheaderPhi->setResultTypeSet(typeSet);
}
return Ok();
}
#ifdef DEBUG
// In debug builds, after compiling a bytecode op, this class is used to 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.
class MOZ_RAII PoppedValueUseChecker {
Vector<MDefinition*, 4, SystemAllocPolicy> popped_;
Vector<size_t, 4, SystemAllocPolicy> poppedUses_;
MBasicBlock* current_;
jsbytecode* pc_;
public:
PoppedValueUseChecker(MBasicBlock* current, jsbytecode* pc)
: current_(current), pc_(pc) {}
MOZ_MUST_USE bool init() {
unsigned nuses = GetUseCount(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;
}
}
return true;
}
void checkAfterOp() {
JSOp op = JSOp(*pc_);
// Don't require SSA uses for values popped by these ops.
switch (op) {
case JSOp::Pop:
case JSOp::PopN:
case JSOp::DupAt:
case JSOp::Dup:
case JSOp::Dup2:
case JSOp::Pick:
case JSOp::Unpick:
case JSOp::Swap:
case JSOp::SetArg:
case JSOp::SetLocal:
case JSOp::InitLexical:
case JSOp::SetRval:
case JSOp::Void:
// Basic stack/local/argument management opcodes.
return;
case JSOp::Case:
case JSOp::Default:
// These ops have to pop the switch value when branching but don't
// actually use it.
return;
default:
break;
}
for (size_t i = 0; i < popped_.length(); i++) {
switch (op) {
case JSOp::Pos:
case JSOp::ToNumeric:
case JSOp::ToPropertyKey:
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;
}
[[fallthrough]];
default:
MOZ_ASSERT(popped_[i]->isImplicitlyUsed() ||
// First value popped by JSOp::EndIter is not used at all,
// it's similar to JSOp::Pop above.
(op == JSOp::EndIter && i == 0) ||
popped_[i]->defUseCount() > poppedUses_[i]);
break;
}
}
}
};
#endif
AbortReasonOr<Ok> IonBuilder::traverseBytecode() {
// See the "Control Flow handling in IonBuilder" comment in IonBuilder.h for
// more information.
MOZ_TRY(startTraversingBlock(current));
const jsbytecode* const codeEnd = script()->codeEnd();
while (true) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
// Skip unreachable ops (for example code after a 'return' or 'throw') until
// we get to the next jump target.
if (hasTerminatedBlock()) {
while (!BytecodeIsJumpTarget(JSOp(*pc))) {
// Finish any "broken" loops with an unreachable backedge. For example:
//
// do {
// ...
// return;
// ...
// } while (x);
//
// This loop never actually loops.
if (!loopStack_.empty() &&
IsBackedgeForLoopHead(pc, loopStack_.back().header()->pc())) {
MOZ_ASSERT(loopDepth_ > 0);
loopDepth_--;
loopStack_.popBack();
}
pc = GetNextPc(pc);
if (pc == codeEnd) {
return Ok();
}
}
}
#ifdef DEBUG
PoppedValueUseChecker useChecker(current, pc);
if (!useChecker.init()) {
return abort(AbortReason::Alloc);
}
#endif
MOZ_ASSERT(script()->containsPC(pc));
nextpc = GetNextPc(pc);
// Nothing in inspectOpcode() is allowed to advance the pc.
JSOp op = JSOp(*pc);
bool restarted = false;
MOZ_TRY(inspectOpcode(op, &restarted));
#ifdef DEBUG
if (!restarted) {
useChecker.checkAfterOp();
}
#endif
if (nextpc == codeEnd) {
return Ok();
}
pc = nextpc;
MOZ_ASSERT(script()->containsPC(pc));
if (!hasTerminatedBlock()) {
current->updateTrackedSite(bytecodeSite(pc));
}
}
// The iloop above never breaks, so this point is unreachable. Don't add code
// here, or you'll trigger compile errors about unreachable code with some
// compilers!
}
AbortReasonOr<Ok> IonBuilder::startTraversingBlock(MBasicBlock* block) {
block->setLoopDepth(loopDepth_);
if (block->pc() && script()->hasScriptCounts()) {
block->setHitCount(script()->getHitCount(block->pc()));
}
// Optimization to move a predecessor that only has this block as successor
// just before this block. Skip this optimization if the previous block is
// not part of the same function, as we might have to backtrack on inlining
// failures.
if (block->numPredecessors() == 1 &&
block->getPredecessor(0)->numSuccessors() == 1 &&
!block->getPredecessor(0)->outerResumePoint()) {
graph().removeBlockFromList(block->getPredecessor(0));
graph().addBlock(block->getPredecessor(0));
}
MOZ_TRY(setCurrentAndSpecializePhis(block));
graph().addBlock(block);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_goto(bool* restarted) {
MOZ_ASSERT(JSOp(*pc) == JSOp::Goto);
if (IsBackedgePC(pc)) {
return visitBackEdge(restarted);
}
jsbytecode* target = pc + GET_JUMP_OFFSET(pc);
return visitGoto(target);
}
AbortReasonOr<Ok> IonBuilder::addPendingEdge(const PendingEdge& edge,
jsbytecode* target) {
PendingEdgesMap::AddPtr p = pendingEdges_.lookupForAdd(target);
if (p) {
if (!p->value().append(edge)) {
return abort(AbortReason::Alloc);
}
return Ok();
}
PendingEdges edges;
static_assert(PendingEdges::InlineLength >= 1,
"Appending one element should be infallible");
MOZ_ALWAYS_TRUE(edges.append(edge));
if (!pendingEdges_.add(p, target, std::move(edges))) {
return abort(AbortReason::Alloc);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitGoto(jsbytecode* target) {
current->end(MGoto::New(alloc(), nullptr));
MOZ_TRY(addPendingEdge(PendingEdge::NewGoto(current), target));
setTerminatedBlock();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_loophead() {
// All loops have the following bytecode structure:
//
// LoopHead
// ...
// IfNe/Goto to LoopHead
MOZ_ASSERT(JSOp(*pc) == JSOp::LoopHead);
if (hasTerminatedBlock()) {
// The whole loop is unreachable.
return Ok();
}
bool osr = pc == info().osrPc();
if (osr) {
MBasicBlock* preheader;
MOZ_TRY_VAR(preheader, newOsrPreheader(current, pc));
current->end(MGoto::New(alloc(), preheader));
MOZ_TRY(setCurrentAndSpecializePhis(preheader));
}
loopDepth_++;
MBasicBlock* header;
MOZ_TRY_VAR(header, newPendingLoopHeader(current, pc, osr));
current->end(MGoto::New(alloc(), header));
if (!loopStack_.emplaceBack(header)) {
return abort(AbortReason::Alloc);
}
MOZ_TRY(analyzeNewLoopTypes(header));
MOZ_TRY(startTraversingBlock(header));
return emitLoopHeadInstructions(pc);
}
AbortReasonOr<Ok> IonBuilder::visitBackEdge(bool* restarted) {
MOZ_ASSERT(loopDepth_ > 0);
loopDepth_--;
MBasicBlock* header = loopStack_.back().header();
current->end(MGoto::New(alloc(), header));
// Compute phis in the loop header and propagate them throughout the loop,
// including the successor.
AbortReason r = header->setBackedge(alloc(), current);
switch (r) {
case AbortReason::NoAbort:
loopStack_.popBack();
setTerminatedBlock();
return Ok();
case 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.
*restarted = true;
MOZ_TRY(restartLoop(header));
return Ok();
default:
return abort(r);
}
}
AbortReasonOr<Ok> IonBuilder::emitLoopHeadInstructions(jsbytecode* pc) {
MOZ_ASSERT(JSOp(*pc) == JSOp::LoopHead);
MInterruptCheck* check = MInterruptCheck::New(alloc());
current->add(check);
insertRecompileCheck(pc);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::inspectOpcode(JSOp op, bool* restarted) {
// Add not yet implemented opcodes at the bottom of the switch!
switch (op) {
case JSOp::NopDestructuring:
case JSOp::Lineno:
case JSOp::Nop:
return Ok();
case JSOp::TryDestructuring:
// Set the hasTryBlock flag to turn off optimizations that eliminate dead
// resume points operands because the exception handler code for
// TryNoteKind::Destructuring is effectively a (specialized) catch-block.
graph().setHasTryBlock();
return Ok();
case JSOp::LoopHead:
return jsop_loophead();
case JSOp::Undefined:
// If this ever changes, change what JSOp::GImplicitThis does too.
pushConstant(UndefinedValue());
return Ok();
case JSOp::Try:
return visitTry();
case JSOp::Default:
current->pop();
return visitGoto(pc + GET_JUMP_OFFSET(pc));
case JSOp::Goto:
return jsop_goto(restarted);
case JSOp::IfNe:
case JSOp::IfEq:
case JSOp::And:
case JSOp::Or:
case JSOp::Case:
return visitTest(op, restarted);
case JSOp::Coalesce:
return jsop_coalesce();
case JSOp::Return:
case JSOp::RetRval:
return visitReturn(op);
case JSOp::Throw:
return visitThrow();
case JSOp::JumpTarget:
return visitJumpTarget(op);
case JSOp::TableSwitch:
return visitTableSwitch();
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::ToNumeric:
return jsop_tonumeric();
case JSOp::Neg:
return jsop_neg();
case JSOp::Inc:
case JSOp::Dec:
return jsop_inc_or_dec(op);
case JSOp::ToString:
return jsop_tostring();
case JSOp::DefVar:
return jsop_defvar();
case JSOp::DefLet:
case JSOp::DefConst:
return jsop_deflexical();
case JSOp::DefFun:
return jsop_deffun();
case JSOp::CheckGlobalOrEvalDecl:
return jsop_checkGlobalOrEvalDecl();
case JSOp::Eq:
case JSOp::Ne:
case JSOp::StrictEq:
case JSOp::StrictNe:
case JSOp::Lt:
case JSOp::Le:
case JSOp::Gt:
case JSOp::Ge: