#include "jit/IonBuilder.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ScopeExit.h"
#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/IonControlFlow.h"
#include "jit/IonOptimizationLevels.h"
#include "jit/JitSpewer.h"
#include "jit/Lowering.h"
#include "jit/MIRGraph.h"
#include "vm/ArgumentsObject.h"
#include "vm/EnvironmentObject.h"
#include "vm/Opcodes.h"
#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/BytecodeUtil-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h"
#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::AssertedCast;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::Nothing;
using JS::TrackedOutcome;
using JS::TrackedStrategy;
using JS::TrackedTypeSite;
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) {
MOZ_ASSERT(frame);
BaselineFrameInspector* inspector =
temp->lifoAlloc()->new_<BaselineFrameInspector>(temp);
if (!inspector) {
return nullptr;
}
if (frame->isFunctionFrame()) {
inspector->thisType =
TypeSet::GetMaybeUntrackedValueType(frame->thisArgument());
}
if (frame->environmentChain()->isSingleton()) {
inspector->singletonEnvChain = frame->environmentChain();
}
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->numValueSlots())) {
return nullptr;
}
for (size_t i = 0; i < frame->numValueSlots(); i++) {
TypeSet::Type type =
TypeSet::GetMaybeUntrackedValueType(*frame->valueSlot(i));
inspector->varTypes.infallibleAppend(type);
}
return inspector;
}
IonBuilder::IonBuilder(JSContext* analysisContext, CompileRealm* realm,
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(realm, options, temp, graph, info, optimizationInfo),
backgroundCodegen_(nullptr),
actionableAbortScript_(nullptr),
actionableAbortPc_(nullptr),
actionableAbortMessage_(nullptr),
rootList_(nullptr),
analysisContext(analysisContext),
baselineFrame_(baselineFrame),
constraints_(constraints),
thisTypes(nullptr),
argTypes(nullptr),
typeArray(nullptr),
typeArrayHint(0),
bytecodeTypeMap(nullptr),
current(nullptr),
loopDepth_(loopDepth),
blockWorklist(*temp),
cfgCurrent(nullptr),
cfg(nullptr),
trackedOptimizationSites_(*temp),
lexicalCheck_(nullptr),
callerResumePoint_(nullptr),
callerBuilder_(nullptr),
iterators_(*temp),
loopHeaders_(*temp),
loopHeaderStack_(*temp),
#ifdef DEBUG
cfgLoopHeaderStack_(*temp),
#endif
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();
scriptHasIonScript_ = script_->hasIonScript();
pc = info->startPC();
MOZ_ASSERT(script()->hasBaselineScript() ==
(info->analysisMode() != Analysis_ArgumentsUsage));
MOZ_ASSERT(!!analysisContext ==
(info->analysisMode() == Analysis_DefiniteProperties));
MOZ_ASSERT(script_->numBytecodeTypeSets() < JSScript::MaxBytecodeTypeSets);
if (!info->isAnalysis()) {
script()->baselineScript()->setIonCompiledOrInlined();
}
}
void IonBuilder::clearForBackEnd() {
MOZ_ASSERT(!analysisContext);
baselineFrame_ = nullptr;
}
mozilla::GenericErrorResult<AbortReason> IonBuilder::abort(AbortReason r) {
auto res = this->MIRGenerator::abort(r);
#ifdef DEBUG
JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(),
PCToLineNumber(script(), pc));
#else
JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename());
#endif
return res;
}
mozilla::GenericErrorResult<AbortReason> IonBuilder::abort(AbortReason r,
const char* message,
...) {
va_list ap;
va_start(ap, message);
auto res = this->MIRGenerator::abortFmt(r, 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
trackActionableAbort(message);
return res;
}
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) {
#ifdef DEBUG
JitSpew(JitSpew_IonMIR, "%s @ %s:%d", 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());
}
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;
}
bool IonBuilder::hasCommonInliningPath(const JSScript* scriptToInline) {
for (IonBuilder* it = this->callerBuilder_; it; it = it->callerBuilder_) {
if (it->script() != scriptToInline) {
continue;
}
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 (target->realm() != script()->realm()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineCrossRealm);
return DontInline(nullptr, "Cross-realm call");
}
if (info().analysisMode() != Analysis_DefiniteProperties) {
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");
}
}
}
if (target->isInterpreted() &&
info().analysisMode() == Analysis_DefiniteProperties) {
RootedFunction fun(analysisContext, target);
RootedScript script(analysisContext,
JSFunction::getOrCreateScript(analysisContext, fun));
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");
}
if (!CanIonInlineScript(inlineScript)) {
trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon);
return DontInline(inlineScript, "Disabled Ion compilation");
}
if (!inlineScript->hasBaselineScript()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline);
return DontInline(inlineScript, "No baseline jitcode");
}
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())) {
trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs);
return DontInline(inlineScript, "Too many args");
}
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");
}
return InliningDecision_Inline;
}
AbortReasonOr<Ok> IonBuilder::analyzeNewLoopTypes(
const CFGBlock* loopEntryBlock) {
CFGLoopEntry* loopEntry = loopEntryBlock->stopIns()->toLoopEntry();
CFGBlock* cfgBlock = loopEntry->successor();
MBasicBlock* entry = blockWorklist[cfgBlock->id()];
MOZ_ASSERT(!entry->isDead());
bool foundEntry = false;
for (size_t i = 0; i < loopHeaders_.length(); i++) {
if (loopHeaders_[i].pc == cfgBlock->startPc()) {
MBasicBlock* oldEntry = loopHeaders_[i].header;
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);
}
}
loopHeaders_[i].header = entry;
return Ok();
}
}
if (!foundEntry) {
if (!loopHeaders_.append(LoopHeader(cfgBlock->startPc(), entry))) {
return abort(AbortReason::Alloc);
}
}
if (loopEntry->isForIn()) {
MPhi* phi = entry->getSlot(entry->stackDepth() - 1)->toPhi();
MOZ_ASSERT(phi->getOperand(0)->type() == MIRType::Undefined);
if (!phi->addBackedgeType(alloc(), MIRType::Value, nullptr)) {
return abort(AbortReason::Alloc);
}
}
jsbytecode* start = loopEntryBlock->stopPc();
start += GetBytecodeLength(start);
jsbytecode* end = loopEntry->loopStopPc();
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 (!last) {
continue;
}
MPhi* phi = entry->getSlot(slot)->toPhi();
if (*last == JSOP_POS || *last == JSOP_TONUMERIC) {
last = earlier;
}
if (CodeSpec[*last].format & JOF_TYPESET) {
TemporaryTypeSet* typeSet = bytecodeTypes(last);
if (!typeSet->empty()) {
MIRType type = typeSet->getKnownMIRType();
if (!phi->addBackedgeType(alloc(), type, typeSet)) {
return abort(AbortReason::Alloc);
}
}
} 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(alloc(), otherPhi->type(),
otherPhi->resultTypeSet())) {
return abort(AbortReason::Alloc);
}
}
}
} 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_RESUMEINDEX:
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:
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);
break;
case JSOP_BIGINT:
type = MIRType::BigInt;
break;
default:
break;
}
if (type != MIRType::None) {
if (!phi->addBackedgeType(alloc(), type, nullptr)) {
return abort(AbortReason::Alloc);
}
}
}
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::init() {
{
LifoAlloc::AutoFallibleScope fallibleAllocator(alloc().lifoAlloc());
if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes,
&argTypes, &typeArray)) {
return abort(AbortReason::Alloc);
}
}
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
if (inlineCallInfo_) {
thisTypes = inlineCallInfo_->thisArg()->resultTypeSet();
argTypes = nullptr;
}
bytecodeTypeMap = script()->types()->bytecodeTypeMap();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::build() {
#ifdef JS_STRUCTURED_SPEW
if (!info().isAnalysis()) {
JitSpewBaselineICStats(script(), "To-Be-Compiled");
}
#endif
MOZ_TRY(init());
if (script()->hasBaselineScript() && isHighestOptimizationLevel()) {
script()->baselineScript()->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();
MInstruction* env = MConstant::New(alloc(), UndefinedValue());
current->add(env);
current->initSlot(info().environmentChainSlot(), env);
MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
current->add(returnValue);
current->initSlot(info().returnValueSlot(), returnValue);
if (info().hasArguments()) {
MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
current->add(argsObj);
current->initSlot(info().argsObjSlot(), argsObj);
}
current->add(MStart::New(alloc()));
MCheckOverRecursed* check = MCheckOverRecursed::New(alloc());
current->add(check);
MResumePoint* entryRpCopy =
MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy) {
return abort(AbortReason::Alloc);
}
check->setResumePoint(entryRpCopy);
MOZ_TRY(rewriteParameters());
if (!info().funMaybeLazy() && !info().module() &&
script()->bodyScope()->is<GlobalScope>() &&
script()->bodyScope()->as<GlobalScope>().hasBindings()) {
MGlobalNameConflictsCheck* redeclCheck =
MGlobalNameConflictsCheck::New(alloc());
current->add(redeclCheck);
MResumePoint* entryRpCopy =
MResumePoint::Copy(alloc(), current->entryResumePoint());
if (!entryRpCopy) {
return abort(AbortReason::Alloc);
}
redeclCheck->setResumePoint(entryRpCopy);
}
MOZ_TRY(initEnvironmentChain());
if (info().needsArgsObj()) {
initArgumentsObject();
}
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
if (info().hasArguments() && !info().argsObjAliasesFormals()) {
hasLazyArguments_ = true;
}
#endif
insertRecompileCheck();
auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
replaceMaybeFallbackFunctionGetter(nullptr);
});
MOZ_TRY(traverseBytecode());
if (isHighestOptimizationLevel() && script_->hasBaselineScript() &&
inlinedBytecodeLength_ >
script_->baselineScript()->inlinedBytecodeLength()) {
script_->baselineScript()->setInlinedBytecodeLength(inlinedBytecodeLength_);
}
MOZ_TRY(maybeAddOsrTypeBarriers());
MOZ_TRY(processIterators());
if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) {
return abort(AbortReason::PreliminaryObjects);
}
MOZ_ASSERT(loopDepth_ == 0);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::processIterators() {
Vector<MDefinition*, 8, SystemAllocPolicy> worklist;
for (size_t i = 0; i < iterators_.length(); i++) {
MDefinition* iter = iterators_[i];
if (!iter->isInWorklist()) {
if (!worklist.append(iter)) {
return abort(AbortReason::Alloc);
}
iter->setInWorklist();
}
}
while (!worklist.empty()) {
MDefinition* def = worklist.popCopy();
def->setNotInWorklist();
if (def->isPhi()) {
MPhi* phi = def->toPhi();
phi->setIterator();
phi->setImplicitlyUsedUnchecked();
}
for (MUseDefIterator iter(def); iter; iter++) {
MDefinition* use = iter.def();
if (!use->isInWorklist() &&
(!use->isPhi() || !use->toPhi()->isIterator())) {
if (!worklist.append(use)) {
return abort(AbortReason::Alloc);
}
use->setInWorklist();
}
}
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::buildInline(IonBuilder* callerBuilder,
MResumePoint* callerResumePoint,
CallInfo& callInfo) {
inlineCallInfo_ = &callInfo;
#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;
}
safeForMinorGC_ = callerBuilder->safeForMinorGC_;
MBasicBlock* entry;
MOZ_TRY_VAR(entry, newBlock(info().firstStackSlot(), pc));
MOZ_TRY(setCurrentAndSpecializePhis(entry));
current->setCallerResumePoint(callerResumePoint);
MBasicBlock* predecessor = callerBuilder->current;
MOZ_ASSERT(predecessor == callerResumePoint->block());
predecessor->end(MGoto::New(alloc(), current));
if (!current->addPredecessorWithoutPhis(predecessor)) {
return abort(AbortReason::Alloc);
}
MInstruction* env = MConstant::New(alloc(), UndefinedValue());
current->add(env);
current->initSlot(info().environmentChainSlot(), env);
MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
current->add(returnValue);
current->initSlot(info().returnValueSlot(), returnValue);
if (info().hasArguments()) {
MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
current->add(argsObj);
current->initSlot(info().argsObjSlot(), argsObj);
}
current->initSlot(info().thisSlot(), callInfo.thisArg());
JitSpew(JitSpew_Inlining, "Initializing %u arg slots", info().nargs());
MOZ_ASSERT(!info().needsArgsObj());
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);
}
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());
MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots());
#ifdef DEBUG
if (script_->argumentsHasVarBinding()) {
hasLazyArguments_ = true;
}
#endif
insertRecompileCheck();
if (script()->trackRecordReplayProgress()) {
MInterruptCheck* check = MInterruptCheck::New(alloc());
check->setTrackRecordReplayProgress();
current->add(check);
}
MOZ_TRY(initEnvironmentChain(callInfo.fun()));
auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
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;
}
current->rewriteSlot(slotIdx, actual);
}
AbortReasonOr<Ok> IonBuilder::rewriteParameters() {
MOZ_ASSERT(info().environmentChainSlot() == 0);
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 (!info().funMaybeLazy()) {
return Ok();
}
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().fallible(), i, types);
if (!param) {
return abort(AbortReason::Alloc);
}
current->add(param);
current->initSlot(info().argSlotUnchecked(i), param);
}
return Ok();
}
void IonBuilder::initLocals() {
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() {
if (info().analysisMode() == Analysis_ArgumentsUsage) {
return true;
}
return script()->baselineScript()->usesEnvironmentChain();
}
AbortReasonOr<Ok> IonBuilder::initEnvironmentChain(MDefinition* callee) {
MInstruction* env = nullptr;
if (!info().needsArgsObj() && !usesEnvironmentChain()) {
return Ok();
}
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);
if (fun->needsSomeEnvironmentObject() &&
info().analysisMode() != Analysis_ArgumentsUsage) {
if (fun->needsNamedLambdaEnvironment()) {
env = createNamedLambdaObject(callee, env);
}
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()) {
env = constant(ObjectValue(module->initialEnvironment()));
} else {
MOZ_ASSERT(!script()->isForEval());
MOZ_ASSERT(!script()->hasNonSyntacticScope());
env = constant(ObjectValue(script()->global().lexicalEnvironment()));
}
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();
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 (type == MIRType::Value) {
type = barrier->type();
}
} else if (type == MIRType::Null || type == MIRType::Undefined ||
type == MIRType::MagicOptimizedArguments) {
TypeSet::Type ntype = TypeSet::PrimitiveType(ValueTypeFromMIRType(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;
}
switch (type) {
case MIRType::Null:
case MIRType::Undefined:
case MIRType::MagicOptimizedArguments:
def->setImplicitlyUsed();
break;
default:
break;
}
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::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:
break;
}
MOZ_ASSERT(def == osrBlock->getSlot(slot));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::maybeAddOsrTypeBarriers() {
if (!info().osrPc()) {
return Ok();
}
MBasicBlock* osrBlock = graph().osrBlock();
if (!osrBlock) {
MOZ_ASSERT(graph().hasTryBlock());
return abort(AbortReason::Disable,
"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++) {
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();
}
enum class CFGState : uint32_t { Alloc = 0, Abort = 1, Success = 2 };
static CFGState GetOrCreateControlFlowGraph(TempAllocator& tempAlloc,
JSScript* script,
const ControlFlowGraph** cfgOut) {
if (script->hasBaselineScript() &&
script->baselineScript()->controlFlowGraph()) {
*cfgOut = script->baselineScript()->controlFlowGraph();
return CFGState::Success;
}
ControlFlowGenerator cfgenerator(tempAlloc, script);
if (!cfgenerator.traverseBytecode()) {
if (cfgenerator.aborted()) {
return CFGState::Abort;
}
return CFGState::Alloc;
}
TempAllocator* graphAlloc = nullptr;
if (script->hasBaselineScript()) {
LifoAlloc& lifoAlloc = script->zone()->jitZone()->cfgSpace()->lifoAlloc();
LifoAlloc::AutoFallibleScope fallibleAllocator(&lifoAlloc);
graphAlloc = lifoAlloc.new_<TempAllocator>(&lifoAlloc);
if (!graphAlloc) {
return CFGState::Alloc;
}
} else {
graphAlloc = &tempAlloc;
}
ControlFlowGraph* cfg = cfgenerator.getGraph(*graphAlloc);
if (!cfg) {
return CFGState::Alloc;
}
if (script->hasBaselineScript()) {
MOZ_ASSERT(!script->baselineScript()->controlFlowGraph());
script->baselineScript()->setControlFlowGraph(cfg);
}
if (JitSpewEnabled(JitSpew_CFG)) {
JitSpew(JitSpew_CFG, "Generating graph for %s:%u:%u", script->filename(),
script->lineno(), script->column());
Fprinter& print = JitSpewPrinter();
cfg->dump(print, script);
}
*cfgOut = cfg;
return CFGState::Success;
}
AbortReasonOr<Ok> IonBuilder::traverseBytecode() {
CFGState state = GetOrCreateControlFlowGraph(alloc(), info().script(), &cfg);
MOZ_ASSERT_IF(cfg && info().script()->hasBaselineScript(),
info().script()->baselineScript()->controlFlowGraph() == cfg);
if (state == CFGState::Alloc) {
return abort(AbortReason::Alloc);
}
if (state == CFGState::Abort) {
return abort(AbortReason::Disable, "Couldn't create the CFG of script");
}
if (!blockWorklist.growBy(cfg->numBlocks())) {
return abort(AbortReason::Alloc);
}
blockWorklist[0] = current;
size_t i = 0;
while (i < cfg->numBlocks()) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
bool restarted = false;
const CFGBlock* cfgblock = cfg->block(i);
MBasicBlock* mblock = blockWorklist[i];
MOZ_ASSERT(mblock && !mblock->isDead());
MOZ_TRY(visitBlock(cfgblock, mblock));
MOZ_TRY(visitControlInstruction(cfgblock->stopIns(), &restarted));
if (restarted) {
while (!blockWorklist[i] || blockWorklist[i]->isDead()) {
MOZ_ASSERT(i > 0);
i--;
}
MOZ_ASSERT(cfgblock->stopIns()->isBackEdge());
MOZ_ASSERT(loopHeaderStack_.back() == blockWorklist[i]);
} else {
i++;
}
}
#ifdef DEBUG
MOZ_ASSERT(graph().numBlocks() >= blockWorklist.length());
for (i = 0; i < cfg->numBlocks(); i++) {
MOZ_ASSERT(blockWorklist[i]);
MOZ_ASSERT(!blockWorklist[i]->isDead());
MOZ_ASSERT_IF(i != 0, blockWorklist[i]->id() != 0);
}
#endif
cfg = nullptr;
blockWorklist.clear();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitBlock(const CFGBlock* cfgblock,
MBasicBlock* mblock) {
mblock->setLoopDepth(loopDepth_);
cfgCurrent = cfgblock;
pc = cfgblock->startPc();
if (mblock->pc() && script()->hasScriptCounts()) {
mblock->setHitCount(script()->getHitCount(mblock->pc()));
}
if (mblock->numPredecessors() == 1 &&
mblock->getPredecessor(0)->numSuccessors() == 1 &&
!mblock->getPredecessor(0)->outerResumePoint()) {
graph().removeBlockFromList(mblock->getPredecessor(0));
graph().addBlock(mblock->getPredecessor(0));
}
MOZ_TRY(setCurrentAndSpecializePhis(mblock));
graph().addBlock(mblock);
while (pc < cfgblock->stopPc()) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
#ifdef DEBUG
Vector<MDefinition*, 4, JitAllocPolicy> popped(alloc());
Vector<size_t, 4, JitAllocPolicy> poppedUses(alloc());
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 abort(AbortReason::Alloc);
}
}
#endif
JSOp op = JSOp(*pc);
MOZ_TRY(inspectOpcode(op));
#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_UNPICK:
case JSOP_SWAP:
case JSOP_SETARG:
case JSOP_SETLOCAL:
case JSOP_INITLEXICAL:
case JSOP_SETRVAL:
case JSOP_VOID:
break;
case JSOP_POS:
case JSOP_TONUMERIC:
case JSOP_TOID:
case JSOP_TOSTRING:
MOZ_ASSERT(i == 0);
if (current->peek(-1) == popped[0]) {
break;
}
MOZ_FALLTHROUGH;
default:
MOZ_ASSERT(popped[i]->isImplicitlyUsed() ||
popped[i]->isNewDerivedTypedObject() ||
popped[i]->defUseCount() > poppedUses[i]);
break;
}
}
#endif
pc += CodeSpec[op].length;
current->updateTrackedSite(bytecodeSite(pc));
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitGoto(CFGGoto* ins) {
return emitGoto(ins->successor(), ins->popAmount());
}
AbortReasonOr<Ok> IonBuilder::emitGoto(CFGBlock* successor, size_t popAmount) {
size_t id = successor->id();
bool create = !blockWorklist[id] || blockWorklist[id]->isDead();
current->popn(popAmount);
if (create) {
MOZ_TRY_VAR(blockWorklist[id], newBlock(current, successor->startPc()));
}
MBasicBlock* succ = blockWorklist[id];
current->end(MGoto::New(alloc(), succ));
if (!create) {
if (!succ->addPredecessor(alloc(), current)) {
return abort(AbortReason::Alloc);
}
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitBackEdge(CFGBackEdge* ins, bool* restarted) {
loopDepth_--;
MBasicBlock* loopEntry = blockWorklist[ins->getSuccessor(0)->id()];
current->end(MGoto::New(alloc(), loopEntry));
MOZ_ASSERT(ins->getSuccessor(0) == cfgLoopHeaderStack_.back());
AbortReason r = loopEntry->setBackedge(alloc(), current);
switch (r) {
case AbortReason::NoAbort:
loopHeaderStack_.popBack();
#ifdef DEBUG
cfgLoopHeaderStack_.popBack();
#endif
return Ok();
case AbortReason::Disable:
*restarted = true;
MOZ_TRY(restartLoop(ins->getSuccessor(0)));
return Ok();
default:
return abort(r);
}
}
AbortReasonOr<Ok> IonBuilder::visitLoopEntry(CFGLoopEntry* loopEntry) {
unsigned stackPhiCount = loopEntry->stackPhiCount();
const CFGBlock* successor = loopEntry->getSuccessor(0);
bool osr = successor->startPc() == info().osrPc();
if (osr) {
MOZ_ASSERT(loopEntry->canOsr());
MBasicBlock* preheader;
MOZ_TRY_VAR(preheader, newOsrPreheader(current, successor->startPc(), pc));
current->end(MGoto::New(alloc(), preheader));
MOZ_TRY(setCurrentAndSpecializePhis(preheader));
}
if (loopEntry->isBrokenLoop()) {
return emitGoto(loopEntry->successor(), 0);
}
loopDepth_++;
MBasicBlock* header;
MOZ_TRY_VAR(header, newPendingLoopHeader(current, successor->startPc(), osr,
loopEntry->canOsr(), stackPhiCount));
blockWorklist[successor->id()] = header;
current->end(MGoto::New(alloc(), header));
if (!loopHeaderStack_.append(header)) {
return abort(AbortReason::Alloc);
}
#ifdef DEBUG
if (!cfgLoopHeaderStack_.append(successor)) {
return abort(AbortReason::Alloc);
}
#endif
MOZ_TRY(analyzeNewLoopTypes(cfgCurrent));
setCurrent(header);
pc = header->pc();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_loopentry() {
MOZ_ASSERT(*pc == JSOP_LOOPENTRY);
MInterruptCheck* check = MInterruptCheck::New(alloc());
current->add(check);
insertRecompileCheck();
if (script()->trackRecordReplayProgress()) {
check->setTrackRecordReplayProgress();
MOZ_TRY(resumeAfter(check));
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitControlInstruction(
CFGControlInstruction* ins, bool* restarted) {
switch (ins->type()) {
case CFGControlInstruction::Type_Test:
return visitTest(ins->toTest());
case CFGControlInstruction::Type_CondSwitchCase:
return visitCondSwitchCase(ins->toCondSwitchCase());
case CFGControlInstruction::Type_Goto:
return visitGoto(ins->toGoto());
case CFGControlInstruction::Type_BackEdge:
return visitBackEdge(ins->toBackEdge(), restarted);
case CFGControlInstruction::Type_LoopEntry:
return visitLoopEntry(ins->toLoopEntry());
case CFGControlInstruction::Type_Return:
case CFGControlInstruction::Type_RetRVal:
return visitReturn(ins);
case CFGControlInstruction::Type_Try:
return visitTry(ins->toTry());
case CFGControlInstruction::Type_Throw:
return visitThrow(ins->toThrow());
case CFGControlInstruction::Type_TableSwitch:
return visitTableSwitch(ins->toTableSwitch());
}
MOZ_CRASH("Unknown Control Instruction");
}
AbortReasonOr<Ok> IonBuilder::inspectOpcode(JSOp op) {
switch (op) {
case JSOP_NOP:
case JSOP_NOP_DESTRUCTURING:
case JSOP_TRY_DESTRUCTURING:
case JSOP_LINENO:
case JSOP_JUMPTARGET:
case JSOP_LABEL:
return Ok();
case JSOP_UNDEFINED:
pushConstant(UndefinedValue());
return Ok();
case JSOP_IFNE:
case JSOP_IFEQ:
case JSOP_RETURN:
case JSOP_RETRVAL:
case JSOP_AND:
case JSOP_OR:
case JSOP_TRY:
case JSOP_THROW:
case JSOP_GOTO:
case JSOP_CONDSWITCH:
case JSOP_TABLESWITCH:
case JSOP_CASE:
case JSOP_DEFAULT:
MOZ_CRASH("Shouldn't encounter this opcode.");
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_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:
case JSOP_BIGINT:
pushConstant(info().getConst(pc));
return Ok();
case JSOP_STRING:
pushConstant(StringValue(info().getAtom(pc)));
return Ok();
case JSOP_SYMBOL: {
unsigned which = GET_UINT8(pc);
JS::Symbol* sym = realm->runtime()->wellKnownSymbols().get(which);
pushConstant(SymbolValue(sym));
return Ok();
}
case JSOP_ZERO:
pushConstant(Int32Value(0));
return Ok();
case JSOP_ONE:
pushConstant(Int32Value(1));
return Ok();
case JSOP_NULL:
pushConstant(NullValue());
return Ok();
case JSOP_VOID:
current->pop();
pushConstant(UndefinedValue());
return Ok();
case JSOP_HOLE:
pushConstant(MagicValue(JS_ELEMENTS_HOLE));
return Ok();
case JSOP_FALSE:
pushConstant(BooleanValue(false));
return Ok();
case JSOP_TRUE:
pushConstant(BooleanValue(true));
return Ok();
case JSOP_ARGUMENTS:
return jsop_arguments();
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 Ok();
case JSOP_SETARG:
return jsop_setarg(GET_ARGNO(pc));
case JSOP_GETLOCAL:
current->pushLocal(GET_LOCALNO(pc));
return Ok();
case JSOP_SETLOCAL:
current->setLocal(GET_LOCALNO(pc));
return Ok();
case JSOP_THROWSETCONST:
case JSOP_THROWSETALIASEDCONST:
case JSOP_THROWSETCALLEE:
return jsop_throwsetconst();
case JSOP_CHECKLEXICAL:
return jsop_checklexical();
case JSOP_INITLEXICAL:
current->setLocal(GET_LOCALNO(pc));
return Ok();
case JSOP_INITGLEXICAL: {
MOZ_ASSERT(!script()->hasNonSyntacticScope());
MDefinition* value = current->pop();
current->push(
constant(ObjectValue(script()->global().lexicalEnvironment())));
current->push(value);
return jsop_setprop(info().getAtom(pc)->asPropertyName());
}
case JSOP_CHECKALIASEDLEXICAL:
return jsop_checkaliasedlexical(EnvironmentCoordinate(pc));
case JSOP_INITALIASEDLEXICAL:
return jsop_setaliasedvar(EnvironmentCoordinate(pc));
case JSOP_UNINITIALIZED:
pushConstant(MagicValue(JS_UNINITIALIZED_LEXICAL));
return Ok();
case JSOP_POP: {
MDefinition* def = current->pop();
if (pc[JSOP_POP_LENGTH] == JSOP_POP) {
return Ok();
}
if (def->isConstant()) {
return Ok();
}
return maybeInsertResume();
}
case JSOP_POPN:
for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) {
current->pop();
}
return Ok();
case JSOP_DUPAT:
current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc));
return Ok();
case JSOP_NEWARRAY:
return jsop_newarray(GET_UINT32(pc));
case JSOP_NEWARRAY_COPYONWRITE:
return jsop_newarray_copyonwrite();
case JSOP_NEWINIT:
case JSOP_NEWOBJECT:
return jsop_newobject();
case JSOP_INITELEM:
case JSOP_INITHIDDENELEM:
return jsop_initelem();
case JSOP_INITELEM_INC:
return jsop_initelem_inc();
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_SPREADCALL:
return jsop_spreadcall();
case JSOP_CALL:
case JSOP_CALL_IGNORES_RV:
case JSOP_CALLITER:
case JSOP_NEW:
MOZ_TRY(jsop_call(GET_ARGC(pc),
(JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL,
(JSOp)*pc == JSOP_CALL_IGNORES_RV));
if (op == JSOP_CALLITER) {
if (!outermostBuilder()->iterators_.append(current->peek(-1))) {
return abort(AbortReason::Alloc);
}
}
return Ok();
case JSOP_EVAL:
case JSOP_STRICTEVAL:
return jsop_eval(GET_ARGC(pc));
case JSOP_INT8:
pushConstant(Int32Value(GET_INT8(pc)));
return Ok();
case JSOP_UINT16:
pushConstant(Int32Value(GET_UINT16(pc)));
return Ok();
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* env = testGlobalLexicalBinding(info().getName(pc))) {
pushConstant(ObjectValue(*env));
return Ok();
}
}
MOZ_FALLTHROUGH;
case JSOP_BINDNAME:
return jsop_bindname(info().getName(pc));
case JSOP_BINDVAR:
return jsop_bindvar();
case JSOP_DUP:
current->pushSlot(current->stackDepth() - 1);
return Ok();
case JSOP_DUP2:
return jsop_dup2();
case JSOP_SWAP:
current->swapAt(-1);
return Ok();
case JSOP_PICK:
current->pick(-GET_INT8(pc));
return Ok();
case JSOP_UNPICK:
current->unpick(-GET_INT8(pc));
return Ok();
case JSOP_GETALIASEDVAR:
return jsop_getaliasedvar(EnvironmentCoordinate(pc));
case JSOP_SETALIASEDVAR:
return jsop_setaliasedvar(EnvironmentCoordinate(pc));
case JSOP_UINT24:
case JSOP_RESUMEINDEX:
pushConstant(Int32Value(GET_UINT24(pc)));
return Ok();
case JSOP_INT32:
pushConstant(Int32Value(GET_INT32(pc)));
return Ok();
case JSOP_LOOPHEAD:
MOZ_CRASH("JSOP_LOOPHEAD outside loop");
case JSOP_GETELEM:
case JSOP_CALLELEM:
MOZ_TRY(jsop_getelem());
if (op == JSOP_CALLELEM) {
MOZ_TRY(improveThisTypesForCall());
}
return Ok();
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 Ok();
}
case JSOP_ENVCALLEE:
return jsop_envcallee();
case JSOP_SUPERBASE:
return jsop_superbase();
case JSOP_GETPROP_SUPER: {
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_getprop_super(name);
}
case JSOP_GETELEM_SUPER:
return jsop_getelem_super();
case JSOP_GETPROP:
case JSOP_CALLPROP: {
PropertyName* name = info().getAtom(pc)->asPropertyName();
MOZ_TRY(jsop_getprop(name));
if (op == JSOP_CALLPROP) {
MOZ_TRY(improveThisTypesForCall());
}
return Ok();
}
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:
pushConstant(ObjectValue(*info().getObject(pc)));
return Ok();
case JSOP_OBJECT:
return jsop_object(info().getObject(pc));
case JSOP_CLASSCONSTRUCTOR:
return jsop_classconstructor();
case JSOP_TYPEOF:
case JSOP_TYPEOFEXPR:
return jsop_typeof();
case JSOP_TOASYNCITER:
return jsop_toasynciter();
case JSOP_TOID:
return jsop_toid();
case JSOP_ITERNEXT:
return jsop_iternext();
case JSOP_LAMBDA:
return jsop_lambda(info().getFunction(pc));
case JSOP_LAMBDA_ARROW:
return jsop_lambda_arrow(info().getFunction(pc));
case JSOP_SETFUNNAME:
return jsop_setfunname(GET_UINT8(pc));
case JSOP_PUSHLEXICALENV:
return jsop_pushlexicalenv(GET_UINT32_INDEX(pc));
case JSOP_POPLEXICALENV:
current->setEnvironmentChain(walkEnvironmentChain(1));
return Ok();
case JSOP_FRESHENLEXICALENV:
return jsop_copylexicalenv(true);
case JSOP_RECREATELEXICALENV:
return jsop_copylexicalenv(false);
case JSOP_ITER:
return jsop_iter();
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_HASOWN:
return jsop_hasown();
case JSOP_SETRVAL:
MOZ_ASSERT(!script()->noScriptRval());
current->setSlot(info().returnValueSlot(), current->pop());
return Ok();
case JSOP_INSTANCEOF:
return jsop_instanceof();
case JSOP_DEBUGLEAVELEXICALENV:
return Ok();
case JSOP_DEBUGGER:
return jsop_debugger();
case JSOP_GIMPLICITTHIS:
if (!script()->hasNonSyntacticScope()) {
pushConstant(UndefinedValue());
return Ok();
}
MOZ_FALLTHROUGH;
case JSOP_IMPLICITTHIS: {
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_implicitthis(name);
}
case JSOP_NEWTARGET:
return jsop_newtarget();
case JSOP_CHECKISOBJ:
return jsop_checkisobj(GET_UINT8(pc));
case JSOP_CHECKISCALLABLE:
return jsop_checkiscallable(GET_UINT8(pc));
case JSOP_CHECKOBJCOERCIBLE:
return jsop_checkobjcoercible();
case JSOP_DEBUGCHECKSELFHOSTED: {
#ifdef DEBUG
MDebugCheckSelfHosted* check =
MDebugCheckSelfHosted::New(alloc(), current->pop());
current->add(check);
current->push(check);
MOZ_TRY(resumeAfter(check));
#endif
return Ok();
}
case JSOP_IS_CONSTRUCTING:
pushConstant(MagicValue(JS_IS_CONSTRUCTING));
return Ok();
case JSOP_OPTIMIZE_SPREADCALL:
return jsop_optimize_spreadcall();
case JSOP_IMPORTMETA:
return jsop_importmeta();
case JSOP_DYNAMIC_IMPORT:
return jsop_dynamic_import();
case JSOP_LOOPENTRY:
return jsop_loopentry();
case JSOP_ENTERWITH:
case JSOP_LEAVEWITH:
case JSOP_SPREADNEW:
case JSOP_SPREADEVAL:
case JSOP_STRICTSPREADEVAL:
case JSOP_CHECKCLASSHERITAGE:
case JSOP_FUNWITHPROTO:
case JSOP_OBJWITHPROTO:
case JSOP_BUILTINPROTO:
case JSOP_INITHOMEOBJECT:
case JSOP_DERIVEDCONSTRUCTOR:
case JSOP_CHECKTHIS:
case JSOP_CHECKRETURN:
case JSOP_CHECKTHISREINIT:
case JSOP_SETPROP_SUPER:
case JSOP_SETELEM_SUPER:
case JSOP_STRICTSETPROP_SUPER:
case JSOP_STRICTSETELEM_SUPER:
case JSOP_SUPERFUN:
case JSOP_SPREADSUPERCALL:
case JSOP_SUPERCALL:
case JSOP_PUSHVARENV:
case JSOP_POPVARENV:
case JSOP_GETBOUNDNAME:
case JSOP_EXCEPTION:
case JSOP_ISGENCLOSING:
case JSOP_INITIALYIELD:
case JSOP_YIELD:
case JSOP_FINALYIELDRVAL:
case JSOP_RESUME:
case JSOP_DEBUGAFTERYIELD:
case JSOP_AWAIT:
case JSOP_TRYSKIPAWAIT:
case JSOP_GENERATOR:
case JSOP_ASYNCAWAIT:
case JSOP_ASYNCRESOLVE:
case JSOP_DELNAME:
case JSOP_FINALLY:
case JSOP_GETRVAL:
case JSOP_GOSUB:
case JSOP_RETSUB:
case JSOP_SETINTRINSIC:
case JSOP_THROWMSG:
break;
case JSOP_FORCEINTERPRETER:
break;
case JSOP_UNUSED71:
case JSOP_UNUSED149:
case JSOP_LIMIT:
break;
}
trackActionableAbort("Unsupported bytecode");
#ifdef DEBUG
return abort(AbortReason::Disable, "Unsupported opcode: %s", CodeName[op]);
#else
return abort(AbortReason::Disable, "Unsupported opcode: %d", op);
#endif
}
AbortReasonOr<Ok> IonBuilder::restartLoop(const CFGBlock* cfgHeader) {
AutoTraceLog logCompile(traceLogger(), TraceLogger_IonBuilderRestartLoop);
spew("New types at loop header, restarting loop body");
if (JitOptions.limitScriptSize) {
if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS) {
return abort(AbortReason::Disable,
"Aborted while processing control flow");
}
}
MBasicBlock* header = blockWorklist[cfgHeader->id()];
replaceMaybeFallbackFunctionGetter(nullptr);
if (!graph().removeSuccessorBlocks(header)) {
return abort(AbortReason::Alloc);
}
graph().removeBlockFromList(header);
header->discardAllInstructions();
header->discardAllResumePoints( false);
header->setStackDepth(header->getPredecessor(0)->stackDepth());
loopDepth_ = header->loopDepth();
setCurrent(header);
pc = header->pc();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::replaceTypeSet(MDefinition* subject,
TemporaryTypeSet* type,
MTest* test) {
if (type->unknown()) {
return Ok();
}
if (subject->resultTypeSet()) {
if (subject->resultTypeSet()->equals(type)) {
return Ok();
}
} else {
TemporaryTypeSet oldTypes(alloc_->lifoAlloc(), subject->type());
if (oldTypes.equals(type)) {
return Ok();
}
}
MInstruction* replace = nullptr;
MDefinition* ins;
for (uint32_t i = 0; i < current->stackDepth(); i++) {
ins = current->getSlot(i);
if (ins->isFilterTypeSet() && ins->getOperand(0) == subject &&
ins->dependency() == test) {
TemporaryTypeSet* intersect = TypeSet::intersectSets(
ins->resultTypeSet(), type, alloc_->lifoAlloc());
if (!intersect) {
return abort(AbortReason::Alloc);
}
ins->toFilterTypeSet()->setResultType(intersect->getKnownMIRType());
ins->toFilterTypeSet()->setResultTypeSet(intersect);
if (ins->type() == MIRType::Undefined) {
current->setSlot(i, constant(UndefinedValue()));
} else if (ins->type() == MIRType::Null) {
current->setSlot(i, constant(NullValue()));
} else if (ins->type() == MIRType::MagicOptimizedArguments) {
current->setSlot(i, constant(MagicValue(JS_OPTIMIZED_ARGUMENTS)));
} else {
MOZ_ASSERT(!IsMagicType(ins->type()));
}
continue;
}
if (ins == subject) {
if (!replace) {
replace = MFilterTypeSet::New(alloc(), subject, type);
current->add(replace);
replace->setDependency(test);
if (replace->type() == MIRType::Undefined) {
replace = constant(UndefinedValue());
} else if (replace->type() == MIRType::Null) {
replace = constant(NullValue());
} else if (replace->type() == MIRType::MagicOptimizedArguments) {
replace = constant(MagicValue(JS_OPTIMIZED_ARGUMENTS));
} else {
MOZ_ASSERT(!IsMagicType(ins->type()));
}
}
current->setSlot(i, replace);
}
}
return Ok();
}
bool IonBuilder::detectAndOrStructure(MPhi* ins, bool* branchIsAnd) {
if (ins->numOperands() != 2) {
return false;
}
MBasicBlock* testBlock = ins->block();
MOZ_ASSERT(testBlock->numPredecessors() == 2);
MBasicBlock* initialBlock;
MBasicBlock* branchBlock;
if (testBlock->getPredecessor(0)->lastIns()->isTest()) {
initialBlock = testBlock->getPredecessor(0);
branchBlock = testBlock->getPredecessor(1);
} else if (testBlock->getPredecessor(1)->lastIns()->isTest()) {
initialBlock = testBlock->getPredecessor(1);
branchBlock = testBlock->getPredecessor(0);
} else {
return false;
}
if (branchBlock->numSuccessors() != 1) {
return false;
}
if (branchBlock->numPredecessors() != 1 ||
branchBlock->getPredecessor(0) != initialBlock) {
return false;
}
if (initialBlock->numSuccessors() != 2) {
return false;
}
MDefinition* branchResult =
ins->getOperand(testBlock->indexForPredecessor(branchBlock));
MDefinition* initialResult =
ins->getOperand(testBlock->indexForPredecessor(initialBlock));
if (branchBlock->stackDepth() != initialBlock->stackDepth()) {
return false;
}
if (branchBlock->stackDepth() != testBlock->stackDepth() + 1) {
return false;
}
if (branchResult != branchBlock->peek(-1) ||
initialResult != initialBlock->peek(-1)) {
return false;
}
MTest* initialTest = initialBlock->lastIns()->toTest();
bool branchIsTrue = branchBlock == initialTest->ifTrue();
if (initialTest->input() == ins->getOperand(0)) {
*branchIsAnd =
branchIsTrue != (testBlock->getPredecessor(0) == branchBlock);
} else if (initialTest->input() == ins->getOperand(1)) {
*branchIsAnd =
branchIsTrue != (testBlock->getPredecessor(1) == branchBlock);
} else {
return false;
}
return true;
}
AbortReasonOr<Ok> IonBuilder::improveTypesAtCompare(MCompare* ins,
bool trueBranch,
MTest* test) {
if (ins->compareType() == MCompare::Compare_Undefined ||
ins->compareType() == MCompare::Compare_Null) {
return improveTypesAtNullOrUndefinedCompare(ins, trueBranch, test);
}
if ((ins->lhs()->isTypeOf() || ins->rhs()->isTypeOf()) &&
(ins->lhs()->isConstant() || ins->rhs()->isConstant())) {
return improveTypesAtTypeOfCompare(ins, trueBranch, test);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::improveTypesAtTypeOfCompare(MCompare* ins,
bool trueBranch,
MTest* test) {
MTypeOf* typeOf =
ins->lhs()->isTypeOf() ? ins->lhs()->toTypeOf() : ins->rhs()->toTypeOf();
MConstant* constant = ins->lhs()->isConstant() ? ins->lhs()->toConstant()
: ins->rhs()->toConstant();
if (constant->type() != MIRType::String) {
return Ok();
}
bool equal = ins->jsop() == JSOP_EQ || ins->jsop() == JSOP_STRICTEQ;
bool notEqual = ins->jsop() == JSOP_NE || ins->jsop() == JSOP_STRICTNE;
if (notEqual) {
trueBranch = !trueBranch;
}
if (!equal && !notEqual) {
return Ok();
}
MDefinition* subject = typeOf->input();
TemporaryTypeSet* inputTypes = subject->resultTypeSet();
TemporaryTypeSet tmp;
if (!inputTypes) {
if (subject->type() == MIRType::Value) {
return Ok();
}
inputTypes = &tmp;
tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())),
alloc_->lifoAlloc());
}
if (inputTypes->unknown()) {
return Ok();
}
TemporaryTypeSet filter;
const JSAtomState& names = runtime->names();
if (constant->toString() == TypeName(JSTYPE_UNDEFINED, names)) {
filter.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
if (typeOf->inputMaybeCallableOrEmulatesUndefined() && trueBranch) {
filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
}
} else if (constant->toString() == TypeName(JSTYPE_BOOLEAN, names)) {
filter.addType(TypeSet::BooleanType(), alloc_->lifoAlloc());
} else if (constant->toString() == TypeName(JSTYPE_NUMBER, names)) {
filter.addType(TypeSet::Int32Type(), alloc_->lifoAlloc());
filter.addType(TypeSet::DoubleType(), alloc_->lifoAlloc());
} else if (constant->toString() == TypeName(JSTYPE_STRING, names)) {
filter.addType(TypeSet::StringType(), alloc_->lifoAlloc());
} else if (constant->toString() == TypeName(JSTYPE_SYMBOL, names)) {
filter.addType(TypeSet::SymbolType(), alloc_->lifoAlloc());
} else if (constant->toString() == TypeName(JSTYPE_BIGINT, names)) {
filter.addType(TypeSet::BigIntType(), alloc_->lifoAlloc());
} else if (constant->toString() == TypeName(JSTYPE_OBJECT, names)) {
filter.addType(TypeSet::NullType(), alloc_->lifoAlloc());
if (trueBranch) {
filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
}
} else if (constant->toString() == TypeName(JSTYPE_FUNCTION, names)) {
if (typeOf->inputMaybeCallableOrEmulatesUndefined() && trueBranch) {
filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
}
} else {
return Ok();
}
TemporaryTypeSet* type;
if (trueBranch) {
type = TypeSet::intersectSets(&filter, inputTypes, alloc_->lifoAlloc());
} else {
type = TypeSet::removeSet(inputTypes, &filter, alloc_->lifoAlloc());
}
if (!type) {
return abort(AbortReason::Alloc);
}
return replaceTypeSet(subject, type, test);
}
AbortReasonOr<Ok> IonBuilder::improveTypesAtNullOrUndefinedCompare(
MCompare* ins, bool trueBranch, MTest* test) {
MOZ_ASSERT(ins->compareType() == MCompare::Compare_Undefined ||
ins->compareType() == MCompare::Compare_Null);
bool altersUndefined, altersNull;
JSOp op = ins->jsop();
switch (op) {
case JSOP_STRICTNE:
case JSOP_STRICTEQ:
altersUndefined = ins->compareType() == MCompare::Compare_Undefined;
altersNull = ins->compareType() == MCompare::Compare_Null;
break;
case JSOP_NE:
case JSOP_EQ:
altersUndefined = altersNull = true;
break;
default:
MOZ_CRASH("Relational compares not supported");
}
MDefinition* subject = ins->lhs();
TemporaryTypeSet* inputTypes = subject->resultTypeSet();
MOZ_ASSERT(IsNullOrUndefined(ins->rhs()->type()));
TemporaryTypeSet tmp;
if (!inputTypes) {
if (subject->type() == MIRType::Value) {
return Ok();
}
inputTypes = &tmp;
tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())),
alloc_->lifoAlloc());
}
if (inputTypes->unknown()) {
return Ok();
}
TemporaryTypeSet* type;
if ((op == JSOP_STRICTEQ || op == JSOP_EQ) ^ trueBranch) {
TemporaryTypeSet remove;
if (altersUndefined) {
remove.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
}
if (altersNull) {
remove.addType(TypeSet::NullType(), alloc_->lifoAlloc());
}
type = TypeSet::removeSet(inputTypes, &remove, alloc_->lifoAlloc());
} else {
TemporaryTypeSet base;
if (altersUndefined) {
base.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
if (inputTypes->maybeEmulatesUndefined(constraints())) {
base.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
}
}
if (altersNull) {
base.addType(TypeSet::NullType(), alloc_->lifoAlloc());
}
type = TypeSet::intersectSets(&base, inputTypes, alloc_->lifoAlloc());
}
if (!type) {
return abort(AbortReason::Alloc);
}
return replaceTypeSet(subject, type, test);
}
AbortReasonOr<Ok> IonBuilder::improveTypesAtTest(MDefinition* ins,
bool trueBranch, MTest* test) {
switch (ins->op()) {
case MDefinition::Opcode::Not:
return improveTypesAtTest(ins->toNot()->getOperand(0), !trueBranch, test);
case MDefinition::Opcode::IsObject: {
MDefinition* subject = ins->getOperand(0);
TemporaryTypeSet* oldType = subject->resultTypeSet();
TemporaryTypeSet tmp;
if (!oldType) {
if (subject->type() == MIRType::Value) {
return Ok();
}
oldType = &tmp;
tmp.addType(
TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())),
alloc_->lifoAlloc());
}
if (oldType->unknown()) {
return Ok();
}
TemporaryTypeSet* type = nullptr;
if (trueBranch) {
type = oldType->cloneObjectsOnly(alloc_->lifoAlloc());
} else {
type = oldType->cloneWithoutObjects(alloc_->lifoAlloc());
}
if (!type) {
return abort(AbortReason::Alloc);
}
return replaceTypeSet(subject, type, test);
}
case MDefinition::Opcode::Phi: {
bool branchIsAnd = true;
if (!detectAndOrStructure(ins->toPhi(), &branchIsAnd)) {
break;
}
if (branchIsAnd) {
if (trueBranch) {
MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(0), true, test));
MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(1), true, test));
}
} else {
if (!trueBranch) {
MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(0), false, test));
MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(1), false, test));
}
}
return Ok();
}
case MDefinition::Opcode::Compare:
return improveTypesAtCompare(ins->toCompare(), trueBranch, test);
default:
break;
}
TemporaryTypeSet* oldType = ins->resultTypeSet();
TemporaryTypeSet* type;
TemporaryTypeSet tmp;
if (!oldType) {
if (ins->type() == MIRType::Value) {
return Ok();
}
oldType = &tmp;
tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(ins->type())),
alloc_->lifoAlloc());
}
if (oldType->unknown()) {
return Ok();
}
if (trueBranch) {
TemporaryTypeSet remove;
remove.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
remove.addType(TypeSet::NullType(), alloc_->lifoAlloc());
type = TypeSet::removeSet(oldType, &remove, alloc_->lifoAlloc());
} else {
TemporaryTypeSet base;
base.addType(TypeSet::UndefinedType(),
alloc_->lifoAlloc()); base.addType(TypeSet::NullType(),
alloc_->lifoAlloc()); base.addType(TypeSet::BooleanType(),
alloc_->lifoAlloc()); base.addType(TypeSet::Int32Type(),
alloc_->lifoAlloc()); base.addType(TypeSet::DoubleType(),
alloc_->lifoAlloc()); base.addType(TypeSet::StringType(),
alloc_->lifoAlloc()); base.addType(TypeSet::BigIntType(),
alloc_->lifoAlloc());
if (oldType->maybeEmulatesUndefined(constraints())) {
base.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
}
type = TypeSet::intersectSets(&base, oldType, alloc_->lifoAlloc());
}
if (!type) {
return abort(AbortReason::Alloc);
}
return replaceTypeSet(ins, type, test);
}
AbortReasonOr<Ok> IonBuilder::jsop_dup2() {
uint32_t lhsSlot = current->stackDepth() - 2;
uint32_t rhsSlot = current->stackDepth() - 1;
current->pushSlot(lhsSlot);
current->pushSlot(rhsSlot);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitTest(CFGTest* test) {
MDefinition* ins =
test->mustKeepCondition() ? current->peek(-1) : current->pop();
MBasicBlock* ifTrue;
MOZ_TRY_VAR(ifTrue, newBlock(current, test->trueBranch()->startPc()));
MBasicBlock* ifFalse;
MOZ_TRY_VAR(ifFalse, newBlock(current, test->falseBranch()->startPc()));
MTest* mir = newTest(ins, ifTrue, ifFalse);
current->end(mir);
MOZ_TRY(setCurrentAndSpecializePhis(ifTrue));
MOZ_TRY(improveTypesAtTest(mir->getOperand(0), true, mir));
blockWorklist[test->trueBranch()->id()] = ifTrue;
MBasicBlock* filterBlock = ifFalse;
ifFalse = nullptr;
graph().addBlock(filterBlock);
MOZ_TRY(setCurrentAndSpecializePhis(filterBlock));
MOZ_TRY(
improveTypesAtTest(mir->getOperand(0), false, mir));
MOZ_TRY_VAR(ifFalse, newBlock(filterBlock, test->falseBranch()->startPc()));
filterBlock->end(MGoto::New(alloc(), ifFalse));
if (filterBlock->pc() && script()->hasScriptCounts()) {
filterBlock->setHitCount(script()->getHitCount(filterBlock->pc()));
}
blockWorklist[test->falseBranch()->id()] = ifFalse;
current = nullptr;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitCondSwitchCase(
CFGCondSwitchCase* switchCase) {
MDefinition* cond = current->peek(-1);
MBasicBlock* ifTrue;
MOZ_TRY_VAR(ifTrue, newBlockPopN(current, switchCase->trueBranch()->startPc(),
switchCase->truePopAmount()));
MBasicBlock* ifFalse;
MOZ_TRY_VAR(ifFalse,
newBlockPopN(current, switchCase->falseBranch()->startPc(),
switchCase->falsePopAmount()));
blockWorklist[switchCase->trueBranch()->id()] = ifTrue;
blockWorklist[switchCase->falseBranch()->id()] = ifFalse;
MTest* mir = newTest(cond, ifTrue, ifFalse);
current->end(mir);
MOZ_TRY(setCurrentAndSpecializePhis(ifTrue));
MOZ_TRY(improveTypesAtTest(mir->getOperand(0), true, mir));
MOZ_TRY(setCurrentAndSpecializePhis(ifFalse));
MOZ_TRY(
improveTypesAtTest(mir->getOperand(0), false, mir));
current = nullptr;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitTry(CFGTry* try_) {
MOZ_ASSERT(!isInlineBuilder());
if (info().isAnalysis()) {
return abort(AbortReason::Disable, "Try-catch during analysis");
}
graph().setHasTryBlock();
MBasicBlock* tryBlock;
MOZ_TRY_VAR(tryBlock, newBlock(current, try_->tryBlock()->startPc()));
blockWorklist[try_->tryBlock()->id()] = tryBlock;
MBasicBlock* successor;
MOZ_TRY_VAR(successor, newBlock(current, try_->getSuccessor(1)->startPc()));
blockWorklist[try_->afterTryCatchBlock()->id()] = successor;
current->end(MGotoWithFake::New(alloc(), tryBlock, successor));
MOZ_ASSERT(info().osrPc() < try_->catchStartPc() ||
info().osrPc() >= try_->afterTryCatchBlock()->startPc());
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitReturn(CFGControlInstruction* control) {
MDefinition* def;
switch (control->type()) {
case CFGControlInstruction::Type_Return:
def = current->pop();
break;
case CFGControlInstruction::Type_RetRVal:
if (script()->noScriptRval()) {
MInstruction* ins = MConstant::New(alloc(), UndefinedValue());
current->add(ins);
def = ins;
break;
}
def = current->getSlot(info().returnValueSlot());
break;
default:
def = nullptr;
MOZ_CRASH("unknown return op");
}
MReturn* ret = MReturn::New(alloc(), def);
current->end(ret);
if (!graph().addReturn(current)) {
return abort(AbortReason::Alloc);
}
setCurrent(nullptr);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitThrow(CFGThrow* cfgIns) {
MDefinition* def = current->pop();
MNop* nop = MNop::New(alloc());
current->add(nop);
MOZ_TRY(resumeAfter(nop));
MThrow* ins = MThrow::New(alloc(), def);
current->end(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::visitTableSwitch(CFGTableSwitch* cfgIns) {
MDefinition* ins = current->pop();
MTableSwitch* tableswitch =
MTableSwitch::New(alloc(), ins, cfgIns->low(), cfgIns->high());
#ifdef DEBUG
MOZ_ASSERT(cfgIns->defaultCase() == cfgIns->getSuccessor(0));
for (size_t i = 1; i < cfgIns->numSuccessors(); i++) {
MOZ_ASSERT(cfgIns->getCase(i - 1) == cfgIns->getSuccessor(i));
}
#endif
for (size_t i = 0; i < cfgIns->numSuccessors(); i++) {
const CFGBlock* cfgblock = cfgIns->getSuccessor(i);
MBasicBlock* caseBlock;
MOZ_TRY_VAR(caseBlock, newBlock(current, cfgblock->startPc()));
blockWorklist[cfgblock->id()] = caseBlock;
size_t index;
if (i == 0) {
if (!tableswitch->addDefault(caseBlock, &index)) {
return abort(AbortReason::Alloc);
}
} else {
if (!tableswitch->addSuccessor(caseBlock, &index)) {
return abort(AbortReason::Alloc);
}
if (!tableswitch->addCase(index)) {
return abort(AbortReason::Alloc);
}
MConstant* constant =
MConstant::New(alloc(), Int32Value(i - 1 + tableswitch->low()));
caseBlock->add(constant);
for (uint32_t j = 0; j < caseBlock->stackDepth(); j++) {
if (ins != caseBlock->getSlot(j)) {
continue;
}
constant->setDependency(ins);
caseBlock->setSlot(j, constant);
}
graph().addBlock(caseBlock);
if (caseBlock->pc() && script()->hasScriptCounts()) {
caseBlock->setHitCount(script()->getHitCount(caseBlock->pc()));
}
MBasicBlock* merge;
MOZ_TRY_VAR(merge, newBlock(caseBlock, cfgblock->startPc()));
if (!merge) {
return abort(AbortReason::Alloc);
}
caseBlock->end(MGoto::New(alloc(), merge));
blockWorklist[cfgblock->id()] = merge;
}
MOZ_ASSERT(index == i);
}
current->end(tableswitch);
return Ok();
}
void IonBuilder::pushConstant(const Value& v) { current->push(constant(v)); }
AbortReasonOr<Ok> IonBuilder::bitnotTrySpecialized(bool* emitted,
MDefinition* input) {
MOZ_ASSERT(*emitted == false);
if (input->mightBeType(MIRType::Object) ||
input->mightBeType(MIRType::Symbol) ||
input->mightBeType(MIRType::BigInt)) {
return Ok();
}
MBitNot* ins = MBitNot::New(alloc(), input);
ins->setSpecialization(MIRType::Int32);
current->add(ins);
current->push(ins);
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_bitnot() {
bool emitted = false;
MDefinition* input = current->pop();
if (!forceInlineCaches()) {
MOZ_TRY(bitnotTrySpecialized(&emitted, input));
if (emitted) return Ok();
}
MOZ_TRY(arithTryBinaryStub(&emitted, JSOP_BITNOT, nullptr, input));
if (emitted) {
return Ok();
}
MBitNot* ins = MBitNot::New(alloc(), input);
current->add(ins);
current->push(ins);
MOZ_ASSERT(ins->isEffectful());
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_bitop(JSOp op) {
MDefinition* right = current->pop();
MDefinition* left = current->pop();
MBinaryBitwiseInstruction* ins;
switch (op) {
case JSOP_BITAND:
ins = MBitAnd::New(alloc(), left, right);
break;
case JSOP_BITOR:
ins = MBitOr::New(alloc(), left, right);
break;
case JSOP_BITXOR:
ins = MBitXor::New(alloc(), left, right);
break;
case JSOP_LSH:
ins = MLsh::New(alloc(), left, right);
break;
case JSOP_RSH:
ins = MRsh::New(alloc(), left, right);
break;
case JSOP_URSH:
ins = MUrsh::New(alloc(), left, right);
break;
default:
MOZ_CRASH("unexpected bitop");
}
current->add(ins);
ins->infer(inspector, pc);
current->push(ins);
if (ins->isEffectful()) {
MOZ_TRY(resumeAfter(ins));
}
return Ok();
}
MDefinition::Opcode BinaryJSOpToMDefinition(JSOp op) {
switch (op) {
case JSOP_ADD:
return MDefinition::Opcode::Add;
case JSOP_SUB:
return MDefinition::Opcode::Sub;
case JSOP_MUL:
return MDefinition::Opcode::Mul;
case JSOP_DIV:
return MDefinition::Opcode::Div;
case JSOP_MOD:
return MDefinition::Opcode::Mod;
default:
MOZ_CRASH("unexpected binary opcode");
}
}
AbortReasonOr<Ok> IonBuilder::binaryArithTryConcat(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
if (op != JSOP_ADD) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::BinaryArith_Concat);
if (left->type() != MIRType::String && right->type() != MIRType::String) {
trackOptimizationOutcome(TrackedOutcome::OperandNotString);
return Ok();
}
if (right->type() != MIRType::String &&
(right->mightBeType(MIRType::Symbol) ||
right->mightBeType(MIRType::Object) || right->mightBeMagicType())) {
trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
return Ok();
}
if (left->type() != MIRType::String &&
(left->mightBeType(MIRType::Symbol) ||
left->mightBeType(MIRType::Object) || left->mightBeMagicType())) {
trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
return Ok();
}
MConcat* ins = MConcat::New(alloc(), left, right);
current->add(ins);
current->push(ins);
MOZ_TRY(maybeInsertResume());
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::powTrySpecialized(bool* emitted,
MDefinition* base,
MDefinition* power,
MIRType outputType) {
MDefinition* output = nullptr;
MIRType baseType = base->type();
MIRType powerType = power->type();
if (outputType != MIRType::Int32 && outputType != MIRType::Double) {
return Ok();
}
if (!IsNumberType(baseType)) {
return Ok();
}
if (!IsNumberType(powerType)) {
return Ok();
}
if (powerType == MIRType::Float32) {
powerType = MIRType::Double;
}
MPow* pow = MPow::New(alloc(), base, power, powerType);
current->add(pow);
output = pow;
if (outputType == MIRType::Int32 && output->type() != MIRType::Int32) {
auto* toInt = MToNumberInt32::New(alloc(), output);
current->add(toInt);
output = toInt;
}
if (outputType == MIRType::Double && output->type() != MIRType::Double) {
MToDouble* toDouble = MToDouble::New(alloc(), output);
current->add(toDouble);
output = toDouble;
}
current->push(output);
*emitted = true;
return Ok();
}
MIRType IonBuilder::binaryArithNumberSpecialization(MDefinition* left,
MDefinition* right) {
if (left->type() == MIRType::Int32 && right->type() == MIRType::Int32 &&
!inspector->hasSeenDoubleResult(pc)) {
return MIRType::Int32;
}
return MIRType::Double;
}
AbortReasonOr<MBinaryArithInstruction*> IonBuilder::binaryArithEmitSpecialized(
MDefinition::Opcode op, MIRType specialization, MDefinition* left,
MDefinition* right) {
MBinaryArithInstruction* ins =
MBinaryArithInstruction::New(alloc(), op, left, right);
ins->setSpecialization(specialization);
if (op == MDefinition::Opcode::Add || op == MDefinition::Opcode::Mul) {
ins->setCommutative();
}
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
MOZ_TRY(maybeInsertResume());
return ins;
}
AbortReasonOr<Ok> IonBuilder::binaryArithTrySpecialized(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
trackOptimizationAttempt(TrackedStrategy::BinaryArith_SpecializedTypes);
if (!SimpleArithOperand(left) || !SimpleArithOperand(right)) {
trackOptimizationOutcome(TrackedOutcome::OperandNotSimpleArith);
return Ok();
}
if (!IsNumberType(left->type()) && !IsNumberType(right->type())) {
trackOptimizationOutcome(TrackedOutcome::OperandNotNumber);
return Ok();
}
MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
MIRType specialization = binaryArithNumberSpecialization(left, right);
MBinaryArithInstruction* ins;
MOZ_TRY_VAR(ins,
binaryArithEmitSpecialized(defOp, specialization, left, right));
if (specialization == MIRType::Int32 && ins->constantDoubleResult(alloc())) {
ins->setSpecialization(MIRType::Double);
}
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::binaryArithTrySpecializedOnBaselineInspector(
bool* emitted, JSOp op, MDefinition* left, MDefinition* right) {
MOZ_ASSERT(*emitted == false);
trackOptimizationAttempt(
TrackedStrategy::BinaryArith_SpecializedOnBaselineTypes);
MIRType specialization = inspector->expectedBinaryArithSpecialization(pc);
if (specialization == MIRType::None) {
trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
return Ok();
}
MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, left, right));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::arithTryBinaryStub(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
JSOp actualOp = JSOp(*pc);
if (JitOptions.disableCacheIRBinaryArith) {
return Ok();
}
if (actualOp == JSOP_POS || actualOp == JSOP_POW) {
return Ok();
}
MInstruction* stub = nullptr;
switch (actualOp) {
case JSOP_NEG:
case JSOP_BITNOT:
MOZ_ASSERT_IF(op == JSOP_MUL,
left->maybeConstantValue() &&
left->maybeConstantValue()->toInt32() == -1);
MOZ_ASSERT_IF(op != JSOP_MUL, !left);
stub = MUnaryCache::New(alloc(), right);
break;
case JSOP_ADD:
case JSOP_SUB:
case JSOP_MUL:
case JSOP_DIV:
case JSOP_MOD:
stub = MBinaryCache::New(alloc(), left, right, MIRType::Value);
break;
default:
MOZ_CRASH("unsupported arith");
}
current->add(stub);
current->push(stub);
maybeMarkEmpty(stub);
MOZ_TRY(resumeAfter(stub));
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_binary_arith(JSOp op, MDefinition* left,
MDefinition* right) {
bool emitted = false;
startTrackingOptimizations();
trackTypeInfo(TrackedTypeSite::Operand, left->type(), left->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Operand, right->type(),
right->resultTypeSet());
if (!forceInlineCaches()) {
MOZ_TRY(binaryArithTryConcat(&emitted, op, left, right));
if (emitted) {
return Ok();
}
MOZ_TRY(binaryArithTrySpecialized(&emitted, op, left, right));
if (emitted) {
return Ok();
}
MOZ_TRY(binaryArithTrySpecializedOnBaselineInspector(&emitted, op, left,
right));
if (emitted) {
return Ok();
}
}
MOZ_TRY(arithTryBinaryStub(&emitted, op, left, right));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::BinaryArith_Call);
trackOptimizationSuccess();
MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
MBinaryArithInstruction* ins =
MBinaryArithInstruction::New(alloc(), defOp, left, right);
maybeMarkEmpty(ins);
current->add(ins);
current->push(ins);
MOZ_ASSERT(ins->isEffectful());
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_binary_arith(JSOp op) {
MDefinition* right = current->pop();
MDefinition* left = current->pop();
return jsop_binary_arith(op, left, right);
}
AbortReasonOr<Ok> IonBuilder::jsop_pow() {
MDefinition* exponent = current->pop();
MDefinition* base = current->pop();
bool emitted = false;
if (!forceInlineCaches()) {
MOZ_TRY(powTrySpecialized(&emitted, base, exponent, MIRType::Double));
if (emitted) {
return Ok();
}
}
MOZ_TRY(arithTryBinaryStub(&emitted, JSOP_POW, base, exponent));
if (emitted) {
return Ok();
}
MPow* pow = MPow::New(alloc(), base, exponent, MIRType::None);
current->add(pow);
current->push(pow);
MOZ_ASSERT(pow->isEffectful());
return resumeAfter(pow);
}
AbortReasonOr<Ok> IonBuilder::jsop_pos() {
if (IsNumberType(current->peek(-1)->type())) {
current->peek(-1)->setImplicitlyUsedUnchecked();
return Ok();
}
MDefinition* value = current->pop();
MConstant* one = MConstant::New(alloc(), Int32Value(1));
current->add(one);
return jsop_binary_arith(JSOP_MUL, value, one);
}
AbortReasonOr<Ok> IonBuilder::jsop_neg() {
MConstant* negator = MConstant::New(alloc(), Int32Value(-1));
current->add(negator);
MDefinition* right = current->pop();
return jsop_binary_arith(JSOP_MUL, negator, right);
}
AbortReasonOr<Ok> IonBuilder::jsop_tonumeric() {
MDefinition* peeked = current->peek(-1);
if (IsNumericType(peeked->type())) {
peeked->setImplicitlyUsedUnchecked();
return Ok();
}
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
TemporaryTypeSet* types = lifoAlloc->new_<TemporaryTypeSet>();
if (!types) {
return abort(AbortReason::Alloc);
}
types->addType(TypeSet::Int32Type(), lifoAlloc);
types->addType(TypeSet::DoubleType(), lifoAlloc);
types->addType(TypeSet::BigIntType(), lifoAlloc);
if (peeked->type() == MIRType::Value && peeked->resultTypeSet() &&
peeked->resultTypeSet()->isSubset(types)) {
peeked->setImplicitlyUsedUnchecked();
return Ok();
}
MDefinition* popped = current->pop();
MToNumeric* ins = MToNumeric::New(alloc(), popped, types);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
MDefinition* IonBuilder::unaryArithConvertToBinary(JSOp op,
MDefinition::Opcode* defOp) {
switch (op) {
case JSOP_INC: {
*defOp = MDefinition::Opcode::Add;
MConstant* right = MConstant::New(alloc(), Int32Value(1));
current->add(right);
return right;
}
case JSOP_DEC: {
*defOp = MDefinition::Opcode::Sub;
MConstant* right = MConstant::New(alloc(), Int32Value(1));
current->add(right);
return right;
}
default:
MOZ_CRASH("unexpected unary opcode");
}
}
AbortReasonOr<Ok> IonBuilder::unaryArithTrySpecialized(bool* emitted, JSOp op,
MDefinition* value) {
MOZ_ASSERT(*emitted == false);
trackOptimizationAttempt(TrackedStrategy::UnaryArith_SpecializedTypes);
if (!IsNumberType(value->type())) {
trackOptimizationOutcome(TrackedOutcome::OperandNotNumber);
return Ok();
}
MDefinition::Opcode defOp;
MDefinition* rhs = unaryArithConvertToBinary(op, &defOp);
MIRType specialization = binaryArithNumberSpecialization(value, rhs);
MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, value, rhs));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::unaryArithTrySpecializedOnBaselineInspector(
bool* emitted, JSOp op, MDefinition* value) {
MOZ_ASSERT(*emitted == false);
trackOptimizationAttempt(
TrackedStrategy::UnaryArith_SpecializedOnBaselineTypes);
MIRType specialization = inspector->expectedBinaryArithSpecialization(pc);
if (specialization == MIRType::None) {
trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
return Ok();
}
MDefinition::Opcode defOp;
MDefinition* rhs = unaryArithConvertToBinary(op, &defOp);
MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, value, rhs));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_inc_or_dec(JSOp op) {
bool emitted = false;
MDefinition* value = current->pop();
startTrackingOptimizations();
trackTypeInfo(TrackedTypeSite::Operand, value->type(),
value->resultTypeSet());
MOZ_TRY(unaryArithTrySpecialized(&emitted, op, value));
if (emitted) {
return Ok();
}
MOZ_TRY(unaryArithTrySpecializedOnBaselineInspector(&emitted, op, value));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::UnaryArith_InlineCache);
trackOptimizationSuccess();
MInstruction* stub = MUnaryCache::New(alloc(), value);
current->add(stub);
current->push(stub);
maybeMarkEmpty(stub);
return resumeAfter(stub);
}
AbortReasonOr<Ok> IonBuilder::jsop_tostring() {
if (current->peek(-1)->type() == MIRType::String) {
return Ok();
}
MDefinition* value = current->pop();
MToString* ins = MToString::New(alloc(), value);
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
return Ok();
}
class AutoAccumulateReturns {
MIRGraph& graph_;
MIRGraphReturns* prev_;
public:
AutoAccumulateReturns(MIRGraph& graph, MIRGraphReturns& returns)
: graph_(graph) {
prev_ = graph_.returnAccumulator();
graph_.setReturnAccumulator(&returns);
}
~AutoAccumulateReturns() { graph_.setReturnAccumulator(prev_); }
};
IonBuilder::InliningResult IonBuilder::inlineScriptedCall(CallInfo& callInfo,
JSFunction* target) {
MOZ_ASSERT(target->hasScript());
MOZ_ASSERT(IsIonInlinablePC(pc));
MBasicBlock::BackupPoint backup(current);
if (!backup.init(alloc())) {
return abort(AbortReason::Alloc);
}
callInfo.setImplicitlyUsedUnchecked();
if (callInfo.constructing()) {
MDefinition* thisDefn =
createThis(target, callInfo.fun(), callInfo.getNewTarget());
if (!thisDefn) {
return abort(AbortReason::Alloc);
}
callInfo.setThis(thisDefn);
}
MOZ_TRY(callInfo.pushCallStack(this, current));
MResumePoint* outerResumePoint =
MResumePoint::New(alloc(), current, pc, MResumePoint::Outer);
if (!outerResumePoint) {
return abort(AbortReason::Alloc);
}
current->setOuterResumePoint(outerResumePoint);
callInfo.popCallStack(current);
current->push(callInfo.fun());
JSScript* calleeScript = target->nonLazyScript();
BaselineInspector inspector(calleeScript);
if (callInfo.constructing() && !callInfo.thisArg()->resultTypeSet()) {
StackTypeSet* types = TypeScript::ThisTypes(calleeScript);
if (types && !types->unknown()) {
TemporaryTypeSet* clonedTypes = types->clone(alloc_->lifoAlloc());
if (!clonedTypes) {
return abort(AbortReason::Alloc);
}
MTypeBarrier* barrier =
MTypeBarrier::New(alloc(), callInfo.thisArg(), clonedTypes);
current->add(barrier);
if (barrier->type() == MIRType::Undefined) {
callInfo.setThis(constant(UndefinedValue()));
} else if (barrier->type() == MIRType::Null) {
callInfo.setThis(constant(NullValue()));
} else {
callInfo.setThis(barrier);
}
}
}
LifoAlloc* lifoAlloc = alloc_->lifoAlloc();
InlineScriptTree* inlineScriptTree =
info().inlineScriptTree()->addCallee(alloc_, pc, calleeScript);
if (!inlineScriptTree) {
return abort(AbortReason::Alloc);
}
CompileInfo* info = lifoAlloc->new_<CompileInfo>(
runtime, calleeScript, target, (jsbytecode*)nullptr,
this->info().analysisMode(),
false, inlineScriptTree);
if (!info) {
return abort(AbortReason::Alloc);
}
MIRGraphReturns returns(alloc());
AutoAccumulateReturns aar(graph(), returns);
IonBuilder inlineBuilder(analysisContext, realm, options, &alloc(), &graph(),
constraints(), &inspector, info, &optimizationInfo(),
nullptr, inliningDepth_ + 1, loopDepth_);
AbortReasonOr<Ok> result =
inlineBuilder.buildInline(this, outerResumePoint, callInfo);
if (result.isErr()) {
if (analysisContext && analysisContext->isExceptionPending()) {
JitSpew(JitSpew_IonAbort, "Inline builder raised exception.");
MOZ_ASSERT(result.unwrapErr() == AbortReason::Error);
return Err(result.unwrapErr());
}
switch (result.unwrapErr()) {
case AbortReason::Disable:
calleeScript->setUninlineable();
if (!JitOptions.disableInlineBacktracking) {
current = backup.restore();
if (!current) {
return abort(AbortReason::Alloc);
}
return InliningStatus_NotInlined;
}
return abort(AbortReason::Inlining);
case AbortReason::PreliminaryObjects: {
const ObjectGroupVector& groups =
inlineBuilder.abortedPreliminaryGroups();
MOZ_ASSERT(!groups.empty());
for (size_t i = 0; i < groups.length(); i++) {
addAbortedPreliminaryGroup(groups[i]);
}
return Err(result.unwrapErr());
}
case AbortReason::Alloc:
case AbortReason::Inlining:
case AbortReason::Error:
return Err(result.unwrapErr());
case AbortReason::NoAbort:
MOZ_CRASH("Abort with AbortReason::NoAbort");
return abort(AbortReason::Error);
}
}
if (returns.empty()) {
calleeScript->setUninlineable();
if (!JitOptions.disableInlineBacktracking) {
current = backup.restore();
if (!current) {
return abort(AbortReason::Alloc);
}
return InliningStatus_NotInlined;
}
return abort(AbortReason::Inlining);
}
jsbytecode* postCall = GetNextPc(pc);
MBasicBlock* returnBlock;
MOZ_TRY_VAR(returnBlock, newBlock(current->stackDepth(), postCall));
graph().addBlock(returnBlock);
returnBlock->setCallerResumePoint(callerResumePoint_);
returnBlock->inheritSlots(current);
returnBlock->pop();
MDefinition* retvalDefn = patchInlinedReturns(callInfo, returns, returnBlock);
if (!retvalDefn) {
return abort(AbortReason::Alloc);
}
returnBlock->push(retvalDefn);
if (!returnBlock->initEntrySlots(alloc())) {
return abort(AbortReason::Alloc);
}
MOZ_TRY(setCurrentAndSpecializePhis(returnBlock));
return InliningStatus_Inlined;
}
MDefinition* IonBuilder::patchInlinedReturn(CallInfo& callInfo,
MBasicBlock* exit,
MBasicBlock* bottom) {
MDefinition* rdef = exit->lastIns()->toReturn()->input();
exit->discardLastIns();
if (callInfo.constructing()) {
if (rdef->type() == MIRType::Value) {
MReturnFromCtor* filter =
MReturnFromCtor::New(alloc(), rdef, callInfo.thisArg());
exit->add(filter);
rdef = filter;
} else if (rdef->type() != MIRType::Object) {
rdef = callInfo.thisArg();
}
} else if (callInfo.isSetter()) {
rdef = callInfo.getArg(0);
}
if (!callInfo.isSetter()) {
rdef = specializeInlinedReturn(rdef, exit);
}
MGoto* replacement = MGoto::New(alloc(), bottom);
exit->end(replacement);
if (!bottom->addPredecessorWithoutPhis(exit)) {
return nullptr;
}
return rdef;
}
MDefinition* IonBuilder::specializeInlinedReturn(MDefinition* rdef,
MBasicBlock* exit) {
TemporaryTypeSet* types = bytecodeTypes(pc);
if (types->empty() || types->unknown()) {
return rdef;
}
if (rdef->resultTypeSet()) {
if (rdef->resultTypeSet()->isSubset(types)) {
return rdef;
}
} else {
MIRType observedType = types->getKnownMIRType();
if (observedType == MIRType::Double && rdef->type() == MIRType::Float32) {
return rdef;
}
if (observedType == rdef->type() && observedType != MIRType::Value &&
(observedType != MIRType::Object || types->unknownObject())) {
return rdef;
}
}
setCurrent(exit);
MTypeBarrier* barrier = nullptr;
rdef = addTypeBarrier(rdef, types, BarrierKind::TypeSet, &barrier);
if (barrier) {
barrier->setNotMovable();
}
return rdef;
}
MDefinition* IonBuilder::patchInlinedReturns(CallInfo& callInfo,
MIRGraphReturns& returns,
MBasicBlock* bottom) {
MOZ_ASSERT(returns.length() > 0);
if (returns.length() == 1) {
return patchInlinedReturn(callInfo, returns[0], bottom);
}
MPhi* phi = MPhi::New(alloc());
if (!phi->reserveLength(returns.length())) {
return nullptr;
}
for (size_t i = 0; i < returns.length(); i++) {
MDefinition* rdef = patchInlinedReturn(callInfo, returns[i], bottom);
if (!rdef) {
return nullptr;
}
phi->addInput(rdef);
}
bottom->addPhi(phi);
return phi;
}
IonBuilder::InliningDecision IonBuilder::makeInliningDecision(
JSObject* targetArg, CallInfo& callInfo) {
if (targetArg == nullptr) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNoTarget);
return InliningDecision_DontInline;
}
if (!targetArg->is<JSFunction>()) {
return InliningDecision_Inline;
}
JSFunction* target = &targetArg->as<JSFunction>();
if (info().analysisMode() == Analysis_ArgumentsUsage) {
return InliningDecision_DontInline;
}
if (target->isNative()) {
return InliningDecision_Inline;
}
InliningDecision decision = canInlineTarget(target, callInfo);
if (decision != InliningDecision_Inline) {
return decision;
}
JSScript* targetScript = target->nonLazyScript();
bool offThread = options.offThreadCompilationAvailable();
if (targetScript->length() >
optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBigCallee);
return DontInline(targetScript, "Vetoed: callee excessively large");
}
if (targetScript->getWarmUpCount() <
optimizationInfo().inliningWarmUpThreshold() &&
!targetScript->baselineScript()->ionCompiledOrInlined() &&
info().analysisMode() != Analysis_DefiniteProperties) {
trackOptimizationOutcome(TrackedOutcome::CantInlineNotHot);
JitSpew(JitSpew_Inlining,
"Cannot inline %s:%u:%u: callee is insufficiently hot.",
targetScript->filename(), targetScript->lineno(),
targetScript->column());
return InliningDecision_WarmUpCountTooLow;
}
uint32_t inlinedBytecodeLength =
targetScript->baselineScript()->inlinedBytecodeLength();
if (inlinedBytecodeLength >
optimizationInfo().inlineMaxCalleeInlinedBytecodeLength()) {
trackOptimizationOutcome(
TrackedOutcome::CantInlineBigCalleeInlinedBytecodeLength);
return DontInline(targetScript,
"Vetoed: callee inlinedBytecodeLength is too big");
}
IonBuilder* outerBuilder = outermostBuilder();
size_t totalBytecodeLength =
outerBuilder->inlinedBytecodeLength_ + targetScript->length();
if (totalBytecodeLength > optimizationInfo().inlineMaxTotalBytecodeLength()) {
trackOptimizationOutcome(
TrackedOutcome::CantInlineExceededTotalBytecodeLength);
return DontInline(targetScript,
"Vetoed: exceeding max total bytecode length");
}
uint32_t maxInlineDepth;
if (JitOptions.isSmallFunction(targetScript)) {
maxInlineDepth = optimizationInfo().smallFunctionMaxInlineDepth();
} else {
maxInlineDepth = optimizationInfo().maxInlineDepth();
if (script()->length() >=
optimizationInfo().inliningMaxCallerBytecodeLength()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller);
return DontInline(targetScript, "Vetoed: caller excessively large");
}
}
BaselineScript* outerBaseline =
outermostBuilder()->script()->baselineScript();
if (inliningDepth_ >= maxInlineDepth) {
if (isHighestOptimizationLevel()) {
outerBaseline->setMaxInliningDepth(0);
}
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript, "Vetoed: exceeding allowed inline depth");
}
if (isHighestOptimizationLevel() && targetScript->hasLoops() &&
inliningDepth_ >= targetScript->baselineScript()->maxInliningDepth()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript,
"Vetoed: exceeding allowed script inline depth");
}
MOZ_ASSERT(maxInlineDepth > inliningDepth_);
uint32_t scriptInlineDepth = maxInlineDepth - inliningDepth_ - 1;
if (scriptInlineDepth < outerBaseline->maxInliningDepth() &&
isHighestOptimizationLevel()) {
outerBaseline->setMaxInliningDepth(scriptInlineDepth);
}
outerBuilder->inlinedBytecodeLength_ += targetScript->length();
return InliningDecision_Inline;
}
AbortReasonOr<Ok> IonBuilder::selectInliningTargets(
const InliningTargets& targets, CallInfo& callInfo, BoolVector& choiceSet,
uint32_t* numInlineable) {
*numInlineable = 0;
uint32_t totalSize = 0;
if (!choiceSet.reserve(targets.length())) {
return abort(AbortReason::Alloc);
}
if (info().analysisMode() == Analysis_DefiniteProperties &&
targets.length() > 1) {
return Ok();
}
for (size_t i = 0; i < targets.length(); i++) {
JSObject* target = targets[i].target;
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
trackTypeInfo(TrackedTypeSite::Call_Target, target);
bool inlineable;
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
inlineable = false;
break;
case InliningDecision_Inline:
inlineable = true;
break;
default:
MOZ_CRASH("Unhandled InliningDecision value!");
}
if (target->is<JSFunction>()) {
if (inlineable && target->as<JSFunction>().isInterpreted()) {
totalSize += target->as<JSFunction>().nonLazyScript()->length();
bool offThread = options.offThreadCompilationAvailable();
if (totalSize >
optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) {
inlineable = false;
}
}
} else {
inlineable = false;
}
if (inlineable && targets[i].group) {
ObjectGroup* group = targets[i].group;
TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(group);
if (!key->hasStableClassAndProto(constraints())) {
inlineable = false;
}
}
choiceSet.infallibleAppend(inlineable);
if (inlineable) {
*numInlineable += 1;
}
}
if (isOptimizationTrackingEnabled()) {
for (size_t i = 0; i < targets.length(); i++) {
if (choiceSet[i] && targets[i].target->as<JSFunction>().isNative()) {
trackTypeInfo(callInfo);
break;
}
}
}
MOZ_ASSERT(choiceSet.length() == targets.length());
return Ok();
}
static bool CanInlineGetPropertyCache(MGetPropertyCache* cache,
MDefinition* thisDef) {
if (cache->value()->type() != MIRType::Object) {
return false;
}
if (cache->value() != thisDef) {
return false;
}
InlinePropertyTable* table = cache->propTable();
if (!table) {
return false;
}
if (table->numEntries() == 0) {
return false;
}
return true;
}
class WrapMGetPropertyCache {
MGetPropertyCache* cache_;
private:
void discardPriorResumePoint() {
if (!cache_) {
return;
}
InlinePropertyTable* propTable = cache_->propTable();
if (!propTable) {
return;
}
MResumePoint* rp = propTable->takePriorResumePoint();
if (!rp) {
return;
}
cache_->block()->discardPreAllocatedResumePoint(rp);
}
public:
explicit WrapMGetPropertyCache(MGetPropertyCache* cache) : cache_(cache) {}
~WrapMGetPropertyCache() { discardPriorResumePoint(); }
MGetPropertyCache* get() { return cache_; }
MGetPropertyCache* operator->() { return get(); }
MGetPropertyCache* moveableCache(bool hasTypeBarrier, MDefinition* thisDef) {
if (!hasTypeBarrier) {
if (cache_->hasUses()) {
return nullptr;
}
} else {
MOZ_ASSERT(cache_->hasUses());
if (!cache_->hasOneUse()) {
return nullptr;
}
}
if (!CanInlineGetPropertyCache(cache_, thisDef)) {
return nullptr;
}
MGetPropertyCache* ret = cache_;
cache_ = nullptr;
return ret;
}
};
MGetPropertyCache* IonBuilder::getInlineableGetPropertyCache(
CallInfo& callInfo) {
if (callInfo.constructing()) {
return nullptr;
}
MDefinition* thisDef = callInfo.thisArg();
if (thisDef->type() != MIRType::Object) {
return nullptr;
}
MDefinition* funcDef = callInfo.fun();
if (funcDef->type() != MIRType::Object) {
return nullptr;
}
if (funcDef->isGetPropertyCache()) {
WrapMGetPropertyCache cache(funcDef->toGetPropertyCache());
return cache.moveableCache( false, thisDef);
}
if (funcDef->isTypeBarrier()) {
MTypeBarrier* barrier = funcDef->toTypeBarrier();
if (barrier->hasUses()) {
return nullptr;
}
if (barrier->type() != MIRType::Object) {
return nullptr;
}
if (!barrier->input()->isGetPropertyCache()) {
return nullptr;
}
WrapMGetPropertyCache cache(barrier->input()->toGetPropertyCache());
return cache.moveableCache( true, thisDef);
}
return nullptr;
}
IonBuilder::InliningResult IonBuilder::inlineSingleCall(CallInfo& callInfo,
JSObject* targetArg) {
InliningStatus status;
if (!targetArg->is<JSFunction>()) {
MOZ_TRY_VAR(status, inlineNonFunctionCall(callInfo, targetArg));
trackInlineSuccess(status);
return status;
}
JSFunction* target = &targetArg->as<JSFunction>();
if (target->isNative()) {
MOZ_TRY_VAR(status, inlineNativeCall(callInfo, target));
trackInlineSuccess(status);
return status;
}
trackInlineSuccess();
return inlineScriptedCall(callInfo, target);
}
IonBuilder::InliningResult IonBuilder::inlineCallsite(
const InliningTargets& targets, CallInfo& callInfo) {
if (targets.empty()) {
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
trackOptimizationOutcome(TrackedOutcome::CantInlineNoTarget);
return InliningStatus_NotInlined;
}
WrapMGetPropertyCache propCache(getInlineableGetPropertyCache(callInfo));
keepFallbackFunctionGetter(propCache.get());
if (!propCache.get() && targets.length() == 1) {
JSObject* target = targets[0].target;
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
trackTypeInfo(TrackedTypeSite::Call_Target, target);
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
return InliningStatus_NotInlined;
case InliningDecision_WarmUpCountTooLow:
return InliningStatus_WarmUpCountTooLow;
case InliningDecision_Inline:
break;
}
callInfo.fun()->setImplicitlyUsedUnchecked();
if (target->isSingleton()) {
MConstant* constFun = constant(ObjectValue(*target));
if (callInfo.constructing() &&
callInfo.getNewTarget() == callInfo.fun()) {
callInfo.setNewTarget(constFun);
}
callInfo.setFun(constFun);
}
return inlineSingleCall(callInfo, target);
}
BoolVector choiceSet(alloc());
uint32_t numInlined;
MOZ_TRY(selectInliningTargets(targets, callInfo, choiceSet, &numInlined));
if (numInlined == 0) {
return InliningStatus_NotInlined;
}
MOZ_TRY(inlineCalls(callInfo, targets, choiceSet, propCache.get()));
return InliningStatus_Inlined;
}
AbortReasonOr<Ok> IonBuilder::inlineGenericFallback(
const Maybe<CallTargets>& targets, CallInfo& callInfo,
MBasicBlock* dispatchBlock) {
MBasicBlock* fallbackBlock;
MOZ_TRY_VAR(fallbackBlock, newBlock(dispatchBlock, pc));
graph().addBlock(fallbackBlock);
CallInfo fallbackInfo(alloc(), pc, callInfo.constructing(),
callInfo.ignoresReturnValue());
if (!fallbackInfo.init(callInfo)) {
return abort(AbortReason::Alloc);
}
fallbackInfo.popCallStack(fallbackBlock);
MOZ_TRY(setCurrentAndSpecializePhis(fallbackBlock));
MOZ_TRY(makeCall(targets, fallbackInfo));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::inlineObjectGroupFallback(
const Maybe<CallTargets>& targets, CallInfo& callInfo,
MBasicBlock* dispatchBlock, MObjectGroupDispatch* dispatch,
MGetPropertyCache* cache, MBasicBlock** fallbackTarget) {
MOZ_ASSERT(callInfo.fun()->isGetPropertyCache() ||
callInfo.fun()->isTypeBarrier());
MOZ_ASSERT(dispatch->numCases() > 0);
MOZ_ASSERT_IF(callInfo.fun()->isGetPropertyCache(), !cache->hasUses());
MOZ_ASSERT_IF(callInfo.fun()->isTypeBarrier(), cache->hasOneUse());
MOZ_ASSERT(cache->idempotent());
CallInfo fallbackInfo(alloc(), pc, callInfo.constructing(),
callInfo.ignoresReturnValue());
if (!fallbackInfo.init(callInfo)) {
return abort(AbortReason::Alloc);
}
MResumePoint* preCallResumePoint =
MResumePoint::New(alloc(), dispatchBlock, pc, MResumePoint::ResumeAt);
if (!preCallResumePoint) {
return abort(AbortReason::Alloc);
}
DebugOnly<size_t> preCallFuncIndex =
preCallResumePoint->stackDepth() - callInfo.numFormals();
MOZ_ASSERT(preCallResumePoint->getOperand(preCallFuncIndex) ==
fallbackInfo.fun());
MConstant* undefined = MConstant::New(alloc(), UndefinedValue());
dispatchBlock->add(undefined);
dispatchBlock->rewriteAtDepth(-int(callInfo.numFormals()), undefined);
MBasicBlock* prepBlock;
MOZ_TRY_VAR(prepBlock, newBlock(dispatchBlock, pc));
graph().addBlock(prepBlock);
fallbackInfo.popCallStack(prepBlock);
InlinePropertyTable* propTable = cache->propTable();
MResumePoint* priorResumePoint = propTable->takePriorResumePoint();
MOZ_ASSERT(propTable->pc() != nullptr);
MOZ_ASSERT(priorResumePoint != nullptr);
MBasicBlock* getPropBlock;
MOZ_TRY_VAR(getPropBlock,
newBlock(prepBlock, propTable->pc(), priorResumePoint));
graph().addBlock(getPropBlock);
prepBlock->end(MGoto::New(alloc(), getPropBlock));
DebugOnly<MDefinition*> checkObject = getPropBlock->pop();
MOZ_ASSERT(checkObject == cache->value());
if (fallbackInfo.fun()->isGetPropertyCache()) {
MOZ_ASSERT(fallbackInfo.fun()->toGetPropertyCache() == cache);
getPropBlock->addFromElsewhere(cache);
getPropBlock->push(cache);
} else {
MTypeBarrier* barrier = callInfo.fun()->toTypeBarrier();
MOZ_ASSERT(barrier->type() == MIRType::Object);
MOZ_ASSERT(barrier->input()->isGetPropertyCache());
MOZ_ASSERT(barrier->input()->toGetPropertyCache() == cache);
getPropBlock->addFromElsewhere(cache);
getPropBlock->addFromElsewhere(barrier);
getPropBlock->push(barrier);
}
MBasicBlock* preCallBlock;
MOZ_TRY_VAR(preCallBlock, newBlock(getPropBlock, pc, preCallResumePoint));
graph().addBlock(preCallBlock);
getPropBlock->end(MGoto::New(alloc(), preCallBlock));
MOZ_TRY(inlineGenericFallback(targets, fallbackInfo, preCallBlock));
preCallBlock->end(MGoto::New(alloc(), current));
*fallbackTarget = prepBlock;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::inlineCalls(CallInfo& callInfo,
const InliningTargets& targets,
BoolVector& choiceSet,
MGetPropertyCache* maybeCache) {
MOZ_ASSERT(IsIonInlinablePC(pc));
MOZ_ASSERT(choiceSet.length() == targets.length());
MOZ_ASSERT_IF(!maybeCache, targets.length() >= 2);
MOZ_ASSERT_IF(maybeCache, targets.length() >= 1);
MOZ_ASSERT_IF(maybeCache, maybeCache->value()->type() == MIRType::Object);
MBasicBlock* dispatchBlock = current;
callInfo.setImplicitlyUsedUnchecked();
MOZ_TRY(callInfo.pushCallStack(this, dispatchBlock));
if (maybeCache) {
InlinePropertyTable* propTable = maybeCache->propTable();
propTable->trimToTargets(targets);
if (propTable->numEntries() == 0) {
maybeCache = nullptr;
}
}
MDispatchInstruction* dispatch;
if (maybeCache) {
dispatch = MObjectGroupDispatch::New(alloc(), maybeCache->value(),
maybeCache->propTable());
callInfo.fun()->setImplicitlyUsedUnchecked();
} else {
dispatch = MFunctionDispatch::New(alloc(), callInfo.fun());
}
MOZ_ASSERT(dispatchBlock->stackDepth() >= callInfo.numFormals());
uint32_t stackDepth = dispatchBlock->stackDepth() - callInfo.numFormals() + 1;
jsbytecode* postCall = GetNextPc(pc);
MBasicBlock* returnBlock;
MOZ_TRY_VAR(returnBlock, newBlock(stackDepth, postCall));
graph().addBlock(returnBlock);
returnBlock->setCallerResumePoint(callerResumePoint_);
returnBlock->inheritSlots(dispatchBlock);
callInfo.popCallStack(returnBlock);
MPhi* retPhi = MPhi::New(alloc());
returnBlock->addPhi(retPhi);
returnBlock->push(retPhi);
if (!returnBlock->initEntrySlots(alloc())) {
return abort(AbortReason::Alloc);
}
uint32_t count = 1; for (uint32_t i = 0; i < targets.length(); i++) {
if (choiceSet[i]) {
count++;
}
}
if (!retPhi->reserveLength(count)) {
return abort(AbortReason::Alloc);
}
for (uint32_t i = 0; i < targets.length(); i++) {
if (!choiceSet[i]) {
continue;
}
amendOptimizationAttempt(i);
JSFunction* target = &targets[i].target->as<JSFunction>();
if (maybeCache && !maybeCache->propTable()->hasFunction(target)) {
choiceSet[i] = false;
trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch);
continue;
}
MBasicBlock* inlineBlock;
MOZ_TRY_VAR(inlineBlock, newBlock(dispatchBlock, pc));
graph().addBlock(inlineBlock);
MInstruction* funcDef;
if (target->isSingleton()) {
funcDef = MConstant::New(alloc(), ObjectValue(*target), constraints());
} else {
funcDef = MPolyInlineGuard::New(alloc(), callInfo.fun());
}
funcDef->setImplicitlyUsedUnchecked();
dispatchBlock->add(funcDef);
int funIndex =
inlineBlock->entryResumePoint()->stackDepth() - callInfo.numFormals();
inlineBlock->entryResumePoint()->replaceOperand(funIndex, funcDef);
inlineBlock->rewriteSlot(funIndex, funcDef);
CallInfo inlineInfo(alloc(), pc, callInfo.constructing(),
callInfo.ignoresReturnValue());
if (!inlineInfo.init(callInfo)) {
return abort(AbortReason::Alloc);
}
inlineInfo.popCallStack(inlineBlock);
inlineInfo.setFun(funcDef);
if (callInfo.constructing() && callInfo.getNewTarget() == callInfo.fun()) {
inlineInfo.setNewTarget(funcDef);
}
if (maybeCache) {
MOZ_ASSERT(callInfo.thisArg() == maybeCache->value());
TemporaryTypeSet* thisTypes =
maybeCache->propTable()->buildTypeSetForFunction(alloc(), target);
if (!thisTypes) {
return abort(AbortReason::Alloc);
}
MFilterTypeSet* filter =
MFilterTypeSet::New(alloc(), inlineInfo.thisArg(), thisTypes);
inlineBlock->add(filter);
inlineInfo.setThis(filter);
}
MOZ_TRY(setCurrentAndSpecializePhis(inlineBlock));
InliningStatus status;
MOZ_TRY_VAR(status, inlineSingleCall(inlineInfo, target));
if (status == InliningStatus_NotInlined) {
MOZ_ASSERT(current == inlineBlock);
graph().removeBlock(inlineBlock);
choiceSet[i] = false;
continue;
}
MBasicBlock* inlineReturnBlock = current;
setCurrent(dispatchBlock);
if (!dispatch->addCase(target, targets[i].group, inlineBlock)) {
return abort(AbortReason::Alloc);
}
MDefinition* retVal = inlineReturnBlock->peek(-1);
retPhi->addInput(retVal);
inlineReturnBlock->end(MGoto::New(alloc(), returnBlock));
if (!returnBlock->addPredecessorWithoutPhis(inlineReturnBlock)) {
return abort(AbortReason::Alloc);
}
}
bool useFallback;
if (maybeCache) {
InlinePropertyTable* propTable = maybeCache->propTable();
propTable->trimTo(targets, choiceSet);
if (propTable->numEntries() == 0 || !propTable->hasPriorResumePoint()) {
MOZ_ASSERT_IF(propTable->numEntries() == 0, dispatch->numCases() == 0);
maybeCache = nullptr;
useFallback = true;
} else {
useFallback = false;
TemporaryTypeSet* objectTypes = maybeCache->value()->resultTypeSet();
for (uint32_t i = 0; i < objectTypes->getObjectCount(); i++) {
TypeSet::ObjectKey* obj = objectTypes->getObject(i);
if (!obj) {
continue;
}
if (!obj->isGroup()) {
useFallback = true;
break;
}
if (!propTable->hasObjectGroup(obj->group())) {
useFallback = true;
break;
}
}
if (!useFallback) {
if (callInfo.fun()->isGetPropertyCache()) {
MOZ_ASSERT(callInfo.fun() == maybeCache);
} else {
MTypeBarrier* barrier = callInfo.fun()->toTypeBarrier();
MOZ_ASSERT(!barrier->hasUses());
MOZ_ASSERT(barrier->type() == MIRType::Object);
MOZ_ASSERT(barrier->input()->isGetPropertyCache());
MOZ_ASSERT(barrier->input()->toGetPropertyCache() == maybeCache);
barrier->block()->discard(barrier);
}
MOZ_ASSERT(!maybeCache->hasUses());
maybeCache->block()->discard(maybeCache);
}
}
} else {
useFallback = dispatch->numCases() < targets.length();
}
if (useFallback) {
Maybe<CallTargets> remainingTargets;
remainingTargets.emplace(alloc());
for (uint32_t i = 0; i < targets.length(); i++) {
if (!maybeCache && choiceSet[i]) {
continue;
}
JSObject* target = targets[i].target;
if (!target->is<JSFunction>()) {
remainingTargets = Nothing();
break;
}
if (!remainingTargets->append(&target->as<JSFunction>())) {
return abort(AbortReason::Alloc);
}
}
if (maybeCache) {
MBasicBlock* fallbackTarget;
MOZ_TRY(inlineObjectGroupFallback(
remainingTargets, callInfo, dispatchBlock,
dispatch->toObjectGroupDispatch(), maybeCache, &fallbackTarget));
dispatch->addFallback(fallbackTarget);
} else {
MOZ_TRY(inlineGenericFallback(remainingTargets, callInfo, dispatchBlock));
dispatch->addFallback(current);
}
MBasicBlock* fallbackReturnBlock = current;
MDefinition* retVal = fallbackReturnBlock->peek(-1);
retPhi->addInput(retVal);
fallbackReturnBlock->end(MGoto::New(alloc(), returnBlock));
if (!returnBlock->addPredecessorWithoutPhis(fallbackReturnBlock)) {
return abort(AbortReason::Alloc);
}
}
dispatchBlock->end(dispatch);
MOZ_ASSERT(returnBlock->stackDepth() ==
dispatchBlock->stackDepth() - callInfo.numFormals() + 1);
graph().moveBlockToEnd(returnBlock);
return setCurrentAndSpecializePhis(returnBlock);
}
MInstruction* IonBuilder::createNamedLambdaObject(MDefinition* callee,
MDefinition* env) {
LexicalEnvironmentObject* templateObj =
inspector->templateNamedLambdaObject();
MOZ_ASSERT(!templateObj->hasDynamicSlots());
MInstruction* declEnvObj = MNewNamedLambdaObject::New(alloc(), templateObj);
current->add(declEnvObj);
current->add(MStoreFixedSlot::New(
alloc(), declEnvObj, NamedLambdaObject::enclosingEnvironmentSlot(), env));
current->add(MStoreFixedSlot::New(alloc(), declEnvObj,
NamedLambdaObject::lambdaSlot(), callee));
return declEnvObj;
}
AbortReasonOr<MInstruction*> IonBuilder::createCallObject(MDefinition* callee,
MDefinition* env) {
CallObject* templateObj = inspector->templateCallObject();
MConstant* templateCst =
MConstant::NewConstraintlessObject(alloc(), templateObj);
current->add(templateCst);
MNewCallObject* callObj = MNewCallObject::New(alloc(), templateCst);
current->add(callObj);
current->add(MStoreFixedSlot::New(
alloc(), callObj, CallObject::enclosingEnvironmentSlot(), env));
current->add(
MStoreFixedSlot::New(alloc(), callObj, CallObject::calleeSlot(), callee));
MSlots* slots = nullptr;
for (PositionalFormalParameterIter fi(script()); fi; fi++) {
if (!fi.closedOver()) {
continue;
}
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
unsigned slot = fi.location().slot();
unsigned formal = fi.argumentSlot();
unsigned numFixedSlots = templateObj->numFixedSlots();
MDefinition* param;
if (script()->functionHasParameterExprs()) {
param = constant(MagicValue(JS_UNINITIALIZED_LEXICAL));
} else {
param = current->getSlot(info().argSlotUnchecked(formal));
}
if (slot >= numFixedSlots) {
if (!slots) {
slots = MSlots::New(alloc(), callObj);
current->add(slots);
}
current->add(
MStoreSlot::New(alloc(), slots, slot - numFixedSlots, param));
} else {
current->add(MStoreFixedSlot::New(alloc(), callObj, slot, param));
}
}
return AbortReasonOr<MInstruction*>(callObj);
}
MDefinition* IonBuilder::createThisScripted(MDefinition* callee,
MDefinition* newTarget) {
MInstruction* getProto;
if (!invalidatedIdempotentCache()) {
MConstant* id = constant(StringValue(names().prototype));
MGetPropertyCache* getPropCache =
MGetPropertyCache::New(alloc(), newTarget, id,
false);
getPropCache->setIdempotent();
getProto = getPropCache;
} else {
MCallGetProperty* callGetProp =
MCallGetProperty::New(alloc(), newTarget, names().prototype);
callGetProp->setIdempotent();
getProto = callGetProp;
}
current->add(getProto);
MCreateThisWithProto* createThis =
MCreateThisWithProto::New(alloc(), callee, newTarget, getProto);
current->add(createThis);
return createThis;
}
JSObject* IonBuilder::getSingletonPrototype(JSFunction* target) {
TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target);
if (targetKey->unknownProperties()) {
return nullptr;
}
jsid protoid = NameToId(names().prototype);
HeapTypeSetKey protoProperty = targetKey->property(protoid);
return protoProperty.singleton(constraints());
}
MDefinition* IonBuilder::createThisScriptedSingleton(JSFunction* target) {
if (!target->hasScript()) {
return nullptr;
}
JSObject* proto = getSingletonPrototype(target);
if (!proto) {
return nullptr;
}
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject) {
return nullptr;
}
if (!templateObject->is<PlainObject>() &&
!templateObject->is<UnboxedPlainObject>()) {
return nullptr;
}
if (templateObject->staticPrototype() != proto) {
return nullptr;
}
if (templateObject->nonCCWRealm() != target->realm()) {
return nullptr;
}
TypeSet::ObjectKey* templateObjectKey =
TypeSet::ObjectKey::get(templateObject->group());
if (templateObjectKey->hasFlags(constraints(),
OBJECT_FLAG_NEW_SCRIPT_CLEARED)) {
return nullptr;
}
StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
return nullptr;
}
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
MCreateThisWithTemplate* createThis = MCreateThisWithTemplate::New(
alloc(), constraints(), templateConst,
templateObject->group()->initialHeap(constraints()));
current->add(templateConst);
current->add(createThis);
return createThis;
}
MDefinition* IonBuilder::createThisScriptedBaseline(MDefinition* callee) {
JSFunction* target = inspector->getSingleCallee(pc);
if (!target || !target->hasScript()) {
return nullptr;
}
if (target->isBoundFunction() || target->isDerivedClassConstructor()) {
return nullptr;
}
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject) {
return nullptr;
}
if (!templateObject->is<PlainObject>() &&
!templateObject->is<UnboxedPlainObject>()) {
return nullptr;
}
if (templateObject->nonCCWRealm() != target->realm()) {
return nullptr;
}
Shape* shape = target->lookupPure(realm->runtime()->names().prototype);
if (!shape || !shape->isDataProperty()) {
return nullptr;
}
Value protov = target->getSlot(shape->slot());
if (!protov.isObject()) {
return nullptr;
}
JSObject* proto = checkNurseryObject(&protov.toObject());
if (proto != templateObject->staticPrototype()) {
return nullptr;
}
TypeSet::ObjectKey* templateObjectKey =
TypeSet::ObjectKey::get(templateObject->group());
if (templateObjectKey->hasFlags(constraints(),
OBJECT_FLAG_NEW_SCRIPT_CLEARED)) {
return nullptr;
}
StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject))) {
return nullptr;
}
callee = addShapeGuard(callee, target->lastProperty(), Bailout_ShapeGuard);
MOZ_ASSERT(shape->numFixedSlots() == 0, "Must be a dynamic slot");
MSlots* slots = MSlots::New(alloc(), callee);
current->add(slots);
MLoadSlot* prototype = MLoadSlot::New(alloc(), slots, shape->slot());
current->add(prototype);
MDefinition* protoConst = constant(ObjectValue(*proto));
MGuardObjectIdentity* guard =
MGuardObjectIdentity::New(alloc(), prototype, protoConst,
false);
current->add(guard);
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
MCreateThisWithTemplate* createThis = MCreateThisWithTemplate::New(
alloc(), constraints(), templateConst,
templateObject->group()->initialHeap(constraints()));
current->add(templateConst);
current->add(createThis);
return createThis;
}
MDefinition* IonBuilder::createThis(JSFunction* target, MDefinition* callee,
MDefinition* newTarget) {
if (!target) {
if (MDefinition* createThis = createThisScriptedBaseline(callee)) {
return createThis;
}
MCreateThis* createThis = MCreateThis::New(alloc(), callee, newTarget);
current->add(createThis);
return createThis;
}
if (target->isNative()) {
if (!target->isConstructor()) {
return nullptr;
}
if (target->isNativeWithJitEntry()) {
MOZ_ASSERT(target->isWasmOptimized());
return nullptr;
}
MConstant* magic = MConstant::New(alloc(), MagicValue(JS_IS_CONSTRUCTING));
current->add(magic);
return magic;
}
if (target->isBoundFunction()) {
return constant(MagicValue(JS_UNINITIALIZED_LEXICAL));
}
if (target->isDerivedClassConstructor()) {
MOZ_ASSERT(target->isClassConstructor());
return constant(MagicValue(JS_UNINITIALIZED_LEXICAL));
}
if (MDefinition* createThis = createThisScriptedSingleton(target)) {
return createThis;
}
if (MDefinition* createThis = createThisScriptedBaseline(callee)) {
return createThis;
}
return createThisScripted(callee, newTarget);
}
AbortReasonOr<Ok> IonBuilder::jsop_funcall(uint32_t argc) {
int calleeDepth = -((int)argc + 2);
int funcDepth = -((int)argc + 1);
TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
JSFunction* native = getSingleCallTarget(calleeTypes);
if (!native || !native->isNative() || native->native() != &fun_call) {
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
return makeCall(native, callInfo);
}
current->peek(calleeDepth)->setImplicitlyUsedUnchecked();
TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
JSFunction* target = getSingleCallTarget(funTypes);
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
MOZ_TRY(callInfo.savePriorCallStack(this, current, argc + 2));
current->shimmySlots(funcDepth - 1);
bool zeroArguments = (argc == 0);
if (zeroArguments) {
pushConstant(UndefinedValue());
} else {
argc -= 1;
}
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
if (!zeroArguments) {
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline: {
InliningStatus status;
MOZ_TRY_VAR(status, inlineSingleCall(callInfo, target));
if (status == InliningStatus_Inlined) {
return Ok();
}
break;
}
}
}
return makeCall(target, callInfo);
}
AbortReasonOr<Ok> IonBuilder::jsop_funapply(uint32_t argc) {
int calleeDepth = -((int)argc + 2);
TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
JSFunction* native = getSingleCallTarget(calleeTypes);
if (argc != 2 || info().analysisMode() == Analysis_ArgumentsUsage) {
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
return makeCall(native, callInfo);
}
MDefinition* argument = current->peek(-1);
if (script()->argumentsHasVarBinding() &&
argument->mightBeType(MIRType::MagicOptimizedArguments) &&
argument->type() != MIRType::MagicOptimizedArguments) {
return abort(AbortReason::Disable, "fun.apply with MaybeArguments");
}
if (argument->type() != MIRType::MagicOptimizedArguments) {
TemporaryTypeSet* objTypes = argument->resultTypeSet();
if (native && native->isNative() && native->native() == fun_apply &&
objTypes &&
objTypes->getKnownClass(constraints()) == &ArrayObject::class_ &&
!objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW) &&
ElementAccessIsPacked(constraints(), argument)) {
return jsop_funapplyarray(argc);
}
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
return makeCall(native, callInfo);
}
if ((!native || !native->isNative() || native->native() != fun_apply) &&
info().analysisMode() != Analysis_DefiniteProperties) {
return abort(AbortReason::Disable, "fun.apply speculation failed");
}
return jsop_funapplyarguments(argc);
}
AbortReasonOr<Ok> IonBuilder::jsop_spreadcall() {
#ifdef DEBUG
MDefinition* argument = current->peek(-1);
if (TemporaryTypeSet* objTypes = argument->resultTypeSet()) {
if (const Class* clasp = objTypes->getKnownClass(constraints())) {
MOZ_ASSERT(clasp == &ArrayObject::class_);
}
}
#endif
MDefinition* argArr = current->pop();
MDefinition* argThis = current->pop();
MDefinition* argFunc = current->pop();
TemporaryTypeSet* funTypes = argFunc->resultTypeSet();
JSFunction* target = getSingleCallTarget(funTypes);
WrappedFunction* wrappedTarget =
target ? new (alloc()) WrappedFunction(target) : nullptr;
MElements* elements = MElements::New(alloc(), argArr);
current->add(elements);
MApplyArray* apply =
MApplyArray::New(alloc(), wrappedTarget, argFunc, elements, argThis);
current->add(apply);
current->push(apply);
MOZ_TRY(resumeAfter(apply));
if (target && target->realm() == script()->realm()) {
apply->setNotCrossRealm();
}
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}
bool IonBuilder::propertyIsConstantFunction(NativeObject* nobj, jsid id,
bool (*test)(IonBuilder* builder,
JSFunction* fun)) {
if (!nobj->isSingleton()) {
return false;
}
TypeSet::ObjectKey* objKey = TypeSet::ObjectKey::get(nobj);
if (analysisContext) {
objKey->ensureTrackedProperty(analysisContext, id);
}
if (objKey->unknownProperties()) {
return false;
}
HeapTypeSetKey property = objKey->property(id);
Value value = UndefinedValue();
if (!property.constant(constraints(), &value)) {
return false;
}
return value.isObject() && value.toObject().is<JSFunction>() &&
test(this, &value.toObject().as<JSFunction>());
}
bool IonBuilder::ensureArrayPrototypeIteratorNotModified() {
NativeObject* obj = script()->global().maybeGetArrayPrototype();
if (!obj) {
return false;
}
jsid id = SYMBOL_TO_JSID(realm->runtime()->wellKnownSymbols().iterator);
return propertyIsConstantFunction(obj, id, [](auto* builder, auto* fun) {
return IsSelfHostedFunctionWithName(fun,
builder->runtime->names().ArrayValues);
});
}
bool IonBuilder::ensureArrayIteratorPrototypeNextNotModified() {
NativeObject* obj = script()->global().maybeGetArrayIteratorPrototype();
if (!obj) {
return false;
}
jsid id = NameToId(runtime->names().next);
return propertyIsConstantFunction(obj, id, [](auto* builder, auto* fun) {
return IsSelfHostedFunctionWithName(
fun, builder->runtime->names().ArrayIteratorNext);
});
}
AbortReasonOr<Ok> IonBuilder::jsop_optimize_spreadcall() {
MDefinition* arr = current->peek(-1);
arr->setImplicitlyUsedUnchecked();
bool result = false;
do {
TemporaryTypeSet* types = arr->resultTypeSet();
if (!types || types->getKnownClass(constraints()) != &ArrayObject::class_) {
break;
}
if (types->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED)) {
break;
}
JSObject* proto;
if (!types->getCommonPrototype(constraints(), &proto)) {
break;
}
NativeObject* arrayProto = script()->global().maybeGetArrayPrototype();
if (!arrayProto || arrayProto != proto) {
break;
}
jsid id = SYMBOL_TO_JSID(realm->runtime()->wellKnownSymbols().iterator);
bool res;
MOZ_TRY_VAR(res, testNotDefinedProperty(arr, id, true));
if (!res) {
break;
}
if (!ensureArrayPrototypeIteratorNotModified()) {
break;
}
if (!ensureArrayIteratorPrototypeNextNotModified()) {
break;
}
result = true;
} while (false);
if (result) {
auto* ins = MIsPackedArray::New(alloc(), arr);
current->add(ins);
current->push(ins);
} else {
pushConstant(BooleanValue(false));
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_funapplyarray(uint32_t argc) {
MOZ_ASSERT(argc == 2);
int funcDepth = -((int)argc + 1);
TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
JSFunction* target = getSingleCallTarget(funTypes);
MDefinition* argObj = current->pop();
MElements* elements = MElements::New(alloc(), argObj);
current->add(elements);
MDefinition* argThis = current->pop();
MDefinition* argFunc = current->pop();
MDefinition* nativeFunc = current->pop();
nativeFunc->setImplicitlyUsedUnchecked();
WrappedFunction* wrappedTarget =
target ? new (alloc()) WrappedFunction(target) : nullptr;
MApplyArray* apply =
MApplyArray::New(alloc(), wrappedTarget, argFunc, elements, argThis);
current->add(apply);
current->push(apply);
MOZ_TRY(resumeAfter(apply));
if (target && target->realm() == script()->realm()) {
apply->setNotCrossRealm();
}
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}
AbortReasonOr<Ok> CallInfo::savePriorCallStack(MIRGenerator* mir,
MBasicBlock* current,
size_t peekDepth) {
MOZ_ASSERT(priorArgs_.empty());
if (!priorArgs_.reserve(peekDepth)) {
return mir->abort(AbortReason::Alloc);
}
while (peekDepth) {
priorArgs_.infallibleAppend(current->peek(0 - int32_t(peekDepth)));
peekDepth--;
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_funapplyarguments(uint32_t argc) {
int funcDepth = -((int)argc + 1);
TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
JSFunction* target = getSingleCallTarget(funTypes);
if (inliningDepth_ == 0 &&
info().analysisMode() != Analysis_DefiniteProperties) {
MDefinition* vp = current->pop();
vp->setImplicitlyUsedUnchecked();
MDefinition* argThis = current->pop();
MDefinition* argFunc = current->pop();
MDefinition* nativeFunc = current->pop();
nativeFunc->setImplicitlyUsedUnchecked();
MArgumentsLength* numArgs = MArgumentsLength::New(alloc());
current->add(numArgs);
WrappedFunction* wrappedTarget =
target ? new (alloc()) WrappedFunction(target) : nullptr;
MApplyArgs* apply =
MApplyArgs::New(alloc(), wrappedTarget, argFunc, numArgs, argThis);
current->add(apply);
current->push(apply);
MOZ_TRY(resumeAfter(apply));
if (target && target->realm() == script()->realm()) {
apply->setNotCrossRealm();
}
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
MOZ_TRY(callInfo.savePriorCallStack(this, current, 4));
MDefinition* vp = current->pop();
vp->setImplicitlyUsedUnchecked();
if (inliningDepth_) {
if (!callInfo.setArgs(inlineCallInfo_->argv())) {
return abort(AbortReason::Alloc);
}
}
MDefinition* argThis = current->pop();
callInfo.setThis(argThis);
MDefinition* argFunc = current->pop();
callInfo.setFun(argFunc);
MDefinition* nativeFunc = current->pop();
nativeFunc->setImplicitlyUsedUnchecked();
InliningDecision decision = makeInliningDecision(target, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline: {
InliningStatus status;
MOZ_TRY_VAR(status, inlineSingleCall(callInfo, target));
if (status == InliningStatus_Inlined) {
return Ok();
}
}
}
return makeCall(target, callInfo);
}
AbortReasonOr<Ok> IonBuilder::jsop_call(uint32_t argc, bool constructing,
bool ignoresReturnValue) {
startTrackingOptimizations();
TemporaryTypeSet* observed = bytecodeTypes(pc);
if (observed->empty()) {
if (BytecodeFlowsToBitop(pc)) {
observed->addType(TypeSet::Int32Type(), alloc_->lifoAlloc());
} else if (*GetNextPc(pc) == JSOP_POS) {
observed->addType(TypeSet::DoubleType(), alloc_->lifoAlloc());
}
}
int calleeDepth = -((int)argc + 2 + constructing);
InliningTargets targets(alloc());
TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
if (calleeTypes) {
MOZ_TRY(getPolyCallTargets(calleeTypes, constructing, targets, 4));
}
CallInfo callInfo(alloc(), pc, constructing, ignoresReturnValue);
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
InliningStatus status;
MOZ_TRY_VAR(status, inlineCallsite(targets, callInfo));
if (status == InliningStatus_Inlined) {
return Ok();
}
replaceMaybeFallbackFunctionGetter(nullptr);
Maybe<CallTargets> callTargets;
if (!targets.empty()) {
callTargets.emplace(alloc());
for (const InliningTarget& target : targets) {
if (!target.target->is<JSFunction>()) {
callTargets = Nothing();
break;
}
if (!callTargets->append(&target.target->as<JSFunction>())) {
return abort(AbortReason::Alloc);
}
}
}
if (status == InliningStatus_WarmUpCountTooLow && callTargets &&
callTargets->length() == 1 && isHighestOptimizationLevel()) {
JSFunction* target = callTargets.ref()[0];
MRecompileCheck* check =
MRecompileCheck::New(alloc(), target->nonLazyScript(),
optimizationInfo().inliningRecompileThreshold(),
MRecompileCheck::RecompileCheckType::Inlining);
current->add(check);
}
return makeCall(callTargets, callInfo);
}
AbortReasonOr<bool> IonBuilder::testShouldDOMCall(TypeSet* inTypes,
JSFunction* func,
JSJitInfo::OpType opType) {
if (!func->isNative() || !func->hasJitInfo()) {
return false;
}
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
return false;
}
DOMInstanceClassHasProtoAtDepth instanceChecker =
realm->runtime()->DOMcallbacks()->instanceClassMatchesProto;
const JSJitInfo* jinfo = func->jitInfo();
if (jinfo->type() != opType) {
return false;
}
for (unsigned i = 0; i < inTypes->getObjectCount(); i++) {
TypeSet::ObjectKey* key = inTypes->getObject(i);
if (!key) {
continue;
}
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
if (!key->hasStableClassAndProto(constraints())) {
return false;
}
if (!instanceChecker(key->clasp(), jinfo->protoID, jinfo->depth)) {
return false;
}
}
return true;
}
static bool ArgumentTypesMatch(MDefinition* def, StackTypeSet* calleeTypes) {
if (!calleeTypes) {
return false;
}
if (def->resultTypeSet()) {
MOZ_ASSERT(def->type() == MIRType::Value || def->mightBeType(def->type()));
return def->resultTypeSet()->isSubset(calleeTypes);
}
if (def->type() == MIRType::Value) {
return false;
}
if (def->type() == MIRType::Object) {
return calleeTypes->unknownObject();
}
return calleeTypes->mightBeMIRType(def->type());
}
bool IonBuilder::testNeedsArgumentCheck(JSFunction* target,
CallInfo& callInfo) {
if (target->isNative()) {
return false;
}
if (!target->hasScript()) {
return true;
}
JSScript* targetScript = target->nonLazyScript();
if (!ArgumentTypesMatch(callInfo.thisArg(),
TypeScript::ThisTypes(targetScript))) {
return true;
}
uint32_t expected_args = Min<uint32_t>(callInfo.argc(), target->nargs());
for (size_t i = 0; i < expected_args; i++) {
if (!ArgumentTypesMatch(callInfo.getArg(i),
TypeScript::ArgTypes(targetScript, i))) {
return true;
}
}
for (size_t i = callInfo.argc(); i < target->nargs(); i++) {
if (!TypeScript::ArgTypes(targetScript, i)
->mightBeMIRType(MIRType::Undefined)) {
return true;
}
}
return false;
}
AbortReasonOr<MCall*> IonBuilder::makeCallHelper(
const Maybe<CallTargets>& targets, CallInfo& callInfo) {
MOZ_ASSERT_IF(targets, !targets->empty());
JSFunction* target = nullptr;
if (targets && targets->length() == 1) {
target = targets.ref()[0];
}
uint32_t targetArgs = callInfo.argc();
if (target && !target->isNativeWithCppEntry()) {
targetArgs = Max<uint32_t>(target->nargs(), callInfo.argc());
}
bool isDOMCall = false;
DOMObjectKind objKind = DOMObjectKind::Unknown;
if (target && !callInfo.constructing()) {
TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet();
if (thisTypes && thisTypes->getKnownMIRType() == MIRType::Object &&
thisTypes->isDOMClass(constraints(), &objKind)) {
MOZ_TRY_VAR(isDOMCall,
testShouldDOMCall(thisTypes, target, JSJitInfo::Method));
}
}
MCall* call =
MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(),
callInfo.argc(), callInfo.constructing(),
callInfo.ignoresReturnValue(), isDOMCall, objKind);
if (!call) {
return abort(AbortReason::Alloc);
}
if (callInfo.constructing()) {
call->addArg(targetArgs + 1, callInfo.getNewTarget());
}
MOZ_ASSERT_IF(target && targetArgs > callInfo.argc(),
!target->isNativeWithCppEntry());
for (int i = targetArgs; i > (int)callInfo.argc(); i--) {
MConstant* undef = constant(UndefinedValue());
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
call->addArg(i, undef);
}
for (int32_t i = callInfo.argc() - 1; i >= 0; i--) {
call->addArg(i + 1, callInfo.getArg(i));
}
call->computeMovable();
if (callInfo.constructing()) {
MDefinition* create =
createThis(target, callInfo.fun(), callInfo.getNewTarget());
if (!create) {
return abort(AbortReason::Disable,
"Failure inlining constructor for call.");
}
callInfo.thisArg()->setImplicitlyUsedUnchecked();
callInfo.setThis(create);
}
MDefinition* thisArg = callInfo.thisArg();
call->addArg(0, thisArg);
if (targets) {
call->disableClassCheck();
bool needArgCheck = false;
bool maybeCrossRealm = false;
for (JSFunction* target : targets.ref()) {
if (testNeedsArgumentCheck(target, callInfo)) {
needArgCheck = true;
}
if (target->realm() != script()->realm()) {
maybeCrossRealm = true;
}
}
if (!needArgCheck) {
call->disableArgCheck();
}
if (!maybeCrossRealm) {
call->setNotCrossRealm();
}
}
call->initFunction(callInfo.fun());
current->add(call);
return call;
}
static bool DOMCallNeedsBarrier(const JSJitInfo* jitinfo,
TemporaryTypeSet* types) {
MOZ_ASSERT(jitinfo->type() != JSJitInfo::InlinableNative);
if (jitinfo->returnType() == JSVAL_TYPE_UNKNOWN) {
return true;
}
if (jitinfo->returnType() == JSVAL_TYPE_OBJECT) {
return true;
}
return MIRTypeFromValueType(jitinfo->returnType()) !=
types->getKnownMIRType();
}
AbortReasonOr<Ok> IonBuilder::makeCall(const Maybe<CallTargets>& targets,
CallInfo& callInfo) {
#ifdef DEBUG
if (callInfo.constructing() && targets) {
for (JSFunction* target : targets.ref()) {
MOZ_ASSERT(target->isConstructor());
}
}
#endif
MCall* call;
MOZ_TRY_VAR(call, makeCallHelper(targets, callInfo));
current->push(call);
if (call->isEffectful()) {
MOZ_TRY(resumeAfter(call));
}
TemporaryTypeSet* types = bytecodeTypes(pc);
if (call->isCallDOMNative()) {
return pushDOMTypeBarrier(call, types,
call->getSingleTarget()->rawJSFunction());
}
return pushTypeBarrier(call, types, BarrierKind::TypeSet);
}
AbortReasonOr<Ok> IonBuilder::makeCall(JSFunction* target, CallInfo& callInfo) {
Maybe<CallTargets> targets;
if (target) {
targets.emplace(alloc());
if (!targets->append(target)) {
return abort(AbortReason::Alloc);
}
}
return makeCall(targets, callInfo);
}
AbortReasonOr<Ok> IonBuilder::jsop_eval(uint32_t argc) {
int calleeDepth = -((int)argc + 2);
TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
if (calleeTypes && calleeTypes->empty()) {
return jsop_call(argc, false, false);
}
JSFunction* target = getSingleCallTarget(calleeTypes);
if (!target) {
return abort(AbortReason::Disable, "No single callee for eval()");
}
if (script()->global().valueIsEval(ObjectValue(*target))) {
if (argc != 1) {
return abort(AbortReason::Disable,
"Direct eval with more than one argument");
}
if (!info().funMaybeLazy()) {
return abort(AbortReason::Disable, "Direct eval in global code");
}
if (info().funMaybeLazy()->isArrow()) {
return abort(AbortReason::Disable, "Direct eval from arrow function");
}
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, argc)) {
return abort(AbortReason::Alloc);
}
callInfo.setImplicitlyUsedUnchecked();
callInfo.fun()->setImplicitlyUsedUnchecked();
MDefinition* envChain = current->environmentChain();
MDefinition* string = callInfo.getArg(0);
if (!string->mightBeType(MIRType::String)) {
current->push(string);
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(string, types, BarrierKind::TypeSet);
}
MOZ_TRY(jsop_newtarget());
MDefinition* newTargetValue = current->pop();
if (string->isConcat() &&
string->getOperand(1)->type() == MIRType::String &&
string->getOperand(1)->maybeConstantValue() &&
!script()->trackRecordReplayProgress()) {
JSAtom* atom =
&string->getOperand(1)->maybeConstantValue()->toString()->asAtom();
if (StringEqualsAscii(atom, "()")) {
MDefinition* name = string->getOperand(0);
MInstruction* dynamicName =
MGetDynamicName::New(alloc(), envChain, name);
current->add(dynamicName);
current->push(dynamicName);
current->push(constant(UndefinedValue()));
CallInfo evalCallInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!evalCallInfo.init(current, 0)) {
return abort(AbortReason::Alloc);
}
return makeCall(nullptr, evalCallInfo);
}
}
MInstruction* ins =
MCallDirectEval::New(alloc(), envChain, string, newTargetValue, pc);
current->add(ins);
current->push(ins);
TemporaryTypeSet* types = bytecodeTypes(pc);
MOZ_TRY(resumeAfter(ins));
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
return jsop_call(argc, false, false);
}
AbortReasonOr<Ok> IonBuilder::jsop_compare(JSOp op) {
MDefinition* right = current->pop();
MDefinition* left = current->pop();
return jsop_compare(op, left, right);
}
AbortReasonOr<Ok> IonBuilder::jsop_compare(JSOp op, MDefinition* left,
MDefinition* right) {
bool canTrackOptimization = !IsCallPC(pc);
bool emitted = false;
if (canTrackOptimization) {
startTrackingOptimizations();
}
if (!forceInlineCaches()) {
MOZ_TRY(compareTryCharacter(&emitted, op, left, right));
if (emitted) {
return Ok();
}
MOZ_TRY(compareTrySpecialized(&emitted, op, left, right));
if (emitted) {
return Ok();
}
MOZ_TRY(compareTryBitwise(&emitted, op, left, right));
if (emitted) {
return Ok();
}
MOZ_TRY(
compareTrySpecializedOnBaselineInspector(&emitted, op, left, right));
if (emitted) {
return Ok();
}
}
MOZ_TRY(compareTryBinaryStub(&emitted, left, right));
if (emitted) {
return Ok();
}
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::Compare_Call);
}
MCompare* ins = MCompare::New(alloc(), left, right, op);
ins->cacheOperandMightEmulateUndefined(constraints());
current->add(ins);
current->push(ins);
if (ins->isEffectful()) {
MOZ_TRY(resumeAfter(ins));
}
if (canTrackOptimization) {
trackOptimizationSuccess();
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::compareTryCharacter(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::Compare_Character);
}
MConstant* constant;
MDefinition* operand;
if (left->isConstant()) {
constant = left->toConstant();
operand = right;
} else if (right->isConstant()) {
constant = right->toConstant();
operand = left;
} else {
return Ok();
}
if (constant->type() != MIRType::String ||
constant->toString()->length() != 1) {
return Ok();
}
if (!operand->isFromCharCode() ||
!operand->toFromCharCode()->input()->isCharCodeAt()) {
return Ok();
}
char16_t charCode = constant->toString()->asAtom().latin1OrTwoByteChar(0);
constant->setImplicitlyUsedUnchecked();
MConstant* charCodeConst = MConstant::New(alloc(), Int32Value(charCode));
current->add(charCodeConst);
MDefinition* charCodeAt = operand->toFromCharCode()->input();
operand->setImplicitlyUsedUnchecked();
if (left == constant) {
left = charCodeConst;
right = charCodeAt;
} else {
left = charCodeAt;
right = charCodeConst;
}
MCompare* ins = MCompare::New(alloc(), left, right, op);
ins->setCompareType(MCompare::Compare_Int32);
ins->cacheOperandMightEmulateUndefined(constraints());
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
static bool ObjectOrSimplePrimitive(MDefinition* op) {
return !op->mightBeType(MIRType::String) &&
!op->mightBeType(MIRType::BigInt) &&
!op->mightBeType(MIRType::Double) &&
!op->mightBeType(MIRType::Float32) &&
!op->mightBeType(MIRType::MagicOptimizedArguments) &&
!op->mightBeType(MIRType::MagicHole) &&
!op->mightBeType(MIRType::MagicIsConstructing);
}
AbortReasonOr<Ok> IonBuilder::compareTrySpecialized(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedTypes);
}
MCompare::CompareType type = MCompare::determineCompareType(op, left, right);
if (type == MCompare::Compare_Unknown) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
}
return Ok();
}
MCompare* ins = MCompare::New(alloc(), left, right, op);
ins->setCompareType(type);
ins->cacheOperandMightEmulateUndefined(constraints());
if (type == MCompare::Compare_StrictString &&
right->type() != MIRType::String) {
ins->swapOperands();
} else if (type == MCompare::Compare_Null && right->type() != MIRType::Null) {
ins->swapOperands();
} else if (type == MCompare::Compare_Undefined &&
right->type() != MIRType::Undefined) {
ins->swapOperands();
} else if (type == MCompare::Compare_Boolean &&
right->type() != MIRType::Boolean) {
ins->swapOperands();
}
if (type == MCompare::Compare_UInt32) {
ins->replaceWithUnsignedOperands();
}
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::compareTryBitwise(bool* emitted, JSOp op,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::Compare_Bitwise);
}
if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ &&
op != JSOP_STRICTNE) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::RelationalCompare);
}
return Ok();
}
if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right)) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::OperandTypeNotBitwiseComparable);
}
return Ok();
}
if (op == JSOP_EQ || op == JSOP_NE) {
if (left->maybeEmulatesUndefined(constraints()) ||
right->maybeEmulatesUndefined(constraints())) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::OperandMaybeEmulatesUndefined);
}
return Ok();
}
if ((left->mightBeType(MIRType::Undefined) &&
right->mightBeType(MIRType::Null)) ||
(left->mightBeType(MIRType::Null) &&
right->mightBeType(MIRType::Undefined))) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::LoosyUndefinedNullCompare);
}
return Ok();
}
if ((left->mightBeType(MIRType::Int32) &&
right->mightBeType(MIRType::Boolean)) ||
(left->mightBeType(MIRType::Boolean) &&
right->mightBeType(MIRType::Int32))) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::LoosyInt32BooleanCompare);
}
return Ok();
}
bool simpleLHS = left->mightBeType(MIRType::Boolean) ||
left->mightBeType(MIRType::Int32) ||
left->mightBeType(MIRType::Symbol);
bool simpleRHS = right->mightBeType(MIRType::Boolean) ||
right->mightBeType(MIRType::Int32) ||
right->mightBeType(MIRType::Symbol);
if ((left->mightBeType(MIRType::Object) && simpleRHS) ||
(right->mightBeType(MIRType::Object) && simpleLHS)) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::CallsValueOf);
}
return Ok();
}
}
MCompare* ins = MCompare::New(alloc(), left, right, op);
ins->setCompareType(MCompare::Compare_Bitwise);
ins->cacheOperandMightEmulateUndefined(constraints());
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::compareTrySpecializedOnBaselineInspector(
bool* emitted, JSOp op, MDefinition* left, MDefinition* right) {
MOZ_ASSERT(*emitted == false);
if (IsCallPC(pc)) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedOnBaselineTypes);
if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) {
trackOptimizationOutcome(TrackedOutcome::StrictCompare);
return Ok();
}
MCompare::CompareType type = inspector->expectedCompareType(pc);
if (type == MCompare::Compare_Unknown) {
trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
return Ok();
}
MCompare* ins = MCompare::New(alloc(), left, right, op);
ins->setCompareType(type);
ins->cacheOperandMightEmulateUndefined(constraints());
current->add(ins);
current->push(ins);
MOZ_ASSERT(!ins->isEffectful());
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::compareTryBinaryStub(bool* emitted,
MDefinition* left,
MDefinition* right) {
MOZ_ASSERT(*emitted == false);
if (JitOptions.disableCacheIR) {
return Ok();
}
if (IsCallPC(pc)) {
return Ok();
}
MBinaryCache* stub =
MBinaryCache::New(alloc(), left, right, MIRType::Boolean);
current->add(stub);
current->push(stub);
MOZ_TRY(resumeAfter(stub));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::newArrayTryTemplateObject(
bool* emitted, JSObject* templateObject, uint32_t length) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);
}
if (!templateObject) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
}
return Ok();
}
MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
size_t arraySlots =
gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) -
ObjectElements::VALUES_PER_HEADER;
if (length > arraySlots) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
}
return Ok();
}
gc::InitialHeap heap = templateObject->group()->initialHeap(constraints());
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewArray* ins =
MNewArray::New(alloc(), constraints(), length, templateConst, heap, pc);
current->add(ins);
current->push(ins);
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::newArrayTryVM(bool* emitted,
JSObject* templateObject,
uint32_t length) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::NewArray_Call);
}
gc::InitialHeap heap = gc::DefaultHeap;
MConstant* templateConst = MConstant::New(alloc(), NullValue());
if (templateObject) {
heap = templateObject->group()->initialHeap(constraints());
templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
}
current->add(templateConst);
MNewArray* ins =
MNewArray::NewVM(alloc(), constraints(), length, templateConst, heap, pc);
current->add(ins);
current->push(ins);
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_newarray(uint32_t length) {
JSObject* templateObject = inspector->getTemplateObject(pc);
MOZ_TRY(jsop_newarray(templateObject, length));
ObjectGroup* templateGroup = inspector->getTemplateObjectGroup(pc);
if (templateGroup) {
TemporaryTypeSet* types =
MakeSingletonTypeSet(alloc(), constraints(), templateGroup);
current->peek(-1)->setResultTypeSet(types);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_newarray(JSObject* templateObject,
uint32_t length) {
bool canTrackOptimization = !IsCallPC(pc);
bool emitted = false;
if (canTrackOptimization) {
startTrackingOptimizations();
}
MOZ_TRY(newArrayTryTemplateObject(&emitted, templateObject, length));
if (emitted) {
return Ok();
}
MOZ_TRY(newArrayTryVM(&emitted, templateObject, length));
if (emitted) {
return Ok();
}
MOZ_CRASH("newarray should have been emited");
}
AbortReasonOr<Ok> IonBuilder::jsop_newarray_copyonwrite() {
ArrayObject* templateObject = ObjectGroup::getCopyOnWriteObject(script(), pc);
ObjectGroup* group = templateObject->group();
MOZ_ASSERT_IF(
info().analysisMode() != Analysis_ArgumentsUsage,
group->hasAllFlagsDontCheckGeneration(OBJECT_FLAG_COPY_ON_WRITE));
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewArrayCopyOnWrite* ins = MNewArrayCopyOnWrite::New(
alloc(), constraints(), templateConst, group->initialHeap(constraints()));
current->add(ins);
current->push(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::newObjectTryTemplateObject(
bool* emitted, JSObject* templateObject) {
MOZ_ASSERT(*emitted == false);
bool canTrackOptimization = !IsCallPC(pc);
if (canTrackOptimization) {
trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
}
if (!templateObject) {
if (canTrackOptimization) {
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
}
return Ok();
}
MNewObject::Mode mode;
if (JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT) {
mode = MNewObject::ObjectLiteral;
} else {
mode = MNewObject::ObjectCreate;
}
gc::InitialHeap heap = templateObject->group()->initialHeap(constraints());
MConstant* templateConst =
MConstant::NewConstraintlessObject(alloc(), templateObject);
current->add(templateConst);
MNewObject* ins =
MNewObject::New(alloc(), constraints(), templateConst, heap, mode);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
if (canTrackOptimization) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::newObjectTryVM(bool* emitted,
JSObject* templateObject) {
MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT);
trackOptimizationAttempt(TrackedStrategy::NewObject_Call);
gc::InitialHeap heap = gc::DefaultHeap;
MConstant* templateConst = MConstant::New(alloc(), NullValue());
if (templateObject) {
heap = templateObject->group()->initialHeap(constraints());
templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
}
current->add(templateConst);
MNewObject* ins = MNewObject::NewVM(alloc(), constraints(), templateConst,
heap, MNewObject::ObjectLiteral);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_newobject() {
bool emitted = false;
startTrackingOptimizations();
JSObject* templateObject = inspector->getTemplateObject(pc);
MOZ_TRY(newObjectTryTemplateObject(&emitted, templateObject));
if (emitted) {
return Ok();
}
MOZ_TRY(newObjectTryVM(&emitted, templateObject));
if (emitted) {
return Ok();
}
MOZ_CRASH("newobject should have been emited");
}
AbortReasonOr<Ok> IonBuilder::jsop_initelem() {
MOZ_ASSERT(*pc == JSOP_INITELEM || *pc == JSOP_INITHIDDENELEM);
MDefinition* value = current->pop();
MDefinition* id = current->pop();
MDefinition* obj = current->peek(-1);
bool emitted = false;
if (!forceInlineCaches() && *pc == JSOP_INITELEM) {
MOZ_TRY(initOrSetElemTryDense(&emitted, obj, id, value,
true));
if (emitted) {
return Ok();
}
}
MOZ_TRY(initOrSetElemTryCache(&emitted, obj, id, value));
if (emitted) {
return Ok();
}
MInitElem* initElem = MInitElem::New(alloc(), obj, id, value);
current->add(initElem);
return resumeAfter(initElem);
}
AbortReasonOr<Ok> IonBuilder::jsop_initelem_inc() {
MDefinition* value = current->pop();
MDefinition* id = current->pop();
MDefinition* obj = current->peek(-1);
bool emitted = false;
MAdd* nextId = MAdd::New(alloc(), id, constantInt(1), MIRType::Int32);
current->add(nextId);
current->push(nextId);
if (!forceInlineCaches()) {
MOZ_TRY(initOrSetElemTryDense(&emitted, obj, id, value,
true));
if (emitted) {
return Ok();
}
}
MOZ_TRY(initOrSetElemTryCache(&emitted, obj, id, value));
if (emitted) {
return Ok();
}
MCallInitElementArray* initElem =
MCallInitElementArray::New(alloc(), obj, id, value);
current->add(initElem);
return resumeAfter(initElem);
}
AbortReasonOr<Ok> IonBuilder::jsop_initelem_array() {
MDefinition* value = current->pop();
MDefinition* obj = current->peek(-1);
bool needStub = false;
if (shouldAbortOnPreliminaryGroups(obj)) {
needStub = true;
} else if (!obj->resultTypeSet() || obj->resultTypeSet()->unknownObject() ||
obj->resultTypeSet()->getObjectCount() != 1) {
needStub = true;
} else {
MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1);
TypeSet::ObjectKey* initializer = obj->resultTypeSet()->getObject(0);
if (value->type() == MIRType::MagicHole) {
if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED)) {
needStub = true;
}
} else if (!initializer->unknownProperties()) {
HeapTypeSetKey elemTypes = initializer->property(JSID_VOID);
if (!TypeSetIncludes(elemTypes.maybeTypes(), value->type(),
value->resultTypeSet())) {
elemTypes.freeze(constraints());
needStub = true;
}
}
}
uint32_t index = GET_UINT32(pc);
if (needStub) {
MOZ_ASSERT(index <= INT32_MAX,
"the bytecode emitter must fail to compile code that would "
"produce JSOP_INITELEM_ARRAY with an index exceeding "
"int32_t range");
MCallInitElementArray* store =
MCallInitElementArray::New(alloc(), obj, constantInt(index), value);
current->add(store);
return resumeAfter(store);
}
return initializeArrayElement(obj, index, value, true);
}
AbortReasonOr<Ok> IonBuilder::initializeArrayElement(
MDefinition* obj, size_t index, MDefinition* value,
bool addResumePointAndIncrementInitializedLength) {
MConstant* id = MConstant::New(alloc(), Int32Value(index));
current->add(id);
MElements* elements = MElements::New(alloc(), obj);
current->add(elements);
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
if (obj->isNewArray() && obj->toNewArray()->convertDoubleElements()) {
MInstruction* valueDouble = MToDouble::New(alloc(), value);
current->add(valueDouble);
value = valueDouble;
}
MStoreElement* store = MStoreElement::New(alloc(), elements, id, value,
false);
current->add(store);
if (addResumePointAndIncrementInitializedLength) {
MSetInitializedLength* initLength =
MSetInitializedLength::New(alloc(), elements, id);
current->add(initLength);
MOZ_TRY(resumeAfter(initLength));
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_mutateproto() {
MDefinition* value = current->pop();
MDefinition* obj = current->peek(-1);
MMutateProto* mutate = MMutateProto::New(alloc(), obj, value);
current->add(mutate);
return resumeAfter(mutate);
}
AbortReasonOr<Ok> IonBuilder::jsop_initprop(PropertyName* name) {
bool useFastPath = false;
MDefinition* obj = current->peek(-2);
if (obj->isNewObject()) {
if (JSObject* templateObject = obj->toNewObject()->templateObject()) {
if (templateObject->is<PlainObject>()) {
if (templateObject->as<PlainObject>().containsPure(name)) {
useFastPath = true;
}
} else {
MOZ_ASSERT(
templateObject->as<UnboxedPlainObject>().layout().lookup(name));
useFastPath = true;
}
}
}
MInstructionReverseIterator last = current->rbegin();
if (useFastPath && !forceInlineCaches()) {
MOZ_TRY(jsop_setprop(name));
} else {
MDefinition* value = current->pop();
MDefinition* obj = current->pop();
bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(),
current, &obj, name, &value,
true);
bool emitted = false;
MOZ_TRY(setPropTryCache(&emitted, obj, name, value, barrier));
MOZ_ASSERT(emitted == true);
}
current->pop();
current->push(obj);
for (MInstructionReverseIterator riter = current->rbegin(); riter != last;
riter++) {
if (MResumePoint* resumePoint = riter->resumePoint()) {
MOZ_ASSERT(resumePoint->pc() == pc);
if (resumePoint->mode() == MResumePoint::ResumeAfter) {
size_t index = resumePoint->numOperands() - 1;
resumePoint->replaceOperand(index, obj);
}
break;
}
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_initprop_getter_setter(PropertyName* name) {
MDefinition* value = current->pop();
MDefinition* obj = current->peek(-1);
MInitPropGetterSetter* init =
MInitPropGetterSetter::New(alloc(), obj, name, value);
current->add(init);
return resumeAfter(init);
}
AbortReasonOr<Ok> IonBuilder::jsop_initelem_getter_setter() {
MDefinition* value = current->pop();
MDefinition* id = current->pop();
MDefinition* obj = current->peek(-1);
MInitElemGetterSetter* init =
MInitElemGetterSetter::New(alloc(), obj, id, value);
current->add(init);
return resumeAfter(init);
}
AbortReasonOr<MBasicBlock*> IonBuilder::newBlock(
size_t stackDepth, jsbytecode* pc, MBasicBlock* maybePredecessor) {
MOZ_ASSERT_IF(maybePredecessor, maybePredecessor->stackDepth() == stackDepth);
MBasicBlock* block =
MBasicBlock::New(graph(), stackDepth, info(), maybePredecessor,
bytecodeSite(pc), MBasicBlock::NORMAL);
if (!block) {
return abort(AbortReason::Alloc);
}
block->setLoopDepth(loopDepth_);
return block;
}
AbortReasonOr<MBasicBlock*> IonBuilder::newBlock(
MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint) {
MBasicBlock* block = MBasicBlock::NewWithResumePoint(
graph(), info(), predecessor, bytecodeSite(pc), priorResumePoint);
if (!block) {
return abort(AbortReason::Alloc);
}
block->setLoopDepth(loopDepth_);
return block;
}
AbortReasonOr<MBasicBlock*> IonBuilder::newBlockPopN(MBasicBlock* predecessor,
jsbytecode* pc,
uint32_t popped) {
MBasicBlock* block =
MBasicBlock::NewPopN(graph(), info(), predecessor, bytecodeSite(pc),
MBasicBlock::NORMAL, popped);
if (!block) {
return abort(AbortReason::Alloc);
}
block->setLoopDepth(loopDepth_);
return block;
}
AbortReasonOr<MBasicBlock*> IonBuilder::newBlockAfter(
MBasicBlock* at, size_t stackDepth, jsbytecode* pc,
MBasicBlock* maybePredecessor) {
MOZ_ASSERT_IF(maybePredecessor, maybePredecessor->stackDepth() == stackDepth);
MBasicBlock* block =
MBasicBlock::New(graph(), stackDepth, info(), maybePredecessor,
bytecodeSite(pc), MBasicBlock::NORMAL);
if (!block) {
return abort(AbortReason::Alloc);
}
block->setLoopDepth(loopDepth_);
block->setHitCount(0); graph().insertBlockAfter(at, block);
return block;
}
AbortReasonOr<MBasicBlock*> IonBuilder::newOsrPreheader(
MBasicBlock* predecessor, jsbytecode* loopEntry,
jsbytecode* beforeLoopEntry) {
MOZ_ASSERT(JSOp(*loopEntry) == JSOP_LOOPENTRY);
MOZ_ASSERT(loopEntry == info().osrPc());
MBasicBlock* osrBlock;
MOZ_TRY_VAR(osrBlock, newBlockAfter(*graph().begin(),
predecessor->stackDepth(), loopEntry));
MBasicBlock* preheader;
MOZ_TRY_VAR(preheader, newBlock(predecessor, loopEntry));
graph().addBlock(preheader);
if (script()->hasScriptCounts()) {
preheader->setHitCount(script()->getHitCount(beforeLoopEntry));
}
MOsrEntry* entry = MOsrEntry::New(alloc());
osrBlock->add(entry);
{
uint32_t slot = info().environmentChainSlot();
MInstruction* envv;
if (usesEnvironmentChain()) {
envv = MOsrEnvironmentChain::New(alloc(), entry);
} else {
envv = MConstant::New(alloc(), UndefinedValue());
}
osrBlock->add(envv);
osrBlock->initSlot(slot, envv);
}
{
MInstruction* returnValue;
if (!script()->noScriptRval()) {
returnValue = MOsrReturnValue::New(alloc(), entry);
} else {
returnValue = MConstant::New(alloc(), UndefinedValue());
}
osrBlock->add(returnValue);
osrBlock->initSlot(info().returnValueSlot(), returnValue);
}
bool needsArgsObj = info().needsArgsObj();
MInstruction* argsObj = nullptr;
if (info().hasArguments()) {
if (needsArgsObj) {
argsObj = MOsrArgumentsObject::New(alloc(), entry);
} else {
argsObj = MConstant::New(alloc(), UndefinedValue());
}
osrBlock->add(argsObj);
osrBlock->initSlot(info().argsObjSlot(), argsObj);
}
if (info().funMaybeLazy()) {
MParameter* thisv =
MParameter::New(alloc(), MParameter::THIS_SLOT, nullptr);
osrBlock->add(thisv);
osrBlock->initSlot(info().thisSlot(), thisv);
for (uint32_t i = 0; i < info().nargs(); i++) {
uint32_t slot =
needsArgsObj ? info().argSlotUnchecked(i) : info().argSlot(i);
if (needsArgsObj && info().argsObjAliasesFormals()) {
MOZ_ASSERT(argsObj && argsObj->isOsrArgumentsObject());
MInstruction* osrv;
if (script()->formalIsAliased(i)) {
osrv = MConstant::New(alloc(), UndefinedValue());
} else {
osrv = MGetArgumentsObjectArg::New(alloc(), argsObj, i);
}
osrBlock->add(osrv);
osrBlock->initSlot(slot, osrv);
} else {
MParameter* arg = MParameter::New(alloc(), i, nullptr);
osrBlock->add(arg);
osrBlock->initSlot(slot, arg);
}
}
}
for (uint32_t i = 0; i < info().nlocals(); i++) {
uint32_t slot = info().localSlot(i);
ptrdiff_t offset = BaselineFrame::reverseOffsetOfLocal(i);
MOsrValue* osrv = MOsrValue::New(alloc().fallible(), entry, offset);
if (!osrv) {
return abort(AbortReason::Alloc);
}
osrBlock->add(osrv);
osrBlock->initSlot(slot, osrv);
}
uint32_t numStackSlots = preheader->stackDepth() - info().firstStackSlot();
for (uint32_t i = 0; i < numStackSlots; i++) {
uint32_t slot = info().stackSlot(i);
ptrdiff_t offset =
BaselineFrame::reverseOffsetOfLocal(info().nlocals() + i);
MOsrValue* osrv = MOsrValue::New(alloc().fallible(), entry, offset);
if (!osrv) {
return abort(AbortReason::Alloc);
}
osrBlock->add(osrv);
osrBlock->initSlot(slot, osrv);
}
MStart* start = MStart::New(alloc());
osrBlock->add(start);
MOZ_TRY(resumeAt(start, loopEntry));
if (!osrBlock->linkOsrValues(start)) {
return abort(AbortReason::Alloc);
}
MOZ_ASSERT(predecessor->stackDepth() == osrBlock->stackDepth());
MOZ_ASSERT(info().environmentChainSlot() == 0);
for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) {
MDefinition* existing = current->getSlot(i);
MDefinition* def = osrBlock->getSlot(i);
MOZ_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i),
def->type() == MIRType::Value);
if (info().isSlotAliased(i)) {
continue;
}
def->setResultType(existing->type());
def->setResultTypeSet(existing->resultTypeSet());
}
osrBlock->end(MGoto::New(alloc(), preheader));
if (!preheader->addPredecessor(alloc(), osrBlock)) {
return abort(AbortReason::Alloc);
}
graph().setOsrBlock(osrBlock);
return preheader;
}
AbortReasonOr<MBasicBlock*> IonBuilder::newPendingLoopHeader(
MBasicBlock* predecessor, jsbytecode* pc, bool osr, bool canOsr,
unsigned stackPhiCount) {
if (canOsr) {
stackPhiCount = predecessor->stackDepth() - info().firstStackSlot();
}
MBasicBlock* block = MBasicBlock::NewPendingLoopHeader(
graph(), info(), predecessor, bytecodeSite(pc), stackPhiCount);
if (!block) {
return abort(AbortReason::Alloc);
}
if (osr) {
MOZ_ASSERT(info().firstLocalSlot() - info().firstArgSlot() ==
baselineFrame_->argTypes.length());
MOZ_ASSERT(block->stackDepth() - info().firstLocalSlot() ==
baselineFrame_->varTypes.length());
for (uint32_t i = info().startArgSlot(); i < block->stackDepth(); i++) {
if (info().isSlotAliased(i)) {
continue;
}
MPhi* phi = block->getSlot(i)->toPhi();
TypeSet::Type existingType = TypeSet::UndefinedType();
uint32_t arg = i - info().firstArgSlot();
uint32_t var = i - info().firstLocalSlot();
if (info().funMaybeLazy() && i == info().thisSlot()) {
existingType = baselineFrame_->thisType;
} else if (arg < info().nargs()) {
existingType = baselineFrame_->argTypes[arg];
} else {
existingType = baselineFrame_->varTypes[var];
}
if (existingType.isSingletonUnchecked()) {
checkNurseryObject(existingType.singleton());
}
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc);
TemporaryTypeSet* typeSet =
lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, existingType);
if (!typeSet) {
return abort(AbortReason::Alloc);
}
MIRType type = typeSet->getKnownMIRType();
if (!phi->addBackedgeType(alloc(), type, typeSet)) {
return abort(AbortReason::Alloc);
}
}
}
return block;
}
MTest* IonBuilder::newTest(MDefinition* ins, MBasicBlock* ifTrue,
MBasicBlock* ifFalse) {
MTest* test = MTest::New(alloc(), ins, ifTrue, ifFalse);
test->cacheOperandMightEmulateUndefined(constraints());
return test;
}
AbortReasonOr<Ok> IonBuilder::resume(MInstruction* ins, jsbytecode* pc,
MResumePoint::Mode mode) {
MOZ_ASSERT(ins->isEffectful() || !ins->isMovable());
MResumePoint* resumePoint =
MResumePoint::New(alloc(), ins->block(), pc, mode);
if (!resumePoint) {
return abort(AbortReason::Alloc);
}
ins->setResumePoint(resumePoint);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::resumeAt(MInstruction* ins, jsbytecode* pc) {
return resume(ins, pc, MResumePoint::ResumeAt);
}
AbortReasonOr<Ok> IonBuilder::resumeAfter(MInstruction* ins) {
return resume(ins, pc, MResumePoint::ResumeAfter);
}
AbortReasonOr<Ok> IonBuilder::maybeInsertResume() {
if (loopDepth_ == 0) {
return Ok();
}
MNop* ins = MNop::New(alloc());
current->add(ins);
return resumeAfter(ins);
}
void IonBuilder::maybeMarkEmpty(MDefinition* ins) {
MOZ_ASSERT(ins->type() == MIRType::Value);
for (size_t i = 0; i < ins->numOperands(); i++) {
if (!ins->getOperand(i)->emptyResultTypeSet()) {
continue;
}
TemporaryTypeSet* types = alloc().lifoAlloc()->new_<TemporaryTypeSet>();
if (types) {
ins->setResultTypeSet(types);
return;
}
}
}
static bool ClassHasEffectlessLookup(const Class* clasp) {
return (clasp == &UnboxedPlainObject::class_) || IsTypedObjectClass(clasp) ||
(clasp->isNative() && !clasp->getOpsLookupProperty());
}
static bool ObjectHasExtraOwnProperty(CompileRealm* realm,
TypeSet::ObjectKey* object, jsid id) {
if (object->isGroup() && object->group()->maybeTypeDescr()) {
return object->group()->typeDescr().hasProperty(realm->runtime()->names(),
id);
}
const Class* clasp = object->clasp();
if (clasp == &ArrayObject::class_) {
return JSID_IS_ATOM(id, realm->runtime()->names().length);
}
JSObject* singleton = object->isSingleton() ? object->singleton() : nullptr;
return ClassMayResolveId(realm->runtime()->names(), clasp, id, singleton);
}
void IonBuilder::insertRecompileCheck() {
MOZ_ASSERT(pc == script()->code() || *pc == JSOP_LOOPENTRY);
OptimizationLevel curLevel = optimizationLevel();
if (IonOptimizations.isLastLevel(curLevel) || info().isAnalysis()) {
return;
}
MOZ_ASSERT(!JitOptions.disableOptimizationLevels);
MRecompileCheck::RecompileCheckType type;
if (*pc == JSOP_LOOPENTRY) {
type = MRecompileCheck::RecompileCheckType::OptimizationLevelOSR;
} else if (this != outermostBuilder()) {
type = MRecompileCheck::RecompileCheckType::OptimizationLevelInlined;
} else {
type = MRecompileCheck::RecompileCheckType::OptimizationLevel;
}
OptimizationLevel nextLevel = IonOptimizations.nextLevel(curLevel);
const OptimizationInfo* info = IonOptimizations.get(nextLevel);
uint32_t warmUpThreshold = info->recompileWarmUpThreshold(script(), pc);
MRecompileCheck* check =
MRecompileCheck::New(alloc(), script(), warmUpThreshold, type);
current->add(check);
}
JSObject* IonBuilder::testSingletonProperty(JSObject* obj, jsid id) {
while (obj) {
if (!alloc().ensureBallast()) {
return nullptr;
}
if (!ClassHasEffectlessLookup(obj->getClass())) {
return nullptr;
}
TypeSet::ObjectKey* objKey = TypeSet::ObjectKey::get(obj);
if (analysisContext) {
objKey->ensureTrackedProperty(analysisContext, id);
}
if (objKey->unknownProperties()) {
return nullptr;
}
HeapTypeSetKey property = objKey->property(id);
if (property.isOwnProperty(constraints())) {
if (obj->isSingleton()) {
return property.singleton(constraints());
}
return nullptr;
}
if (ObjectHasExtraOwnProperty(realm, objKey, id)) {
return nullptr;
}
obj = checkNurseryObject(obj->staticPrototype());
}
return nullptr;
}
JSObject* IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id) {
TemporaryTypeSet* types = obj->resultTypeSet();
if (types && types->unknownObject()) {
return nullptr;
}
JSObject* objectSingleton = types ? types->maybeSingleton() : nullptr;
if (objectSingleton) {
return testSingletonProperty(objectSingleton, id);
}
MIRType objType = obj->type();
if (objType == MIRType::Value && types) {
objType = types->getKnownMIRType();
}
JSProtoKey key;
switch (objType) {
case MIRType::String:
key = JSProto_String;
break;
case MIRType::Symbol:
key = JSProto_Symbol;
break;
case MIRType::BigInt:
key = JSProto_BigInt;
break;
case MIRType::Int32:
case MIRType::Double:
key = JSProto_Number;
break;
case MIRType::Boolean:
key = JSProto_Boolean;
break;
case MIRType::Object: {
if (!types) {
return nullptr;
}
JSObject* singleton = nullptr;
for (unsigned i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
if (!alloc().ensureBallast()) {
return nullptr;
}
if (analysisContext) {
key->ensureTrackedProperty(analysisContext, id);
}
const Class* clasp = key->clasp();
if (!ClassHasEffectlessLookup(clasp) ||
ObjectHasExtraOwnProperty(realm, key, id)) {
return nullptr;
}
if (key->unknownProperties()) {
return nullptr;
}
HeapTypeSetKey property = key->property(id);
if (property.isOwnProperty(constraints())) {
return nullptr;
}
if (JSObject* proto =
checkNurseryObject(key->proto().toObjectOrNull())) {
JSObject* thisSingleton = testSingletonProperty(proto, id);
if (!thisSingleton) {
return nullptr;
}
if (singleton) {
if (thisSingleton != singleton) {
return nullptr;
}
} else {
singleton = thisSingleton;
}
} else {
return nullptr;
}
}
return singleton;
}
default:
return nullptr;
}
if (JSObject* proto = script()->global().maybeGetPrototype(key)) {
return testSingletonProperty(proto, id);
}
return nullptr;
}
AbortReasonOr<bool> IonBuilder::testNotDefinedProperty(
MDefinition* obj, jsid id, bool ownProperty ) {
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types || types->unknownObject() ||
types->getKnownMIRType() != MIRType::Object) {
return false;
}
for (unsigned i = 0, count = types->getObjectCount(); i < count; i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
while (true) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
if (!key->hasStableClassAndProto(constraints()) ||
key->unknownProperties()) {
return false;
}
const Class* clasp = key->clasp();
if (!ClassHasEffectlessLookup(clasp) ||
ObjectHasExtraOwnProperty(realm, key, id)) {
return false;
}
if (key->isSingleton() && key->singleton()->is<NativeObject>() &&
key->singleton()->as<NativeObject>().lookupPure(id)) {
return false;
}
HeapTypeSetKey property = key->property(id);
if (property.isOwnProperty(constraints())) {
return false;
}
if (ownProperty) {
break;
}
JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
if (!proto) {
break;
}
key = TypeSet::ObjectKey::get(proto);
}
}
return true;
}
AbortReasonOr<Ok> IonBuilder::pushTypeBarrier(MDefinition* def,
TemporaryTypeSet* observed,
BarrierKind kind) {
MOZ_ASSERT(def == current->peek(-1));
MDefinition* replace = addTypeBarrier(current->pop(), observed, kind);
if (!replace) {
return abort(AbortReason::Alloc);
}
current->push(replace);
return Ok();
}
MDefinition* IonBuilder::addTypeBarrier(MDefinition* def,
TemporaryTypeSet* observed,
BarrierKind kind,
MTypeBarrier** pbarrier) {
if (BytecodeIsPopped(pc)) {
return def;
}
if (kind == BarrierKind::NoBarrier) {
MDefinition* replace = ensureDefiniteType(def, observed->getKnownMIRType());
replace->setResultTypeSet(observed);
return replace;
}
if (observed->unknown()) {
return def;
}
MTypeBarrier* barrier = MTypeBarrier::New(alloc(), def, observed, kind);
current->add(barrier);
if (pbarrier) {
*pbarrier = barrier;
}
if (barrier->type() == MIRType::Undefined) {
return constant(UndefinedValue());
}
if (barrier->type() == MIRType::Null) {
return constant(NullValue());
}
return barrier;
}
AbortReasonOr<Ok> IonBuilder::pushDOMTypeBarrier(MInstruction* ins,
TemporaryTypeSet* observed,
JSFunction* func) {
MOZ_ASSERT(func && func->isNative() && func->hasJitInfo());
const JSJitInfo* jitinfo = func->jitInfo();
bool barrier = DOMCallNeedsBarrier(jitinfo, observed);
MDefinition* replace = ins;
if (jitinfo->returnType() != JSVAL_TYPE_DOUBLE ||
observed->getKnownMIRType() != MIRType::Int32) {
replace =
ensureDefiniteType(ins, MIRTypeFromValueType(jitinfo->returnType()));
if (replace != ins) {
current->pop();
current->push(replace);
}
} else {
MOZ_ASSERT(barrier);
}
return pushTypeBarrier(
replace, observed,
barrier ? BarrierKind::TypeSet : BarrierKind::NoBarrier);
}
MDefinition* IonBuilder::ensureDefiniteType(MDefinition* def,
MIRType definiteType) {
MInstruction* replace;
switch (definiteType) {
case MIRType::Undefined:
def->setImplicitlyUsedUnchecked();
replace = MConstant::New(alloc(), UndefinedValue());
break;
case MIRType::Null:
def->setImplicitlyUsedUnchecked();
replace = MConstant::New(alloc(), NullValue());
break;
case MIRType::Value:
return def;
default: {
if (def->type() != MIRType::Value) {
if (def->type() == MIRType::Int32 && definiteType == MIRType::Double) {
replace = MToDouble::New(alloc(), def);
break;
}
return def;
}
replace = MUnbox::New(alloc(), def, definiteType, MUnbox::Infallible);
break;
}
}
current->add(replace);
return replace;
}
static size_t NumFixedSlots(JSObject* object) {
gc::AllocKind kind = object->asTenured().getAllocKind();
return gc::GetGCKindSlots(kind, object->getClass());
}
static bool IsUninitializedGlobalLexicalSlot(JSObject* obj,
PropertyName* name) {
LexicalEnvironmentObject& globalLexical = obj->as<LexicalEnvironmentObject>();
MOZ_ASSERT(globalLexical.isGlobal());
Shape* shape = globalLexical.lookupPure(name);
if (!shape) {
return false;
}
return globalLexical.getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL);
}
AbortReasonOr<Ok> IonBuilder::getStaticName(bool* emitted,
JSObject* staticObject,
PropertyName* name,
MDefinition* lexicalCheck) {
MOZ_ASSERT(*emitted == false);
jsid id = NameToId(name);
bool isGlobalLexical =
staticObject->is<LexicalEnvironmentObject>() &&
staticObject->as<LexicalEnvironmentObject>().isGlobal();
MOZ_ASSERT(isGlobalLexical || staticObject->is<GlobalObject>());
MOZ_ASSERT(staticObject->isSingleton());
if (lexicalCheck) {
return Ok();
}
TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject);
if (analysisContext) {
staticKey->ensureTrackedProperty(analysisContext, NameToId(name));
}
if (staticKey->unknownProperties()) {
return Ok();
}
HeapTypeSetKey property = staticKey->property(id);
if (!property.maybeTypes() || !property.maybeTypes()->definiteProperty() ||
property.nonData(constraints())) {
return Ok();
}
if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name)) {
return Ok();
}
*emitted = true;
TemporaryTypeSet* types = bytecodeTypes(pc);
BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), staticKey, name, types,
true);
if (barrier == BarrierKind::NoBarrier) {
JSObject* singleton = types->maybeSingleton();
if (singleton) {
if (testSingletonProperty(staticObject, id) == singleton) {
pushConstant(ObjectValue(*singleton));
return Ok();
}
}
Value constantValue;
if (property.constant(constraints(), &constantValue)) {
pushConstant(constantValue);
return Ok();
}
}
MOZ_TRY(loadStaticSlot(staticObject, barrier, types,
property.maybeTypes()->definiteSlot()));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::loadStaticSlot(JSObject* staticObject,
BarrierKind barrier,
TemporaryTypeSet* types,
uint32_t slot) {
if (barrier == BarrierKind::NoBarrier) {
MIRType knownType = types->getKnownMIRType();
if (knownType == MIRType::Undefined) {
pushConstant(UndefinedValue());
return Ok();
}
if (knownType == MIRType::Null) {
pushConstant(NullValue());
return Ok();
}
}
MInstruction* obj = constant(ObjectValue(*staticObject));
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier) {
rvalType = MIRType::Value;
}
return loadSlot(obj, slot, NumFixedSlots(staticObject), rvalType, barrier,
types);
}
bool IonBuilder::needsPostBarrier(MDefinition* value) {
CompileZone* zone = realm->zone();
if (!zone->nurseryExists()) {
return false;
}
if (value->mightBeType(MIRType::Object)) {
return true;
}
if (value->mightBeType(MIRType::String) &&
zone->canNurseryAllocateStrings()) {
return true;
}
return false;
}
AbortReasonOr<Ok> IonBuilder::setStaticName(JSObject* staticObject,
PropertyName* name) {
jsid id = NameToId(name);
bool isGlobalLexical =
staticObject->is<LexicalEnvironmentObject>() &&
staticObject->as<LexicalEnvironmentObject>().isGlobal();
MOZ_ASSERT(isGlobalLexical || staticObject->is<GlobalObject>());
MDefinition* value = current->peek(-1);
TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject);
if (staticKey->unknownProperties()) {
return jsop_setprop(name);
}
HeapTypeSetKey property = staticKey->property(id);
if (!property.maybeTypes() || !property.maybeTypes()->definiteProperty() ||
property.nonData(constraints()) || property.nonWritable(constraints())) {
return jsop_setprop(name);
}
if (!CanWriteProperty(alloc(), constraints(), property, value)) {
return jsop_setprop(name);
}
if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name)) {
return jsop_setprop(name);
}
current->pop();
MDefinition* obj = current->pop();
MOZ_ASSERT(&obj->toConstant()->toObject() == staticObject);
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
MIRType slotType = MIRType::None;
MIRType knownType = property.knownMIRType(constraints());
if (knownType != MIRType::Value) {
slotType = knownType;
}
bool needsPreBarrier = property.needsBarrier(constraints());
return storeSlot(obj, property.maybeTypes()->definiteSlot(),
NumFixedSlots(staticObject), value, needsPreBarrier,
slotType);
}
JSObject* IonBuilder::testGlobalLexicalBinding(PropertyName* name) {
MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME || JSOp(*pc) == JSOP_GETGNAME ||
JSOp(*pc) == JSOP_SETGNAME || JSOp(*pc) == JSOP_STRICTSETGNAME);
NativeObject* obj = &script()->global().lexicalEnvironment();
TypeSet::ObjectKey* lexicalKey = TypeSet::ObjectKey::get(obj);
jsid id = NameToId(name);
if (analysisContext) {
lexicalKey->ensureTrackedProperty(analysisContext, id);
}
Maybe<HeapTypeSetKey> lexicalProperty;
if (!lexicalKey->unknownProperties()) {
lexicalProperty.emplace(lexicalKey->property(id));
}
Shape* shape = obj->lookupPure(name);
if (shape) {
if ((JSOp(*pc) != JSOP_GETGNAME && !shape->writable()) ||
obj->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
return nullptr;
}
} else {
shape = script()->global().lookupPure(name);
if (!shape || shape->configurable()) {
if (lexicalProperty.isSome()) {
MOZ_ALWAYS_FALSE(lexicalProperty->isOwnProperty(constraints()));
} else {
return nullptr;
}
}
obj = &script()->global();
}
return obj;
}
AbortReasonOr<Ok> IonBuilder::jsop_getgname(PropertyName* name) {
if (name == names().undefined) {
pushConstant(UndefinedValue());
return Ok();
}
if (name == names().NaN) {
pushConstant(realm->runtime()->NaNValue());
return Ok();
}
if (name == names().Infinity) {
pushConstant(realm->runtime()->positiveInfinityValue());
return Ok();
}
if (JSObject* obj = testGlobalLexicalBinding(name)) {
bool emitted = false;
MOZ_TRY(getStaticName(&emitted, obj, name));
if (emitted) {
return Ok();
}
if (!forceInlineCaches() && obj->is<GlobalObject>()) {
TemporaryTypeSet* types = bytecodeTypes(pc);
MDefinition* globalObj = constant(ObjectValue(*obj));
MOZ_TRY(
getPropTryCommonGetter(&emitted, globalObj, NameToId(name), types));
if (emitted) {
return Ok();
}
}
}
return jsop_getname(name);
}
AbortReasonOr<Ok> IonBuilder::jsop_getname(PropertyName* name) {
MDefinition* object;
if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope()) {
object = constant(ObjectValue(script()->global().lexicalEnvironment()));
} else {
object = current->environmentChain();
}
MGetNameCache* ins = MGetNameCache::New(alloc(), object);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
AbortReasonOr<Ok> IonBuilder::jsop_intrinsic(PropertyName* name) {
TemporaryTypeSet* types = bytecodeTypes(pc);
Value vp = UndefinedValue();
if (!script()->global().maybeExistingIntrinsicValue(name, &vp)) {
MCallGetIntrinsicValue* ins = MCallGetIntrinsicValue::New(alloc(), name);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
if (types->empty()) {
types->addType(TypeSet::GetValueType(vp), alloc().lifoAlloc());
}
MOZ_ASSERT(types->hasType(TypeSet::GetValueType(vp)));
pushConstant(vp);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_getimport(PropertyName* name) {
ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script());
MOZ_ASSERT(env);
Shape* shape;
ModuleEnvironmentObject* targetEnv;
MOZ_ALWAYS_TRUE(env->lookupImport(NameToId(name), &targetEnv, &shape));
TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(targetEnv);
TemporaryTypeSet* types = bytecodeTypes(pc);
BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), staticKey, name, types,
true);
MOZ_TRY(loadStaticSlot(targetEnv, barrier, types, shape->slot()));
if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
MDefinition* checked;
MOZ_TRY_VAR(checked, addLexicalCheck(current->pop()));
current->push(checked);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_bindname(PropertyName* name) {
MDefinition* envChain;
if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope()) {
envChain = constant(ObjectValue(script()->global().lexicalEnvironment()));
} else {
envChain = current->environmentChain();
}
MBindNameCache* ins =
MBindNameCache::New(alloc(), envChain, name, script(), pc);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_bindvar() {
MOZ_ASSERT(usesEnvironmentChain());
MCallBindVar* ins = MCallBindVar::New(alloc(), current->environmentChain());
current->add(ins);
current->push(ins);
return Ok();
}
static MIRType GetElemKnownType(bool needsHoleCheck, TemporaryTypeSet* types) {
MIRType knownType = types->getKnownMIRType();
if (knownType == MIRType::Undefined || knownType == MIRType::Null) {
knownType = MIRType::Value;
}
if (needsHoleCheck && !LIRGenerator::allowTypedElementHoleCheck()) {
knownType = MIRType::Value;
}
return knownType;
}
AbortReasonOr<Ok> IonBuilder::jsop_getelem() {
startTrackingOptimizations();
MDefinition* index = current->pop();
MDefinition* obj = current->pop();
trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());
if (info().isAnalysis() || shouldAbortOnPreliminaryGroups(obj)) {
MInstruction* ins = MCallGetElement::New(alloc(), obj, index);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
bool emitted = false;
if (obj->mightBeType(MIRType::MagicOptimizedArguments) &&
!info().isAnalysis()) {
trackOptimizationAttempt(TrackedStrategy::GetElem_Arguments);
MOZ_TRY(getElemTryArguments(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedConstant);
MOZ_TRY(getElemTryArgumentsInlinedConstant(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedSwitch);
MOZ_TRY(getElemTryArgumentsInlinedIndex(&emitted, obj, index));
if (emitted) {
return Ok();
}
if (script()->argumentsHasVarBinding()) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return abort(AbortReason::Disable,
"Type is not definitely lazy arguments.");
}
}
obj = maybeUnboxForPropertyAccess(obj);
if (obj->type() == MIRType::Object) {
obj = convertUnboxedObjects(obj);
}
if (!forceInlineCaches()) {
MOZ_TRY(getElemTryGetProp(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_CallSiteObject);
MOZ_TRY(getElemTryCallSiteObject(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_Dense);
MOZ_TRY(getElemTryDense(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_TypedArray);
MOZ_TRY(getElemTryTypedArray(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_String);
MOZ_TRY(getElemTryString(&emitted, obj, index));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetElem_TypedObject);
MOZ_TRY(getElemTryTypedObject(&emitted, obj, index));
if (emitted) {
return Ok();
}
}
trackOptimizationAttempt(TrackedStrategy::GetElem_InlineCache);
return getElemAddCache(obj, index);
}
AbortReasonOr<Ok> IonBuilder::getElemTryTypedObject(bool* emitted,
MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject);
TypedObjectPrediction objPrediction = typedObjectPrediction(obj);
if (objPrediction.isUseless()) {
return Ok();
}
if (!objPrediction.ofArrayKind()) {
return Ok();
}
TypedObjectPrediction elemPrediction = objPrediction.arrayElementType();
if (elemPrediction.isUseless()) {
return Ok();
}
uint32_t elemSize;
if (!elemPrediction.hasKnownSize(&elemSize)) {
return Ok();
}
switch (elemPrediction.kind()) {
case type::Struct:
case type::Array:
return getElemTryComplexElemOfTypedObject(
emitted, obj, index, objPrediction, elemPrediction, elemSize);
case type::Scalar:
return getElemTryScalarElemOfTypedObject(
emitted, obj, index, objPrediction, elemPrediction, elemSize);
case type::Reference:
return getElemTryReferenceElemOfTypedObject(
emitted, obj, index, objPrediction, elemPrediction);
}
MOZ_CRASH("Bad kind");
}
bool IonBuilder::checkTypedObjectIndexInBounds(
uint32_t elemSize, MDefinition* index, TypedObjectPrediction objPrediction,
LinearSum* indexAsByteOffset) {
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
int32_t lenOfAll;
MDefinition* length;
if (objPrediction.hasKnownArrayLength(&lenOfAll)) {
length = constantInt(lenOfAll);
TypeSet::ObjectKey* globalKey =
TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
trackOptimizationOutcome(TrackedOutcome::TypedObjectHasDetachedBuffer);
return false;
}
} else {
trackOptimizationOutcome(TrackedOutcome::TypedObjectArrayRange);
return false;
}
index = addBoundsCheck(idInt32, length);
return indexAsByteOffset->add(index, AssertedCast<int32_t>(elemSize));
}
AbortReasonOr<Ok> IonBuilder::getElemTryScalarElemOfTypedObject(
bool* emitted, MDefinition* obj, MDefinition* index,
TypedObjectPrediction objPrediction, TypedObjectPrediction elemPrediction,
uint32_t elemSize) {
MOZ_ASSERT(objPrediction.ofArrayKind());
ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
&indexAsByteOffset)) {
return Ok();
}
trackOptimizationSuccess();
*emitted = true;
return pushScalarLoadFromTypedObject(obj, indexAsByteOffset, elemType);
}
AbortReasonOr<Ok> IonBuilder::getElemTryReferenceElemOfTypedObject(
bool* emitted, MDefinition* obj, MDefinition* index,
TypedObjectPrediction objPrediction, TypedObjectPrediction elemPrediction) {
MOZ_ASSERT(objPrediction.ofArrayKind());
ReferenceType elemType = elemPrediction.referenceType();
uint32_t elemSize = ReferenceTypeDescr::size(elemType);
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
&indexAsByteOffset)) {
return Ok();
}
if (elemType == ReferenceType::TYPE_WASM_ANYREF) {
return Ok();
}
trackOptimizationSuccess();
*emitted = true;
return pushReferenceLoadFromTypedObject(obj, indexAsByteOffset, elemType,
nullptr);
}
AbortReasonOr<Ok> IonBuilder::pushScalarLoadFromTypedObject(
MDefinition* obj, const LinearSum& byteOffset,
ScalarTypeDescr::Type elemType) {
uint32_t size = ScalarTypeDescr::size(elemType);
MOZ_ASSERT(size == ScalarTypeDescr::alignment(elemType));
MDefinition* elements;
MDefinition* scaledOffset;
int32_t adjustment;
MOZ_TRY(loadTypedObjectElements(obj, byteOffset, size, &elements,
&scaledOffset, &adjustment));
MLoadUnboxedScalar* load =
MLoadUnboxedScalar::New(alloc(), elements, scaledOffset, elemType,
DoesNotRequireMemoryBarrier, adjustment);
current->add(load);
current->push(load);
TemporaryTypeSet* resultTypes = bytecodeTypes(pc);
bool allowDouble = resultTypes->hasType(TypeSet::DoubleType());
MIRType knownType = MIRTypeForTypedArrayRead(elemType, allowDouble);
load->setResultType(knownType);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::pushReferenceLoadFromTypedObject(
MDefinition* typedObj, const LinearSum& byteOffset, ReferenceType type,
PropertyName* name) {
MDefinition* elements;
MDefinition* scaledOffset;
int32_t adjustment;
uint32_t alignment = ReferenceTypeDescr::alignment(type);
MOZ_TRY(loadTypedObjectElements(typedObj, byteOffset, alignment, &elements,
&scaledOffset, &adjustment));
TemporaryTypeSet* observedTypes = bytecodeTypes(pc);
MInstruction* load = nullptr; BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), typedObj, name, observedTypes);
switch (type) {
case ReferenceType::TYPE_ANY: {
bool bailOnUndefined = barrier == BarrierKind::NoBarrier &&
!observedTypes->hasType(TypeSet::UndefinedType());
if (bailOnUndefined) {
barrier = BarrierKind::TypeTagOnly;
}
load = MLoadElement::New(alloc(), elements, scaledOffset, false, false,
adjustment);
break;
}
case ReferenceType::TYPE_OBJECT: {
MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
if (barrier == BarrierKind::NoBarrier &&
!observedTypes->hasType(TypeSet::NullType())) {
nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
} else {
nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
}
load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset,
nullBehavior, adjustment);
break;
}
case ReferenceType::TYPE_STRING: {
load =
MLoadUnboxedString::New(alloc(), elements, scaledOffset, adjustment);
observedTypes->addType(TypeSet::StringType(), alloc().lifoAlloc());
break;
}
case ReferenceType::TYPE_WASM_ANYREF: {
MOZ_CRASH();
}
}
current->add(load);
current->push(load);
return pushTypeBarrier(load, observedTypes, barrier);
}
AbortReasonOr<Ok> IonBuilder::getElemTryComplexElemOfTypedObject(
bool* emitted, MDefinition* obj, MDefinition* index,
TypedObjectPrediction objPrediction, TypedObjectPrediction elemPrediction,
uint32_t elemSize) {
MOZ_ASSERT(objPrediction.ofArrayKind());
MDefinition* type = loadTypedObjectType(obj);
MDefinition* elemTypeObj = typeObjectForElementFromArrayStructType(type);
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
&indexAsByteOffset)) {
return Ok();
}
return pushDerivedTypedObject(emitted, obj, indexAsByteOffset, elemPrediction,
elemTypeObj);
}
AbortReasonOr<Ok> IonBuilder::pushDerivedTypedObject(
bool* emitted, MDefinition* obj, const LinearSum& baseByteOffset,
TypedObjectPrediction derivedPrediction, MDefinition* derivedTypeObj) {
MDefinition* owner;
LinearSum ownerByteOffset(alloc());
MOZ_TRY(loadTypedObjectData(obj, &owner, &ownerByteOffset));
if (!ownerByteOffset.add(baseByteOffset, 1)) {
return abort(AbortReason::Disable,
"Overflow/underflow on type object offset.");
}
MDefinition* offset = ConvertLinearSum(alloc(), current, ownerByteOffset,
true);
MInstruction* derivedTypedObj = MNewDerivedTypedObject::New(
alloc(), derivedPrediction, derivedTypeObj, owner, offset);
current->add(derivedTypedObj);
current->push(derivedTypedObj);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
const Class* expectedClass = nullptr;
if (const Class* objClass =
objTypes ? objTypes->getKnownClass(constraints()) : nullptr) {
MOZ_ASSERT(IsTypedObjectClass(objClass));
expectedClass =
GetOutlineTypedObjectClass(IsOpaqueTypedObjectClass(objClass));
}
const TypedProto* expectedProto = derivedPrediction.getKnownPrototype();
MOZ_ASSERT_IF(expectedClass, IsTypedObjectClass(expectedClass));
TemporaryTypeSet* observedTypes = bytecodeTypes(pc);
const Class* observedClass = observedTypes->getKnownClass(constraints());
JSObject* observedProto;
if (observedTypes->getCommonPrototype(constraints(), &observedProto) &&
observedClass && observedProto && observedClass == expectedClass &&
observedProto == expectedProto) {
derivedTypedObj->setResultTypeSet(observedTypes);
} else {
MOZ_TRY(
pushTypeBarrier(derivedTypedObj, observedTypes, BarrierKind::TypeSet));
}
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryGetProp(bool* emitted, MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
MConstant* indexConst = index->maybeConstantValue();
jsid id;
if (!indexConst || !ValueToIdPure(indexConst->toJSValue(), &id)) {
return Ok();
}
if (id != IdToTypeId(id)) {
return Ok();
}
TemporaryTypeSet* types = bytecodeTypes(pc);
trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
MOZ_TRY(getPropTryConstant(emitted, obj, id, types));
if (*emitted) {
index->setImplicitlyUsedUnchecked();
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined);
MOZ_TRY(getPropTryNotDefined(emitted, obj, id, types));
if (*emitted) {
index->setImplicitlyUsedUnchecked();
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
MOZ_TRY(getPropTryCommonGetter(emitted, obj, id, types));
if (*emitted) {
index->setImplicitlyUsedUnchecked();
return Ok();
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
return Ok();
}
bool hasExtraIndexedProperty;
MOZ_TRY_VAR(hasExtraIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
if (hasExtraIndexedProperty && failedBoundsCheck_) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return Ok();
}
if (inspector->hasSeenNonIntegerIndex(pc)) {
trackOptimizationOutcome(TrackedOutcome::ArraySeenNonIntegerIndex);
return Ok();
}
if (inspector->hasSeenNegativeIndexGetElement(pc)) {
trackOptimizationOutcome(TrackedOutcome::ArraySeenNegativeIndex);
return Ok();
}
MOZ_TRY(jsop_getelem_dense(obj, index));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryTypedArray(bool* emitted,
MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
Scalar::Type arrayType;
if (!ElementAccessIsTypedArray(constraints(), obj, index, &arrayType)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotTypedArray);
return Ok();
}
if (inspector->hasSeenNonIntegerIndex(pc)) {
trackOptimizationOutcome(TrackedOutcome::ArraySeenNonIntegerIndex);
return Ok();
}
if (inspector->hasSeenNegativeIndexGetElement(pc)) {
trackOptimizationOutcome(TrackedOutcome::ArraySeenNegativeIndex);
return Ok();
}
MOZ_TRY(jsop_getelem_typed(obj, index, arrayType));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryCallSiteObject(bool* emitted,
MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (!obj->isConstant() || obj->type() != MIRType::Object) {
trackOptimizationOutcome(TrackedOutcome::NotObject);
return Ok();
}
if (!index->isConstant() || index->type() != MIRType::Int32) {
trackOptimizationOutcome(TrackedOutcome::IndexType);
return Ok();
}
JSObject* cst = &obj->toConstant()->toObject();
if (!cst->is<ArrayObject>()) {
trackOptimizationOutcome(TrackedOutcome::GenericFailure);
return Ok();
}
ArrayObject* array = &cst->as<ArrayObject>();
if (array->lengthIsWritable() || array->hasEmptyElements() ||
!array->denseElementsAreFrozen()) {
trackOptimizationOutcome(TrackedOutcome::GenericFailure);
return Ok();
}
int32_t idx = index->toConstant()->toInt32();
if (idx < 0 || !array->containsDenseElement(uint32_t(idx))) {
trackOptimizationOutcome(TrackedOutcome::OutOfBounds);
return Ok();
}
const Value& v = array->getDenseElement(uint32_t(idx));
if (!v.isString() || !v.toString()->isAtom()) {
return Ok();
}
obj->setImplicitlyUsedUnchecked();
index->setImplicitlyUsedUnchecked();
pushConstant(v);
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryString(bool* emitted, MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (obj->type() != MIRType::String || !IsNumberType(index->type())) {
trackOptimizationOutcome(TrackedOutcome::AccessNotString);
return Ok();
}
if (bytecodeTypes(pc)->hasType(TypeSet::UndefinedType())) {
trackOptimizationOutcome(TrackedOutcome::OutOfBounds);
return Ok();
}
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
index = idInt32;
MStringLength* length = MStringLength::New(alloc(), obj);
current->add(length);
index = addBoundsCheck(index, length);
MCharCodeAt* charCode = MCharCodeAt::New(alloc(), obj, index);
current->add(charCode);
MFromCharCode* result = MFromCharCode::New(alloc(), charCode);
current->add(result);
current->push(result);
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryArguments(bool* emitted,
MDefinition* obj,
MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (inliningDepth_ > 0) {
return Ok();
}
if (obj->type() != MIRType::MagicOptimizedArguments) {
return Ok();
}
MOZ_ASSERT(!info().argsObjAliasesFormals());
obj->setImplicitlyUsedUnchecked();
MArgumentsLength* length = MArgumentsLength::New(alloc());
current->add(length);
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
index = idInt32;
index = addBoundsCheck(index, length);
bool modifiesArgs = script()->baselineScript()->modifiesArguments();
MGetFrameArgument* load =
MGetFrameArgument::New(alloc(), index, modifiesArgs);
current->add(load);
current->push(load);
TemporaryTypeSet* types = bytecodeTypes(pc);
MOZ_TRY(pushTypeBarrier(load, types, BarrierKind::TypeSet));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryArgumentsInlinedConstant(
bool* emitted, MDefinition* obj, MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (inliningDepth_ == 0) {
return Ok();
}
if (obj->type() != MIRType::MagicOptimizedArguments) {
return Ok();
}
MConstant* indexConst = index->maybeConstantValue();
if (!indexConst || indexConst->type() != MIRType::Int32) {
return Ok();
}
obj->setImplicitlyUsedUnchecked();
MOZ_ASSERT(!info().argsObjAliasesFormals());
MOZ_ASSERT(inliningDepth_ > 0);
int32_t id = indexConst->toInt32();
index->setImplicitlyUsedUnchecked();
if (id < (int32_t)inlineCallInfo_->argc() && id >= 0) {
current->push(inlineCallInfo_->getArg(id));
} else {
pushConstant(UndefinedValue());
}
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemTryArgumentsInlinedIndex(
bool* emitted, MDefinition* obj, MDefinition* index) {
MOZ_ASSERT(*emitted == false);
if (inliningDepth_ == 0) {
return Ok();
}
if (obj->type() != MIRType::MagicOptimizedArguments) {
return Ok();
}
if (!IsNumberType(index->type())) {
return Ok();
}
if (inlineCallInfo_->argc() > 10) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBound);
return abort(AbortReason::Disable,
"NYI get argument element with too many arguments");
}
obj->setImplicitlyUsedUnchecked();
MOZ_ASSERT(!info().argsObjAliasesFormals());
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
index = idInt32;
index = addBoundsCheck(index, constantInt(inlineCallInfo_->argc()));
MInstruction* args =
MArgumentState::New(alloc().fallible(), inlineCallInfo_->argv());
if (!args) {
return abort(AbortReason::Alloc);
}
current->add(args);
MInstruction* load = MLoadElementFromState::New(alloc(), args, index);
current->add(load);
current->push(load);
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getElemAddCache(MDefinition* obj,
MDefinition* index) {
MGetPropertyCache* ins = MGetPropertyCache::New(alloc(), obj, index,
true);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
TemporaryTypeSet* types = bytecodeTypes(pc);
MOZ_TRY(pushTypeBarrier(ins, types, BarrierKind::TypeSet));
trackOptimizationSuccess();
return Ok();
}
TemporaryTypeSet* IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes,
const jsid id) {
if (objTypes->unknownObject() || objTypes->getObjectCount() == 0) {
return nullptr;
}
TemporaryTypeSet* acc = nullptr;
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
Vector<HeapTypeSetKey, 4, SystemAllocPolicy> properties;
if (!properties.reserve(objTypes->getObjectCount())) {
return nullptr;
}
for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
TypeSet::ObjectKey* key = objTypes->getObject(i);
if (!key) {
continue;
}
if (key->unknownProperties()) {
return nullptr;
}
HeapTypeSetKey property = key->property(id);
HeapTypeSet* currentSet = property.maybeTypes();
if (!currentSet || currentSet->unknown()) {
return nullptr;
}
properties.infallibleAppend(property);
if (acc) {
acc = TypeSet::unionSets(acc, currentSet, lifoAlloc);
} else {
TemporaryTypeSet empty;
acc = TypeSet::unionSets(&empty, currentSet, lifoAlloc);
}
if (!acc) {
return nullptr;
}
}
for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++) {
i->freeze(constraints());
}
return acc;
}
AbortReasonOr<Ok> IonBuilder::jsop_getelem_dense(MDefinition* obj,
MDefinition* index) {
TemporaryTypeSet* types = bytecodeTypes(pc);
MOZ_ASSERT(index->type() == MIRType::Int32 ||
index->type() == MIRType::Double);
BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), obj, nullptr, types);
bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);
bool readOutOfBounds = false;
if (types->hasType(TypeSet::UndefinedType())) {
bool hasExtraIndexedProperty;
MOZ_TRY_VAR(hasExtraIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
readOutOfBounds = !hasExtraIndexedProperty;
}
MIRType knownType = MIRType::Value;
if (barrier == BarrierKind::NoBarrier) {
knownType = GetElemKnownType(needsHoleCheck, types);
}
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
index = idInt32;
MInstruction* elements = MElements::New(alloc(), obj);
current->add(elements);
MInstruction* initLength = initializedLength(elements);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
bool inBounds = !readOutOfBounds && !needsHoleCheck;
if (inBounds) {
TemporaryTypeSet* heapTypes = computeHeapType(objTypes, JSID_VOID);
if (heapTypes && heapTypes->isSubset(types)) {
knownType = heapTypes->getKnownMIRType();
types = heapTypes;
}
}
bool loadDouble = barrier == BarrierKind::NoBarrier && loopDepth_ &&
inBounds && knownType == MIRType::Double && objTypes &&
objTypes->convertDoubleElements(constraints()) ==
TemporaryTypeSet::AlwaysConvertToDoubles;
if (loadDouble) {
elements = addConvertElementsToDoubles(elements);
}
MInstruction* load;
if (!readOutOfBounds) {
index = addBoundsCheck(index, initLength);
load =
MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
current->add(load);
} else {
load = MLoadElementHole::New(alloc(), elements, index, initLength,
needsHoleCheck);
current->add(load);
MOZ_ASSERT(knownType == MIRType::Value);
}
if (knownType != MIRType::Value) {
load->setResultType(knownType);
load->setResultTypeSet(types);
}
current->push(load);
return pushTypeBarrier(load, types, barrier);
}
MInstruction* IonBuilder::addArrayBufferByteLength(MDefinition* obj) {
MLoadFixedSlot* ins = MLoadFixedSlot::New(
alloc(), obj, size_t(ArrayBufferObject::BYTE_LENGTH_SLOT));
current->add(ins);
ins->setResultType(MIRType::Int32);
return ins;
}
TypedArrayObject* IonBuilder::tryTypedArrayEmbedConstantElements(
MDefinition* obj) {
JSObject* object = nullptr;
if (MConstant* objConst = obj->maybeConstantValue()) {
if (objConst->type() == MIRType::Object) {
object = &objConst->toObject();
}
} else if (TemporaryTypeSet* types = obj->resultTypeSet()) {
object = types->maybeSingleton();
}
if (!object || !object->isSingleton()) {
return nullptr;
}
TypedArrayObject* tarr = &object->as<TypedArrayObject>();
MOZ_ASSERT(tarr->hasBuffer());
MOZ_ASSERT(tarr->byteLength() >= TypedArrayObject::SINGLETON_BYTE_LENGTH ||
tarr->hasDetachedBuffer());
MOZ_ASSERT(!tarr->runtimeFromMainThread()->gc.nursery().isInside(
tarr->dataPointerEither()));
TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarr);
if (tarrKey->unknownProperties()) {
return nullptr;
}
if (!tarr->isSharedMemory()) {
tarrKey->watchStateChangeForTypedArrayData(constraints());
}
return tarr;
}
void IonBuilder::addTypedArrayLengthAndData(MDefinition* obj,
BoundsChecking checking,
MDefinition** index,
MInstruction** length,
MInstruction** elements) {
MOZ_ASSERT((index != nullptr) == (elements != nullptr));
if (TypedArrayObject* tarr = tryTypedArrayEmbedConstantElements(obj)) {
obj->setImplicitlyUsedUnchecked();
int32_t len = AssertedCast<int32_t>(tarr->length());
*length = MConstant::New(alloc(), Int32Value(len));
current->add(*length);
if (index) {
if (checking == DoBoundsCheck) {
*index = addBoundsCheck(*index, *length);
}
*elements = MConstantElements::New(alloc(), tarr->dataPointerEither());
current->add(*elements);
}
return;
}
*length = MTypedArrayLength::New(alloc(), obj);
current->add(*length);
if (index) {
if (checking == DoBoundsCheck) {
*index = addBoundsCheck(*index, *length);
}
*elements = MTypedArrayElements::New(alloc(), obj);
current->add(*elements);
}
}
MInstruction* IonBuilder::addTypedArrayByteOffset(MDefinition* obj) {
MInstruction* byteOffset;
if (TypedArrayObject* tarr = tryTypedArrayEmbedConstantElements(obj)) {
obj->setImplicitlyUsedUnchecked();
int32_t offset = AssertedCast<int32_t>(tarr->byteOffset());
byteOffset = MConstant::New(alloc(), Int32Value(offset));
} else {
byteOffset = MTypedArrayByteOffset::New(alloc(), obj);
}
current->add(byteOffset);
return byteOffset;
}
AbortReasonOr<Ok> IonBuilder::jsop_getelem_typed(MDefinition* obj,
MDefinition* index,
Scalar::Type arrayType) {
TemporaryTypeSet* types = bytecodeTypes(pc);
bool maybeUndefined = types->hasType(TypeSet::UndefinedType());
bool allowDouble = types->hasType(TypeSet::DoubleType());
MInstruction* idInt32 = MToNumberInt32::New(alloc(), index);
current->add(idInt32);
index = idInt32;
if (!maybeUndefined) {
MIRType knownType = MIRTypeForTypedArrayRead(arrayType, allowDouble);
MInstruction* length;
MInstruction* elements;
addTypedArrayLengthAndData(obj, DoBoundsCheck, &index, &length, &elements);
MLoadUnboxedScalar* load =
MLoadUnboxedScalar::New(alloc(), elements, index, arrayType);
current->add(load);
current->push(load);
load->setResultType(knownType);
return Ok();
} else {
BarrierKind barrier = BarrierKind::TypeSet;
switch (arrayType) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Uint8Clamped:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
if (types->hasType(TypeSet::Int32Type())) {
barrier = BarrierKind::NoBarrier;
}
break;
case Scalar::Float32:
case Scalar::Float64:
if (allowDouble) {
barrier = BarrierKind::NoBarrier;
}
break;
default:
MOZ_CRASH("Unknown typed array type");
}
MLoadTypedArrayElementHole* load = MLoadTypedArrayElementHole::New(
alloc(), obj, index, arrayType, allowDouble);
current->add(load);
current->push(load);
return pushTypeBarrier(load, types, barrier);
}
}
AbortReasonOr<Ok> IonBuilder::jsop_setelem() {
bool emitted = false;
startTrackingOptimizations();
MDefinition* value = current->pop();
MDefinition* index = current->pop();
MDefinition* object = convertUnboxedObjects(current->pop());
trackTypeInfo(TrackedTypeSite::Receiver, object->type(),
object->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Value, value->type(), value->resultTypeSet());
if (shouldAbortOnPreliminaryGroups(object)) {
MInstruction* ins =
MCallSetElement::New(alloc(), object, index, value, IsStrictSetPC(pc));
current->add(ins);
current->push(value);
return resumeAfter(ins);
}
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::SetElem_TypedArray);
MOZ_TRY(setElemTryTypedArray(&emitted, object, index, value));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::SetElem_Dense);
SetElemICInspector icInspect(inspector->setElemICInspector(pc));
bool writeHole = icInspect.sawOOBDenseWrite();
MOZ_TRY(initOrSetElemTryDense(&emitted, object, index, value, writeHole));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::SetElem_Arguments);
MOZ_TRY(setElemTryArguments(&emitted, object));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::SetElem_TypedObject);
MOZ_TRY(setElemTryTypedObject(&emitted, object, index, value));
if (emitted) {
return Ok();
}
}
if (script()->argumentsHasVarBinding() &&
object->mightBeType(MIRType::MagicOptimizedArguments) &&
info().analysisMode() != Analysis_ArgumentsUsage) {
return abort(AbortReason::Disable,
"Type is not definitely lazy arguments.");
}
trackOptimizationAttempt(TrackedStrategy::SetElem_InlineCache);
MOZ_TRY(initOrSetElemTryCache(&emitted, object, index, value));
if (emitted) {
return Ok();
}
MInstruction* ins =
MCallSetElement::New(alloc(), object, index, value, IsStrictSetPC(pc));
current->add(ins);
current->push(value);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::setElemTryTypedObject(bool* emitted,
MDefinition* obj,
MDefinition* index,
MDefinition* value) {
MOZ_ASSERT(*emitted == false);
trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject);
TypedObjectPrediction objPrediction = typedObjectPrediction(obj);
if (objPrediction.isUseless()) {
return Ok();
}
if (!objPrediction.ofArrayKind()) {
return Ok();
}
TypedObjectPrediction elemPrediction = objPrediction.arrayElementType();
if (elemPrediction.isUseless()) {
return Ok();
}
uint32_t elemSize;
if (!elemPrediction.hasKnownSize(&elemSize)) {
return Ok();
}
switch (elemPrediction.kind()) {
case type::Reference:
return setElemTryReferenceElemOfTypedObject(
emitted, obj, index, objPrediction, value, elemPrediction);
case type::Scalar:
return setElemTryScalarElemOfTypedObject(
emitted, obj, index, objPrediction, value, elemPrediction, elemSize);
case type::Struct:
case type::Array:
trackOptimizationOutcome(TrackedOutcome::GenericFailure);
return Ok();
}
MOZ_CRASH("Bad kind");
}
AbortReasonOr<Ok> IonBuilder::setElemTryReferenceElemOfTypedObject(
bool* emitted, MDefinition* obj, MDefinition* index,
TypedObjectPrediction objPrediction, MDefinition* value,
TypedObjectPrediction elemPrediction) {
ReferenceType elemType = elemPrediction.referenceType();
uint32_t elemSize = ReferenceTypeDescr::size(elemType);
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
&indexAsByteOffset)) {
return Ok();
}
if (elemType == ReferenceType::TYPE_WASM_ANYREF) {
return Ok();
}
return setPropTryReferenceTypedObjectValue(emitted, obj, indexAsByteOffset,
elemType, value, nullptr);
}
AbortReasonOr<Ok> IonBuilder::setElemTryScalarElemOfTypedObject(
bool* emitted, MDefinition* obj, MDefinition* index,
TypedObjectPrediction objPrediction, MDefinition* value,
TypedObjectPrediction elemPrediction, uint32_t elemSize) {
ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
LinearSum indexAsByteOffset(alloc());
if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
&indexAsByteOffset)) {
return Ok();
}
return setPropTryScalarTypedObjectValue(emitted, obj, indexAsByteOffset,
elemType, value);
}
AbortReasonOr<Ok> IonBuilder::setElemTryTypedArray(bool* emitted,
MDefinition* object,
MDefinition* index,
MDefinition* value) {
MOZ_ASSERT(*emitted == false);
Scalar::Type arrayType;
if (!ElementAccessIsTypedArray(constraints(), object, index, &arrayType)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotTypedArray);
return Ok();
}
MOZ_TRY(jsop_setelem_typed(arrayType, object, index, value));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::initOrSetElemTryDense(bool* emitted,
MDefinition* object,
MDefinition* index,
MDefinition* value,
bool writeHole) {
MOZ_ASSERT(*emitted == false);
if (value->type() == MIRType::MagicHole) {
{
trackOptimizationOutcome(TrackedOutcome::InitHole);
}
return Ok();
}
if (!ElementAccessIsDenseNative(constraints(), object, index)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
return Ok();
}
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &object,
nullptr, &value, true)) {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
return Ok();
}
if (!object->resultTypeSet()) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return Ok();
}
TemporaryTypeSet::DoubleConversion conversion =
object->resultTypeSet()->convertDoubleElements(constraints());
if (conversion == TemporaryTypeSet::AmbiguousDoubleConversion &&
value->type() != MIRType::Int32) {
trackOptimizationOutcome(TrackedOutcome::ArrayDoubleConversion);
return Ok();
}
bool hasExtraIndexedProperty;
MOZ_TRY_VAR(hasExtraIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, object));
if (hasExtraIndexedProperty && failedBoundsCheck_) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return Ok();
}
MOZ_TRY(
initOrSetElemDense(conversion, object, index, value, writeHole, emitted));
if (!*emitted) {
trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
return Ok();
}
trackOptimizationSuccess();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setElemTryArguments(bool* emitted,
MDefinition* object) {
MOZ_ASSERT(*emitted == false);
if (object->type() != MIRType::MagicOptimizedArguments) {
return Ok();
}
return abort(AbortReason::Disable, "NYI arguments[]=");
}
AbortReasonOr<Ok> IonBuilder::initOrSetElemTryCache(bool* emitted,
MDefinition* object,
MDefinition* index,
MDefinition* value) {
MOZ_ASSERT(*emitted == false);
if (!object->mightBeType(MIRType::Object)) {
trackOptimizationOutcome(TrackedOutcome::NotObject);
return Ok();
}
if (value->type() == MIRType::MagicHole) {
{
trackOptimizationOutcome(TrackedOutcome::InitHole);
}
return Ok();
}
bool barrier = true;
if (index->type() == MIRType::Int32 &&
!PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &object,
nullptr, &value, true)) {
barrier = false;
}
bool guardHoles;
MOZ_TRY_VAR(guardHoles, ElementAccessHasExtraIndexedProperty(this, object));
const Class* clasp =
object->resultTypeSet()
? object->resultTypeSet()->getKnownClass(constraints())
: nullptr;
bool checkNative = !clasp || !clasp->isNative();
object = addMaybeCopyElementsForWrite(object, checkNative);
bool strict = JSOp(*pc) == JSOP_STRICTSETELEM;
MSetPropertyCache* ins =
MSetPropertyCache::New(alloc(), object, index, value, strict,
needsPostBarrier(value), barrier, guardHoles);
current->add(ins);
if (!IsPropertyInitOp(JSOp(*pc))) {
current->push(value);
}
MOZ_TRY(resumeAfter(ins));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::initOrSetElemDense(
TemporaryTypeSet::DoubleConversion conversion, MDefinition* obj,
MDefinition* id, MDefinition* value, bool writeHole, bool* emitted) {
MOZ_ASSERT(*emitted == false);
MIRType elementType = DenseNativeElementType(constraints(), obj);
bool packed = ElementAccessIsPacked(constraints(), obj);
bool hasExtraIndexedProperty;
MOZ_TRY_VAR(hasExtraIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
bool mayBeNonExtensible =
ElementAccessMightBeNonExtensible(constraints(), obj);
if (mayBeNonExtensible) {
if (hasExtraIndexedProperty) {
return Ok();
}
if (IsPropertyInitOp(JSOp(*pc))) {
return Ok();
}
}
*emitted = true;
MInstruction* idInt32 = MToNumberInt32::New(alloc(), id);
current->add(idInt32);
id = idInt32;
if (needsPostBarrier(value)) {
current->add(MPostWriteElementBarrier::New(alloc(), obj, value, id));
}
obj = addMaybeCopyElementsForWrite(obj, false);
MElements* elements = MElements::New(alloc(), obj);
current->add(elements);
MDefinition* newValue = value;
switch (conversion) {
case TemporaryTypeSet::AlwaysConvertToDoubles:
case TemporaryTypeSet::MaybeConvertToDoubles: {
MInstruction* valueDouble = MToDouble::New(alloc(), value);
current->add(valueDouble);
newValue = valueDouble;
break;
}
case TemporaryTypeSet::AmbiguousDoubleConversion: {
MOZ_ASSERT(value->type() == MIRType::Int32);
MInstruction* maybeDouble =
MMaybeToDoubleElement::New(alloc(), elements, value);
current->add(maybeDouble);
newValue = maybeDouble;
break;
}
case TemporaryTypeSet::DontConvertToDoubles:
break;
default:
MOZ_CRASH("Unknown double conversion");
}
MInstruction* store;
MStoreElementCommon* common = nullptr;
if (writeHole && !hasExtraIndexedProperty && !mayBeNonExtensible) {
MStoreElementHole* ins =
MStoreElementHole::New(alloc(), obj, elements, id, newValue);
store = ins;
common = ins;
current->add(ins);
} else if (mayBeNonExtensible) {
MOZ_ASSERT(
!hasExtraIndexedProperty,
"FallibleStoreElement codegen assumes no extra indexed properties");
bool needsHoleCheck = !packed;
MFallibleStoreElement* ins = MFallibleStoreElement::New(
alloc(), obj, elements, id, newValue, needsHoleCheck);
store = ins;
common = ins;
current->add(ins);
} else {
MInstruction* initLength = initializedLength(elements);
id = addBoundsCheck(id, initLength);
bool needsHoleCheck = !packed && hasExtraIndexedProperty;
MStoreElement* ins =
MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
store = ins;
common = ins;
current->add(store);
}
if (!IsPropertyInitOp(JSOp(*pc))) {
current->push(value);
}
MOZ_TRY(resumeAfter(store));
if (common) {
if (obj->resultTypeSet()->propertyNeedsBarrier(constraints(), JSID_VOID)) {
common->setNeedsBarrier();
}
if (elementType != MIRType::None && packed) {
common->setElementType(elementType);
}
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_setelem_typed(Scalar::Type arrayType,
MDefinition* obj,
MDefinition* id,
MDefinition* value) {
SetElemICInspector icInspect(inspector->setElemICInspector(pc));
bool expectOOB = icInspect.sawOOBTypedArrayWrite();
if (expectOOB) {
spew("Emitting OOB TypedArray SetElem");
}
MInstruction* idInt32 = MToNumberInt32::New(alloc(), id);
current->add(idInt32);
id = idInt32;
MInstruction* length;
MInstruction* elements;
BoundsChecking checking = expectOOB ? SkipBoundsCheck : DoBoundsCheck;
addTypedArrayLengthAndData(obj, checking, &id, &length, &elements);
MDefinition* toWrite = value;
if (arrayType == Scalar::Uint8Clamped) {
toWrite = MClampToUint8::New(alloc(), value);
current->add(toWrite->toInstruction());
}
MInstruction* ins;
if (expectOOB) {
ins = MStoreTypedArrayElementHole::New(alloc(), elements, length, id,
toWrite, arrayType);
} else {
MStoreUnboxedScalar* store =
MStoreUnboxedScalar::New(alloc(), elements, id, toWrite, arrayType,
MStoreUnboxedScalar::TruncateInput);
ins = store;
}
current->add(ins);
current->push(value);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_length() {
if (jsop_length_fastPath()) {
return Ok();
}
PropertyName* name = info().getAtom(pc)->asPropertyName();
return jsop_getprop(name);
}
bool IonBuilder::jsop_length_fastPath() {
TemporaryTypeSet* types = bytecodeTypes(pc);
if (types->getKnownMIRType() != MIRType::Int32) {
return false;
}
MDefinition* obj = current->peek(-1);
if (shouldAbortOnPreliminaryGroups(obj)) {
return false;
}
if (obj->mightBeType(MIRType::String)) {
if (obj->mightBeType(MIRType::Object)) {
return false;
}
current->pop();
MStringLength* ins = MStringLength::New(alloc(), obj);
current->add(ins);
current->push(ins);
return true;
}
if (obj->mightBeType(MIRType::Object)) {
TemporaryTypeSet* objTypes = obj->resultTypeSet();
if (objTypes &&
objTypes->getKnownClass(constraints()) == &ArrayObject::class_ &&
!objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW)) {
current->pop();
MElements* elements = MElements::New(alloc(), obj);
current->add(elements);
MArrayLength* length = MArrayLength::New(alloc(), elements);
current->add(length);
current->push(length);
return true;
}
TypedObjectPrediction prediction = typedObjectPrediction(obj);
if (!prediction.isUseless()) {
TypeSet::ObjectKey* globalKey =
TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return false;
}
MInstruction* length;
int32_t sizedLength;
if (prediction.hasKnownArrayLength(&sizedLength)) {
obj->setImplicitlyUsedUnchecked();
length = MConstant::New(alloc(), Int32Value(sizedLength));
} else {
return false;
}
current->pop();
current->add(length);
current->push(length);
return true;
}
}
return false;
}
AbortReasonOr<Ok> IonBuilder::jsop_arguments() {
if (info().needsArgsObj()) {
current->push(current->argumentsObject());
return Ok();
}
MOZ_ASSERT(hasLazyArguments_);
MConstant* lazyArg =
MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
current->add(lazyArg);
current->push(lazyArg);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_newtarget() {
if (!info().funMaybeLazy()) {
MOZ_ASSERT(!info().script()->isForEval());
pushConstant(NullValue());
return Ok();
}
MOZ_ASSERT(info().funMaybeLazy());
if (info().funMaybeLazy()->isArrow()) {
MArrowNewTarget* arrowNewTarget =
MArrowNewTarget::New(alloc(), getCallee());
current->add(arrowNewTarget);
current->push(arrowNewTarget);
return Ok();
}
if (inliningDepth_ == 0) {
MNewTarget* newTarget = MNewTarget::New(alloc());
current->add(newTarget);
current->push(newTarget);
return Ok();
}
if (!inlineCallInfo_->constructing()) {
pushConstant(UndefinedValue());
return Ok();
}
current->push(inlineCallInfo_->getNewTarget());
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_rest() {
if (info().analysisMode() == Analysis_ArgumentsUsage) {
MUnknownValue* unknown = MUnknownValue::New(alloc());
current->add(unknown);
current->push(unknown);
return Ok();
}
ArrayObject* templateObject =
&inspector->getTemplateObject(pc)->as<ArrayObject>();
if (inliningDepth_ == 0) {
MArgumentsLength* numActuals = MArgumentsLength::New(alloc());
current->add(numActuals);
MRest* rest = MRest::New(alloc(), constraints(), numActuals,
info().nargs() - 1, templateObject);
current->add(rest);
current->push(rest);
return Ok();
}
unsigned numActuals = inlineCallInfo_->argc();
unsigned numFormals = info().nargs() - 1;
unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
MOZ_TRY(jsop_newarray(numRest));
if (numRest == 0) {
return Ok();
}
MDefinition* array = current->peek(-1);
MElements* elements = MElements::New(alloc(), array);
current->add(elements);
MConstant* index = nullptr;
for (unsigned i = numFormals; i < numActuals; i++) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
index = MConstant::New(alloc(), Int32Value(i - numFormals));
current->add(index);
MDefinition* arg = inlineCallInfo_->argv()[i];
MStoreElement* store = MStoreElement::New(alloc(), elements, index, arg,
false);
current->add(store);
if (needsPostBarrier(arg)) {
current->add(MPostWriteBarrier::New(alloc(), array, arg));
}
}
MSetArrayLength* length = MSetArrayLength::New(alloc(), elements, index);
current->add(length);
MSetInitializedLength* initLength =
MSetInitializedLength::New(alloc(), elements, index);
current->add(initLength);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_checkisobj(uint8_t kind) {
MDefinition* toCheck = current->peek(-1);
if (toCheck->type() == MIRType::Object) {
toCheck->setImplicitlyUsedUnchecked();
return Ok();
}
MCheckIsObj* check = MCheckIsObj::New(alloc(), current->pop(), kind);
current->add(check);
current->push(check);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_checkiscallable(uint8_t kind) {
MCheckIsCallable* check =
MCheckIsCallable::New(alloc(), current->pop(), kind);
current->add(check);
current->push(check);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_checkobjcoercible() {
MDefinition* toCheck = current->peek(-1);
if (!toCheck->mightBeType(MIRType::Undefined) &&
!toCheck->mightBeType(MIRType::Null)) {
toCheck->setImplicitlyUsedUnchecked();
return Ok();
}
MOZ_ASSERT(toCheck->type() == MIRType::Value ||
toCheck->type() == MIRType::Null ||
toCheck->type() == MIRType::Undefined);
MCheckObjCoercible* check = MCheckObjCoercible::New(alloc(), current->pop());
current->add(check);
current->push(check);
return resumeAfter(check);
}
uint32_t IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, jsid id,
uint32_t* pnfixed) {
if (!types || types->unknownObject() || !types->objectOrSentinel()) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return UINT32_MAX;
}
uint32_t slot = UINT32_MAX;
for (size_t i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
if (key->unknownProperties()) {
trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
return UINT32_MAX;
}
if (key->isSingleton()) {
trackOptimizationOutcome(TrackedOutcome::Singleton);
return UINT32_MAX;
}
HeapTypeSetKey property = key->property(id);
if (!property.maybeTypes() || !property.maybeTypes()->definiteProperty() ||
property.nonData(constraints())) {
trackOptimizationOutcome(TrackedOutcome::NotFixedSlot);
return UINT32_MAX;
}
size_t nfixed = NativeObject::MAX_FIXED_SLOTS;
if (ObjectGroup* group = key->group()->maybeOriginalUnboxedGroup()) {
AutoSweepObjectGroup sweepGroup(group);
nfixed =
gc::GetGCKindSlots(group->unboxedLayout(sweepGroup).getAllocKind());
}
uint32_t propertySlot = property.maybeTypes()->definiteSlot();
if (slot == UINT32_MAX) {
slot = propertySlot;
*pnfixed = nfixed;
} else if (slot != propertySlot || nfixed != *pnfixed) {
trackOptimizationOutcome(TrackedOutcome::InconsistentFixedSlot);
return UINT32_MAX;
}
}
return slot;
}
uint32_t IonBuilder::getUnboxedOffset(TemporaryTypeSet* types, jsid id,
JSValueType* punboxedType) {
if (!types || types->unknownObject() || !types->objectOrSentinel()) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return UINT32_MAX;
}
uint32_t offset = UINT32_MAX;
for (size_t i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
if (key->unknownProperties()) {
trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
return UINT32_MAX;
}
if (key->isSingleton()) {
trackOptimizationOutcome(TrackedOutcome::Singleton);
return UINT32_MAX;
}
AutoSweepObjectGroup sweep(key->group());
UnboxedLayout* layout = key->group()->maybeUnboxedLayout(sweep);
if (!layout) {
trackOptimizationOutcome(TrackedOutcome::NotUnboxed);
return UINT32_MAX;
}
const UnboxedLayout::Property* property = layout->lookup(id);
if (!property) {
trackOptimizationOutcome(TrackedOutcome::StructNoField);
return UINT32_MAX;
}
if (layout->nativeGroup()) {
trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
return UINT32_MAX;
}
key->watchStateChangeForUnboxedConvertedToNative(constraints());
if (offset == UINT32_MAX) {
offset = property->offset;
*punboxedType = property->type;
} else if (offset != property->offset) {
trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
return UINT32_MAX;
} else if (*punboxedType != property->type) {
trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
return UINT32_MAX;
}
}
return offset;
}
AbortReasonOr<Ok> IonBuilder::jsop_not() {
MDefinition* value = current->pop();
MNot* ins = MNot::New(alloc(), value, constraints());
current->add(ins);
current->push(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_envcallee() {
uint8_t numHops = GET_UINT8(pc);
MDefinition* env = walkEnvironmentChain(numHops);
MInstruction* callee =
MLoadFixedSlot::New(alloc(), env, CallObject::calleeSlot());
current->add(callee);
current->push(callee);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_superbase() {
MDefinition* callee = current->pop();
auto* homeObject = MHomeObject::New(alloc(), callee);
current->add(homeObject);
auto* superBase = MHomeObjectSuperBase::New(alloc(), homeObject);
current->add(superBase);
current->push(superBase);
MOZ_TRY(resumeAfter(superBase));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_getprop_super(PropertyName* name) {
MDefinition* obj = current->pop();
MDefinition* receiver = current->pop();
MConstant* id = constant(StringValue(name));
auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
AbortReasonOr<Ok> IonBuilder::jsop_getelem_super() {
MDefinition* obj = current->pop();
MDefinition* id = current->pop();
MDefinition* receiver = current->pop();
#if defined(JS_CODEGEN_X86)
if (instrumentedProfiling()) {
return abort(AbortReason::Disable,
"profiling functions with GETELEM_SUPER is disabled on x86");
}
#endif
auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}
NativeObject* IonBuilder::commonPrototypeWithGetterSetter(
TemporaryTypeSet* types, jsid id, bool isGetter, JSFunction* getterOrSetter,
bool* guardGlobal) {
if (!types || types->unknownObject()) {
return nullptr;
}
*guardGlobal = false;
NativeObject* foundProto = nullptr;
for (unsigned i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
while (key) {
if (key->unknownProperties()) {
return nullptr;
}
const Class* clasp = key->clasp();
if (!ClassHasEffectlessLookup(clasp)) {
return nullptr;
}
JSObject* singleton = key->isSingleton() ? key->singleton() : nullptr;
if (ObjectHasExtraOwnProperty(realm, key, id)) {
if (!singleton || !singleton->is<GlobalObject>()) {
return nullptr;
}
*guardGlobal = true;
}
if (isGetter && clasp->getOpsGetProperty()) {
return nullptr;
}
if (!isGetter && clasp->getOpsSetProperty()) {
return nullptr;
}
if (singleton) {
if (!singleton->is<NativeObject>()) {
return nullptr;
}
NativeObject* singletonNative = &singleton->as<NativeObject>();
if (Shape* propShape = singletonNative->lookupPure(id)) {
Value getterSetterVal = ObjectValue(*getterOrSetter);
if (isGetter) {
if (propShape->getterOrUndefined() != getterSetterVal) {
return nullptr;
}
} else {
if (propShape->setterOrUndefined() != getterSetterVal) {
return nullptr;
}
}
if (!foundProto) {
foundProto = singletonNative;
} else if (foundProto != singletonNative) {
return nullptr;
}
break;
}
}
HeapTypeSetKey property = key->property(id);
if (TypeSet* types = property.maybeTypes()) {
if (!types->empty() || types->nonDataProperty()) {
return nullptr;
}
}
if (singleton) {
if (CanHaveEmptyPropertyTypesForOwnProperty(singleton)) {
MOZ_ASSERT(singleton->is<GlobalObject>());
*guardGlobal = true;
}
}
JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
if (foundProto && proto == foundProto) {
break;
}
if (!proto) {
return nullptr;
}
key = TypeSet::ObjectKey::get(proto);
}
}
return foundProto;
}
AbortReasonOr<Ok> IonBuilder::freezePropertiesForCommonPrototype(
TemporaryTypeSet* types, jsid id, JSObject* foundProto,
bool allowEmptyTypesforGlobal) {
for (unsigned i = 0; i < types->getObjectCount(); i++) {
if (types->getSingleton(i) == foundProto) {
continue;
}
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
while (true) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
HeapTypeSetKey property = key->property(id);
MOZ_ALWAYS_TRUE(
!property.isOwnProperty(constraints(), allowEmptyTypesforGlobal));
if (key->proto() == TaggedProto(foundProto)) {
break;
}
key = TypeSet::ObjectKey::get(key->proto().toObjectOrNull());
}
}
return Ok();
}
AbortReasonOr<bool> IonBuilder::testCommonGetterSetter(
TemporaryTypeSet* types, jsid id, bool isGetter, JSFunction* getterOrSetter,
MDefinition** guard, Shape* globalShape ,
MDefinition** globalGuard ) {
MOZ_ASSERT(getterOrSetter);
MOZ_ASSERT_IF(globalShape, globalGuard);
bool guardGlobal;
NativeObject* foundProto = commonPrototypeWithGetterSetter(
types, id, isGetter, getterOrSetter, &guardGlobal);
if (!foundProto || (guardGlobal && !globalShape)) {
trackOptimizationOutcome(TrackedOutcome::MultiProtoPaths);
return false;
}
MOZ_TRY(
freezePropertiesForCommonPrototype(types, id, foundProto, guardGlobal));
if (guardGlobal) {
JSObject* obj = &script()->global();
MDefinition* globalObj = constant(ObjectValue(*obj));
*globalGuard = addShapeGuard(globalObj, globalShape, Bailout_ShapeGuard);
}
Shape* propShape = foundProto->lookupPure(id);
MOZ_ASSERT_IF(isGetter, propShape->getterObject() == getterOrSetter);
MOZ_ASSERT_IF(!isGetter, propShape->setterObject() == getterOrSetter);
if (propShape && !propShape->configurable()) {
return true;
}
MInstruction* wrapper = constant(ObjectValue(*foundProto));
*guard =
addShapeGuard(wrapper, foundProto->lastProperty(), Bailout_ShapeGuard);
return true;
}
void IonBuilder::replaceMaybeFallbackFunctionGetter(MGetPropertyCache* cache) {
WrapMGetPropertyCache rai(maybeFallbackFunctionGetter_);
maybeFallbackFunctionGetter_ = cache;
}
AbortReasonOr<Ok> IonBuilder::annotateGetPropertyCache(
MDefinition* obj, PropertyName* name, MGetPropertyCache* getPropCache,
TemporaryTypeSet* objTypes, TemporaryTypeSet* pushedTypes) {
if (pushedTypes->unknownObject() || pushedTypes->baseFlags() != 0) {
return Ok();
}
for (unsigned i = 0; i < pushedTypes->getObjectCount(); i++) {
if (pushedTypes->getGroup(i) != nullptr) {
return Ok();
}
}
if (!objTypes || objTypes->baseFlags() || objTypes->unknownObject()) {
return Ok();
}
unsigned int objCount = objTypes->getObjectCount();
if (objCount == 0) {
return Ok();
}
InlinePropertyTable* inlinePropTable =
getPropCache->initInlinePropertyTable(alloc(), pc);
if (!inlinePropTable) {
return abort(AbortReason::Alloc);
}
for (unsigned int i = 0; i < objCount; i++) {
ObjectGroup* group = objTypes->getGroup(i);
if (!group) {
continue;
}
TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(group);
if (key->unknownProperties() || !key->proto().isObject()) {
continue;
}
JSObject* proto = checkNurseryObject(key->proto().toObject());
const Class* clasp = key->clasp();
if (!ClassHasEffectlessLookup(clasp) ||
ObjectHasExtraOwnProperty(realm, key, NameToId(name))) {
continue;
}
HeapTypeSetKey ownTypes = key->property(NameToId(name));
if (ownTypes.isOwnProperty(constraints())) {
continue;
}
JSObject* singleton = testSingletonProperty(proto, NameToId(name));
if (!singleton || !singleton->is<JSFunction>()) {
continue;
}
if (!pushedTypes->hasType(TypeSet::ObjectType(singleton))) {
continue;
}
if (!inlinePropTable->addEntry(alloc(), group,
&singleton->as<JSFunction>())) {
return abort(AbortReason::Alloc);
}
}
if (inlinePropTable->numEntries() == 0) {
getPropCache->clearInlinePropertyTable();
return Ok();
}
#ifdef JS_JITSPEW
if (inlinePropTable->numEntries() > 0) {
JitSpew(JitSpew_Inlining,
"Annotated GetPropertyCache with %d/%d inline cases",
(int)inlinePropTable->numEntries(), (int)objCount);
}
#endif
if (inlinePropTable->numEntries() > 0) {
current->push(obj);
MResumePoint* resumePoint =
MResumePoint::New(alloc(), current, pc, MResumePoint::ResumeAt);
if (!resumePoint) {
return abort(AbortReason::Alloc);
}
inlinePropTable->setPriorResumePoint(resumePoint);
replaceMaybeFallbackFunctionGetter(getPropCache);
current->pop();
}
return Ok();
}
bool IonBuilder::invalidatedIdempotentCache() {
IonBuilder* builder = this;
do {
if (builder->script()->invalidatedIdempotentCache()) {
return true;
}
builder = builder->callerBuilder_;
} while (builder);
return false;
}
AbortReasonOr<Ok> IonBuilder::loadSlot(MDefinition* obj, size_t slot,
size_t nfixed, MIRType rvalType,
BarrierKind barrier,
TemporaryTypeSet* types) {
if (slot < nfixed) {
MLoadFixedSlot* load = MLoadFixedSlot::New(alloc(), obj, slot);
current->add(load);
current->push(load);
load->setResultType(rvalType);
return pushTypeBarrier(load, types, barrier);
}
MSlots* slots = MSlots::New(alloc(), obj);
current->add(slots);
MLoadSlot* load = MLoadSlot::New(alloc(), slots, slot - nfixed);
current->add(load);
current->push(load);
load->setResultType(rvalType);
return pushTypeBarrier(load, types, barrier);
}
AbortReasonOr<Ok> IonBuilder::loadSlot(MDefinition* obj, Shape* shape,
MIRType rvalType, BarrierKind barrier,
TemporaryTypeSet* types) {
return loadSlot(obj, shape->slot(), shape->numFixedSlots(), rvalType, barrier,
types);
}
AbortReasonOr<Ok> IonBuilder::storeSlot(
MDefinition* obj, size_t slot, size_t nfixed, MDefinition* value,
bool needsBarrier, MIRType slotType ) {
if (slot < nfixed) {
MStoreFixedSlot* store = MStoreFixedSlot::New(alloc(), obj, slot, value);
current->add(store);
current->push(value);
if (needsBarrier) {
store->setNeedsBarrier();
}
return resumeAfter(store);
}
MSlots* slots = MSlots::New(alloc(), obj);
current->add(slots);
MStoreSlot* store = MStoreSlot::New(alloc(), slots, slot - nfixed, value);
current->add(store);
current->push(value);
if (needsBarrier) {
store->setNeedsBarrier();
}
if (slotType != MIRType::None) {
store->setSlotType(slotType);
}
return resumeAfter(store);
}
AbortReasonOr<Ok> IonBuilder::storeSlot(
MDefinition* obj, Shape* shape, MDefinition* value, bool needsBarrier,
MIRType slotType ) {
MOZ_ASSERT(shape->writable());
return storeSlot(obj, shape->slot(), shape->numFixedSlots(), value,
needsBarrier, slotType);
}
bool IonBuilder::shouldAbortOnPreliminaryGroups(MDefinition* obj) {
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types || types->unknownObject()) {
return false;
}
bool preliminary = false;
for (size_t i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key) {
continue;
}
if (ObjectGroup* group = key->maybeGroup()) {
if (group->hasUnanalyzedPreliminaryObjects()) {
addAbortedPreliminaryGroup(group);
preliminary = true;
}
}
}
return preliminary;
}
MDefinition* IonBuilder::maybeUnboxForPropertyAccess(MDefinition* def) {
if (def->type() != MIRType::Value) {
return def;
}
MIRType type = inspector->expectedPropertyAccessInputType(pc);
if (type == MIRType::Value || !def->mightBeType(type)) {
return def;
}
MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible);
current->add(unbox);
if (*pc == JSOP_CALLPROP || *pc == JSOP_CALLELEM) {
uint32_t idx = current->stackDepth() - 1;
MOZ_ASSERT(current->getSlot(idx) == def);
current->setSlot(idx, unbox);
}
return unbox;
}
AbortReasonOr<Ok> IonBuilder::jsop_getprop(PropertyName* name) {
bool emitted = false;
startTrackingOptimizations();
MDefinition* obj = current->pop();
TemporaryTypeSet* types = bytecodeTypes(pc);
trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());
if (!info().isAnalysis()) {
trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsLength);
MOZ_TRY(getPropTryArgumentsLength(&emitted, obj));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsCallee);
MOZ_TRY(getPropTryArgumentsCallee(&emitted, obj, name));
if (emitted) {
return Ok();
}
}
obj = maybeUnboxForPropertyAccess(obj);
if (obj->type() == MIRType::Object) {
obj = convertUnboxedObjects(obj);
}
BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), obj, name, types);
trackOptimizationAttempt(TrackedStrategy::GetProp_InferredConstant);
if (barrier == BarrierKind::NoBarrier) {
MOZ_TRY(getPropTryInferredConstant(&emitted, obj, name, types));
if (emitted) {
return Ok();
}
} else {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
}
if (info().isAnalysis() || types->empty() ||
shouldAbortOnPreliminaryGroups(obj)) {
if (types->empty()) {
trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache);
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
}
MCallGetProperty* call = MCallGetProperty::New(alloc(), obj, name);
current->add(call);
if (info().isAnalysis()) {
MOZ_TRY(getPropTryConstant(&emitted, obj, NameToId(name), types));
if (emitted) {
return Ok();
}
}
current->push(call);
MOZ_TRY(resumeAfter(call));
return pushTypeBarrier(call, types, BarrierKind::TypeSet);
}
trackOptimizationAttempt(TrackedStrategy::GetProp_Innerize);
MOZ_TRY(getPropTryInnerize(&emitted, obj, name, types));
if (emitted) {
return Ok();
}
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
MOZ_TRY(getPropTryConstant(&emitted, obj, NameToId(name), types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined);
MOZ_TRY(getPropTryNotDefined(&emitted, obj, NameToId(name), types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_DefiniteSlot);
MOZ_TRY(getPropTryDefiniteSlot(&emitted, obj, name, barrier, types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
MOZ_TRY(getPropTryUnboxed(&emitted, obj, name, barrier, types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
MOZ_TRY(getPropTryCommonGetter(&emitted, obj, NameToId(name), types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
MOZ_TRY(getPropTryInlineAccess(&emitted, obj, name, barrier, types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_InlineProtoAccess);
MOZ_TRY(getPropTryInlineProtoAccess(&emitted, obj, name, types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_ModuleNamespace);
MOZ_TRY(getPropTryModuleNamespace(&emitted, obj, name, barrier, types));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_TypedObject);
MOZ_TRY(getPropTryTypedObject(&emitted, obj, name));
if (emitted) {
return Ok();
}
}
trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache);
return getPropAddCache(obj, name, barrier, types);
}
AbortReasonOr<Ok> IonBuilder::improveThisTypesForCall() {
MOZ_ASSERT(*pc == JSOP_CALLPROP || *pc == JSOP_CALLELEM);
MDefinition* thisDef = current->peek(-2);
MDefinition* calleeDef = current->peek(-1);
if (thisDef->type() != MIRType::Value ||
!thisDef->mightBeType(MIRType::Object) || !thisDef->resultTypeSet() ||
!thisDef->resultTypeSet()->objectOrSentinel() || calleeDef->isPhi()) {
return Ok();
}
TemporaryTypeSet* types =
thisDef->resultTypeSet()->cloneObjectsOnly(alloc_->lifoAlloc());
if (!types) {
return abort(AbortReason::Alloc);
}
MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), thisDef, types);
current->add(filter);
current->rewriteAtDepth(-2, filter);
filter->setDependency(calleeDef);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::checkIsDefinitelyOptimizedArguments(
MDefinition* obj, bool* isOptimizedArgs) {
if (obj->type() != MIRType::MagicOptimizedArguments) {
if (script()->argumentsHasVarBinding() &&
obj->mightBeType(MIRType::MagicOptimizedArguments)) {
return abort(AbortReason::Disable,
"Type is not definitely lazy arguments.");
}
*isOptimizedArgs = false;
return Ok();
}
*isOptimizedArgs = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryInferredConstant(
bool* emitted, MDefinition* obj, PropertyName* name,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
if (!objTypes) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return Ok();
}
JSObject* singleton = objTypes->maybeSingleton();
if (!singleton) {
trackOptimizationOutcome(TrackedOutcome::NotSingleton);
return Ok();
}
TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton);
if (key->unknownProperties()) {
trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
return Ok();
}
HeapTypeSetKey property = key->property(NameToId(name));
Value constantValue = UndefinedValue();
if (property.constant(constraints(), &constantValue)) {
spew("Optimized constant property");
obj->setImplicitlyUsedUnchecked();
pushConstant(constantValue);
types->addType(TypeSet::GetValueType(constantValue), alloc_->lifoAlloc());
trackOptimizationSuccess();
*emitted = true;
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryArgumentsLength(bool* emitted,
MDefinition* obj) {
MOZ_ASSERT(*emitted == false);
if (JSOp(*pc) != JSOP_LENGTH) {
return Ok();
}
bool isOptimizedArgs = false;
MOZ_TRY(checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs));
if (!isOptimizedArgs) {
return Ok();
}
trackOptimizationSuccess();
*emitted = true;
obj->setImplicitlyUsedUnchecked();
if (inliningDepth_ == 0) {
MInstruction* ins = MArgumentsLength::New(alloc());
current->add(ins);
current->push(ins);
return Ok();
}
pushConstant(Int32Value(inlineCallInfo_->argv().length()));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryArgumentsCallee(bool* emitted,
MDefinition* obj,
PropertyName* name) {
MOZ_ASSERT(*emitted == false);
if (name != names().callee) {
return Ok();
}
bool isOptimizedArgs = false;
MOZ_TRY(checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs));
if (!isOptimizedArgs) {
return Ok();
}
MOZ_ASSERT(script()->hasMappedArgsObj());
obj->setImplicitlyUsedUnchecked();
current->push(getCallee());
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryConstant(bool* emitted,
MDefinition* obj, jsid id,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
if (!types->mightBeMIRType(MIRType::Object)) {
trackOptimizationOutcome(TrackedOutcome::NotObject);
return Ok();
}
JSObject* singleton = testSingletonPropertyTypes(obj, id);
if (!singleton) {
trackOptimizationOutcome(TrackedOutcome::NotSingleton);
return Ok();
}
obj->setImplicitlyUsedUnchecked();
pushConstant(ObjectValue(*singleton));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryNotDefined(bool* emitted,
MDefinition* obj, jsid id,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
if (!types->mightBeMIRType(MIRType::Undefined)) {
trackOptimizationOutcome(TrackedOutcome::NotUndefined);
return Ok();
}
bool res;
MOZ_TRY_VAR(res, testNotDefinedProperty(obj, id));
if (!res) {
trackOptimizationOutcome(TrackedOutcome::GenericFailure);
return Ok();
}
obj->setImplicitlyUsedUnchecked();
pushConstant(UndefinedValue());
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryTypedObject(bool* emitted,
MDefinition* obj,
PropertyName* name) {
TypedObjectPrediction fieldPrediction;
size_t fieldOffset;
size_t fieldIndex;
bool fieldMutable;
if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction,
&fieldIndex, &fieldMutable)) {
return Ok();
}
switch (fieldPrediction.kind()) {
case type::Struct:
case type::Array:
return getPropTryComplexPropOfTypedObject(emitted, obj, fieldOffset,
fieldPrediction, fieldIndex);
case type::Reference:
return getPropTryReferencePropOfTypedObject(emitted, obj, fieldOffset,
fieldPrediction, name);
case type::Scalar:
return getPropTryScalarPropOfTypedObject(emitted, obj, fieldOffset,
fieldPrediction);
}
MOZ_CRASH("Bad kind");
}
AbortReasonOr<Ok> IonBuilder::getPropTryScalarPropOfTypedObject(
bool* emitted, MDefinition* typedObj, int32_t fieldOffset,
TypedObjectPrediction fieldPrediction) {
Scalar::Type fieldType = fieldPrediction.scalarType();
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return Ok();
}
trackOptimizationSuccess();
*emitted = true;
LinearSum byteOffset(alloc());
if (!byteOffset.add(fieldOffset)) {
return abort(AbortReason::Disable, "Overflow of field offsets.");
}
return pushScalarLoadFromTypedObject(typedObj, byteOffset, fieldType);
}
AbortReasonOr<Ok> IonBuilder::getPropTryReferencePropOfTypedObject(
bool* emitted, MDefinition* typedObj, int32_t fieldOffset,
TypedObjectPrediction fieldPrediction, PropertyName* name) {
ReferenceType fieldType = fieldPrediction.referenceType();
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return Ok();
}
if (fieldType == ReferenceType::TYPE_WASM_ANYREF) {
return Ok();
}
trackOptimizationSuccess();
*emitted = true;
LinearSum byteOffset(alloc());
if (!byteOffset.add(fieldOffset)) {
return abort(AbortReason::Disable, "Overflow of field offsets.");
}
return pushReferenceLoadFromTypedObject(typedObj, byteOffset, fieldType,
name);
}
AbortReasonOr<Ok> IonBuilder::getPropTryComplexPropOfTypedObject(
bool* emitted, MDefinition* typedObj, int32_t fieldOffset,
TypedObjectPrediction fieldPrediction, size_t fieldIndex) {
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return Ok();
}
MDefinition* type = loadTypedObjectType(typedObj);
MDefinition* fieldTypeObj =
typeObjectForFieldFromStructType(type, fieldIndex);
LinearSum byteOffset(alloc());
if (!byteOffset.add(fieldOffset)) {
return abort(AbortReason::Disable, "Overflow of field offsets.");
}
return pushDerivedTypedObject(emitted, typedObj, byteOffset, fieldPrediction,
fieldTypeObj);
}
MDefinition* IonBuilder::convertUnboxedObjects(MDefinition* obj) {
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types || types->unknownObject() || !types->objectOrSentinel()) {
return obj;
}
BaselineInspector::ObjectGroupVector list(alloc());
for (size_t i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = obj->resultTypeSet()->getObject(i);
if (!key || !key->isGroup()) {
continue;
}
AutoSweepObjectGroup sweep(key->group());
if (UnboxedLayout* layout = key->group()->maybeUnboxedLayout(sweep)) {
AutoEnterOOMUnsafeRegion oomUnsafe;
if (layout->nativeGroup() && !list.append(key->group())) {
oomUnsafe.crash("IonBuilder::convertUnboxedObjects");
}
}
}
return convertUnboxedObjects(obj, list);
}
MDefinition* IonBuilder::convertUnboxedObjects(
MDefinition* obj, const BaselineInspector::ObjectGroupVector& list) {
for (size_t i = 0; i < list.length(); i++) {
ObjectGroup* group = list[i];
if (TemporaryTypeSet* types = obj->resultTypeSet()) {
if (!types->hasType(TypeSet::ObjectType(group))) {
continue;
}
}
obj = MConvertUnboxedObjectToNative::New(alloc(), obj, group);
current->add(obj->toInstruction());
}
return obj;
}
AbortReasonOr<Ok> IonBuilder::getPropTryDefiniteSlot(bool* emitted,
MDefinition* obj,
PropertyName* name,
BarrierKind barrier,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
uint32_t nfixed;
uint32_t slot =
getDefiniteSlot(obj->resultTypeSet(), NameToId(name), &nfixed);
if (slot == UINT32_MAX) {
return Ok();
}
if (obj->type() != MIRType::Object) {
MGuardObject* guard = MGuardObject::New(alloc(), obj);
current->add(guard);
obj = guard;
}
MInstruction* load;
if (slot < nfixed) {
load = MLoadFixedSlot::New(alloc(), obj, slot);
} else {
MInstruction* slots = MSlots::New(alloc(), obj);
current->add(slots);
load = MLoadSlot::New(alloc(), slots, slot - nfixed);
}
if (barrier == BarrierKind::NoBarrier) {
load->setResultType(types->getKnownMIRType());
}
current->add(load);
current->push(load);
MOZ_TRY(pushTypeBarrier(load, types, barrier));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryModuleNamespace(
bool* emitted, MDefinition* obj, PropertyName* name, BarrierKind barrier,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
if (!objTypes) {
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
return Ok();
}
JSObject* singleton = objTypes->maybeSingleton();
if (!singleton) {
trackOptimizationOutcome(TrackedOutcome::NotSingleton);
return Ok();
}
if (!singleton->is<ModuleNamespaceObject>()) {
trackOptimizationOutcome(TrackedOutcome::NotModuleNamespace);
return Ok();
}
ModuleNamespaceObject* ns = &singleton->as<ModuleNamespaceObject>();
ModuleEnvironmentObject* env;
Shape* shape;
if (!ns->bindings().lookup(NameToId(name), &env, &shape)) {
trackOptimizationOutcome(TrackedOutcome::UnknownProperty);
return Ok();
}
obj->setImplicitlyUsedUnchecked();
MConstant* envConst = constant(ObjectValue(*env));
uint32_t slot = shape->slot();
uint32_t nfixed = env->numFixedSlots();
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) {
rvalType = MIRType::Value;
}
MOZ_TRY(loadSlot(envConst, slot, nfixed, rvalType, barrier, types));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
MInstruction* IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset,
JSValueType unboxedType,
BarrierKind barrier,
TemporaryTypeSet* types) {
size_t index = offset / UnboxedTypeSize(unboxedType);
MInstruction* indexConstant = MConstant::New(alloc(), Int32Value(index));
current->add(indexConstant);
return loadUnboxedValue(obj, UnboxedPlainObject::offsetOfData(),
indexConstant, unboxedType, barrier, types);
}
MInstruction* IonBuilder::loadUnboxedValue(
MDefinition* elements, size_t elementsOffset, MDefinition* index,
JSValueType unboxedType, BarrierKind barrier, TemporaryTypeSet* types) {
MInstruction* load;
switch (unboxedType) {
case JSVAL_TYPE_BOOLEAN:
load =
MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Uint8,
DoesNotRequireMemoryBarrier, elementsOffset);
load->setResultType(MIRType::Boolean);
break;
case JSVAL_TYPE_INT32:
load =
MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Int32,
DoesNotRequireMemoryBarrier, elementsOffset);
load->setResultType(MIRType::Int32);
break;
case JSVAL_TYPE_DOUBLE:
load =
MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Float64,
DoesNotRequireMemoryBarrier, elementsOffset,
false);
load->setResultType(MIRType::Double);
break;
case JSVAL_TYPE_STRING:
load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset);
break;
case JSVAL_TYPE_OBJECT: {
MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
if (types->hasType(TypeSet::NullType())) {
nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
} else if (barrier != BarrierKind::NoBarrier) {
nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
} else {
nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
}
load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index,
nullBehavior, elementsOffset);
break;
}
default:
MOZ_CRASH();
}
current->add(load);
return load;
}
AbortReasonOr<Ok> IonBuilder::getPropTryUnboxed(bool* emitted, MDefinition* obj,
PropertyName* name,
BarrierKind barrier,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
JSValueType unboxedType;
uint32_t offset =
getUnboxedOffset(obj->resultTypeSet(), NameToId(name), &unboxedType);
if (offset == UINT32_MAX) {
return Ok();
}
if (obj->type() != MIRType::Object) {
MGuardObject* guard = MGuardObject::New(alloc(), obj);
current->add(guard);
obj = guard;
}
MInstruction* load =
loadUnboxedProperty(obj, offset, unboxedType, barrier, types);
current->push(load);
MOZ_TRY(pushTypeBarrier(load, types, barrier));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
MDefinition* IonBuilder::addShapeGuardsForGetterSetter(
MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
bool isOwnProperty) {
MOZ_ASSERT(isOwnProperty == !holder);
MOZ_ASSERT(holderShape);
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
if (isOwnProperty) {
MOZ_ASSERT(receivers.empty());
return addShapeGuard(obj, holderShape, Bailout_ShapeGuard);
}
MDefinition* holderDef = constant(ObjectValue(*holder));
addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard);
return addGuardReceiverPolymorphic(obj, receivers);
}
AbortReasonOr<Ok> IonBuilder::getPropTryCommonGetter(bool* emitted,
MDefinition* obj, jsid id,
TemporaryTypeSet* types,
bool innerized) {
MOZ_ASSERT(*emitted == false);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
JSFunction* commonGetter = nullptr;
MDefinition* guard = nullptr;
MDefinition* globalGuard = nullptr;
{
Shape* lastProperty = nullptr;
Shape* globalShape = nullptr;
JSObject* foundProto = nullptr;
bool isOwnProperty = false;
BaselineInspector::ReceiverVector receivers(alloc());
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (inspector->commonGetPropFunction(
pc, id, innerized, &foundProto, &lastProperty, &commonGetter,
&globalShape, &isOwnProperty, receivers, convertUnboxedGroups)) {
bool canUseTIForGetter = false;
if (!isOwnProperty) {
MOZ_TRY_VAR(canUseTIForGetter,
testCommonGetterSetter(objTypes, id,
true, commonGetter,
&guard, globalShape, &globalGuard));
}
if (!canUseTIForGetter) {
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
receivers, convertUnboxedGroups,
isOwnProperty);
if (!obj) {
return abort(AbortReason::Alloc);
}
}
} else if (inspector->megamorphicGetterSetterFunction(
pc, id, true, &commonGetter)) {
bool canUseTIForGetter = false;
MOZ_TRY_VAR(canUseTIForGetter,
testCommonGetterSetter(objTypes, id, true,
commonGetter, &guard));
if (!canUseTIForGetter) {
return Ok();
}
} else {
return Ok();
}
}
DOMObjectKind objKind = DOMObjectKind::Unknown;
bool isDOM = objTypes && objTypes->isDOMClass(constraints(), &objKind);
if (isDOM) {
MOZ_TRY_VAR(isDOM,
testShouldDOMCall(objTypes, commonGetter, JSJitInfo::Getter));
}
if (isDOM) {
const JSJitInfo* jitinfo = commonGetter->jitInfo();
if (objKind == DOMObjectKind::Native ||
(!jitinfo->isAlwaysInSlot && !jitinfo->isLazilyCachedInSlot)) {
MInstruction* get;
if (jitinfo->isAlwaysInSlot) {
JSObject* singleton = objTypes->maybeSingleton();
if (singleton && jitinfo->aliasSet() == JSJitInfo::AliasNone) {
size_t slot = jitinfo->slotIndex;
*emitted = true;
pushConstant(GetReservedSlot(singleton, slot));
return Ok();
}
get = MGetDOMMember::New(alloc(), jitinfo, obj, guard, globalGuard);
} else {
get = MGetDOMProperty::New(alloc(), jitinfo, objKind,
commonGetter->realm(), obj, guard,
globalGuard);
}
if (!get) {
return abort(AbortReason::Alloc);
}
current->add(get);
current->push(get);
if (get->isEffectful()) {
MOZ_TRY(resumeAfter(get));
}
MOZ_TRY(pushDOMTypeBarrier(get, types, commonGetter));
trackOptimizationOutcome(TrackedOutcome::DOM);
*emitted = true;
return Ok();
}
}
if (obj->type() != MIRType::Object) {
MGuardObject* guardObj = MGuardObject::New(alloc(), obj);
current->add(guardObj);
obj = guardObj;
}
if (!current->ensureHasSlots(2)) {
return abort(AbortReason::Alloc);
}
current->push(constant(ObjectValue(*commonGetter)));
current->push(obj);
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, 0)) {
return abort(AbortReason::Alloc);
}
if (commonGetter->isNative()) {
InliningStatus status;
MOZ_TRY_VAR(status, inlineNativeGetter(callInfo, commonGetter));
switch (status) {
case InliningStatus_WarmUpCountTooLow:
case InliningStatus_NotInlined:
break;
case InliningStatus_Inlined:
trackOptimizationOutcome(TrackedOutcome::Inlined);
*emitted = true;
return Ok();
}
}
if (commonGetter->isInterpreted()) {
InliningDecision decision = makeInliningDecision(commonGetter, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline: {
InliningStatus status;
MOZ_TRY_VAR(status, inlineScriptedCall(callInfo, commonGetter));
if (status == InliningStatus_Inlined) {
*emitted = true;
return Ok();
}
break;
}
}
}
MOZ_TRY(makeCall(commonGetter, callInfo));
if (!commonGetter->isInterpreted()) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
bool IonBuilder::canInlinePropertyOpShapes(
const BaselineInspector::ReceiverVector& receivers) {
if (receivers.empty()) {
trackOptimizationOutcome(TrackedOutcome::NoShapeInfo);
return false;
}
for (size_t i = 0; i < receivers.length(); i++) {
if (receivers[i].shape && receivers[i].shape->inDictionary()) {
trackOptimizationOutcome(TrackedOutcome::InDictionaryMode);
return false;
}
}
return true;
}
static Shape* PropertyShapesHaveSameSlot(
const BaselineInspector::ReceiverVector& receivers, jsid id) {
Shape* firstShape = nullptr;
for (size_t i = 0; i < receivers.length(); i++) {
if (receivers[i].group) {
return nullptr;
}
Shape* shape = receivers[i].shape->searchLinear(id);
MOZ_ASSERT(shape);
if (i == 0) {
firstShape = shape;
} else if (shape->slot() != firstShape->slot() ||
shape->numFixedSlots() != firstShape->numFixedSlots()) {
return nullptr;
}
}
return firstShape;
}
AbortReasonOr<Ok> IonBuilder::getPropTryInlineAccess(bool* emitted,
MDefinition* obj,
PropertyName* name,
BarrierKind barrier,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
BaselineInspector::ReceiverVector receivers(alloc());
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups)) {
return abort(AbortReason::Alloc);
}
if (!canInlinePropertyOpShapes(receivers)) {
return Ok();
}
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) {
rvalType = MIRType::Value;
}
if (receivers.length() == 1) {
if (!receivers[0].group) {
spew("Inlining monomorphic native GETPROP");
obj = addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard);
Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
MOZ_ASSERT(shape);
MOZ_TRY(loadSlot(obj, shape, rvalType, barrier, types));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
if (receivers[0].shape) {
spew("Inlining monomorphic unboxed expando GETPROP");
obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
obj = addUnboxedExpandoGuard(obj, true,
Bailout_ShapeGuard);
MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
current->add(expando);
expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
MOZ_ASSERT(shape);
MOZ_TRY(loadSlot(expando, shape, rvalType, barrier, types));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
ObjectGroup* group = receivers[0].group;
if (obj->resultTypeSet() &&
!obj->resultTypeSet()->hasType(TypeSet::ObjectType(group))) {
return Ok();
}
obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
AutoSweepObjectGroup sweep(group);
const UnboxedLayout::Property* property =
group->unboxedLayout(sweep).lookup(name);
MInstruction* load = loadUnboxedProperty(obj, property->offset,
property->type, barrier, types);
current->push(load);
MOZ_TRY(pushTypeBarrier(load, types, barrier));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
MOZ_ASSERT(receivers.length() > 1);
spew("Inlining polymorphic GETPROP");
if (Shape* propShape =
PropertyShapesHaveSameSlot(receivers, NameToId(name))) {
obj = addGuardReceiverPolymorphic(obj, receivers);
if (!obj) {
return abort(AbortReason::Alloc);
}
MOZ_TRY(loadSlot(obj, propShape, rvalType, barrier, types));
trackOptimizationOutcome(TrackedOutcome::Polymorphic);
*emitted = true;
return Ok();
}
MGetPropertyPolymorphic* load =
MGetPropertyPolymorphic::New(alloc(), obj, name);
current->add(load);
current->push(load);
for (size_t i = 0; i < receivers.length(); i++) {
Shape* propShape = nullptr;
if (receivers[i].shape) {
propShape = receivers[i].shape->searchLinear(NameToId(name));
MOZ_ASSERT(propShape);
}
if (!load->addReceiver(receivers[i], propShape)) {
return abort(AbortReason::Alloc);
}
}
if (failedShapeGuard_) {
load->setNotMovable();
}
load->setResultType(rvalType);
MOZ_TRY(pushTypeBarrier(load, types, barrier));
trackOptimizationOutcome(TrackedOutcome::Polymorphic);
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropTryInlineProtoAccess(
bool* emitted, MDefinition* obj, PropertyName* name,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
BaselineInspector::ReceiverVector receivers(alloc());
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
JSObject* holder = nullptr;
if (!inspector->maybeInfoForProtoReadSlot(pc, receivers, convertUnboxedGroups,
&holder)) {
return abort(AbortReason::Alloc);
}
if (!canInlinePropertyOpShapes(receivers)) {
return Ok();
}
MOZ_ASSERT(holder);
holder = checkNurseryObject(holder);
BarrierKind barrier;
MOZ_TRY_VAR(barrier,
PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types));
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) {
rvalType = MIRType::Value;
}
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
obj = addGuardReceiverPolymorphic(obj, receivers);
if (!obj) {
return abort(AbortReason::Alloc);
}
MInstruction* holderDef = constant(ObjectValue(*holder));
Shape* holderShape = holder->as<NativeObject>().shape();
holderDef = addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard);
Shape* propShape = holderShape->searchLinear(NameToId(name));
MOZ_ASSERT(propShape);
MOZ_TRY(loadSlot(holderDef, propShape, rvalType, barrier, types));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::getPropAddCache(MDefinition* obj,
PropertyName* name,
BarrierKind barrier,
TemporaryTypeSet* types) {
if (obj->type() != MIRType::Object) {
barrier = BarrierKind::TypeSet;
}
if (inspector->hasSeenAccessedGetter(pc)) {
barrier = BarrierKind::TypeSet;
}
if (barrier != BarrierKind::TypeSet) {
BarrierKind protoBarrier;
MOZ_TRY_VAR(protoBarrier, PropertyReadOnPrototypeNeedsTypeBarrier(
this, obj, name, types));
if (protoBarrier != BarrierKind::NoBarrier) {
MOZ_ASSERT(barrier <= protoBarrier);
barrier = protoBarrier;
}
}
if (barrier != BarrierKind::TypeSet && !types->unknown()) {
MOZ_ASSERT(obj->resultTypeSet());
switch (obj->resultTypeSet()->forAllClasses(constraints(),
IsTypedObjectClass)) {
case TemporaryTypeSet::ForAllResult::ALL_FALSE:
case TemporaryTypeSet::ForAllResult::EMPTY:
break;
case TemporaryTypeSet::ForAllResult::ALL_TRUE:
case TemporaryTypeSet::ForAllResult::MIXED:
barrier = BarrierKind::TypeSet;
break;
}
}
MConstant* id = constant(StringValue(name));
MGetPropertyCache* load =
MGetPropertyCache::New(alloc(), obj, id, barrier == BarrierKind::TypeSet);
if (obj->type() == MIRType::Object && !invalidatedIdempotentCache()) {
if (PropertyReadIsIdempotent(constraints(), obj, name)) {
load->setIdempotent();
}
}
if (JSOp(*pc) == JSOP_CALLPROP && load->idempotent()) {
MOZ_TRY(
annotateGetPropertyCache(obj, name, load, obj->resultTypeSet(), types));
}
current->add(load);
current->push(load);
if (load->isEffectful()) {
MOZ_TRY(resumeAfter(load));
}
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier) {
rvalType = MIRType::Value;
} else {
load->setResultTypeSet(types);
if (IsNullOrUndefined(rvalType)) {
rvalType = MIRType::Value;
}
}
load->setResultType(rvalType);
if (*pc != JSOP_CALLPROP || !IsNullOrUndefined(obj->type())) {
MOZ_TRY(pushTypeBarrier(load, types, barrier));
}
trackOptimizationSuccess();
return Ok();
}
MDefinition* IonBuilder::tryInnerizeWindow(MDefinition* obj) {
if (obj->type() != MIRType::Object) {
return obj;
}
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types) {
return obj;
}
JSObject* singleton = types->maybeSingleton();
if (!singleton) {
return obj;
}
if (!IsWindowProxyForScriptGlobal(script(), singleton)) {
return obj;
}
TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton);
if (key->hasFlags(constraints(), OBJECT_FLAG_UNKNOWN_PROPERTIES)) {
return obj;
}
obj->setImplicitlyUsedUnchecked();
return constant(ObjectValue(script()->global()));
}
AbortReasonOr<Ok> IonBuilder::getPropTryInnerize(bool* emitted,
MDefinition* obj,
PropertyName* name,
TemporaryTypeSet* types) {
MOZ_ASSERT(*emitted == false);
MDefinition* inner = tryInnerizeWindow(obj);
if (inner == obj) {
return Ok();
}
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
MOZ_TRY(getPropTryConstant(emitted, inner, NameToId(name), types));
if (*emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName);
MOZ_TRY(getStaticName(emitted, &script()->global(), name));
if (*emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
MOZ_TRY(getPropTryCommonGetter(emitted, inner, NameToId(name), types,
true));
if (*emitted) {
return Ok();
}
}
BarrierKind barrier = PropertyReadNeedsTypeBarrier(
analysisContext, alloc(), constraints(), inner, name, types);
trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache);
MOZ_TRY(getPropAddCache(inner, name, barrier, types));
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_setprop(PropertyName* name) {
MDefinition* value = current->pop();
MDefinition* obj = convertUnboxedObjects(current->pop());
bool emitted = false;
startTrackingOptimizations();
trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Value, value->type(), value->resultTypeSet());
if (info().isAnalysis() || shouldAbortOnPreliminaryGroups(obj)) {
bool strict = IsStrictSetPC(pc);
MInstruction* ins =
MCallSetProperty::New(alloc(), obj, value, name, strict);
current->add(ins);
current->push(value);
return resumeAfter(ins);
}
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::SetProp_CommonSetter);
MOZ_TRY(setPropTryCommonSetter(&emitted, obj, name, value));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::SetProp_TypedObject);
MOZ_TRY(setPropTryTypedObject(&emitted, obj, name, value));
if (emitted) {
return Ok();
}
}
TemporaryTypeSet* objTypes = obj->resultTypeSet();
bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
&obj, name, &value,
true);
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
MOZ_TRY(setPropTryUnboxed(&emitted, obj, name, value, barrier));
if (emitted) {
return Ok();
}
}
if (!forceInlineCaches()) {
trackOptimizationAttempt(TrackedStrategy::SetProp_DefiniteSlot);
MOZ_TRY(setPropTryDefiniteSlot(&emitted, obj, name, value, barrier));
if (emitted) {
return Ok();
}
trackOptimizationAttempt(TrackedStrategy::SetProp_InlineAccess);
MOZ_TRY(
setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes));
if (emitted) {
return Ok();
}
}
trackOptimizationAttempt(TrackedStrategy::SetProp_InlineCache);
MOZ_TRY(setPropTryCache(&emitted, obj, name, value, barrier));
MOZ_ASSERT(emitted == true);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setPropTryCommonSetter(bool* emitted,
MDefinition* obj,
PropertyName* name,
MDefinition* value) {
MOZ_ASSERT(*emitted == false);
TemporaryTypeSet* objTypes = obj->resultTypeSet();
JSFunction* commonSetter = nullptr;
MDefinition* guard = nullptr;
{
Shape* lastProperty = nullptr;
JSObject* foundProto = nullptr;
bool isOwnProperty;
BaselineInspector::ReceiverVector receivers(alloc());
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (inspector->commonSetPropFunction(pc, &foundProto, &lastProperty,
&commonSetter, &isOwnProperty,
receivers, convertUnboxedGroups)) {
bool canUseTIForSetter = false;
if (!isOwnProperty) {
MOZ_TRY_VAR(canUseTIForSetter,
testCommonGetterSetter(objTypes, NameToId(name),
false, commonSetter,
&guard));
}
if (!canUseTIForSetter) {
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
receivers, convertUnboxedGroups,
isOwnProperty);
if (!obj) {
return abort(AbortReason::Alloc);
}
}
} else if (inspector->megamorphicGetterSetterFunction(
pc, NameToId(name), false, &commonSetter)) {
bool canUseTIForSetter = false;
MOZ_TRY_VAR(
canUseTIForSetter,
testCommonGetterSetter(objTypes, NameToId(name),
false, commonSetter, &guard));
if (!canUseTIForSetter) {
return Ok();
}
} else {
return Ok();
}
}
MOZ_TRY(
setPropTryCommonDOMSetter(emitted, obj, value, commonSetter, objTypes));
if (*emitted) {
trackOptimizationOutcome(TrackedOutcome::DOM);
return Ok();
}
if (obj->type() != MIRType::Object) {
MGuardObject* guardObj = MGuardObject::New(alloc(), obj);
current->add(guardObj);
obj = guardObj;
}
if (!current->ensureHasSlots(3)) {
return abort(AbortReason::Alloc);
}
current->push(constant(ObjectValue(*commonSetter)));
current->push(obj);
current->push(value);
CallInfo callInfo(alloc(), pc, false,
BytecodeIsPopped(pc));
if (!callInfo.init(current, 1)) {
return abort(AbortReason::Alloc);
}
callInfo.markAsSetter();
if (commonSetter->isInterpreted()) {
InliningDecision decision = makeInliningDecision(commonSetter, callInfo);
switch (decision) {
case InliningDecision_Error:
return abort(AbortReason::Error);
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline: {
InliningStatus status;
MOZ_TRY_VAR(status, inlineScriptedCall(callInfo, commonSetter));
if (status == InliningStatus_Inlined) {
*emitted = true;
return Ok();
}
}
}
}
Maybe<CallTargets> targets;
targets.emplace(alloc());
if (!targets->append(commonSetter)) {
return abort(AbortReason::Alloc);
}
MCall* call;
MOZ_TRY_VAR(call, makeCallHelper(targets, callInfo));
current->push(value);
MOZ_TRY(resumeAfter(call));
if (!commonSetter->isInterpreted()) {
trackOptimizationSuccess();
}
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setPropTryCommonDOMSetter(
bool* emitted, MDefinition* obj, MDefinition* value, JSFunction* setter,
TemporaryTypeSet* objTypes) {
MOZ_ASSERT(*emitted == false);
DOMObjectKind objKind = DOMObjectKind::Unknown;
if (!objTypes || !objTypes->isDOMClass(constraints(), &objKind)) {
return Ok();
}
bool isDOM = false;
MOZ_TRY_VAR(isDOM, testShouldDOMCall(objTypes, setter, JSJitInfo::Setter));
if (!isDOM) {
return Ok();
}
MOZ_ASSERT(setter->jitInfo()->type() == JSJitInfo::Setter);
MSetDOMProperty* set = MSetDOMProperty::New(
alloc(), setter->jitInfo()->setter, objKind, setter->realm(), obj, value);
current->add(set);
current->push(value);
MOZ_TRY(resumeAfter(set));
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setPropTryTypedObject(bool* emitted,
MDefinition* obj,
PropertyName* name,
MDefinition* value) {
TypedObjectPrediction fieldPrediction;
size_t fieldOffset;
size_t fieldIndex;
bool fieldMutable = false;
if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction,
&fieldIndex, &fieldMutable)) {
return Ok();
}
if (!fieldMutable) {
return Ok();
}
switch (fieldPrediction.kind()) {
case type::Reference:
return setPropTryReferencePropOfTypedObject(emitted, obj, fieldOffset,
value, fieldPrediction, name);
case type::Scalar:
return setPropTryScalarPropOfTypedObject(emitted, obj, fieldOffset, value,
fieldPrediction);
case type::Struct:
case type::Array:
return Ok();
}
MOZ_CRASH("Unknown kind");
}
AbortReasonOr<Ok> IonBuilder::setPropTryReferencePropOfTypedObject(
bool* emitted, MDefinition* obj, int32_t fieldOffset, MDefinition* value,
TypedObjectPrediction fieldPrediction, PropertyName* name) {
ReferenceType fieldType = fieldPrediction.referenceType();
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return Ok();
}
if (fieldType == ReferenceType::TYPE_WASM_ANYREF) {
return Ok();
}
LinearSum byteOffset(alloc());
if (!byteOffset.add(fieldOffset)) {
return abort(AbortReason::Disable, "Overflow of field offset.");
}
return setPropTryReferenceTypedObjectValue(emitted, obj, byteOffset,
fieldType, value, name);
}
AbortReasonOr<Ok> IonBuilder::setPropTryScalarPropOfTypedObject(
bool* emitted, MDefinition* obj, int32_t fieldOffset, MDefinition* value,
TypedObjectPrediction fieldPrediction) {
Scalar::Type fieldType = fieldPrediction.scalarType();
TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
if (globalKey->hasFlags(constraints(),
OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
return Ok();
}
LinearSum byteOffset(alloc());
if (!byteOffset.add(fieldOffset)) {
return abort(AbortReason::Disable, "Overflow of field offet.");
}
return setPropTryScalarTypedObjectValue(emitted, obj, byteOffset, fieldType,
value);
}
AbortReasonOr<Ok> IonBuilder::setPropTryDefiniteSlot(bool* emitted,
MDefinition* obj,
PropertyName* name,
MDefinition* value,
bool barrier) {
MOZ_ASSERT(*emitted == false);
if (barrier) {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
return Ok();
}
uint32_t nfixed;
uint32_t slot =
getDefiniteSlot(obj->resultTypeSet(), NameToId(name), &nfixed);
if (slot == UINT32_MAX) {
return Ok();
}
bool writeBarrier = false;
for (size_t i = 0; i < obj->resultTypeSet()->getObjectCount(); i++) {
TypeSet::ObjectKey* key = obj->resultTypeSet()->getObject(i);
if (!key) {
continue;
}
HeapTypeSetKey property = key->property(NameToId(name));
if (property.nonWritable(constraints())) {
trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
return Ok();
}
writeBarrier |= property.needsBarrier(constraints());
}
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
MInstruction* store;
if (slot < nfixed) {
store = MStoreFixedSlot::New(alloc(), obj, slot, value);
if (writeBarrier) {
store->toStoreFixedSlot()->setNeedsBarrier();
}
} else {
MInstruction* slots = MSlots::New(alloc(), obj);
current->add(slots);
store = MStoreSlot::New(alloc(), slots, slot - nfixed, value);
if (writeBarrier) {
store->toStoreSlot()->setNeedsBarrier();
}
}
current->add(store);
current->push(value);
MOZ_TRY(resumeAfter(store));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
MInstruction* IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset,
JSValueType unboxedType,
MDefinition* value) {
size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
MInstruction* scaledOffset =
MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
current->add(scaledOffset);
return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(),
scaledOffset, unboxedType, value);
}
MInstruction* IonBuilder::storeUnboxedValue(
MDefinition* obj, MDefinition* elements, int32_t elementsOffset,
MDefinition* scaledOffset, JSValueType unboxedType, MDefinition* value,
bool preBarrier ) {
MInstruction* store;
switch (unboxedType) {
case JSVAL_TYPE_BOOLEAN:
store = MStoreUnboxedScalar::New(
alloc(), elements, scaledOffset, value, Scalar::Uint8,
MStoreUnboxedScalar::DontTruncateInput, DoesNotRequireMemoryBarrier,
elementsOffset);
break;
case JSVAL_TYPE_INT32:
store = MStoreUnboxedScalar::New(
alloc(), elements, scaledOffset, value, Scalar::Int32,
MStoreUnboxedScalar::DontTruncateInput, DoesNotRequireMemoryBarrier,
elementsOffset);
break;
case JSVAL_TYPE_DOUBLE:
store = MStoreUnboxedScalar::New(
alloc(), elements, scaledOffset, value, Scalar::Float64,
MStoreUnboxedScalar::DontTruncateInput, DoesNotRequireMemoryBarrier,
elementsOffset);
break;
case JSVAL_TYPE_STRING:
store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value,
obj, elementsOffset, preBarrier);
break;
case JSVAL_TYPE_OBJECT:
MOZ_ASSERT(value->type() == MIRType::Object ||
value->type() == MIRType::Null ||
value->type() == MIRType::Value);
MOZ_ASSERT(!value->mightBeType(MIRType::Undefined),
"MToObjectOrNull slow path is invalid for unboxed objects");
store =
MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value,
obj, elementsOffset, preBarrier);
break;
default:
MOZ_CRASH();
}
current->add(store);
return store;
}
AbortReasonOr<Ok> IonBuilder::setPropTryUnboxed(bool* emitted, MDefinition* obj,
PropertyName* name,
MDefinition* value,
bool barrier) {
MOZ_ASSERT(*emitted == false);
if (barrier) {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
return Ok();
}
JSValueType unboxedType;
uint32_t offset =
getUnboxedOffset(obj->resultTypeSet(), NameToId(name), &unboxedType);
if (offset == UINT32_MAX) {
return Ok();
}
if (obj->type() != MIRType::Object) {
MGuardObject* guard = MGuardObject::New(alloc(), obj);
current->add(guard);
obj = guard;
}
MInstruction* store = storeUnboxedProperty(obj, offset, unboxedType, value);
current->push(value);
MOZ_TRY(resumeAfter(store));
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setPropTryInlineAccess(
bool* emitted, MDefinition* obj, PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes) {
MOZ_ASSERT(*emitted == false);
if (barrier) {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
return Ok();
}
BaselineInspector::ReceiverVector receivers(alloc());
BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups)) {
return abort(AbortReason::Alloc);
}
if (!canInlinePropertyOpShapes(receivers)) {
return Ok();
}
obj = convertUnboxedObjects(obj, convertUnboxedGroups);
if (receivers.length() == 1) {
if (!receivers[0].group) {
spew("Inlining monomorphic native SETPROP");
obj = addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard);
Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
MOZ_ASSERT(shape);
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
bool needsPreBarrier =
objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
MOZ_TRY(storeSlot(obj, shape, value, needsPreBarrier));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
if (receivers[0].shape) {
spew("Inlining monomorphic unboxed expando SETPROP");
obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
obj = addUnboxedExpandoGuard(obj, true,
Bailout_ShapeGuard);
MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
current->add(expando);
expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
MOZ_ASSERT(shape);
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
bool needsPreBarrier =
objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
MOZ_TRY(storeSlot(expando, shape, value, needsPreBarrier));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
spew("Inlining monomorphic unboxed SETPROP");
ObjectGroup* group = receivers[0].group;
if (!objTypes->hasType(TypeSet::ObjectType(group))) {
return Ok();
}
obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
AutoSweepObjectGroup sweep(group);
const UnboxedLayout::Property* property =
group->unboxedLayout(sweep).lookup(name);
MInstruction* store =
storeUnboxedProperty(obj, property->offset, property->type, value);
current->push(value);
MOZ_TRY(resumeAfter(store));
trackOptimizationOutcome(TrackedOutcome::Monomorphic);
*emitted = true;
return Ok();
}
MOZ_ASSERT(receivers.length() > 1);
spew("Inlining polymorphic SETPROP");
if (Shape* propShape =
PropertyShapesHaveSameSlot(receivers, NameToId(name))) {
obj = addGuardReceiverPolymorphic(obj, receivers);
if (!obj) {
return abort(AbortReason::Alloc);
}
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
bool needsPreBarrier =
objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
MOZ_TRY(storeSlot(obj, propShape, value, needsPreBarrier));
trackOptimizationOutcome(TrackedOutcome::Polymorphic);
*emitted = true;
return Ok();
}
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), obj, value));
}
MSetPropertyPolymorphic* ins =
MSetPropertyPolymorphic::New(alloc(), obj, value, name);
current->add(ins);
current->push(value);
for (size_t i = 0; i < receivers.length(); i++) {
Shape* propShape = nullptr;
if (receivers[i].shape) {
propShape = receivers[i].shape->searchLinear(NameToId(name));
MOZ_ASSERT(propShape);
}
if (!ins->addReceiver(receivers[i], propShape)) {
return abort(AbortReason::Alloc);
}
}
if (objTypes->propertyNeedsBarrier(constraints(), NameToId(name))) {
ins->setNeedsBarrier();
}
MOZ_TRY(resumeAfter(ins));
trackOptimizationOutcome(TrackedOutcome::Polymorphic);
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::setPropTryCache(bool* emitted, MDefinition* obj,
PropertyName* name,
MDefinition* value,
bool barrier) {
MOZ_ASSERT(*emitted == false);
bool strict = IsStrictSetPC(pc);
MConstant* id = constant(StringValue(name));
MSetPropertyCache* ins = MSetPropertyCache::New(
alloc(), obj, id, value, strict, needsPostBarrier(value), barrier,
false);
current->add(ins);
current->push(value);
MOZ_TRY(resumeAfter(ins));
trackOptimizationSuccess();
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_delprop(PropertyName* name) {
MDefinition* obj = current->pop();
bool strict = JSOp(*pc) == JSOP_STRICTDELPROP;
MInstruction* ins = MDeleteProperty::New(alloc(), obj, name, strict);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_delelem() {
MDefinition* index = current->pop();
MDefinition* obj = current->pop();
bool strict = JSOp(*pc) == JSOP_STRICTDELELEM;
MDeleteElement* ins = MDeleteElement::New(alloc(), obj, index, strict);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_regexp(RegExpObject* reobj) {
MOZ_ASSERT(!IsInsideNursery(reobj));
bool hasShared = reobj->hasShared();
MRegExp* regexp = MRegExp::New(alloc(), constraints(), reobj, hasShared);
current->add(regexp);
current->push(regexp);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_object(JSObject* obj) {
if (options.cloneSingletons()) {
MCloneLiteral* clone =
MCloneLiteral::New(alloc(), constant(ObjectValue(*obj)));
current->add(clone);
current->push(clone);
return resumeAfter(clone);
}
realm->setSingletonsAsValues();
pushConstant(ObjectValue(*obj));
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_classconstructor() {
MClassConstructor* constructor = MClassConstructor::New(alloc(), pc);
current->add(constructor);
current->push(constructor);
return resumeAfter(constructor);
}
AbortReasonOr<Ok> IonBuilder::jsop_lambda(JSFunction* fun) {
MOZ_ASSERT(usesEnvironmentChain());
MOZ_ASSERT(!fun->isArrow());
if (IsAsmJSModule(fun)) {
return abort(AbortReason::Disable, "Lambda is an asm.js module function");
}
MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun);
current->add(cst);
MLambda* ins =
MLambda::New(alloc(), constraints(), current->environmentChain(), cst);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_lambda_arrow(JSFunction* fun) {
MOZ_ASSERT(usesEnvironmentChain());
MOZ_ASSERT(fun->isArrow());
MOZ_ASSERT(!fun->isNative());
MDefinition* newTargetDef = current->pop();
MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun);
current->add(cst);
MLambdaArrow* ins = MLambdaArrow::New(
alloc(), constraints(), current->environmentChain(), newTargetDef, cst);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_setfunname(uint8_t prefixKind) {
MDefinition* name = current->pop();
MDefinition* fun = current->pop();
MOZ_ASSERT(fun->type() == MIRType::Object);
MSetFunName* ins = MSetFunName::New(alloc(), fun, name, prefixKind);
current->add(ins);
current->push(fun);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_pushlexicalenv(uint32_t index) {
MOZ_ASSERT(usesEnvironmentChain());
LexicalScope* scope = &script()->getScope(index)->as<LexicalScope>();
MNewLexicalEnvironmentObject* ins = MNewLexicalEnvironmentObject::New(
alloc(), current->environmentChain(), scope);
current->add(ins);
current->setEnvironmentChain(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_copylexicalenv(bool copySlots) {
MOZ_ASSERT(usesEnvironmentChain());
MCopyLexicalEnvironmentObject* ins = MCopyLexicalEnvironmentObject::New(
alloc(), current->environmentChain(), copySlots);
current->add(ins);
current->setEnvironmentChain(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_setarg(uint32_t arg) {
MOZ_ASSERT_IF(script()->hasBaselineScript(),
script()->baselineScript()->modifiesArguments());
MDefinition* val = current->peek(-1);
if (info().argsObjAliasesFormals()) {
if (needsPostBarrier(val)) {
current->add(
MPostWriteBarrier::New(alloc(), current->argumentsObject(), val));
}
auto* ins = MSetArgumentsObjectArg::New(alloc(), current->argumentsObject(),
GET_ARGNO(pc), val);
current->add(ins);
MOZ_TRY(resumeAfter(ins));
return Ok();
}
if (info().hasArguments()) {
return abort(AbortReason::Disable, "NYI: arguments & setarg.");
}
if (info().argumentsAliasesFormals()) {
MOZ_ASSERT(script()->uninlineable() && !isInlineBuilder());
MSetFrameArgument* store = MSetFrameArgument::New(alloc(), arg, val);
modifiesFrameArguments_ = true;
current->add(store);
current->setArg(arg);
return Ok();
}
current->setArg(arg);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_defvar() {
MOZ_ASSERT(JSOp(*pc) == JSOP_DEFVAR);
MOZ_ASSERT(usesEnvironmentChain());
MDefVar* defvar = MDefVar::New(alloc(), current->environmentChain());
current->add(defvar);
return resumeAfter(defvar);
}
AbortReasonOr<Ok> IonBuilder::jsop_deflexical() {
MOZ_ASSERT(JSOp(*pc) == JSOP_DEFLET || JSOp(*pc) == JSOP_DEFCONST);
MOZ_ASSERT(usesEnvironmentChain());
MDefinition* env = current->environmentChain();
MDefLexical* defLexical = MDefLexical::New(alloc(), env);
current->add(defLexical);
return resumeAfter(defLexical);
}
AbortReasonOr<Ok> IonBuilder::jsop_deffun() {
MOZ_ASSERT(usesEnvironmentChain());
MDefFun* deffun =
MDefFun::New(alloc(), current->pop(), current->environmentChain());
current->add(deffun);
return resumeAfter(deffun);
}
AbortReasonOr<Ok> IonBuilder::jsop_throwsetconst() {
current->peek(-1)->setImplicitlyUsedUnchecked();
MInstruction* lexicalError =
MThrowRuntimeLexicalError::New(alloc(), JSMSG_BAD_CONST_ASSIGN);
current->add(lexicalError);
return resumeAfter(lexicalError);
}
AbortReasonOr<Ok> IonBuilder::jsop_checklexical() {
uint32_t slot = info().localSlot(GET_LOCALNO(pc));
MDefinition* lexical;
MOZ_TRY_VAR(lexical, addLexicalCheck(current->getSlot(slot)));
current->setSlot(slot, lexical);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_checkaliasedlexical(
EnvironmentCoordinate ec) {
MDefinition* let;
MOZ_TRY_VAR(let, addLexicalCheck(getAliasedVar(ec)));
jsbytecode* nextPc = pc + JSOP_CHECKALIASEDLEXICAL_LENGTH;
MOZ_ASSERT(JSOp(*nextPc) == JSOP_GETALIASEDVAR ||
JSOp(*nextPc) == JSOP_SETALIASEDVAR ||
JSOp(*nextPc) == JSOP_THROWSETALIASEDCONST);
MOZ_ASSERT(ec == EnvironmentCoordinate(nextPc));
if (JSOp(*nextPc) == JSOP_GETALIASEDVAR) {
setLexicalCheck(let);
}
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_functionthis() {
MOZ_ASSERT(info().funMaybeLazy());
MOZ_ASSERT(!info().funMaybeLazy()->isArrow());
if (script()->strict()) {
current->pushSlot(info().thisSlot());
return Ok();
}
if (thisTypes && (thisTypes->getKnownMIRType() == MIRType::Object ||
(thisTypes->empty() && baselineFrame_ &&
baselineFrame_->thisType.isSomeObject()))) {
current->pushSlot(info().thisSlot());
return Ok();
}
if (info().isAnalysis()) {
current->pushSlot(info().thisSlot());
return Ok();
}
MDefinition* def = current->getSlot(info().thisSlot());
if (def->type() == MIRType::Object) {
current->push(def);
return Ok();
}
if (script()->hasNonSyntacticScope()) {
return abort(AbortReason::Disable,
"JSOP_FUNCTIONTHIS would need non-syntactic global");
}
if (IsNullOrUndefined(def->type())) {
LexicalEnvironmentObject* globalLexical =
&script()->global().lexicalEnvironment();
pushConstant(globalLexical->thisValue());
return Ok();
}
MComputeThis* thisObj = MComputeThis::New(alloc(), def);
current->add(thisObj);
current->push(thisObj);
return resumeAfter(thisObj);
}
AbortReasonOr<Ok> IonBuilder::jsop_globalthis() {
if (script()->hasNonSyntacticScope()) {
return abort(AbortReason::Disable,
"JSOP_GLOBALTHIS in script with non-syntactic scope");
}
LexicalEnvironmentObject* globalLexical =
&script()->global().lexicalEnvironment();
pushConstant(globalLexical->thisValue());
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_typeof() {
MDefinition* input = current->pop();
MTypeOf* ins = MTypeOf::New(alloc(), input, input->type());
ins->cacheInputMaybeCallableOrEmulatesUndefined(constraints());
current->add(ins);
current->push(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_toasynciter() {
MDefinition* nextMethod = current->pop();
MDefinition* iterator = current->pop();
MOZ_ASSERT(iterator->type() == MIRType::Object);
MToAsyncIter* ins = MToAsyncIter::New(alloc(), iterator, nextMethod);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_toid() {
MIRType type = current->peek(-1)->type();
if (type == MIRType::Int32 || type == MIRType::String ||
type == MIRType::Symbol) {
return Ok();
}
MDefinition* index = current->pop();
MToId* ins = MToId::New(alloc(), index);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_iter() {
MDefinition* obj = current->pop();
MInstruction* ins = MGetIteratorCache::New(alloc(), obj);
if (!outermostBuilder()->iterators_.append(ins)) {
return abort(AbortReason::Alloc);
}
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_itermore() {
MDefinition* iter = current->peek(-1);
MInstruction* ins = MIteratorMore::New(alloc(), iter);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_isnoiter() {
MDefinition* def = current->peek(-1);
MOZ_ASSERT(def->isIteratorMore());
MInstruction* ins = MIsNoIter::New(alloc(), def);
current->add(ins);
current->push(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_iterend() {
MDefinition* iter = current->pop();
MInstruction* ins = MIteratorEnd::New(alloc(), iter);
current->add(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_iternext() {
MDefinition* def = current->pop();
MOZ_ASSERT(def->type() == MIRType::Value);
MInstruction* unbox =
MUnbox::New(alloc(), def, MIRType::String, MUnbox::Infallible);
current->add(unbox);
current->push(unbox);
return Ok();
}
MDefinition* IonBuilder::walkEnvironmentChain(unsigned hops) {
MDefinition* env = current->getSlot(info().environmentChainSlot());
for (unsigned i = 0; i < hops; i++) {
MInstruction* ins = MEnclosingEnvironment::New(alloc(), env);
current->add(ins);
env = ins;
}
return env;
}
MDefinition* IonBuilder::getAliasedVar(EnvironmentCoordinate ec) {
MDefinition* obj = walkEnvironmentChain(ec.hops());
MInstruction* load;
if (EnvironmentObject::nonExtensibleIsFixedSlot(ec)) {
load = MLoadFixedSlot::New(alloc(), obj, ec.slot());
} else {
MInstruction* slots = MSlots::New(alloc(), obj);
current->add(slots);
uint32_t slot = EnvironmentObject::nonExtensibleDynamicSlotIndex(ec);
load = MLoadSlot::New(alloc(), slots, slot);
}
current->add(load);
return load;
}
AbortReasonOr<Ok> IonBuilder::jsop_getaliasedvar(EnvironmentCoordinate ec) {
MDefinition* load = takeLexicalCheck();
if (!load) {
load = getAliasedVar(ec);
}
current->push(load);
TemporaryTypeSet* types = bytecodeTypes(pc);
return pushTypeBarrier(load, types, BarrierKind::TypeSet);
}
AbortReasonOr<Ok> IonBuilder::jsop_setaliasedvar(EnvironmentCoordinate ec) {
MDefinition* rval = current->peek(-1);
MDefinition* obj = walkEnvironmentChain(ec.hops());
if (needsPostBarrier(rval)) {
current->add(MPostWriteBarrier::New(alloc(), obj, rval));
}
MInstruction* store;
if (EnvironmentObject::nonExtensibleIsFixedSlot(ec)) {
store = MStoreFixedSlot::NewBarriered(alloc(), obj, ec.slot(), rval);
} else {
MInstruction* slots = MSlots::New(alloc(), obj);
current->add(slots);
uint32_t slot = EnvironmentObject::nonExtensibleDynamicSlotIndex(ec);
store = MStoreSlot::NewBarriered(alloc(), slots, slot, rval);
}
current->add(store);
return resumeAfter(store);
}
AbortReasonOr<Ok> IonBuilder::jsop_in() {
MDefinition* obj = convertUnboxedObjects(current->pop());
MDefinition* id = current->pop();
if (!forceInlineCaches()) {
bool emitted = false;
MOZ_TRY(inTryDense(&emitted, obj, id));
if (emitted) {
return Ok();
}
MOZ_TRY(hasTryNotDefined(&emitted, obj, id, false));
if (emitted) {
return Ok();
}
MOZ_TRY(hasTryDefiniteSlotOrUnboxed(&emitted, obj, id));
if (emitted) {
return Ok();
}
}
MInCache* ins = MInCache::New(alloc(), id, obj);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::inTryDense(bool* emitted, MDefinition* obj,
MDefinition* id) {
MOZ_ASSERT(!*emitted);
if (shouldAbortOnPreliminaryGroups(obj)) {
return Ok();
}
if (!ElementAccessIsDenseNative(constraints(), obj, id)) {
return Ok();
}
bool hasExtraIndexedProperty;
MOZ_TRY_VAR(hasExtraIndexedProperty,
ElementAccessHasExtraIndexedProperty(this, obj));
if (hasExtraIndexedProperty) {
return Ok();
}
*emitted = true;
bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);
MInstruction* idInt32 = MToNumberInt32::New(alloc(), id);
current->add(idInt32);
id = idInt32;
MElements* elements = MElements::New(alloc(), obj);
current->add(elements);
MInstruction* initLength = initializedLength(elements);
if (!needsHoleCheck && !failedBoundsCheck_) {
addBoundsCheck(idInt32, initLength);
pushConstant(BooleanValue(true));
return Ok();
}
MInArray* ins =
MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck);
current->add(ins);
current->push(ins);
return Ok();
}
AbortReasonOr<Ok> IonBuilder::hasTryNotDefined(bool* emitted, MDefinition* obj,
MDefinition* id,
bool ownProperty) {
MOZ_ASSERT(!*emitted);
MConstant* idConst = id->maybeConstantValue();
jsid propId;
if (!idConst || !ValueToIdPure(idConst->toJSValue(), &propId)) {
return Ok();
}
if (propId != IdToTypeId(propId)) {
return Ok();
}
bool res;
MOZ_TRY_VAR(res, testNotDefinedProperty(obj, propId, ownProperty));
if (!res) {
return Ok();
}
*emitted = true;
pushConstant(BooleanValue(false));
obj->setImplicitlyUsedUnchecked();
id->setImplicitlyUsedUnchecked();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::hasTryDefiniteSlotOrUnboxed(bool* emitted,
MDefinition* obj,
MDefinition* id) {
MOZ_ASSERT(!*emitted);
if (obj->type() != MIRType::Object) {
return Ok();
}
MConstant* idConst = id->maybeConstantValue();
jsid propId;
if (!idConst || !ValueToIdPure(idConst->toJSValue(), &propId)) {
return Ok();
}
if (propId != IdToTypeId(propId)) {
return Ok();
}
uint32_t nfixed;
uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), propId, &nfixed);
if (slot == UINT32_MAX) {
JSValueType unboxedType;
uint32_t offset =
getUnboxedOffset(obj->resultTypeSet(), propId, &unboxedType);
if (offset == UINT32_MAX) {
return Ok();
}
}
*emitted = true;
pushConstant(BooleanValue(true));
obj->setImplicitlyUsedUnchecked();
id->setImplicitlyUsedUnchecked();
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_hasown() {
MDefinition* obj = convertUnboxedObjects(current->pop());
MDefinition* id = current->pop();
if (!forceInlineCaches()) {
bool emitted = false;
MOZ_TRY(hasTryNotDefined(&emitted, obj, id, true));
if (emitted) {
return Ok();
}
MOZ_TRY(hasTryDefiniteSlotOrUnboxed(&emitted, obj, id));
if (emitted) {
return Ok();
}
}
MHasOwnCache* ins = MHasOwnCache::New(alloc(), obj, id);
current->add(ins);
current->push(ins);
MOZ_TRY(resumeAfter(ins));
return Ok();
}
AbortReasonOr<bool> IonBuilder::hasOnProtoChain(TypeSet::ObjectKey* key,
JSObject* protoObject,
bool* onProto) {
MOZ_ASSERT(protoObject);
while (true) {
if (!alloc().ensureBallast()) {
return abort(AbortReason::Alloc);
}
if (!key->hasStableClassAndProto(constraints()) ||
!key->clasp()->isNative()) {
return false;
}
JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
if (!proto) {
*onProto = false;
return true;
}
if (proto == protoObject) {
*onProto = true;
return true;
}
key = TypeSet::ObjectKey::get(proto);
}
MOZ_CRASH("Unreachable");
}
AbortReasonOr<Ok> IonBuilder::tryFoldInstanceOf(bool* emitted, MDefinition* lhs,
JSObject* protoObject) {
MOZ_ASSERT(*emitted == false);
if (!lhs->mightBeType(MIRType::Object)) {
lhs->setImplicitlyUsedUnchecked();
pushConstant(BooleanValue(false));
*emitted = true;
return Ok();
}
TemporaryTypeSet* lhsTypes = lhs->resultTypeSet();
if (!lhsTypes || lhsTypes->unknownObject()) {
return Ok();
}
bool isFirst = true;
bool knownIsInstance = false;
for (unsigned i = 0; i < lhsTypes->getObjectCount(); i++) {
TypeSet::ObjectKey* key = lhsTypes->getObject(i);
if (!key) {
continue;
}
bool checkSucceeded;
bool isInstance;
MOZ_TRY_VAR(checkSucceeded, hasOnProtoChain(key, protoObject, &isInstance));
if (!checkSucceeded) {
return Ok();
}
if (isFirst) {
knownIsInstance = isInstance;
isFirst = false;
} else if (knownIsInstance != isInstance) {
return Ok();
}
}
if (knownIsInstance && lhsTypes->getKnownMIRType() != MIRType::Object) {
MIsObject* isObject = MIsObject::New(alloc(), lhs);
current->add(isObject);
current->push(isObject);
*emitted = true;
return Ok();
}
lhs->setImplicitlyUsedUnchecked();
pushConstant(BooleanValue(knownIsInstance));
*emitted = true;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::jsop_instanceof() {
MDefinition* rhs = current->pop();
MDefinition* obj = current->pop();
bool emitted = false;
do {
TemporaryTypeSet* rhsTypes = rhs->resultTypeSet();
JSObject* rhsObject = rhsTypes ? rhsTypes->maybeSingleton() : nullptr;
if (!rhsObject || !rhsObject->is<JSFunction>() ||
rhsObject->isBoundFunction()) {
break;
}
if (rhsObject->hasUncacheableProto() || !rhsObject->hasStaticPrototype()) {
break;
}
Value funProto = script()->global().getPrototype(JSProto_Function);
if (!funProto.isObject() ||
rhsObject->staticPrototype() != &funProto.toObject()) {
break;
}
JSFunction* fun = &rhsObject->as<JSFunction>();
const WellKnownSymbols* symbols = &realm->runtime()->wellKnownSymbols();
if (!js::FunctionHasDefaultHasInstance(fun, *symbols)) {
break;
}
TypeSet::ObjectKey* rhsKey = TypeSet::ObjectKey::get(rhsObject);
if (!rhsKey->hasStableClassAndProto(constraints())) {
break;
}
if (rhsKey->unknownProperties()) {
break;
}
HeapTypeSetKey hasInstanceObject =
rhsKey->property(SYMBOL_TO_JSID(symbols->hasInstance));
if (hasInstanceObject.isOwnProperty(constraints())) {
break;
}
HeapTypeSetKey protoProperty =
rhsKey->property(NameToId(names().prototype));
JSObject* protoObject = protoProperty.singleton(constraints());
if (!protoObject) {
break;
}
rhs->setImplicitlyUsedUnchecked();
MOZ_TRY(tryFoldInstanceOf(&emitted, obj, protoObject));
if (emitted) {
return Ok();
}
MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
} while (false);
do {
Shape* shape;
uint32_t slot;
JSObject* protoObject;
if (!inspector->instanceOfData(pc, &shape, &slot, &protoObject)) {
break;
}
rhs = addShapeGuard(rhs, shape, Bailout_ShapeGuard);
MOZ_ASSERT(shape->numFixedSlots() == 0, "Must be a dynamic slot");
MSlots* slots = MSlots::New(alloc(), rhs);
current->add(slots);
MLoadSlot* prototype = MLoadSlot::New(alloc(), slots, slot);
current->add(prototype);
MConstant* protoConst =
MConstant::NewConstraintlessObject(alloc(), protoObject);
current->add(protoConst);
MGuardObjectIdentity* guard =
MGuardObjectIdentity::New(alloc(), prototype, protoConst,
false);
current->add(guard);
MOZ_TRY(tryFoldInstanceOf(&emitted, obj, protoObject));
if (emitted) {
return Ok();
}
MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
} while (false);
MInstanceOfCache* ins = MInstanceOfCache::New(alloc(), obj, rhs);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
AbortReasonOr<Ok> IonBuilder::jsop_debugger() {
MDebugger* debugger = MDebugger::New(alloc());
current->add(debugger);
return resumeAt(debugger, pc);
}
AbortReasonOr<Ok> IonBuilder::jsop_implicitthis(PropertyName* name) {
MOZ_ASSERT(usesEnvironmentChain());
MImplicitThis* implicitThis =
MImplicitThis::New(alloc(), current->environmentChain(), name);
current->add(implicitThis);
current->push(implicitThis);
return resumeAfter(implicitThis);
}
AbortReasonOr<Ok> IonBuilder::jsop_importmeta() {
if (info().analysisMode() == Analysis_ArgumentsUsage) {
MUnknownValue* unknown = MUnknownValue::New(alloc());
current->add(unknown);
current->push(unknown);
return Ok();
}
ModuleObject* module = GetModuleObjectForScript(script());
MOZ_ASSERT(module);
MModuleMetadata* meta = MModuleMetadata::New(alloc(), module);
current->add(meta);
current->push(meta);
return resumeAfter(meta);
}
AbortReasonOr<Ok> IonBuilder::jsop_dynamic_import() {
MDefinition* specifier = current->pop();
MDynamicImport* ins = MDynamicImport::New(alloc(), specifier);
current->add(ins);
current->push(ins);
return resumeAfter(ins);
}
MInstruction* IonBuilder::addConvertElementsToDoubles(MDefinition* elements) {
MInstruction* convert = MConvertElementsToDoubles::New(alloc(), elements);
current->add(convert);
return convert;
}
MDefinition* IonBuilder::addMaybeCopyElementsForWrite(MDefinition* object,
bool checkNative) {
if (!ElementAccessMightBeCopyOnWrite(constraints(), object)) {
return object;
}
MInstruction* copy =
MMaybeCopyElementsForWrite::New(alloc(), object, checkNative);
current->add(copy);
return copy;
}
MInstruction* IonBuilder::addBoundsCheck(MDefinition* index,
MDefinition* length) {
MInstruction* check = MBoundsCheck::New(alloc(), index, length);
current->add(check);
if (failedBoundsCheck_) {
check->setNotMovable();
}
if (JitOptions.spectreIndexMasking) {
check = MSpectreMaskIndex::New(alloc(), check, length);
current->add(check);
}
return check;
}
MInstruction* IonBuilder::addShapeGuard(MDefinition* obj, Shape* const shape,
BailoutKind bailoutKind) {
MGuardShape* guard = MGuardShape::New(alloc(), obj, shape, bailoutKind);
current->add(guard);
if (failedShapeGuard_) {
guard->setNotMovable();
}
return guard;
}
MInstruction* IonBuilder::addGroupGuard(MDefinition* obj, ObjectGroup* group,
BailoutKind bailoutKind) {
MGuardObjectGroup* guard =
MGuardObjectGroup::New(alloc(), obj, group,
false, bailoutKind);
current->add(guard);
if (failedShapeGuard_) {
guard->setNotMovable();
}
LifoAlloc* lifoAlloc = alloc().lifoAlloc();
guard->setResultTypeSet(
lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, TypeSet::ObjectType(group)));
return guard;
}
MInstruction* IonBuilder::addUnboxedExpandoGuard(MDefinition* obj,
bool hasExpando,
BailoutKind bailoutKind) {
MGuardUnboxedExpando* guard =
MGuardUnboxedExpando::New(alloc(), obj, hasExpando, bailoutKind);
current->add(guard);
if (failedShapeGuard_) {
guard->setNotMovable();
}
return guard;
}
MInstruction* IonBuilder::addGuardReceiverPolymorphic(
MDefinition* obj, const BaselineInspector::ReceiverVector& receivers) {
if (receivers.length() == 1) {
if (!receivers[0].group) {
return addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard);
}
if (!receivers[0].shape) {
obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
return addUnboxedExpandoGuard(obj, false,
Bailout_ShapeGuard);
}
}
MGuardReceiverPolymorphic* guard =
MGuardReceiverPolymorphic::New(alloc(), obj);
current->add(guard);
if (failedShapeGuard_) {
guard->setNotMovable();
}
for (size_t i = 0; i < receivers.length(); i++) {
if (!guard->addReceiver(receivers[i])) {
return nullptr;
}
}
return guard;
}
MInstruction* IonBuilder::addSharedTypedArrayGuard(MDefinition* obj) {
MGuardSharedTypedArray* guard = MGuardSharedTypedArray::New(alloc(), obj);
current->add(guard);
return guard;
}
TemporaryTypeSet* IonBuilder::bytecodeTypes(jsbytecode* pc) {
return TypeScript::BytecodeTypes(script(), pc, bytecodeTypeMap,
&typeArrayHint, typeArray);
}
TypedObjectPrediction IonBuilder::typedObjectPrediction(MDefinition* typedObj) {
if (typedObj->isNewDerivedTypedObject()) {
return typedObj->toNewDerivedTypedObject()->prediction();
}
TemporaryTypeSet* types = typedObj->resultTypeSet();
return typedObjectPrediction(types);
}
TypedObjectPrediction IonBuilder::typedObjectPrediction(
TemporaryTypeSet* types) {
if (!types || types->getKnownMIRType() != MIRType::Object) {
return TypedObjectPrediction();
}
if (types->unknownObject()) {
return TypedObjectPrediction();
}
TypedObjectPrediction out;
for (uint32_t i = 0; i < types->getObjectCount(); i++) {
ObjectGroup* group = types->getGroup(i);
if (!group || !IsTypedObjectClass(group->clasp())) {
return TypedObjectPrediction();
}
if (!TypeSet::ObjectKey::get(group)->hasStableClassAndProto(
constraints())) {
return TypedObjectPrediction();
}
out.addDescr(group->typeDescr());
}
return out;
}
MDefinition* IonBuilder::loadTypedObjectType(MDefinition* typedObj) {
if (typedObj->isNewDerivedTypedObject()) {
return typedObj->toNewDerivedTypedObject()->type();
}
MInstruction* descr = MTypedObjectDescr::New(alloc(), typedObj);
current->add(descr);
return descr;
}
AbortReasonOr<Ok> IonBuilder::loadTypedObjectData(MDefinition* typedObj,
MDefinition** owner,
LinearSum* ownerOffset) {
MOZ_ASSERT(typedObj->type() == MIRType::Object);
if (typedObj->isNewDerivedTypedObject()) {
MNewDerivedTypedObject* ins = typedObj->toNewDerivedTypedObject();
SimpleLinearSum base = ExtractLinearSum(ins->offset());
if (!ownerOffset->add(base)) {
return abort(AbortReason::Disable,
"Overflow/underflow on type object offset.");
}
*owner = ins->owner();
return Ok();
}
*owner = typedObj;
return Ok();
}
AbortReasonOr<Ok> IonBuilder::loadTypedObjectElements(
MDefinition* typedObj, const LinearSum& baseByteOffset, uint32_t scale,
MDefinition** ownerElements, MDefinition** ownerScaledOffset,
int32_t* ownerByteAdjustment) {
MDefinition* owner;
LinearSum ownerByteOffset(alloc());
MOZ_TRY(loadTypedObjectData(typedObj, &owner, &ownerByteOffset));
if (!ownerByteOffset.add(baseByteOffset)) {
return abort(AbortReason::Disable,
"Overflow after adding the base offset.");
}
TemporaryTypeSet* ownerTypes = owner->resultTypeSet();
const Class* clasp =
ownerTypes ? ownerTypes->getKnownClass(constraints()) : nullptr;
if (clasp && IsInlineTypedObjectClass(clasp)) {
if (!ownerByteOffset.add(InlineTypedObject::offsetOfDataStart())) {
return abort(AbortReason::Disable,
"Overflow after adding the data start.");
}
*ownerElements = owner;
} else {
bool definitelyOutline = clasp && IsOutlineTypedObjectClass(clasp);
*ownerElements =
MTypedObjectElements::New(alloc(), owner, definitelyOutline);
current->add((*ownerElements)->toInstruction());
}
*ownerByteAdjustment = ownerByteOffset.constant();
int32_t negativeAdjustment;
if (!SafeSub(0, *ownerByteAdjustment, &negativeAdjustment)) {
return abort(AbortReason::Disable);
}
if (!ownerByteOffset.add(negativeAdjustment)) {
return abort(AbortReason::Disable);
}
if (ownerByteOffset.divide(scale)) {
*ownerScaledOffset = ConvertLinearSum(alloc(), current, ownerByteOffset);
} else {
MDefinition* unscaledOffset =
ConvertLinearSum(alloc(), current, ownerByteOffset);
*ownerScaledOffset = MDiv::New(alloc(), unscaledOffset, constantInt(scale),
MIRType::Int32, false);
current->add((*ownerScaledOffset)->toInstruction());
}
return Ok();
}
bool IonBuilder::typedObjectHasField(MDefinition* typedObj, PropertyName* name,
size_t* fieldOffset,
TypedObjectPrediction* fieldPrediction,
size_t* fieldIndex, bool* fieldMutable) {
TypedObjectPrediction objPrediction = typedObjectPrediction(typedObj);
if (objPrediction.isUseless()) {
trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject);
return false;
}
if (objPrediction.kind() != type::Struct) {
trackOptimizationOutcome(TrackedOutcome::NotStruct);
return false;
}
if (!objPrediction.hasFieldNamed(NameToId(name), fieldOffset, fieldPrediction,
fieldIndex, fieldMutable)) {
trackOptimizationOutcome(TrackedOutcome::StructNoField);
return false;
}
return true;
}
MDefinition* IonBuilder::typeObjectForElementFromArrayStructType(
MDefinition* typeObj) {
MInstruction* elemType =
MLoadFixedSlot::New(alloc(), typeObj, JS_DESCR_SLOT_ARRAY_ELEM_TYPE);
current->add(elemType);
MInstruction* unboxElemType =
MUnbox::New(alloc(), elemType, MIRType::Object, MUnbox::Infallible);
current->add(unboxElemType);
return unboxElemType;
}
MDefinition* IonBuilder::typeObjectForFieldFromStructType(MDefinition* typeObj,
size_t fieldIndex) {
MInstruction* fieldTypes =
MLoadFixedSlot::New(alloc(), typeObj, JS_DESCR_SLOT_STRUCT_FIELD_TYPES);
current->add(fieldTypes);
MInstruction* unboxFieldTypes =
MUnbox::New(alloc(), fieldTypes, MIRType::Object, MUnbox::Infallible);
current->add(unboxFieldTypes);
MInstruction* fieldTypesElements = MElements::New(alloc(), unboxFieldTypes);
current->add(fieldTypesElements);
MConstant* fieldIndexDef = constantInt(fieldIndex);
MInstruction* fieldType = MLoadElement::New(alloc(), fieldTypesElements,
fieldIndexDef, false, false);
current->add(fieldType);
MInstruction* unboxFieldType =
MUnbox::New(alloc(), fieldType, MIRType::Object, MUnbox::Infallible);
current->add(unboxFieldType);
return unboxFieldType;
}
AbortReasonOr<Ok> IonBuilder::setPropTryScalarTypedObjectValue(
bool* emitted, MDefinition* typedObj, const LinearSum& byteOffset,
ScalarTypeDescr::Type type, MDefinition* value) {
MOZ_ASSERT(!*emitted);
MDefinition* elements;
MDefinition* scaledOffset;
int32_t adjustment;
uint32_t alignment = ScalarTypeDescr::alignment(type);
MOZ_TRY(loadTypedObjectElements(typedObj, byteOffset, alignment, &elements,
&scaledOffset, &adjustment));
MDefinition* toWrite = value;
if (type == Scalar::Uint8Clamped) {
toWrite = MClampToUint8::New(alloc(), value);
current->add(toWrite->toInstruction());
}
MStoreUnboxedScalar* store =
MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, toWrite, type,
MStoreUnboxedScalar::TruncateInput,
DoesNotRequireMemoryBarrier, adjustment);
current->add(store);
current->push(value);
trackOptimizationSuccess();
*emitted = true;
return resumeAfter(store);
}
AbortReasonOr<Ok> IonBuilder::setPropTryReferenceTypedObjectValue(
bool* emitted, MDefinition* typedObj, const LinearSum& byteOffset,
ReferenceType type, MDefinition* value, PropertyName* name) {
MOZ_ASSERT(!*emitted);
if (type != ReferenceType::TYPE_STRING) {
MOZ_ASSERT(type == ReferenceType::TYPE_ANY ||
type == ReferenceType::TYPE_OBJECT);
MIRType implicitType =
(type == ReferenceType::TYPE_ANY) ? MIRType::Undefined : MIRType::Null;
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
&typedObj, name, &value,
true, implicitType)) {
trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
return Ok();
}
}
MDefinition* elements;
MDefinition* scaledOffset;
int32_t adjustment;
uint32_t alignment = ReferenceTypeDescr::alignment(type);
MOZ_TRY(loadTypedObjectElements(typedObj, byteOffset, alignment, &elements,
&scaledOffset, &adjustment));
MInstruction* store = nullptr; switch (type) {
case ReferenceType::TYPE_ANY:
if (needsPostBarrier(value)) {
current->add(MPostWriteBarrier::New(alloc(), typedObj, value));
}
store = MStoreElement::New(alloc(), elements, scaledOffset, value, false,
adjustment);
store->toStoreElement()->setNeedsBarrier();
break;
case ReferenceType::TYPE_OBJECT:
store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset,
value, typedObj, adjustment);
break;
case ReferenceType::TYPE_STRING:
store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value,
typedObj, adjustment);
break;
case ReferenceType::TYPE_WASM_ANYREF:
MOZ_CRASH();
}
current->add(store);
current->push(value);
trackOptimizationSuccess();
*emitted = true;
return resumeAfter(store);
}
JSObject* IonBuilder::checkNurseryObject(JSObject* obj) {
if (obj && IsInsideNursery(obj)) {
realm->zone()->setMinorGCShouldCancelIonCompilations();
IonBuilder* builder = this;
while (builder) {
builder->setNotSafeForMinorGC();
builder = builder->callerBuilder_;
}
}
return obj;
}
MConstant* IonBuilder::constant(const Value& v) {
MOZ_ASSERT(!v.isString() || v.toString()->isAtom(),
"Handle non-atomized strings outside IonBuilder.");
if (v.isObject()) {
checkNurseryObject(&v.toObject());
}
MConstant* c = MConstant::New(alloc(), v, constraints());
current->add(c);
return c;
}
MConstant* IonBuilder::constantInt(int32_t i) {
return constant(Int32Value(i));
}
MInstruction* IonBuilder::initializedLength(MDefinition* elements) {
MInstruction* res = MInitializedLength::New(alloc(), elements);
current->add(res);
return res;
}
MInstruction* IonBuilder::setInitializedLength(MDefinition* obj, size_t count) {
MOZ_ASSERT(count);
MInstruction* elements = MElements::New(alloc(), obj);
current->add(elements);
MInstruction* res = MSetInitializedLength::New(
alloc(), elements, constant(Int32Value(count - 1)));
current->add(res);
return res;
}
MDefinition* IonBuilder::getCallee() {
if (inliningDepth_ == 0) {
MInstruction* callee = MCallee::New(alloc());
current->add(callee);
return callee;
}
return inlineCallInfo_->fun();
}
AbortReasonOr<MDefinition*> IonBuilder::addLexicalCheck(MDefinition* input) {
MOZ_ASSERT(JSOp(*pc) == JSOP_CHECKLEXICAL ||
JSOp(*pc) == JSOP_CHECKALIASEDLEXICAL ||
JSOp(*pc) == JSOP_GETIMPORT);
MInstruction* lexicalCheck;
if (input->type() == MIRType::MagicUninitializedLexical) {
input->setImplicitlyUsedUnchecked();
lexicalCheck =
MThrowRuntimeLexicalError::New(alloc(), JSMSG_UNINITIALIZED_LEXICAL);
current->add(lexicalCheck);
MOZ_TRY(resumeAfter(lexicalCheck));
return constant(UndefinedValue());
}
if (input->type() == MIRType::Value) {
lexicalCheck = MLexicalCheck::New(alloc(), input);
current->add(lexicalCheck);
if (failedLexicalCheck_) {
lexicalCheck->setNotMovableUnchecked();
}
return lexicalCheck;
}
return input;
}
MDefinition* IonBuilder::convertToBoolean(MDefinition* input) {
MNot* resultInverted = MNot::New(alloc(), input, constraints());
current->add(resultInverted);
MNot* result = MNot::New(alloc(), resultInverted, constraints());
current->add(result);
return result;
}
void IonBuilder::trace(JSTracer* trc) {
if (!realm->runtime()->runtimeMatches(trc->runtime())) {
return;
}
MOZ_ASSERT(rootList_);
rootList_->trace(trc);
}
size_t IonBuilder::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t result = alloc_->lifoAlloc()->sizeOfIncludingThis(mallocSizeOf);
if (backgroundCodegen_) {
result += mallocSizeOf(backgroundCodegen_);
}
return result;
}