diff options
Diffstat (limited to 'js/src/jit/IonBuilder.cpp')
-rw-r--r-- | js/src/jit/IonBuilder.cpp | 14696 |
1 files changed, 14696 insertions, 0 deletions
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp new file mode 100644 index 000000000..1488d7d34 --- /dev/null +++ b/js/src/jit/IonBuilder.cpp @@ -0,0 +1,14696 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/IonBuilder.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/SizePrintfMacros.h" + +#include "builtin/Eval.h" +#include "builtin/TypedObject.h" +#include "frontend/SourceNotes.h" +#include "jit/BaselineFrame.h" +#include "jit/BaselineInspector.h" +#include "jit/Ion.h" +#include "jit/IonOptimizationLevels.h" +#include "jit/JitSpewer.h" +#include "jit/Lowering.h" +#include "jit/MIRGraph.h" +#include "vm/ArgumentsObject.h" +#include "vm/Opcodes.h" +#include "vm/RegExpStatics.h" +#include "vm/TraceLogging.h" + +#include "jsopcodeinlines.h" +#include "jsscriptinlines.h" + +#include "jit/CompileInfo-inl.h" +#include "jit/shared/Lowering-shared-inl.h" +#include "vm/EnvironmentObject-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 JS::TrackedStrategy; +using JS::TrackedOutcome; +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, CompileInfo* info) +{ + MOZ_ASSERT(frame); + + BaselineFrameInspector* inspector = temp->lifoAlloc()->new_<BaselineFrameInspector>(temp); + if (!inspector) + return nullptr; + + // Note: copying the actual values into a temporary structure for use + // during compilation could capture nursery pointers, so the values' types + // are recorded instead. + + if (frame->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, CompileCompartment* comp, + const JitCompileOptions& options, TempAllocator* temp, + MIRGraph* graph, CompilerConstraintList* constraints, + BaselineInspector* inspector, CompileInfo* info, + const OptimizationInfo* optimizationInfo, + BaselineFrameInspector* baselineFrame, size_t inliningDepth, + uint32_t loopDepth) + : MIRGenerator(comp, options, temp, graph, info, optimizationInfo), + backgroundCodegen_(nullptr), + actionableAbortScript_(nullptr), + actionableAbortPc_(nullptr), + actionableAbortMessage_(nullptr), + rootList_(nullptr), + analysisContext(analysisContext), + baselineFrame_(baselineFrame), + constraints_(constraints), + analysis_(*temp, info->script()), + thisTypes(nullptr), + argTypes(nullptr), + typeArray(nullptr), + typeArrayHint(0), + bytecodeTypeMap(nullptr), + loopDepth_(loopDepth), + trackedOptimizationSites_(*temp), + lexicalCheck_(nullptr), + callerResumePoint_(nullptr), + callerBuilder_(nullptr), + cfgStack_(*temp), + loops_(*temp), + switches_(*temp), + labels_(*temp), + iterators_(*temp), + loopHeaders_(*temp), + inspector(inspector), + inliningDepth_(inliningDepth), + inlinedBytecodeLength_(0), + numLoopRestarts_(0), + failedBoundsCheck_(info->script()->failedBoundsCheck()), + failedShapeGuard_(info->script()->failedShapeGuard()), + failedLexicalCheck_(info->script()->failedLexicalCheck()), + nonStringIteration_(false), + lazyArguments_(nullptr), + inlineCallInfo_(nullptr), + maybeFallbackFunctionGetter_(nullptr) +{ + script_ = info->script(); + scriptHasIonScript_ = script_->hasIonScript(); + pc = info->startPC(); + abortReason_ = AbortReason_Disable; + + MOZ_ASSERT(script()->hasBaselineScript() == (info->analysisMode() != Analysis_ArgumentsUsage)); + MOZ_ASSERT(!!analysisContext == (info->analysisMode() == Analysis_DefiniteProperties)); + MOZ_ASSERT(script_->nTypeSets() < UINT16_MAX); + + if (!info->isAnalysis()) + script()->baselineScript()->setIonCompiledOrInlined(); +} + +void +IonBuilder::clearForBackEnd() +{ + MOZ_ASSERT(!analysisContext); + baselineFrame_ = nullptr; + + // The caches below allocate data from the malloc heap. Release this before + // later phases of compilation to avoid leaks, as the top level IonBuilder + // is not explicitly destroyed. Note that builders for inner scripts are + // constructed on the stack and will release this memory on destruction. + gsn.purge(); + envCoordinateNameCache.purge(); +} + +bool +IonBuilder::abort(const char* message, ...) +{ + // Don't call PCToLineNumber in release builds. +#ifdef JS_JITSPEW + va_list ap; + va_start(ap, message); + abortFmt(message, ap); + va_end(ap); +# ifdef DEBUG + JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(), PCToLineNumber(script(), pc)); +# else + JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename()); +# endif +#endif + trackActionableAbort(message); + return false; +} + +IonBuilder* +IonBuilder::outermostBuilder() +{ + IonBuilder* builder = this; + while (builder->callerBuilder_) + builder = builder->callerBuilder_; + return builder; +} + +void +IonBuilder::trackActionableAbort(const char* message) +{ + if (!isOptimizationTrackingEnabled()) + return; + + IonBuilder* topBuilder = outermostBuilder(); + if (topBuilder->hadActionableAbort()) + return; + + topBuilder->actionableAbortScript_ = script(); + topBuilder->actionableAbortPc_ = pc; + topBuilder->actionableAbortMessage_ = message; +} + +void +IonBuilder::spew(const char* message) +{ + // Don't call PCToLineNumber in release builds. +#ifdef DEBUG + JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc)); +#endif +} + +static inline int32_t +GetJumpOffset(jsbytecode* pc) +{ + MOZ_ASSERT(CodeSpec[JSOp(*pc)].type() == JOF_JUMP); + return GET_JUMP_OFFSET(pc); +} + +IonBuilder::CFGState +IonBuilder::CFGState::If(jsbytecode* join, MTest* test) +{ + CFGState state; + state.state = IF_TRUE; + state.stopAt = join; + state.branch.ifFalse = test->ifFalse(); + state.branch.test = test; + return state; +} + +IonBuilder::CFGState +IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test) +{ + MBasicBlock* ifFalse = test->ifFalse(); + + CFGState state; + // If the end of the false path is the same as the start of the + // false path, then the "else" block is empty and we can devolve + // this to the IF_TRUE case. We handle this here because there is + // still an extra GOTO on the true path and we want stopAt to point + // there, whereas the IF_TRUE case does not have the GOTO. + state.state = (falseEnd == ifFalse->pc()) + ? IF_TRUE_EMPTY_ELSE + : IF_ELSE_TRUE; + state.stopAt = trueEnd; + state.branch.falseEnd = falseEnd; + state.branch.ifFalse = ifFalse; + state.branch.test = test; + return state; +} + +IonBuilder::CFGState +IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs) +{ + CFGState state; + state.state = AND_OR; + state.stopAt = join; + state.branch.ifFalse = lhs; + state.branch.test = nullptr; + return state; +} + +IonBuilder::CFGState +IonBuilder::CFGState::TableSwitch(jsbytecode* exitpc, MTableSwitch* ins) +{ + CFGState state; + state.state = TABLE_SWITCH; + state.stopAt = exitpc; + state.tableswitch.exitpc = exitpc; + state.tableswitch.breaks = nullptr; + state.tableswitch.ins = ins; + state.tableswitch.currentBlock = 0; + return state; +} + +JSFunction* +IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes) +{ + if (!calleeTypes) + return nullptr; + + JSObject* obj = calleeTypes->maybeSingleton(); + if (!obj || !obj->is<JSFunction>()) + return nullptr; + + return &obj->as<JSFunction>(); +} + +bool +IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, + ObjectVector& targets, uint32_t maxTargets) +{ + MOZ_ASSERT(targets.empty()); + + if (!calleeTypes) + return true; + + if (calleeTypes->baseFlags() != 0) + return true; + + unsigned objCount = calleeTypes->getObjectCount(); + + if (objCount == 0 || objCount > maxTargets) + return true; + + if (!targets.reserve(objCount)) + return false; + for (unsigned i = 0; i < objCount; i++) { + JSObject* obj = calleeTypes->getSingleton(i); + if (obj) { + MOZ_ASSERT(obj->isSingleton()); + } else { + ObjectGroup* group = calleeTypes->getGroup(i); + if (!group) + continue; + + obj = group->maybeInterpretedFunction(); + if (!obj) { + targets.clear(); + return true; + } + + MOZ_ASSERT(!obj->isSingleton()); + } + + // Don't optimize if the callee is not callable or constructable per + // the manner it is being invoked, so that CallKnown does not have to + // handle these cases (they will always throw). + if (constructing ? !obj->isConstructor() : !obj->isCallable()) { + targets.clear(); + return true; + } + + targets.infallibleAppend(obj); + } + + return true; +} + +IonBuilder::InliningDecision +IonBuilder::DontInline(JSScript* targetScript, const char* reason) +{ + if (targetScript) { + JitSpew(JitSpew_Inlining, "Cannot inline %s:%" PRIuSIZE ": %s", + targetScript->filename(), targetScript->lineno(), reason); + } else { + JitSpew(JitSpew_Inlining, "Cannot inline: %s", reason); + } + + return InliningDecision_DontInline; +} + +/* + * |hasCommonInliningPath| determines whether the current inlining path has been + * seen before based on the sequence of scripts in the chain of |IonBuilder|s. + * + * An inlining path for a function |f| is the sequence of functions whose + * inlinings precede |f| up to any previous occurrences of |f|. + * So, if we have the chain of inlinings + * + * f1 -> f2 -> f -> f3 -> f4 -> f5 -> f + * -------- -------------- + * + * the inlining paths for |f| are [f2, f1] and [f5, f4, f3]. + * When attempting to inline |f|, we find all existing inlining paths for |f| + * and check whether they share a common prefix with the path created were |f| + * inlined. + * + * For example, given mutually recursive functions |f| and |g|, a possible + * inlining is + * + * +---- Inlining stopped here... + * | + * v + * a -> f -> g -> f \ -> g -> f -> g -> ... + * + * where the vertical bar denotes the termination of inlining. + * Inlining is terminated because we have already observed the inlining path + * [f] when inlining function |g|. Note that this will inline recursive + * functions such as |fib| only one level, as |fib| has a zero length inlining + * path which trivially prefixes all inlining paths. + * + */ +bool +IonBuilder::hasCommonInliningPath(const JSScript* scriptToInline) +{ + // Find all previous inlinings of the |scriptToInline| and check for common + // inlining paths with the top of the inlining stack. + for (IonBuilder* it = this->callerBuilder_; it; it = it->callerBuilder_) { + if (it->script() != scriptToInline) + continue; + + // This only needs to check the top of each stack for a match, + // as a match of length one ensures a common prefix. + IonBuilder* path = it->callerBuilder_; + if (!path || this->script() == path->script()) + return true; + } + + return false; +} + +IonBuilder::InliningDecision +IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo) +{ + if (!optimizationInfo().inlineInterpreted()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningDecision_DontInline; + } + + if (TraceLogTextIdEnabled(TraceLogger_InlinedScripts)) { + return DontInline(nullptr, "Tracelogging of inlined scripts is enabled" + "but Tracelogger cannot do that yet."); + } + + if (!target->isInterpreted()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNotInterpreted); + return DontInline(nullptr, "Non-interpreted target"); + } + + if (info().analysisMode() != Analysis_DefiniteProperties) { + // If |this| or an argument has an empty resultTypeSet, don't bother + // inlining, as the call is currently unreachable due to incomplete type + // information. This does not apply to the definite properties analysis, + // in that case we want to inline anyway. + + if (callInfo.thisArg()->emptyResultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); + return DontInline(nullptr, "Empty TypeSet for |this|"); + } + + for (size_t i = 0; i < callInfo.argc(); i++) { + if (callInfo.getArg(i)->emptyResultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); + return DontInline(nullptr, "Empty TypeSet for argument"); + } + } + } + + // Allow constructing lazy scripts when performing the definite properties + // analysis, as baseline has not been used to warm the caller up yet. + if (target->isInterpreted() && info().analysisMode() == Analysis_DefiniteProperties) { + RootedScript script(analysisContext, target->getOrCreateScript(analysisContext)); + if (!script) + return InliningDecision_Error; + + if (!script->hasBaselineScript() && script->canBaselineCompile()) { + MethodStatus status = BaselineCompile(analysisContext, script); + if (status == Method_Error) + return InliningDecision_Error; + if (status != Method_Compiled) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline); + return InliningDecision_DontInline; + } + } + } + + if (!target->hasScript()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineLazy); + return DontInline(nullptr, "Lazy script"); + } + + JSScript* inlineScript = target->nonLazyScript(); + if (callInfo.constructing() && !target->isConstructor()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNotConstructor); + return DontInline(inlineScript, "Callee is not a constructor"); + } + + if (!callInfo.constructing() && target->isClassConstructor()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor); + return DontInline(inlineScript, "Not constructing class constructor"); + } + + AnalysisMode analysisMode = info().analysisMode(); + if (!CanIonCompile(inlineScript, analysisMode)) { + trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon); + return DontInline(inlineScript, "Disabled Ion compilation"); + } + + // Don't inline functions which don't have baseline scripts. + if (!inlineScript->hasBaselineScript()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline); + return DontInline(inlineScript, "No baseline jitcode"); + } + + if (TooManyFormalArguments(target->nargs())) { + trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs); + return DontInline(inlineScript, "Too many args"); + } + + // We check the number of actual arguments against the maximum number of + // formal arguments as we do not want to encode all actual arguments in the + // callerResumePoint. + if (TooManyFormalArguments(callInfo.argc())) { + trackOptimizationOutcome(TrackedOutcome::CantInlineTooManyArgs); + return DontInline(inlineScript, "Too many actual args"); + } + + if (hasCommonInliningPath(inlineScript)) { + trackOptimizationOutcome(TrackedOutcome::HasCommonInliningPath); + return DontInline(inlineScript, "Common inlining path"); + } + + if (inlineScript->uninlineable()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return DontInline(inlineScript, "Uninlineable script"); + } + + if (inlineScript->needsArgsObj()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNeedsArgsObj); + return DontInline(inlineScript, "Script that needs an arguments object"); + } + + if (inlineScript->isDebuggee()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineDebuggee); + return DontInline(inlineScript, "Script is debuggee"); + } + + TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); + if (targetKey->unknownProperties()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps); + return DontInline(inlineScript, "Target type has unknown properties"); + } + + return InliningDecision_Inline; +} + +void +IonBuilder::popCfgStack() +{ + if (cfgStack_.back().isLoop()) + loops_.popBack(); + if (cfgStack_.back().state == CFGState::LABEL) + labels_.popBack(); + cfgStack_.popBack(); +} + +bool +IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecode* end) +{ + // The phi inputs at the loop head only reflect types for variables that + // were present at the start of the loop. If the variable changes to a new + // type within the loop body, and that type is carried around to the loop + // head, then we need to know about the new type up front. + // + // Since SSA information hasn't been constructed for the loop body yet, we + // need a separate analysis to pick out the types that might flow around + // the loop header. This is a best-effort analysis that may either over- + // or under-approximate the set of such types. + // + // Over-approximating the types may lead to inefficient generated code, and + // under-approximating the types will cause the loop body to be analyzed + // multiple times as the correct types are deduced (see finishLoop). + + // If we restarted processing of an outer loop then get loop header types + // directly from the last time we have previously processed this loop. This + // both avoids repeated work from the bytecode traverse below, and will + // also pick up types discovered while previously building the loop body. + for (size_t i = 0; i < loopHeaders_.length(); i++) { + if (loopHeaders_[i].pc == start) { + MBasicBlock* oldEntry = loopHeaders_[i].header; + + // If this block has been discarded, its resume points will have + // already discarded their operands. + if (!oldEntry->isDead()) { + MResumePoint* oldEntryRp = oldEntry->entryResumePoint(); + size_t stackDepth = oldEntryRp->stackDepth(); + for (size_t slot = 0; slot < stackDepth; slot++) { + MDefinition* oldDef = oldEntryRp->getOperand(slot); + if (!oldDef->isPhi()) { + MOZ_ASSERT(oldDef->block()->id() < oldEntry->id()); + MOZ_ASSERT(oldDef == entry->getSlot(slot)); + continue; + } + MPhi* oldPhi = oldDef->toPhi(); + MPhi* newPhi = entry->getSlot(slot)->toPhi(); + if (!newPhi->addBackedgeType(alloc(), oldPhi->type(), oldPhi->resultTypeSet())) + return false; + } + } + + // Update the most recent header for this loop encountered, in case + // new types flow to the phis and the loop is processed at least + // three times. + loopHeaders_[i].header = entry; + return true; + } + } + if (!loopHeaders_.append(LoopHeader(start, entry))) + return false; + + jsbytecode* last = nullptr; + jsbytecode* earlier = nullptr; + for (jsbytecode* pc = start; pc != end; earlier = last, last = pc, pc += GetBytecodeLength(pc)) { + uint32_t slot; + if (*pc == JSOP_SETLOCAL) + slot = info().localSlot(GET_LOCALNO(pc)); + else if (*pc == JSOP_SETARG) + slot = info().argSlotUnchecked(GET_ARGNO(pc)); + else + continue; + if (slot >= info().firstStackSlot()) + continue; + if (!analysis().maybeInfo(pc)) + continue; + if (!last) + continue; + + MPhi* phi = entry->getSlot(slot)->toPhi(); + + if (*last == JSOP_POS) + last = earlier; + + if (CodeSpec[*last].format & JOF_TYPESET) { + TemporaryTypeSet* typeSet = bytecodeTypes(last); + if (!typeSet->empty()) { + MIRType type = typeSet->getKnownMIRType(); + if (!phi->addBackedgeType(alloc(), type, typeSet)) + return false; + } + } else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) { + uint32_t slot = (*last == JSOP_GETLOCAL) + ? info().localSlot(GET_LOCALNO(last)) + : info().argSlotUnchecked(GET_ARGNO(last)); + if (slot < info().firstStackSlot()) { + MPhi* otherPhi = entry->getSlot(slot)->toPhi(); + if (otherPhi->hasBackedgeType()) { + if (!phi->addBackedgeType(alloc(), otherPhi->type(), otherPhi->resultTypeSet())) + return false; + } + } + } else { + MIRType type = MIRType::None; + switch (*last) { + case JSOP_VOID: + case JSOP_UNDEFINED: + type = MIRType::Undefined; + break; + case JSOP_GIMPLICITTHIS: + if (!script()->hasNonSyntacticScope()) + type = MIRType::Undefined; + break; + case JSOP_NULL: + type = MIRType::Null; + break; + case JSOP_ZERO: + case JSOP_ONE: + case JSOP_INT8: + case JSOP_INT32: + case JSOP_UINT16: + case JSOP_UINT24: + case JSOP_BITAND: + case JSOP_BITOR: + case JSOP_BITXOR: + case JSOP_BITNOT: + case JSOP_RSH: + case JSOP_LSH: + case JSOP_URSH: + type = MIRType::Int32; + break; + case JSOP_FALSE: + case JSOP_TRUE: + case JSOP_EQ: + case JSOP_NE: + case JSOP_LT: + case JSOP_LE: + case JSOP_GT: + case JSOP_GE: + case JSOP_NOT: + case JSOP_STRICTEQ: + case JSOP_STRICTNE: + case JSOP_IN: + case JSOP_INSTANCEOF: + type = MIRType::Boolean; + break; + case JSOP_DOUBLE: + type = MIRType::Double; + break; + case JSOP_STRING: + case JSOP_TOSTRING: + case JSOP_TYPEOF: + case JSOP_TYPEOFEXPR: + type = MIRType::String; + break; + case JSOP_SYMBOL: + type = MIRType::Symbol; + break; + case JSOP_ADD: + case JSOP_SUB: + case JSOP_MUL: + case JSOP_DIV: + case JSOP_MOD: + case JSOP_NEG: + type = inspector->expectedResultType(last); + break; + default: + break; + } + if (type != MIRType::None) { + if (!phi->addBackedgeType(alloc(), type, nullptr)) + return false; + } + } + } + return true; +} + +bool +IonBuilder::pushLoop(CFGState::State initial, jsbytecode* stopAt, MBasicBlock* entry, bool osr, + jsbytecode* loopHead, jsbytecode* initialPc, + jsbytecode* bodyStart, jsbytecode* bodyEnd, + jsbytecode* exitpc, jsbytecode* continuepc) +{ + ControlFlowInfo loop(cfgStack_.length(), continuepc); + if (!loops_.append(loop)) + return false; + + CFGState state; + state.state = initial; + state.stopAt = stopAt; + state.loop.bodyStart = bodyStart; + state.loop.bodyEnd = bodyEnd; + state.loop.exitpc = exitpc; + state.loop.continuepc = continuepc; + state.loop.entry = entry; + state.loop.osr = osr; + state.loop.successor = nullptr; + state.loop.breaks = nullptr; + state.loop.continues = nullptr; + state.loop.initialState = initial; + state.loop.initialPc = initialPc; + state.loop.initialStopAt = stopAt; + state.loop.loopHead = loopHead; + return cfgStack_.append(state); +} + +bool +IonBuilder::init() +{ + { + LifoAlloc::AutoFallibleScope fallibleAllocator(alloc().lifoAlloc()); + if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray)) + return false; + } + + if (!alloc().ensureBallast()) + return false; + + if (inlineCallInfo_) { + // If we're inlining, the actual this/argument types are not necessarily + // a subset of the script's observed types. |argTypes| is never accessed + // for inlined scripts, so we just null it. + thisTypes = inlineCallInfo_->thisArg()->resultTypeSet(); + argTypes = nullptr; + } + + if (!analysis().init(alloc(), gsn)) + return false; + + // The baseline script normally has the bytecode type map, but compute + // it ourselves if we do not have a baseline script. + if (script()->hasBaselineScript()) { + bytecodeTypeMap = script()->baselineScript()->bytecodeTypeMap(); + } else { + bytecodeTypeMap = alloc_->lifoAlloc()->newArrayUninitialized<uint32_t>(script()->nTypeSets()); + if (!bytecodeTypeMap) + return false; + FillBytecodeTypeMap(script(), bytecodeTypeMap); + } + + return true; +} + +bool +IonBuilder::build() +{ + if (!init()) + return false; + + if (script()->hasBaselineScript()) + script()->baselineScript()->resetMaxInliningDepth(); + + if (!setCurrentAndSpecializePhis(newBlock(pc))) + return false; + if (!current) + return false; + +#ifdef JS_JITSPEW + if (info().isAnalysis()) { + JitSpew(JitSpew_IonScripts, "Analyzing script %s:%" PRIuSIZE " (%p) %s", + script()->filename(), script()->lineno(), (void*)script(), + AnalysisModeString(info().analysisMode())); + } else { + JitSpew(JitSpew_IonScripts, "%sompiling script %s:%" PRIuSIZE " (%p) (warmup-counter=%" PRIu32 ", level=%s)", + (script()->hasIonScript() ? "Rec" : "C"), + script()->filename(), script()->lineno(), (void*)script(), + script()->getWarmUpCount(), OptimizationLevelString(optimizationInfo().level())); + } +#endif + + if (!initParameters()) + return false; + initLocals(); + + // Initialize something for the env chain. We can bail out before the + // start instruction, but the snapshot is encoded *at* the start + // instruction, which means generating any code that could load into + // registers is illegal. + MInstruction* env = MConstant::New(alloc(), UndefinedValue()); + current->add(env); + current->initSlot(info().environmentChainSlot(), env); + + // Initialize the return value. + MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue()); + current->add(returnValue); + current->initSlot(info().returnValueSlot(), returnValue); + + // Initialize the arguments object slot to undefined if necessary. + if (info().hasArguments()) { + MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue()); + current->add(argsObj); + current->initSlot(info().argsObjSlot(), argsObj); + } + + // Emit the start instruction, so we can begin real instructions. + current->add(MStart::New(alloc())); + + // Guard against over-recursion. Do this before we start unboxing, since + // this will create an OSI point that will read the incoming argument + // values, which is nice to do before their last real use, to minimize + // register/stack pressure. + MCheckOverRecursed* check = MCheckOverRecursed::New(alloc()); + current->add(check); + MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint()); + if (!entryRpCopy) + return false; + check->setResumePoint(entryRpCopy); + + // Parameters have been checked to correspond to the typeset, now we unbox + // what we can in an infallible manner. + if (!rewriteParameters()) + return false; + + // Check for redeclaration errors for global scripts. + 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 false; + redeclCheck->setResumePoint(entryRpCopy); + } + + // It's safe to start emitting actual IR, so now build the env chain. + if (!initEnvironmentChain()) + return false; + + if (info().needsArgsObj() && !initArgumentsObject()) + return false; + + // The type analysis phase attempts to insert unbox operations near + // definitions of values. It also attempts to replace uses in resume points + // with the narrower, unboxed variants. However, we must prevent this + // replacement from happening on values in the entry snapshot. Otherwise we + // could get this: + // + // v0 = MParameter(0) + // v1 = MParameter(1) + // -- ResumePoint(v2, v3) + // v2 = Unbox(v0, INT32) + // v3 = Unbox(v1, INT32) + // + // So we attach the initial resume point to each parameter, which the type + // analysis explicitly checks (this is the same mechanism used for + // effectful operations). + for (uint32_t i = 0; i < info().endArgSlot(); i++) { + MInstruction* ins = current->getEntrySlot(i)->toInstruction(); + if (ins->type() != MIRType::Value) + continue; + + MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint()); + if (!entryRpCopy) + return false; + ins->setResumePoint(entryRpCopy); + } + + // lazyArguments should never be accessed in |argsObjAliasesFormals| scripts. + if (info().hasArguments() && !info().argsObjAliasesFormals()) { + lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); + current->add(lazyArguments_); + } + + insertRecompileCheck(); + + if (!traverseBytecode()) + return false; + + // Discard unreferenced & pre-allocated resume points. + replaceMaybeFallbackFunctionGetter(nullptr); + + if (script_->hasBaselineScript() && + inlinedBytecodeLength_ > script_->baselineScript()->inlinedBytecodeLength()) + { + script_->baselineScript()->setInlinedBytecodeLength(inlinedBytecodeLength_); + } + + if (!maybeAddOsrTypeBarriers()) + return false; + + if (!processIterators()) + return false; + + if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) { + abortReason_ = AbortReason_PreliminaryObjects; + return false; + } + + if (shouldForceAbort()) { + abortReason_ = AbortReason_Disable; + return false; + } + + MOZ_ASSERT(loopDepth_ == 0); + abortReason_ = AbortReason_NoAbort; + return true; +} + +bool +IonBuilder::processIterators() +{ + // Find phis that must directly hold an iterator live. + Vector<MPhi*, 0, SystemAllocPolicy> worklist; + for (size_t i = 0; i < iterators_.length(); i++) { + MInstruction* ins = iterators_[i]; + for (MUseDefIterator iter(ins); iter; iter++) { + if (iter.def()->isPhi()) { + if (!worklist.append(iter.def()->toPhi())) + return false; + } + } + } + + // Propagate the iterator and live status of phis to all other connected + // phis. + while (!worklist.empty()) { + MPhi* phi = worklist.popCopy(); + phi->setIterator(); + phi->setImplicitlyUsedUnchecked(); + + for (MUseDefIterator iter(phi); iter; iter++) { + if (iter.def()->isPhi()) { + MPhi* other = iter.def()->toPhi(); + if (!other->isIterator() && !worklist.append(other)) + return false; + } + } + } + + return true; +} + +bool +IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint, + CallInfo& callInfo) +{ + inlineCallInfo_ = &callInfo; + + if (!init()) + return false; + + JitSpew(JitSpew_IonScripts, "Inlining script %s:%" PRIuSIZE " (%p)", + script()->filename(), script()->lineno(), (void*)script()); + + callerBuilder_ = callerBuilder; + callerResumePoint_ = callerResumePoint; + + if (callerBuilder->failedBoundsCheck_) + failedBoundsCheck_ = true; + + if (callerBuilder->failedShapeGuard_) + failedShapeGuard_ = true; + + if (callerBuilder->failedLexicalCheck_) + failedLexicalCheck_ = true; + + safeForMinorGC_ = callerBuilder->safeForMinorGC_; + + // Generate single entrance block. + if (!setCurrentAndSpecializePhis(newBlock(pc))) + return false; + if (!current) + return false; + + current->setCallerResumePoint(callerResumePoint); + + // Connect the entrance block to the last block in the caller's graph. + MBasicBlock* predecessor = callerBuilder->current; + MOZ_ASSERT(predecessor == callerResumePoint->block()); + + predecessor->end(MGoto::New(alloc(), current)); + if (!current->addPredecessorWithoutPhis(predecessor)) + return false; + + // Initialize env chain slot to Undefined. It's set later by + // |initEnvironmentChain|. + MInstruction* env = MConstant::New(alloc(), UndefinedValue()); + current->add(env); + current->initSlot(info().environmentChainSlot(), env); + + // Initialize |return value| slot. + MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue()); + current->add(returnValue); + current->initSlot(info().returnValueSlot(), returnValue); + + // Initialize |arguments| slot. + if (info().hasArguments()) { + MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue()); + current->add(argsObj); + current->initSlot(info().argsObjSlot(), argsObj); + } + + // Initialize |this| slot. + current->initSlot(info().thisSlot(), callInfo.thisArg()); + + JitSpew(JitSpew_Inlining, "Initializing %u arg slots", info().nargs()); + + // NB: Ion does not inline functions which |needsArgsObj|. So using argSlot() + // instead of argSlotUnchecked() below is OK + MOZ_ASSERT(!info().needsArgsObj()); + + // Initialize actually set arguments. + uint32_t existing_args = Min<uint32_t>(callInfo.argc(), info().nargs()); + for (size_t i = 0; i < existing_args; ++i) { + MDefinition* arg = callInfo.getArg(i); + current->initSlot(info().argSlot(i), arg); + } + + // Pass Undefined for missing arguments + for (size_t i = callInfo.argc(); i < info().nargs(); ++i) { + MConstant* arg = MConstant::New(alloc(), UndefinedValue()); + current->add(arg); + current->initSlot(info().argSlot(i), arg); + } + + JitSpew(JitSpew_Inlining, "Initializing %u locals", info().nlocals()); + + initLocals(); + + JitSpew(JitSpew_Inlining, "Inline entry block MResumePoint %p, %u stack slots", + (void*) current->entryResumePoint(), current->entryResumePoint()->stackDepth()); + + // +2 for the env chain and |this|, maybe another +1 for arguments object slot. + MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots()); + + if (script_->argumentsHasVarBinding()) { + lazyArguments_ = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS)); + current->add(lazyArguments_); + } + + insertRecompileCheck(); + + // Initialize the env chain now that all resume points operands are + // initialized. + if (!initEnvironmentChain(callInfo.fun())) + return false; + + if (!traverseBytecode()) + return false; + + // Discard unreferenced & pre-allocated resume points. + replaceMaybeFallbackFunctionGetter(nullptr); + + MOZ_ASSERT(iterators_.empty(), "Iterators should be added to outer builder"); + + if (!info().isAnalysis() && !abortedPreliminaryGroups().empty()) { + abortReason_ = AbortReason_PreliminaryObjects; + return false; + } + + if (shouldForceAbort()) { + abortReason_ = AbortReason_Disable; + return false; + } + + return true; +} + +void +IonBuilder::rewriteParameter(uint32_t slotIdx, MDefinition* param, int32_t argIndex) +{ + MOZ_ASSERT(param->isParameter() || param->isGetArgumentsObjectArg()); + + TemporaryTypeSet* types = param->resultTypeSet(); + MDefinition* actual = ensureDefiniteType(param, types->getKnownMIRType()); + if (actual == param) + return; + + // Careful! We leave the original MParameter in the entry resume point. The + // arguments still need to be checked unless proven otherwise at the call + // site, and these checks can bailout. We can end up: + // v0 = Parameter(0) + // v1 = Unbox(v0, INT32) + // -- ResumePoint(v0) + // + // As usual, it would be invalid for v1 to be captured in the initial + // resume point, rather than v0. + current->rewriteSlot(slotIdx, actual); +} + +// Apply Type Inference information to parameters early on, unboxing them if +// they have a definitive type. The actual guards will be emitted by the code +// generator, explicitly, as part of the function prologue. +bool +IonBuilder::rewriteParameters() +{ + MOZ_ASSERT(info().environmentChainSlot() == 0); + + if (!info().funMaybeLazy()) + return true; + + for (uint32_t i = info().startArgSlot(); i < info().endArgSlot(); i++) { + if (!alloc().ensureBallast()) + return false; + MDefinition* param = current->getSlot(i); + rewriteParameter(i, param, param->toParameter()->index()); + } + + return true; +} + +bool +IonBuilder::initParameters() +{ + if (!info().funMaybeLazy()) + return true; + + // If we are doing OSR on a frame which initially executed in the + // interpreter and didn't accumulate type information, try to use that OSR + // frame to determine possible initial types for 'this' and parameters. + + if (thisTypes->empty() && baselineFrame_) { + TypeSet::Type type = baselineFrame_->thisType; + if (type.isSingletonUnchecked()) + checkNurseryObject(type.singleton()); + thisTypes->addType(type, alloc_->lifoAlloc()); + } + + MParameter* param = MParameter::New(alloc(), MParameter::THIS_SLOT, thisTypes); + current->add(param); + current->initSlot(info().thisSlot(), param); + + for (uint32_t i = 0; i < info().nargs(); i++) { + TemporaryTypeSet* types = &argTypes[i]; + if (types->empty() && baselineFrame_ && + !script_->baselineScript()->modifiesArguments()) + { + TypeSet::Type type = baselineFrame_->argTypes[i]; + if (type.isSingletonUnchecked()) + checkNurseryObject(type.singleton()); + types->addType(type, alloc_->lifoAlloc()); + } + + param = MParameter::New(alloc().fallible(), i, types); + if (!param) + return false; + current->add(param); + current->initSlot(info().argSlotUnchecked(i), param); + } + + return true; +} + +void +IonBuilder::initLocals() +{ + // Initialize all frame slots to undefined. Lexical bindings are temporal + // dead zoned in bytecode. + + if (info().nlocals() == 0) + return; + + MConstant* undef = MConstant::New(alloc(), UndefinedValue()); + current->add(undef); + + for (uint32_t i = 0; i < info().nlocals(); i++) + current->initSlot(info().localSlot(i), undef); +} + +bool +IonBuilder::initEnvironmentChain(MDefinition* callee) +{ + MInstruction* env = nullptr; + + // If the script doesn't use the envchain, then it's already initialized + // from earlier. However, always make a env chain when |needsArgsObj| is true + // for the script, since arguments object construction requires the env chain + // to be passed in. + if (!info().needsArgsObj() && !analysis().usesEnvironmentChain()) + return true; + + // The env chain is only tracked in scripts that have NAME opcodes which + // will try to access the env. For other scripts, the env instructions + // will be held live by resume points and code will still be generated for + // them, so just use a constant undefined value. + + if (JSFunction* fun = info().funMaybeLazy()) { + if (!callee) { + MCallee* calleeIns = MCallee::New(alloc()); + current->add(calleeIns); + callee = calleeIns; + } + env = MFunctionEnvironment::New(alloc(), callee); + current->add(env); + + // This reproduce what is done in CallObject::createForFunction. Skip + // this for the arguments analysis, as the script might not have a + // baseline script with template objects yet. + if (fun->needsSomeEnvironmentObject() && + info().analysisMode() != Analysis_ArgumentsUsage) + { + if (fun->needsNamedLambdaEnvironment()) { + env = createNamedLambdaObject(callee, env); + if (!env) + return false; + } + + // TODO: Parameter expression-induced extra var environment not + // yet handled. + if (fun->needsExtraBodyVarEnvironment()) + return abort("Extra var environment unsupported"); + + if (fun->needsCallObject()) { + env = createCallObject(callee, env); + if (!env) + return false; + } + } + } else if (ModuleObject* module = info().module()) { + // Modules use a pre-created env object. + env = constant(ObjectValue(module->initialEnvironment())); + } else { + // For global scripts without a non-syntactic global scope, the env + // chain is the global lexical env. + MOZ_ASSERT(!script()->isForEval()); + MOZ_ASSERT(!script()->hasNonSyntacticScope()); + env = constant(ObjectValue(script()->global().lexicalEnvironment())); + } + + current->setEnvironmentChain(env); + return true; +} + +bool +IonBuilder::initArgumentsObject() +{ + JitSpew(JitSpew_IonMIR, "%s:%" PRIuSIZE " - Emitting code to initialize arguments object! block=%p", + script()->filename(), script()->lineno(), current); + MOZ_ASSERT(info().needsArgsObj()); + + bool mapped = script()->hasMappedArgsObj(); + ArgumentsObject* templateObj = script()->compartment()->maybeArgumentsTemplateObject(mapped); + + MCreateArgumentsObject* argsObj = + MCreateArgumentsObject::New(alloc(), current->environmentChain(), templateObj); + current->add(argsObj); + current->setArgumentsObject(argsObj); + return true; +} + +bool +IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_, + MIRType type, TemporaryTypeSet* typeSet) +{ + MInstruction*& def = *def_; + MBasicBlock* osrBlock = def->block(); + + // Clear bogus type information added in newOsrPreheader(). + def->setResultType(MIRType::Value); + def->setResultTypeSet(nullptr); + + if (typeSet && !typeSet->unknown()) { + MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet); + osrBlock->insertBefore(osrBlock->lastIns(), barrier); + osrBlock->rewriteSlot(slot, barrier); + def = barrier; + + // If the TypeSet is more precise than |type|, adjust |type| for the + // code below. + if (type == MIRType::Value) + type = barrier->type(); + } else if (type == MIRType::Null || + type == MIRType::Undefined || + type == MIRType::MagicOptimizedArguments) + { + // No unbox instruction will be added below, so check the type by + // adding a type barrier for a singleton type set. + TypeSet::Type ntype = TypeSet::PrimitiveType(ValueTypeFromMIRType(type)); + LifoAlloc* lifoAlloc = alloc().lifoAlloc(); + typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, ntype); + if (!typeSet) + return false; + MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet); + osrBlock->insertBefore(osrBlock->lastIns(), barrier); + osrBlock->rewriteSlot(slot, barrier); + def = barrier; + } + + switch (type) { + case MIRType::Boolean: + case MIRType::Int32: + case MIRType::Double: + case MIRType::String: + case MIRType::Symbol: + case MIRType::Object: + if (type != def->type()) { + MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible); + osrBlock->insertBefore(osrBlock->lastIns(), unbox); + osrBlock->rewriteSlot(slot, unbox); + def = unbox; + } + break; + + case MIRType::Null: + { + MConstant* c = MConstant::New(alloc(), NullValue()); + osrBlock->insertBefore(osrBlock->lastIns(), c); + osrBlock->rewriteSlot(slot, c); + def = c; + break; + } + + case MIRType::Undefined: + { + MConstant* c = MConstant::New(alloc(), UndefinedValue()); + osrBlock->insertBefore(osrBlock->lastIns(), c); + osrBlock->rewriteSlot(slot, c); + def = c; + break; + } + + case MIRType::MagicOptimizedArguments: + MOZ_ASSERT(lazyArguments_); + osrBlock->rewriteSlot(slot, lazyArguments_); + def = lazyArguments_; + break; + + default: + break; + } + + MOZ_ASSERT(def == osrBlock->getSlot(slot)); + return true; +} + +bool +IonBuilder::maybeAddOsrTypeBarriers() +{ + if (!info().osrPc()) + return true; + + // The loop has successfully been processed, and the loop header phis + // have their final type. Add unboxes and type barriers in the OSR + // block to check that the values have the appropriate type, and update + // the types in the preheader. + + MBasicBlock* osrBlock = graph().osrBlock(); + if (!osrBlock) { + // Because IonBuilder does not compile catch blocks, it's possible to + // end up without an OSR block if the OSR pc is only reachable via a + // break-statement inside the catch block. For instance: + // + // for (;;) { + // try { + // throw 3; + // } catch(e) { + // break; + // } + // } + // while (..) { } // <= OSR here, only reachable via catch block. + // + // For now we just abort in this case. + MOZ_ASSERT(graph().hasTryBlock()); + return abort("OSR block only reachable through catch block"); + } + + MBasicBlock* preheader = osrBlock->getSuccessor(0); + MBasicBlock* header = preheader->getSuccessor(0); + static const size_t OSR_PHI_POSITION = 1; + MOZ_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock); + + MResumePoint* headerRp = header->entryResumePoint(); + size_t stackDepth = headerRp->stackDepth(); + MOZ_ASSERT(stackDepth == osrBlock->stackDepth()); + for (uint32_t slot = info().startArgSlot(); slot < stackDepth; slot++) { + // Aliased slots are never accessed, since they need to go through + // the callobject. The typebarriers are added there and can be + // discarded here. + if (info().isSlotAliased(slot)) + continue; + + if (!alloc().ensureBallast()) + return false; + + MInstruction* def = osrBlock->getSlot(slot)->toInstruction(); + MPhi* preheaderPhi = preheader->getSlot(slot)->toPhi(); + MPhi* headerPhi = headerRp->getOperand(slot)->toPhi(); + + MIRType type = headerPhi->type(); + TemporaryTypeSet* typeSet = headerPhi->resultTypeSet(); + + if (!addOsrValueTypeBarrier(slot, &def, type, typeSet)) + return false; + + preheaderPhi->replaceOperand(OSR_PHI_POSITION, def); + preheaderPhi->setResultType(type); + preheaderPhi->setResultTypeSet(typeSet); + } + + return true; +} + +// We try to build a control-flow graph in the order that it would be built as +// if traversing the AST. This leads to a nice ordering and lets us build SSA +// in one pass, since the bytecode is structured. +// +// We traverse the bytecode iteratively, maintaining a current basic block. +// Each basic block has a mapping of local slots to instructions, as well as a +// stack depth. As we encounter instructions we mutate this mapping in the +// current block. +// +// Things get interesting when we encounter a control structure. This can be +// either an IFEQ, downward GOTO, or a decompiler hint stashed away in source +// notes. Once we encounter such an opcode, we recover the structure of the +// control flow (its branches and bounds), and push it on a stack. +// +// As we continue traversing the bytecode, we look for points that would +// terminate the topmost control flow path pushed on the stack. These are: +// (1) The bounds of the current structure (end of a loop or join/edge of a +// branch). +// (2) A "return", "break", or "continue" statement. +// +// For (1), we expect that there is a current block in the progress of being +// built, and we complete the necessary edges in the CFG. For (2), we expect +// that there is no active block. +// +// For normal diamond join points, we construct Phi nodes as we add +// predecessors. For loops, care must be taken to propagate Phi nodes back +// through uses in the loop body. +bool +IonBuilder::traverseBytecode() +{ + for (;;) { + MOZ_ASSERT(pc < info().limitPC()); + + for (;;) { + if (!alloc().ensureBallast()) + return false; + + // Check if we've hit an expected join point or edge in the bytecode. + // Leaving one control structure could place us at the edge of another, + // thus |while| instead of |if| so we don't skip any opcodes. + MOZ_ASSERT_IF(!cfgStack_.empty(), cfgStack_.back().stopAt >= pc); + if (!cfgStack_.empty() && cfgStack_.back().stopAt == pc) { + ControlStatus status = processCfgStack(); + if (status == ControlStatus_Error) + return false; + if (status == ControlStatus_Abort) + return abort("Aborted while processing control flow"); + if (!current) + return true; + continue; + } + + // Some opcodes need to be handled early because they affect control + // flow, terminating the current basic block and/or instructing the + // traversal algorithm to continue from a new pc. + // + // (1) If the opcode does not affect control flow, then the opcode + // is inspected and transformed to IR. This is the process_opcode + // label. + // (2) A loop could be detected via a forward GOTO. In this case, + // we don't want to process the GOTO, but the following + // instruction. + // (3) A RETURN, STOP, BREAK, or CONTINUE may require processing the + // CFG stack to terminate open branches. + // + // Similar to above, snooping control flow could land us at another + // control flow point, so we iterate until it's time to inspect a real + // opcode. + ControlStatus status; + if ((status = snoopControlFlow(JSOp(*pc))) == ControlStatus_None) + break; + if (status == ControlStatus_Error) + return false; + if (status == ControlStatus_Abort) + return abort("Aborted while processing control flow"); + if (!current) + return true; + } + +#ifdef DEBUG + // In debug builds, after compiling this op, check that all values + // popped by this opcode either: + // + // (1) Have the ImplicitlyUsed flag set on them. + // (2) Have more uses than before compiling this op (the value is + // used as operand of a new MIR instruction). + // + // This is used to catch problems where IonBuilder pops a value without + // adding any SSA uses and doesn't call setImplicitlyUsedUnchecked on it. + Vector<MDefinition*, 4, JitAllocPolicy> popped(alloc()); + Vector<size_t, 4, JitAllocPolicy> poppedUses(alloc()); + unsigned nuses = GetUseCount(script_, script_->pcToOffset(pc)); + + for (unsigned i = 0; i < nuses; i++) { + MDefinition* def = current->peek(-int32_t(i + 1)); + if (!popped.append(def) || !poppedUses.append(def->defUseCount())) + return false; + } +#endif + + // Nothing in inspectOpcode() is allowed to advance the pc. + JSOp op = JSOp(*pc); + if (!inspectOpcode(op)) + return false; + +#ifdef DEBUG + for (size_t i = 0; i < popped.length(); i++) { + switch (op) { + case JSOP_POP: + case JSOP_POPN: + case JSOP_DUPAT: + case JSOP_DUP: + case JSOP_DUP2: + case JSOP_PICK: + case JSOP_SWAP: + case JSOP_SETARG: + case JSOP_SETLOCAL: + case JSOP_INITLEXICAL: + case JSOP_SETRVAL: + case JSOP_VOID: + // Don't require SSA uses for values popped by these ops. + break; + + case JSOP_POS: + case JSOP_TOID: + case JSOP_TOSTRING: + // These ops may leave their input on the stack without setting + // the ImplicitlyUsed flag. If this value will be popped immediately, + // we may replace it with |undefined|, but the difference is + // not observable. + MOZ_ASSERT(i == 0); + if (current->peek(-1) == popped[0]) + break; + MOZ_FALLTHROUGH; + + default: + MOZ_ASSERT(popped[i]->isImplicitlyUsed() || + + // MNewDerivedTypedObject instances are + // often dead unless they escape from the + // fn. See IonBuilder::loadTypedObjectData() + // for more details. + popped[i]->isNewDerivedTypedObject() || + + popped[i]->defUseCount() > poppedUses[i]); + break; + } + } +#endif + + pc += CodeSpec[op].length; + current->updateTrackedSite(bytecodeSite(pc)); + } + + return true; +} + +IonBuilder::ControlStatus +IonBuilder::snoopControlFlow(JSOp op) +{ + switch (op) { + case JSOP_NOP: + return maybeLoop(op, info().getNote(gsn, pc)); + + case JSOP_POP: + return maybeLoop(op, info().getNote(gsn, pc)); + + case JSOP_RETURN: + case JSOP_RETRVAL: + return processReturn(op); + + case JSOP_THROW: + return processThrow(); + + case JSOP_GOTO: + { + jssrcnote* sn = info().getNote(gsn, pc); + switch (sn ? SN_TYPE(sn) : SRC_NULL) { + case SRC_BREAK: + case SRC_BREAK2LABEL: + return processBreak(op, sn); + + case SRC_CONTINUE: + return processContinue(op); + + case SRC_SWITCHBREAK: + return processSwitchBreak(op); + + case SRC_WHILE: + case SRC_FOR_IN: + case SRC_FOR_OF: + // while (cond) { } + return whileOrForInLoop(sn); + + default: + // Hard assert for now - make an error later. + MOZ_CRASH("unknown goto case"); + } + break; + } + + case JSOP_TABLESWITCH: + return tableSwitch(op, info().getNote(gsn, pc)); + + case JSOP_IFNE: + // We should never reach an IFNE, it's a stopAt point, which will + // trigger closing the loop. + MOZ_CRASH("we should never reach an ifne!"); + + default: + break; + } + return ControlStatus_None; +} + +bool +IonBuilder::inspectOpcode(JSOp op) +{ + MOZ_ASSERT(analysis_.maybeInfo(pc), "Compiling unreachable op"); + + switch (op) { + case JSOP_NOP: + case JSOP_NOP_DESTRUCTURING: + case JSOP_LINENO: + case JSOP_LOOPENTRY: + case JSOP_JUMPTARGET: + return true; + + case JSOP_LABEL: + return jsop_label(); + + case JSOP_UNDEFINED: + // If this ever changes, change what JSOP_GIMPLICITTHIS does too. + pushConstant(UndefinedValue()); + return true; + + case JSOP_IFEQ: + return jsop_ifeq(JSOP_IFEQ); + + case JSOP_TRY: + return jsop_try(); + + case JSOP_CONDSWITCH: + return jsop_condswitch(); + + case JSOP_BITNOT: + return jsop_bitnot(); + + case JSOP_BITAND: + case JSOP_BITOR: + case JSOP_BITXOR: + case JSOP_LSH: + case JSOP_RSH: + case JSOP_URSH: + return jsop_bitop(op); + + case JSOP_ADD: + case JSOP_SUB: + case JSOP_MUL: + case JSOP_DIV: + case JSOP_MOD: + return jsop_binary_arith(op); + + case JSOP_POW: + return jsop_pow(); + + case JSOP_POS: + return jsop_pos(); + + case JSOP_NEG: + return jsop_neg(); + + case JSOP_TOSTRING: + return jsop_tostring(); + + case JSOP_AND: + case JSOP_OR: + return jsop_andor(op); + + case JSOP_DEFVAR: + return jsop_defvar(GET_UINT32_INDEX(pc)); + + case JSOP_DEFLET: + case JSOP_DEFCONST: + return jsop_deflexical(GET_UINT32_INDEX(pc)); + + case JSOP_DEFFUN: + return jsop_deffun(GET_UINT32_INDEX(pc)); + + case JSOP_EQ: + case JSOP_NE: + case JSOP_STRICTEQ: + case JSOP_STRICTNE: + case JSOP_LT: + case JSOP_LE: + case JSOP_GT: + case JSOP_GE: + return jsop_compare(op); + + case JSOP_DOUBLE: + pushConstant(info().getConst(pc)); + return true; + + case JSOP_STRING: + pushConstant(StringValue(info().getAtom(pc))); + return true; + + case JSOP_SYMBOL: { + unsigned which = GET_UINT8(pc); + JS::Symbol* sym = compartment->runtime()->wellKnownSymbols().get(which); + pushConstant(SymbolValue(sym)); + return true; + } + + case JSOP_ZERO: + pushConstant(Int32Value(0)); + return true; + + case JSOP_ONE: + pushConstant(Int32Value(1)); + return true; + + case JSOP_NULL: + pushConstant(NullValue()); + return true; + + case JSOP_VOID: + current->pop(); + pushConstant(UndefinedValue()); + return true; + + case JSOP_HOLE: + pushConstant(MagicValue(JS_ELEMENTS_HOLE)); + return true; + + case JSOP_FALSE: + pushConstant(BooleanValue(false)); + return true; + + case JSOP_TRUE: + pushConstant(BooleanValue(true)); + return true; + + case JSOP_ARGUMENTS: + return jsop_arguments(); + + case JSOP_RUNONCE: + return jsop_runonce(); + + case JSOP_REST: + return jsop_rest(); + + case JSOP_GETARG: + if (info().argsObjAliasesFormals()) { + MGetArgumentsObjectArg* getArg = MGetArgumentsObjectArg::New(alloc(), + current->argumentsObject(), + GET_ARGNO(pc)); + current->add(getArg); + current->push(getArg); + } else { + current->pushArg(GET_ARGNO(pc)); + } + return true; + + case JSOP_SETARG: + return jsop_setarg(GET_ARGNO(pc)); + + case JSOP_GETLOCAL: + current->pushLocal(GET_LOCALNO(pc)); + return true; + + case JSOP_SETLOCAL: + current->setLocal(GET_LOCALNO(pc)); + return true; + + case JSOP_THROWSETCONST: + case JSOP_THROWSETALIASEDCONST: + case JSOP_THROWSETCALLEE: + return jsop_throwsetconst(); + + case JSOP_CHECKLEXICAL: + return jsop_checklexical(); + + case JSOP_INITLEXICAL: + current->setLocal(GET_LOCALNO(pc)); + return true; + + case JSOP_INITGLEXICAL: { + MOZ_ASSERT(!script()->hasNonSyntacticScope()); + MDefinition* value = current->pop(); + current->push(constant(ObjectValue(script()->global().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 true; + + case JSOP_POP: { + MDefinition* def = current->pop(); + + // POP opcodes frequently appear where values are killed, e.g. after + // SET* opcodes. Place a resume point afterwards to avoid capturing + // the dead value in later snapshots, except in places where that + // resume point is obviously unnecessary. + if (pc[JSOP_POP_LENGTH] == JSOP_POP) + return true; + if (def->isConstant()) + return true; + return maybeInsertResume(); + } + + case JSOP_POPN: + for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++) + current->pop(); + return true; + + case JSOP_DUPAT: + current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc)); + return true; + + case JSOP_NEWINIT: + if (GET_UINT8(pc) == JSProto_Array) + return jsop_newarray(0); + return jsop_newobject(); + + case JSOP_NEWARRAY: + return jsop_newarray(GET_UINT32(pc)); + + case JSOP_NEWARRAY_COPYONWRITE: + return jsop_newarray_copyonwrite(); + + case JSOP_NEWOBJECT: + return jsop_newobject(); + + case JSOP_INITELEM: + case JSOP_INITHIDDENELEM: + return jsop_initelem(); + + case JSOP_INITELEM_ARRAY: + return jsop_initelem_array(); + + case JSOP_INITPROP: + case JSOP_INITLOCKEDPROP: + case JSOP_INITHIDDENPROP: + { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + return jsop_initprop(name); + } + + case JSOP_MUTATEPROTO: + { + return jsop_mutateproto(); + } + + case JSOP_INITPROP_GETTER: + case JSOP_INITHIDDENPROP_GETTER: + case JSOP_INITPROP_SETTER: + case JSOP_INITHIDDENPROP_SETTER: { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + return jsop_initprop_getter_setter(name); + } + + case JSOP_INITELEM_GETTER: + case JSOP_INITHIDDENELEM_GETTER: + case JSOP_INITELEM_SETTER: + case JSOP_INITHIDDENELEM_SETTER: + return jsop_initelem_getter_setter(); + + case JSOP_FUNCALL: + return jsop_funcall(GET_ARGC(pc)); + + case JSOP_FUNAPPLY: + return jsop_funapply(GET_ARGC(pc)); + + case JSOP_CALL: + case JSOP_CALLITER: + case JSOP_NEW: + case JSOP_SUPERCALL: + return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); + + case JSOP_EVAL: + case JSOP_STRICTEVAL: + return jsop_eval(GET_ARGC(pc)); + + case JSOP_INT8: + pushConstant(Int32Value(GET_INT8(pc))); + return true; + + case JSOP_UINT16: + pushConstant(Int32Value(GET_UINT16(pc))); + return true; + + 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 true; + } + } + // Fall through to JSOP_BINDNAME + 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 true; + + case JSOP_DUP2: + return jsop_dup2(); + + case JSOP_SWAP: + current->swapAt(-1); + return true; + + case JSOP_PICK: + current->pick(-GET_INT8(pc)); + return true; + + case JSOP_GETALIASEDVAR: + return jsop_getaliasedvar(EnvironmentCoordinate(pc)); + + case JSOP_SETALIASEDVAR: + return jsop_setaliasedvar(EnvironmentCoordinate(pc)); + + case JSOP_UINT24: + pushConstant(Int32Value(GET_UINT24(pc))); + return true; + + case JSOP_INT32: + pushConstant(Int32Value(GET_INT32(pc))); + return true; + + case JSOP_LOOPHEAD: + // JSOP_LOOPHEAD is handled when processing the loop header. + MOZ_CRASH("JSOP_LOOPHEAD outside loop"); + + case JSOP_GETELEM: + case JSOP_CALLELEM: + if (!jsop_getelem()) + return false; + if (op == JSOP_CALLELEM && !improveThisTypesForCall()) + return false; + return true; + + case JSOP_SETELEM: + case JSOP_STRICTSETELEM: + return jsop_setelem(); + + case JSOP_LENGTH: + return jsop_length(); + + case JSOP_NOT: + return jsop_not(); + + case JSOP_FUNCTIONTHIS: + return jsop_functionthis(); + + case JSOP_GLOBALTHIS: + return jsop_globalthis(); + + case JSOP_CALLEE: { + MDefinition* callee = getCallee(); + current->push(callee); + return true; + } + + case JSOP_GETPROP: + case JSOP_CALLPROP: + { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + if (!jsop_getprop(name)) + return false; + if (op == JSOP_CALLPROP && !improveThisTypesForCall()) + return false; + return true; + } + + case JSOP_SETPROP: + case JSOP_STRICTSETPROP: + case JSOP_SETNAME: + case JSOP_STRICTSETNAME: + { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + return jsop_setprop(name); + } + + case JSOP_DELPROP: + case JSOP_STRICTDELPROP: + { + PropertyName* name = info().getAtom(pc)->asPropertyName(); + return jsop_delprop(name); + } + + case JSOP_DELELEM: + case JSOP_STRICTDELELEM: + return jsop_delelem(); + + case JSOP_REGEXP: + return jsop_regexp(info().getRegExp(pc)); + + case JSOP_CALLSITEOBJ: + pushConstant(ObjectValue(*(info().getObject(pc)))); + return true; + + case JSOP_OBJECT: + return jsop_object(info().getObject(pc)); + + case JSOP_TYPEOF: + case JSOP_TYPEOFEXPR: + return jsop_typeof(); + + case JSOP_TOASYNC: + return jsop_toasync(); + + case JSOP_TOID: + return jsop_toid(); + + case JSOP_LAMBDA: + return jsop_lambda(info().getFunction(pc)); + + case JSOP_LAMBDA_ARROW: + return jsop_lambda_arrow(info().getFunction(pc)); + + case JSOP_ITER: + return jsop_iter(GET_INT8(pc)); + + case JSOP_MOREITER: + return jsop_itermore(); + + case JSOP_ISNOITER: + return jsop_isnoiter(); + + case JSOP_ENDITER: + return jsop_iterend(); + + case JSOP_IN: + return jsop_in(); + + case JSOP_SETRVAL: + MOZ_ASSERT(!script()->noScriptRval()); + current->setSlot(info().returnValueSlot(), current->pop()); + return true; + + case JSOP_INSTANCEOF: + return jsop_instanceof(); + + case JSOP_DEBUGLEAVELEXICALENV: + return true; + + case JSOP_DEBUGGER: + return jsop_debugger(); + + case JSOP_GIMPLICITTHIS: + if (!script()->hasNonSyntacticScope()) { + pushConstant(UndefinedValue()); + return true; + } + + // Just fall through to the unsupported bytecode case. + break; + + case JSOP_NEWTARGET: + return jsop_newtarget(); + + case JSOP_CHECKISOBJ: + return jsop_checkisobj(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); + if (!resumeAfter(check)) + return false; +#endif + return true; + } + + case JSOP_IS_CONSTRUCTING: + pushConstant(MagicValue(JS_IS_CONSTRUCTING)); + return true; + +#ifdef DEBUG + case JSOP_PUSHLEXICALENV: + case JSOP_FRESHENLEXICALENV: + case JSOP_RECREATELEXICALENV: + case JSOP_POPLEXICALENV: + // These opcodes are currently unhandled by Ion, but in principle + // there's no reason they couldn't be. Whenever this happens, OSR + // will have to consider that JSOP_{FRESHEN,RECREATE}LEXICALENV + // mutates the env chain -- right now MBasicBlock::environmentChain() + // caches the env chain. JSOP_{FRESHEN,RECREATE}LEXICALENV must + // update that stale value. +#endif + default: + break; + } + + // Track a simpler message, since the actionable abort message is a + // static string, and the internal opcode name isn't an actionable + // thing anyways. + trackActionableAbort("Unsupported bytecode"); +#ifdef DEBUG + return abort("Unsupported opcode: %s", CodeName[op]); +#else + return abort("Unsupported opcode: %d", op); +#endif +} + +// Given that the current control flow structure has ended forcefully, +// via a return, break, or continue (rather than joining), propagate the +// termination up. For example, a return nested 5 loops deep may terminate +// every outer loop at once, if there are no intervening conditionals: +// +// for (...) { +// for (...) { +// return x; +// } +// } +// +// If |current| is nullptr when this function returns, then there is no more +// control flow to be processed. +IonBuilder::ControlStatus +IonBuilder::processControlEnd() +{ + MOZ_ASSERT(!current); + + if (cfgStack_.empty()) { + // If there is no more control flow to process, then this is the + // last return in the function. + return ControlStatus_Ended; + } + + return processCfgStack(); +} + +// Processes the top of the CFG stack. This is used from two places: +// (1) processControlEnd(), whereby a break, continue, or return may interrupt +// an in-progress CFG structure before reaching its actual termination +// point in the bytecode. +// (2) traverseBytecode(), whereby we reach the last instruction in a CFG +// structure. +IonBuilder::ControlStatus +IonBuilder::processCfgStack() +{ + ControlStatus status = processCfgEntry(cfgStack_.back()); + + // If this terminated a CFG structure, act like processControlEnd() and + // keep propagating upward. + while (status == ControlStatus_Ended) { + popCfgStack(); + if (cfgStack_.empty()) + return status; + status = processCfgEntry(cfgStack_.back()); + } + + // If some join took place, the current structure is finished. + if (status == ControlStatus_Joined) + popCfgStack(); + + return status; +} + +IonBuilder::ControlStatus +IonBuilder::processCfgEntry(CFGState& state) +{ + switch (state.state) { + case CFGState::IF_TRUE: + case CFGState::IF_TRUE_EMPTY_ELSE: + return processIfEnd(state); + + case CFGState::IF_ELSE_TRUE: + return processIfElseTrueEnd(state); + + case CFGState::IF_ELSE_FALSE: + return processIfElseFalseEnd(state); + + case CFGState::DO_WHILE_LOOP_BODY: + return processDoWhileBodyEnd(state); + + case CFGState::DO_WHILE_LOOP_COND: + return processDoWhileCondEnd(state); + + case CFGState::WHILE_LOOP_COND: + return processWhileCondEnd(state); + + case CFGState::WHILE_LOOP_BODY: + return processWhileBodyEnd(state); + + case CFGState::FOR_LOOP_COND: + return processForCondEnd(state); + + case CFGState::FOR_LOOP_BODY: + return processForBodyEnd(state); + + case CFGState::FOR_LOOP_UPDATE: + return processForUpdateEnd(state); + + case CFGState::TABLE_SWITCH: + return processNextTableSwitchCase(state); + + case CFGState::COND_SWITCH_CASE: + return processCondSwitchCase(state); + + case CFGState::COND_SWITCH_BODY: + return processCondSwitchBody(state); + + case CFGState::AND_OR: + return processAndOrEnd(state); + + case CFGState::LABEL: + return processLabelEnd(state); + + case CFGState::TRY: + return processTryEnd(state); + + default: + MOZ_CRASH("unknown cfgstate"); + } +} + +IonBuilder::ControlStatus +IonBuilder::processIfEnd(CFGState& state) +{ + bool thenBranchTerminated = !current; + if (!thenBranchTerminated) { + // Here, the false block is the join point. Create an edge from the + // current block to the false block. Note that a RETURN opcode + // could have already ended the block. + current->end(MGoto::New(alloc(), state.branch.ifFalse)); + + if (!state.branch.ifFalse->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + if (!setCurrentAndSpecializePhis(state.branch.ifFalse)) + return ControlStatus_Error; + graph().moveBlockToEnd(current); + pc = current->pc(); + + if (thenBranchTerminated) { + // If we can't reach here via the then-branch, we can filter the types + // after the if-statement based on the if-condition. + MTest* test = state.branch.test; + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return ControlStatus_Error; + } + + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::processIfElseTrueEnd(CFGState& state) +{ + // We've reached the end of the true branch of an if-else. Don't + // create an edge yet, just transition to parsing the false branch. + state.state = CFGState::IF_ELSE_FALSE; + state.branch.ifTrue = current; + state.stopAt = state.branch.falseEnd; + pc = state.branch.ifFalse->pc(); + if (!setCurrentAndSpecializePhis(state.branch.ifFalse)) + return ControlStatus_Error; + graph().moveBlockToEnd(current); + + MTest* test = state.branch.test; + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return ControlStatus_Error; + + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processIfElseFalseEnd(CFGState& state) +{ + // Update the state to have the latest block from the false path. + state.branch.ifFalse = current; + + // To create the join node, we need an incoming edge that has not been + // terminated yet. + MBasicBlock* pred = state.branch.ifTrue + ? state.branch.ifTrue + : state.branch.ifFalse; + MBasicBlock* other = (pred == state.branch.ifTrue) ? state.branch.ifFalse : state.branch.ifTrue; + + if (!pred) + return ControlStatus_Ended; + + // Create a new block to represent the join. + MBasicBlock* join = newBlock(pred, state.branch.falseEnd); + if (!join) + return ControlStatus_Error; + + // Create edges from the true and false blocks as needed. + pred->end(MGoto::New(alloc(), join)); + + if (other) { + other->end(MGoto::New(alloc(), join)); + if (!join->addPredecessor(alloc(), other)) + return ControlStatus_Error; + } + + // Ignore unreachable remainder of false block if existent. + if (!setCurrentAndSpecializePhis(join)) + return ControlStatus_Error; + pc = current->pc(); + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::processBrokenLoop(CFGState& state) +{ + MOZ_ASSERT(!current); + + MOZ_ASSERT(loopDepth_); + loopDepth_--; + + // A broken loop is not a real loop (it has no header or backedge), so + // reset the loop depth. + for (MBasicBlockIterator i(graph().begin(state.loop.entry)); i != graph().end(); i++) { + if (i->loopDepth() > loopDepth_) + i->setLoopDepth(i->loopDepth() - 1); + } + + // If the loop started with a condition (while/for) then even if the + // structure never actually loops, the condition itself can still fail and + // thus we must resume at the successor, if one exists. + if (!setCurrentAndSpecializePhis(state.loop.successor)) + return ControlStatus_Error; + if (current) { + MOZ_ASSERT(current->loopDepth() == loopDepth_); + graph().moveBlockToEnd(current); + } + + // Join the breaks together and continue parsing. + if (state.loop.breaks) { + MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc); + if (!block) + return ControlStatus_Error; + + if (current) { + current->end(MGoto::New(alloc(), block)); + if (!block->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + if (!setCurrentAndSpecializePhis(block)) + return ControlStatus_Error; + } + + // If the loop is not gated on a condition, and has only returns, we'll + // reach this case. For example: + // do { ... return; } while (); + if (!current) + return ControlStatus_Ended; + + // Otherwise, the loop is gated on a condition and/or has breaks so keep + // parsing at the successor. + pc = current->pc(); + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::finishLoop(CFGState& state, MBasicBlock* successor) +{ + MOZ_ASSERT(current); + + MOZ_ASSERT(loopDepth_); + loopDepth_--; + MOZ_ASSERT_IF(successor, successor->loopDepth() == loopDepth_); + + // Compute phis in the loop header and propagate them throughout the loop, + // including the successor. + AbortReason r = state.loop.entry->setBackedge(alloc(), current); + if (r == AbortReason_Alloc) + return ControlStatus_Error; + if (r == AbortReason_Disable) { + // If there are types for variables on the backedge that were not + // present at the original loop header, then uses of the variables' + // phis may have generated incorrect nodes. The new types have been + // incorporated into the header phis, so remove all blocks for the + // loop body and restart with the new types. + return restartLoop(state); + } + + if (successor) { + graph().moveBlockToEnd(successor); + successor->inheritPhis(state.loop.entry); + } + + if (state.loop.breaks) { + // Propagate phis placed in the header to individual break exit points. + DeferredEdge* edge = state.loop.breaks; + while (edge) { + edge->block->inheritPhis(state.loop.entry); + edge = edge->next; + } + + // Create a catch block to join all break exits. + MBasicBlock* block = createBreakCatchBlock(state.loop.breaks, state.loop.exitpc); + if (!block) + return ControlStatus_Error; + + if (successor) { + // Finally, create an unconditional edge from the successor to the + // catch block. + successor->end(MGoto::New(alloc(), block)); + if (!block->addPredecessor(alloc(), successor)) + return ControlStatus_Error; + } + successor = block; + } + + if (!setCurrentAndSpecializePhis(successor)) + return ControlStatus_Error; + + // An infinite loop (for (;;) { }) will not have a successor. + if (!current) + return ControlStatus_Ended; + + pc = current->pc(); + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::restartLoop(const CFGState& state) +{ + AutoTraceLog logCompile(traceLogger(), TraceLogger_IonBuilderRestartLoop); + + spew("New types at loop header, restarting loop body"); + + if (JitOptions.limitScriptSize) { + if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS) + return ControlStatus_Abort; + } + + MBasicBlock* header = state.loop.entry; + + // Discard unreferenced & pre-allocated resume points. + replaceMaybeFallbackFunctionGetter(nullptr); + + // Remove all blocks in the loop body other than the header, which has phis + // of the appropriate type and incoming edges to preserve. + graph().removeBlocksAfter(header); + + // Remove all instructions from the header itself, and all resume points + // except the entry resume point. + header->discardAllInstructions(); + header->discardAllResumePoints(/* discardEntry = */ false); + header->setStackDepth(header->getPredecessor(0)->stackDepth()); + + popCfgStack(); + + loopDepth_++; + + // Keep a local copy for these pointers since state will be overwritten in + // pushLoop since state is a reference to cfgStack_.back() + jsbytecode* condpc = state.loop.condpc; + jsbytecode* updatepc = state.loop.updatepc; + jsbytecode* updateEnd = state.loop.updateEnd; + + if (!pushLoop(state.loop.initialState, state.loop.initialStopAt, header, state.loop.osr, + state.loop.loopHead, state.loop.initialPc, + state.loop.bodyStart, state.loop.bodyEnd, + state.loop.exitpc, state.loop.continuepc)) + { + return ControlStatus_Error; + } + + CFGState& nstate = cfgStack_.back(); + + nstate.loop.condpc = condpc; + nstate.loop.updatepc = updatepc; + nstate.loop.updateEnd = updateEnd; + + // Don't specializePhis(), as the header has been visited before and the + // phis have already had their type set. + setCurrent(header); + + if (!jsop_loophead(nstate.loop.loopHead)) + return ControlStatus_Error; + + pc = nstate.loop.initialPc; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processDoWhileBodyEnd(CFGState& state) +{ + if (!processDeferredContinues(state)) + return ControlStatus_Error; + + // No current means control flow cannot reach the condition, so this will + // never loop. + if (!current) + return processBrokenLoop(state); + + MBasicBlock* header = newBlock(current, state.loop.updatepc); + if (!header) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), header)); + + state.state = CFGState::DO_WHILE_LOOP_COND; + state.stopAt = state.loop.updateEnd; + pc = state.loop.updatepc; + if (!setCurrentAndSpecializePhis(header)) + return ControlStatus_Error; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processDoWhileCondEnd(CFGState& state) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE); + + // We're guaranteed a |current|, it's impossible to break or return from + // inside the conditional expression. + MOZ_ASSERT(current); + + // Pop the last value, and create the successor block. + MDefinition* vins = current->pop(); + MBasicBlock* successor = newBlock(current, GetNextPc(pc), loopDepth_ - 1); + if (!successor) + return ControlStatus_Error; + + // Test for do {} while(false) and don't create a loop in that case. + if (MConstant* vinsConst = vins->maybeConstantValue()) { + bool b; + if (vinsConst->valueToBoolean(&b) && !b) { + current->end(MGoto::New(alloc(), successor)); + current = nullptr; + + state.loop.successor = successor; + return processBrokenLoop(state); + } + } + + // Create the test instruction and end the current block. + MTest* test = newTest(vins, state.loop.entry, successor); + current->end(test); + return finishLoop(state, successor); +} + +IonBuilder::ControlStatus +IonBuilder::processWhileCondEnd(CFGState& state) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ); + + // Balance the stack past the IFNE. + MDefinition* ins = current->pop(); + + // Create the body and successor blocks. + MBasicBlock* body = newBlock(current, state.loop.bodyStart); + state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1); + if (!body || !state.loop.successor) + return ControlStatus_Error; + + MTest* test; + if (JSOp(*pc) == JSOP_IFNE) + test = newTest(ins, body, state.loop.successor); + else + test = newTest(ins, state.loop.successor, body); + current->end(test); + + state.state = CFGState::WHILE_LOOP_BODY; + state.stopAt = state.loop.bodyEnd; + pc = state.loop.bodyStart; + if (!setCurrentAndSpecializePhis(body)) + return ControlStatus_Error; + + // Filter the types in the loop body. + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return ControlStatus_Error; + + // If this is a for-in loop, unbox the current value as string if possible. + if (ins->isIsNoIter()) { + MIteratorMore* iterMore = ins->toIsNoIter()->input()->toIteratorMore(); + jsbytecode* iterMorePc = iterMore->resumePoint()->pc(); + MOZ_ASSERT(*iterMorePc == JSOP_MOREITER); + + if (!nonStringIteration_ && !inspector->hasSeenNonStringIterMore(iterMorePc)) { + MDefinition* val = current->peek(-1); + MOZ_ASSERT(val == iterMore); + MInstruction* ins = MUnbox::New(alloc(), val, MIRType::String, MUnbox::Fallible, + Bailout_NonStringInputInvalidate); + current->add(ins); + current->rewriteAtDepth(-1, ins); + } + } + + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processWhileBodyEnd(CFGState& state) +{ + if (!processDeferredContinues(state)) + return ControlStatus_Error; + + if (!current) + return processBrokenLoop(state); + + current->end(MGoto::New(alloc(), state.loop.entry)); + return finishLoop(state, state.loop.successor); +} + +IonBuilder::ControlStatus +IonBuilder::processForCondEnd(CFGState& state) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_IFNE); + + // Balance the stack past the IFNE. + MDefinition* ins = current->pop(); + + // Create the body and successor blocks. + MBasicBlock* body = newBlock(current, state.loop.bodyStart); + state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1); + if (!body || !state.loop.successor) + return ControlStatus_Error; + + MTest* test = newTest(ins, body, state.loop.successor); + current->end(test); + + state.state = CFGState::FOR_LOOP_BODY; + state.stopAt = state.loop.bodyEnd; + pc = state.loop.bodyStart; + if (!setCurrentAndSpecializePhis(body)) + return ControlStatus_Error; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processForBodyEnd(CFGState& state) +{ + if (!processDeferredContinues(state)) + return ControlStatus_Error; + + // If there is no updatepc, just go right to processing what would be the + // end of the update clause. Otherwise, |current| might be nullptr; if this is + // the case, the udpate is unreachable anyway. + if (!state.loop.updatepc || !current) + return processForUpdateEnd(state); + + pc = state.loop.updatepc; + + state.state = CFGState::FOR_LOOP_UPDATE; + state.stopAt = state.loop.updateEnd; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processForUpdateEnd(CFGState& state) +{ + // If there is no current, we couldn't reach the loop edge and there was no + // update clause. + if (!current) + return processBrokenLoop(state); + + current->end(MGoto::New(alloc(), state.loop.entry)); + return finishLoop(state, state.loop.successor); +} + +IonBuilder::DeferredEdge* +IonBuilder::filterDeadDeferredEdges(DeferredEdge* edge) +{ + DeferredEdge* head = edge; + DeferredEdge* prev = nullptr; + + while (edge) { + if (edge->block->isDead()) { + if (prev) + prev->next = edge->next; + else + head = edge->next; + } else { + prev = edge; + } + edge = edge->next; + } + + // There must be at least one deferred edge from a block that was not + // deleted; blocks are deleted when restarting processing of a loop, and + // the final version of the loop body will have edges from live blocks. + MOZ_ASSERT(head); + + return head; +} + +bool +IonBuilder::processDeferredContinues(CFGState& state) +{ + // If there are any continues for this loop, and there is an update block, + // then we need to create a new basic block to house the update. + if (state.loop.continues) { + DeferredEdge* edge = filterDeadDeferredEdges(state.loop.continues); + + MBasicBlock* update = newBlock(edge->block, loops_.back().continuepc); + if (!update) + return false; + + if (current) { + current->end(MGoto::New(alloc(), update)); + if (!update->addPredecessor(alloc(), current)) + return false; + } + + // No need to use addPredecessor for first edge, + // because it is already predecessor. + edge->block->end(MGoto::New(alloc(), update)); + edge = edge->next; + + // Remaining edges + while (edge) { + edge->block->end(MGoto::New(alloc(), update)); + if (!update->addPredecessor(alloc(), edge->block)) + return false; + edge = edge->next; + } + state.loop.continues = nullptr; + + if (!setCurrentAndSpecializePhis(update)) + return ControlStatus_Error; + } + + return true; +} + +MBasicBlock* +IonBuilder::createBreakCatchBlock(DeferredEdge* edge, jsbytecode* pc) +{ + edge = filterDeadDeferredEdges(edge); + + // Create block, using the first break statement as predecessor + MBasicBlock* successor = newBlock(edge->block, pc); + if (!successor) + return nullptr; + + // No need to use addPredecessor for first edge, + // because it is already predecessor. + edge->block->end(MGoto::New(alloc(), successor)); + edge = edge->next; + + // Finish up remaining breaks. + while (edge) { + MGoto* brk = MGoto::New(alloc().fallible(), successor); + if (!brk) + return nullptr; + edge->block->end(brk); + if (!successor->addPredecessor(alloc(), edge->block)) + return nullptr; + edge = edge->next; + } + + return successor; +} + +IonBuilder::ControlStatus +IonBuilder::processNextTableSwitchCase(CFGState& state) +{ + MOZ_ASSERT(state.state == CFGState::TABLE_SWITCH); + + state.tableswitch.currentBlock++; + + // Test if there are still unprocessed successors (cases/default) + if (state.tableswitch.currentBlock >= state.tableswitch.ins->numBlocks()) + return processSwitchEnd(state.tableswitch.breaks, state.tableswitch.exitpc); + + // Get the next successor + MBasicBlock* successor = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock); + + // Add current block as predecessor if available. + // This means the previous case didn't have a break statement. + // So flow will continue in this block. + if (current) { + current->end(MGoto::New(alloc(), successor)); + if (!successor->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } else { + // If this is an actual case statement, optimize by replacing the + // input to the switch case with the actual number of the case. + // This constant has been emitted when creating the case blocks. + if (state.tableswitch.ins->getDefault() != successor) { + MConstant* constant = successor->begin()->toConstant(); + for (uint32_t j = 0; j < successor->stackDepth(); j++) { + MDefinition* ins = successor->getSlot(j); + if (ins != state.tableswitch.ins->getOperand(0)) + continue; + + constant->setDependency(state.tableswitch.ins); + successor->setSlot(j, constant); + } + } + } + + // Insert successor after the current block, to maintain RPO. + graph().moveBlockToEnd(successor); + + // If this is the last successor the block should stop at the end of the tableswitch + // Else it should stop at the start of the next successor + if (state.tableswitch.currentBlock+1 < state.tableswitch.ins->numBlocks()) + state.stopAt = state.tableswitch.ins->getBlock(state.tableswitch.currentBlock+1)->pc(); + else + state.stopAt = state.tableswitch.exitpc; + + if (!setCurrentAndSpecializePhis(successor)) + return ControlStatus_Error; + pc = current->pc(); + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processAndOrEnd(CFGState& state) +{ + MOZ_ASSERT(current); + MBasicBlock* lhs = state.branch.ifFalse; + + // Create a new block to represent the join. + MBasicBlock* join = newBlock(current, state.stopAt); + if (!join) + return ControlStatus_Error; + + // End the rhs. + current->end(MGoto::New(alloc(), join)); + + // End the lhs. + lhs->end(MGoto::New(alloc(), join)); + if (!join->addPredecessor(alloc(), state.branch.ifFalse)) + return ControlStatus_Error; + + // Set the join path as current path. + if (!setCurrentAndSpecializePhis(join)) + return ControlStatus_Error; + pc = current->pc(); + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::processLabelEnd(CFGState& state) +{ + MOZ_ASSERT(state.state == CFGState::LABEL); + + // If there are no breaks and no current, controlflow is terminated. + if (!state.label.breaks && !current) + return ControlStatus_Ended; + + // If there are no breaks to this label, there's nothing to do. + if (!state.label.breaks) + return ControlStatus_Joined; + + MBasicBlock* successor = createBreakCatchBlock(state.label.breaks, state.stopAt); + if (!successor) + return ControlStatus_Error; + + if (current) { + current->end(MGoto::New(alloc(), successor)); + if (!successor->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + pc = state.stopAt; + if (!setCurrentAndSpecializePhis(successor)) + return ControlStatus_Error; + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::processTryEnd(CFGState& state) +{ + MOZ_ASSERT(state.state == CFGState::TRY); + + if (!state.try_.successor) { + MOZ_ASSERT(!current); + return ControlStatus_Ended; + } + + if (current) { + current->end(MGoto::New(alloc(), state.try_.successor)); + + if (!state.try_.successor->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + // Start parsing the code after this try-catch statement. + if (!setCurrentAndSpecializePhis(state.try_.successor)) + return ControlStatus_Error; + graph().moveBlockToEnd(current); + pc = current->pc(); + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::processBreak(JSOp op, jssrcnote* sn) +{ + MOZ_ASSERT(op == JSOP_GOTO); + + MOZ_ASSERT(SN_TYPE(sn) == SRC_BREAK || + SN_TYPE(sn) == SRC_BREAK2LABEL); + + // Find the break target. + jsbytecode* target = pc + GetJumpOffset(pc); + DebugOnly<bool> found = false; + + if (SN_TYPE(sn) == SRC_BREAK2LABEL) { + for (size_t i = labels_.length() - 1; i < labels_.length(); i--) { + CFGState& cfg = cfgStack_[labels_[i].cfgEntry]; + MOZ_ASSERT(cfg.state == CFGState::LABEL); + if (cfg.stopAt == target) { + cfg.label.breaks = new(alloc()) DeferredEdge(current, cfg.label.breaks); + found = true; + break; + } + } + } else { + for (size_t i = loops_.length() - 1; i < loops_.length(); i--) { + CFGState& cfg = cfgStack_[loops_[i].cfgEntry]; + MOZ_ASSERT(cfg.isLoop()); + if (cfg.loop.exitpc == target) { + cfg.loop.breaks = new(alloc()) DeferredEdge(current, cfg.loop.breaks); + found = true; + break; + } + } + } + + MOZ_ASSERT(found); + + setCurrent(nullptr); + pc += CodeSpec[op].length; + return processControlEnd(); +} + +static inline jsbytecode* +EffectiveContinue(jsbytecode* pc) +{ + if (JSOp(*pc) == JSOP_GOTO) + return pc + GetJumpOffset(pc); + return pc; +} + +IonBuilder::ControlStatus +IonBuilder::processContinue(JSOp op) +{ + MOZ_ASSERT(op == JSOP_GOTO); + + // Find the target loop. + CFGState* found = nullptr; + jsbytecode* target = pc + GetJumpOffset(pc); + for (size_t i = loops_.length() - 1; i < loops_.length(); i--) { + // +1 to skip JSOP_JUMPTARGET. + if (loops_[i].continuepc == target + 1 || + EffectiveContinue(loops_[i].continuepc) == target) + { + found = &cfgStack_[loops_[i].cfgEntry]; + break; + } + } + + // There must always be a valid target loop structure. If not, there's + // probably an off-by-something error in which pc we track. + MOZ_ASSERT(found); + CFGState& state = *found; + + state.loop.continues = new(alloc()) DeferredEdge(current, state.loop.continues); + + setCurrent(nullptr); + pc += CodeSpec[op].length; + return processControlEnd(); +} + +IonBuilder::ControlStatus +IonBuilder::processSwitchBreak(JSOp op) +{ + MOZ_ASSERT(op == JSOP_GOTO); + + // Find the target switch. + CFGState* found = nullptr; + jsbytecode* target = pc + GetJumpOffset(pc); + for (size_t i = switches_.length() - 1; i < switches_.length(); i--) { + if (switches_[i].continuepc == target) { + found = &cfgStack_[switches_[i].cfgEntry]; + break; + } + } + + // There must always be a valid target loop structure. If not, there's + // probably an off-by-something error in which pc we track. + MOZ_ASSERT(found); + CFGState& state = *found; + + DeferredEdge** breaks = nullptr; + switch (state.state) { + case CFGState::TABLE_SWITCH: + breaks = &state.tableswitch.breaks; + break; + case CFGState::COND_SWITCH_BODY: + breaks = &state.condswitch.breaks; + break; + default: + MOZ_CRASH("Unexpected switch state."); + } + + *breaks = new(alloc()) DeferredEdge(current, *breaks); + + setCurrent(nullptr); + pc += CodeSpec[op].length; + return processControlEnd(); +} + +IonBuilder::ControlStatus +IonBuilder::processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc) +{ + // No break statements, no current. + // This means that control flow is cut-off from this point + // (e.g. all cases have return statements). + if (!breaks && !current) + return ControlStatus_Ended; + + // Create successor block. + // If there are breaks, create block with breaks as predecessor + // Else create a block with current as predecessor + MBasicBlock* successor = nullptr; + if (breaks) + successor = createBreakCatchBlock(breaks, exitpc); + else + successor = newBlock(current, exitpc); + + if (!successor) + return ControlStatus_Error; + + // If there is current, the current block flows into this one. + // So current is also a predecessor to this block + if (current) { + current->end(MGoto::New(alloc(), successor)); + if (breaks) { + if (!successor->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + } + + pc = exitpc; + if (!setCurrentAndSpecializePhis(successor)) + return ControlStatus_Error; + return ControlStatus_Joined; +} + +IonBuilder::ControlStatus +IonBuilder::maybeLoop(JSOp op, jssrcnote* sn) +{ + // This function looks at the opcode and source note and tries to + // determine the structure of the loop. For some opcodes, like + // POP/NOP which are not explicitly control flow, this source note is + // optional. For opcodes with control flow, like GOTO, an unrecognized + // or not-present source note is a compilation failure. + switch (op) { + case JSOP_POP: + // for (init; ; update?) ... + if (sn && SN_TYPE(sn) == SRC_FOR) { + current->pop(); + return forLoop(op, sn); + } + break; + + case JSOP_NOP: + if (sn) { + // do { } while (cond) + if (SN_TYPE(sn) == SRC_WHILE) + return doWhileLoop(op, sn); + // Build a mapping such that given a basic block, whose successor + // has a phi + + // for (; ; update?) + if (SN_TYPE(sn) == SRC_FOR) + return forLoop(op, sn); + } + break; + + default: + MOZ_CRASH("unexpected opcode"); + } + + return ControlStatus_None; +} + +void +IonBuilder::assertValidLoopHeadOp(jsbytecode* pc) +{ +#ifdef DEBUG + MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPHEAD); + + // Make sure this is the next opcode after the loop header, + // unless the for loop is unconditional. + CFGState& state = cfgStack_.back(); + MOZ_ASSERT_IF((JSOp)*(state.loop.entry->pc()) == JSOP_GOTO, + GetNextPc(state.loop.entry->pc()) == pc); + + // do-while loops have a source note. + jssrcnote* sn = info().getNote(gsn, pc); + if (sn) { + jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 0); + + jsbytecode* expected_ifne; + switch (state.state) { + case CFGState::DO_WHILE_LOOP_BODY: + expected_ifne = state.loop.updateEnd; + break; + + default: + MOZ_CRASH("JSOP_LOOPHEAD unexpected source note"); + } + + // Make sure this loop goes to the same ifne as the loop header's + // source notes or GOTO. + MOZ_ASSERT(ifne == expected_ifne); + } else { + MOZ_ASSERT(state.state != CFGState::DO_WHILE_LOOP_BODY); + } +#endif +} + +IonBuilder::ControlStatus +IonBuilder::doWhileLoop(JSOp op, jssrcnote* sn) +{ + // do { } while() loops have the following structure: + // NOP ; SRC_WHILE (offset to COND) + // LOOPHEAD ; SRC_WHILE (offset to IFNE) + // LOOPENTRY + // ... ; body + // ... + // COND ; start of condition + // ... + // IFNE -> ; goes to LOOPHEAD + int condition_offset = GetSrcNoteOffset(sn, 0); + jsbytecode* conditionpc = pc + condition_offset; + + jssrcnote* sn2 = info().getNote(gsn, pc+1); + int offset = GetSrcNoteOffset(sn2, 0); + jsbytecode* ifne = pc + offset + 1; + MOZ_ASSERT(ifne > pc); + + // Verify that the IFNE goes back to a loophead op. + jsbytecode* loopHead = GetNextPc(pc); + MOZ_ASSERT(JSOp(*loopHead) == JSOP_LOOPHEAD); + MOZ_ASSERT(loopHead == ifne + GetJumpOffset(ifne)); + + jsbytecode* loopEntry = GetNextPc(loopHead); + bool canOsr = LoopEntryCanIonOsr(loopEntry); + bool osr = info().hasOsrAt(loopEntry); + + if (osr) { + MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc); + if (!preheader) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), preheader)); + if (!setCurrentAndSpecializePhis(preheader)) + return ControlStatus_Error; + } + + unsigned stackPhiCount = 0; + MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount); + if (!header) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), header)); + + jsbytecode* loophead = GetNextPc(pc); + jsbytecode* bodyStart = GetNextPc(loophead); + jsbytecode* bodyEnd = conditionpc; + jsbytecode* exitpc = GetNextPc(ifne); + if (!analyzeNewLoopTypes(header, bodyStart, exitpc)) + return ControlStatus_Error; + if (!pushLoop(CFGState::DO_WHILE_LOOP_BODY, conditionpc, header, osr, + loopHead, bodyStart, bodyStart, bodyEnd, exitpc, conditionpc)) + { + return ControlStatus_Error; + } + + CFGState& state = cfgStack_.back(); + state.loop.updatepc = conditionpc; + state.loop.updateEnd = ifne; + + if (!setCurrentAndSpecializePhis(header)) + return ControlStatus_Error; + if (!jsop_loophead(loophead)) + return ControlStatus_Error; + + pc = bodyStart; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::whileOrForInLoop(jssrcnote* sn) +{ + // while (cond) { } loops have the following structure: + // GOTO cond ; SRC_WHILE (offset to IFNE) + // LOOPHEAD + // ... + // cond: + // LOOPENTRY + // ... + // IFNE ; goes to LOOPHEAD + // for (x in y) { } loops are similar; the cond will be a MOREITER. + MOZ_ASSERT(SN_TYPE(sn) == SRC_FOR_OF || SN_TYPE(sn) == SRC_FOR_IN || SN_TYPE(sn) == SRC_WHILE); + int ifneOffset = GetSrcNoteOffset(sn, 0); + jsbytecode* ifne = pc + ifneOffset; + MOZ_ASSERT(ifne > pc); + + // Verify that the IFNE goes back to a loophead op. + MOZ_ASSERT(JSOp(*GetNextPc(pc)) == JSOP_LOOPHEAD); + MOZ_ASSERT(GetNextPc(pc) == ifne + GetJumpOffset(ifne)); + + jsbytecode* loopEntry = pc + GetJumpOffset(pc); + bool canOsr = LoopEntryCanIonOsr(loopEntry); + bool osr = info().hasOsrAt(loopEntry); + + if (osr) { + MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc); + if (!preheader) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), preheader)); + if (!setCurrentAndSpecializePhis(preheader)) + return ControlStatus_Error; + } + + unsigned stackPhiCount; + if (SN_TYPE(sn) == SRC_FOR_OF) + stackPhiCount = 2; + else if (SN_TYPE(sn) == SRC_FOR_IN) + stackPhiCount = 1; + else + stackPhiCount = 0; + + MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount); + if (!header) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), header)); + + // Skip past the JSOP_LOOPHEAD for the body start. + jsbytecode* loopHead = GetNextPc(pc); + jsbytecode* bodyStart = GetNextPc(loopHead); + jsbytecode* bodyEnd = pc + GetJumpOffset(pc); + jsbytecode* exitpc = GetNextPc(ifne); + jsbytecode* continuepc = pc; + if (!analyzeNewLoopTypes(header, bodyStart, exitpc)) + return ControlStatus_Error; + if (!pushLoop(CFGState::WHILE_LOOP_COND, ifne, header, osr, + loopHead, bodyEnd, bodyStart, bodyEnd, exitpc, continuepc)) + { + return ControlStatus_Error; + } + + // Parse the condition first. + if (!setCurrentAndSpecializePhis(header)) + return ControlStatus_Error; + if (!jsop_loophead(loopHead)) + return ControlStatus_Error; + + pc = bodyEnd; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::forLoop(JSOp op, jssrcnote* sn) +{ + // Skip the NOP. + MOZ_ASSERT(op == JSOP_NOP); + pc = GetNextPc(pc); + + jsbytecode* condpc = pc + GetSrcNoteOffset(sn, 0); + jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, 1); + jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 2); + jsbytecode* exitpc = GetNextPc(ifne); + + // for loops have the following structures: + // + // NOP or POP + // [GOTO cond | NOP] + // LOOPHEAD + // body: + // ; [body] + // [increment:] + // [{FRESHEN,RECREATE}LEXICALENV, if needed by a lexical env] + // ; [increment] + // [cond:] + // LOOPENTRY + // GOTO body + // + // If there is a condition (condpc != ifne), this acts similar to a while + // loop otherwise, it acts like a do-while loop. + // + // Note that currently Ion doesn't compile pushlexicalenv/poplexicalenv, + // necessary prerequisites to {freshen,recreate}lexicalenv. So the code + // below doesn't and needn't consider either op's implications. + jsbytecode* bodyStart = pc; + jsbytecode* bodyEnd = updatepc; + jsbytecode* loopEntry = condpc; + if (condpc != ifne) { + MOZ_ASSERT(JSOp(*bodyStart) == JSOP_GOTO); + MOZ_ASSERT(bodyStart + GetJumpOffset(bodyStart) == condpc); + bodyStart = GetNextPc(bodyStart); + } else { + // No loop condition, such as for(j = 0; ; j++) + if (op != JSOP_NOP) { + // If the loop starts with POP, we have to skip a NOP. + MOZ_ASSERT(JSOp(*bodyStart) == JSOP_NOP); + bodyStart = GetNextPc(bodyStart); + } + loopEntry = GetNextPc(bodyStart); + } + jsbytecode* loopHead = bodyStart; + MOZ_ASSERT(JSOp(*bodyStart) == JSOP_LOOPHEAD); + MOZ_ASSERT(ifne + GetJumpOffset(ifne) == bodyStart); + bodyStart = GetNextPc(bodyStart); + + bool osr = info().hasOsrAt(loopEntry); + bool canOsr = LoopEntryCanIonOsr(loopEntry); + + if (osr) { + MBasicBlock* preheader = newOsrPreheader(current, loopEntry, pc); + if (!preheader) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), preheader)); + if (!setCurrentAndSpecializePhis(preheader)) + return ControlStatus_Error; + } + + unsigned stackPhiCount = 0; + MBasicBlock* header = newPendingLoopHeader(current, loopEntry, osr, canOsr, stackPhiCount); + if (!header) + return ControlStatus_Error; + current->end(MGoto::New(alloc(), header)); + + // If there is no condition, we immediately parse the body. Otherwise, we + // parse the condition. + jsbytecode* stopAt; + CFGState::State initial; + if (condpc != ifne) { + pc = condpc; + stopAt = ifne; + initial = CFGState::FOR_LOOP_COND; + } else { + pc = bodyStart; + stopAt = bodyEnd; + initial = CFGState::FOR_LOOP_BODY; + } + + if (!analyzeNewLoopTypes(header, bodyStart, exitpc)) + return ControlStatus_Error; + if (!pushLoop(initial, stopAt, header, osr, + loopHead, pc, bodyStart, bodyEnd, exitpc, updatepc)) + { + return ControlStatus_Error; + } + + CFGState& state = cfgStack_.back(); + state.loop.condpc = (condpc != ifne) ? condpc : nullptr; + state.loop.updatepc = (updatepc != condpc) ? updatepc : nullptr; + if (state.loop.updatepc) + state.loop.updateEnd = condpc; + + if (!setCurrentAndSpecializePhis(header)) + return ControlStatus_Error; + if (!jsop_loophead(loopHead)) + return ControlStatus_Error; + + return ControlStatus_Jumped; +} + +int +IonBuilder::CmpSuccessors(const void* a, const void* b) +{ + const MBasicBlock* a0 = * (MBasicBlock * const*)a; + const MBasicBlock* b0 = * (MBasicBlock * const*)b; + if (a0->pc() == b0->pc()) + return 0; + + return (a0->pc() > b0->pc()) ? 1 : -1; +} + +IonBuilder::ControlStatus +IonBuilder::tableSwitch(JSOp op, jssrcnote* sn) +{ + // TableSwitch op contains the following data + // (length between data is JUMP_OFFSET_LEN) + // + // 0: Offset of default case + // 1: Lowest number in tableswitch + // 2: Highest number in tableswitch + // 3: Offset of case low + // 4: Offset of case low+1 + // .: ... + // .: Offset of case high + + MOZ_ASSERT(op == JSOP_TABLESWITCH); + MOZ_ASSERT(SN_TYPE(sn) == SRC_TABLESWITCH); + + // Pop input. + MDefinition* ins = current->pop(); + + // Get the default and exit pc + jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0); + jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc); + + MOZ_ASSERT(defaultpc > pc && defaultpc <= exitpc); + + // Get the low and high from the tableswitch + jsbytecode* pc2 = pc; + pc2 += JUMP_OFFSET_LEN; + int low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + int high = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + + // Create MIR instruction + MTableSwitch* tableswitch = MTableSwitch::New(alloc(), ins, low, high); + + // Create default case + MBasicBlock* defaultcase = newBlock(current, defaultpc); + if (!defaultcase) + return ControlStatus_Error; + + if (!tableswitch->addDefault(defaultcase)) + return ControlStatus_Error; + + if (!tableswitch->addBlock(defaultcase)) + return ControlStatus_Error; + + // Create cases + jsbytecode* casepc = nullptr; + for (int i = 0; i < high-low+1; i++) { + casepc = pc + GET_JUMP_OFFSET(pc2); + + MOZ_ASSERT(casepc >= pc && casepc <= exitpc); + MBasicBlock* caseblock; + + if (casepc == pc) { + // If the casepc equals the current pc, it is not a written case, + // but a filled gap. That way we can use a tableswitch instead of + // condswitch, even if not all numbers are consecutive. + // In that case this block goes to the default case + caseblock = newBlock(current, defaultpc); + if (!caseblock) + return ControlStatus_Error; + caseblock->end(MGoto::New(alloc(), defaultcase)); + if (!defaultcase->addPredecessor(alloc(), caseblock)) + return ControlStatus_Error; + } else { + // If this is an actual case (not filled gap), + // add this block to the list that still needs to get processed. + caseblock = newBlock(current, casepc); + if (!caseblock) + return ControlStatus_Error; + + if (!tableswitch->addBlock(caseblock)) + return ControlStatus_Error; + + // Add constant to indicate which case this is for use by + // processNextTableSwitchCase. + MConstant* constant = MConstant::New(alloc(), Int32Value(i + low)); + caseblock->add(constant); + } + + size_t caseIndex; + if (!tableswitch->addSuccessor(caseblock, &caseIndex)) + return ControlStatus_Error; + + if (!tableswitch->addCase(caseIndex)) + return ControlStatus_Error; + + pc2 += JUMP_OFFSET_LEN; + } + + // Move defaultcase to the end, to maintain RPO. + graph().moveBlockToEnd(defaultcase); + + MOZ_ASSERT(tableswitch->numCases() == (uint32_t)(high - low + 1)); + MOZ_ASSERT(tableswitch->numSuccessors() > 0); + + // Sort the list of blocks that still needs to get processed by pc + qsort(tableswitch->blocks(), tableswitch->numBlocks(), + sizeof(MBasicBlock*), CmpSuccessors); + + // Create info + ControlFlowInfo switchinfo(cfgStack_.length(), exitpc); + if (!switches_.append(switchinfo)) + return ControlStatus_Error; + + // Use a state to retrieve some information + CFGState state = CFGState::TableSwitch(exitpc, tableswitch); + + // Save the MIR instruction as last instruction of this block. + current->end(tableswitch); + + // If there is only one successor the block should stop at the end of the switch + // Else it should stop at the start of the next successor + if (tableswitch->numBlocks() > 1) + state.stopAt = tableswitch->getBlock(1)->pc(); + if (!setCurrentAndSpecializePhis(tableswitch->getBlock(0))) + return ControlStatus_Error; + + if (!cfgStack_.append(state)) + return ControlStatus_Error; + + pc = current->pc(); + return ControlStatus_Jumped; +} + +bool +IonBuilder::replaceTypeSet(MDefinition* subject, TemporaryTypeSet* type, MTest* test) +{ + if (type->unknown()) + return true; + + // Don't emit MFilterTypeSet if it doesn't improve the typeset. + if (subject->resultTypeSet()) { + if (subject->resultTypeSet()->equals(type)) + return true; + } else { + TemporaryTypeSet oldTypes(alloc_->lifoAlloc(), subject->type()); + if (oldTypes.equals(type)) + return true; + } + + MInstruction* replace = nullptr; + MDefinition* ins; + + for (uint32_t i = 0; i < current->stackDepth(); i++) { + ins = current->getSlot(i); + + // Instead of creating a new MFilterTypeSet, try to update the old one. + if (ins->isFilterTypeSet() && ins->getOperand(0) == subject && + ins->dependency() == test) + { + TemporaryTypeSet* intersect = + TypeSet::intersectSets(ins->resultTypeSet(), type, alloc_->lifoAlloc()); + if (!intersect) + return false; + + 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); + if (!replace) + return false; + + current->add(replace); + + // Make sure we don't hoist it above the MTest, we can use the + // 'dependency' of an MInstruction. This is normally used by + // Alias Analysis, but won't get overwritten, since this + // instruction doesn't have an AliasSet. + 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 true; +} + +bool +IonBuilder::detectAndOrStructure(MPhi* ins, bool* branchIsAnd) +{ + // Look for a triangle pattern: + // + // initialBlock + // / | + // branchBlock | + // \ | + // testBlock + // + // Where ins is a phi from testBlock which combines two values + // pushed onto the stack by initialBlock and branchBlock. + + 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; +} + +bool +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 true; +} + +bool +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 true; + + bool equal = ins->jsop() == JSOP_EQ || ins->jsop() == JSOP_STRICTEQ; + bool notEqual = ins->jsop() == JSOP_NE || ins->jsop() == JSOP_STRICTNE; + + if (notEqual) + trueBranch = !trueBranch; + + // Relational compares not supported. + if (!equal && !notEqual) + return true; + + MDefinition* subject = typeOf->input(); + TemporaryTypeSet* inputTypes = subject->resultTypeSet(); + + // Create temporary typeset equal to the type if there is no resultTypeSet. + TemporaryTypeSet tmp; + if (!inputTypes) { + if (subject->type() == MIRType::Value) + return true; + inputTypes = &tmp; + tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc()); + } + + if (inputTypes->unknown()) + return true; + + // Note: we cannot remove the AnyObject type in the false branch, + // since there are multiple ways to get an object. That is the reason + // for the 'trueBranch' test. + TemporaryTypeSet filter; + const JSAtomState& names = GetJitContext()->runtime->names(); + if (constant->toString() == TypeName(JSTYPE_VOID, 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_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 true; + } + + TemporaryTypeSet* type; + if (trueBranch) + type = TypeSet::intersectSets(&filter, inputTypes, alloc_->lifoAlloc()); + else + type = TypeSet::removeSet(inputTypes, &filter, alloc_->lifoAlloc()); + + if (!type) + return false; + + return replaceTypeSet(subject, type, test); +} + +bool +IonBuilder::improveTypesAtNullOrUndefinedCompare(MCompare* ins, bool trueBranch, MTest* test) +{ + MOZ_ASSERT(ins->compareType() == MCompare::Compare_Undefined || + ins->compareType() == MCompare::Compare_Null); + + // altersUndefined/Null represents if we can filter/set Undefined/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())); + + // Create temporary typeset equal to the type if there is no resultTypeSet. + TemporaryTypeSet tmp; + if (!inputTypes) { + if (subject->type() == MIRType::Value) + return true; + inputTypes = &tmp; + tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc()); + } + + if (inputTypes->unknown()) + return true; + + TemporaryTypeSet* type; + + // Decide if we need to filter the type or set it. + if ((op == JSOP_STRICTEQ || op == JSOP_EQ) ^ trueBranch) { + // Remove undefined/null + 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 { + // Set undefined/null. + TemporaryTypeSet base; + if (altersUndefined) { + base.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc()); + // If TypeSet emulates undefined, then we cannot filter the objects. + 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 false; + + return replaceTypeSet(subject, type, test); +} + +bool +IonBuilder::improveTypesAtTest(MDefinition* ins, bool trueBranch, MTest* test) +{ + // We explore the test condition to try and deduce as much type information + // as possible. + + // All branches of this switch that don't want to fall through to the + // default behavior must return. The default behavior assumes that a true + // test means the incoming ins is not null or undefined and that a false + // tests means it's one of null, undefined, false, 0, "", and objects + // emulating undefined + switch (ins->op()) { + case MDefinition::Op_Not: + return improveTypesAtTest(ins->toNot()->getOperand(0), !trueBranch, test); + case MDefinition::Op_IsObject: { + MDefinition* subject = ins->getOperand(0); + TemporaryTypeSet* oldType = subject->resultTypeSet(); + + // Create temporary typeset equal to the type if there is no resultTypeSet. + TemporaryTypeSet tmp; + if (!oldType) { + if (subject->type() == MIRType::Value) + return true; + oldType = &tmp; + tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc()); + } + + if (oldType->unknown()) + return true; + + TemporaryTypeSet* type = nullptr; + if (trueBranch) + type = oldType->cloneObjectsOnly(alloc_->lifoAlloc()); + else + type = oldType->cloneWithoutObjects(alloc_->lifoAlloc()); + + if (!type) + return false; + + return replaceTypeSet(subject, type, test); + } + case MDefinition::Op_Phi: { + bool branchIsAnd = true; + if (!detectAndOrStructure(ins->toPhi(), &branchIsAnd)) { + // Just fall through to the default behavior. + break; + } + + // Now we have detected the triangular structure and determined if it + // was an AND or an OR. + if (branchIsAnd) { + if (trueBranch) { + if (!improveTypesAtTest(ins->toPhi()->getOperand(0), true, test)) + return false; + if (!improveTypesAtTest(ins->toPhi()->getOperand(1), true, test)) + return false; + } + } else { + /* + * if (a || b) { + * ... + * } else { + * ... + * } + * + * If we have a statements like the one described above, + * And we are in the else branch of it. It amounts to: + * if (!(a || b)) and being in the true branch. + * + * Simplifying, we have (!a && !b) + * In this case we can use the same logic we use for branchIsAnd + * + */ + if (!trueBranch) { + if (!improveTypesAtTest(ins->toPhi()->getOperand(0), false, test)) + return false; + if (!improveTypesAtTest(ins->toPhi()->getOperand(1), false, test)) + return false; + } + } + return true; + } + + case MDefinition::Op_Compare: + return improveTypesAtCompare(ins->toCompare(), trueBranch, test); + + default: + break; + } + + // By default MTest tests ToBoolean(input). As a result in the true branch we can filter + // undefined and null. In false branch we can only encounter undefined, null, false, 0, "" + // and objects that emulate undefined. + + TemporaryTypeSet* oldType = ins->resultTypeSet(); + TemporaryTypeSet* type; + + // Create temporary typeset equal to the type if there is no resultTypeSet. + TemporaryTypeSet tmp; + if (!oldType) { + if (ins->type() == MIRType::Value) + return true; + oldType = &tmp; + tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(ins->type())), alloc_->lifoAlloc()); + } + + // If ins does not have a typeset we return as we cannot optimize. + if (oldType->unknown()) + return true; + + // Decide either to set or remove. + 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()); // ToBoolean(undefined) == false + base.addType(TypeSet::NullType(), alloc_->lifoAlloc()); // ToBoolean(null) == false + base.addType(TypeSet::BooleanType(), alloc_->lifoAlloc()); // ToBoolean(false) == false + base.addType(TypeSet::Int32Type(), alloc_->lifoAlloc()); // ToBoolean(0) == false + base.addType(TypeSet::DoubleType(), alloc_->lifoAlloc()); // ToBoolean(0.0) == false + base.addType(TypeSet::StringType(), alloc_->lifoAlloc()); // ToBoolean("") == false + + // If the typeset does emulate undefined, then we cannot filter out + // objects. + if (oldType->maybeEmulatesUndefined(constraints())) + base.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc()); + + type = TypeSet::intersectSets(&base, oldType, alloc_->lifoAlloc()); + } + + return type && replaceTypeSet(ins, type, test); +} + +bool +IonBuilder::jsop_label() +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_LABEL); + + jsbytecode* endpc = pc + GET_JUMP_OFFSET(pc); + MOZ_ASSERT(endpc > pc); + + ControlFlowInfo label(cfgStack_.length(), endpc); + if (!labels_.append(label)) + return false; + + return cfgStack_.append(CFGState::Label(endpc)); +} + +bool +IonBuilder::jsop_condswitch() +{ + // CondSwitch op looks as follows: + // condswitch [length +exit_pc; first case offset +next-case ] + // { + // { + // ... any code ... + // case (+jump) [pcdelta offset +next-case] + // }+ + // default (+jump) + // ... jump targets ... + // } + // + // The default case is always emitted even if there is no default case in + // the source. The last case statement pcdelta source note might have a 0 + // offset on the last case (not all the time). + // + // A conditional evaluate the condition of each case and compare it to the + // switch value with a strict equality. Cases conditions are iterated + // linearly until one is matching. If one case succeeds, the flow jumps into + // the corresponding body block. The body block might alias others and + // might continue in the next body block if the body is not terminated with + // a break. + // + // Algorithm: + // 1/ Loop over the case chain to reach the default target + // & Estimate the number of uniq bodies. + // 2/ Generate code for all cases (see processCondSwitchCase). + // 3/ Generate code for all bodies (see processCondSwitchBody). + + MOZ_ASSERT(JSOp(*pc) == JSOP_CONDSWITCH); + jssrcnote* sn = info().getNote(gsn, pc); + MOZ_ASSERT(SN_TYPE(sn) == SRC_CONDSWITCH); + + // Get the exit pc + jsbytecode* exitpc = pc + GetSrcNoteOffset(sn, 0); + jsbytecode* firstCase = pc + GetSrcNoteOffset(sn, 1); + + // Iterate all cases in the conditional switch. + // - Stop at the default case. (always emitted after the last case) + // - Estimate the number of uniq bodies. This estimation might be off by 1 + // if the default body alias a case body. + jsbytecode* curCase = firstCase; + jsbytecode* lastTarget = GetJumpOffset(curCase) + curCase; + size_t nbBodies = 2; // default target and the first body. + + MOZ_ASSERT(pc < curCase && curCase <= exitpc); + while (JSOp(*curCase) == JSOP_CASE) { + // Fetch the next case. + jssrcnote* caseSn = info().getNote(gsn, curCase); + MOZ_ASSERT(caseSn && SN_TYPE(caseSn) == SRC_NEXTCASE); + ptrdiff_t off = GetSrcNoteOffset(caseSn, 0); + MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(curCase)) == JSOP_JUMPTARGET); + curCase = off ? curCase + off : GetNextPc(GetNextPc(curCase)); + MOZ_ASSERT(pc < curCase && curCase <= exitpc); + + // Count non-aliased cases. + jsbytecode* curTarget = GetJumpOffset(curCase) + curCase; + if (lastTarget < curTarget) + nbBodies++; + lastTarget = curTarget; + } + + // The current case now be the default case which jump to the body of the + // default case, which might be behind the last target. + MOZ_ASSERT(JSOp(*curCase) == JSOP_DEFAULT); + jsbytecode* defaultTarget = GetJumpOffset(curCase) + curCase; + MOZ_ASSERT(curCase < defaultTarget && defaultTarget <= exitpc); + + // Allocate the current graph state. + CFGState state = CFGState::CondSwitch(this, exitpc, defaultTarget); + if (!state.condswitch.bodies || !state.condswitch.bodies->init(alloc(), nbBodies)) + return ControlStatus_Error; + + // We loop on case conditions with processCondSwitchCase. + MOZ_ASSERT(JSOp(*firstCase) == JSOP_CASE); + state.stopAt = firstCase; + state.state = CFGState::COND_SWITCH_CASE; + + return cfgStack_.append(state); +} + +IonBuilder::CFGState +IonBuilder::CFGState::CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget) +{ + CFGState state; + state.state = COND_SWITCH_CASE; + state.stopAt = nullptr; + state.condswitch.bodies = (FixedList<MBasicBlock*>*)builder->alloc_->allocate( + sizeof(FixedList<MBasicBlock*>)); + state.condswitch.currentIdx = 0; + state.condswitch.defaultTarget = defaultTarget; + state.condswitch.defaultIdx = uint32_t(-1); + state.condswitch.exitpc = exitpc; + state.condswitch.breaks = nullptr; + return state; +} + +IonBuilder::CFGState +IonBuilder::CFGState::Label(jsbytecode* exitpc) +{ + CFGState state; + state.state = LABEL; + state.stopAt = exitpc; + state.label.breaks = nullptr; + return state; +} + +IonBuilder::CFGState +IonBuilder::CFGState::Try(jsbytecode* exitpc, MBasicBlock* successor) +{ + CFGState state; + state.state = TRY; + state.stopAt = exitpc; + state.try_.successor = successor; + return state; +} + +IonBuilder::ControlStatus +IonBuilder::processCondSwitchCase(CFGState& state) +{ + MOZ_ASSERT(state.state == CFGState::COND_SWITCH_CASE); + MOZ_ASSERT(!state.condswitch.breaks); + MOZ_ASSERT(current); + MOZ_ASSERT(JSOp(*pc) == JSOP_CASE); + FixedList<MBasicBlock*>& bodies = *state.condswitch.bodies; + jsbytecode* defaultTarget = state.condswitch.defaultTarget; + uint32_t& currentIdx = state.condswitch.currentIdx; + jsbytecode* lastTarget = currentIdx ? bodies[currentIdx - 1]->pc() : nullptr; + + // Fetch the following case in which we will continue. + jssrcnote* sn = info().getNote(gsn, pc); + ptrdiff_t off = GetSrcNoteOffset(sn, 0); + MOZ_ASSERT_IF(off == 0, JSOp(*GetNextPc(pc)) == JSOP_JUMPTARGET); + jsbytecode* casePc = off ? pc + off : GetNextPc(GetNextPc(pc)); + bool caseIsDefault = JSOp(*casePc) == JSOP_DEFAULT; + MOZ_ASSERT(JSOp(*casePc) == JSOP_CASE || caseIsDefault); + + // Allocate the block of the matching case. + bool bodyIsNew = false; + MBasicBlock* bodyBlock = nullptr; + jsbytecode* bodyTarget = pc + GetJumpOffset(pc); + if (lastTarget < bodyTarget) { + // If the default body is in the middle or aliasing the current target. + if (lastTarget < defaultTarget && defaultTarget <= bodyTarget) { + MOZ_ASSERT(state.condswitch.defaultIdx == uint32_t(-1)); + state.condswitch.defaultIdx = currentIdx; + bodies[currentIdx] = nullptr; + // If the default body does not alias any and it would be allocated + // later and stored in the defaultIdx location. + if (defaultTarget < bodyTarget) + currentIdx++; + } + + bodyIsNew = true; + // Pop switch and case operands. + bodyBlock = newBlockPopN(current, bodyTarget, 2); + bodies[currentIdx++] = bodyBlock; + } else { + // This body alias the previous one. + MOZ_ASSERT(lastTarget == bodyTarget); + MOZ_ASSERT(currentIdx > 0); + bodyBlock = bodies[currentIdx - 1]; + } + + if (!bodyBlock) + return ControlStatus_Error; + + lastTarget = bodyTarget; + + // Allocate the block of the non-matching case. This can either be a normal + // case or the default case. + bool caseIsNew = false; + MBasicBlock* caseBlock = nullptr; + if (!caseIsDefault) { + caseIsNew = true; + // Pop the case operand. + caseBlock = newBlockPopN(current, GetNextPc(pc), 1); + } else { + // The non-matching case is the default case, which jump directly to its + // body. Skip the creation of a default case block and directly create + // the default body if it does not alias any previous body. + + if (state.condswitch.defaultIdx == uint32_t(-1)) { + // The default target is the last target. + MOZ_ASSERT(lastTarget < defaultTarget); + state.condswitch.defaultIdx = currentIdx++; + caseIsNew = true; + } else if (bodies[state.condswitch.defaultIdx] == nullptr) { + // The default target is in the middle and it does not alias any + // case target. + MOZ_ASSERT(defaultTarget < lastTarget); + caseIsNew = true; + } else { + // The default target is in the middle and it alias a case target. + MOZ_ASSERT(defaultTarget <= lastTarget); + caseBlock = bodies[state.condswitch.defaultIdx]; + } + + // Allocate and register the default body. + if (caseIsNew) { + // Pop the case & switch operands. + caseBlock = newBlockPopN(current, defaultTarget, 2); + bodies[state.condswitch.defaultIdx] = caseBlock; + } + } + + if (!caseBlock) + return ControlStatus_Error; + + // Terminate the last case condition block by emitting the code + // corresponding to JSOP_CASE bytecode. + if (bodyBlock != caseBlock) { + MDefinition* caseOperand = current->pop(); + MDefinition* switchOperand = current->peek(-1); + + if (!jsop_compare(JSOP_STRICTEQ, switchOperand, caseOperand)) + return ControlStatus_Error; + MInstruction* cmpResult = current->pop()->toInstruction(); + MOZ_ASSERT(!cmpResult->isEffectful()); + current->end(newTest(cmpResult, bodyBlock, caseBlock)); + + // Add last case as predecessor of the body if the body is aliasing + // the previous case body. + if (!bodyIsNew && !bodyBlock->addPredecessorPopN(alloc(), current, 1)) + return ControlStatus_Error; + + // Add last case as predecessor of the non-matching case if the + // non-matching case is an aliased default case. We need to pop the + // switch operand as we skip the default case block and use the default + // body block directly. + MOZ_ASSERT_IF(!caseIsNew, caseIsDefault); + if (!caseIsNew && !caseBlock->addPredecessorPopN(alloc(), current, 1)) + return ControlStatus_Error; + } else { + // The default case alias the last case body. + MOZ_ASSERT(caseIsDefault); + current->pop(); // Case operand + current->pop(); // Switch operand + current->end(MGoto::New(alloc(), bodyBlock)); + if (!bodyIsNew && !bodyBlock->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + if (caseIsDefault) { + // The last case condition is finished. Loop in processCondSwitchBody, + // with potential stops in processSwitchBreak. Check that the bodies + // fixed list is over-estimate by at most 1, and shrink the size such as + // length can be used as an upper bound while iterating bodies. + MOZ_ASSERT(currentIdx == bodies.length() || currentIdx + 1 == bodies.length()); + bodies.shrink(bodies.length() - currentIdx); + + // Handle break statements in processSwitchBreak while processing + // bodies. + ControlFlowInfo breakInfo(cfgStack_.length() - 1, state.condswitch.exitpc); + if (!switches_.append(breakInfo)) + return ControlStatus_Error; + + // Jump into the first body. + currentIdx = 0; + setCurrent(nullptr); + state.state = CFGState::COND_SWITCH_BODY; + return processCondSwitchBody(state); + } + + // Continue until the case condition. + if (!setCurrentAndSpecializePhis(caseBlock)) + return ControlStatus_Error; + pc = current->pc(); + state.stopAt = casePc; + return ControlStatus_Jumped; +} + +IonBuilder::ControlStatus +IonBuilder::processCondSwitchBody(CFGState& state) +{ + MOZ_ASSERT(state.state == CFGState::COND_SWITCH_BODY); + MOZ_ASSERT(pc <= state.condswitch.exitpc); + FixedList<MBasicBlock*>& bodies = *state.condswitch.bodies; + uint32_t& currentIdx = state.condswitch.currentIdx; + + MOZ_ASSERT(currentIdx <= bodies.length()); + if (currentIdx == bodies.length()) { + MOZ_ASSERT_IF(current, pc == state.condswitch.exitpc); + return processSwitchEnd(state.condswitch.breaks, state.condswitch.exitpc); + } + + // Get the next body + MBasicBlock* nextBody = bodies[currentIdx++]; + MOZ_ASSERT_IF(current, pc == nextBody->pc()); + + // Fix the reverse post-order iteration. + graph().moveBlockToEnd(nextBody); + + // The last body continue into the new one. + if (current) { + current->end(MGoto::New(alloc(), nextBody)); + if (!nextBody->addPredecessor(alloc(), current)) + return ControlStatus_Error; + } + + // Continue in the next body. + if (!setCurrentAndSpecializePhis(nextBody)) + return ControlStatus_Error; + pc = current->pc(); + + if (currentIdx < bodies.length()) + state.stopAt = bodies[currentIdx]->pc(); + else + state.stopAt = state.condswitch.exitpc; + return ControlStatus_Jumped; +} + +bool +IonBuilder::jsop_andor(JSOp op) +{ + MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR); + + jsbytecode* rhsStart = pc + CodeSpec[op].length; + jsbytecode* joinStart = pc + GetJumpOffset(pc); + MOZ_ASSERT(joinStart > pc); + + // We have to leave the LHS on the stack. + MDefinition* lhs = current->peek(-1); + + MBasicBlock* evalLhs = newBlock(current, joinStart); + MBasicBlock* evalRhs = newBlock(current, rhsStart); + if (!evalLhs || !evalRhs) + return false; + + MTest* test = (op == JSOP_AND) + ? newTest(lhs, evalRhs, evalLhs) + : newTest(lhs, evalLhs, evalRhs); + current->end(test); + + // Create the lhs block and specialize. + if (!setCurrentAndSpecializePhis(evalLhs)) + return false; + + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return false; + + // Create the rhs block. + if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs))) + return false; + + if (!setCurrentAndSpecializePhis(evalRhs)) + return false; + + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return false; + + return true; +} + +bool +IonBuilder::jsop_dup2() +{ + uint32_t lhsSlot = current->stackDepth() - 2; + uint32_t rhsSlot = current->stackDepth() - 1; + current->pushSlot(lhsSlot); + current->pushSlot(rhsSlot); + return true; +} + +bool +IonBuilder::jsop_loophead(jsbytecode* pc) +{ + assertValidLoopHeadOp(pc); + + current->add(MInterruptCheck::New(alloc())); + insertRecompileCheck(); + + return true; +} + +bool +IonBuilder::jsop_ifeq(JSOp op) +{ + // IFEQ always has a forward offset. + jsbytecode* trueStart = pc + CodeSpec[op].length; + jsbytecode* falseStart = pc + GetJumpOffset(pc); + MOZ_ASSERT(falseStart > pc); + + // We only handle cases that emit source notes. + jssrcnote* sn = info().getNote(gsn, pc); + if (!sn) + return abort("expected sourcenote"); + + MDefinition* ins = current->pop(); + + // Create true and false branches. + MBasicBlock* ifTrue = newBlock(current, trueStart); + MBasicBlock* ifFalse = newBlock(current, falseStart); + if (!ifTrue || !ifFalse) + return false; + + MTest* test = newTest(ins, ifTrue, ifFalse); + current->end(test); + + // The bytecode for if/ternary gets emitted either like this: + // + // IFEQ X ; src note (IF_ELSE, COND) points to the GOTO + // ... + // GOTO Z + // X: ... ; else/else if + // ... + // Z: ; join + // + // Or like this: + // + // IFEQ X ; src note (IF) has no offset + // ... + // Z: ... ; join + // + // We want to parse the bytecode as if we were parsing the AST, so for the + // IF_ELSE/COND cases, we use the source note and follow the GOTO. For the + // IF case, the IFEQ offset is the join point. + switch (SN_TYPE(sn)) { + case SRC_IF: + if (!cfgStack_.append(CFGState::If(falseStart, test))) + return false; + break; + + case SRC_IF_ELSE: + case SRC_COND: + { + // Infer the join point from the JSOP_GOTO[X] sitting here, then + // assert as we much we can that this is the right GOTO. + jsbytecode* trueEnd = pc + GetSrcNoteOffset(sn, 0); + MOZ_ASSERT(trueEnd > pc); + MOZ_ASSERT(trueEnd < falseStart); + MOZ_ASSERT(JSOp(*trueEnd) == JSOP_GOTO); + MOZ_ASSERT(!info().getNote(gsn, trueEnd)); + + jsbytecode* falseEnd = trueEnd + GetJumpOffset(trueEnd); + MOZ_ASSERT(falseEnd > trueEnd); + MOZ_ASSERT(falseEnd >= falseStart); + + if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test))) + return false; + break; + } + + default: + MOZ_CRASH("unexpected source note type"); + } + + // Switch to parsing the true branch. Note that no PC update is needed, + // it's the next instruction. + if (!setCurrentAndSpecializePhis(ifTrue)) + return false; + + // Filter the types in the true branch. + if (!improveTypesAtTest(test->getOperand(0), test->ifTrue() == current, test)) + return false; + + return true; +} + +bool +IonBuilder::jsop_try() +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_TRY); + + // Try-finally is not yet supported. + if (analysis().hasTryFinally()) + return abort("Has try-finally"); + + // Try-catch within inline frames is not yet supported. + MOZ_ASSERT(!isInlineBuilder()); + + // Try-catch during the arguments usage analysis is not yet supported. Code + // accessing the arguments within the 'catch' block is not accounted for. + if (info().analysisMode() == Analysis_ArgumentsUsage) + return abort("Try-catch during arguments usage analysis"); + + graph().setHasTryBlock(); + + jssrcnote* sn = info().getNote(gsn, pc); + MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY); + + // Get the pc of the last instruction in the try block. It's a JSOP_GOTO to + // jump over the catch block. + jsbytecode* endpc = pc + GetSrcNoteOffset(sn, 0); + MOZ_ASSERT(JSOp(*endpc) == JSOP_GOTO); + MOZ_ASSERT(GetJumpOffset(endpc) > 0); + + jsbytecode* afterTry = endpc + GetJumpOffset(endpc); + + // If controlflow in the try body is terminated (by a return or throw + // statement), the code after the try-statement may still be reachable + // via the catch block (which we don't compile) and OSR can enter it. + // For example: + // + // try { + // throw 3; + // } catch(e) { } + // + // for (var i=0; i<1000; i++) {} + // + // To handle this, we create two blocks: one for the try block and one + // for the code following the try-catch statement. Both blocks are + // connected to the graph with an MGotoWithFake instruction that always + // jumps to the try block. This ensures the successor block always has a + // predecessor. + // + // If the code after the try block is unreachable (control flow in both the + // try and catch blocks is terminated), only create the try block, to avoid + // parsing unreachable code. + + MBasicBlock* tryBlock = newBlock(current, GetNextPc(pc)); + if (!tryBlock) + return false; + + MBasicBlock* successor; + if (analysis().maybeInfo(afterTry)) { + successor = newBlock(current, afterTry); + if (!successor) + return false; + + current->end(MGotoWithFake::New(alloc(), tryBlock, successor)); + } else { + successor = nullptr; + current->end(MGoto::New(alloc(), tryBlock)); + } + + if (!cfgStack_.append(CFGState::Try(endpc, successor))) + return false; + + // The baseline compiler should not attempt to enter the catch block + // via OSR. + MOZ_ASSERT(info().osrPc() < endpc || info().osrPc() >= afterTry); + + // Start parsing the try block. + return setCurrentAndSpecializePhis(tryBlock); +} + +IonBuilder::ControlStatus +IonBuilder::processReturn(JSOp op) +{ + MDefinition* def; + switch (op) { + case JSOP_RETURN: + // Return the last instruction. + def = current->pop(); + break; + + case JSOP_RETRVAL: + // Return undefined eagerly if script doesn't use return value. + 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 ControlStatus_Error; + + // Make sure no one tries to use this block now. + setCurrent(nullptr); + return processControlEnd(); +} + +IonBuilder::ControlStatus +IonBuilder::processThrow() +{ + MDefinition* def = current->pop(); + + // MThrow is not marked as effectful. This means when it throws and we + // are inside a try block, we could use an earlier resume point and this + // resume point may not be up-to-date, for example: + // + // (function() { + // try { + // var x = 1; + // foo(); // resume point + // x = 2; + // throw foo; + // } catch(e) { + // print(x); + // } + // ])(); + // + // If we use the resume point after the call, this will print 1 instead + // of 2. To fix this, we create a resume point right before the MThrow. + // + // Note that this is not a problem for instructions other than MThrow + // because they are either marked as effectful (have their own resume + // point) or cannot throw a catchable exception. + // + // We always install this resume point (instead of only when the function + // has a try block) in order to handle the Debugger onExceptionUnwind + // hook. When we need to handle the hook, we bail out to baseline right + // after the throw and propagate the exception when debug mode is on. This + // is opposed to the normal behavior of resuming directly in the + // associated catch block. + MNop* nop = MNop::New(alloc()); + current->add(nop); + + if (!resumeAfter(nop)) + return ControlStatus_Error; + + MThrow* ins = MThrow::New(alloc(), def); + current->end(ins); + + // Make sure no one tries to use this block now. + setCurrent(nullptr); + return processControlEnd(); +} + +void +IonBuilder::pushConstant(const Value& v) +{ + current->push(constant(v)); +} + +bool +IonBuilder::bitnotTrySpecialized(bool* emitted, MDefinition* input) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a specialized bitnot instruction based on the input type + // of the operand. + + if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::Symbol)) + return true; + + MBitNot* ins = MBitNot::New(alloc(), input); + ins->setSpecialization(MIRType::Int32); + + current->add(ins); + current->push(ins); + + *emitted = true; + return true; +} + +bool +IonBuilder::jsop_bitnot() +{ + bool emitted = false; + + MDefinition* input = current->pop(); + + if (!forceInlineCaches()) { + if (!bitnotTrySpecialized(&emitted, input) || emitted) + return emitted; + } + + if (!arithTrySharedStub(&emitted, JSOP_BITNOT, nullptr, input) || emitted) + return emitted; + + // Not possible to optimize. Do a slow vm call. + MBitNot* ins = MBitNot::New(alloc(), input); + + current->add(ins); + current->push(ins); + MOZ_ASSERT(ins->isEffectful()); + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_bitop(JSOp op) +{ + // Pop inputs. + 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() && !resumeAfter(ins)) + return false; + + return true; +} + +MDefinition::Opcode +JSOpToMDefinition(JSOp op) +{ + switch (op) { + case JSOP_ADD: + return MDefinition::Op_Add; + case JSOP_SUB: + return MDefinition::Op_Sub; + case JSOP_MUL: + return MDefinition::Op_Mul; + case JSOP_DIV: + return MDefinition::Op_Div; + case JSOP_MOD: + return MDefinition::Op_Mod; + default: + MOZ_CRASH("unexpected binary opcode"); + } +} + +bool +IonBuilder::binaryArithTryConcat(bool* emitted, JSOp op, MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to convert an addition into a concat operation if the inputs + // indicate this might be a concatenation. + + // Only try to replace this with concat when we have an addition. + if (op != JSOP_ADD) + return true; + + trackOptimizationAttempt(TrackedStrategy::BinaryArith_Concat); + + // Make sure one of the inputs is a string. + if (left->type() != MIRType::String && right->type() != MIRType::String) { + trackOptimizationOutcome(TrackedOutcome::OperandNotString); + return true; + } + + // The none-string input (if present) should be atleast a numerical type. + // Which we can easily coerce to string. + if (right->type() != MIRType::String && !IsNumberType(right->type())) { + trackOptimizationOutcome(TrackedOutcome::OperandNotStringOrNumber); + return true; + } + if (left->type() != MIRType::String && !IsNumberType(left->type())) { + trackOptimizationOutcome(TrackedOutcome::OperandNotStringOrNumber); + return true; + } + + MConcat* ins = MConcat::New(alloc(), left, right); + current->add(ins); + current->push(ins); + + if (!maybeInsertResume()) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::powTrySpecialized(bool* emitted, MDefinition* base, MDefinition* power, + MIRType outputType) +{ + // Typechecking. + MDefinition* output = nullptr; + MIRType baseType = base->type(); + MIRType powerType = power->type(); + + if (outputType != MIRType::Int32 && outputType != MIRType::Double) + return true; + if (!IsNumberType(baseType)) + return true; + if (!IsNumberType(powerType)) + return true; + + if (powerType == MIRType::Float32) + powerType = MIRType::Double; + + MPow* pow = MPow::New(alloc(), base, power, powerType); + current->add(pow); + output = pow; + + // Cast to the right type + if (outputType == MIRType::Int32 && output->type() != MIRType::Int32) { + MToInt32* toInt = MToInt32::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 true; +} + +static inline bool +SimpleArithOperand(MDefinition* op) +{ + return !op->mightBeType(MIRType::Object) + && !op->mightBeType(MIRType::String) + && !op->mightBeType(MIRType::Symbol) + && !op->mightBeType(MIRType::MagicOptimizedArguments) + && !op->mightBeType(MIRType::MagicHole) + && !op->mightBeType(MIRType::MagicIsConstructing); +} + +bool +IonBuilder::binaryArithTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a specialized binary instruction based on the input types + // of the operands. + + trackOptimizationAttempt(TrackedStrategy::BinaryArith_SpecializedTypes); + + // Anything complex - strings, symbols, and objects - are not specialized + if (!SimpleArithOperand(left) || !SimpleArithOperand(right)) { + trackOptimizationOutcome(TrackedOutcome::OperandNotSimpleArith); + return true; + } + + // One of the inputs need to be a number. + if (!IsNumberType(left->type()) && !IsNumberType(right->type())) { + trackOptimizationOutcome(TrackedOutcome::OperandNotNumber); + return true; + } + + MDefinition::Opcode defOp = JSOpToMDefinition(op); + MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), defOp, left, right); + ins->setNumberSpecialization(alloc(), inspector, pc); + + if (op == JSOP_ADD || op == JSOP_MUL) + ins->setCommutative(); + + current->add(ins); + current->push(ins); + + MOZ_ASSERT(!ins->isEffectful()); + if (!maybeInsertResume()) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::binaryArithTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, + MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a specialized binary instruction speculating the + // type using the baseline caches. + + trackOptimizationAttempt(TrackedStrategy::BinaryArith_SpecializedOnBaselineTypes); + + MIRType specialization = inspector->expectedBinaryArithSpecialization(pc); + if (specialization == MIRType::None) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return true; + } + + MDefinition::Opcode def_op = JSOpToMDefinition(op); + MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), def_op, left, right); + ins->setSpecialization(specialization); + + current->add(ins); + current->push(ins); + + MOZ_ASSERT(!ins->isEffectful()); + if (!maybeInsertResume()) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::arithTrySharedStub(bool* emitted, JSOp op, + MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + JSOp actualOp = JSOp(*pc); + + // Try to emit a shared stub cache. + + if (JitOptions.disableSharedStubs) + return true; + + // The actual jsop 'jsop_pos' is not supported yet. + if (actualOp == JSOP_POS) + return true; + + // FIXME: The JSOP_BITNOT path doesn't track optimizations yet. + if (actualOp != JSOP_BITNOT) { + trackOptimizationAttempt(TrackedStrategy::BinaryArith_SharedCache); + trackOptimizationSuccess(); + } + + 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 = MUnarySharedStub::New(alloc(), right); + break; + case JSOP_ADD: + case JSOP_SUB: + case JSOP_MUL: + case JSOP_DIV: + case JSOP_MOD: + case JSOP_POW: + stub = MBinarySharedStub::New(alloc(), left, right); + break; + default: + MOZ_CRASH("unsupported arith"); + } + + current->add(stub); + current->push(stub); + + // Decrease type from 'any type' to 'empty type' when one of the operands + // is 'empty typed'. + maybeMarkEmpty(stub); + + if (!resumeAfter(stub)) + return false; + + *emitted = true; + return true; +} + +bool +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()) { + if (!binaryArithTryConcat(&emitted, op, left, right) || emitted) + return emitted; + + if (!binaryArithTrySpecialized(&emitted, op, left, right) || emitted) + return emitted; + + if (!binaryArithTrySpecializedOnBaselineInspector(&emitted, op, left, right) || emitted) + return emitted; + } + + if (!arithTrySharedStub(&emitted, op, left, right) || emitted) + return emitted; + + // Not possible to optimize. Do a slow vm call. + trackOptimizationAttempt(TrackedStrategy::BinaryArith_Call); + trackOptimizationSuccess(); + + MDefinition::Opcode def_op = JSOpToMDefinition(op); + MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), def_op, left, right); + + // Decrease type from 'any type' to 'empty type' when one of the operands + // is 'empty typed'. + maybeMarkEmpty(ins); + + current->add(ins); + current->push(ins); + MOZ_ASSERT(ins->isEffectful()); + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_binary_arith(JSOp op) +{ + MDefinition* right = current->pop(); + MDefinition* left = current->pop(); + + return jsop_binary_arith(op, left, right); +} + + +bool +IonBuilder::jsop_pow() +{ + MDefinition* exponent = current->pop(); + MDefinition* base = current->pop(); + + bool emitted = false; + + if (!forceInlineCaches()) { + if (!powTrySpecialized(&emitted, base, exponent, MIRType::Double) || emitted) + return emitted; + } + + if (!arithTrySharedStub(&emitted, JSOP_POW, base, exponent) || emitted) + return emitted; + + // For now, use MIRType::Double, as a safe cover-all. See bug 1188079. + MPow* pow = MPow::New(alloc(), base, exponent, MIRType::Double); + current->add(pow); + current->push(pow); + return true; +} + +bool +IonBuilder::jsop_pos() +{ + if (IsNumberType(current->peek(-1)->type())) { + // Already int32 or double. Set the operand as implicitly used so it + // doesn't get optimized out if it has no other uses, as we could bail + // out. + current->peek(-1)->setImplicitlyUsedUnchecked(); + return true; + } + + // Compile +x as x * 1. + MDefinition* value = current->pop(); + MConstant* one = MConstant::New(alloc(), Int32Value(1)); + current->add(one); + + return jsop_binary_arith(JSOP_MUL, value, one); +} + +bool +IonBuilder::jsop_neg() +{ + // Since JSOP_NEG does not use a slot, we cannot push the MConstant. + // The MConstant is therefore passed to JSOP_MUL without slot traffic. + MConstant* negator = MConstant::New(alloc(), Int32Value(-1)); + current->add(negator); + + MDefinition* right = current->pop(); + + return jsop_binary_arith(JSOP_MUL, negator, right); +} + +bool +IonBuilder::jsop_tostring() +{ + if (current->peek(-1)->type() == MIRType::String) + return true; + + MDefinition* value = current->pop(); + MToString* ins = MToString::New(alloc(), value); + current->add(ins); + current->push(ins); + MOZ_ASSERT(!ins->isEffectful()); + return true; +} + +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::InliningStatus +IonBuilder::inlineScriptedCall(CallInfo& callInfo, JSFunction* target) +{ + MOZ_ASSERT(target->hasScript()); + MOZ_ASSERT(IsIonInlinablePC(pc)); + + MBasicBlock::BackupPoint backup(current); + if (!backup.init(alloc())) + return InliningStatus_Error; + + callInfo.setImplicitlyUsedUnchecked(); + + // Ensure sufficient space in the slots: needed for inlining from FUNAPPLY. + uint32_t depth = current->stackDepth() + callInfo.numFormals(); + if (depth > current->nslots()) { + if (!current->increaseSlots(depth - current->nslots())) + return InliningStatus_Error; + } + + // Create new |this| on the caller-side for inlined constructors. + if (callInfo.constructing()) { + MDefinition* thisDefn = createThis(target, callInfo.fun(), callInfo.getNewTarget()); + if (!thisDefn) + return InliningStatus_Error; + callInfo.setThis(thisDefn); + } + + // Capture formals in the outer resume point. + callInfo.pushFormals(current); + + MResumePoint* outerResumePoint = + MResumePoint::New(alloc(), current, pc, MResumePoint::Outer); + if (!outerResumePoint) + return InliningStatus_Error; + current->setOuterResumePoint(outerResumePoint); + + // Pop formals again, except leave |fun| on stack for duration of call. + callInfo.popFormals(current); + current->push(callInfo.fun()); + + JSScript* calleeScript = target->nonLazyScript(); + BaselineInspector inspector(calleeScript); + + // Improve type information of |this| when not set. + if (callInfo.constructing() && + !callInfo.thisArg()->resultTypeSet()) + { + StackTypeSet* types = TypeScript::ThisTypes(calleeScript); + if (types && !types->unknown()) { + TemporaryTypeSet* clonedTypes = types->clone(alloc_->lifoAlloc()); + if (!clonedTypes) + return InliningStatus_Error; + 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); + } + } + + // Start inlining. + LifoAlloc* lifoAlloc = alloc_->lifoAlloc(); + InlineScriptTree* inlineScriptTree = + info().inlineScriptTree()->addCallee(alloc_, pc, calleeScript); + if (!inlineScriptTree) + return InliningStatus_Error; + CompileInfo* info = lifoAlloc->new_<CompileInfo>(calleeScript, target, + (jsbytecode*)nullptr, + this->info().analysisMode(), + /* needsArgsObj = */ false, + inlineScriptTree); + if (!info) + return InliningStatus_Error; + + MIRGraphReturns returns(alloc()); + AutoAccumulateReturns aar(graph(), returns); + + // Build the graph. + IonBuilder inlineBuilder(analysisContext, compartment, options, &alloc(), &graph(), constraints(), + &inspector, info, &optimizationInfo(), nullptr, inliningDepth_ + 1, + loopDepth_); + if (!inlineBuilder.buildInline(this, outerResumePoint, callInfo)) { + if (analysisContext && analysisContext->isExceptionPending()) { + JitSpew(JitSpew_IonAbort, "Inline builder raised exception."); + abortReason_ = AbortReason_Error; + return InliningStatus_Error; + } + + // Inlining the callee failed. Mark the callee as uninlineable only if + // the inlining was aborted for a non-exception reason. + if (inlineBuilder.abortReason_ == AbortReason_Disable) { + calleeScript->setUninlineable(); + if (!JitOptions.disableInlineBacktracking) { + current = backup.restore(); + return InliningStatus_NotInlined; + } + abortReason_ = AbortReason_Inlining; + } else if (inlineBuilder.abortReason_ == AbortReason_Inlining) { + abortReason_ = AbortReason_Inlining; + } else if (inlineBuilder.abortReason_ == AbortReason_Alloc) { + abortReason_ = AbortReason_Alloc; + } else if (inlineBuilder.abortReason_ == AbortReason_PreliminaryObjects) { + const ObjectGroupVector& groups = inlineBuilder.abortedPreliminaryGroups(); + MOZ_ASSERT(!groups.empty()); + for (size_t i = 0; i < groups.length(); i++) + addAbortedPreliminaryGroup(groups[i]); + abortReason_ = AbortReason_PreliminaryObjects; + } + + return InliningStatus_Error; + } + + // Create return block. + jsbytecode* postCall = GetNextPc(pc); + MBasicBlock* returnBlock = newBlock(nullptr, postCall); + if (!returnBlock) + return InliningStatus_Error; + returnBlock->setCallerResumePoint(callerResumePoint_); + + // Inherit the slots from current and pop |fun|. + returnBlock->inheritSlots(current); + returnBlock->pop(); + + // Accumulate return values. + if (returns.empty()) { + // Inlining of functions that have no exit is not supported. + calleeScript->setUninlineable(); + if (!JitOptions.disableInlineBacktracking) { + current = backup.restore(); + return InliningStatus_NotInlined; + } + abortReason_ = AbortReason_Inlining; + return InliningStatus_Error; + } + MDefinition* retvalDefn = patchInlinedReturns(callInfo, returns, returnBlock); + if (!retvalDefn) + return InliningStatus_Error; + returnBlock->push(retvalDefn); + + // Initialize entry slots now that the stack has been fixed up. + if (!returnBlock->initEntrySlots(alloc())) + return InliningStatus_Error; + + if (!setCurrentAndSpecializePhis(returnBlock)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +MDefinition* +IonBuilder::patchInlinedReturn(CallInfo& callInfo, MBasicBlock* exit, MBasicBlock* bottom) +{ + // Replaces the MReturn in the exit block with an MGoto. + MDefinition* rdef = exit->lastIns()->toReturn()->input(); + exit->discardLastIns(); + + // Constructors must be patched by the caller to always return an object. + if (callInfo.constructing()) { + if (rdef->type() == MIRType::Value) { + // Unknown return: dynamically detect objects. + MReturnFromCtor* filter = MReturnFromCtor::New(alloc(), rdef, callInfo.thisArg()); + exit->add(filter); + rdef = filter; + } else if (rdef->type() != MIRType::Object) { + // Known non-object return: force |this|. + rdef = callInfo.thisArg(); + } + } else if (callInfo.isSetter()) { + // Setters return their argument, not whatever value is returned. + 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) +{ + // Remove types from the return definition that weren't observed. + TemporaryTypeSet* types = bytecodeTypes(pc); + + // The observed typeset doesn't contain extra information. + if (types->empty() || types->unknown()) + return rdef; + + // Decide if specializing is needed using the result typeset if available, + // else use the result type. + + if (rdef->resultTypeSet()) { + // Don't specialize if return typeset is a subset of the + // observed typeset. The return typeset is already more specific. + if (rdef->resultTypeSet()->isSubset(types)) + return rdef; + } else { + MIRType observedType = types->getKnownMIRType(); + + // Don't specialize if type is MIRType::Float32 and TI reports + // MIRType::Double. Float is more specific than double. + if (observedType == MIRType::Double && rdef->type() == MIRType::Float32) + return rdef; + + // Don't specialize if types are inaccordance, except for MIRType::Value + // and MIRType::Object (when not unknown object), since the typeset + // contains more specific information. + 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) +{ + // Replaces MReturns with MGotos, returning the MDefinition + // representing the return value, or nullptr. + MOZ_ASSERT(returns.length() > 0); + + if (returns.length() == 1) + return patchInlinedReturn(callInfo, returns[0], bottom); + + // Accumulate multiple returns with a phi. + 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) +{ + // When there is no target, inlining is impossible. + if (targetArg == nullptr) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNoTarget); + return InliningDecision_DontInline; + } + + // Inlining non-function targets is handled by inlineNonFunctionCall(). + if (!targetArg->is<JSFunction>()) + return InliningDecision_Inline; + + JSFunction* target = &targetArg->as<JSFunction>(); + + // Never inline during the arguments usage analysis. + if (info().analysisMode() == Analysis_ArgumentsUsage) + return InliningDecision_DontInline; + + // Native functions provide their own detection in inlineNativeCall(). + if (target->isNative()) + return InliningDecision_Inline; + + // Determine whether inlining is possible at callee site + InliningDecision decision = canInlineTarget(target, callInfo); + if (decision != InliningDecision_Inline) + return decision; + + // Heuristics! + JSScript* targetScript = target->nonLazyScript(); + + // Callee must not be excessively large. + // This heuristic also applies to the callsite as a whole. + bool offThread = options.offThreadCompilationAvailable(); + if (targetScript->length() > optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) { + trackOptimizationOutcome(TrackedOutcome::CantInlineBigCallee); + return DontInline(targetScript, "Vetoed: callee excessively large"); + } + + // Callee must have been called a few times to have somewhat stable + // type information, except for definite properties analysis, + // as the caller has not run yet. + if (targetScript->getWarmUpCount() < optimizationInfo().inliningWarmUpThreshold() && + !targetScript->baselineScript()->ionCompiledOrInlined() && + info().analysisMode() != Analysis_DefiniteProperties) + { + trackOptimizationOutcome(TrackedOutcome::CantInlineNotHot); + JitSpew(JitSpew_Inlining, "Cannot inline %s:%" PRIuSIZE ": callee is insufficiently hot.", + targetScript->filename(), targetScript->lineno()); + return InliningDecision_WarmUpCountTooLow; + } + + // Don't inline if the callee is known to inline a lot of code, to avoid + // huge MIR graphs. + 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(); + + // Cap the total bytecode length we inline under a single script, to avoid + // excessive inlining in pathological cases. + size_t totalBytecodeLength = outerBuilder->inlinedBytecodeLength_ + targetScript->length(); + if (totalBytecodeLength > optimizationInfo().inlineMaxTotalBytecodeLength()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededTotalBytecodeLength); + return DontInline(targetScript, "Vetoed: exceeding max total bytecode length"); + } + + // Cap the inlining depth. + + uint32_t maxInlineDepth; + if (JitOptions.isSmallFunction(targetScript)) { + maxInlineDepth = optimizationInfo().smallFunctionMaxInlineDepth(); + } else { + maxInlineDepth = optimizationInfo().maxInlineDepth(); + + // Caller must not be excessively large. + if (script()->length() >= optimizationInfo().inliningMaxCallerBytecodeLength()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller); + return DontInline(targetScript, "Vetoed: caller excessively large"); + } + } + + BaselineScript* outerBaseline = outermostBuilder()->script()->baselineScript(); + if (inliningDepth_ >= maxInlineDepth) { + // We hit the depth limit and won't inline this function. Give the + // outermost script a max inlining depth of 0, so that it won't be + // inlined in other scripts. This heuristic is currently only used + // when we're inlining scripts with loops, see the comment below. + outerBaseline->setMaxInliningDepth(0); + + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); + return DontInline(targetScript, "Vetoed: exceeding allowed inline depth"); + } + + // Inlining functions with loops can be complicated. For instance, if we're + // close to the inlining depth limit and we inline the function f below, we + // can no longer inline the call to g: + // + // function f() { + // while (cond) { + // g(); + // } + // } + // + // If the loop has many iterations, it's more efficient to call f and inline + // g in f. + // + // To avoid this problem, we record a separate max inlining depth for each + // script, indicating at which depth we won't be able to inline all functions + // we inlined this time. This solves the issue above, because we will only + // inline f if it means we can also inline g. + if (targetScript->hasLoops() && + inliningDepth_ >= targetScript->baselineScript()->maxInliningDepth()) + { + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); + return DontInline(targetScript, "Vetoed: exceeding allowed script inline depth"); + } + + // Update the max depth at which we can inline the outer script. + MOZ_ASSERT(maxInlineDepth > inliningDepth_); + uint32_t scriptInlineDepth = maxInlineDepth - inliningDepth_ - 1; + if (scriptInlineDepth < outerBaseline->maxInliningDepth()) + outerBaseline->setMaxInliningDepth(scriptInlineDepth); + + // End of heuristics, we will inline this function. + + // TI calls ObjectStateChange to trigger invalidation of the caller. + TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); + targetKey->watchStateChangeForInlinedCall(constraints()); + + outerBuilder->inlinedBytecodeLength_ += targetScript->length(); + + return InliningDecision_Inline; +} + +bool +IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInfo, BoolVector& choiceSet, + uint32_t* numInlineable) +{ + *numInlineable = 0; + uint32_t totalSize = 0; + + // For each target, ask whether it may be inlined. + if (!choiceSet.reserve(targets.length())) + return false; + + // Don't inline polymorphic sites during the definite properties analysis. + // AddClearDefiniteFunctionUsesInScript depends on this for correctness. + if (info().analysisMode() == Analysis_DefiniteProperties && targets.length() > 1) + return true; + + for (size_t i = 0; i < targets.length(); i++) { + JSObject* target = targets[i]; + + trackOptimizationAttempt(TrackedStrategy::Call_Inline); + trackTypeInfo(TrackedTypeSite::Call_Target, target); + + bool inlineable; + InliningDecision decision = makeInliningDecision(target, callInfo); + switch (decision) { + case InliningDecision_Error: + return false; + 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>()) { + // Enforce a maximum inlined bytecode limit at the callsite. + if (inlineable && target->as<JSFunction>().isInterpreted()) { + totalSize += target->as<JSFunction>().nonLazyScript()->length(); + bool offThread = options.offThreadCompilationAvailable(); + if (totalSize > optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) + inlineable = false; + } + } else { + // Non-function targets are not supported by polymorphic inlining. + inlineable = false; + } + + choiceSet.infallibleAppend(inlineable); + if (inlineable) + *numInlineable += 1; + } + + // If optimization tracking is turned on and one of the inlineable targets + // is a native, track the type info of the call. Most native inlinings + // depend on the types of the arguments and the return value. + if (isOptimizationTrackingEnabled()) { + for (size_t i = 0; i < targets.length(); i++) { + if (choiceSet[i] && targets[i]->as<JSFunction>().isNative()) { + trackTypeInfo(callInfo); + break; + } + } + } + + MOZ_ASSERT(choiceSet.length() == targets.length()); + return true; +} + +static bool +CanInlineGetPropertyCache(MGetPropertyCache* cache, MDefinition* thisDef) +{ + MOZ_ASSERT(cache->object()->type() == MIRType::Object); + if (cache->object() != 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(); + } + + // This function returns the cache given to the constructor if the + // GetPropertyCache can be moved into the ObjectGroup fallback path. + MGetPropertyCache* moveableCache(bool hasTypeBarrier, MDefinition* thisDef) { + // If we have unhandled uses of the MGetPropertyCache, then we cannot + // move it to the ObjectGroup fallback path. + if (!hasTypeBarrier) { + if (cache_->hasUses()) + return nullptr; + } else { + // There is the TypeBarrier consumer, so we check that this is the + // only consumer. + MOZ_ASSERT(cache_->hasUses()); + if (!cache_->hasOneUse()) + return nullptr; + } + + // If the this-object is not identical to the object of the + // MGetPropertyCache, then we cannot use the InlinePropertyTable, or if + // we do not yet have enough information from the ObjectGroup. + 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; + + // MGetPropertyCache with no uses may be optimized away. + if (funcDef->isGetPropertyCache()) { + WrapMGetPropertyCache cache(funcDef->toGetPropertyCache()); + return cache.moveableCache(/* hasTypeBarrier = */ false, thisDef); + } + + // Optimize away the following common pattern: + // MTypeBarrier[MIRType::Object] <- MGetPropertyCache + 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(/* hasTypeBarrier = */ true, thisDef); + } + + return nullptr; +} + +IonBuilder::InliningStatus +IonBuilder::inlineSingleCall(CallInfo& callInfo, JSObject* targetArg) +{ + if (!targetArg->is<JSFunction>()) { + InliningStatus status = inlineNonFunctionCall(callInfo, targetArg); + trackInlineSuccess(status); + return status; + } + + JSFunction* target = &targetArg->as<JSFunction>(); + if (target->isNative()) { + InliningStatus status = inlineNativeCall(callInfo, target); + trackInlineSuccess(status); + return status; + } + + // Track success now, as inlining a scripted call makes a new return block + // which has a different pc than the current call pc. + trackInlineSuccess(); + return inlineScriptedCall(callInfo, target); +} + +IonBuilder::InliningStatus +IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo) +{ + if (targets.empty()) { + trackOptimizationAttempt(TrackedStrategy::Call_Inline); + trackOptimizationOutcome(TrackedOutcome::CantInlineNoTarget); + return InliningStatus_NotInlined; + } + + // Is the function provided by an MGetPropertyCache? + // If so, the cache may be movable to a fallback path, with a dispatch + // instruction guarding on the incoming ObjectGroup. + WrapMGetPropertyCache propCache(getInlineableGetPropertyCache(callInfo)); + keepFallbackFunctionGetter(propCache.get()); + + // Inline single targets -- unless they derive from a cache, in which case + // avoiding the cache and guarding is still faster. + if (!propCache.get() && targets.length() == 1) { + JSObject* target = targets[0]; + + trackOptimizationAttempt(TrackedStrategy::Call_Inline); + trackTypeInfo(TrackedTypeSite::Call_Target, target); + + InliningDecision decision = makeInliningDecision(target, callInfo); + switch (decision) { + case InliningDecision_Error: + return InliningStatus_Error; + case InliningDecision_DontInline: + return InliningStatus_NotInlined; + case InliningDecision_WarmUpCountTooLow: + return InliningStatus_WarmUpCountTooLow; + case InliningDecision_Inline: + break; + } + + // Inlining will elminate uses of the original callee, but it needs to + // be preserved in phis if we bail out. Mark the old callee definition as + // implicitly used to ensure this happens. + callInfo.fun()->setImplicitlyUsedUnchecked(); + + // If the callee is not going to be a lambda (which may vary across + // different invocations), then the callee definition can be replaced by a + // constant. + if (target->isSingleton()) { + // Replace the function with an MConstant. + MConstant* constFun = constant(ObjectValue(*target)); + if (callInfo.constructing() && callInfo.getNewTarget() == callInfo.fun()) + callInfo.setNewTarget(constFun); + callInfo.setFun(constFun); + } + + return inlineSingleCall(callInfo, target); + } + + // Choose a subset of the targets for polymorphic inlining. + BoolVector choiceSet(alloc()); + uint32_t numInlined; + if (!selectInliningTargets(targets, callInfo, choiceSet, &numInlined)) + return InliningStatus_Error; + if (numInlined == 0) + return InliningStatus_NotInlined; + + // Perform a polymorphic dispatch. + if (!inlineCalls(callInfo, targets, choiceSet, propCache.get())) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + +bool +IonBuilder::inlineGenericFallback(JSFunction* target, CallInfo& callInfo, MBasicBlock* dispatchBlock) +{ + // Generate a new block with all arguments on-stack. + MBasicBlock* fallbackBlock = newBlock(dispatchBlock, pc); + if (!fallbackBlock) + return false; + + // Create a new CallInfo to track modified state within this block. + CallInfo fallbackInfo(alloc(), callInfo.constructing()); + if (!fallbackInfo.init(callInfo)) + return false; + fallbackInfo.popFormals(fallbackBlock); + + // Generate an MCall, which uses stateful |current|. + if (!setCurrentAndSpecializePhis(fallbackBlock)) + return false; + if (!makeCall(target, fallbackInfo)) + return false; + + // Pass return block to caller as |current|. + return true; +} + +bool +IonBuilder::inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchBlock, + MObjectGroupDispatch* dispatch, MGetPropertyCache* cache, + MBasicBlock** fallbackTarget) +{ + // Getting here implies the following: + // 1. The call function is an MGetPropertyCache, or an MGetPropertyCache + // followed by an MTypeBarrier. + MOZ_ASSERT(callInfo.fun()->isGetPropertyCache() || callInfo.fun()->isTypeBarrier()); + + // 2. The MGetPropertyCache has inlineable cases by guarding on the ObjectGroup. + MOZ_ASSERT(dispatch->numCases() > 0); + + // 3. The MGetPropertyCache (and, if applicable, MTypeBarrier) only + // have at most a single use. + MOZ_ASSERT_IF(callInfo.fun()->isGetPropertyCache(), !cache->hasUses()); + MOZ_ASSERT_IF(callInfo.fun()->isTypeBarrier(), cache->hasOneUse()); + + // This means that no resume points yet capture the MGetPropertyCache, + // so everything from the MGetPropertyCache up until the call is movable. + // We now move the MGetPropertyCache and friends into a fallback path. + MOZ_ASSERT(cache->idempotent()); + + // Create a new CallInfo to track modified state within the fallback path. + CallInfo fallbackInfo(alloc(), callInfo.constructing()); + if (!fallbackInfo.init(callInfo)) + return false; + + // Capture stack prior to the call operation. This captures the function. + MResumePoint* preCallResumePoint = + MResumePoint::New(alloc(), dispatchBlock, pc, MResumePoint::ResumeAt); + if (!preCallResumePoint) + return false; + + DebugOnly<size_t> preCallFuncIndex = preCallResumePoint->stackDepth() - callInfo.numFormals(); + MOZ_ASSERT(preCallResumePoint->getOperand(preCallFuncIndex) == fallbackInfo.fun()); + + // In the dispatch block, replace the function's slot entry with Undefined. + MConstant* undefined = MConstant::New(alloc(), UndefinedValue()); + dispatchBlock->add(undefined); + dispatchBlock->rewriteAtDepth(-int(callInfo.numFormals()), undefined); + + // Construct a block that does nothing but remove formals from the stack. + // This is effectively changing the entry resume point of the later fallback block. + MBasicBlock* prepBlock = newBlock(dispatchBlock, pc); + if (!prepBlock) + return false; + fallbackInfo.popFormals(prepBlock); + + // Construct a block into which the MGetPropertyCache can be moved. + // This is subtle: the pc and resume point are those of the MGetPropertyCache! + InlinePropertyTable* propTable = cache->propTable(); + MResumePoint* priorResumePoint = propTable->takePriorResumePoint(); + MOZ_ASSERT(propTable->pc() != nullptr); + MOZ_ASSERT(priorResumePoint != nullptr); + MBasicBlock* getPropBlock = newBlock(prepBlock, propTable->pc(), priorResumePoint); + if (!getPropBlock) + return false; + + prepBlock->end(MGoto::New(alloc(), getPropBlock)); + + // Since the getPropBlock inherited the stack from right before the MGetPropertyCache, + // the target of the MGetPropertyCache is still on the stack. + DebugOnly<MDefinition*> checkObject = getPropBlock->pop(); + MOZ_ASSERT(checkObject == cache->object()); + + // Move the MGetPropertyCache and friends into the getPropBlock. + 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); + } + + // Construct an end block with the correct resume point. + MBasicBlock* preCallBlock = newBlock(getPropBlock, pc, preCallResumePoint); + if (!preCallBlock) + return false; + getPropBlock->end(MGoto::New(alloc(), preCallBlock)); + + // Now inline the MCallGeneric, using preCallBlock as the dispatch point. + if (!inlineGenericFallback(nullptr, fallbackInfo, preCallBlock)) + return false; + + // inlineGenericFallback() set the return block as |current|. + preCallBlock->end(MGoto::New(alloc(), current)); + *fallbackTarget = prepBlock; + return true; +} + +bool +IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVector& choiceSet, + MGetPropertyCache* maybeCache) +{ + // Only handle polymorphic inlining. + MOZ_ASSERT(IsIonInlinablePC(pc)); + MOZ_ASSERT(choiceSet.length() == targets.length()); + MOZ_ASSERT_IF(!maybeCache, targets.length() >= 2); + MOZ_ASSERT_IF(maybeCache, targets.length() >= 1); + + MBasicBlock* dispatchBlock = current; + callInfo.setImplicitlyUsedUnchecked(); + callInfo.pushFormals(dispatchBlock); + + // Patch any InlinePropertyTable to only contain functions that are + // inlineable. The InlinePropertyTable will also be patched at the end to + // exclude native functions that vetoed inlining. + if (maybeCache) { + InlinePropertyTable* propTable = maybeCache->propTable(); + propTable->trimToTargets(targets); + if (propTable->numEntries() == 0) + maybeCache = nullptr; + } + + // Generate a dispatch based on guard kind. + MDispatchInstruction* dispatch; + if (maybeCache) { + dispatch = MObjectGroupDispatch::New(alloc(), maybeCache->object(), maybeCache->propTable()); + callInfo.fun()->setImplicitlyUsedUnchecked(); + } else { + dispatch = MFunctionDispatch::New(alloc(), callInfo.fun()); + } + + // Generate a return block to host the rval-collecting MPhi. + jsbytecode* postCall = GetNextPc(pc); + MBasicBlock* returnBlock = newBlock(nullptr, postCall); + if (!returnBlock) + return false; + returnBlock->setCallerResumePoint(callerResumePoint_); + + // Set up stack, used to manually create a post-call resume point. + returnBlock->inheritSlots(dispatchBlock); + callInfo.popFormals(returnBlock); + + MPhi* retPhi = MPhi::New(alloc()); + returnBlock->addPhi(retPhi); + returnBlock->push(retPhi); + + // Create a resume point from current stack state. + if (!returnBlock->initEntrySlots(alloc())) + return false; + + // Reserve the capacity for the phi. + // Note: this is an upperbound. Unreachable targets and uninlineable natives are also counted. + uint32_t count = 1; // Possible fallback block. + for (uint32_t i = 0; i < targets.length(); i++) { + if (choiceSet[i]) + count++; + } + if (!retPhi->reserveLength(count)) + return false; + + // Inline each of the inlineable targets. + for (uint32_t i = 0; i < targets.length(); i++) { + // Target must be inlineable. + if (!choiceSet[i]) + continue; + + // Even though we made one round of inline decisions already, we may + // be amending them below. + amendOptimizationAttempt(i); + + // Target must be reachable by the MDispatchInstruction. + JSFunction* target = &targets[i]->as<JSFunction>(); + if (maybeCache && !maybeCache->propTable()->hasFunction(target)) { + choiceSet[i] = false; + trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch); + continue; + } + + MBasicBlock* inlineBlock = newBlock(dispatchBlock, pc); + if (!inlineBlock) + return false; + + // Create a function MConstant to use in the entry ResumePoint. If we + // can't use a constant, add a no-op MPolyInlineGuard, to prevent + // hoisting env chain gets above the dispatch instruction. + MInstruction* funcDef; + if (target->isSingleton()) + funcDef = MConstant::New(alloc(), ObjectValue(*target), constraints()); + else + funcDef = MPolyInlineGuard::New(alloc(), callInfo.fun()); + + funcDef->setImplicitlyUsedUnchecked(); + dispatchBlock->add(funcDef); + + // Use the inlined callee in the inline resume point and on stack. + int funIndex = inlineBlock->entryResumePoint()->stackDepth() - callInfo.numFormals(); + inlineBlock->entryResumePoint()->replaceOperand(funIndex, funcDef); + inlineBlock->rewriteSlot(funIndex, funcDef); + + // Create a new CallInfo to track modified state within the inline block. + CallInfo inlineInfo(alloc(), callInfo.constructing()); + if (!inlineInfo.init(callInfo)) + return false; + inlineInfo.popFormals(inlineBlock); + inlineInfo.setFun(funcDef); + + if (maybeCache) { + // Assign the 'this' value a TypeSet specialized to the groups that + // can generate this inlining target. + MOZ_ASSERT(callInfo.thisArg() == maybeCache->object()); + TemporaryTypeSet* thisTypes = maybeCache->propTable()->buildTypeSetForFunction(target); + if (!thisTypes) + return false; + + MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), inlineInfo.thisArg(), thisTypes); + inlineBlock->add(filter); + inlineInfo.setThis(filter); + } + + // Inline the call into the inlineBlock. + if (!setCurrentAndSpecializePhis(inlineBlock)) + return false; + InliningStatus status = inlineSingleCall(inlineInfo, target); + if (status == InliningStatus_Error) + return false; + + // Natives may veto inlining. + if (status == InliningStatus_NotInlined) { + MOZ_ASSERT(current == inlineBlock); + graph().removeBlock(inlineBlock); + choiceSet[i] = false; + continue; + } + + // inlineSingleCall() changed |current| to the inline return block. + MBasicBlock* inlineReturnBlock = current; + setCurrent(dispatchBlock); + + // Connect the inline path to the returnBlock. + ObjectGroup* funcGroup = target->isSingleton() ? nullptr : target->group(); + if (!dispatch->addCase(target, funcGroup, inlineBlock)) + return false; + + MDefinition* retVal = inlineReturnBlock->peek(-1); + retPhi->addInput(retVal); + inlineReturnBlock->end(MGoto::New(alloc(), returnBlock)); + if (!returnBlock->addPredecessorWithoutPhis(inlineReturnBlock)) + return false; + } + + // Patch the InlinePropertyTable to not dispatch to vetoed paths. + bool useFallback; + if (maybeCache) { + InlinePropertyTable* propTable = maybeCache->propTable(); + propTable->trimTo(targets, choiceSet); + + if (propTable->numEntries() == 0 || !propTable->hasPriorResumePoint()) { + // Output a generic fallback path. + MOZ_ASSERT_IF(propTable->numEntries() == 0, dispatch->numCases() == 0); + maybeCache = nullptr; + useFallback = true; + } else { + // We need a fallback path if the ObjectGroup dispatch does not + // handle all incoming objects. + useFallback = false; + TemporaryTypeSet* objectTypes = maybeCache->object()->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) { + // The object group dispatch handles all possible incoming + // objects, so the cache and barrier will not be reached and + // can be eliminated. + 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 necessary, generate a fallback path. + if (useFallback) { + // Generate fallback blocks, and set |current| to the fallback return block. + if (maybeCache) { + MBasicBlock* fallbackTarget; + if (!inlineObjectGroupFallback(callInfo, dispatchBlock, + dispatch->toObjectGroupDispatch(), + maybeCache, &fallbackTarget)) + { + return false; + } + dispatch->addFallback(fallbackTarget); + } else { + JSFunction* remaining = nullptr; + + // If there is only 1 remaining case, we can annotate the fallback call + // with the target information. + if (dispatch->numCases() + 1 == targets.length()) { + for (uint32_t i = 0; i < targets.length(); i++) { + if (choiceSet[i]) + continue; + + MOZ_ASSERT(!remaining); + if (targets[i]->is<JSFunction>() && targets[i]->as<JSFunction>().isSingleton()) + remaining = &targets[i]->as<JSFunction>(); + break; + } + } + + if (!inlineGenericFallback(remaining, callInfo, dispatchBlock)) + return false; + dispatch->addFallback(current); + } + + MBasicBlock* fallbackReturnBlock = current; + + // Connect fallback case to return infrastructure. + MDefinition* retVal = fallbackReturnBlock->peek(-1); + retPhi->addInput(retVal); + fallbackReturnBlock->end(MGoto::New(alloc(), returnBlock)); + if (!returnBlock->addPredecessorWithoutPhis(fallbackReturnBlock)) + return false; + } + + // Finally add the dispatch instruction. + // This must be done at the end so that add() may be called above. + dispatchBlock->end(dispatch); + + // Check the depth change: +1 for retval + MOZ_ASSERT(returnBlock->stackDepth() == dispatchBlock->stackDepth() - callInfo.numFormals() + 1); + + graph().moveBlockToEnd(returnBlock); + return setCurrentAndSpecializePhis(returnBlock); +} + +MInstruction* +IonBuilder::createNamedLambdaObject(MDefinition* callee, MDefinition* env) +{ + // Get a template CallObject that we'll use to generate inline object + // creation. + LexicalEnvironmentObject* templateObj = inspector->templateNamedLambdaObject(); + + // One field is added to the function to handle its name. This cannot be a + // dynamic slot because there is still plenty of room on the NamedLambda object. + MOZ_ASSERT(!templateObj->hasDynamicSlots()); + + // Allocate the actual object. It is important that no intervening + // instructions could potentially bailout, thus leaking the dynamic slots + // pointer. + MInstruction* declEnvObj = MNewNamedLambdaObject::New(alloc(), templateObj); + current->add(declEnvObj); + + // Initialize the object's reserved slots. No post barrier is needed here: + // the object will be allocated in the nursery if possible, and if the + // tenured heap is used instead, a minor collection will have been performed + // that moved env/callee to the tenured heap. + current->add(MStoreFixedSlot::New(alloc(), declEnvObj, + NamedLambdaObject::enclosingEnvironmentSlot(), env)); + current->add(MStoreFixedSlot::New(alloc(), declEnvObj, + NamedLambdaObject::lambdaSlot(), callee)); + + return declEnvObj; +} + +MInstruction* +IonBuilder::createCallObject(MDefinition* callee, MDefinition* env) +{ + // Get a template CallObject that we'll use to generate inline object + // creation. + CallObject* templateObj = inspector->templateCallObject(); + + // Allocate the object. Run-once scripts need a singleton type, so always do + // a VM call in such cases. + MNewCallObjectBase* callObj; + if (script()->treatAsRunOnce() || templateObj->isSingleton()) + callObj = MNewSingletonCallObject::New(alloc(), templateObj); + else + callObj = MNewCallObject::New(alloc(), templateObj); + current->add(callObj); + + // Initialize the object's reserved slots. No post barrier is needed here, + // for the same reason as in createNamedLambdaObject. + current->add(MStoreFixedSlot::New(alloc(), callObj, CallObject::enclosingEnvironmentSlot(), env)); + current->add(MStoreFixedSlot::New(alloc(), callObj, CallObject::calleeSlot(), callee)); + + //if (!script()->functionHasParameterExprs()) { + + // Copy closed-over argument slots if there aren't parameter expressions. + MSlots* slots = nullptr; + for (PositionalFormalParameterIter fi(script()); fi; fi++) { + if (!fi.closedOver()) + continue; + + if (!alloc().ensureBallast()) + return nullptr; + + 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 callObj; +} + +MDefinition* +IonBuilder::createThisScripted(MDefinition* callee, MDefinition* newTarget) +{ + // Get callee.prototype. + // + // This instruction MUST be idempotent: since it does not correspond to an + // explicit operation in the bytecode, we cannot use resumeAfter(). + // Getters may not override |prototype| fetching, so this operation is indeed idempotent. + // - First try an idempotent property cache. + // - Upon failing idempotent property cache, we can't use a non-idempotent cache, + // therefore we fallback to CallGetProperty + // + // Note: both CallGetProperty and GetPropertyCache can trigger a GC, + // and thus invalidation. + MInstruction* getProto; + if (!invalidatedIdempotentCache()) { + MConstant* id = constant(StringValue(names().prototype)); + MGetPropertyCache* getPropCache = MGetPropertyCache::New(alloc(), newTarget, id, + /* monitored = */ false); + getPropCache->setIdempotent(); + getProto = getPropCache; + } else { + MCallGetProperty* callGetProp = MCallGetProperty::New(alloc(), newTarget, names().prototype); + callGetProp->setIdempotent(); + getProto = callGetProp; + } + current->add(getProto); + + // Create this from prototype + 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, MDefinition* callee) +{ + if (!target->hasScript()) + return nullptr; + + // Get the singleton prototype (if exists) + 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; + + 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; + + // Generate an inline path to create a new |this| object with + // the given singleton prototype. + 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) +{ + // Try to inline |this| creation based on Baseline feedback. + + JSFunction* target = inspector->getSingleCallee(pc); + if (!target || !target->hasScript()) + return nullptr; + + JSObject* templateObject = inspector->getTemplateObject(pc); + if (!templateObject) + return nullptr; + if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>()) + return nullptr; + + Shape* shape = target->lookupPure(compartment->runtime()->names().prototype); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) + 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; + + // Shape guard. + callee = addShapeGuard(callee, target->lastProperty(), Bailout_ShapeGuard); + + // Guard callee.prototype == proto. + 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, + /* bailOnEquality = */ false); + current->add(guard); + + // Generate an inline path to create a new |this| object with + // the given prototype. + 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) +{ + // Create |this| for unknown target. + if (!target) { + if (MDefinition* createThis = createThisScriptedBaseline(callee)) + return createThis; + + MCreateThis* createThis = MCreateThis::New(alloc(), callee, newTarget); + current->add(createThis); + return createThis; + } + + // Native constructors build the new Object themselves. + if (target->isNative()) { + if (!target->isConstructor()) + 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)); + } + + // Try baking in the prototype. + if (MDefinition* createThis = createThisScriptedSingleton(target, callee)) + return createThis; + + if (MDefinition* createThis = createThisScriptedBaseline(callee)) + return createThis; + + return createThisScripted(callee, newTarget); +} + +bool +IonBuilder::jsop_funcall(uint32_t argc) +{ + // Stack for JSOP_FUNCALL: + // 1: arg0 + // ... + // argc: argN + // argc+1: JSFunction*, the 'f' in |f.call()|, in |this| position. + // argc+2: The native 'call' function. + + int calleeDepth = -((int)argc + 2); + int funcDepth = -((int)argc + 1); + + // If |Function.prototype.call| may be overridden, don't optimize callsite. + TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet(); + JSFunction* native = getSingleCallTarget(calleeTypes); + if (!native || !native->isNative() || native->native() != &fun_call) { + CallInfo callInfo(alloc(), false); + if (!callInfo.init(current, argc)) + return false; + return makeCall(native, callInfo); + } + current->peek(calleeDepth)->setImplicitlyUsedUnchecked(); + + // Extract call target. + TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet(); + JSFunction* target = getSingleCallTarget(funTypes); + + // Shimmy the slots down to remove the native 'call' function. + current->shimmySlots(funcDepth - 1); + + bool zeroArguments = (argc == 0); + + // If no |this| argument was provided, explicitly pass Undefined. + // Pushing is safe here, since one stack slot has been removed. + if (zeroArguments) { + pushConstant(UndefinedValue()); + } else { + // |this| becomes implicit in the call. + argc -= 1; + } + + CallInfo callInfo(alloc(), false); + if (!callInfo.init(current, argc)) + return false; + + // Try to inline the call. + if (!zeroArguments) { + InliningDecision decision = makeInliningDecision(target, callInfo); + switch (decision) { + case InliningDecision_Error: + return false; + case InliningDecision_DontInline: + case InliningDecision_WarmUpCountTooLow: + break; + case InliningDecision_Inline: + if (target->isInterpreted()) { + InliningStatus status = inlineScriptedCall(callInfo, target); + if (status == InliningStatus_Inlined) + return true; + if (status == InliningStatus_Error) + return false; + } + break; + } + } + + // Call without inlining. + return makeCall(target, callInfo); +} + +bool +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(), false); + if (!callInfo.init(current, argc)) + return false; + return makeCall(native, callInfo); + } + + // Disable compilation if the second argument to |apply| cannot be guaranteed + // to be either definitely |arguments| or definitely not |arguments|. + MDefinition* argument = current->peek(-1); + if (script()->argumentsHasVarBinding() && + argument->mightBeType(MIRType::MagicOptimizedArguments) && + argument->type() != MIRType::MagicOptimizedArguments) + { + return abort("fun.apply with MaybeArguments"); + } + + // Fallback to regular call if arg 2 is not definitely |arguments|. + if (argument->type() != MIRType::MagicOptimizedArguments) { + // Optimize fun.apply(self, array) if the length is sane and there are no holes. + 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(), false); + if (!callInfo.init(current, argc)) + return false; + return makeCall(native, callInfo); + } + + if ((!native || !native->isNative() || + native->native() != fun_apply) && + info().analysisMode() != Analysis_DefiniteProperties) + { + return abort("fun.apply speculation failed"); + } + + // Use funapply that definitely uses |arguments| + return jsop_funapplyarguments(argc); +} + +bool +IonBuilder::jsop_funapplyarray(uint32_t argc) +{ + MOZ_ASSERT(argc == 2); + + int funcDepth = -((int)argc + 1); + + // Extract call target. + TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet(); + JSFunction* target = getSingleCallTarget(funTypes); + + // Pop the array agument + MDefinition* argObj = current->pop(); + + MElements* elements = MElements::New(alloc(), argObj); + current->add(elements); + + // Pop the |this| argument. + MDefinition* argThis = current->pop(); + + // Unwrap the (JSFunction *) parameter. + MDefinition* argFunc = current->pop(); + + // Pop apply function. + 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); + if (!resumeAfter(apply)) + return false; + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(apply, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::jsop_funapplyarguments(uint32_t argc) +{ + // Stack for JSOP_FUNAPPLY: + // 1: Vp + // 2: This + // argc+1: JSFunction*, the 'f' in |f.call()|, in |this| position. + // argc+2: The native 'apply' function. + + int funcDepth = -((int)argc + 1); + + // Extract call target. + TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet(); + JSFunction* target = getSingleCallTarget(funTypes); + + // When this script isn't inlined, use MApplyArgs, + // to copy the arguments from the stack and call the function + if (inliningDepth_ == 0 && info().analysisMode() != Analysis_DefiniteProperties) { + // The array argument corresponds to the arguments object. As the JIT + // is implicitly reading the arguments object in the next instruction, + // we need to prevent the deletion of the arguments object from resume + // points, so that Baseline will behave correctly after a bailout. + MDefinition* vp = current->pop(); + vp->setImplicitlyUsedUnchecked(); + + MDefinition* argThis = current->pop(); + + // Unwrap the (JSFunction*) parameter. + MDefinition* argFunc = current->pop(); + + // Pop apply function. + 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); + if (!resumeAfter(apply)) + return false; + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(apply, types, BarrierKind::TypeSet); + } + + // When inlining we have the arguments the function gets called with + // and can optimize even more, by just calling the functions with the args. + // We also try this path when doing the definite properties analysis, as we + // can inline the apply() target and don't care about the actual arguments + // that were passed in. + + CallInfo callInfo(alloc(), false); + + // Vp + MDefinition* vp = current->pop(); + vp->setImplicitlyUsedUnchecked(); + + // Arguments + if (inliningDepth_) { + if (!callInfo.setArgs(inlineCallInfo_->argv())) + return false; + } + + // This + MDefinition* argThis = current->pop(); + callInfo.setThis(argThis); + + // Pop function parameter. + MDefinition* argFunc = current->pop(); + callInfo.setFun(argFunc); + + // Pop apply function. + MDefinition* nativeFunc = current->pop(); + nativeFunc->setImplicitlyUsedUnchecked(); + + // Try to inline the call. + InliningDecision decision = makeInliningDecision(target, callInfo); + switch (decision) { + case InliningDecision_Error: + return false; + case InliningDecision_DontInline: + case InliningDecision_WarmUpCountTooLow: + break; + case InliningDecision_Inline: + if (target->isInterpreted()) { + InliningStatus status = inlineScriptedCall(callInfo, target); + if (status == InliningStatus_Inlined) + return true; + if (status == InliningStatus_Error) + return false; + } + } + + return makeCall(target, callInfo); +} + +bool +IonBuilder::jsop_call(uint32_t argc, bool constructing) +{ + startTrackingOptimizations(); + + // If this call has never executed, try to seed the observed type set + // based on how the call result is used. + TemporaryTypeSet* observed = bytecodeTypes(pc); + if (observed->empty()) { + if (BytecodeFlowsToBitop(pc)) { + observed->addType(TypeSet::Int32Type(), alloc_->lifoAlloc()); + } else if (*GetNextPc(pc) == JSOP_POS) { + // Note: this is lame, overspecialized on the code patterns used + // by asm.js and should be replaced by a more general mechanism. + // See bug 870847. + observed->addType(TypeSet::DoubleType(), alloc_->lifoAlloc()); + } + } + + int calleeDepth = -((int)argc + 2 + constructing); + + // Acquire known call target if existent. + ObjectVector targets(alloc()); + TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet(); + if (calleeTypes && !getPolyCallTargets(calleeTypes, constructing, targets, 4)) + return false; + + CallInfo callInfo(alloc(), constructing); + if (!callInfo.init(current, argc)) + return false; + + // Try inlining + InliningStatus status = inlineCallsite(targets, callInfo); + if (status == InliningStatus_Inlined) + return true; + if (status == InliningStatus_Error) + return false; + + // Discard unreferenced & pre-allocated resume points. + replaceMaybeFallbackFunctionGetter(nullptr); + + // No inline, just make the call. + JSFunction* target = nullptr; + if (targets.length() == 1 && targets[0]->is<JSFunction>()) + target = &targets[0]->as<JSFunction>(); + + if (target && status == InliningStatus_WarmUpCountTooLow) { + MRecompileCheck* check = + MRecompileCheck::New(alloc(), target->nonLazyScript(), + optimizationInfo().inliningRecompileThreshold(), + MRecompileCheck::RecompileCheck_Inlining); + current->add(check); + } + + return makeCall(target, callInfo); +} + +bool +IonBuilder::testShouldDOMCall(TypeSet* inTypes, JSFunction* func, JSJitInfo::OpType opType) +{ + if (!func->isNative() || !func->jitInfo()) + return false; + + // If all the DOM objects flowing through are legal with this + // property, we can bake in a call to the bottom half of the DOM + // accessor + DOMInstanceClassHasProtoAtDepth instanceChecker = + compartment->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 (!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 we have a known target, check if the caller arg types are a subset of callee. + // Since typeset accumulates and can't decrease that means we don't need to check + // the arguments anymore. + 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; +} + +MCall* +IonBuilder::makeCallHelper(JSFunction* target, CallInfo& callInfo) +{ + // This function may be called with mutated stack. + // Querying TI for popped types is invalid. + + uint32_t targetArgs = callInfo.argc(); + + // Collect number of missing arguments provided that the target is + // scripted. Native functions are passed an explicit 'argc' parameter. + if (target && !target->isNative()) + targetArgs = Max<uint32_t>(target->nargs(), callInfo.argc()); + + bool isDOMCall = false; + if (target && !callInfo.constructing()) { + // We know we have a single call target. Check whether the "this" types + // are DOM types and our function a DOM function, and if so flag the + // MCall accordingly. + TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet(); + if (thisTypes && + thisTypes->getKnownMIRType() == MIRType::Object && + thisTypes->isDOMClass(constraints()) && + testShouldDOMCall(thisTypes, target, JSJitInfo::Method)) + { + isDOMCall = true; + } + } + + MCall* call = MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(), + callInfo.argc(), callInfo.constructing(), isDOMCall); + if (!call) + return nullptr; + + if (callInfo.constructing()) + call->addArg(targetArgs + 1, callInfo.getNewTarget()); + + // Explicitly pad any missing arguments with |undefined|. + // This permits skipping the argumentsRectifier. + for (int i = targetArgs; i > (int)callInfo.argc(); i--) { + MOZ_ASSERT_IF(target, !target->isNative()); + MConstant* undef = constant(UndefinedValue()); + if (!alloc().ensureBallast()) + return nullptr; + call->addArg(i, undef); + } + + // Add explicit arguments. + // Skip addArg(0) because it is reserved for this + for (int32_t i = callInfo.argc() - 1; i >= 0; i--) + call->addArg(i + 1, callInfo.getArg(i)); + + // Now that we've told it about all the args, compute whether it's movable + call->computeMovable(); + + // Inline the constructor on the caller-side. + if (callInfo.constructing()) { + MDefinition* create = createThis(target, callInfo.fun(), callInfo.getNewTarget()); + if (!create) { + abort("Failure inlining constructor for call."); + return nullptr; + } + + callInfo.thisArg()->setImplicitlyUsedUnchecked(); + callInfo.setThis(create); + } + + // Pass |this| and function. + MDefinition* thisArg = callInfo.thisArg(); + call->addArg(0, thisArg); + + if (target && !testNeedsArgumentCheck(target, callInfo)) + call->disableArgCheck(); + + call->initFunction(callInfo.fun()); + + current->add(call); + return call; +} + +static bool +DOMCallNeedsBarrier(const JSJitInfo* jitinfo, TemporaryTypeSet* types) +{ + MOZ_ASSERT(jitinfo->type() != JSJitInfo::InlinableNative); + + // If the return type of our DOM native is in "types" already, we don't + // actually need a barrier. + if (jitinfo->returnType() == JSVAL_TYPE_UNKNOWN) + return true; + + // JSVAL_TYPE_OBJECT doesn't tell us much; we still have to barrier on the + // actual type of the object. + if (jitinfo->returnType() == JSVAL_TYPE_OBJECT) + return true; + + // No need for a barrier if we're already expecting the type we'll produce. + return MIRTypeFromValueType(jitinfo->returnType()) != types->getKnownMIRType(); +} + +bool +IonBuilder::makeCall(JSFunction* target, CallInfo& callInfo) +{ + // Constructor calls to non-constructors should throw. We don't want to use + // CallKnown in this case. + MOZ_ASSERT_IF(callInfo.constructing() && target, target->isConstructor()); + + MCall* call = makeCallHelper(target, callInfo); + if (!call) + return false; + + current->push(call); + if (call->isEffectful() && !resumeAfter(call)) + return false; + + TemporaryTypeSet* types = bytecodeTypes(pc); + + if (call->isCallDOMNative()) + return pushDOMTypeBarrier(call, types, call->getSingleTarget()->rawJSFunction()); + + return pushTypeBarrier(call, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::jsop_eval(uint32_t argc) +{ + int calleeDepth = -((int)argc + 2); + TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet(); + + // Emit a normal call if the eval has never executed. This keeps us from + // disabling compilation for the script when testing with --ion-eager. + if (calleeTypes && calleeTypes->empty()) + return jsop_call(argc, /* constructing = */ false); + + JSFunction* singleton = getSingleCallTarget(calleeTypes); + if (!singleton) + return abort("No singleton callee for eval()"); + + if (script()->global().valueIsEval(ObjectValue(*singleton))) { + if (argc != 1) + return abort("Direct eval with more than one argument"); + + if (!info().funMaybeLazy()) + return abort("Direct eval in global code"); + + if (info().funMaybeLazy()->isArrow()) + return abort("Direct eval from arrow function"); + + CallInfo callInfo(alloc(), /* constructing = */ false); + if (!callInfo.init(current, argc)) + return false; + callInfo.setImplicitlyUsedUnchecked(); + + callInfo.fun()->setImplicitlyUsedUnchecked(); + + MDefinition* envChain = current->environmentChain(); + MDefinition* string = callInfo.getArg(0); + + // Direct eval acts as identity on non-string types according to + // ES5 15.1.2.1 step 1. + if (!string->mightBeType(MIRType::String)) { + current->push(string); + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(string, types, BarrierKind::TypeSet); + } + + if (!jsop_newtarget()) + return false; + MDefinition* newTargetValue = current->pop(); + + // Try to pattern match 'eval(v + "()")'. In this case v is likely a + // name on the env chain and the eval is performing a call on that + // value. Use an env chain lookup rather than a full eval. + if (string->isConcat() && + string->getOperand(1)->type() == MIRType::String && + string->getOperand(1)->maybeConstantValue()) + { + 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())); // thisv + + CallInfo evalCallInfo(alloc(), /* constructing = */ false); + if (!evalCallInfo.init(current, /* argc = */ 0)) + return false; + + return makeCall(nullptr, evalCallInfo); + } + } + + MInstruction* ins = MCallDirectEval::New(alloc(), envChain, string, + newTargetValue, pc); + current->add(ins); + current->push(ins); + + TemporaryTypeSet* types = bytecodeTypes(pc); + return resumeAfter(ins) && pushTypeBarrier(ins, types, BarrierKind::TypeSet); + } + + return jsop_call(argc, /* constructing = */ false); +} + +bool +IonBuilder::jsop_compare(JSOp op) +{ + MDefinition* right = current->pop(); + MDefinition* left = current->pop(); + + return jsop_compare(op, left, right); +} + +bool +IonBuilder::jsop_compare(JSOp op, MDefinition* left, MDefinition* right) +{ + bool emitted = false; + + if (!forceInlineCaches()) { + if (!compareTrySpecialized(&emitted, op, left, right) || emitted) + return emitted; + if (!compareTryBitwise(&emitted, op, left, right) || emitted) + return emitted; + if (!compareTrySpecializedOnBaselineInspector(&emitted, op, left, right) || emitted) + return emitted; + } + + if (!compareTrySharedStub(&emitted, op, left, right) || emitted) + return emitted; + + // Not possible to optimize. Do a slow vm call. + MCompare* ins = MCompare::New(alloc(), left, right, op); + ins->cacheOperandMightEmulateUndefined(constraints()); + + current->add(ins); + current->push(ins); + if (ins->isEffectful() && !resumeAfter(ins)) + return false; + return true; +} + +static bool +ObjectOrSimplePrimitive(MDefinition* op) +{ + // Return true if op is either undefined/null/boolean/int32 or an object. + return !op->mightBeType(MIRType::String) + && !op->mightBeType(MIRType::Symbol) + && !op->mightBeType(MIRType::Double) + && !op->mightBeType(MIRType::Float32) + && !op->mightBeType(MIRType::MagicOptimizedArguments) + && !op->mightBeType(MIRType::MagicHole) + && !op->mightBeType(MIRType::MagicIsConstructing); +} + +bool +IonBuilder::compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit an compare based on the input types. + + MCompare::CompareType type = MCompare::determineCompareType(op, left, right); + if (type == MCompare::Compare_Unknown) + return true; + + MCompare* ins = MCompare::New(alloc(), left, right, op); + ins->setCompareType(type); + ins->cacheOperandMightEmulateUndefined(constraints()); + + // Some compare types need to have the specific type in the rhs. + // Swap operands if that is not the case. + 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(); + + // Replace inputs with unsigned variants if needed. + if (type == MCompare::Compare_UInt32) + ins->replaceWithUnsignedOperands(); + + current->add(ins); + current->push(ins); + + MOZ_ASSERT(!ins->isEffectful()); + *emitted = true; + return true; +} + +bool +IonBuilder::compareTryBitwise(bool* emitted, JSOp op, MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a bitwise compare. Check if a bitwise compare equals the wanted + // result for all observed operand types. + + // Onlye allow loose and strict equality. + if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ && op != JSOP_STRICTNE) + return true; + + // Only primitive (not double/string) or objects are supported. + // I.e. Undefined/Null/Boolean/Int32 and Object + if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right)) + return true; + + // Objects that emulate undefined are not supported. + if (left->maybeEmulatesUndefined(constraints()) || right->maybeEmulatesUndefined(constraints())) + return true; + + // In the loose comparison more values could be the same, + // but value comparison reporting otherwise. + if (op == JSOP_EQ || op == JSOP_NE) { + + // Undefined compared loosy to Null is not supported, + // because tag is different, but value can be the same (undefined == null). + if ((left->mightBeType(MIRType::Undefined) && right->mightBeType(MIRType::Null)) || + (left->mightBeType(MIRType::Null) && right->mightBeType(MIRType::Undefined))) + { + return true; + } + + // Int32 compared loosy to Boolean is not supported, + // because tag is different, but value can be the same (1 == true). + if ((left->mightBeType(MIRType::Int32) && right->mightBeType(MIRType::Boolean)) || + (left->mightBeType(MIRType::Boolean) && right->mightBeType(MIRType::Int32))) + { + return true; + } + + // For loosy comparison of an object with a Boolean/Number/String + // the valueOf the object is taken. Therefore not supported. + bool simpleLHS = left->mightBeType(MIRType::Boolean) || left->mightBeType(MIRType::Int32); + bool simpleRHS = right->mightBeType(MIRType::Boolean) || right->mightBeType(MIRType::Int32); + if ((left->mightBeType(MIRType::Object) && simpleRHS) || + (right->mightBeType(MIRType::Object) && simpleLHS)) + { + return true; + } + } + + 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()); + *emitted = true; + return true; +} + +bool +IonBuilder::compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, MDefinition* left, + MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to specialize based on any baseline caches that have been generated + // for the opcode. These will cause the instruction's type policy to insert + // fallible unboxes to the appropriate input types. + + // Strict equality isn't supported. + if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) + return true; + + MCompare::CompareType type = inspector->expectedCompareType(pc); + if (type == MCompare::Compare_Unknown) + return true; + + MCompare* ins = MCompare::New(alloc(), left, right, op); + ins->setCompareType(type); + ins->cacheOperandMightEmulateUndefined(constraints()); + + current->add(ins); + current->push(ins); + + MOZ_ASSERT(!ins->isEffectful()); + *emitted = true; + return true; +} + +bool +IonBuilder::compareTrySharedStub(bool* emitted, JSOp op, MDefinition* left, MDefinition* right) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a shared stub cache. + + if (JitOptions.disableSharedStubs) + return true; + + if (JSOp(*pc) == JSOP_CASE) + return true; + + MBinarySharedStub* stub = MBinarySharedStub::New(alloc(), left, right); + current->add(stub); + current->push(stub); + if (!resumeAfter(stub)) + return false; + + MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Boolean, MUnbox::Infallible); + current->add(unbox); + current->push(unbox); + + *emitted = true; + return true; +} + +bool +IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, uint32_t length) +{ + MOZ_ASSERT(*emitted == false); + + if (!templateObject) + return true; + + if (templateObject->is<UnboxedArrayObject>()) { + MOZ_ASSERT(templateObject->as<UnboxedArrayObject>().capacity() >= length); + if (!templateObject->as<UnboxedArrayObject>().hasInlineElements()) + return true; + } + + MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT); + + size_t arraySlots = + gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER; + + if (length > arraySlots) + return true; + + // Emit fastpath. + + 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); + + *emitted = true; + return true; +} + +bool +IonBuilder::newArrayTrySharedStub(bool* emitted) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a shared stub cache. + + if (JitOptions.disableSharedStubs) + return true; + + if (*pc != JSOP_NEWINIT && *pc != JSOP_NEWARRAY) + return true; + + MInstruction* stub = MNullarySharedStub::New(alloc()); + current->add(stub); + current->push(stub); + + if (!resumeAfter(stub)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t length) +{ + MOZ_ASSERT(*emitted == false); + + // Emit a VM 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); + + *emitted = true; + return true; +} + +bool +IonBuilder::jsop_newarray(uint32_t length) +{ + JSObject* templateObject = inspector->getTemplateObject(pc); + if (!jsop_newarray(templateObject, length)) + return false; + + // Improve resulting typeset. + ObjectGroup* templateGroup = inspector->getTemplateObjectGroup(pc); + if (templateGroup) { + TemporaryTypeSet* types = MakeSingletonTypeSet(constraints(), templateGroup); + current->peek(-1)->setResultTypeSet(types); + } + + return true; +} + +bool +IonBuilder::jsop_newarray(JSObject* templateObject, uint32_t length) +{ + bool emitted = false; + + if (!forceInlineCaches()) { + if (!newArrayTryTemplateObject(&emitted, templateObject, length) || emitted) + return emitted; + } + + if (!newArrayTrySharedStub(&emitted) || emitted) + return emitted; + + if (!newArrayTryVM(&emitted, templateObject, length) || emitted) + return emitted; + + MOZ_CRASH("newarray should have been emited"); +} + +bool +IonBuilder::jsop_newarray_copyonwrite() +{ + ArrayObject* templateObject = ObjectGroup::getCopyOnWriteObject(script(), pc); + + // The baseline compiler should have ensured the template object has a type + // with the copy on write flag set already. During the arguments usage + // analysis the baseline compiler hasn't run yet, however, though in this + // case the template object's type doesn't matter. + MOZ_ASSERT_IF(info().analysisMode() != Analysis_ArgumentsUsage, + templateObject->group()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE)); + + MNewArrayCopyOnWrite* ins = + MNewArrayCopyOnWrite::New(alloc(), constraints(), templateObject, + templateObject->group()->initialHeap(constraints())); + + current->add(ins); + current->push(ins); + + return true; +} + +bool +IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject) +{ + MOZ_ASSERT(*emitted == false); + + if (!templateObject) + return true; + + if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots()) + return true; + + // Emit fastpath. + + 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); + + if (!resumeAfter(ins)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::newObjectTrySharedStub(bool* emitted) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a shared stub cache. + + if (JitOptions.disableSharedStubs) + return true; + + MInstruction* stub = MNullarySharedStub::New(alloc()); + current->add(stub); + current->push(stub); + + if (!resumeAfter(stub)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject) +{ + // Emit a VM call. + MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT); + + 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); + + if (!resumeAfter(ins)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::jsop_newobject() +{ + bool emitted = false; + + JSObject* templateObject = inspector->getTemplateObject(pc); + + if (!forceInlineCaches()) { + if (!newObjectTryTemplateObject(&emitted, templateObject) || emitted) + return emitted; + } + if (!newObjectTrySharedStub(&emitted) || emitted) + return emitted; + + if (!newObjectTryVM(&emitted, templateObject) || emitted) + return emitted; + + MOZ_CRASH("newobject should have been emited"); +} + +bool +IonBuilder::jsop_initelem() +{ + MDefinition* value = current->pop(); + MDefinition* id = current->pop(); + MDefinition* obj = current->peek(-1); + + MInitElem* initElem = MInitElem::New(alloc(), obj, id, value); + current->add(initElem); + + return resumeAfter(initElem); +} + +bool +IonBuilder::jsop_initelem_array() +{ + MDefinition* value = current->pop(); + MDefinition* obj = current->peek(-1); + + // Make sure that arrays have the type being written to them by the + // intializer, and that arrays are marked as non-packed when writing holes + // to them during initialization. + bool needStub = false; + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + 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 (initializer->clasp() == &UnboxedArrayObject::class_) { + if (initializer->group()->unboxedLayout().nativeGroup()) + needStub = true; + else + unboxedType = initializer->group()->unboxedLayout().elementType(); + } + 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) { + MCallInitElementArray* store = MCallInitElementArray::New(alloc(), obj, index, value); + current->add(store); + return resumeAfter(store); + } + + return initializeArrayElement(obj, index, value, unboxedType, /* addResumePoint = */ true); +} + +bool +IonBuilder::initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value, + JSValueType unboxedType, + bool addResumePointAndIncrementInitializedLength) +{ + MConstant* id = MConstant::New(alloc(), Int32Value(index)); + current->add(id); + + // Get the elements vector. + MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC); + current->add(elements); + + if (unboxedType != JSVAL_TYPE_MAGIC) { + // Note: storeUnboxedValue takes care of any post barriers on the value. + storeUnboxedValue(obj, elements, 0, id, unboxedType, value, /* preBarrier = */ false); + + if (addResumePointAndIncrementInitializedLength) { + MInstruction* increment = MIncrementUnboxedArrayInitializedLength::New(alloc(), obj); + current->add(increment); + + if (!resumeAfter(increment)) + return false; + } + } else { + if (NeedsPostBarrier(value)) + current->add(MPostWriteBarrier::New(alloc(), obj, value)); + + if ((obj->isNewArray() && obj->toNewArray()->convertDoubleElements()) || + (obj->isNullarySharedStub() && + obj->resultTypeSet()->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles)) + { + MInstruction* valueDouble = MToDouble::New(alloc(), value); + current->add(valueDouble); + value = valueDouble; + } + + // Store the value. + MStoreElement* store = MStoreElement::New(alloc(), elements, id, value, + /* needsHoleCheck = */ false); + current->add(store); + + if (addResumePointAndIncrementInitializedLength) { + // Update the initialized length. (The template object for this + // array has the array's ultimate length, so the length field is + // already correct: no updating needed.) + MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id); + current->add(initLength); + + if (!resumeAfter(initLength)) + return false; + } + } + + return true; +} + +bool +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); +} + +bool +IonBuilder::jsop_initprop(PropertyName* name) +{ + bool useSlowPath = false; + + MDefinition* value = current->peek(-1); + MDefinition* obj = current->peek(-2); + if (obj->isLambda()) { + useSlowPath = true; + } else if (obj->isNewObject()) { + if (JSObject* templateObject = obj->toNewObject()->templateObject()) { + if (templateObject->is<PlainObject>()) { + if (!templateObject->as<PlainObject>().containsPure(name)) + useSlowPath = true; + } else { + MOZ_ASSERT(templateObject->as<UnboxedPlainObject>().layout().lookup(name)); + } + } else { + useSlowPath = true; + } + } else { + MOZ_ASSERT(obj->isNullarySharedStub()); + useSlowPath = true; + } + + if (useSlowPath) { + current->pop(); + MInitProp* init = MInitProp::New(alloc(), obj, name, value); + current->add(init); + return resumeAfter(init); + } + + MInstruction* last = *current->rbegin(); + + // This is definitely initializing an 'own' property of the object, treat + // it as an assignment. + if (!jsop_setprop(name)) + return false; + + // SETPROP pushed the value, instead of the object. Fix this on the stack, + // and check the most recent resume point to see if it needs updating too. + 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 true; +} + +bool +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); +} + +bool +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); +} + +MBasicBlock* +IonBuilder::addBlock(MBasicBlock* block, uint32_t loopDepth) +{ + if (!block) + return nullptr; + if (block->pc() && script()->hasScriptCounts()) + block->setHitCount(script()->getHitCount(block->pc())); + graph().addBlock(block); + block->setLoopDepth(loopDepth); + return block; +} + +MBasicBlock* +IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc) +{ + MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor, + bytecodeSite(pc), MBasicBlock::NORMAL); + return addBlock(block, loopDepth_); +} + +MBasicBlock* +IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint) +{ + MBasicBlock* block = MBasicBlock::NewWithResumePoint(graph(), info(), predecessor, + bytecodeSite(pc), priorResumePoint); + return addBlock(block, loopDepth_); +} + +MBasicBlock* +IonBuilder::newBlockPopN(MBasicBlock* predecessor, jsbytecode* pc, uint32_t popped) +{ + MBasicBlock* block = MBasicBlock::NewPopN(graph(), info(), predecessor, bytecodeSite(pc), + MBasicBlock::NORMAL, popped); + return addBlock(block, loopDepth_); +} + +MBasicBlock* +IonBuilder::newBlockAfter(MBasicBlock* at, MBasicBlock* predecessor, jsbytecode* pc) +{ + MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor, + bytecodeSite(pc), MBasicBlock::NORMAL); + if (!block) + return nullptr; + block->setHitCount(0); // osr block + graph().insertBlockAfter(at, block); + return block; +} + +MBasicBlock* +IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc, uint32_t loopDepth) +{ + MBasicBlock* block = MBasicBlock::New(graph(), &analysis(), info(), predecessor, + bytecodeSite(pc), MBasicBlock::NORMAL); + return addBlock(block, loopDepth); +} + +MBasicBlock* +IonBuilder::newOsrPreheader(MBasicBlock* predecessor, jsbytecode* loopEntry, jsbytecode* beforeLoopEntry) +{ + MOZ_ASSERT(LoopEntryCanIonOsr(loopEntry)); + MOZ_ASSERT(loopEntry == info().osrPc()); + + // Create two blocks: one for the OSR entry with no predecessors, one for + // the preheader, which has the OSR entry block as a predecessor. The + // OSR block is always the second block (with id 1). + MBasicBlock* osrBlock = newBlockAfter(*graph().begin(), loopEntry); + MBasicBlock* preheader = newBlock(predecessor, loopEntry); + if (!osrBlock || !preheader) + return nullptr; + + // Give the pre-header the same hit count as the code before the loop. + if (script()->hasScriptCounts()) + preheader->setHitCount(script()->getHitCount(beforeLoopEntry)); + + MOsrEntry* entry = MOsrEntry::New(alloc()); + osrBlock->add(entry); + + // Initialize |envChain|. + { + uint32_t slot = info().environmentChainSlot(); + + MInstruction* envv; + if (analysis().usesEnvironmentChain()) { + envv = MOsrEnvironmentChain::New(alloc(), entry); + } else { + // Use an undefined value if the script does not need its env + // chain, to match the type that is already being tracked for the + // slot. + envv = MConstant::New(alloc(), UndefinedValue()); + } + + osrBlock->add(envv); + osrBlock->initSlot(slot, envv); + } + // Initialize |return value| + { + MInstruction* returnValue; + if (!script()->noScriptRval()) + returnValue = MOsrReturnValue::New(alloc(), entry); + else + returnValue = MConstant::New(alloc(), UndefinedValue()); + osrBlock->add(returnValue); + osrBlock->initSlot(info().returnValueSlot(), returnValue); + } + + // Initialize arguments object. + 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()) { + // Initialize |this| parameter. + MParameter* thisv = MParameter::New(alloc(), MParameter::THIS_SLOT, nullptr); + osrBlock->add(thisv); + osrBlock->initSlot(info().thisSlot(), thisv); + + // Initialize arguments. + for (uint32_t i = 0; i < info().nargs(); i++) { + uint32_t slot = needsArgsObj ? info().argSlotUnchecked(i) : info().argSlot(i); + + // Only grab arguments from the arguments object if the arguments object + // aliases formals. If the argsobj does not alias formals, then the + // formals may have been assigned to during interpretation, and that change + // will not be reflected in the argsobj. + if (needsArgsObj && info().argsObjAliasesFormals()) { + MOZ_ASSERT(argsObj && argsObj->isOsrArgumentsObject()); + // If this is an aliased formal, then the arguments object + // contains a hole at this index. Any references to this + // variable in the jitcode will come from JSOP_*ALIASEDVAR + // opcodes, so the slot itself can be set to undefined. If + // it's not aliased, it must be retrieved from the arguments + // object. + 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); + } + } + } + + // Initialize locals. + 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 nullptr; + osrBlock->add(osrv); + osrBlock->initSlot(slot, osrv); + } + + // Initialize stack. + 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 nullptr; + osrBlock->add(osrv); + osrBlock->initSlot(slot, osrv); + } + + // Create an MStart to hold the first valid MResumePoint. + MStart* start = MStart::New(alloc()); + osrBlock->add(start); + + // MOsrValue instructions are infallible, so the first MResumePoint must + // occur after they execute, at the point of the MStart. + if (!resumeAt(start, loopEntry)) + return nullptr; + + // Link the same MResumePoint from the MStart to each MOsrValue. + // This causes logic in ShouldSpecializeInput() to not replace Uses with + // Unboxes in the MResumePiont, so that the MStart always sees Values. + if (!osrBlock->linkOsrValues(start)) + return nullptr; + + // Clone types of the other predecessor of the pre-header to the osr block, + // such as pre-header phi's won't discard specialized type of the + // predecessor. + MOZ_ASSERT(predecessor->stackDepth() == osrBlock->stackDepth()); + MOZ_ASSERT(info().environmentChainSlot() == 0); + + // Treat the OSR values as having the same type as the existing values + // coming in to the loop. These will be fixed up with appropriate + // unboxing and type barriers in finishLoop, once the possible types + // at the loop header are known. + 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); + + // Aliased slots are never accessed, since they need to go through + // the callobject. No need to type them here. + if (info().isSlotAliased(i)) + continue; + + def->setResultType(existing->type()); + def->setResultTypeSet(existing->resultTypeSet()); + } + + // Finish the osrBlock. + osrBlock->end(MGoto::New(alloc(), preheader)); + if (!preheader->addPredecessor(alloc(), osrBlock)) + return nullptr; + graph().setOsrBlock(osrBlock); + + return preheader; +} + +MBasicBlock* +IonBuilder::newPendingLoopHeader(MBasicBlock* predecessor, jsbytecode* pc, bool osr, bool canOsr, + unsigned stackPhiCount) +{ + loopDepth_++; + // If this site can OSR, all values on the expression stack are part of the loop. + if (canOsr) + stackPhiCount = predecessor->stackDepth() - info().firstStackSlot(); + MBasicBlock* block = MBasicBlock::NewPendingLoopHeader(graph(), info(), predecessor, + bytecodeSite(pc), stackPhiCount); + if (!addBlock(block, loopDepth_)) + return nullptr; + + if (osr) { + // Incorporate type information from the OSR frame into the loop + // header. The OSR frame may have unexpected types due to type changes + // within the loop body or due to incomplete profiling information, + // in which case this may avoid restarts of loop analysis or bailouts + // during the OSR itself. + + MOZ_ASSERT(info().firstLocalSlot() - info().firstArgSlot() == + baselineFrame_->argTypes.length()); + MOZ_ASSERT(block->stackDepth() - info().firstLocalSlot() == + baselineFrame_->varTypes.length()); + + // Unbox the MOsrValue if it is known to be unboxable. + for (uint32_t i = info().startArgSlot(); i < block->stackDepth(); i++) { + + // The value of aliased args and slots are in the callobject. So we can't + // the value from the baseline frame. + if (info().isSlotAliased(i)) + continue; + + MPhi* phi = block->getSlot(i)->toPhi(); + + // Get the type from the baseline frame. + 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()); + + // Extract typeset from value. + LifoAlloc* lifoAlloc = alloc().lifoAlloc(); + TemporaryTypeSet* typeSet = + lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, existingType); + if (!typeSet) + return nullptr; + MIRType type = typeSet->getKnownMIRType(); + if (!phi->addBackedgeType(alloc(), type, typeSet)) + return nullptr; + } + } + + return block; +} + +MTest* +IonBuilder::newTest(MDefinition* ins, MBasicBlock* ifTrue, MBasicBlock* ifFalse) +{ + MTest* test = MTest::New(alloc(), ins, ifTrue, ifFalse); + test->cacheOperandMightEmulateUndefined(constraints()); + return test; +} + +// A resume point is a mapping of stack slots to MDefinitions. It is used to +// capture the environment such that if a guard fails, and IonMonkey needs +// to exit back to the interpreter, the interpreter state can be +// reconstructed. +// +// We capture stack state at critical points: +// * (1) At the beginning of every basic block. +// * (2) After every effectful operation. +// +// As long as these two properties are maintained, instructions can +// be moved, hoisted, or, eliminated without problems, and ops without side +// effects do not need to worry about capturing state at precisely the +// right point in time. +// +// Effectful instructions, of course, need to capture state after completion, +// where the interpreter will not attempt to repeat the operation. For this, +// ResumeAfter must be used. The state is attached directly to the effectful +// instruction to ensure that no intermediate instructions could be injected +// in between by a future analysis pass. +// +// During LIR construction, if an instruction can bail back to the interpreter, +// we create an LSnapshot, which uses the last known resume point to request +// register/stack assignments for every live value. +bool +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) { + abortReason_ = AbortReason_Alloc; + return false; + } + ins->setResumePoint(resumePoint); + return true; +} + +bool +IonBuilder::resumeAt(MInstruction* ins, jsbytecode* pc) +{ + return resume(ins, pc, MResumePoint::ResumeAt); +} + +bool +IonBuilder::resumeAfter(MInstruction* ins) +{ + return resume(ins, pc, MResumePoint::ResumeAfter); +} + +bool +IonBuilder::maybeInsertResume() +{ + // Create a resume point at the current position, without an existing + // effectful instruction. This resume point is not necessary for correct + // behavior (see above), but is added to avoid holding any values from the + // previous resume point which are now dead. This shortens the live ranges + // of such values and improves register allocation. + // + // This optimization is not performed outside of loop bodies, where good + // register allocation is not as critical, in order to avoid creating + // excessive resume points. + + if (loopDepth_ == 0) + return true; + + MNop* ins = MNop::New(alloc()); + current->add(ins); + + return resumeAfter(ins); +} + +void +IonBuilder::maybeMarkEmpty(MDefinition* ins) +{ + MOZ_ASSERT(ins->type() == MIRType::Value); + + // When one of the operands has no type information, mark the output + // as having no possible types too. This is to avoid degrading + // subsequent analysis. + for (size_t i = 0; i < ins->numOperands(); i++) { + if (!ins->emptyResultTypeSet()) + continue; + + TemporaryTypeSet* types = alloc().lifoAlloc()->new_<TemporaryTypeSet>(); + if (types) + ins->setResultTypeSet(types); + } +} + +// Return whether property lookups can be performed effectlessly on clasp. +static bool +ClassHasEffectlessLookup(const Class* clasp) +{ + return (clasp == &UnboxedPlainObject::class_) || + (clasp == &UnboxedArrayObject::class_) || + IsTypedObjectClass(clasp) || + (clasp->isNative() && !clasp->getOpsLookupProperty()); +} + +// Return whether an object might have a property for name which is not +// accounted for by type information. +static bool +ObjectHasExtraOwnProperty(CompileCompartment* comp, TypeSet::ObjectKey* object, jsid id) +{ + // Some typed object properties are not reflected in type information. + if (object->isGroup() && object->group()->maybeTypeDescr()) + return object->group()->typeDescr().hasProperty(comp->runtime()->names(), id); + + const Class* clasp = object->clasp(); + + // Array |length| properties are not reflected in type information. + if (clasp == &ArrayObject::class_) + return JSID_IS_ATOM(id, comp->runtime()->names().length); + + // Resolve hooks can install new properties on objects on demand. + JSObject* singleton = object->isSingleton() ? object->singleton() : nullptr; + return ClassMayResolveId(comp->runtime()->names(), clasp, id, singleton); +} + +void +IonBuilder::insertRecompileCheck() +{ + // No need for recompile checks if this is the highest optimization level. + OptimizationLevel curLevel = optimizationInfo().level(); + if (IonOptimizations.isLastLevel(curLevel)) + return; + + // Add recompile check. + + // Get the topmost builder. The topmost script will get recompiled when + // warm-up counter is high enough to justify a higher optimization level. + IonBuilder* topBuilder = outermostBuilder(); + + // Add recompile check to recompile when the warm-up count reaches the + // threshold of the next optimization level. + OptimizationLevel nextLevel = IonOptimizations.nextLevel(curLevel); + const OptimizationInfo* info = IonOptimizations.get(nextLevel); + uint32_t warmUpThreshold = info->compilerWarmUpThreshold(topBuilder->script()); + MRecompileCheck* check = MRecompileCheck::New(alloc(), topBuilder->script(), warmUpThreshold, + MRecompileCheck::RecompileCheck_OptimizationLevel); + current->add(check); +} + +JSObject* +IonBuilder::testSingletonProperty(JSObject* obj, jsid id) +{ + // We would like to completely no-op property/global accesses which can + // produce only a particular JSObject. When indicating the access result is + // definitely an object, type inference does not account for the + // possibility that the property is entirely missing from the input object + // and its prototypes (if this happens, a semantic trigger would be hit and + // the pushed types updated, even if there is no type barrier). + // + // If the access definitely goes through obj, either directly or on the + // prototype chain, and the object has singleton type, then the type + // information for that property reflects the value that will definitely be + // read on accesses to the object. If the property is later deleted or + // reconfigured as a getter/setter then the type information for the + // property will change and trigger invalidation. + + while (obj) { + 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(compartment, objKey, id)) + return nullptr; + + obj = checkNurseryObject(obj->staticPrototype()); + } + + return nullptr; +} + +JSObject* +IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id) +{ + // As for TestSingletonProperty, but the input is any value in a type set + // rather than a specific object. + + 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::Int32: + case MIRType::Double: + key = JSProto_Number; + break; + + case MIRType::Boolean: + key = JSProto_Boolean; + break; + + case MIRType::Object: { + if (!types) + return nullptr; + + // For property accesses which may be on many objects, we just need to + // find a prototype common to all the objects; if that prototype + // has the singleton property, the access will not be on a missing property. + JSObject* singleton = nullptr; + for (unsigned i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + if (analysisContext) + key->ensureTrackedProperty(analysisContext, id); + + const Class* clasp = key->clasp(); + if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, 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())) { + // Test this type. + JSObject* thisSingleton = testSingletonProperty(proto, id); + if (!thisSingleton) + return nullptr; + if (singleton) { + if (thisSingleton != singleton) + return nullptr; + } else { + singleton = thisSingleton; + } + } else { + // Can't be on the prototype chain with no prototypes... + return nullptr; + } + } + return singleton; + } + default: + return nullptr; + } + + JSObject* proto = GetBuiltinPrototypePure(&script()->global(), key); + if (proto) + return testSingletonProperty(proto, id); + + return nullptr; +} + +ResultWithOOM<bool> +IonBuilder::testNotDefinedProperty(MDefinition* obj, jsid id) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types || types->unknownObject() || types->getKnownMIRType() != MIRType::Object) + return ResultWithOOM<bool>::ok(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 ResultWithOOM<bool>::fail(); + + if (!key->hasStableClassAndProto(constraints()) || key->unknownProperties()) + return ResultWithOOM<bool>::ok(false); + + const Class* clasp = key->clasp(); + if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, id)) + return ResultWithOOM<bool>::ok(false); + + // If the object is a singleton, we can do a lookup now to avoid + // unnecessary invalidations later on, in case the property types + // have not yet been instantiated. + if (key->isSingleton() && + key->singleton()->is<NativeObject>() && + key->singleton()->as<NativeObject>().lookupPure(id)) + { + return ResultWithOOM<bool>::ok(false); + } + + HeapTypeSetKey property = key->property(id); + if (property.isOwnProperty(constraints())) + return ResultWithOOM<bool>::ok(false); + + JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull()); + if (!proto) + break; + key = TypeSet::ObjectKey::get(proto); + } + } + + return ResultWithOOM<bool>::ok(true); +} + +bool +IonBuilder::pushTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind) +{ + MOZ_ASSERT(def == current->peek(-1)); + + MDefinition* replace = addTypeBarrier(current->pop(), observed, kind); + if (!replace) + return false; + + current->push(replace); + return true; +} + +// Given an observed type set, annotates the IR as much as possible: +// (1) If no type information is provided, the given value is returned. +// (2) If a single type definitely exists, and no type barrier is needed, +// then an infallible unbox instruction is returned. +// (3) If a type barrier is needed, but has an unknown type set, the given +// value is returned. +// (4) Lastly, a type barrier instruction is added and returned. +MDefinition* +IonBuilder::addTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind, + MTypeBarrier** pbarrier) +{ + // Barriers are never needed for instructions whose result will not be used. + if (BytecodeIsPopped(pc)) + return def; + + // If the instruction has no side effects, we'll resume the entire operation. + // The actual type barrier will occur in the interpreter. If the + // instruction is effectful, even if it has a singleton type, there + // must be a resume point capturing the original def, and resuming + // to that point will explicitly monitor the new type. + 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; +} + +bool +IonBuilder::pushDOMTypeBarrier(MInstruction* ins, TemporaryTypeSet* observed, JSFunction* func) +{ + MOZ_ASSERT(func && func->isNative() && func->jitInfo()); + + const JSJitInfo* jitinfo = func->jitInfo(); + bool barrier = DOMCallNeedsBarrier(jitinfo, observed); + // Need to be a bit careful: if jitinfo->returnType is JSVAL_TYPE_DOUBLE but + // types->getKnownMIRType() is MIRType::Int32, then don't unconditionally + // unbox as a double. Instead, go ahead and barrier on having an int type, + // since we know we need a barrier anyway due to the type mismatch. This is + // the only situation in which TI actually has more information about the + // JSValueType than codegen can, short of jitinfo->returnType just being + // JSVAL_TYPE_UNKNOWN. + 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; + } + MOZ_ASSERT(def->type() == definiteType); + return def; + } + replace = MUnbox::New(alloc(), def, definiteType, MUnbox::Infallible); + break; + } + } + + current->add(replace); + return replace; +} + +MDefinition* +IonBuilder::ensureDefiniteTypeSet(MDefinition* def, TemporaryTypeSet* types) +{ + // We cannot arbitrarily add a typeset to a definition. It can be shared + // in another path. So we always need to create a new MIR. + + // Use ensureDefiniteType to do unboxing. If that happened the type can + // be added on the newly created unbox operation. + MDefinition* replace = ensureDefiniteType(def, types->getKnownMIRType()); + if (replace != def) { + replace->setResultTypeSet(types); + return replace; + } + + // Don't replace if input type is more accurate than given typeset. + if (def->type() != types->getKnownMIRType()) { + MOZ_ASSERT(types->getKnownMIRType() == MIRType::Value); + return def; + } + + // Create a NOP mir instruction to filter the typeset. + MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), def, types); + current->add(filter); + return filter; +} + +static size_t +NumFixedSlots(JSObject* object) +{ + // Note: we can't use object->numFixedSlots() here, as this will read the + // shape and can race with the main thread if we are building off thread. + // The allocation kind and object class (which goes through the type) can + // be read freely, however. + 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); +} + +bool +IonBuilder::getStaticName(JSObject* staticObject, PropertyName* name, bool* psucceeded, + MDefinition* lexicalCheck) +{ + MOZ_ASSERT(*psucceeded == false); + + jsid id = NameToId(name); + + bool isGlobalLexical = staticObject->is<LexicalEnvironmentObject>() && + staticObject->as<LexicalEnvironmentObject>().isGlobal(); + MOZ_ASSERT(isGlobalLexical || + staticObject->is<GlobalObject>() || + staticObject->is<CallObject>() || + staticObject->is<ModuleEnvironmentObject>()); + MOZ_ASSERT(staticObject->isSingleton()); + + *psucceeded = true; + + // Always emit the lexical check. This could be optimized, but is + // currently not for simplicity's sake. + if (lexicalCheck) { + *psucceeded = false; + return true; + } + + TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject); + if (analysisContext) + staticKey->ensureTrackedProperty(analysisContext, NameToId(name)); + + if (staticKey->unknownProperties()) { + *psucceeded = false; + return true; + } + + HeapTypeSetKey property = staticKey->property(id); + if (!property.maybeTypes() || + !property.maybeTypes()->definiteProperty() || + property.nonData(constraints())) + { + // The property has been reconfigured as non-configurable, non-enumerable + // or non-writable. + *psucceeded = false; + return true; + } + + // Don't optimize global lexical bindings if they aren't initialized at + // compile time. + if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name)) { + *psucceeded = false; + return true; + } + + TemporaryTypeSet* types = bytecodeTypes(pc); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticKey, + name, types, /* updateObserved = */ true); + + if (barrier == BarrierKind::NoBarrier) { + // Try to inline properties holding a known constant object. + JSObject* singleton = types->maybeSingleton(); + if (singleton) { + if (testSingletonProperty(staticObject, id) == singleton) { + pushConstant(ObjectValue(*singleton)); + return true; + } + } + + // Try to inline properties that have never been overwritten. + Value constantValue; + if (property.constant(constraints(), &constantValue)) { + pushConstant(constantValue); + return true; + } + } + + if (!loadStaticSlot(staticObject, barrier, types, property.maybeTypes()->definiteSlot())) { + *psucceeded = false; + return false; + } + + return true; +} + +bool +IonBuilder::loadStaticSlot(JSObject* staticObject, BarrierKind barrier, TemporaryTypeSet* types, + uint32_t slot) +{ + if (barrier == BarrierKind::NoBarrier) { + // Try to inline properties that can only have one value. + MIRType knownType = types->getKnownMIRType(); + if (knownType == MIRType::Undefined) { + pushConstant(UndefinedValue()); + return true; + } + if (knownType == MIRType::Null) { + pushConstant(NullValue()); + return true; + } + } + + 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); +} + +// Whether a write of the given value may need a post-write barrier for GC purposes. +bool +jit::NeedsPostBarrier(MDefinition* value) +{ + if (!GetJitContext()->runtime->gcNursery().exists()) + return false; + return value->mightBeType(MIRType::Object); +} + +bool +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>() || + staticObject->is<CallObject>()); + + 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())) + { + // The property has been reconfigured as non-configurable, non-enumerable + // or non-writable. + return jsop_setprop(name); + } + + if (!CanWriteProperty(alloc(), constraints(), property, value)) + return jsop_setprop(name); + + // Don't optimize global lexical bindings if they aren't initialized at + // compile time. + if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name)) + return jsop_setprop(name); + + current->pop(); + + // Pop the bound object on the stack. + MDefinition* obj = current->pop(); + MOZ_ASSERT(&obj->toConstant()->toObject() == staticObject); + + if (NeedsPostBarrier(value)) + current->add(MPostWriteBarrier::New(alloc(), obj, value)); + + // If the property has a known type, we may be able to optimize typed stores by not + // storing the type tag. + MIRType slotType = MIRType::None; + MIRType knownType = property.knownMIRType(constraints()); + if (knownType != MIRType::Value) + slotType = knownType; + + bool needsBarrier = property.needsBarrier(constraints()); + return storeSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject), + value, needsBarrier, slotType); +} + +JSObject* +IonBuilder::testGlobalLexicalBinding(PropertyName* name) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME || + JSOp(*pc) == JSOP_GETGNAME || + JSOp(*pc) == JSOP_SETGNAME || + JSOp(*pc) == JSOP_STRICTSETGNAME); + + // The global isn't the global lexical env's prototype, but its enclosing + // env. Test for the existence of |name| manually on the global lexical + // env. If it is not found, look for it on the global itself. + + NativeObject* obj = &script()->global().lexicalEnvironment(); + TypeSet::ObjectKey* lexicalKey = TypeSet::ObjectKey::get(obj); + jsid id = NameToId(name); + if (analysisContext) + lexicalKey->ensureTrackedProperty(analysisContext, id); + + // If the property is not found on the global lexical env but it is found + // on the global and is configurable, try to freeze the typeset for its + // non-existence. If we don't have type information then fail. + // + // In the case that it is found on the global but is non-configurable, + // the binding cannot be shadowed by a global lexical binding. + 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; +} + +bool +IonBuilder::jsop_getgname(PropertyName* name) +{ + // Optimize undefined/NaN/Infinity first. We must ensure we handle these + // cases *exactly* like Baseline, because it's invalid to add an Ion IC or + // VM call (that might trigger invalidation) if there's no Baseline IC for + // this op. + if (name == names().undefined) { + pushConstant(UndefinedValue()); + return true; + } + if (name == names().NaN) { + pushConstant(compartment->runtime()->NaNValue()); + return true; + } + if (name == names().Infinity) { + pushConstant(compartment->runtime()->positiveInfinityValue()); + return true; + } + + if (JSObject* obj = testGlobalLexicalBinding(name)) { + bool emitted = false; + if (!getStaticName(obj, name, &emitted) || emitted) + return emitted; + + if (!forceInlineCaches() && obj->is<GlobalObject>()) { + TemporaryTypeSet* types = bytecodeTypes(pc); + MDefinition* globalObj = constant(ObjectValue(*obj)); + if (!getPropTryCommonGetter(&emitted, globalObj, name, types) || emitted) + return emitted; + } + } + + return jsop_getname(name); +} + +bool +IonBuilder::jsop_getname(PropertyName* name) +{ + MDefinition* object; + if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope()) { + MInstruction* global = constant(ObjectValue(script()->global().lexicalEnvironment())); + object = global; + } else { + current->push(current->environmentChain()); + object = current->pop(); + } + + MGetNameCache* ins; + if (JSOp(*GetNextPc(pc)) == JSOP_TYPEOF) + ins = MGetNameCache::New(alloc(), object, name, MGetNameCache::NAMETYPEOF); + else + ins = MGetNameCache::New(alloc(), object, name, MGetNameCache::NAME); + + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return false; + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::jsop_intrinsic(PropertyName* name) +{ + TemporaryTypeSet* types = bytecodeTypes(pc); + + Value vp = UndefinedValue(); + // If the intrinsic value doesn't yet exist, we haven't executed this + // opcode yet, so we need to get it and monitor the result. + if (!script()->global().maybeExistingIntrinsicValue(name, &vp)) { + MCallGetIntrinsicValue* ins = MCallGetIntrinsicValue::New(alloc(), name); + + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return false; + + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); + } + + if (types->empty()) + types->addType(TypeSet::GetValueType(vp), alloc().lifoAlloc()); + + // Bake in the intrinsic, guaranteed to exist because a non-empty typeset + // means the intrinsic was successfully gotten in the VM call above. + // Assert that TI agrees with us on the type. + MOZ_ASSERT(types->hasType(TypeSet::GetValueType(vp))); + + pushConstant(vp); + return true; +} + +bool +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)); + + PropertyName* localName = JSID_TO_STRING(shape->propid())->asAtom().asPropertyName(); + bool emitted = false; + if (!getStaticName(targetEnv, localName, &emitted)) + return false; + + if (!emitted) { + // This can happen if we don't have type information. + TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(targetEnv); + TemporaryTypeSet* types = bytecodeTypes(pc); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticKey, + name, types, /* updateObserved = */ true); + + if (!loadStaticSlot(targetEnv, barrier, types, shape->slot())) + return false; + } + + // In the rare case where this import hasn't been initialized already (we + // have an import cycle where modules reference each other's imports), emit + // a check. + if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { + MDefinition* checked = addLexicalCheck(current->pop()); + current->push(checked); + } + + return true; +} + +bool +IonBuilder::jsop_bindname(PropertyName* name) +{ + MDefinition* envChain; + if (analysis().usesEnvironmentChain()) { + envChain = current->environmentChain(); + } else { + // We take the slow path when trying to BINDGNAME a name that resolves + // to a 'const' or an uninitialized binding. + MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME); + envChain = constant(ObjectValue(script()->global().lexicalEnvironment())); + } + MBindNameCache* ins = MBindNameCache::New(alloc(), envChain, name, script(), pc); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_bindvar() +{ + MOZ_ASSERT(analysis().usesEnvironmentChain()); + MCallBindVar* ins = MCallBindVar::New(alloc(), current->environmentChain()); + current->add(ins); + current->push(ins); + return true; +} + +static MIRType +GetElemKnownType(bool needsHoleCheck, TemporaryTypeSet* types) +{ + MIRType knownType = types->getKnownMIRType(); + + // Null and undefined have no payload so they can't be specialized. + // Since folding null/undefined while building SSA is not safe (see the + // comment in IsPhiObservable), we just add an untyped load instruction + // and rely on pushTypeBarrier and DCE to replace it with a null/undefined + // constant. + if (knownType == MIRType::Undefined || knownType == MIRType::Null) + knownType = MIRType::Value; + + // Different architectures may want typed element reads which require + // hole checks to be done as either value or typed reads. + if (needsHoleCheck && !LIRGenerator::allowTypedElementHoleCheck()) + knownType = MIRType::Value; + + return knownType; +} + +bool +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()); + + // Always use a call if we are performing analysis and not actually + // emitting code, to simplify later analysis. + if (info().isAnalysis() || shouldAbortOnPreliminaryGroups(obj)) { + MInstruction* ins = MCallGetElement::New(alloc(), obj, index); + + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return false; + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); + } + + obj = maybeUnboxForPropertyAccess(obj); + if (obj->type() == MIRType::Object) + obj = convertUnboxedObjects(obj); + + bool emitted = false; + + if (!forceInlineCaches()) { + trackOptimizationAttempt(TrackedStrategy::GetElem_TypedObject); + if (!getElemTryTypedObject(&emitted, obj, index) || emitted) + return emitted; + + // Note: no trackOptimizationAttempt call is needed, getElemTryGetProp + // will call it. + if (!getElemTryGetProp(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_Dense); + if (!getElemTryDense(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_TypedStatic); + if (!getElemTryTypedStatic(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_TypedArray); + if (!getElemTryTypedArray(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_String); + if (!getElemTryString(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_Arguments); + if (!getElemTryArguments(&emitted, obj, index) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlined); + if (!getElemTryArgumentsInlined(&emitted, obj, index) || emitted) + return emitted; + } + + if (script()->argumentsHasVarBinding() && obj->mightBeType(MIRType::MagicOptimizedArguments)) + return abort("Type is not definitely lazy arguments."); + + trackOptimizationAttempt(TrackedStrategy::GetElem_InlineCache); + if (!getElemTryCache(&emitted, obj, index) || emitted) + return emitted; + + // Emit call. + MInstruction* ins = MCallGetElement::New(alloc(), obj, index); + + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return false; + + if (*pc == JSOP_CALLELEM && IsNullOrUndefined(obj->type())) { + // Due to inlining, it's possible the observed TypeSet is non-empty, + // even though we know |obj| is null/undefined and the MCallGetElement + // will throw. Don't push a TypeBarrier in this case, to avoid + // inlining the following (unreachable) JSOP_CALL. + return true; + } + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + // The next several failures are all due to types not predicting that we + // are definitely doing a getelem access on a typed object. + trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject); + + TypedObjectPrediction objPrediction = typedObjectPrediction(obj); + if (objPrediction.isUseless()) + return true; + + if (!objPrediction.ofArrayKind()) + return true; + + TypedObjectPrediction elemPrediction = objPrediction.arrayElementType(); + if (elemPrediction.isUseless()) + return true; + + uint32_t elemSize; + if (!elemPrediction.hasKnownSize(&elemSize)) + return true; + + switch (elemPrediction.kind()) { + case type::Simd: + // FIXME (bug 894105): load into a MIRType::float32x4 etc + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return true; + + 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* obj, + MDefinition* index, + TypedObjectPrediction objPrediction, + LinearSum* indexAsByteOffset) +{ + // Ensure index is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), index); + current->add(idInt32); + + // If we know the length statically from the type, just embed it. + // Otherwise, load it from the appropriate reserved slot on the + // typed object. We know it's an int32, so we can convert from + // Value to int32 using truncation. + int32_t lenOfAll; + MDefinition* length; + if (objPrediction.hasKnownArrayLength(&lenOfAll)) { + length = constantInt(lenOfAll); + + // If we are not loading the length from the object itself, only + // optimize if the array buffer can never be a detached array buffer. + 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)); +} + +bool +IonBuilder::getElemTryScalarElemOfTypedObject(bool* emitted, + MDefinition* obj, + MDefinition* index, + TypedObjectPrediction objPrediction, + TypedObjectPrediction elemPrediction, + uint32_t elemSize) +{ + MOZ_ASSERT(objPrediction.ofArrayKind()); + + // Must always be loading the same scalar type + ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); + MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); + + LinearSum indexAsByteOffset(alloc()); + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + trackOptimizationSuccess(); + *emitted = true; + + return pushScalarLoadFromTypedObject(obj, indexAsByteOffset, elemType); +} + +bool +IonBuilder::getElemTryReferenceElemOfTypedObject(bool* emitted, + MDefinition* obj, + MDefinition* index, + TypedObjectPrediction objPrediction, + TypedObjectPrediction elemPrediction) +{ + MOZ_ASSERT(objPrediction.ofArrayKind()); + + ReferenceTypeDescr::Type elemType = elemPrediction.referenceType(); + uint32_t elemSize = ReferenceTypeDescr::size(elemType); + + LinearSum indexAsByteOffset(alloc()); + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + trackOptimizationSuccess(); + *emitted = true; + + return pushReferenceLoadFromTypedObject(obj, indexAsByteOffset, elemType, nullptr); +} + +bool +IonBuilder::pushScalarLoadFromTypedObject(MDefinition* obj, + const LinearSum& byteOffset, + ScalarTypeDescr::Type elemType) +{ + uint32_t size = ScalarTypeDescr::size(elemType); + MOZ_ASSERT(size == ScalarTypeDescr::alignment(elemType)); + + // Find location within the owner object. + MDefinition* elements; + MDefinition* scaledOffset; + int32_t adjustment; + loadTypedObjectElements(obj, byteOffset, size, &elements, &scaledOffset, &adjustment); + + // Load the element. + MLoadUnboxedScalar* load = MLoadUnboxedScalar::New(alloc(), elements, scaledOffset, + elemType, + DoesNotRequireMemoryBarrier, + adjustment); + current->add(load); + current->push(load); + + // If we are reading in-bounds elements, we can use knowledge about + // the array type to determine the result type, even if the opcode has + // never executed. The known pushed type is only used to distinguish + // uint32 reads that may produce either doubles or integers. + TemporaryTypeSet* resultTypes = bytecodeTypes(pc); + bool allowDouble = resultTypes->hasType(TypeSet::DoubleType()); + + // Note: knownType is not necessarily in resultTypes; e.g. if we + // have only observed integers coming out of float array. + MIRType knownType = MIRTypeForTypedArrayRead(elemType, allowDouble); + + // Note: we can ignore the type barrier here, we know the type must + // be valid and unbarriered. Also, need not set resultTypeSet, + // because knownType is scalar and a resultTypeSet would provide + // no useful additional info. + load->setResultType(knownType); + + return true; +} + +bool +IonBuilder::pushReferenceLoadFromTypedObject(MDefinition* typedObj, + const LinearSum& byteOffset, + ReferenceTypeDescr::Type type, + PropertyName* name) +{ + // Find location within the owner object. + MDefinition* elements; + MDefinition* scaledOffset; + int32_t adjustment; + uint32_t alignment = ReferenceTypeDescr::alignment(type); + loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset, &adjustment); + + TemporaryTypeSet* observedTypes = bytecodeTypes(pc); + + MInstruction* load = nullptr; // initialize to silence GCC warning + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + typedObj, name, observedTypes); + + switch (type) { + case ReferenceTypeDescr::TYPE_ANY: { + // Make sure the barrier reflects the possibility of reading undefined. + 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 ReferenceTypeDescr::TYPE_OBJECT: { + // Make sure the barrier reflects the possibility of reading null. When + // there is no other barrier needed we include the null bailout with + // MLoadUnboxedObjectOrNull, which avoids the need to box the result + // for a type barrier instruction. + 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 ReferenceTypeDescr::TYPE_STRING: { + load = MLoadUnboxedString::New(alloc(), elements, scaledOffset, adjustment); + observedTypes->addType(TypeSet::StringType(), alloc().lifoAlloc()); + break; + } + } + + current->add(load); + current->push(load); + + return pushTypeBarrier(load, observedTypes, barrier); +} + +bool +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, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + return pushDerivedTypedObject(emitted, obj, indexAsByteOffset, + elemPrediction, elemTypeObj); +} + +bool +IonBuilder::pushDerivedTypedObject(bool* emitted, + MDefinition* obj, + const LinearSum& baseByteOffset, + TypedObjectPrediction derivedPrediction, + MDefinition* derivedTypeObj) +{ + // Find location within the owner object. + MDefinition* owner; + LinearSum ownerByteOffset(alloc()); + loadTypedObjectData(obj, &owner, &ownerByteOffset); + + if (!ownerByteOffset.add(baseByteOffset, 1)) + setForceAbort(); + + MDefinition* offset = ConvertLinearSum(alloc(), current, ownerByteOffset, + /* convertConstant = */ true); + + // Create the derived typed object. + MInstruction* derivedTypedObj = MNewDerivedTypedObject::New(alloc(), + derivedPrediction, + derivedTypeObj, + owner, + offset); + current->add(derivedTypedObj); + current->push(derivedTypedObj); + + // Determine (if possible) the class/proto that `derivedTypedObj` will + // have. For derived typed objects, the opacity will be the same as the + // incoming object from which the derived typed object is, well, derived. + // The prototype will be determined based on the type descriptor (and is + // immutable). + 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)); + + // Determine (if possible) the class/proto that the observed type set + // describes. + TemporaryTypeSet* observedTypes = bytecodeTypes(pc); + const Class* observedClass = observedTypes->getKnownClass(constraints()); + + // If expectedClass/expectedProto are both non-null (and hence known), we + // can predict precisely what object group derivedTypedObj will have. + // Therefore, if we observe that this group is already contained in the set + // of observedTypes, we can skip the barrier. + // + // Barriers still wind up being needed in some relatively + // rare cases: + // + // - if multiple kinds of typed objects flow into this point, + // in which case we will not be able to predict expectedClass + // nor expectedProto. + // + // - if the code has never executed, in which case the set of + // observed types will be incomplete. + // + // Barriers are particularly expensive here because they prevent + // us from optimizing the MNewDerivedTypedObject away. + JSObject* observedProto; + if (observedTypes->getCommonPrototype(constraints(), &observedProto) && + observedClass && observedProto && observedClass == expectedClass && + observedProto == expectedProto) + { + derivedTypedObj->setResultTypeSet(observedTypes); + } else { + if (!pushTypeBarrier(derivedTypedObj, observedTypes, BarrierKind::TypeSet)) + return false; + } + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getElemTryGetProp(bool* emitted, MDefinition* obj, MDefinition* index) +{ + // If index is a constant string or symbol, try to optimize this GETELEM + // as a GETPROP. + + MOZ_ASSERT(*emitted == false); + + MConstant* indexConst = index->maybeConstantValue(); + jsid id; + if (!indexConst || !ValueToIdPure(indexConst->toJSValue(), &id)) + return true; + + if (id != IdToTypeId(id)) + return true; + + TemporaryTypeSet* types = bytecodeTypes(pc); + + trackOptimizationAttempt(TrackedStrategy::GetProp_Constant); + if (!getPropTryConstant(emitted, obj, id, types) || *emitted) { + if (*emitted) + index->setImplicitlyUsedUnchecked(); + return *emitted; + } + + trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined); + if (!getPropTryNotDefined(emitted, obj, id, types) || *emitted) { + if (*emitted) + index->setImplicitlyUsedUnchecked(); + return *emitted; + } + + return true; +} + +bool +IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, index); + if (unboxedType == JSVAL_TYPE_MAGIC) { + if (!ElementAccessIsDenseNative(constraints(), obj, index)) { + trackOptimizationOutcome(TrackedOutcome::AccessNotDense); + return true; + } + } + + // Don't generate a fast path if there have been bounds check failures + // and this access might be on a sparse property. + if (ElementAccessHasExtraIndexedProperty(this, obj) && failedBoundsCheck_) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return true; + } + + // Don't generate a fast path if this pc has seen negative indexes accessed, + // which will not appear to be extra indexed properties. + if (inspector->hasSeenNegativeIndexGetElement(pc)) { + trackOptimizationOutcome(TrackedOutcome::ArraySeenNegativeIndex); + return true; + } + + if (!jsop_getelem_dense(obj, index, unboxedType)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +JSObject* +IonBuilder::getStaticTypedArrayObject(MDefinition* obj, MDefinition* index) +{ + Scalar::Type arrayType; + if (!ElementAccessIsTypedArray(constraints(), obj, index, &arrayType)) { + trackOptimizationOutcome(TrackedOutcome::AccessNotTypedArray); + return nullptr; + } + + if (!LIRGenerator::allowStaticTypedArrayAccesses()) { + trackOptimizationOutcome(TrackedOutcome::Disabled); + return nullptr; + } + + if (ElementAccessHasExtraIndexedProperty(this, obj)) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return nullptr; + } + + if (!obj->resultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return nullptr; + } + + JSObject* tarrObj = obj->resultTypeSet()->maybeSingleton(); + if (!tarrObj) { + trackOptimizationOutcome(TrackedOutcome::NotSingleton); + return nullptr; + } + + TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarrObj); + if (tarrKey->unknownProperties()) { + trackOptimizationOutcome(TrackedOutcome::UnknownProperties); + return nullptr; + } + + return tarrObj; +} + +bool +IonBuilder::getElemTryTypedStatic(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + JSObject* tarrObj = getStaticTypedArrayObject(obj, index); + if (!tarrObj) + return true; + + // LoadTypedArrayElementStatic currently treats uint32 arrays as int32. + Scalar::Type viewType = tarrObj->as<TypedArrayObject>().type(); + if (viewType == Scalar::Uint32) { + trackOptimizationOutcome(TrackedOutcome::StaticTypedArrayUint32); + return true; + } + + MDefinition* ptr = convertShiftToMaskForStaticTypedArray(index, viewType); + if (!ptr) + return true; + + // Emit LoadTypedArrayElementStatic. + + if (tarrObj->is<TypedArrayObject>()) { + TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarrObj); + tarrKey->watchStateChangeForTypedArrayData(constraints()); + } + + obj->setImplicitlyUsedUnchecked(); + index->setImplicitlyUsedUnchecked(); + + MLoadTypedArrayElementStatic* load = MLoadTypedArrayElementStatic::New(alloc(), tarrObj, ptr); + current->add(load); + current->push(load); + + // The load is infallible if an undefined result will be coerced to the + // appropriate numeric type if the read is out of bounds. The truncation + // analysis picks up some of these cases, but is incomplete with respect + // to others. For now, sniff the bytecode for simple patterns following + // the load which guarantee a truncation or numeric conversion. + if (viewType == Scalar::Float32 || viewType == Scalar::Float64) { + jsbytecode* next = pc + JSOP_GETELEM_LENGTH; + if (*next == JSOP_POS) + load->setInfallible(); + } else { + jsbytecode* next = pc + JSOP_GETELEM_LENGTH; + if (*next == JSOP_ZERO && *(next + JSOP_ZERO_LENGTH) == JSOP_BITOR) + load->setInfallible(); + } + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +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 true; + } + + // Emit typed getelem variant. + if (!jsop_getelem_typed(obj, index, arrayType)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getElemTryString(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + if (obj->type() != MIRType::String || !IsNumberType(index->type())) { + trackOptimizationOutcome(TrackedOutcome::AccessNotString); + return true; + } + + // If the index is expected to be out-of-bounds, don't optimize to avoid + // frequent bailouts. + if (bytecodeTypes(pc)->hasType(TypeSet::UndefinedType())) { + trackOptimizationOutcome(TrackedOutcome::OutOfBounds); + return true; + } + + // Emit fast path for string[index]. + MInstruction* idInt32 = MToInt32::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 true; +} + +bool +IonBuilder::getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + if (inliningDepth_ > 0) + return true; + + if (obj->type() != MIRType::MagicOptimizedArguments) + return true; + + // Emit GetFrameArgument. + + MOZ_ASSERT(!info().argsObjAliasesFormals()); + + // Type Inference has guaranteed this is an optimized arguments object. + obj->setImplicitlyUsedUnchecked(); + + // To ensure that we are not looking above the number of actual arguments. + MArgumentsLength* length = MArgumentsLength::New(alloc()); + current->add(length); + + // Ensure index is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), index); + current->add(idInt32); + index = idInt32; + + // Bailouts if we read more than the number of actual arguments. + index = addBoundsCheck(index, length); + + // Load the argument from the actual arguments. + MGetFrameArgument* load = MGetFrameArgument::New(alloc(), index, analysis_.hasSetArg()); + current->add(load); + current->push(load); + + TemporaryTypeSet* types = bytecodeTypes(pc); + if (!pushTypeBarrier(load, types, BarrierKind::TypeSet)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + if (inliningDepth_ == 0) + return true; + + if (obj->type() != MIRType::MagicOptimizedArguments) + return true; + + // Emit inlined arguments. + obj->setImplicitlyUsedUnchecked(); + + MOZ_ASSERT(!info().argsObjAliasesFormals()); + + // When the id is constant, we can just return the corresponding inlined argument + MConstant* indexConst = index->maybeConstantValue(); + if (indexConst && indexConst->type() == MIRType::Int32) { + 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 true; + } + + // inlined not constant not supported, yet. + return abort("NYI inlined not constant get argument element"); +} + +bool +IonBuilder::getElemTryCache(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + // Make sure we have at least an object. + if (!obj->mightBeType(MIRType::Object)) { + trackOptimizationOutcome(TrackedOutcome::NotObject); + return true; + } + + // Don't cache for strings. + if (obj->mightBeType(MIRType::String)) { + trackOptimizationOutcome(TrackedOutcome::GetElemStringNotCached); + return true; + } + + // Index should be integer, string, or symbol + if (!index->mightBeType(MIRType::Int32) && + !index->mightBeType(MIRType::String) && + !index->mightBeType(MIRType::Symbol)) + { + trackOptimizationOutcome(TrackedOutcome::IndexType); + return true; + } + + // Turn off cacheing if the element is int32 and we've seen non-native objects as the target + // of this getelem. + bool nonNativeGetElement = inspector->hasSeenNonNativeGetElement(pc); + if (index->mightBeType(MIRType::Int32) && nonNativeGetElement) { + trackOptimizationOutcome(TrackedOutcome::NonNativeReceiver); + return true; + } + + // Emit GetElementCache. + + TemporaryTypeSet* types = bytecodeTypes(pc); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, + nullptr, types); + + // Always add a barrier if the index might be a string or symbol, so that + // the cache can attach stubs for particular properties. + if (index->mightBeType(MIRType::String) || index->mightBeType(MIRType::Symbol)) + barrier = BarrierKind::TypeSet; + + MGetPropertyCache* ins = MGetPropertyCache::New(alloc(), obj, index, + barrier == BarrierKind::TypeSet); + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return false; + + // Spice up type information. + if (index->type() == MIRType::Int32 && barrier == BarrierKind::NoBarrier) { + bool needHoleCheck = !ElementAccessIsPacked(constraints(), obj); + MIRType knownType = GetElemKnownType(needHoleCheck, types); + + if (knownType != MIRType::Value && knownType != MIRType::Double) + ins->setResultType(knownType); + } + + if (!pushTypeBarrier(ins, types, barrier)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +TemporaryTypeSet* +IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id) +{ + if (objTypes->unknownObject() || objTypes->getObjectCount() == 0) + return nullptr; + + TemporaryTypeSet empty; + TemporaryTypeSet* acc = ∅ + 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->unknownProperties()) + return nullptr; + + HeapTypeSetKey property = key->property(id); + HeapTypeSet* currentSet = property.maybeTypes(); + + if (!currentSet || currentSet->unknown()) + return nullptr; + + properties.infallibleAppend(property); + acc = TypeSet::unionSets(acc, currentSet, lifoAlloc); + if (!acc) + return nullptr; + } + + // Freeze all the properties associated with the refined type set. + for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++) + i->freeze(constraints()); + + return acc; +} + +bool +IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index, JSValueType unboxedType) +{ + TemporaryTypeSet* types = bytecodeTypes(pc); + + MOZ_ASSERT(index->type() == MIRType::Int32 || index->type() == MIRType::Double); + if (JSOp(*pc) == JSOP_CALLELEM) { + // Indexed call on an element of an array. Populate the observed types + // with any objects that could be in the array, to avoid extraneous + // type barriers. + AddObjectsForPropertyRead(obj, nullptr, types); + } + + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, + nullptr, types); + bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj); + + // Reads which are on holes in the object do not have to bail out if + // undefined values have been observed at this access site and the access + // cannot hit another indexed property on the object or its prototypes. + bool readOutOfBounds = + types->hasType(TypeSet::UndefinedType()) && + !ElementAccessHasExtraIndexedProperty(this, obj); + + MIRType knownType = MIRType::Value; + if (unboxedType == JSVAL_TYPE_MAGIC && barrier == BarrierKind::NoBarrier) + knownType = GetElemKnownType(needsHoleCheck, types); + + // Ensure index is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), index); + current->add(idInt32); + index = idInt32; + + // Get the elements vector. + MInstruction* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC); + current->add(elements); + + // Note: to help GVN, use the original MElements instruction and not + // MConvertElementsToDoubles as operand. This is fine because converting + // elements to double does not change the initialized length. + MInstruction* initLength = initializedLength(obj, elements, unboxedType); + + // If we can load the element as a definite double, make sure to check that + // the array has been converted to homogenous doubles first. + 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 = + unboxedType == JSVAL_TYPE_MAGIC && + barrier == BarrierKind::NoBarrier && + loopDepth_ && + inBounds && + knownType == MIRType::Double && + objTypes && + objTypes->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles; + if (loadDouble) + elements = addConvertElementsToDoubles(elements); + + MInstruction* load; + + if (!readOutOfBounds) { + // This load should not return undefined, so likely we're reading + // in-bounds elements, and the array is packed or its holes are not + // read. This is the best case: we can separate the bounds check for + // hoisting. + index = addBoundsCheck(index, initLength); + + if (unboxedType != JSVAL_TYPE_MAGIC) { + load = loadUnboxedValue(elements, 0, index, unboxedType, barrier, types); + } else { + load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble); + current->add(load); + } + } else { + // This load may return undefined, so assume that we *can* read holes, + // or that we can read out-of-bounds accesses. In this case, the bounds + // check is part of the opcode. + load = MLoadElementHole::New(alloc(), elements, index, initLength, + unboxedType, needsHoleCheck); + current->add(load); + + // If maybeUndefined was true, the typeset must have undefined, and + // then either additional types or a barrier. This means we should + // never have a typed version of LoadElementHole. + MOZ_ASSERT(knownType == MIRType::Value); + } + + if (knownType != MIRType::Value) { + if (unboxedType == JSVAL_TYPE_MAGIC) + 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; +} + +void +IonBuilder::addTypedArrayLengthAndData(MDefinition* obj, + BoundsChecking checking, + MDefinition** index, + MInstruction** length, MInstruction** elements) +{ + MOZ_ASSERT((index != nullptr) == (elements != nullptr)); + + JSObject* tarr = nullptr; + + if (MConstant* objConst = obj->maybeConstantValue()) { + if (objConst->type() == MIRType::Object) + tarr = &objConst->toObject(); + } else if (TemporaryTypeSet* types = obj->resultTypeSet()) { + tarr = types->maybeSingleton(); + } + + if (tarr) { + SharedMem<void*> data = tarr->as<TypedArrayObject>().viewDataEither(); + // Bug 979449 - Optimistically embed the elements and use TI to + // invalidate if we move them. + bool isTenured = !tarr->runtimeFromMainThread()->gc.nursery.isInside(data); + if (isTenured && tarr->isSingleton()) { + // The 'data' pointer of TypedArrayObject can change in rare circumstances + // (ArrayBufferObject::changeContents). + TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarr); + if (!tarrKey->unknownProperties()) { + if (tarr->is<TypedArrayObject>()) + tarrKey->watchStateChangeForTypedArrayData(constraints()); + + obj->setImplicitlyUsedUnchecked(); + + int32_t len = AssertedCast<int32_t>(tarr->as<TypedArrayObject>().length()); + *length = MConstant::New(alloc(), Int32Value(len)); + current->add(*length); + + if (index) { + if (checking == DoBoundsCheck) + *index = addBoundsCheck(*index, *length); + + *elements = MConstantElements::New(alloc(), data); + 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); + } +} + +MDefinition* +IonBuilder::convertShiftToMaskForStaticTypedArray(MDefinition* id, + Scalar::Type viewType) +{ + trackOptimizationOutcome(TrackedOutcome::StaticTypedArrayCantComputeMask); + + // No shifting is necessary if the typed array has single byte elements. + if (TypedArrayShift(viewType) == 0) + return id; + + // If the index is an already shifted constant, undo the shift to get the + // absolute offset being accessed. + if (MConstant* idConst = id->maybeConstantValue()) { + if (idConst->type() == MIRType::Int32) { + int32_t index = idConst->toInt32(); + MConstant* offset = MConstant::New(alloc(), Int32Value(index << TypedArrayShift(viewType))); + current->add(offset); + return offset; + } + } + + if (!id->isRsh() || id->isEffectful()) + return nullptr; + + MConstant* shiftAmount = id->toRsh()->rhs()->maybeConstantValue(); + if (!shiftAmount || shiftAmount->type() != MIRType::Int32) + return nullptr; + if (uint32_t(shiftAmount->toInt32()) != TypedArrayShift(viewType)) + return nullptr; + + // Instead of shifting, mask off the low bits of the index so that + // a non-scaled access on the typed array can be performed. + MConstant* mask = MConstant::New(alloc(), Int32Value(~((1 << shiftAmount->toInt32()) - 1))); + MBitAnd* ptr = MBitAnd::New(alloc(), id->getOperand(0), mask); + + ptr->infer(nullptr, nullptr); + MOZ_ASSERT(!ptr->isEffectful()); + + current->add(mask); + current->add(ptr); + + return ptr; +} + +bool +IonBuilder::jsop_getelem_typed(MDefinition* obj, MDefinition* index, + Scalar::Type arrayType) +{ + TemporaryTypeSet* types = bytecodeTypes(pc); + + bool maybeUndefined = types->hasType(TypeSet::UndefinedType()); + + // Reading from an Uint32Array will result in a double for values + // that don't fit in an int32. We have to bailout if this happens + // and the instruction is not known to return a double. + bool allowDouble = types->hasType(TypeSet::DoubleType()); + + // Ensure id is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), index); + current->add(idInt32); + index = idInt32; + + if (!maybeUndefined) { + // Assume the index is in range, so that we can hoist the length, + // elements vector and bounds check. + + // If we are reading in-bounds elements, we can use knowledge about + // the array type to determine the result type, even if the opcode has + // never executed. The known pushed type is only used to distinguish + // uint32 reads that may produce either doubles or integers. + MIRType knownType = MIRTypeForTypedArrayRead(arrayType, allowDouble); + + // Get length, bounds-check, then get elements, and add all instructions. + MInstruction* length; + MInstruction* elements; + addTypedArrayLengthAndData(obj, DoBoundsCheck, &index, &length, &elements); + + // Load the element. + MLoadUnboxedScalar* load = MLoadUnboxedScalar::New(alloc(), elements, index, arrayType); + current->add(load); + current->push(load); + + // Note: we can ignore the type barrier here, we know the type must + // be valid and unbarriered. + load->setResultType(knownType); + return true; + } else { + // We need a type barrier if the array's element type has never been + // observed (we've only read out-of-bounds values). Note that for + // Uint32Array, we only check for int32: if allowDouble is false we + // will bailout when we read a double. + 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"); + } + + // Assume we will read out-of-bound values. In this case the + // bounds check will be part of the instruction, and the instruction + // will always return a Value. + MLoadTypedArrayElementHole* load = + MLoadTypedArrayElementHole::New(alloc(), obj, index, arrayType, allowDouble); + current->add(load); + current->push(load); + + return pushTypeBarrier(load, types, barrier); + } +} + +bool +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_TypedObject); + if (!setElemTryTypedObject(&emitted, object, index, value) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::SetElem_TypedStatic); + if (!setElemTryTypedStatic(&emitted, object, index, value) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::SetElem_TypedArray); + if (!setElemTryTypedArray(&emitted, object, index, value) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::SetElem_Dense); + SetElemICInspector icInspect(inspector->setElemICInspector(pc)); + bool writeHole = icInspect.sawOOBDenseWrite(); + if (!setElemTryDense(&emitted, object, index, value, writeHole) || emitted) + return emitted; + + trackOptimizationAttempt(TrackedStrategy::SetElem_Arguments); + if (!setElemTryArguments(&emitted, object, index, value) || emitted) + return emitted; + } + + if (script()->argumentsHasVarBinding() && + object->mightBeType(MIRType::MagicOptimizedArguments) && + info().analysisMode() != Analysis_ArgumentsUsage) + { + return abort("Type is not definitely lazy arguments."); + } + + trackOptimizationAttempt(TrackedStrategy::SetElem_InlineCache); + if (!setElemTryCache(&emitted, object, index, value) || emitted) + return emitted; + + // Emit call. + MInstruction* ins = MCallSetElement::New(alloc(), object, index, value, IsStrictSetPC(pc)); + current->add(ins); + current->push(value); + + return resumeAfter(ins); +} + +bool +IonBuilder::setElemTryTypedObject(bool* emitted, MDefinition* obj, + MDefinition* index, MDefinition* value) +{ + MOZ_ASSERT(*emitted == false); + + // The next several failures are all due to types not predicting that we + // are definitely doing a getelem access on a typed object. + trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject); + + TypedObjectPrediction objPrediction = typedObjectPrediction(obj); + if (objPrediction.isUseless()) + return true; + + if (!objPrediction.ofArrayKind()) + return true; + + TypedObjectPrediction elemPrediction = objPrediction.arrayElementType(); + if (elemPrediction.isUseless()) + return true; + + uint32_t elemSize; + if (!elemPrediction.hasKnownSize(&elemSize)) + return true; + + switch (elemPrediction.kind()) { + case type::Simd: + // FIXME (bug 894105): store a MIRType::float32x4 etc + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return true; + + 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: + // Not yet optimized. + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return true; + } + + MOZ_CRASH("Bad kind"); +} + +bool +IonBuilder::setElemTryReferenceElemOfTypedObject(bool* emitted, + MDefinition* obj, + MDefinition* index, + TypedObjectPrediction objPrediction, + MDefinition* value, + TypedObjectPrediction elemPrediction) +{ + ReferenceTypeDescr::Type elemType = elemPrediction.referenceType(); + uint32_t elemSize = ReferenceTypeDescr::size(elemType); + + LinearSum indexAsByteOffset(alloc()); + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + if (!storeReferenceTypedObjectValue(obj, indexAsByteOffset, elemType, value, nullptr)) + return true; + + current->push(value); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::setElemTryScalarElemOfTypedObject(bool* emitted, + MDefinition* obj, + MDefinition* index, + TypedObjectPrediction objPrediction, + MDefinition* value, + TypedObjectPrediction elemPrediction, + uint32_t elemSize) +{ + // Must always be loading the same scalar type + ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); + MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); + + LinearSum indexAsByteOffset(alloc()); + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + // Store the element + if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, value)) + return false; + + current->push(value); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::setElemTryTypedStatic(bool* emitted, MDefinition* object, + MDefinition* index, MDefinition* value) +{ + MOZ_ASSERT(*emitted == false); + + JSObject* tarrObj = getStaticTypedArrayObject(object, index); + if (!tarrObj) + return true; + + SharedMem<void*> viewData = tarrObj->as<TypedArrayObject>().viewDataEither(); + if (tarrObj->runtimeFromMainThread()->gc.nursery.isInside(viewData)) + return true; + + Scalar::Type viewType = tarrObj->as<TypedArrayObject>().type(); + MDefinition* ptr = convertShiftToMaskForStaticTypedArray(index, viewType); + if (!ptr) + return true; + + // Emit StoreTypedArrayElementStatic. + + if (tarrObj->is<TypedArrayObject>()) { + TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarrObj); + tarrKey->watchStateChangeForTypedArrayData(constraints()); + } + + object->setImplicitlyUsedUnchecked(); + index->setImplicitlyUsedUnchecked(); + + // Clamp value to [0, 255] for Uint8ClampedArray. + MDefinition* toWrite = value; + if (viewType == Scalar::Uint8Clamped) { + toWrite = MClampToUint8::New(alloc(), value); + current->add(toWrite->toInstruction()); + } + + MInstruction* store = MStoreTypedArrayElementStatic::New(alloc(), tarrObj, ptr, toWrite); + current->add(store); + current->push(value); + + if (!resumeAfter(store)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +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 true; + } + + // Emit typed setelem variant. + if (!jsop_setelem_typed(arrayType, object, index, value)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::setElemTryDense(bool* emitted, MDefinition* object, + MDefinition* index, MDefinition* value, bool writeHole) +{ + MOZ_ASSERT(*emitted == false); + + JSValueType unboxedType = UnboxedArrayElementType(constraints(), object, index); + if (unboxedType == JSVAL_TYPE_MAGIC) { + if (!ElementAccessIsDenseNative(constraints(), object, index)) { + trackOptimizationOutcome(TrackedOutcome::AccessNotDense); + return true; + } + } + + if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, + &object, nullptr, &value, /* canModify = */ true)) + { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return true; + } + + if (!object->resultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return true; + } + + TemporaryTypeSet::DoubleConversion conversion = + object->resultTypeSet()->convertDoubleElements(constraints()); + + // If AmbiguousDoubleConversion, only handle int32 values for now. + if (conversion == TemporaryTypeSet::AmbiguousDoubleConversion && + value->type() != MIRType::Int32) + { + trackOptimizationOutcome(TrackedOutcome::ArrayDoubleConversion); + return true; + } + + // Don't generate a fast path if there have been bounds check failures + // and this access might be on a sparse property. + if (ElementAccessHasExtraIndexedProperty(this, object) && failedBoundsCheck_) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return true; + } + + // Emit dense setelem variant. + if (!jsop_setelem_dense(conversion, object, index, value, unboxedType, writeHole, emitted)) + return false; + + if (!*emitted) { + trackOptimizationOutcome(TrackedOutcome::NonWritableProperty); + return true; + } + + trackOptimizationSuccess(); + return true; +} + +bool +IonBuilder::setElemTryArguments(bool* emitted, MDefinition* object, + MDefinition* index, MDefinition* value) +{ + MOZ_ASSERT(*emitted == false); + + if (object->type() != MIRType::MagicOptimizedArguments) + return true; + + // Arguments are not supported yet. + return abort("NYI arguments[]="); +} + +bool +IonBuilder::setElemTryCache(bool* emitted, MDefinition* object, + MDefinition* index, MDefinition* value) +{ + MOZ_ASSERT(*emitted == false); + + if (!object->mightBeType(MIRType::Object)) { + trackOptimizationOutcome(TrackedOutcome::NotObject); + return true; + } + + if (!index->mightBeType(MIRType::Int32) && + !index->mightBeType(MIRType::String) && + !index->mightBeType(MIRType::Symbol)) + { + trackOptimizationOutcome(TrackedOutcome::IndexType); + return true; + } + + bool barrier = true; + bool indexIsInt32 = index->type() == MIRType::Int32; + + if (indexIsInt32 && + !PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, + &object, nullptr, &value, /* canModify = */ true)) + { + barrier = false; + } + + // We can avoid worrying about holes in the IC if we know a priori we are safe + // from them. If TI can guard that there are no indexed properties on the prototype + // chain, we know that we anen't missing any setters by overwriting the hole with + // another value. + bool guardHoles = ElementAccessHasExtraIndexedProperty(this, object); + + // Make sure the object being written to doesn't have copy on write elements. + const Class* clasp = object->resultTypeSet() ? object->resultTypeSet()->getKnownClass(constraints()) : nullptr; + bool checkNative = !clasp || !clasp->isNative(); + object = addMaybeCopyElementsForWrite(object, checkNative); + + if (NeedsPostBarrier(value)) { + if (indexIsInt32) + current->add(MPostWriteElementBarrier::New(alloc(), object, value, index)); + else + current->add(MPostWriteBarrier::New(alloc(), object, value)); + } + + // Emit SetPropertyCache. + bool strict = JSOp(*pc) == JSOP_STRICTSETELEM; + MSetPropertyCache* ins = + MSetPropertyCache::New(alloc(), object, index, value, strict, barrier, guardHoles); + current->add(ins); + current->push(value); + + if (!resumeAfter(ins)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, + MDefinition* obj, MDefinition* id, MDefinition* value, + JSValueType unboxedType, bool writeHole, bool* emitted) +{ + MOZ_ASSERT(*emitted == false); + + MIRType elementType = MIRType::None; + if (unboxedType == JSVAL_TYPE_MAGIC) + elementType = DenseNativeElementType(constraints(), obj); + bool packed = ElementAccessIsPacked(constraints(), obj); + + // Writes which are on holes in the object do not have to bail out if they + // cannot hit another indexed property on the object or its prototypes. + bool hasNoExtraIndexedProperty = !ElementAccessHasExtraIndexedProperty(this, obj); + + bool mayBeFrozen = ElementAccessMightBeFrozen(constraints(), obj); + + if (mayBeFrozen && !hasNoExtraIndexedProperty) { + // FallibleStoreElement does not know how to deal with extra indexed + // properties on the prototype. This case should be rare so we fall back + // to an IC. + return true; + } + + *emitted = true; + + // Ensure id is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), id); + current->add(idInt32); + id = idInt32; + + if (NeedsPostBarrier(value)) + current->add(MPostWriteElementBarrier::New(alloc(), obj, value, id)); + + // Copy the elements vector if necessary. + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); + + // Get the elements vector. + MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC); + current->add(elements); + + // Ensure the value is a double, if double conversion might be needed. + 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"); + } + + // Use MStoreElementHole if this SETELEM has written to out-of-bounds + // indexes in the past. Otherwise, use MStoreElement so that we can hoist + // the initialized length and bounds check. + // If an object may have been frozen, no previous expectation hold and we + // fallback to MFallibleStoreElement. + MInstruction* store; + MStoreElementCommon* common = nullptr; + if (writeHole && hasNoExtraIndexedProperty && !mayBeFrozen) { + MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue, unboxedType); + store = ins; + common = ins; + + current->add(ins); + current->push(value); + } else if (mayBeFrozen) { + MOZ_ASSERT(hasNoExtraIndexedProperty, + "FallibleStoreElement codegen assumes no extra indexed properties"); + + bool strict = IsStrictSetPC(pc); + MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id, + newValue, unboxedType, strict); + store = ins; + common = ins; + + current->add(ins); + current->push(value); + } else { + MInstruction* initLength = initializedLength(obj, elements, unboxedType); + + id = addBoundsCheck(id, initLength); + bool needsHoleCheck = !packed && !hasNoExtraIndexedProperty; + + if (unboxedType != JSVAL_TYPE_MAGIC) { + store = storeUnboxedValue(obj, elements, 0, id, unboxedType, newValue); + } else { + MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck); + store = ins; + common = ins; + + current->add(store); + } + + current->push(value); + } + + if (!resumeAfter(store)) + return false; + + if (common) { + // Determine whether a write barrier is required. + if (obj->resultTypeSet()->propertyNeedsBarrier(constraints(), JSID_VOID)) + common->setNeedsBarrier(); + + if (elementType != MIRType::None && packed) + common->setElementType(elementType); + } + + return true; +} + + +bool +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"); + + // Ensure id is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), id); + current->add(idInt32); + id = idInt32; + + // Get length, bounds-check, then get elements, and add all instructions. + MInstruction* length; + MInstruction* elements; + BoundsChecking checking = expectOOB ? SkipBoundsCheck : DoBoundsCheck; + addTypedArrayLengthAndData(obj, checking, &id, &length, &elements); + + // Clamp value to [0, 255] for Uint8ClampedArray. + MDefinition* toWrite = value; + if (arrayType == Scalar::Uint8Clamped) { + toWrite = MClampToUint8::New(alloc(), value); + current->add(toWrite->toInstruction()); + } + + // Store the value. + 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); +} + +bool +IonBuilder::jsop_length() +{ + if (jsop_length_fastPath()) + return true; + + 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(); + + // Compute the length for array objects. + 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); + + // Read length. + MArrayLength* length = MArrayLength::New(alloc(), elements); + current->add(length); + current->push(length); + return true; + } + + // Compute the length for unboxed array objects. + if (UnboxedArrayElementType(constraints(), obj, nullptr) != JSVAL_TYPE_MAGIC && + !objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW)) + { + current->pop(); + + MUnboxedArrayLength* length = MUnboxedArrayLength::New(alloc(), obj); + current->add(length); + current->push(length); + return true; + } + + // Compute the length for array typed objects. + 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; +} + +bool +IonBuilder::jsop_arguments() +{ + if (info().needsArgsObj()) { + current->push(current->argumentsObject()); + return true; + } + MOZ_ASSERT(lazyArguments_); + current->push(lazyArguments_); + return true; +} + +bool +IonBuilder::jsop_newtarget() +{ + if (!info().funMaybeLazy()) { + MOZ_ASSERT(!info().script()->isForEval()); + pushConstant(NullValue()); + return true; + } + + MOZ_ASSERT(info().funMaybeLazy()); + + if (info().funMaybeLazy()->isArrow()) { + MArrowNewTarget* arrowNewTarget = MArrowNewTarget::New(alloc(), getCallee()); + current->add(arrowNewTarget); + current->push(arrowNewTarget); + return true; + } + + if (inliningDepth_ == 0) { + MNewTarget* newTarget = MNewTarget::New(alloc()); + current->add(newTarget); + current->push(newTarget); + return true; + } + + if (!inlineCallInfo_->constructing()) { + pushConstant(UndefinedValue()); + return true; + } + + current->push(inlineCallInfo_->getNewTarget()); + return true; +} + +bool +IonBuilder::jsop_rest() +{ + if (info().analysisMode() == Analysis_ArgumentsUsage) { + // There's no BaselineScript with the template object. Just push a + // dummy value, it does not affect the arguments analysis. + MUnknownValue* unknown = MUnknownValue::New(alloc()); + current->add(unknown); + current->push(unknown); + return true; + } + + ArrayObject* templateObject = &inspector->getTemplateObject(pc)->as<ArrayObject>(); + + if (inliningDepth_ == 0) { + // We don't know anything about the callee. + MArgumentsLength* numActuals = MArgumentsLength::New(alloc()); + current->add(numActuals); + + // Pass in the number of actual arguments, the number of formals (not + // including the rest parameter slot itself), and the template object. + MRest* rest = MRest::New(alloc(), constraints(), numActuals, info().nargs() - 1, + templateObject); + current->add(rest); + current->push(rest); + return true; + } + + // We know the exact number of arguments the callee pushed. + unsigned numActuals = inlineCallInfo_->argc(); + unsigned numFormals = info().nargs() - 1; + unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; + + if (!jsop_newarray(numRest)) + return false; + + if (numRest == 0) { + // No more updating to do. (Note that in this one case the length from + // the template object is already correct.) + return true; + } + + MDefinition *array = current->peek(-1); + MElements* elements = MElements::New(alloc(), array); + current->add(elements); + + // Unroll the argument copy loop. We don't need to do any bounds or hole + // checking here. + MConstant* index = nullptr; + for (unsigned i = numFormals; i < numActuals; i++) { + index = MConstant::New(alloc(), Int32Value(i - numFormals)); + current->add(index); + + MDefinition* arg = inlineCallInfo_->argv()[i]; + MStoreElement* store = MStoreElement::New(alloc(), elements, index, arg, + /* needsHoleCheck = */ false); + current->add(store); + + if (NeedsPostBarrier(arg)) + current->add(MPostWriteBarrier::New(alloc(), array, arg)); + } + + // The array's length is incorrectly 0 now, from the template object + // created by BaselineCompiler::emit_JSOP_REST() before the actual argument + // count was known. Set the correct length now that we know that count. + MSetArrayLength* length = MSetArrayLength::New(alloc(), elements, index); + current->add(length); + + // Update the initialized length for all the (necessarily non-hole) + // elements added. + MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, index); + current->add(initLength); + + return true; +} + +bool +IonBuilder::jsop_checkisobj(uint8_t kind) +{ + MDefinition* toCheck = current->peek(-1); + + if (toCheck->type() == MIRType::Object) { + toCheck->setImplicitlyUsedUnchecked(); + return true; + } + + MCheckIsObj* check = MCheckIsObj::New(alloc(), current->pop(), kind); + current->add(check); + current->push(check); + return true; +} + +bool +IonBuilder::jsop_checkobjcoercible() +{ + MDefinition* toCheck = current->peek(-1); + + if (!toCheck->mightBeType(MIRType::Undefined) && + !toCheck->mightBeType(MIRType::Null)) + { + toCheck->setImplicitlyUsedUnchecked(); + return true; + } + + MOZ_ASSERT(toCheck->type() == MIRType::Value || + toCheck->type() == MIRType::Null || + toCheck->type() == MIRType::Undefined); + + // If we want to squeeze more perf here, we can throw without checking, + // if IsNullOrUndefined(toCheck->type()). Since this is a failure case, + // it should be OK. + MCheckObjCoercible* check = MCheckObjCoercible::New(alloc(), current->pop()); + current->add(check); + current->push(check); + return resumeAfter(check); +} + +uint32_t +IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, 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(NameToId(name)); + if (!property.maybeTypes() || + !property.maybeTypes()->definiteProperty() || + property.nonData(constraints())) + { + trackOptimizationOutcome(TrackedOutcome::NotFixedSlot); + return UINT32_MAX; + } + + // Definite slots will always be fixed slots when they are in the + // allowable range for fixed slots, except for objects which were + // converted from unboxed objects and have a smaller allocation size. + size_t nfixed = NativeObject::MAX_FIXED_SLOTS; + if (ObjectGroup* group = key->group()->maybeOriginalUnboxedGroup()) + nfixed = gc::GetGCKindSlots(group->unboxedLayout().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, PropertyName* name, 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; + } + + UnboxedLayout* layout = key->group()->maybeUnboxedLayout(); + if (!layout) { + trackOptimizationOutcome(TrackedOutcome::NotUnboxed); + return UINT32_MAX; + } + + const UnboxedLayout::Property* property = layout->lookup(name); + 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; +} + +bool +IonBuilder::jsop_runonce() +{ + MRunOncePrologue* ins = MRunOncePrologue::New(alloc()); + current->add(ins); + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_not() +{ + MDefinition* value = current->pop(); + + MNot* ins = MNot::New(alloc(), value, constraints()); + current->add(ins); + current->push(ins); + return true; +} + +bool +IonBuilder::objectsHaveCommonPrototype(TemporaryTypeSet* types, PropertyName* name, + bool isGetter, JSObject* foundProto, bool* guardGlobal) +{ + // With foundProto a prototype with a getter or setter for name, return + // whether looking up name on any object in |types| will go through + // foundProto, i.e. all the objects have foundProto on their prototype + // chain and do not have a property for name before reaching foundProto. + + // No sense looking if we don't know what's going on. + if (!types || types->unknownObject()) + return false; + *guardGlobal = false; + + for (unsigned i = 0; i < types->getObjectCount(); i++) { + if (types->getSingleton(i) == foundProto) + continue; + + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + while (key) { + if (key->unknownProperties()) + return false; + + const Class* clasp = key->clasp(); + if (!ClassHasEffectlessLookup(clasp)) + return false; + JSObject* singleton = key->isSingleton() ? key->singleton() : nullptr; + if (ObjectHasExtraOwnProperty(compartment, key, NameToId(name))) { + if (!singleton || !singleton->is<GlobalObject>()) + return false; + *guardGlobal = true; + } + + // Look for a getter/setter on the class itself which may need + // to be called. + if (isGetter && clasp->getOpsGetProperty()) + return false; + if (!isGetter && clasp->getOpsSetProperty()) + return false; + + // Test for isOwnProperty() without freezing. If we end up + // optimizing, freezePropertiesForCommonPropFunc will freeze the + // property type sets later on. + HeapTypeSetKey property = key->property(NameToId(name)); + if (TypeSet* types = property.maybeTypes()) { + if (!types->empty() || types->nonDataProperty()) + return false; + } + if (singleton) { + if (CanHaveEmptyPropertyTypesForOwnProperty(singleton)) { + MOZ_ASSERT(singleton->is<GlobalObject>()); + *guardGlobal = true; + } + } + + JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull()); + + if (proto == foundProto) + break; + if (!proto) { + // The foundProto being searched for did not show up on the + // object's prototype chain. + return false; + } + key = TypeSet::ObjectKey::get(proto); + } + } + + return true; +} + +void +IonBuilder::freezePropertiesForCommonPrototype(TemporaryTypeSet* types, PropertyName* name, + JSObject* foundProto, + bool allowEmptyTypesforGlobal/* = false*/) +{ + for (unsigned i = 0; i < types->getObjectCount(); i++) { + // If we found a Singleton object's own-property, there's nothing to + // freeze. + if (types->getSingleton(i) == foundProto) + continue; + + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + while (true) { + HeapTypeSetKey property = key->property(NameToId(name)); + JS_ALWAYS_TRUE(!property.isOwnProperty(constraints(), allowEmptyTypesforGlobal)); + + // Don't mark the proto. It will be held down by the shape + // guard. This allows us to use properties found on prototypes + // with properties unknown to TI. + if (key->proto() == TaggedProto(foundProto)) + break; + key = TypeSet::ObjectKey::get(key->proto().toObjectOrNull()); + } + } +} + +bool +IonBuilder::testCommonGetterSetter(TemporaryTypeSet* types, PropertyName* name, + bool isGetter, JSObject* foundProto, Shape* lastProperty, + JSFunction* getterOrSetter, + MDefinition** guard, + Shape* globalShape/* = nullptr*/, + MDefinition** globalGuard/* = nullptr */) +{ + MOZ_ASSERT_IF(globalShape, globalGuard); + bool guardGlobal; + + // Check if all objects being accessed will lookup the name through foundProto. + if (!objectsHaveCommonPrototype(types, name, isGetter, foundProto, &guardGlobal) || + (guardGlobal && !globalShape)) + { + trackOptimizationOutcome(TrackedOutcome::MultiProtoPaths); + return false; + } + + // We can optimize the getter/setter, so freeze all involved properties to + // ensure there isn't a lower shadowing getter or setter installed in the + // future. + freezePropertiesForCommonPrototype(types, name, foundProto, guardGlobal); + + // Add a shape guard on the prototype we found the property on. The rest of + // the prototype chain is guarded by TI freezes, except when name is a global + // name. In this case, we also have to guard on the globals shape to be able + // to optimize, because the way global property sets are handled means + // freezing doesn't work for what we want here. Note that a shape guard is + // good enough here, even in the proxy case, because we have ensured there + // are no lookup hooks for this property. + if (guardGlobal) { + JSObject* obj = &script()->global(); + MDefinition* globalObj = constant(ObjectValue(*obj)); + *globalGuard = addShapeGuard(globalObj, globalShape, Bailout_ShapeGuard); + } + + if (foundProto->isNative()) { + NativeObject& nativeProto = foundProto->as<NativeObject>(); + if (nativeProto.lastProperty() == lastProperty) { + // The proto shape is the same as it was at the point when we + // created the baseline IC, so looking up the prop on the object as + // it is now should be safe. + Shape* propShape = nativeProto.lookupPure(name); + 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, lastProperty, Bailout_ShapeGuard); + return true; +} + +void +IonBuilder::replaceMaybeFallbackFunctionGetter(MGetPropertyCache* cache) +{ + // Discard the last prior resume point of the previous MGetPropertyCache. + WrapMGetPropertyCache rai(maybeFallbackFunctionGetter_); + maybeFallbackFunctionGetter_ = cache; +} + +bool +IonBuilder::annotateGetPropertyCache(MDefinition* obj, PropertyName* name, + MGetPropertyCache* getPropCache, TemporaryTypeSet* objTypes, + TemporaryTypeSet* pushedTypes) +{ + // Ensure every pushed value is a singleton. + if (pushedTypes->unknownObject() || pushedTypes->baseFlags() != 0) + return true; + + for (unsigned i = 0; i < pushedTypes->getObjectCount(); i++) { + if (pushedTypes->getGroup(i) != nullptr) + return true; + } + + // Object's typeset should be a proper object + if (!objTypes || objTypes->baseFlags() || objTypes->unknownObject()) + return true; + + unsigned int objCount = objTypes->getObjectCount(); + if (objCount == 0) + return true; + + InlinePropertyTable* inlinePropTable = getPropCache->initInlinePropertyTable(alloc(), pc); + if (!inlinePropTable) + return false; + + // Ensure that the relevant property typeset for each group is + // is a single-object typeset containing a JSFunction + 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(compartment, 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; + + // Don't add cases corresponding to non-observed pushes + if (!pushedTypes->hasType(TypeSet::ObjectType(singleton))) + continue; + + if (!inlinePropTable->addEntry(alloc(), group, &singleton->as<JSFunction>())) + return false; + } + + if (inlinePropTable->numEntries() == 0) { + getPropCache->clearInlinePropertyTable(); + return true; + } + +#ifdef JS_JITSPEW + if (inlinePropTable->numEntries() > 0) + JitSpew(JitSpew_Inlining, "Annotated GetPropertyCache with %d/%d inline cases", + (int) inlinePropTable->numEntries(), (int) objCount); +#endif + + // If we successfully annotated the GetPropertyCache and there are inline cases, + // then keep a resume point of the state right before this instruction for use + // later when we have to bail out to this point in the fallback case of a + // PolyInlineDispatch. + if (inlinePropTable->numEntries() > 0) { + // Push the object back onto the stack temporarily to capture the resume point. + current->push(obj); + MResumePoint* resumePoint = MResumePoint::New(alloc(), current, pc, + MResumePoint::ResumeAt); + if (!resumePoint) + return false; + inlinePropTable->setPriorResumePoint(resumePoint); + replaceMaybeFallbackFunctionGetter(getPropCache); + current->pop(); + } + return true; +} + +// Returns true if an idempotent cache has ever invalidated this script +// or an outer script. +bool +IonBuilder::invalidatedIdempotentCache() +{ + IonBuilder* builder = this; + do { + if (builder->script()->invalidatedIdempotentCache()) + return true; + builder = builder->callerBuilder_; + } while (builder); + + return false; +} + +bool +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); +} + +bool +IonBuilder::loadSlot(MDefinition* obj, Shape* shape, MIRType rvalType, + BarrierKind barrier, TemporaryTypeSet* types) +{ + return loadSlot(obj, shape->slot(), shape->numFixedSlots(), rvalType, barrier, types); +} + +bool +IonBuilder::storeSlot(MDefinition* obj, size_t slot, size_t nfixed, + MDefinition* value, bool needsBarrier, + MIRType slotType /* = MIRType::None */) +{ + 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); +} + +bool +IonBuilder::storeSlot(MDefinition* obj, Shape* shape, MDefinition* value, bool needsBarrier, + MIRType slotType /* = MIRType::None */) +{ + MOZ_ASSERT(shape->writable()); + return storeSlot(obj, shape->slot(), shape->numFixedSlots(), value, needsBarrier, slotType); +} + +bool +IonBuilder::shouldAbortOnPreliminaryGroups(MDefinition *obj) +{ + // Watch for groups which still have preliminary object information and + // have not had the new script properties or unboxed layout analyses + // performed. Normally this is done after a small number of the objects + // have been created, but if only a few have been created we can still + // perform the analysis with a smaller object population. The analysis can + // have side effects so we will end up aborting compilation after building + // finishes and retrying later. + 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); + + // Fixup type information for a common case where a property call + // is converted to the following bytecodes + // + // a.foo() + // ================= Compiles to ================ + // LOAD "a" + // DUP + // CALLPROP "foo" + // SWAP + // CALL 0 + // + // If we have better type information to unbox the first copy going into + // the CALLPROP operation, we can replace the duplicated copy on the + // stack as well. + 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; +} + +bool +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()) { + // The calls below can abort compilation, so we only try this if we're + // not analyzing. + // Try to optimize arguments.length. + trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsLength); + if (!getPropTryArgumentsLength(&emitted, obj) || emitted) + return emitted; + + // Try to optimize arguments.callee. + trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsCallee); + if (!getPropTryArgumentsCallee(&emitted, obj, name) || emitted) + return emitted; + } + + obj = maybeUnboxForPropertyAccess(obj); + if (obj->type() == MIRType::Object) + obj = convertUnboxedObjects(obj); + + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + obj, name, types); + + // Try to optimize to a specific constant. + trackOptimizationAttempt(TrackedStrategy::GetProp_InferredConstant); + if (barrier == BarrierKind::NoBarrier) { + if (!getPropTryInferredConstant(&emitted, obj, name, types) || emitted) + return emitted; + } else { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + } + + // Always use a call if we are performing analysis and + // not actually emitting code, to simplify later analysis. Also skip deeper + // analysis if there are no known types for this operation, as it will + // always invalidate when executing. + if (info().isAnalysis() || types->empty() || shouldAbortOnPreliminaryGroups(obj)) { + if (types->empty()) { + // Since no further optimizations will be tried, use the IC + // strategy, which would have been the last one to be tried, as a + // sentinel value for why everything failed. + trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache); + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + } + + MCallGetProperty* call = MCallGetProperty::New(alloc(), obj, name); + current->add(call); + + // During the definite properties analysis we can still try to bake in + // constants read off the prototype chain, to allow inlining later on. + // In this case we still need the getprop call so that the later + // analysis knows when the |this| value has been read from. + if (info().isAnalysis()) { + if (!getPropTryConstant(&emitted, obj, NameToId(name), types) || emitted) + return emitted; + } + + current->push(call); + return resumeAfter(call) && pushTypeBarrier(call, types, BarrierKind::TypeSet); + } + + // Try to optimize accesses on outer window proxies, for example window.foo. + // This needs to come before the various strategies getPropTryInnerize tries + // internally, since some of those strategies will "succeed" in silly ways + // even for an outer object. + trackOptimizationAttempt(TrackedStrategy::GetProp_Innerize); + if (!getPropTryInnerize(&emitted, obj, name, types) || emitted) + return emitted; + + if (!forceInlineCaches()) { + // Try to hardcode known constants. + trackOptimizationAttempt(TrackedStrategy::GetProp_Constant); + if (!getPropTryConstant(&emitted, obj, NameToId(name), types) || emitted) + return emitted; + + // Try to hardcode known not-defined + trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined); + if (!getPropTryNotDefined(&emitted, obj, NameToId(name), types) || emitted) + return emitted; + + // Try to emit loads from known binary data blocks + trackOptimizationAttempt(TrackedStrategy::GetProp_TypedObject); + if (!getPropTryTypedObject(&emitted, obj, name) || emitted) + return emitted; + + // Try to emit loads from definite slots. + trackOptimizationAttempt(TrackedStrategy::GetProp_DefiniteSlot); + if (!getPropTryDefiniteSlot(&emitted, obj, name, barrier, types) || emitted) + return emitted; + + // Try to emit loads from unboxed objects. + trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed); + if (!getPropTryUnboxed(&emitted, obj, name, barrier, types) || emitted) + return emitted; + + // Try to inline a common property getter, or make a call. + trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter); + if (!getPropTryCommonGetter(&emitted, obj, name, types) || emitted) + return emitted; + + // Try to emit a monomorphic/polymorphic access based on baseline caches. + trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess); + if (!getPropTryInlineAccess(&emitted, obj, name, barrier, types) || emitted) + return emitted; + + // Try to emit loads from a module namespace. + trackOptimizationAttempt(TrackedStrategy::GetProp_ModuleNamespace); + if (!getPropTryModuleNamespace(&emitted, obj, name, barrier, types) || emitted) + return emitted; + } + + // Try to emit a polymorphic cache. + trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache); + if (!getPropTryCache(&emitted, obj, name, barrier, types) || emitted) + return emitted; + + // Try to emit a shared stub. + trackOptimizationAttempt(TrackedStrategy::GetProp_SharedCache); + if (!getPropTrySharedStub(&emitted, obj, types) || emitted) + return emitted; + + // Emit a call. + MCallGetProperty* call = MCallGetProperty::New(alloc(), obj, name); + current->add(call); + current->push(call); + if (!resumeAfter(call)) + return false; + + if (*pc == JSOP_CALLPROP && IsNullOrUndefined(obj->type())) { + // Due to inlining, it's possible the observed TypeSet is non-empty, + // even though we know |obj| is null/undefined and the MCallGetProperty + // will throw. Don't push a TypeBarrier in this case, to avoid + // inlining the following (unreachable) JSOP_CALL. + return true; + } + + return pushTypeBarrier(call, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::improveThisTypesForCall() +{ + // After a CALLPROP (or CALLELEM) for obj.prop(), the this-value and callee + // for the call are on top of the stack: + // + // ... [this: obj], [callee: obj.prop] + // + // If obj is null or undefined, obj.prop would have thrown an exception so + // at this point we can remove null and undefined from obj's TypeSet, to + // improve type information for the call that will follow. + + MOZ_ASSERT(*pc == JSOP_CALLPROP || *pc == JSOP_CALLELEM); + + // Ensure |this| has types {object, null/undefined}. + MDefinition* thisDef = current->peek(-2); + if (thisDef->type() != MIRType::Value || + !thisDef->mightBeType(MIRType::Object) || + !thisDef->resultTypeSet() || + !thisDef->resultTypeSet()->objectOrSentinel()) + { + return true; + } + + // Remove null/undefined from the TypeSet. + TemporaryTypeSet* types = thisDef->resultTypeSet()->cloneObjectsOnly(alloc_->lifoAlloc()); + if (!types) + return false; + + MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), thisDef, types); + current->add(filter); + current->rewriteAtDepth(-2, filter); + + // FilterTypeSetPolicy::adjustInputs will insert an infallible Unbox(Object) + // for the input. Don't hoist this unbox above the getprop or getelem + // operation. + filter->setDependency(current->peek(-1)->toInstruction()); + return true; +} + +bool +IonBuilder::checkIsDefinitelyOptimizedArguments(MDefinition* obj, bool* isOptimizedArgs) +{ + if (obj->type() != MIRType::MagicOptimizedArguments) { + if (script()->argumentsHasVarBinding() && + obj->mightBeType(MIRType::MagicOptimizedArguments)) + { + return abort("Type is not definitely lazy arguments."); + } + + *isOptimizedArgs = false; + return true; + } + + *isOptimizedArgs = true; + return true; +} + +bool +IonBuilder::getPropTryInferredConstant(bool* emitted, MDefinition* obj, PropertyName* name, + TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + // Need a result typeset to optimize. + TemporaryTypeSet* objTypes = obj->resultTypeSet(); + if (!objTypes) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return true; + } + + JSObject* singleton = objTypes->maybeSingleton(); + if (!singleton) { + trackOptimizationOutcome(TrackedOutcome::NotSingleton); + return true; + } + + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton); + if (key->unknownProperties()) { + trackOptimizationOutcome(TrackedOutcome::UnknownProperties); + return true; + } + + 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 true; +} + +bool +IonBuilder::getPropTryArgumentsLength(bool* emitted, MDefinition* obj) +{ + MOZ_ASSERT(*emitted == false); + + if (JSOp(*pc) != JSOP_LENGTH) + return true; + + bool isOptimizedArgs = false; + if (!checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs)) + return false; + if (!isOptimizedArgs) + return true; + + trackOptimizationSuccess(); + *emitted = true; + + obj->setImplicitlyUsedUnchecked(); + + // We don't know anything from the callee + if (inliningDepth_ == 0) { + MInstruction* ins = MArgumentsLength::New(alloc()); + current->add(ins); + current->push(ins); + return true; + } + + // We are inlining and know the number of arguments the callee pushed + pushConstant(Int32Value(inlineCallInfo_->argv().length())); + return true; +} + +bool +IonBuilder::getPropTryArgumentsCallee(bool* emitted, MDefinition* obj, PropertyName* name) +{ + MOZ_ASSERT(*emitted == false); + + if (name != names().callee) + return true; + + bool isOptimizedArgs = false; + if (!checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs)) + return false; + if (!isOptimizedArgs) + return true; + + MOZ_ASSERT(script()->hasMappedArgsObj()); + + obj->setImplicitlyUsedUnchecked(); + current->push(getCallee()); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + if (!types->mightBeMIRType(MIRType::Object)) { + // If we have not observed an object result here, don't look for a + // singleton constant. + trackOptimizationOutcome(TrackedOutcome::NotObject); + return true; + } + + JSObject* singleton = testSingletonPropertyTypes(obj, id); + if (!singleton) { + trackOptimizationOutcome(TrackedOutcome::NotSingleton); + return true; + } + + // Property access is a known constant -- safe to emit. + obj->setImplicitlyUsedUnchecked(); + + pushConstant(ObjectValue(*singleton)); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getPropTryNotDefined(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + if (!types->mightBeMIRType(MIRType::Undefined)) { + // Only optimize if we expect this property access to return undefined. + trackOptimizationOutcome(TrackedOutcome::NotUndefined); + return true; + } + + ResultWithOOM<bool> res = testNotDefinedProperty(obj, id); + if (res.oom) + return false; + if (!res.value) { + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return true; + } + + obj->setImplicitlyUsedUnchecked(); + pushConstant(UndefinedValue()); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getPropTryTypedObject(bool* emitted, + MDefinition* obj, + PropertyName* name) +{ + TypedObjectPrediction fieldPrediction; + size_t fieldOffset; + size_t fieldIndex; + if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex)) + return true; + + switch (fieldPrediction.kind()) { + case type::Simd: + // FIXME (bug 894104): load into a MIRType::float32x4 etc + return true; + + 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"); +} + +bool +IonBuilder::getPropTryScalarPropOfTypedObject(bool* emitted, MDefinition* typedObj, + int32_t fieldOffset, + TypedObjectPrediction fieldPrediction) +{ + // Must always be loading the same scalar type + Scalar::Type fieldType = fieldPrediction.scalarType(); + + // Don't optimize if the typed object's underlying buffer may be detached. + TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global()); + if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) + return true; + + trackOptimizationSuccess(); + *emitted = true; + + LinearSum byteOffset(alloc()); + if (!byteOffset.add(fieldOffset)) + setForceAbort(); + + return pushScalarLoadFromTypedObject(typedObj, byteOffset, fieldType); +} + +bool +IonBuilder::getPropTryReferencePropOfTypedObject(bool* emitted, MDefinition* typedObj, + int32_t fieldOffset, + TypedObjectPrediction fieldPrediction, + PropertyName* name) +{ + ReferenceTypeDescr::Type fieldType = fieldPrediction.referenceType(); + + TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global()); + if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) + return true; + + trackOptimizationSuccess(); + *emitted = true; + + LinearSum byteOffset(alloc()); + if (!byteOffset.add(fieldOffset)) + setForceAbort(); + + return pushReferenceLoadFromTypedObject(typedObj, byteOffset, fieldType, name); +} + +bool +IonBuilder::getPropTryComplexPropOfTypedObject(bool* emitted, + MDefinition* typedObj, + int32_t fieldOffset, + TypedObjectPrediction fieldPrediction, + size_t fieldIndex) +{ + // Don't optimize if the typed object's underlying buffer may be detached. + TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global()); + if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) + return true; + + // OK, perform the optimization + + // Identify the type object for the field. + MDefinition* type = loadTypedObjectType(typedObj); + MDefinition* fieldTypeObj = typeObjectForFieldFromStructType(type, fieldIndex); + + LinearSum byteOffset(alloc()); + if (!byteOffset.add(fieldOffset)) + setForceAbort(); + + return pushDerivedTypedObject(emitted, typedObj, byteOffset, + fieldPrediction, fieldTypeObj); +} + +MDefinition* +IonBuilder::convertUnboxedObjects(MDefinition* obj) +{ + // If obj might be in any particular unboxed group which should be + // converted to a native representation, perform that conversion. This does + // not guarantee the object will not have such a group afterwards, if the + // object's possible groups are not precisely known. + 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; + + if (UnboxedLayout* layout = key->group()->maybeUnboxedLayout()) { + 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; +} + +bool +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(), name, &nfixed); + if (slot == UINT32_MAX) + return true; + + 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); + + if (!pushTypeBarrier(load, types, barrier)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +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 true; + } + + JSObject* singleton = objTypes->maybeSingleton(); + if (!singleton) { + trackOptimizationOutcome(TrackedOutcome::NotSingleton); + return true; + } + + if (!singleton->is<ModuleNamespaceObject>()) { + trackOptimizationOutcome(TrackedOutcome::NotModuleNamespace); + return true; + } + + ModuleNamespaceObject* ns = &singleton->as<ModuleNamespaceObject>(); + ModuleEnvironmentObject* env; + Shape* shape; + if (!ns->bindings().lookup(NameToId(name), &env, &shape)) { + trackOptimizationOutcome(TrackedOutcome::UnknownProperty); + return true; + } + + obj->setImplicitlyUsedUnchecked(); + MConstant* envConst = constant(ObjectValue(*env)); + uint32_t slot = shape->slot(); + uint32_t nfixed = env->numFixedSlots(); + if (!loadSlot(envConst, slot, nfixed, types->getKnownMIRType(), barrier, types)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +MInstruction* +IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, + BarrierKind barrier, TemporaryTypeSet* types) +{ + // loadUnboxedValue is designed to load any value as if it were contained in + // an array. Thus a property offset is converted to an index, when the + // object is reinterpreted as an array of properties of the same size. + 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, + /* canonicalizeDoubles = */ 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; +} + +bool +IonBuilder::getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name, + BarrierKind barrier, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + JSValueType unboxedType; + uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType); + if (offset == UINT32_MAX) + return true; + + 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); + + if (!pushTypeBarrier(load, types, barrier)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +MDefinition* +IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape, + const BaselineInspector::ReceiverVector& receivers, + const BaselineInspector::ObjectGroupVector& convertUnboxedGroups, + bool isOwnProperty) +{ + MOZ_ASSERT(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); +} + +bool +IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name, + TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + Shape* lastProperty = nullptr; + JSFunction* commonGetter = nullptr; + Shape* globalShape = nullptr; + JSObject* foundProto = nullptr; + bool isOwnProperty = false; + BaselineInspector::ReceiverVector receivers(alloc()); + BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc()); + if (!inspector->commonGetPropFunction(pc, &foundProto, &lastProperty, &commonGetter, + &globalShape, &isOwnProperty, + receivers, convertUnboxedGroups)) + { + return true; + } + + TemporaryTypeSet* objTypes = obj->resultTypeSet(); + MDefinition* guard = nullptr; + MDefinition* globalGuard = nullptr; + bool canUseTIForGetter = + testCommonGetterSetter(objTypes, name, /* isGetter = */ true, + foundProto, lastProperty, commonGetter, &guard, + globalShape, &globalGuard); + if (!canUseTIForGetter) { + // If type information is bad, we can still optimize the getter if we + // shape guard. + obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty, + receivers, convertUnboxedGroups, + isOwnProperty); + if (!obj) + return false; + } + + bool isDOM = objTypes && objTypes->isDOMClass(constraints()); + + if (isDOM && testShouldDOMCall(objTypes, commonGetter, JSJitInfo::Getter)) { + const JSJitInfo* jitinfo = commonGetter->jitInfo(); + MInstruction* get; + if (jitinfo->isAlwaysInSlot) { + // If our object is a singleton and we know the property is + // constant (which is true if and only if the get doesn't alias + // anything), we can just read the slot here and use that constant. + JSObject* singleton = objTypes->maybeSingleton(); + if (singleton && jitinfo->aliasSet() == JSJitInfo::AliasNone) { + size_t slot = jitinfo->slotIndex; + *emitted = true; + pushConstant(GetReservedSlot(singleton, slot)); + return true; + } + + // We can't use MLoadFixedSlot here because it might not have the + // right aliasing behavior; we want to alias DOM setters as needed. + get = MGetDOMMember::New(alloc(), jitinfo, obj, guard, globalGuard); + } else { + get = MGetDOMProperty::New(alloc(), jitinfo, obj, guard, globalGuard); + } + if (!get) { + return false; + } + current->add(get); + current->push(get); + + if (get->isEffectful() && !resumeAfter(get)) + return false; + + if (!pushDOMTypeBarrier(get, types, commonGetter)) + return false; + + trackOptimizationOutcome(TrackedOutcome::DOM); + *emitted = true; + return true; + } + + // Don't call the getter with a primitive value. + if (obj->type() != MIRType::Object) { + MGuardObject* guardObj = MGuardObject::New(alloc(), obj); + current->add(guardObj); + obj = guardObj; + } + + // Spoof stack to expected state for call. + + // Make sure there's enough room + if (!current->ensureHasSlots(2)) + return false; + current->push(constant(ObjectValue(*commonGetter))); + + current->push(obj); + + CallInfo callInfo(alloc(), false); + if (!callInfo.init(current, 0)) + return false; + + if (commonGetter->isNative()) { + InliningStatus status = inlineNativeGetter(callInfo, commonGetter); + switch (status) { + case InliningStatus_Error: + return false; + case InliningStatus_WarmUpCountTooLow: + case InliningStatus_NotInlined: + break; + case InliningStatus_Inlined: + trackOptimizationOutcome(TrackedOutcome::Inlined); + *emitted = true; + return true; + } + } + + // Inline if we can, otherwise, forget it and just generate a call. + if (commonGetter->isInterpreted()) { + InliningDecision decision = makeInliningDecision(commonGetter, callInfo); + switch (decision) { + case InliningDecision_Error: + return false; + case InliningDecision_DontInline: + case InliningDecision_WarmUpCountTooLow: + break; + case InliningDecision_Inline: { + InliningStatus status = inlineScriptedCall(callInfo, commonGetter); + if (status == InliningStatus_Inlined) { + *emitted = true; + return true; + } + if (status == InliningStatus_Error) + return false; + break; + } + } + } + + if (!makeCall(commonGetter, callInfo)) + return false; + + // If the getter could have been inlined, don't track success. The call to + // makeInliningDecision above would have tracked a specific reason why we + // couldn't inline. + if (!commonGetter->isInterpreted()) + trackOptimizationSuccess(); + + *emitted = true; + return true; +} + +bool +IonBuilder::canInlinePropertyOpShapes(const BaselineInspector::ReceiverVector& receivers) +{ + if (receivers.empty()) { + trackOptimizationOutcome(TrackedOutcome::NoShapeInfo); + return false; + } + + for (size_t i = 0; i < receivers.length(); i++) { + // We inline the property access as long as the shape is not in + // dictionary mode. We cannot be sure that the shape is still a + // lastProperty, and calling Shape::search() on dictionary mode + // shapes that aren't lastProperty is invalid. + 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; +} + +bool +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 false; + + if (!canInlinePropertyOpShapes(receivers)) + return true; + + 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) { + // Monomorphic load from a native object. + spew("Inlining monomorphic native GETPROP"); + + obj = addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard); + + Shape* shape = receivers[0].shape->searchLinear(NameToId(name)); + MOZ_ASSERT(shape); + + if (!loadSlot(obj, shape, rvalType, barrier, types)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + if (receivers[0].shape) { + // Monomorphic load from an unboxed object expando. + spew("Inlining monomorphic unboxed expando GETPROP"); + + obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard); + obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ 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 (!loadSlot(expando, shape, rvalType, barrier, types)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + // Monomorphic load from an unboxed object. + ObjectGroup* group = receivers[0].group; + if (obj->resultTypeSet() && !obj->resultTypeSet()->hasType(TypeSet::ObjectType(group))) + return true; + + obj = addGroupGuard(obj, group, Bailout_ShapeGuard); + + const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name); + MInstruction* load = loadUnboxedProperty(obj, property->offset, property->type, barrier, types); + current->push(load); + + if (!pushTypeBarrier(load, types, barrier)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + MOZ_ASSERT(receivers.length() > 1); + spew("Inlining polymorphic GETPROP"); + + if (Shape* propShape = PropertyShapesHaveSameSlot(receivers, NameToId(name))) { + obj = addGuardReceiverPolymorphic(obj, receivers); + if (!obj) + return false; + + if (!loadSlot(obj, propShape, rvalType, barrier, types)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Polymorphic); + *emitted = true; + return true; + } + + 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 false; + } + + if (failedShapeGuard_) + load->setNotMovable(); + + load->setResultType(rvalType); + if (!pushTypeBarrier(load, types, barrier)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Polymorphic); + *emitted = true; + return true; +} + +bool +IonBuilder::getPropTryCache(bool* emitted, MDefinition* obj, PropertyName* name, + BarrierKind barrier, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + // The input value must either be an object, or we should have strong suspicions + // that it can be safely unboxed to an object. + if (obj->type() != MIRType::Object) { + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types || !types->objectOrSentinel()) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return true; + } + } + + // Since getters have no guaranteed return values, we must barrier in order to be + // able to attach stubs for them. + if (inspector->hasSeenAccessedGetter(pc)) + barrier = BarrierKind::TypeSet; + + // Caches can read values from prototypes, so update the barrier to + // reflect such possible values. + if (barrier != BarrierKind::TypeSet) { + ResultWithOOM<BarrierKind> protoBarrier = + PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types); + if (protoBarrier.oom) + return false; + if (protoBarrier.value != BarrierKind::NoBarrier) { + MOZ_ASSERT(barrier <= protoBarrier.value); + barrier = protoBarrier.value; + } + } + + MConstant* id = constant(StringValue(name)); + MGetPropertyCache* load = MGetPropertyCache::New(alloc(), obj, id, + barrier == BarrierKind::TypeSet); + + // Try to mark the cache as idempotent. + if (obj->type() == MIRType::Object && !invalidatedIdempotentCache()) { + if (PropertyReadIsIdempotent(constraints(), obj, name)) + load->setIdempotent(); + } + + // When we are in the context of making a call from the value returned from + // a property, we query the typeObject for the given property name to fill + // the InlinePropertyTable of the GetPropertyCache. This information is + // then used in inlineCallsite and inlineCalls, if the "this" definition is + // matching the "object" definition of the GetPropertyCache (see + // CanInlineGetPropertyCache). + // + // If this GetPropertyCache is idempotent, then we can dispatch to the right + // function only by checking the typed object, instead of querying the value + // of the property. Thus this GetPropertyCache can be moved into the + // fallback path (see inlineObjectGroupFallback). Otherwise, we always have + // to do the GetPropertyCache, and we can dispatch based on the JSFunction + // value. + if (JSOp(*pc) == JSOP_CALLPROP && load->idempotent()) { + if (!annotateGetPropertyCache(obj, name, load, obj->resultTypeSet(), types)) + return false; + } + + current->add(load); + current->push(load); + + if (load->isEffectful() && !resumeAfter(load)) + return false; + + MIRType rvalType = types->getKnownMIRType(); + if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) + rvalType = MIRType::Value; + load->setResultType(rvalType); + + if (!pushTypeBarrier(load, types, barrier)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::getPropTrySharedStub(bool* emitted, MDefinition* obj, TemporaryTypeSet* types) +{ + MOZ_ASSERT(*emitted == false); + + // Try to emit a shared stub cache. + + if (JitOptions.disableSharedStubs) + return true; + + MInstruction* stub = MUnarySharedStub::New(alloc(), obj); + current->add(stub); + current->push(stub); + + if (!resumeAfter(stub)) + return false; + + // Due to inlining, it's possible the observed TypeSet is non-empty, + // even though we know |obj| is null/undefined and the MCallGetProperty + // will throw. Don't push a TypeBarrier in this case, to avoid + // inlining the following (unreachable) JSOP_CALL. + if (*pc != JSOP_CALLPROP || !IsNullOrUndefined(obj->type())) { + if (!pushTypeBarrier(stub, types, BarrierKind::TypeSet)) + return false; + } + + *emitted = true; + return true; +} + +MDefinition* +IonBuilder::tryInnerizeWindow(MDefinition* obj) +{ + // Try to optimize accesses on outer window proxies (window.foo, for + // example) to go directly to the inner window, the global. + // + // Callers should be careful not to pass the inner object to getters or + // setters that require outerization. + + if (obj->type() != MIRType::Object) + return obj; + + TemporaryTypeSet* types = obj->resultTypeSet(); + if (!types) + return obj; + + JSObject* singleton = types->maybeSingleton(); + if (!singleton) + return obj; + + if (!IsWindowProxy(singleton)) + return obj; + + // This must be a WindowProxy for the current Window/global. Else it'd be + // a cross-compartment wrapper and IsWindowProxy returns false for those. + MOZ_ASSERT(ToWindowIfWindowProxy(singleton) == &script()->global()); + + // When we navigate, the WindowProxy is brain transplanted and we'll mark + // its ObjectGroup as having unknown properties. The type constraint we add + // here will invalidate JIT code when this happens. + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton); + if (key->hasFlags(constraints(), OBJECT_FLAG_UNKNOWN_PROPERTIES)) + return obj; + + obj->setImplicitlyUsedUnchecked(); + return constant(ObjectValue(script()->global())); +} + +bool +IonBuilder::getPropTryInnerize(bool* emitted, MDefinition* obj, PropertyName* name, + TemporaryTypeSet* types) +{ + // See the comment in tryInnerizeWindow for how this works. + + // Note that it's important that we do this _before_ we'd try to + // do the optimizations below on obj normally, since some of those + // optimizations have fallback paths that are slower than the path + // we'd produce here. + + MOZ_ASSERT(*emitted == false); + + MDefinition* inner = tryInnerizeWindow(obj); + if (inner == obj) + return true; + + if (!forceInlineCaches()) { + trackOptimizationAttempt(TrackedStrategy::GetProp_Constant); + if (!getPropTryConstant(emitted, inner, NameToId(name), types) || *emitted) + return *emitted; + + trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName); + if (!getStaticName(&script()->global(), name, emitted) || *emitted) + return *emitted; + + trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter); + if (!getPropTryCommonGetter(emitted, inner, name, types) || *emitted) + return *emitted; + } + + // Passing the inner object to GetProperty IC is safe, see the + // needsOuterizedThisObject check in IsCacheableGetPropCallNative. + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + inner, name, types); + trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache); + if (!getPropTryCache(emitted, inner, name, barrier, types) || *emitted) + return *emitted; + + MOZ_ASSERT(*emitted == false); + return true; +} + +bool +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()); + + // Always use a call if we are doing the definite properties analysis and + // not actually emitting code, to simplify later analysis. + 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()) { + // Try to inline a common property setter, or make a call. + trackOptimizationAttempt(TrackedStrategy::SetProp_CommonSetter); + if (!setPropTryCommonSetter(&emitted, obj, name, value) || emitted) + return emitted; + + // Try to emit stores to known binary data blocks + trackOptimizationAttempt(TrackedStrategy::SetProp_TypedObject); + if (!setPropTryTypedObject(&emitted, obj, name, value) || emitted) + return emitted; + } + + TemporaryTypeSet* objTypes = obj->resultTypeSet(); + bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value, + /* canModify = */ true); + + if (!forceInlineCaches()) { + // Try to emit stores to unboxed objects. + trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed); + if (!setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes) || emitted) + return emitted; + } + + // Add post barrier if needed. The instructions above manage any post + // barriers they need directly. + if (NeedsPostBarrier(value)) + current->add(MPostWriteBarrier::New(alloc(), obj, value)); + + if (!forceInlineCaches()) { + // Try to emit store from definite slots. + trackOptimizationAttempt(TrackedStrategy::SetProp_DefiniteSlot); + if (!setPropTryDefiniteSlot(&emitted, obj, name, value, barrier, objTypes) || emitted) + return emitted; + + // Try to emit a monomorphic/polymorphic store based on baseline caches. + trackOptimizationAttempt(TrackedStrategy::SetProp_InlineAccess); + if (!setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes) || emitted) + return emitted; + } + + // Emit a polymorphic cache. + trackOptimizationAttempt(TrackedStrategy::SetProp_InlineCache); + return setPropTryCache(&emitted, obj, name, value, barrier, objTypes); +} + +bool +IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value) +{ + MOZ_ASSERT(*emitted == false); + + Shape* lastProperty = nullptr; + JSFunction* commonSetter = nullptr; + JSObject* foundProto = nullptr; + bool isOwnProperty; + BaselineInspector::ReceiverVector receivers(alloc()); + BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc()); + if (!inspector->commonSetPropFunction(pc, &foundProto, &lastProperty, &commonSetter, + &isOwnProperty, + receivers, convertUnboxedGroups)) + { + trackOptimizationOutcome(TrackedOutcome::NoProtoFound); + return true; + } + + TemporaryTypeSet* objTypes = obj->resultTypeSet(); + MDefinition* guard = nullptr; + bool canUseTIForSetter = + testCommonGetterSetter(objTypes, name, /* isGetter = */ false, + foundProto, lastProperty, commonSetter, &guard); + if (!canUseTIForSetter) { + // If type information is bad, we can still optimize the setter if we + // shape guard. + obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty, + receivers, convertUnboxedGroups, + isOwnProperty); + if (!obj) + return false; + } + + // Emit common setter. + + // Setters can be called even if the property write needs a type + // barrier, as calling the setter does not actually write any data + // properties. + + // Try emitting dom call. + if (!setPropTryCommonDOMSetter(emitted, obj, value, commonSetter, objTypes)) + return false; + + if (*emitted) { + trackOptimizationOutcome(TrackedOutcome::DOM); + return true; + } + + // Don't call the setter with a primitive value. + if (obj->type() != MIRType::Object) { + MGuardObject* guardObj = MGuardObject::New(alloc(), obj); + current->add(guardObj); + obj = guardObj; + } + + // Dummy up the stack, as in getprop. We are pushing an extra value, so + // ensure there is enough space. + if (!current->ensureHasSlots(3)) + return false; + + current->push(constant(ObjectValue(*commonSetter))); + current->push(obj); + current->push(value); + + // Call the setter. Note that we have to push the original value, not + // the setter's return value. + CallInfo callInfo(alloc(), false); + if (!callInfo.init(current, 1)) + return false; + + // Ensure that we know we are calling a setter in case we inline it. + callInfo.markAsSetter(); + + // Inline the setter if we can. + if (commonSetter->isInterpreted()) { + InliningDecision decision = makeInliningDecision(commonSetter, callInfo); + switch (decision) { + case InliningDecision_Error: + return false; + case InliningDecision_DontInline: + case InliningDecision_WarmUpCountTooLow: + break; + case InliningDecision_Inline: { + InliningStatus status = inlineScriptedCall(callInfo, commonSetter); + if (status == InliningStatus_Inlined) { + *emitted = true; + return true; + } + if (status == InliningStatus_Error) + return false; + } + } + } + + MCall* call = makeCallHelper(commonSetter, callInfo); + if (!call) + return false; + + current->push(value); + if (!resumeAfter(call)) + return false; + + // If the setter could have been inlined, don't track success. The call to + // makeInliningDecision above would have tracked a specific reason why we + // couldn't inline. + if (!commonSetter->isInterpreted()) + trackOptimizationSuccess(); + + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryCommonDOMSetter(bool* emitted, MDefinition* obj, + MDefinition* value, JSFunction* setter, + TemporaryTypeSet* objTypes) +{ + MOZ_ASSERT(*emitted == false); + + if (!objTypes || !objTypes->isDOMClass(constraints())) + return true; + + if (!testShouldDOMCall(objTypes, setter, JSJitInfo::Setter)) + return true; + + // Emit SetDOMProperty. + MOZ_ASSERT(setter->jitInfo()->type() == JSJitInfo::Setter); + MSetDOMProperty* set = MSetDOMProperty::New(alloc(), setter->jitInfo()->setter, obj, value); + + current->add(set); + current->push(value); + + if (!resumeAfter(set)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryTypedObject(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value) +{ + TypedObjectPrediction fieldPrediction; + size_t fieldOffset; + size_t fieldIndex; + if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex)) + return true; + + switch (fieldPrediction.kind()) { + case type::Simd: + // FIXME (bug 894104): store into a MIRType::float32x4 etc + return true; + + 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 true; + } + + MOZ_CRASH("Unknown kind"); +} + +bool +IonBuilder::setPropTryReferencePropOfTypedObject(bool* emitted, + MDefinition* obj, + int32_t fieldOffset, + MDefinition* value, + TypedObjectPrediction fieldPrediction, + PropertyName* name) +{ + ReferenceTypeDescr::Type fieldType = fieldPrediction.referenceType(); + + TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global()); + if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) + return true; + + LinearSum byteOffset(alloc()); + if (!byteOffset.add(fieldOffset)) + setForceAbort(); + + if (!storeReferenceTypedObjectValue(obj, byteOffset, fieldType, value, name)) + return true; + + current->push(value); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryScalarPropOfTypedObject(bool* emitted, + MDefinition* obj, + int32_t fieldOffset, + MDefinition* value, + TypedObjectPrediction fieldPrediction) +{ + // Must always be loading the same scalar type + Scalar::Type fieldType = fieldPrediction.scalarType(); + + // Don't optimize if the typed object's underlying buffer may be detached. + TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global()); + if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) + return true; + + LinearSum byteOffset(alloc()); + if (!byteOffset.add(fieldOffset)) + setForceAbort(); + + if (!storeScalarTypedObjectValue(obj, byteOffset, fieldType, value)) + return false; + + current->push(value); + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryDefiniteSlot(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value, + bool barrier, TemporaryTypeSet* objTypes) +{ + MOZ_ASSERT(*emitted == false); + + if (barrier) { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return true; + } + + uint32_t nfixed; + uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name, &nfixed); + if (slot == UINT32_MAX) + return true; + + 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 true; + } + writeBarrier |= property.needsBarrier(constraints()); + } + + 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); + + if (!resumeAfter(store)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +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 /* = true */) +{ + 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, + 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; +} + +bool +IonBuilder::setPropTryUnboxed(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value, + bool barrier, TemporaryTypeSet* objTypes) +{ + MOZ_ASSERT(*emitted == false); + + if (barrier) { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return true; + } + + JSValueType unboxedType; + uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType); + if (offset == UINT32_MAX) + return true; + + 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); + + if (!resumeAfter(store)) + return false; + + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value, + bool barrier, TemporaryTypeSet* objTypes) +{ + MOZ_ASSERT(*emitted == false); + + if (barrier) { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return true; + } + + BaselineInspector::ReceiverVector receivers(alloc()); + BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc()); + if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups)) + return false; + + if (!canInlinePropertyOpShapes(receivers)) + return true; + + obj = convertUnboxedObjects(obj, convertUnboxedGroups); + + if (receivers.length() == 1) { + if (!receivers[0].group) { + // Monomorphic store to a native object. + spew("Inlining monomorphic native SETPROP"); + + obj = addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard); + + Shape* shape = receivers[0].shape->searchLinear(NameToId(name)); + MOZ_ASSERT(shape); + + bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name)); + if (!storeSlot(obj, shape, value, needsBarrier)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + if (receivers[0].shape) { + // Monomorphic store to an unboxed object expando. + spew("Inlining monomorphic unboxed expando SETPROP"); + + obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard); + obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ 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); + + bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name)); + if (!storeSlot(expando, shape, value, needsBarrier)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + // Monomorphic store to an unboxed object. + spew("Inlining monomorphic unboxed SETPROP"); + + ObjectGroup* group = receivers[0].group; + if (!objTypes->hasType(TypeSet::ObjectType(group))) + return true; + + obj = addGroupGuard(obj, group, Bailout_ShapeGuard); + + const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name); + storeUnboxedProperty(obj, property->offset, property->type, value); + + current->push(value); + + trackOptimizationOutcome(TrackedOutcome::Monomorphic); + *emitted = true; + return true; + } + + MOZ_ASSERT(receivers.length() > 1); + spew("Inlining polymorphic SETPROP"); + + if (Shape* propShape = PropertyShapesHaveSameSlot(receivers, NameToId(name))) { + obj = addGuardReceiverPolymorphic(obj, receivers); + if (!obj) + return false; + + bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name)); + if (!storeSlot(obj, propShape, value, needsBarrier)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Polymorphic); + *emitted = true; + return true; + } + + 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 false; + } + + if (objTypes->propertyNeedsBarrier(constraints(), NameToId(name))) + ins->setNeedsBarrier(); + + if (!resumeAfter(ins)) + return false; + + trackOptimizationOutcome(TrackedOutcome::Polymorphic); + *emitted = true; + return true; +} + +bool +IonBuilder::setPropTryCache(bool* emitted, MDefinition* obj, + PropertyName* name, MDefinition* value, + bool barrier, TemporaryTypeSet* objTypes) +{ + MOZ_ASSERT(*emitted == false); + + bool strict = IsStrictSetPC(pc); + + MConstant* id = constant(StringValue(name)); + MSetPropertyCache* ins = MSetPropertyCache::New(alloc(), obj, id, value, strict, barrier, + /* guardHoles = */ false); + current->add(ins); + current->push(value); + + if (!resumeAfter(ins)) + return false; + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +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); +} + +bool +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); +} + +bool +IonBuilder::jsop_regexp(RegExpObject* reobj) +{ + MRegExp* regexp = MRegExp::New(alloc(), constraints(), reobj); + current->add(regexp); + current->push(regexp); + + return true; +} + +bool +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); + } + + compartment->setSingletonsAsValues(); + pushConstant(ObjectValue(*obj)); + return true; +} + +bool +IonBuilder::jsop_lambda(JSFunction* fun) +{ + MOZ_ASSERT(analysis().usesEnvironmentChain()); + MOZ_ASSERT(!fun->isArrow()); + + if (IsAsmJSModule(fun)) + return abort("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); +} + +bool +IonBuilder::jsop_lambda_arrow(JSFunction* fun) +{ + MOZ_ASSERT(analysis().usesEnvironmentChain()); + MOZ_ASSERT(fun->isArrow()); + MOZ_ASSERT(!fun->isNative()); + + MDefinition* newTargetDef = current->pop(); + MLambdaArrow* ins = MLambdaArrow::New(alloc(), constraints(), current->environmentChain(), + newTargetDef, fun); + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_setarg(uint32_t arg) +{ + // To handle this case, we should spill the arguments to the space where + // actual arguments are stored. The tricky part is that if we add a MIR + // to wrap the spilling action, we don't want the spilling to be + // captured by the GETARG and by the resume point, only by + // MGetFrameArgument. + MOZ_ASSERT(analysis_.hasSetArg()); + MDefinition* val = current->peek(-1); + + // If an arguments object is in use, and it aliases formals, then all SETARGs + // must go through the arguments object. + if (info().argsObjAliasesFormals()) { + if (NeedsPostBarrier(val)) + current->add(MPostWriteBarrier::New(alloc(), current->argumentsObject(), val)); + current->add(MSetArgumentsObjectArg::New(alloc(), current->argumentsObject(), + GET_ARGNO(pc), val)); + return true; + } + + // :TODO: if hasArguments() is true, and the script has a JSOP_SETARG, then + // convert all arg accesses to go through the arguments object. (see Bug 957475) + if (info().hasArguments()) + return abort("NYI: arguments & setarg."); + + // Otherwise, if a magic arguments is in use, and it aliases formals, and there exist + // arguments[...] GETELEM expressions in the script, then SetFrameArgument must be used. + // If no arguments[...] GETELEM expressions are in the script, and an argsobj is not + // required, then it means that any aliased argument set can never be observed, and + // the frame does not actually need to be updated with the new arg value. + if (info().argumentsAliasesFormals()) { + // JSOP_SETARG with magic arguments within inline frames is not yet supported. + MOZ_ASSERT(script()->uninlineable() && !isInlineBuilder()); + + MSetFrameArgument* store = MSetFrameArgument::New(alloc(), arg, val); + modifiesFrameArguments_ = true; + current->add(store); + current->setArg(arg); + return true; + } + + // If this assignment is at the start of the function and is coercing + // the original value for the argument which was passed in, loosen + // the type information for that original argument if it is currently + // empty due to originally executing in the interpreter. + if (graph().numBlocks() == 1 && + (val->isBitOr() || val->isBitAnd() || val->isMul() /* for JSOP_POS */)) + { + for (size_t i = 0; i < val->numOperands(); i++) { + MDefinition* op = val->getOperand(i); + if (op->isParameter() && + op->toParameter()->index() == (int32_t)arg && + op->resultTypeSet() && + op->resultTypeSet()->empty()) + { + bool otherUses = false; + for (MUseDefIterator iter(op); iter; iter++) { + MDefinition* def = iter.def(); + if (def == val) + continue; + otherUses = true; + } + if (!otherUses) { + MOZ_ASSERT(op->resultTypeSet() == &argTypes[arg]); + argTypes[arg].addType(TypeSet::UnknownType(), alloc_->lifoAlloc()); + if (val->isMul()) { + val->setResultType(MIRType::Double); + val->toMul()->setSpecialization(MIRType::Double); + } else { + MOZ_ASSERT(val->type() == MIRType::Int32); + } + val->setResultTypeSet(nullptr); + } + } + } + } + + current->setArg(arg); + return true; +} + +bool +IonBuilder::jsop_defvar(uint32_t index) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_DEFVAR); + + PropertyName* name = script()->getName(index); + + // Bake in attrs. + unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; + MOZ_ASSERT(!script()->isForEval()); + + // Pass the EnvironmentChain. + MOZ_ASSERT(analysis().usesEnvironmentChain()); + + // Bake the name pointer into the MDefVar. + MDefVar* defvar = MDefVar::New(alloc(), name, attrs, current->environmentChain()); + current->add(defvar); + + return resumeAfter(defvar); +} + +bool +IonBuilder::jsop_deflexical(uint32_t index) +{ + MOZ_ASSERT(!script()->hasNonSyntacticScope()); + MOZ_ASSERT(JSOp(*pc) == JSOP_DEFLET || JSOp(*pc) == JSOP_DEFCONST); + + PropertyName* name = script()->getName(index); + unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; + if (JSOp(*pc) == JSOP_DEFCONST) + attrs |= JSPROP_READONLY; + + MDefLexical* deflex = MDefLexical::New(alloc(), name, attrs); + current->add(deflex); + + return resumeAfter(deflex); +} + +bool +IonBuilder::jsop_deffun(uint32_t index) +{ + MOZ_ASSERT(analysis().usesEnvironmentChain()); + + MDefFun* deffun = MDefFun::New(alloc(), current->pop(), current->environmentChain()); + current->add(deffun); + + return resumeAfter(deffun); +} + +bool +IonBuilder::jsop_throwsetconst() +{ + current->peek(-1)->setImplicitlyUsedUnchecked(); + MInstruction* lexicalError = MThrowRuntimeLexicalError::New(alloc(), JSMSG_BAD_CONST_ASSIGN); + current->add(lexicalError); + return resumeAfter(lexicalError); +} + +bool +IonBuilder::jsop_checklexical() +{ + uint32_t slot = info().localSlot(GET_LOCALNO(pc)); + MDefinition* lexical = addLexicalCheck(current->getSlot(slot)); + if (!lexical) + return false; + current->setSlot(slot, lexical); + return true; +} + +bool +IonBuilder::jsop_checkaliasedlexical(EnvironmentCoordinate ec) +{ + MDefinition* let = addLexicalCheck(getAliasedVar(ec)); + if (!let) + return false; + + 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 we are checking for a load, push the checked let so that the load + // can use it. + if (JSOp(*nextPc) == JSOP_GETALIASEDVAR) + setLexicalCheck(let); + + return true; +} + +bool +IonBuilder::jsop_functionthis() +{ + MOZ_ASSERT(info().funMaybeLazy()); + MOZ_ASSERT(!info().funMaybeLazy()->isArrow()); + + if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) { + // No need to wrap primitive |this| in strict mode or self-hosted code. + current->pushSlot(info().thisSlot()); + return true; + } + + if (thisTypes && (thisTypes->getKnownMIRType() == MIRType::Object || + (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject()))) + { + // This is safe, because if the entry type of |this| is an object, it + // will necessarily be an object throughout the entire function. OSR + // can introduce a phi, but this phi will be specialized. + current->pushSlot(info().thisSlot()); + return true; + } + + // If we are doing an analysis, we might not yet know the type of |this|. + // Instead of bailing out just push the |this| slot, as this code won't + // actually execute and it does not matter whether |this| is primitive. + if (info().isAnalysis()) { + current->pushSlot(info().thisSlot()); + return true; + } + + // Hard case: |this| may be a primitive we have to wrap. + MDefinition* def = current->getSlot(info().thisSlot()); + + if (def->type() == MIRType::Object) { + current->push(def); + return true; + } + + if (IsNullOrUndefined(def->type())) { + pushConstant(GetThisValue(&script()->global())); + return true; + } + + MComputeThis* thisObj = MComputeThis::New(alloc(), def); + current->add(thisObj); + current->push(thisObj); + + return resumeAfter(thisObj); +} + +bool +IonBuilder::jsop_globalthis() +{ + if (script()->hasNonSyntacticScope()) { + // Ion does not compile global scripts with a non-syntactic scope, but + // we can end up here when we're compiling an arrow function. + return abort("JSOP_GLOBALTHIS in script with non-syntactic scope"); + } + + LexicalEnvironmentObject* globalLexical = &script()->global().lexicalEnvironment(); + pushConstant(globalLexical->thisValue()); + return true; +} + +bool +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 true; +} + +bool +IonBuilder::jsop_toasync() +{ + MDefinition* unwrapped = current->pop(); + MOZ_ASSERT(unwrapped->type() == MIRType::Object); + + MToAsync* ins = MToAsync::New(alloc(), unwrapped); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_toid() +{ + // No-op if the index is an integer. + if (current->peek(-1)->type() == MIRType::Int32) + return true; + + MDefinition* index = current->pop(); + MToId* ins = MToId::New(alloc(), index); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_iter(uint8_t flags) +{ + if (flags != JSITER_ENUMERATE) + nonStringIteration_ = true; + + MDefinition* obj = current->pop(); + MInstruction* ins = MIteratorStart::New(alloc(), obj, flags); + + if (!outermostBuilder()->iterators_.append(ins)) + return false; + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_itermore() +{ + MDefinition* iter = current->peek(-1); + MInstruction* ins = MIteratorMore::New(alloc(), iter); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +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 true; +} + +bool +IonBuilder::jsop_iterend() +{ + MDefinition* iter = current->pop(); + MInstruction* ins = MIteratorEnd::New(alloc(), iter); + + current->add(ins); + + return resumeAfter(ins); +} + +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; +} + +bool +IonBuilder::hasStaticEnvironmentObject(EnvironmentCoordinate ec, JSObject** pcall) +{ + JSScript* outerScript = EnvironmentCoordinateFunctionScript(script(), pc); + if (!outerScript || !outerScript->treatAsRunOnce()) + return false; + + TypeSet::ObjectKey* funKey = + TypeSet::ObjectKey::get(outerScript->functionNonDelazifying()); + if (funKey->hasFlags(constraints(), OBJECT_FLAG_RUNONCE_INVALIDATED)) + return false; + + // The script this aliased var operation is accessing will run only once, + // so there will be only one call object and the aliased var access can be + // compiled in the same manner as a global access. We still need to find + // the call object though. + + // Look for the call object on the current script's function's env chain. + // If the current script is inner to the outer script and the function has + // singleton type then it should show up here. + + MDefinition* envDef = current->getSlot(info().environmentChainSlot()); + envDef->setImplicitlyUsedUnchecked(); + + JSObject* environment = script()->functionNonDelazifying()->environment(); + while (environment && !environment->is<GlobalObject>()) { + if (environment->is<CallObject>() && + environment->as<CallObject>().callee().nonLazyScript() == outerScript) + { + MOZ_ASSERT(environment->isSingleton()); + *pcall = environment; + return true; + } + environment = environment->enclosingEnvironment(); + } + + // Look for the call object on the current frame, if we are compiling the + // outer script itself. Don't do this if we are at entry to the outer + // script, as the call object we see will not be the real one --- after + // entering the Ion code a different call object will be created. + + if (script() == outerScript && baselineFrame_ && info().osrPc()) { + JSObject* singletonScope = baselineFrame_->singletonEnvChain; + if (singletonScope && + singletonScope->is<CallObject>() && + singletonScope->as<CallObject>().callee().nonLazyScript() == outerScript) + { + MOZ_ASSERT(singletonScope->isSingleton()); + *pcall = singletonScope; + return true; + } + } + + return true; +} + +MDefinition* +IonBuilder::getAliasedVar(EnvironmentCoordinate ec) +{ + MDefinition* obj = walkEnvironmentChain(ec.hops()); + + Shape* shape = EnvironmentCoordinateToEnvironmentShape(script(), pc); + + MInstruction* load; + if (shape->numFixedSlots() <= ec.slot()) { + MInstruction* slots = MSlots::New(alloc(), obj); + current->add(slots); + + load = MLoadSlot::New(alloc(), slots, ec.slot() - shape->numFixedSlots()); + } else { + load = MLoadFixedSlot::New(alloc(), obj, ec.slot()); + } + + current->add(load); + return load; +} + +bool +IonBuilder::jsop_getaliasedvar(EnvironmentCoordinate ec) +{ + JSObject* call = nullptr; + if (hasStaticEnvironmentObject(ec, &call) && call) { + PropertyName* name = EnvironmentCoordinateName(envCoordinateNameCache, script(), pc); + bool emitted = false; + if (!getStaticName(call, name, &emitted, takeLexicalCheck()) || emitted) + return emitted; + } + + // See jsop_checkaliasedlexical. + MDefinition* load = takeLexicalCheck(); + if (!load) + load = getAliasedVar(ec); + current->push(load); + + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(load, types, BarrierKind::TypeSet); +} + +bool +IonBuilder::jsop_setaliasedvar(EnvironmentCoordinate ec) +{ + JSObject* call = nullptr; + if (hasStaticEnvironmentObject(ec, &call)) { + uint32_t depth = current->stackDepth() + 1; + if (depth > current->nslots()) { + if (!current->increaseSlots(depth - current->nslots())) + return false; + } + MDefinition* value = current->pop(); + PropertyName* name = EnvironmentCoordinateName(envCoordinateNameCache, script(), pc); + + if (call) { + // Push the object on the stack to match the bound object expected in + // the global and property set cases. + pushConstant(ObjectValue(*call)); + current->push(value); + return setStaticName(call, name); + } + + // The call object has type information we need to respect but we + // couldn't find it. Just do a normal property assign. + MDefinition* obj = walkEnvironmentChain(ec.hops()); + current->push(obj); + current->push(value); + return jsop_setprop(name); + } + + MDefinition* rval = current->peek(-1); + MDefinition* obj = walkEnvironmentChain(ec.hops()); + + Shape* shape = EnvironmentCoordinateToEnvironmentShape(script(), pc); + + if (NeedsPostBarrier(rval)) + current->add(MPostWriteBarrier::New(alloc(), obj, rval)); + + MInstruction* store; + if (shape->numFixedSlots() <= ec.slot()) { + MInstruction* slots = MSlots::New(alloc(), obj); + current->add(slots); + + store = MStoreSlot::NewBarriered(alloc(), slots, ec.slot() - shape->numFixedSlots(), rval); + } else { + store = MStoreFixedSlot::NewBarriered(alloc(), obj, ec.slot(), rval); + } + + current->add(store); + return resumeAfter(store); +} + +bool +IonBuilder::jsop_in() +{ + MDefinition* obj = convertUnboxedObjects(current->pop()); + MDefinition* id = current->pop(); + + bool emitted = false; + + if (!inTryDense(&emitted, obj, id) || emitted) + return emitted; + + if (!inTryFold(&emitted, obj, id) || emitted) + return emitted; + + MIn* ins = MIn::New(alloc(), id, obj); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id) +{ + MOZ_ASSERT(!*emitted); + + if (shouldAbortOnPreliminaryGroups(obj)) + return true; + + JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, id); + if (unboxedType == JSVAL_TYPE_MAGIC) { + if (!ElementAccessIsDenseNative(constraints(), obj, id)) + return true; + } + + if (ElementAccessHasExtraIndexedProperty(this, obj)) + return true; + + *emitted = true; + + bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj); + + // Ensure id is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), id); + current->add(idInt32); + id = idInt32; + + // Get the elements vector. + MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC); + current->add(elements); + + MInstruction* initLength = initializedLength(obj, elements, unboxedType); + + // If there are no holes, speculate the InArray check will not fail. + if (!needsHoleCheck && !failedBoundsCheck_) { + addBoundsCheck(idInt32, initLength); + pushConstant(BooleanValue(true)); + return true; + } + + // Check if id < initLength and elem[id] not a hole. + MInArray* ins = MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck, + unboxedType); + + current->add(ins); + current->push(ins); + + return true; +} + +bool +IonBuilder::inTryFold(bool* emitted, MDefinition* obj, MDefinition* id) +{ + // Fold |id in obj| to |false|, if we know the object (or an object on its + // prototype chain) does not have this property. + + MOZ_ASSERT(!*emitted); + + MConstant* idConst = id->maybeConstantValue(); + jsid propId; + if (!idConst || !ValueToIdPure(idConst->toJSValue(), &propId)) + return true; + + if (propId != IdToTypeId(propId)) + return true; + + ResultWithOOM<bool> res = testNotDefinedProperty(obj, propId); + if (res.oom) + return false; + if (!res.value) + return true; + + *emitted = true; + + pushConstant(BooleanValue(false)); + obj->setImplicitlyUsedUnchecked(); + id->setImplicitlyUsedUnchecked(); + return true; +} + +bool +IonBuilder::hasOnProtoChain(TypeSet::ObjectKey* key, JSObject* protoObject, bool* hasOnProto) +{ + MOZ_ASSERT(protoObject); + + while (true) { + if (!key->hasStableClassAndProto(constraints()) || !key->clasp()->isNative()) + return false; + + JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull()); + if (!proto) { + *hasOnProto = false; + return true; + } + + if (proto == protoObject) { + *hasOnProto = true; + return true; + } + + key = TypeSet::ObjectKey::get(proto); + } + + MOZ_CRASH("Unreachable"); +} + +bool +IonBuilder::tryFoldInstanceOf(MDefinition* lhs, JSObject* protoObject) +{ + // Try to fold the js::IsDelegate part of the instanceof operation. + + if (!lhs->mightBeType(MIRType::Object)) { + // If the lhs is a primitive, the result is false. + lhs->setImplicitlyUsedUnchecked(); + pushConstant(BooleanValue(false)); + return true; + } + + TemporaryTypeSet* lhsTypes = lhs->resultTypeSet(); + if (!lhsTypes || lhsTypes->unknownObject()) + return false; + + // We can fold if either all objects have protoObject on their proto chain + // or none have. + bool isFirst = true; + bool knownIsInstance = false; + + for (unsigned i = 0; i < lhsTypes->getObjectCount(); i++) { + TypeSet::ObjectKey* key = lhsTypes->getObject(i); + if (!key) + continue; + + bool isInstance; + if (!hasOnProtoChain(key, protoObject, &isInstance)) + return false; + + if (isFirst) { + knownIsInstance = isInstance; + isFirst = false; + } else if (knownIsInstance != isInstance) { + // Some of the objects have protoObject on their proto chain and + // others don't, so we can't optimize this. + return false; + } + } + + if (knownIsInstance && lhsTypes->getKnownMIRType() != MIRType::Object) { + // The result is true for all objects, but the lhs might be a primitive. + // We can't fold this completely but we can use a much faster IsObject + // test. + MIsObject* isObject = MIsObject::New(alloc(), lhs); + current->add(isObject); + current->push(isObject); + return true; + } + + lhs->setImplicitlyUsedUnchecked(); + pushConstant(BooleanValue(knownIsInstance)); + return true; +} + +bool +IonBuilder::jsop_instanceof() +{ + MDefinition* rhs = current->pop(); + MDefinition* obj = current->pop(); + + // If this is an 'x instanceof function' operation and we can determine the + // exact function and prototype object being tested for, use a typed path. + do { + TemporaryTypeSet* rhsTypes = rhs->resultTypeSet(); + JSObject* rhsObject = rhsTypes ? rhsTypes->maybeSingleton() : nullptr; + if (!rhsObject || !rhsObject->is<JSFunction>() || rhsObject->isBoundFunction()) + break; + + // Refuse to optimize anything whose [[Prototype]] isn't Function.prototype + // since we can't guarantee that it uses the default @@hasInstance method. + if (rhsObject->hasUncacheableProto() || !rhsObject->hasStaticPrototype()) + break; + + Value funProto = script()->global().getPrototype(JSProto_Function); + if (!funProto.isObject() || rhsObject->staticPrototype() != &funProto.toObject()) + break; + + // If the user has supplied their own @@hasInstance method we shouldn't + // clobber it. + JSFunction* fun = &rhsObject->as<JSFunction>(); + const WellKnownSymbols* symbols = &compartment->runtime()->wellKnownSymbols(); + if (!js::FunctionHasDefaultHasInstance(fun, *symbols)) + break; + + // Ensure that we will bail if the @@hasInstance property or [[Prototype]] + // change. + 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(); + + if (tryFoldInstanceOf(obj, protoObject)) + return true; + + MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); + } while (false); + + // Try to inline a fast path based on Baseline ICs. + do { + Shape* shape; + uint32_t slot; + JSObject* protoObject; + if (!inspector->instanceOfData(pc, &shape, &slot, &protoObject)) + break; + + // Shape guard. + rhs = addShapeGuard(rhs, shape, Bailout_ShapeGuard); + + // Guard .prototype == protoObject. + 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, + /* bailOnEquality = */ false); + current->add(guard); + + if (tryFoldInstanceOf(obj, protoObject)) + return true; + + MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject); + current->add(ins); + current->push(ins); + return resumeAfter(ins); + } while (false); + + MCallInstanceOf* ins = MCallInstanceOf::New(alloc(), obj, rhs); + + current->add(ins); + current->push(ins); + + return resumeAfter(ins); +} + +bool +IonBuilder::jsop_debugger() +{ + MDebugger* debugger = MDebugger::New(alloc()); + current->add(debugger); + + // The |debugger;| statement will always bail out to baseline if + // cx->compartment()->isDebuggee(). Resume in-place and have baseline + // handle the details. + return resumeAt(debugger, pc); +} + +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 a bounds check failed in the past, don't optimize bounds checks. + if (failedBoundsCheck_) + check->setNotMovable(); + + return check; +} + +MInstruction* +IonBuilder::addShapeGuard(MDefinition* obj, Shape* const shape, BailoutKind bailoutKind) +{ + MGuardShape* guard = MGuardShape::New(alloc(), obj, shape, bailoutKind); + current->add(guard); + + // If a shape guard failed in the past, don't optimize shape guard. + if (failedShapeGuard_) + guard->setNotMovable(); + + return guard; +} + +MInstruction* +IonBuilder::addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bailoutKind) +{ + MGuardObjectGroup* guard = MGuardObjectGroup::New(alloc(), obj, group, + /* bailOnEquality = */ false, bailoutKind); + current->add(guard); + + // If a shape guard failed in the past, don't optimize group guards. + 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 a shape guard failed in the past, don't optimize group guards. + if (failedShapeGuard_) + guard->setNotMovable(); + + return guard; +} + +MInstruction* +IonBuilder::addGuardReceiverPolymorphic(MDefinition* obj, + const BaselineInspector::ReceiverVector& receivers) +{ + if (receivers.length() == 1) { + if (!receivers[0].group) { + // Monomorphic guard on a native object. + return addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard); + } + + if (!receivers[0].shape) { + // Guard on an unboxed object that does not have an expando. + obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard); + return addUnboxedExpandoGuard(obj, /* hasExpando = */ false, Bailout_ShapeGuard); + } + + // Monomorphic receiver guards are not yet supported when the receiver + // is an unboxed object with an expando. + } + + 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) +{ + // Extract TypedObjectPrediction directly if we can + if (typedObj->isNewDerivedTypedObject()) { + return typedObj->toNewDerivedTypedObject()->prediction(); + } + + TemporaryTypeSet* types = typedObj->resultTypeSet(); + return typedObjectPrediction(types); +} + +TypedObjectPrediction +IonBuilder::typedObjectPrediction(TemporaryTypeSet* types) +{ + // Type set must be known to be an object. + if (!types || types->getKnownMIRType() != MIRType::Object) + return TypedObjectPrediction(); + + // And only known objects. + if (types->unknownObject()) + return TypedObjectPrediction(); + + TypedObjectPrediction out; + for (uint32_t i = 0; i < types->getObjectCount(); i++) { + ObjectGroup* group = types->getGroup(i); + if (!group || !TypeSet::ObjectKey::get(group)->hasStableClassAndProto(constraints())) + return TypedObjectPrediction(); + + if (!IsTypedObjectClass(group->clasp())) + return TypedObjectPrediction(); + + out.addDescr(group->typeDescr()); + } + + return out; +} + +MDefinition* +IonBuilder::loadTypedObjectType(MDefinition* typedObj) +{ + // Shortcircuit derived type objects, meaning the intermediate + // objects created to represent `a.b` in an expression like + // `a.b.c`. In that case, the type object can be simply pulled + // from the operands of that instruction. + if (typedObj->isNewDerivedTypedObject()) + return typedObj->toNewDerivedTypedObject()->type(); + + MInstruction* descr = MTypedObjectDescr::New(alloc(), typedObj); + current->add(descr); + + return descr; +} + +// Given a typed object `typedObj` and an offset `offset` into that +// object's data, returns another typed object and adusted offset +// where the data can be found. Often, these returned values are the +// same as the inputs, but in cases where intermediate derived type +// objects have been created, the return values will remove +// intermediate layers (often rendering those derived type objects +// into dead code). +void +IonBuilder::loadTypedObjectData(MDefinition* typedObj, + MDefinition** owner, + LinearSum* ownerOffset) +{ + MOZ_ASSERT(typedObj->type() == MIRType::Object); + + // Shortcircuit derived type objects, meaning the intermediate + // objects created to represent `a.b` in an expression like + // `a.b.c`. In that case, the owned and a base offset can be + // pulled from the operands of the instruction and combined with + // `offset`. + if (typedObj->isNewDerivedTypedObject()) { + MNewDerivedTypedObject* ins = typedObj->toNewDerivedTypedObject(); + + SimpleLinearSum base = ExtractLinearSum(ins->offset()); + if (!ownerOffset->add(base)) + setForceAbort(); + + *owner = ins->owner(); + return; + } + + *owner = typedObj; +} + +// Takes as input a typed object, an offset into that typed object's +// memory, and the type repr of the data found at that offset. Returns +// the elements pointer and a scaled offset. The scaled offset is +// expressed in units of `unit`; when working with typed array MIR, +// this is typically the alignment. +void +IonBuilder::loadTypedObjectElements(MDefinition* typedObj, + const LinearSum& baseByteOffset, + uint32_t scale, + MDefinition** ownerElements, + MDefinition** ownerScaledOffset, + int32_t* ownerByteAdjustment) +{ + MDefinition* owner; + LinearSum ownerByteOffset(alloc()); + loadTypedObjectData(typedObj, &owner, &ownerByteOffset); + + if (!ownerByteOffset.add(baseByteOffset)) + setForceAbort(); + + TemporaryTypeSet* ownerTypes = owner->resultTypeSet(); + const Class* clasp = ownerTypes ? ownerTypes->getKnownClass(constraints()) : nullptr; + if (clasp && IsInlineTypedObjectClass(clasp)) { + // Perform the load directly from the owner pointer. + if (!ownerByteOffset.add(InlineTypedObject::offsetOfDataStart())) + setForceAbort(); + *ownerElements = owner; + } else { + bool definitelyOutline = clasp && IsOutlineTypedObjectClass(clasp); + *ownerElements = MTypedObjectElements::New(alloc(), owner, definitelyOutline); + current->add((*ownerElements)->toInstruction()); + } + + // Extract the constant adjustment from the byte offset. + *ownerByteAdjustment = ownerByteOffset.constant(); + int32_t negativeAdjustment; + if (!SafeSub(0, *ownerByteAdjustment, &negativeAdjustment)) + setForceAbort(); + if (!ownerByteOffset.add(negativeAdjustment)) + setForceAbort(); + + // Scale the byte offset if required by the MIR node which will access the + // typed object. In principle we should always be able to cleanly divide + // the terms in this lienar sum due to alignment restrictions, but due to + // limitations of ExtractLinearSum when applied to the terms in derived + // typed objects this isn't always be possible. In these cases, fall back + // on an explicit division operation. + 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, /* unsigned = */ false); + current->add((*ownerScaledOffset)->toInstruction()); + } +} + +// Looks up the offset/type-repr-set of the field `id`, given the type +// set `objTypes` of the field owner. If a field is found, returns true +// and sets *fieldOffset, *fieldPrediction, and *fieldIndex. Returns false +// otherwise. Infallible. +bool +IonBuilder::typedObjectHasField(MDefinition* typedObj, + PropertyName* name, + size_t* fieldOffset, + TypedObjectPrediction* fieldPrediction, + size_t* fieldIndex) +{ + TypedObjectPrediction objPrediction = typedObjectPrediction(typedObj); + if (objPrediction.isUseless()) { + trackOptimizationOutcome(TrackedOutcome::AccessNotTypedObject); + return false; + } + + // Must be accessing a struct. + if (objPrediction.kind() != type::Struct) { + trackOptimizationOutcome(TrackedOutcome::NotStruct); + return false; + } + + // Determine the type/offset of the field `name`, if any. + if (!objPrediction.hasFieldNamed(NameToId(name), fieldOffset, + fieldPrediction, fieldIndex)) + { + 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) +{ + // Load list of field type objects. + + 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); + + // Index into list with index of field. + + 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; +} + +bool +IonBuilder::storeScalarTypedObjectValue(MDefinition* typedObj, + const LinearSum& byteOffset, + ScalarTypeDescr::Type type, + MDefinition* value) +{ + // Find location within the owner object. + MDefinition* elements; + MDefinition* scaledOffset; + int32_t adjustment; + uint32_t alignment = ScalarTypeDescr::alignment(type); + loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset, &adjustment); + + // Clamp value to [0, 255] when type is Uint8Clamped + 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); + + return true; +} + +bool +IonBuilder::storeReferenceTypedObjectValue(MDefinition* typedObj, + const LinearSum& byteOffset, + ReferenceTypeDescr::Type type, + MDefinition* value, + PropertyName* name) +{ + // Make sure we aren't adding new type information for writes of object and value + // references. + if (type != ReferenceTypeDescr::TYPE_STRING) { + MOZ_ASSERT(type == ReferenceTypeDescr::TYPE_ANY || + type == ReferenceTypeDescr::TYPE_OBJECT); + MIRType implicitType = + (type == ReferenceTypeDescr::TYPE_ANY) ? MIRType::Undefined : MIRType::Null; + + if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &typedObj, name, &value, + /* canModify = */ true, implicitType)) + { + trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier); + return false; + } + } + + // Find location within the owner object. + MDefinition* elements; + MDefinition* scaledOffset; + int32_t adjustment; + uint32_t alignment = ReferenceTypeDescr::alignment(type); + loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset, &adjustment); + + MInstruction* store = nullptr; // initialize to silence GCC warning + switch (type) { + case ReferenceTypeDescr::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 ReferenceTypeDescr::TYPE_OBJECT: + // Note: We cannot necessarily tell at this point whether a post + // barrier is needed, because the type policy may insert ToObjectOrNull + // instructions later, and those may require a post barrier. Therefore, + // defer the insertion of post barriers to the type policy. + store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, typedObj, adjustment); + break; + case ReferenceTypeDescr::TYPE_STRING: + // Strings are not nursery allocated, so these writes do not need post + // barriers. + store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value, adjustment); + break; + } + + current->add(store); + return true; +} + +JSObject* +IonBuilder::checkNurseryObject(JSObject* obj) +{ + // If we try to use any nursery pointers during compilation, make sure that + // the main thread will cancel this compilation before performing a minor + // GC. All constants used during compilation should either go through this + // function or should come from a type set (which has a similar barrier). + if (obj && IsInsideNursery(obj)) { + compartment->runtime()->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* obj, MDefinition* elements, JSValueType unboxedType) +{ + MInstruction* res; + if (unboxedType != JSVAL_TYPE_MAGIC) + res = MUnboxedArrayInitializedLength::New(alloc(), obj); + else + res = MInitializedLength::New(alloc(), elements); + current->add(res); + return res; +} + +MInstruction* +IonBuilder::setInitializedLength(MDefinition* obj, JSValueType unboxedType, size_t count) +{ + MOZ_ASSERT(count); + + MInstruction* res; + if (unboxedType != JSVAL_TYPE_MAGIC) { + res = MSetUnboxedArrayInitializedLength::New(alloc(), obj, constant(Int32Value(count))); + } else { + // MSetInitializedLength takes the index of the last element, rather + // than the count itself. + MInstruction* elements = MElements::New(alloc(), obj, /* unboxed = */ false); + current->add(elements); + 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(); +} + +MDefinition* +IonBuilder::addLexicalCheck(MDefinition* input) +{ + MOZ_ASSERT(JSOp(*pc) == JSOP_CHECKLEXICAL || + JSOp(*pc) == JSOP_CHECKALIASEDLEXICAL || + JSOp(*pc) == JSOP_GETIMPORT); + + MInstruction* lexicalCheck; + + // If we're guaranteed to not be JS_UNINITIALIZED_LEXICAL, no need to check. + if (input->type() == MIRType::MagicUninitializedLexical) { + // Mark the input as implicitly used so the JS_UNINITIALIZED_LEXICAL + // magic value will be preserved on bailout. + input->setImplicitlyUsedUnchecked(); + lexicalCheck = MThrowRuntimeLexicalError::New(alloc(), JSMSG_UNINITIALIZED_LEXICAL); + current->add(lexicalCheck); + if (!resumeAfter(lexicalCheck)) + return nullptr; + 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) +{ + // Convert to bool with the '!!' idiom + 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 (!compartment->runtime()->runtimeMatches(trc->runtime())) + return; + + MOZ_ASSERT(rootList_); + rootList_->trace(trc); +} |