/* -*- 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/ScopeExit.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/IonControlFlow.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 "gc/Nursery-inl.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),
    thisTypes(nullptr),
    argTypes(nullptr),
    typeArray(nullptr),
    typeArrayHint(0),
    bytecodeTypeMap(nullptr),
    loopDepth_(loopDepth),
    blockWorklist(*temp),
    cfgCurrent(nullptr),
    cfg(nullptr),
    trackedOptimizationSites_(*temp),
    lexicalCheck_(nullptr),
    callerResumePoint_(nullptr),
    callerBuilder_(nullptr),
    iterators_(*temp),
    loopHeaders_(*temp),
    loopHeaderStack_(*temp),
#ifdef DEBUG
    cfgLoopHeaderStack_(*temp),
#endif
    inspector(inspector),
    inliningDepth_(inliningDepth),
    inlinedBytecodeLength_(0),
    numLoopRestarts_(0),
    failedBoundsCheck_(info->script()->failedBoundsCheck()),
    failedShapeGuard_(info->script()->failedShapeGuard()),
    failedLexicalCheck_(info->script()->failedLexicalCheck()),
#ifdef DEBUG
    hasLazyArguments_(false),
#endif
    inlineCallInfo_(nullptr),
    maybeFallbackFunctionGetter_(nullptr)
{
    script_ = info->script();
    scriptHasIonScript_ = script_->hasIonScript();
    pc = info->startPC();

    MOZ_ASSERT(script()->hasBaselineScript() == (info->analysisMode() != Analysis_ArgumentsUsage));
    MOZ_ASSERT(!!analysisContext == (info->analysisMode() == Analysis_DefiniteProperties));
    MOZ_ASSERT(script_->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.
    envCoordinateNameCache.purge();
}

mozilla::GenericErrorResult<AbortReason>
IonBuilder::abort(AbortReason r)
{
    auto res = this->MIRGenerator::abort(r);
# ifdef DEBUG
    JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(), PCToLineNumber(script(), pc));
# else
    JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename());
# endif
    return res;
}

mozilla::GenericErrorResult<AbortReason>
IonBuilder::abort(AbortReason r, const char* message, ...)
{
    // Don't call PCToLineNumber in release builds.
    va_list ap;
    va_start(ap, message);
    auto res = this->MIRGenerator::abortFmt(r, message, ap);
    va_end(ap);
# ifdef DEBUG
    JitSpew(JitSpew_IonAbort, "aborted @ %s:%d", script()->filename(), PCToLineNumber(script(), pc));
# else
    JitSpew(JitSpew_IonAbort, "aborted @ %s", script()->filename());
# endif
    trackActionableAbort(message);
    return res;
}

IonBuilder*
IonBuilder::outermostBuilder()
{
    IonBuilder* builder = this;
    while (builder->callerBuilder_)
        builder = builder->callerBuilder_;
    return builder;
}

void
IonBuilder::trackActionableAbort(const char* message)
{
    if (!isOptimizationTrackingEnabled())
        return;

    IonBuilder* topBuilder = outermostBuilder();
    if (topBuilder->hadActionableAbort())
        return;

    topBuilder->actionableAbortScript_ = script();
    topBuilder->actionableAbortPc_ = pc;
    topBuilder->actionableAbortMessage_ = message;
}

void
IonBuilder::spew(const char* message)
{
    // Don't call PCToLineNumber in release builds.
#ifdef DEBUG
    JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc));
#endif
}

JSFunction*
IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes)
{
    if (!calleeTypes)
        return nullptr;

    TemporaryTypeSet::ObjectKey* key = calleeTypes->maybeSingleObject();
    if (!key || key->clasp() != &JSFunction::class_)
        return nullptr;

    if (key->isSingleton())
        return &key->singleton()->as<JSFunction>();

    if (JSFunction* fun = key->group()->maybeInterpretedFunction())
        return fun;

    return nullptr;
}

AbortReasonOr<Ok>
IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
                               InliningTargets& targets, uint32_t maxTargets)
{
    MOZ_ASSERT(targets.empty());

    if (!calleeTypes)
        return Ok();

    if (calleeTypes->baseFlags() != 0)
        return Ok();

    unsigned objCount = calleeTypes->getObjectCount();

    if (objCount == 0 || objCount > maxTargets)
        return Ok();

    if (!targets.reserve(objCount))
        return abort(AbortReason::Alloc);
    for (unsigned i = 0; i < objCount; i++) {
        JSObject* obj = calleeTypes->getSingleton(i);
        ObjectGroup* group = nullptr;
        if (obj) {
            MOZ_ASSERT(obj->isSingleton());
        } else {
            group = calleeTypes->getGroup(i);
            if (!group)
                continue;

            obj = group->maybeInterpretedFunction();
            if (!obj) {
                targets.clear();
                return Ok();
            }

            MOZ_ASSERT(!obj->isSingleton());
        }

        // Don't optimize if the callee is not callable or constructable per
        // the manner it is being invoked, so that CallKnown does not have to
        // handle these cases (they will always throw).
        if (constructing ? !obj->isConstructor() : !obj->isCallable()) {
            targets.clear();
            return Ok();
        }

        targets.infallibleAppend(InliningTarget(obj, group));
    }

    return Ok();
}

IonBuilder::InliningDecision
IonBuilder::DontInline(JSScript* targetScript, const char* reason)
{
    if (targetScript) {
        JitSpew(JitSpew_Inlining, "Cannot inline %s:%zu: %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) {
        RootedFunction fun(analysisContext, target);
        RootedScript script(analysisContext, JSFunction::getOrCreateScript(analysisContext, fun));
        if (!script)
            return InliningDecision_Error;

        if (!script->hasBaselineScript() && script->canBaselineCompile()) {
            MethodStatus status = BaselineCompile(analysisContext, script);
            if (status == Method_Error)
                return InliningDecision_Error;
            if (status != Method_Compiled) {
                trackOptimizationOutcome(TrackedOutcome::CantInlineNoBaseline);
                return InliningDecision_DontInline;
            }
        }
    }

    if (!target->hasScript()) {
        trackOptimizationOutcome(TrackedOutcome::CantInlineLazy);
        return DontInline(nullptr, "Lazy script");
    }

    JSScript* inlineScript = target->nonLazyScript();
    if (callInfo.constructing() && !target->isConstructor()) {
        trackOptimizationOutcome(TrackedOutcome::CantInlineNotConstructor);
        return DontInline(inlineScript, "Callee is not a constructor");
    }

    if (!callInfo.constructing() && target->isClassConstructor()) {
        trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor);
        return DontInline(inlineScript, "Not constructing class constructor");
    }

    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");
    }

    return InliningDecision_Inline;
}

AbortReasonOr<Ok>
IonBuilder::analyzeNewLoopTypes(const CFGBlock* loopEntryBlock)
{
    CFGLoopEntry* loopEntry = loopEntryBlock->stopIns()->toLoopEntry();
    CFGBlock* cfgBlock = loopEntry->successor();
    MBasicBlock* entry = blockWorklist[cfgBlock->id()];
    MOZ_ASSERT(!entry->isDead());

    // The phi inputs at the loop head only reflect types for variables that
    // were present at the start of the loop. If the variable changes to a new
    // type within the loop body, and that type is carried around to the loop
    // head, then we need to know about the new type up front.
    //
    // Since SSA information hasn't been constructed for the loop body yet, we
    // need a separate analysis to pick out the types that might flow around
    // the loop header. This is a best-effort analysis that may either over-
    // or under-approximate the set of such types.
    //
    // Over-approximating the types may lead to inefficient generated code, and
    // under-approximating the types will cause the loop body to be analyzed
    // multiple times as the correct types are deduced (see finishLoop).

    // If we restarted processing of an outer loop then get loop header types
    // directly from the last time we have previously processed this loop. This
    // both avoids repeated work from the bytecode traverse below, and will
    // also pick up types discovered while previously building the loop body.
    bool foundEntry = false;
    for (size_t i = 0; i < loopHeaders_.length(); i++) {
        if (loopHeaders_[i].pc == cfgBlock->startPc()) {
            MBasicBlock* oldEntry = loopHeaders_[i].header;

            // If this block has been discarded, its resume points will have
            // already discarded their operands.
            if (oldEntry->isDead()) {
                loopHeaders_[i].header = entry;
                foundEntry = true;
                break;
            }

            MResumePoint* oldEntryRp = oldEntry->entryResumePoint();
            size_t stackDepth = oldEntryRp->stackDepth();
            for (size_t slot = 0; slot < stackDepth; slot++) {
                MDefinition* oldDef = oldEntryRp->getOperand(slot);
                if (!oldDef->isPhi()) {
                    MOZ_ASSERT(oldDef->block()->id() < oldEntry->id());
                    MOZ_ASSERT(oldDef == entry->getSlot(slot));
                    continue;
                }
                MPhi* oldPhi = oldDef->toPhi();
                MPhi* newPhi = entry->getSlot(slot)->toPhi();
                if (!newPhi->addBackedgeType(alloc(), oldPhi->type(), oldPhi->resultTypeSet()))
                    return abort(AbortReason::Alloc);
            }

            // Update the most recent header for this loop encountered, in case
            // new types flow to the phis and the loop is processed at least
            // three times.
            loopHeaders_[i].header = entry;
            return Ok();
        }
    }
    if (!foundEntry) {
        if (!loopHeaders_.append(LoopHeader(cfgBlock->startPc(), entry)))
            return abort(AbortReason::Alloc);
    }

    if (loopEntry->isForIn()) {
        // The backedge will have MIteratorMore with MIRType::Value. This slot
        // is initialized to MIRType::Undefined before the loop. Add
        // MIRType::Value to avoid unnecessary loop restarts.

        MPhi* phi = entry->getSlot(entry->stackDepth() - 1)->toPhi();
        MOZ_ASSERT(phi->getOperand(0)->type() == MIRType::Undefined);

        if (!phi->addBackedgeType(alloc(), MIRType::Value, nullptr))
            return abort(AbortReason::Alloc);
    }

    // Get the start and end pc of this loop.
    jsbytecode* start = loopEntryBlock->stopPc();
    start += GetBytecodeLength(start);
    jsbytecode* end = loopEntry->loopStopPc();

    // Iterate the bytecode quickly to seed possible types in the loopheader.
    jsbytecode* last = nullptr;
    jsbytecode* earlier = nullptr;
    for (jsbytecode* pc = start; pc != end; earlier = last, last = pc, pc += GetBytecodeLength(pc)) {
        uint32_t slot;
        if (*pc == JSOP_SETLOCAL)
            slot = info().localSlot(GET_LOCALNO(pc));
        else if (*pc == JSOP_SETARG)
            slot = info().argSlotUnchecked(GET_ARGNO(pc));
        else
            continue;
        if (slot >= info().firstStackSlot())
            continue;
        if (!last)
            continue;

        MPhi* phi = entry->getSlot(slot)->toPhi();

        if (*last == JSOP_POS)
            last = earlier;

        if (CodeSpec[*last].format & JOF_TYPESET) {
            TemporaryTypeSet* typeSet = bytecodeTypes(last);
            if (!typeSet->empty()) {
                MIRType type = typeSet->getKnownMIRType();
                if (!phi->addBackedgeType(alloc(), type, typeSet))
                    return abort(AbortReason::Alloc);
            }
        } else if (*last == JSOP_GETLOCAL || *last == JSOP_GETARG) {
            uint32_t slot = (*last == JSOP_GETLOCAL)
                            ? info().localSlot(GET_LOCALNO(last))
                            : info().argSlotUnchecked(GET_ARGNO(last));
            if (slot < info().firstStackSlot()) {
                MPhi* otherPhi = entry->getSlot(slot)->toPhi();
                if (otherPhi->hasBackedgeType()) {
                    if (!phi->addBackedgeType(alloc(), otherPhi->type(), otherPhi->resultTypeSet()))
                        return abort(AbortReason::Alloc);
                }
            }
        } else {
            MIRType type = MIRType::None;
            switch (*last) {
              case JSOP_VOID:
              case JSOP_UNDEFINED:
                type = MIRType::Undefined;
                break;
              case JSOP_GIMPLICITTHIS:
                if (!script()->hasNonSyntacticScope())
                    type = MIRType::Undefined;
                break;
              case JSOP_NULL:
                type = MIRType::Null;
                break;
              case JSOP_ZERO:
              case JSOP_ONE:
              case JSOP_INT8:
              case JSOP_INT32:
              case JSOP_UINT16:
              case JSOP_UINT24:
              case JSOP_BITAND:
              case JSOP_BITOR:
              case JSOP_BITXOR:
              case JSOP_BITNOT:
              case JSOP_RSH:
              case JSOP_LSH:
              case JSOP_URSH:
                type = MIRType::Int32;
                break;
              case JSOP_FALSE:
              case JSOP_TRUE:
              case JSOP_EQ:
              case JSOP_NE:
              case JSOP_LT:
              case JSOP_LE:
              case JSOP_GT:
              case JSOP_GE:
              case JSOP_NOT:
              case JSOP_STRICTEQ:
              case JSOP_STRICTNE:
              case JSOP_IN:
              case JSOP_INSTANCEOF:
              case JSOP_HASOWN:
                type = MIRType::Boolean;
                break;
              case JSOP_DOUBLE:
                type = MIRType::Double;
                break;
              case JSOP_ITERNEXT:
              case JSOP_STRING:
              case JSOP_TOSTRING:
              case JSOP_TYPEOF:
              case JSOP_TYPEOFEXPR:
                type = MIRType::String;
                break;
              case JSOP_SYMBOL:
                type = MIRType::Symbol;
                break;
              case JSOP_ADD:
              case JSOP_SUB:
              case JSOP_MUL:
              case JSOP_DIV:
              case JSOP_MOD:
              case JSOP_NEG:
                type = inspector->expectedResultType(last);
                break;
              default:
                break;
            }
            if (type != MIRType::None) {
                if (!phi->addBackedgeType(alloc(), type, nullptr))
                    return abort(AbortReason::Alloc);
            }
        }
    }
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::init()
{
    {
        LifoAlloc::AutoFallibleScope fallibleAllocator(alloc().lifoAlloc());
        if (!TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray))
            return abort(AbortReason::Alloc);
    }

    if (!alloc().ensureBallast())
        return abort(AbortReason::Alloc);

    if (inlineCallInfo_) {
        // 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;
    }

    // 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 abort(AbortReason::Alloc);
        FillBytecodeTypeMap(script(), bytecodeTypeMap);
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::build()
{
    MOZ_TRY(init());

    if (script()->hasBaselineScript())
        script()->baselineScript()->resetMaxInliningDepth();

    MBasicBlock* entry;
    MOZ_TRY_VAR(entry, newBlock(info().firstStackSlot(), pc));
    MOZ_TRY(setCurrentAndSpecializePhis(entry));

#ifdef JS_JITSPEW
    if (info().isAnalysis()) {
        JitSpew(JitSpew_IonScripts, "Analyzing script %s:%zu (%p) %s",
                script()->filename(), script()->lineno(), (void*)script(),
                AnalysisModeString(info().analysisMode()));
    } else {
        JitSpew(JitSpew_IonScripts, "%sompiling script %s:%zu (%p) (warmup-counter=%" PRIu32 ", level=%s)",
                (script()->hasIonScript() ? "Rec" : "C"),
                script()->filename(), script()->lineno(), (void*)script(),
                script()->getWarmUpCount(), OptimizationLevelString(optimizationInfo().level()));
    }
#endif

    MOZ_TRY(initParameters());
    initLocals();

    // Initialize something for the env chain. We can bail out before the
    // start instruction, but the snapshot is encoded *at* the start
    // instruction, which means generating any code that could load into
    // registers is illegal.
    MInstruction* env = MConstant::New(alloc(), UndefinedValue());
    current->add(env);
    current->initSlot(info().environmentChainSlot(), env);

    // Initialize the return value.
    MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
    current->add(returnValue);
    current->initSlot(info().returnValueSlot(), returnValue);

    // Initialize the arguments object slot to undefined if necessary.
    if (info().hasArguments()) {
        MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
        current->add(argsObj);
        current->initSlot(info().argsObjSlot(), argsObj);
    }

    // Emit the start instruction, so we can begin real instructions.
    current->add(MStart::New(alloc()));

    // Guard against over-recursion. Do this before we start unboxing, since
    // this will create an OSI point that will read the incoming argument
    // values, which is nice to do before their last real use, to minimize
    // register/stack pressure.
    MCheckOverRecursed* check = MCheckOverRecursed::New(alloc());
    current->add(check);
    MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
    if (!entryRpCopy)
        return abort(AbortReason::Alloc);
    check->setResumePoint(entryRpCopy);

    // Parameters have been checked to correspond to the typeset, now we unbox
    // what we can in an infallible manner.
    MOZ_TRY(rewriteParameters());

    // 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 abort(AbortReason::Alloc);
        redeclCheck->setResumePoint(entryRpCopy);
    }

    // It's safe to start emitting actual IR, so now build the env chain.
    MOZ_TRY(initEnvironmentChain());
    if (info().needsArgsObj())
        initArgumentsObject();

    // The type analysis phase attempts to insert unbox operations near
    // definitions of values. It also attempts to replace uses in resume points
    // with the narrower, unboxed variants. However, we must prevent this
    // replacement from happening on values in the entry snapshot. Otherwise we
    // could get this:
    //
    //       v0 = MParameter(0)
    //       v1 = MParameter(1)
    //       --   ResumePoint(v2, v3)
    //       v2 = Unbox(v0, INT32)
    //       v3 = Unbox(v1, INT32)
    //
    // So we attach the initial resume point to each parameter, which the type
    // analysis explicitly checks (this is the same mechanism used for
    // effectful operations).
    for (uint32_t i = 0; i < info().endArgSlot(); i++) {
        MInstruction* ins = current->getEntrySlot(i)->toInstruction();
        if (ins->type() != MIRType::Value)
            continue;

        MResumePoint* entryRpCopy = MResumePoint::Copy(alloc(), current->entryResumePoint());
        if (!entryRpCopy)
            return abort(AbortReason::Alloc);
        ins->setResumePoint(entryRpCopy);
    }

#ifdef DEBUG
    // lazyArguments should never be accessed in |argsObjAliasesFormals| scripts.
    if (info().hasArguments() && !info().argsObjAliasesFormals())
        hasLazyArguments_ = true;
#endif

    insertRecompileCheck();

    auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
        // Discard unreferenced & pre-allocated resume points.
        replaceMaybeFallbackFunctionGetter(nullptr);
    });

    MOZ_TRY(traverseBytecode());

    if (script_->hasBaselineScript() &&
        inlinedBytecodeLength_ > script_->baselineScript()->inlinedBytecodeLength())
    {
        script_->baselineScript()->setInlinedBytecodeLength(inlinedBytecodeLength_);
    }

    MOZ_TRY(maybeAddOsrTypeBarriers());
    MOZ_TRY(processIterators());

    if (!info().isAnalysis() && !abortedPreliminaryGroups().empty())
        return abort(AbortReason::PreliminaryObjects);

    MOZ_ASSERT(loopDepth_ == 0);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::processIterators()
{
    // Find and mark phis that must transitively hold an iterator live.

    Vector<MDefinition*, 8, SystemAllocPolicy> worklist;

    for (size_t i = 0; i < iterators_.length(); i++) {
        MDefinition* iter = iterators_[i];
        if (!iter->isInWorklist()) {
            if (!worklist.append(iter))
                return abort(AbortReason::Alloc);
            iter->setInWorklist();
        }
    }

    while (!worklist.empty()) {
        MDefinition* def = worklist.popCopy();
        def->setNotInWorklist();

        if (def->isPhi()) {
            MPhi* phi = def->toPhi();
            phi->setIterator();
            phi->setImplicitlyUsedUnchecked();
        }

        for (MUseDefIterator iter(def); iter; iter++) {
            MDefinition* use = iter.def();
            if (!use->isInWorklist() && (!use->isPhi() || !use->toPhi()->isIterator())) {
                if (!worklist.append(use))
                    return abort(AbortReason::Alloc);
                use->setInWorklist();
            }
        }
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::buildInline(IonBuilder* callerBuilder, MResumePoint* callerResumePoint,
                        CallInfo& callInfo)
{
    inlineCallInfo_ = &callInfo;

    MOZ_TRY(init());

    JitSpew(JitSpew_IonScripts, "Inlining script %s:%zu (%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.
    MBasicBlock* entry;
    MOZ_TRY_VAR(entry, newBlock(info().firstStackSlot(), pc));
    MOZ_TRY(setCurrentAndSpecializePhis(entry));

    current->setCallerResumePoint(callerResumePoint);

    // Connect the entrance block to the last block in the caller's graph.
    MBasicBlock* predecessor = callerBuilder->current;
    MOZ_ASSERT(predecessor == callerResumePoint->block());

    predecessor->end(MGoto::New(alloc(), current));
    if (!current->addPredecessorWithoutPhis(predecessor))
        return abort(AbortReason::Alloc);

    // Initialize env chain slot to Undefined.  It's set later by
    // |initEnvironmentChain|.
    MInstruction* env = MConstant::New(alloc(), UndefinedValue());
    current->add(env);
    current->initSlot(info().environmentChainSlot(), env);

    // Initialize |return value| slot.
    MInstruction* returnValue = MConstant::New(alloc(), UndefinedValue());
    current->add(returnValue);
    current->initSlot(info().returnValueSlot(), returnValue);

    // Initialize |arguments| slot.
    if (info().hasArguments()) {
        MInstruction* argsObj = MConstant::New(alloc(), UndefinedValue());
        current->add(argsObj);
        current->initSlot(info().argsObjSlot(), argsObj);
    }

    // Initialize |this| slot.
    current->initSlot(info().thisSlot(), callInfo.thisArg());

    JitSpew(JitSpew_Inlining, "Initializing %u arg slots", info().nargs());

    // NB: Ion does not inline functions which |needsArgsObj|.  So using argSlot()
    // instead of argSlotUnchecked() below is OK
    MOZ_ASSERT(!info().needsArgsObj());

    // Initialize actually set arguments.
    uint32_t existing_args = Min<uint32_t>(callInfo.argc(), info().nargs());
    for (size_t i = 0; i < existing_args; ++i) {
        MDefinition* arg = callInfo.getArg(i);
        current->initSlot(info().argSlot(i), arg);
    }

    // Pass Undefined for missing arguments
    for (size_t i = callInfo.argc(); i < info().nargs(); ++i) {
        MConstant* arg = MConstant::New(alloc(), UndefinedValue());
        current->add(arg);
        current->initSlot(info().argSlot(i), arg);
    }

    JitSpew(JitSpew_Inlining, "Initializing %u locals", info().nlocals());

    initLocals();

    JitSpew(JitSpew_Inlining, "Inline entry block MResumePoint %p, %u stack slots",
            (void*) current->entryResumePoint(), current->entryResumePoint()->stackDepth());

    // +2 for the env chain and |this|, maybe another +1 for arguments object slot.
    MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots());

#ifdef DEBUG
    if (script_->argumentsHasVarBinding())
        hasLazyArguments_ = true;
#endif

    insertRecompileCheck();

    // Initialize the env chain now that all resume points operands are
    // initialized.
    MOZ_TRY(initEnvironmentChain(callInfo.fun()));

    auto clearLastPriorResumePoint = mozilla::MakeScopeExit([&] {
        // Discard unreferenced & pre-allocated resume points.
        replaceMaybeFallbackFunctionGetter(nullptr);
    });

    MOZ_TRY(traverseBytecode());


    MOZ_ASSERT(iterators_.empty(), "Iterators should be added to outer builder");

    if (!info().isAnalysis() && !abortedPreliminaryGroups().empty())
        return abort(AbortReason::PreliminaryObjects);

    return Ok();
}

void
IonBuilder::rewriteParameter(uint32_t slotIdx, MDefinition* param, 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.
AbortReasonOr<Ok>
IonBuilder::rewriteParameters()
{
    MOZ_ASSERT(info().environmentChainSlot() == 0);

    // If this JSScript is not the code of a function, then skip the
    // initialization of function parameters.
    if (!info().funMaybeLazy())
        return Ok();

    for (uint32_t i = info().startArgSlot(); i < info().endArgSlot(); i++) {
        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);
        MDefinition* param = current->getSlot(i);
        rewriteParameter(i, param, param->toParameter()->index());
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::initParameters()
{
    // If this JSScript is not the code of a function, then skip the
    // initialization of function parameters.
    if (!info().funMaybeLazy())
        return Ok();

    // If we are doing OSR on a frame which initially executed in the
    // interpreter and didn't accumulate type information, try to use that OSR
    // frame to determine possible initial types for 'this' and parameters.

    if (thisTypes->empty() && baselineFrame_) {
        TypeSet::Type type = baselineFrame_->thisType;
        if (type.isSingletonUnchecked())
            checkNurseryObject(type.singleton());
        thisTypes->addType(type, alloc_->lifoAlloc());
    }

    MParameter* param = MParameter::New(alloc(), MParameter::THIS_SLOT, thisTypes);
    current->add(param);
    current->initSlot(info().thisSlot(), param);

    for (uint32_t i = 0; i < info().nargs(); i++) {
        TemporaryTypeSet* types = &argTypes[i];
        if (types->empty() && baselineFrame_ &&
            !script_->baselineScript()->modifiesArguments())
        {
            TypeSet::Type type = baselineFrame_->argTypes[i];
            if (type.isSingletonUnchecked())
                checkNurseryObject(type.singleton());
            types->addType(type, alloc_->lifoAlloc());
        }

        param = MParameter::New(alloc().fallible(), i, types);
        if (!param)
            return abort(AbortReason::Alloc);
        current->add(param);
        current->initSlot(info().argSlotUnchecked(i), param);
    }

    return Ok();
}

void
IonBuilder::initLocals()
{
    // Initialize all frame slots to undefined. Lexical bindings are temporal
    // dead zoned in bytecode.

    if (info().nlocals() == 0)
        return;

    MConstant* undef = MConstant::New(alloc(), UndefinedValue());
    current->add(undef);

    for (uint32_t i = 0; i < info().nlocals(); i++)
        current->initSlot(info().localSlot(i), undef);
}

bool
IonBuilder::usesEnvironmentChain()
{
    // We don't have a BaselineScript if we're running the arguments analysis,
    // but it's fine to assume we always use the environment chain in this case.
    if (info().analysisMode() == Analysis_ArgumentsUsage)
        return true;
    return script()->baselineScript()->usesEnvironmentChain();
}

AbortReasonOr<Ok>
IonBuilder::initEnvironmentChain(MDefinition* callee)
{
    MInstruction* env = nullptr;

    // If the script doesn't use the envchain, then it's already initialized
    // from earlier.  However, always make a env chain when |needsArgsObj| is true
    // for the script, since arguments object construction requires the env chain
    // to be passed in.
    if (!info().needsArgsObj() && !usesEnvironmentChain())
        return Ok();

    // The env chain is only tracked in scripts that have NAME opcodes which
    // will try to access the env. For other scripts, the env instructions
    // will be held live by resume points and code will still be generated for
    // them, so just use a constant undefined value.

    if (JSFunction* fun = info().funMaybeLazy()) {
        if (!callee) {
            MCallee* calleeIns = MCallee::New(alloc());
            current->add(calleeIns);
            callee = calleeIns;
        }
        env = MFunctionEnvironment::New(alloc(), callee);
        current->add(env);

        // This reproduce what is done in CallObject::createForFunction. Skip
        // this for the arguments analysis, as the script might not have a
        // baseline script with template objects yet.
        if (fun->needsSomeEnvironmentObject() &&
            info().analysisMode() != Analysis_ArgumentsUsage)
        {
            if (fun->needsNamedLambdaEnvironment())
                env = createNamedLambdaObject(callee, env);

            // TODO: Parameter expression-induced extra var environment not
            // yet handled.
            if (fun->needsExtraBodyVarEnvironment())
                return abort(AbortReason::Disable, "Extra var environment unsupported");

            if (fun->needsCallObject())
                MOZ_TRY_VAR(env, createCallObject(callee, env));
        }
    } else if (ModuleObject* module = info().module()) {
        // Modules use a pre-created env object.
        env = constant(ObjectValue(module->initialEnvironment()));
    } else {
        // For global scripts without a non-syntactic global scope, the env
        // chain is the global lexical env.
        MOZ_ASSERT(!script()->isForEval());
        MOZ_ASSERT(!script()->hasNonSyntacticScope());
        env = constant(ObjectValue(script()->global().lexicalEnvironment()));
    }

    // Update the environment slot from UndefinedValue only after initial
    // environment is created so that bailout doesn't see a partial env.
    // See: |InitFromBailout|
    current->setEnvironmentChain(env);
    return Ok();
}

void
IonBuilder::initArgumentsObject()
{
    JitSpew(JitSpew_IonMIR, "%s:%zu - 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);
}

AbortReasonOr<Ok>
IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_,
                                   MIRType type, TemporaryTypeSet* typeSet)
{
    MInstruction*& def = *def_;
    MBasicBlock* osrBlock = def->block();

    // Clear bogus type information added in newOsrPreheader().
    def->setResultType(MIRType::Value);
    def->setResultTypeSet(nullptr);

    if (typeSet && !typeSet->unknown()) {
        MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet);
        osrBlock->insertBefore(osrBlock->lastIns(), barrier);
        osrBlock->rewriteSlot(slot, barrier);
        def = barrier;

        // If the TypeSet is more precise than |type|, adjust |type| for the
        // code below.
        if (type == MIRType::Value)
            type = barrier->type();
    } else if (type == MIRType::Null ||
               type == MIRType::Undefined ||
               type == MIRType::MagicOptimizedArguments)
    {
        // No unbox instruction will be added below, so check the type by
        // adding a type barrier for a singleton type set.
        TypeSet::Type ntype = TypeSet::PrimitiveType(ValueTypeFromMIRType(type));
        LifoAlloc* lifoAlloc = alloc().lifoAlloc();
        typeSet = lifoAlloc->new_<TemporaryTypeSet>(lifoAlloc, ntype);
        if (!typeSet)
            return abort(AbortReason::Alloc);
        MInstruction* barrier = MTypeBarrier::New(alloc(), def, typeSet);
        osrBlock->insertBefore(osrBlock->lastIns(), barrier);
        osrBlock->rewriteSlot(slot, barrier);
        def = barrier;
    }

    switch (type) {
      case MIRType::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(hasLazyArguments_);
        MConstant* lazyArg = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
        osrBlock->insertBefore(osrBlock->lastIns(), lazyArg);
        osrBlock->rewriteSlot(slot, lazyArg);
        def = lazyArg;
        break;
      }

      default:
        break;
    }

    MOZ_ASSERT(def == osrBlock->getSlot(slot));
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::maybeAddOsrTypeBarriers()
{
    if (!info().osrPc())
        return Ok();

    // 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(AbortReason::Disable, "OSR block only reachable through catch block");
    }

    MBasicBlock* preheader = osrBlock->getSuccessor(0);
    MBasicBlock* header = preheader->getSuccessor(0);
    static const size_t OSR_PHI_POSITION = 1;
    MOZ_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock);

    MResumePoint* headerRp = header->entryResumePoint();
    size_t stackDepth = headerRp->stackDepth();
    MOZ_ASSERT(stackDepth == osrBlock->stackDepth());
    for (uint32_t slot = info().startArgSlot(); slot < stackDepth; slot++) {
        // Aliased slots are never accessed, since they need to go through
        // the callobject. The typebarriers are added there and can be
        // discarded here.
        if (info().isSlotAliased(slot))
            continue;

        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

        MInstruction* def = osrBlock->getSlot(slot)->toInstruction();
        MPhi* preheaderPhi = preheader->getSlot(slot)->toPhi();
        MPhi* headerPhi = headerRp->getOperand(slot)->toPhi();

        MIRType type = headerPhi->type();
        TemporaryTypeSet* typeSet = headerPhi->resultTypeSet();

        MOZ_TRY(addOsrValueTypeBarrier(slot, &def, type, typeSet));

        preheaderPhi->replaceOperand(OSR_PHI_POSITION, def);
        preheaderPhi->setResultType(type);
        preheaderPhi->setResultTypeSet(typeSet);
    }

    return Ok();
}

enum class CFGState : uint32_t {
    Alloc = 0,
    Abort = 1,
    Success = 2
};

static CFGState
GetOrCreateControlFlowGraph(TempAllocator& tempAlloc, JSScript* script,
                            const ControlFlowGraph** cfgOut)
{
    if (script->hasBaselineScript() && script->baselineScript()->controlFlowGraph()) {
        *cfgOut = script->baselineScript()->controlFlowGraph();
        return CFGState::Success;
    }

    ControlFlowGenerator cfgenerator(tempAlloc, script);
    if (!cfgenerator.traverseBytecode()) {
        if (cfgenerator.aborted())
            return CFGState::Abort;
        return CFGState::Alloc;
    }

    // If possible cache the control flow graph on the baseline script.
    TempAllocator* graphAlloc = nullptr;
    if (script->hasBaselineScript()) {
        LifoAlloc& lifoAlloc = script->zone()->jitZone()->cfgSpace()->lifoAlloc();
        LifoAlloc::AutoFallibleScope fallibleAllocator(&lifoAlloc);
        graphAlloc = lifoAlloc.new_<TempAllocator>(&lifoAlloc);
        if (!graphAlloc)
            return CFGState::Alloc;
    } else {
        graphAlloc = &tempAlloc;
    }

    ControlFlowGraph* cfg = cfgenerator.getGraph(*graphAlloc);
    if (!cfg)
        return CFGState::Alloc;

    if (script->hasBaselineScript()) {
        MOZ_ASSERT(!script->baselineScript()->controlFlowGraph());
        script->baselineScript()->setControlFlowGraph(cfg);
    }

    if (JitSpewEnabled(JitSpew_CFG)) {
        JitSpew(JitSpew_CFG, "Generating graph for %s:%zu",
                             script->filename(), script->lineno());
        Fprinter& print = JitSpewPrinter();
        cfg->dump(print, script);
    }

    *cfgOut = cfg;
    return CFGState::Success;
}

// We traverse the bytecode using the control flow graph. This structure contains
// a graph of CFGBlocks in RPO order.
//
// Per CFGBlock we take the corresponding MBasicBlock and start iterating the
// bytecode of that CFGBlock. 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.
//
// Afterwards we visit the control flow instruction. There we add the ending ins
// of the MBasicBlock and create new MBasicBlocks for the successors. That means
// adding phi nodes for diamond join points, making sure to propagate types
// around loops ...
//
// We keep a link between a CFGBlock and the entry MBasicBlock (in blockWorklist).
// That vector only contains the MBasicBlocks that correspond with a CFGBlock.
// We can create new MBasicBlocks that don't correspond to a CFGBlock.
AbortReasonOr<Ok>
IonBuilder::traverseBytecode()
{
    CFGState state = GetOrCreateControlFlowGraph(alloc(), info().script(), &cfg);
    MOZ_ASSERT_IF(cfg && info().script()->hasBaselineScript(),
                  info().script()->baselineScript()->controlFlowGraph() == cfg);
    if (state == CFGState::Alloc)
        return abort(AbortReason::Alloc);
    if (state == CFGState::Abort)
        return abort(AbortReason::Disable, "Couldn't create the CFG of script");

    if (!blockWorklist.growBy(cfg->numBlocks()))
        return abort(AbortReason::Alloc);
    blockWorklist[0] = current;

    size_t i = 0;
    while (i < cfg->numBlocks()) {
        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

        bool restarted = false;
        const CFGBlock* cfgblock = cfg->block(i);
        MBasicBlock* mblock = blockWorklist[i];
        MOZ_ASSERT(mblock && !mblock->isDead());

        MOZ_TRY(visitBlock(cfgblock, mblock));
        MOZ_TRY(visitControlInstruction(cfgblock->stopIns(), &restarted));

        if (restarted) {
            // Move back to the start of the loop.
            while (!blockWorklist[i] || blockWorklist[i]->isDead()) {
                MOZ_ASSERT(i > 0);
                i--;
            }
            MOZ_ASSERT(cfgblock->stopIns()->isBackEdge());
            MOZ_ASSERT(loopHeaderStack_.back() == blockWorklist[i]);
        } else {
            i++;
        }
    }

#ifdef DEBUG
    MOZ_ASSERT(graph().numBlocks() >= blockWorklist.length());
    for (i = 0; i < cfg->numBlocks(); i++) {
        MOZ_ASSERT(blockWorklist[i]);
        MOZ_ASSERT(!blockWorklist[i]->isDead());
        MOZ_ASSERT_IF(i != 0, blockWorklist[i]->id() != 0);
    }
#endif

    cfg = nullptr;

    blockWorklist.clear();
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitBlock(const CFGBlock* cfgblock, MBasicBlock* mblock)
{
    mblock->setLoopDepth(loopDepth_);

    cfgCurrent = cfgblock;
    pc = cfgblock->startPc();

    if (mblock->pc() && script()->hasScriptCounts())
        mblock->setHitCount(script()->getHitCount(mblock->pc()));

    // Optimization to move a predecessor that only has this block as successor
    // just before this block.  Skip this optimization if the previous block is
    // not part of the same function, as we might have to backtrack on inlining
    // failures.
    if (mblock->numPredecessors() == 1 && mblock->getPredecessor(0)->numSuccessors() == 1 &&
        !mblock->getPredecessor(0)->outerResumePoint())
    {
        graph().removeBlockFromList(mblock->getPredecessor(0));
        graph().addBlock(mblock->getPredecessor(0));
    }

    MOZ_TRY(setCurrentAndSpecializePhis(mblock));
    graph().addBlock(mblock);

    while (pc < cfgblock->stopPc()) {
        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

#ifdef DEBUG
        // 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(pc);

        for (unsigned i = 0; i < nuses; i++) {
            MDefinition* def = current->peek(-int32_t(i + 1));
            if (!popped.append(def) || !poppedUses.append(def->defUseCount()))
                return abort(AbortReason::Alloc);
        }
#endif

        // Nothing in inspectOpcode() is allowed to advance the pc.
        JSOp op = JSOp(*pc);
        MOZ_TRY(inspectOpcode(op));

#ifdef DEBUG
        for (size_t i = 0; i < popped.length(); i++) {
            switch (op) {
              case JSOP_POP:
              case JSOP_POPN:
              case JSOP_DUPAT:
              case JSOP_DUP:
              case JSOP_DUP2:
              case JSOP_PICK:
              case JSOP_UNPICK:
              case JSOP_SWAP:
              case JSOP_SETARG:
              case JSOP_SETLOCAL:
              case JSOP_INITLEXICAL:
              case JSOP_SETRVAL:
              case JSOP_VOID:
                // 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 Ok();
}

bool
IonBuilder::blockIsOSREntry(const CFGBlock* block, const CFGBlock* predecessor)
{
    jsbytecode* entryPc = block->startPc();

    if (!info().osrPc())
        return false;

    if (entryPc == predecessor->startPc()) {
        // The predecessor is the actual osr entry block. Since it is empty
        // the current block also starts a the osr pc. But it isn't the osr entry.
        MOZ_ASSERT(predecessor->stopPc() == predecessor->startPc());
        return false;
    }

    if (block->stopPc() == block->startPc() && block->stopIns()->isBackEdge()) {
        // An empty block with only a backedge can never be a loop entry.
        return false;
    }

    MOZ_ASSERT(*info().osrPc() == JSOP_LOOPENTRY);
    // Skip over the LOOPENTRY to match.
    return GetNextPc(info().osrPc()) == entryPc;
}

AbortReasonOr<Ok>
IonBuilder::visitGoto(CFGGoto* ins)
{
    // Test if this potentially was a fake loop and create OSR entry if that is
    // the case.
    const CFGBlock* successor = ins->getSuccessor(0);
    if (blockIsOSREntry(successor, cfgCurrent)) {
        MBasicBlock* preheader;
        MOZ_TRY_VAR(preheader, newOsrPreheader(current, successor->startPc(), pc));
        current->end(MGoto::New(alloc(), preheader));
        MOZ_TRY(setCurrentAndSpecializePhis(preheader));
    }

    size_t id = successor->id();
    bool create = !blockWorklist[id] || blockWorklist[id]->isDead();

    current->popn(ins->popAmount());

    if (create)
        MOZ_TRY_VAR(blockWorklist[id], newBlock(current, successor->startPc()));

    MBasicBlock* succ = blockWorklist[id];
    current->end(MGoto::New(alloc(), succ));

    if (!create) {
        if (!succ->addPredecessor(alloc(), current))
            return abort(AbortReason::Alloc);
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitBackEdge(CFGBackEdge* ins, bool* restarted)
{
    loopDepth_--;

    MBasicBlock* loopEntry = blockWorklist[ins->getSuccessor(0)->id()];
    current->end(MGoto::New(alloc(), loopEntry));

    MOZ_ASSERT(ins->getSuccessor(0) == cfgLoopHeaderStack_.back());

    // Compute phis in the loop header and propagate them throughout the loop,
    // including the successor.
    AbortReason r = loopEntry->setBackedge(alloc(), current);
    switch (r) {
      case AbortReason::NoAbort:
        loopHeaderStack_.popBack();
#ifdef DEBUG
        cfgLoopHeaderStack_.popBack();
#endif
        return Ok();

      case AbortReason::Disable:
        // If there are types for variables on the backedge that were not
        // present at the original loop header, then uses of the variables'
        // phis may have generated incorrect nodes. The new types have been
        // incorporated into the header phis, so remove all blocks for the
        // loop body and restart with the new types.
        *restarted = true;
        MOZ_TRY(restartLoop(ins->getSuccessor(0)));
        return Ok();

      default:
        return abort(r);
    }
}

AbortReasonOr<Ok>
IonBuilder::visitLoopEntry(CFGLoopEntry* loopEntry)
{
    unsigned stackPhiCount = loopEntry->stackPhiCount();
    const CFGBlock* successor = loopEntry->getSuccessor(0);
    bool osr = blockIsOSREntry(successor, cfgCurrent);
    if (osr) {
        MOZ_ASSERT(loopEntry->canOsr());
        MBasicBlock* preheader;
        MOZ_TRY_VAR(preheader, newOsrPreheader(current, successor->startPc(), pc));
        current->end(MGoto::New(alloc(), preheader));
        MOZ_TRY(setCurrentAndSpecializePhis(preheader));
    }

    loopDepth_++;
    MBasicBlock* header;
    MOZ_TRY_VAR(header, newPendingLoopHeader(current, successor->startPc(), osr,
                                             loopEntry->canOsr(), stackPhiCount));
    blockWorklist[successor->id()] = header;

    current->end(MGoto::New(alloc(), header));

    if (!loopHeaderStack_.append(header))
        return abort(AbortReason::Alloc);
#ifdef DEBUG
    if (!cfgLoopHeaderStack_.append(successor))
        return abort(AbortReason::Alloc);
#endif

    MOZ_TRY(analyzeNewLoopTypes(cfgCurrent));

    setCurrent(header);
    pc = header->pc();

    initLoopEntry();
    return Ok();
}

bool
IonBuilder::initLoopEntry()
{
    current->add(MInterruptCheck::New(alloc()));
    insertRecompileCheck();

    return true;
}

AbortReasonOr<Ok>
IonBuilder::visitControlInstruction(CFGControlInstruction* ins, bool* restarted)
{
    switch (ins->type()) {
      case CFGControlInstruction::Type_Test:
        return visitTest(ins->toTest());
      case CFGControlInstruction::Type_Compare:
        return visitCompare(ins->toCompare());
      case CFGControlInstruction::Type_Goto:
        return visitGoto(ins->toGoto());
      case CFGControlInstruction::Type_BackEdge:
        return visitBackEdge(ins->toBackEdge(), restarted);
      case CFGControlInstruction::Type_LoopEntry:
        return visitLoopEntry(ins->toLoopEntry());
      case CFGControlInstruction::Type_Return:
      case CFGControlInstruction::Type_RetRVal:
        return visitReturn(ins);
      case CFGControlInstruction::Type_Try:
        return visitTry(ins->toTry());
      case CFGControlInstruction::Type_Throw:
        return visitThrow(ins->toThrow());
      case CFGControlInstruction::Type_TableSwitch:
        return visitTableSwitch(ins->toTableSwitch());
    }
    MOZ_CRASH("Unknown Control Instruction");
}

AbortReasonOr<Ok>
IonBuilder::inspectOpcode(JSOp op)
{
    // Add not yet implemented opcodes at the bottom of the switch!
    switch (op) {
      case JSOP_NOP:
      case JSOP_NOP_DESTRUCTURING:
      case JSOP_TRY_DESTRUCTURING_ITERCLOSE:
      case JSOP_LINENO:
      case JSOP_JUMPTARGET:
      case JSOP_LABEL:
        return Ok();

      case JSOP_UNDEFINED:
        // If this ever changes, change what JSOP_GIMPLICITTHIS does too.
        pushConstant(UndefinedValue());
        return Ok();

      case JSOP_IFNE:
      case JSOP_IFEQ:
      case JSOP_RETURN:
      case JSOP_RETRVAL:
      case JSOP_AND:
      case JSOP_OR:
      case JSOP_TRY:
      case JSOP_THROW:
      case JSOP_GOTO:
      case JSOP_CONDSWITCH:
      case JSOP_LOOPENTRY:
      case JSOP_TABLESWITCH:
      case JSOP_CASE:
      case JSOP_DEFAULT:
        // Control flow opcodes should be handled in the ControlFlowGenerator.
        MOZ_CRASH("Shouldn't encounter this opcode.");

      case JSOP_BITNOT:
        return jsop_bitnot();

      case JSOP_BITAND:
      case JSOP_BITOR:
      case JSOP_BITXOR:
      case JSOP_LSH:
      case JSOP_RSH:
      case JSOP_URSH:
        return jsop_bitop(op);

      case JSOP_ADD:
      case JSOP_SUB:
      case JSOP_MUL:
      case JSOP_DIV:
      case JSOP_MOD:
        return jsop_binary_arith(op);

      case JSOP_POW:
        return jsop_pow();

      case JSOP_POS:
        return jsop_pos();

      case JSOP_NEG:
        return jsop_neg();

      case JSOP_TOSTRING:
        return jsop_tostring();

      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 Ok();

      case JSOP_STRING:
        pushConstant(StringValue(info().getAtom(pc)));
        return Ok();

      case JSOP_SYMBOL: {
        unsigned which = GET_UINT8(pc);
        JS::Symbol* sym = compartment->runtime()->wellKnownSymbols().get(which);
        pushConstant(SymbolValue(sym));
        return Ok();
      }

      case JSOP_ZERO:
        pushConstant(Int32Value(0));
        return Ok();

      case JSOP_ONE:
        pushConstant(Int32Value(1));
        return Ok();

      case JSOP_NULL:
        pushConstant(NullValue());
        return Ok();

      case JSOP_VOID:
        current->pop();
        pushConstant(UndefinedValue());
        return Ok();

      case JSOP_HOLE:
        pushConstant(MagicValue(JS_ELEMENTS_HOLE));
        return Ok();

      case JSOP_FALSE:
        pushConstant(BooleanValue(false));
        return Ok();

      case JSOP_TRUE:
        pushConstant(BooleanValue(true));
        return Ok();

      case JSOP_ARGUMENTS:
        return jsop_arguments();

      case JSOP_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 Ok();

      case JSOP_SETARG:
        return jsop_setarg(GET_ARGNO(pc));

      case JSOP_GETLOCAL:
        current->pushLocal(GET_LOCALNO(pc));
        return Ok();

      case JSOP_SETLOCAL:
        current->setLocal(GET_LOCALNO(pc));
        return Ok();

      case JSOP_THROWSETCONST:
      case JSOP_THROWSETALIASEDCONST:
      case JSOP_THROWSETCALLEE:
        return jsop_throwsetconst();

      case JSOP_CHECKLEXICAL:
        return jsop_checklexical();

      case JSOP_INITLEXICAL:
        current->setLocal(GET_LOCALNO(pc));
        return Ok();

      case JSOP_INITGLEXICAL: {
        MOZ_ASSERT(!script()->hasNonSyntacticScope());
        MDefinition* value = current->pop();
        current->push(constant(ObjectValue(script()->global().lexicalEnvironment())));
        current->push(value);
        return jsop_setprop(info().getAtom(pc)->asPropertyName());
      }

      case JSOP_CHECKALIASEDLEXICAL:
        return jsop_checkaliasedlexical(EnvironmentCoordinate(pc));

      case JSOP_INITALIASEDLEXICAL:
        return jsop_setaliasedvar(EnvironmentCoordinate(pc));

      case JSOP_UNINITIALIZED:
        pushConstant(MagicValue(JS_UNINITIALIZED_LEXICAL));
        return Ok();

      case JSOP_POP: {
        MDefinition* def = current->pop();

        // 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 Ok();
        if (def->isConstant())
            return Ok();
        return maybeInsertResume();
      }

      case JSOP_POPN:
        for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++)
            current->pop();
        return Ok();

      case JSOP_DUPAT:
        current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc));
        return Ok();

      case JSOP_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_INC:
        return jsop_initelem_inc();

      case JSOP_INITELEM_ARRAY:
        return jsop_initelem_array();

      case JSOP_INITPROP:
      case JSOP_INITLOCKEDPROP:
      case JSOP_INITHIDDENPROP:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_initprop(name);
      }

      case JSOP_MUTATEPROTO:
      {
        return jsop_mutateproto();
      }

      case JSOP_INITPROP_GETTER:
      case JSOP_INITHIDDENPROP_GETTER:
      case JSOP_INITPROP_SETTER:
      case JSOP_INITHIDDENPROP_SETTER: {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_initprop_getter_setter(name);
      }

      case JSOP_INITELEM_GETTER:
      case JSOP_INITHIDDENELEM_GETTER:
      case JSOP_INITELEM_SETTER:
      case JSOP_INITHIDDENELEM_SETTER:
        return jsop_initelem_getter_setter();

      case JSOP_FUNCALL:
        return jsop_funcall(GET_ARGC(pc));

      case JSOP_FUNAPPLY:
        return jsop_funapply(GET_ARGC(pc));

      case JSOP_SPREADCALL:
        return jsop_spreadcall();

      case JSOP_CALL:
      case JSOP_CALL_IGNORES_RV:
      case JSOP_CALLITER:
      case JSOP_NEW:
        MOZ_TRY(jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL,
                          (JSOp)*pc == JSOP_CALL_IGNORES_RV));
        if (op == JSOP_CALLITER) {
            if (!outermostBuilder()->iterators_.append(current->peek(-1)))
                return abort(AbortReason::Alloc);
        }
        return Ok();

      case JSOP_EVAL:
      case JSOP_STRICTEVAL:
        return jsop_eval(GET_ARGC(pc));

      case JSOP_INT8:
        pushConstant(Int32Value(GET_INT8(pc)));
        return Ok();

      case JSOP_UINT16:
        pushConstant(Int32Value(GET_UINT16(pc)));
        return Ok();

      case JSOP_GETGNAME:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        if (!script()->hasNonSyntacticScope())
            return jsop_getgname(name);
        return jsop_getname(name);
      }

      case JSOP_SETGNAME:
      case JSOP_STRICTSETGNAME:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        JSObject* obj = nullptr;
        if (!script()->hasNonSyntacticScope())
            obj = testGlobalLexicalBinding(name);
        if (obj)
            return setStaticName(obj, name);
        return jsop_setprop(name);
      }

      case JSOP_GETNAME:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_getname(name);
      }

      case JSOP_GETINTRINSIC:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_intrinsic(name);
      }

      case JSOP_GETIMPORT:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_getimport(name);
      }

      case JSOP_BINDGNAME:
        if (!script()->hasNonSyntacticScope()) {
            if (JSObject* env = testGlobalLexicalBinding(info().getName(pc))) {
                pushConstant(ObjectValue(*env));
                return Ok();
            }
        }
        // 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 Ok();

      case JSOP_DUP2:
        return jsop_dup2();

      case JSOP_SWAP:
        current->swapAt(-1);
        return Ok();

      case JSOP_PICK:
        current->pick(-GET_INT8(pc));
        return Ok();

      case JSOP_UNPICK:
        current->unpick(-GET_INT8(pc));
        return Ok();

      case JSOP_GETALIASEDVAR:
        return jsop_getaliasedvar(EnvironmentCoordinate(pc));

      case JSOP_SETALIASEDVAR:
        return jsop_setaliasedvar(EnvironmentCoordinate(pc));

      case JSOP_UINT24:
        pushConstant(Int32Value(GET_UINT24(pc)));
        return Ok();

      case JSOP_INT32:
        pushConstant(Int32Value(GET_INT32(pc)));
        return Ok();

      case JSOP_LOOPHEAD:
        // JSOP_LOOPHEAD is handled when processing the loop header.
        MOZ_CRASH("JSOP_LOOPHEAD outside loop");

      case JSOP_GETELEM:
      case JSOP_CALLELEM:
        MOZ_TRY(jsop_getelem());
        if (op == JSOP_CALLELEM)
            MOZ_TRY(improveThisTypesForCall());
        return Ok();

      case JSOP_SETELEM:
      case JSOP_STRICTSETELEM:
        return jsop_setelem();

      case JSOP_LENGTH:
        return jsop_length();

      case JSOP_NOT:
        return jsop_not();

      case JSOP_FUNCTIONTHIS:
        return jsop_functionthis();

      case JSOP_GLOBALTHIS:
        return jsop_globalthis();

      case JSOP_CALLEE: {
         MDefinition* callee = getCallee();
         current->push(callee);
         return Ok();
      }

      case JSOP_SUPERBASE:
        return jsop_superbase();

      case JSOP_GETPROP_SUPER:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_getprop_super(name);
      }

      case JSOP_GETELEM_SUPER:
        return jsop_getelem_super();

      case JSOP_GETPROP:
      case JSOP_CALLPROP:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        MOZ_TRY(jsop_getprop(name));
        if (op == JSOP_CALLPROP)
            MOZ_TRY(improveThisTypesForCall());
        return Ok();
      }

      case JSOP_SETPROP:
      case JSOP_STRICTSETPROP:
      case JSOP_SETNAME:
      case JSOP_STRICTSETNAME:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_setprop(name);
      }

      case JSOP_DELPROP:
      case JSOP_STRICTDELPROP:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_delprop(name);
      }

      case JSOP_DELELEM:
      case JSOP_STRICTDELELEM:
        return jsop_delelem();

      case JSOP_REGEXP:
        return jsop_regexp(info().getRegExp(pc));

      case JSOP_CALLSITEOBJ:
        if (info().analysisMode() == Analysis_ArgumentsUsage) {
            // When analyzing arguments usage, it is possible that the
            // template object is not yet canonicalized. Push an incorrect
            // object; it does not matter for arguments analysis.
            pushConstant(ObjectValue(*info().getObject(pc)));
        } else {
            ArrayObject* raw = &script()->getObject(GET_UINT32_INDEX(pc) + 1)->as<ArrayObject>();
            JSObject* obj = script()->compartment()->getExistingTemplateLiteralObject(raw);
            pushConstant(ObjectValue(*obj));
        }
        return Ok();

      case JSOP_OBJECT:
        return jsop_object(info().getObject(pc));

      case JSOP_CLASSCONSTRUCTOR:
        return jsop_classconstructor();

      case JSOP_TYPEOF:
      case JSOP_TYPEOFEXPR:
        return jsop_typeof();

      case JSOP_TOASYNC:
        return jsop_toasync();

      case JSOP_TOASYNCGEN:
        return jsop_toasyncgen();

      case JSOP_TOASYNCITER:
        return jsop_toasynciter();

      case JSOP_TOID:
        return jsop_toid();

      case JSOP_ITERNEXT:
        return jsop_iternext();

      case JSOP_LAMBDA:
        return jsop_lambda(info().getFunction(pc));

      case JSOP_LAMBDA_ARROW:
        return jsop_lambda_arrow(info().getFunction(pc));

      case JSOP_SETFUNNAME:
        return jsop_setfunname(GET_UINT8(pc));

      case JSOP_PUSHLEXICALENV:
        return jsop_pushlexicalenv(GET_UINT32_INDEX(pc));

      case JSOP_POPLEXICALENV:
        current->setEnvironmentChain(walkEnvironmentChain(1));
        return Ok();

      case JSOP_FRESHENLEXICALENV:
        return jsop_copylexicalenv(true);

      case JSOP_RECREATELEXICALENV:
        return jsop_copylexicalenv(false);

      case JSOP_ITER:
        return jsop_iter();

      case JSOP_MOREITER:
        return jsop_itermore();

      case JSOP_ISNOITER:
        return jsop_isnoiter();

      case JSOP_ENDITER:
        return jsop_iterend();

      case JSOP_IN:
        return jsop_in();

      case JSOP_HASOWN:
        return jsop_hasown();

      case JSOP_SETRVAL:
        MOZ_ASSERT(!script()->noScriptRval());
        current->setSlot(info().returnValueSlot(), current->pop());
        return Ok();

      case JSOP_INSTANCEOF:
        return jsop_instanceof();

      case JSOP_DEBUGLEAVELEXICALENV:
        return Ok();

      case JSOP_DEBUGGER:
        return jsop_debugger();

      case JSOP_GIMPLICITTHIS:
        if (!script()->hasNonSyntacticScope()) {
            pushConstant(UndefinedValue());
            return Ok();
        }
        // Fallthrough to IMPLICITTHIS in non-syntactic scope case
        MOZ_FALLTHROUGH;
      case JSOP_IMPLICITTHIS:
      {
        PropertyName* name = info().getAtom(pc)->asPropertyName();
        return jsop_implicitthis(name);
      }

      case JSOP_NEWTARGET:
        return jsop_newtarget();

      case JSOP_CHECKISOBJ:
        return jsop_checkisobj(GET_UINT8(pc));

      case JSOP_CHECKISCALLABLE:
        return jsop_checkiscallable(GET_UINT8(pc));

      case JSOP_CHECKOBJCOERCIBLE:
        return jsop_checkobjcoercible();

      case JSOP_DEBUGCHECKSELFHOSTED:
      {
#ifdef DEBUG
        MDebugCheckSelfHosted* check = MDebugCheckSelfHosted::New(alloc(), current->pop());
        current->add(check);
        current->push(check);
        MOZ_TRY(resumeAfter(check));
#endif
        return Ok();
      }

      case JSOP_IS_CONSTRUCTING:
        pushConstant(MagicValue(JS_IS_CONSTRUCTING));
        return Ok();

      case JSOP_OPTIMIZE_SPREADCALL:
      {
        // Assuming optimization isn't available doesn't affect correctness.
        // TODO: Investigate dynamic checks.
        MDefinition* arr = current->peek(-1);
        arr->setImplicitlyUsedUnchecked();
        pushConstant(BooleanValue(false));
        return Ok();
      }

      // ===== NOT Yet Implemented =====
      // Read below!

      // With
      case JSOP_ENTERWITH:
      case JSOP_LEAVEWITH:

      // Spread
      case JSOP_SPREADNEW:
      case JSOP_SPREADEVAL:
      case JSOP_STRICTSPREADEVAL:

      // Classes
      case JSOP_CHECKCLASSHERITAGE:
      case JSOP_FUNWITHPROTO:
      case JSOP_OBJWITHPROTO:
      case JSOP_BUILTINPROTO:
      case JSOP_INITHOMEOBJECT:
      case JSOP_DERIVEDCONSTRUCTOR:
      case JSOP_CHECKTHIS:
      case JSOP_CHECKRETURN:
      case JSOP_CHECKTHISREINIT:

      // Super
      case JSOP_SETPROP_SUPER:
      case JSOP_SETELEM_SUPER:
      case JSOP_STRICTSETPROP_SUPER:
      case JSOP_STRICTSETELEM_SUPER:
      case JSOP_SUPERFUN:
      // Most of the infrastructure for these exists in Ion, but needs review
      // and testing before these are enabled. Once other opcodes that are used
      // in derived classes are supported in Ion, this can be better validated
      // with testcases. Pay special attention to bailout and other areas where
      // JSOP_NEW has special handling.
      case JSOP_SPREADSUPERCALL:
      case JSOP_SUPERCALL:

      // Environments (bug 1366470)
      case JSOP_PUSHVARENV:
      case JSOP_POPVARENV:

      // Compound assignment
      case JSOP_GETBOUNDNAME:

      // Generators / Async (bug 1317690)
      case JSOP_EXCEPTION:
      case JSOP_THROWING:
      case JSOP_ISGENCLOSING:
      case JSOP_INITIALYIELD:
      case JSOP_YIELD:
      case JSOP_FINALYIELDRVAL:
      case JSOP_RESUME:
      case JSOP_DEBUGAFTERYIELD:
      case JSOP_AWAIT:
      case JSOP_GENERATOR:

      // Misc
      case JSOP_DELNAME:
      case JSOP_FINALLY:
      case JSOP_GETRVAL:
      case JSOP_GOSUB:
      case JSOP_RETSUB:
      case JSOP_SETINTRINSIC:
      case JSOP_THROWMSG:
        // === !! WARNING WARNING WARNING !! ===
        // Do you really want to sacrifice performance by not implementing this
        // operation in the optimizing compiler?
        break;

      case JSOP_FORCEINTERPRETER:
        // Intentionally not implemented.
        break;

      case JSOP_UNUSED126:
      case JSOP_UNUSED206:
      case JSOP_UNUSED223:
      case JSOP_LIMIT:
        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(AbortReason::Disable, "Unsupported opcode: %s", CodeName[op]);
#else
    return abort(AbortReason::Disable, "Unsupported opcode: %d", op);
#endif
}

AbortReasonOr<Ok>
IonBuilder::restartLoop(const CFGBlock* cfgHeader)
{
    AutoTraceLog logCompile(traceLogger(), TraceLogger_IonBuilderRestartLoop);

    spew("New types at loop header, restarting loop body");

    if (JitOptions.limitScriptSize) {
        if (++numLoopRestarts_ >= MAX_LOOP_RESTARTS)
            return abort(AbortReason::Disable, "Aborted while processing control flow");
    }

    MBasicBlock* header = blockWorklist[cfgHeader->id()];

    // 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.
    if (!graph().removeSuccessorBlocks(header))
        return abort(AbortReason::Alloc);
    graph().removeBlockFromList(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());

    loopDepth_ = header->loopDepth();

    // Don't specializePhis(), as the header has been visited before and the
    // phis have already had their type set.
    setCurrent(header);
    pc = header->pc();

    initLoopEntry();
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::replaceTypeSet(MDefinition* subject, TemporaryTypeSet* type, MTest* test)
{
    if (type->unknown())
        return Ok();

    // Don't emit MFilterTypeSet if it doesn't improve the typeset.
    if (subject->resultTypeSet()) {
        if (subject->resultTypeSet()->equals(type))
            return Ok();
    } else {
        TemporaryTypeSet oldTypes(alloc_->lifoAlloc(), subject->type());
        if (oldTypes.equals(type))
            return Ok();
    }

    MInstruction* replace = nullptr;
    MDefinition* ins;

    for (uint32_t i = 0; i < current->stackDepth(); i++) {
        ins = current->getSlot(i);

        // 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 abort(AbortReason::Alloc);

            ins->toFilterTypeSet()->setResultType(intersect->getKnownMIRType());
            ins->toFilterTypeSet()->setResultTypeSet(intersect);

            if (ins->type() == MIRType::Undefined)
                current->setSlot(i, constant(UndefinedValue()));
            else if (ins->type() == MIRType::Null)
                current->setSlot(i, constant(NullValue()));
            else if (ins->type() == MIRType::MagicOptimizedArguments)
                current->setSlot(i, constant(MagicValue(JS_OPTIMIZED_ARGUMENTS)));
            else
                MOZ_ASSERT(!IsMagicType(ins->type()));
            continue;
        }

        if (ins == subject) {
            if (!replace) {
                replace = MFilterTypeSet::New(alloc(), subject, type);
                current->add(replace);

                // 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 Ok();
}

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;
}

AbortReasonOr<Ok>
IonBuilder::improveTypesAtCompare(MCompare* ins, bool trueBranch, MTest* test)
{
    if (ins->compareType() == MCompare::Compare_Undefined ||
        ins->compareType() == MCompare::Compare_Null)
    {
        return improveTypesAtNullOrUndefinedCompare(ins, trueBranch, test);
    }

    if ((ins->lhs()->isTypeOf() || ins->rhs()->isTypeOf()) &&
        (ins->lhs()->isConstant() || ins->rhs()->isConstant()))
    {
        return improveTypesAtTypeOfCompare(ins, trueBranch, test);
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::improveTypesAtTypeOfCompare(MCompare* ins, bool trueBranch, MTest* test)
{
    MTypeOf* typeOf = ins->lhs()->isTypeOf() ? ins->lhs()->toTypeOf() : ins->rhs()->toTypeOf();
    MConstant* constant = ins->lhs()->isConstant() ? ins->lhs()->toConstant() : ins->rhs()->toConstant();

    if (constant->type() != MIRType::String)
        return Ok();

    bool equal = ins->jsop() == JSOP_EQ || ins->jsop() == JSOP_STRICTEQ;
    bool notEqual = ins->jsop() == JSOP_NE || ins->jsop() == JSOP_STRICTNE;

    if (notEqual)
        trueBranch = !trueBranch;

    // Relational compares not supported.
    if (!equal && !notEqual)
        return Ok();

    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 Ok();
        inputTypes = &tmp;
        tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc());
    }

    if (inputTypes->unknown())
        return Ok();

    // 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 = runtime->names();
    if (constant->toString() == TypeName(JSTYPE_UNDEFINED, names)) {
        filter.addType(TypeSet::UndefinedType(), alloc_->lifoAlloc());
        if (typeOf->inputMaybeCallableOrEmulatesUndefined() && trueBranch)
            filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_BOOLEAN, names)) {
        filter.addType(TypeSet::BooleanType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_NUMBER, names)) {
        filter.addType(TypeSet::Int32Type(), alloc_->lifoAlloc());
        filter.addType(TypeSet::DoubleType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_STRING, names)) {
        filter.addType(TypeSet::StringType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_SYMBOL, names)) {
        filter.addType(TypeSet::SymbolType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_OBJECT, names)) {
        filter.addType(TypeSet::NullType(), alloc_->lifoAlloc());
        if (trueBranch)
            filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
    } else if (constant->toString() == TypeName(JSTYPE_FUNCTION, names)) {
        if (typeOf->inputMaybeCallableOrEmulatesUndefined() && trueBranch)
            filter.addType(TypeSet::AnyObjectType(), alloc_->lifoAlloc());
    } else {
        return Ok();
    }

    TemporaryTypeSet* type;
    if (trueBranch)
        type = TypeSet::intersectSets(&filter, inputTypes, alloc_->lifoAlloc());
    else
        type = TypeSet::removeSet(inputTypes, &filter, alloc_->lifoAlloc());

    if (!type)
        return abort(AbortReason::Alloc);

    return replaceTypeSet(subject, type, test);
}

AbortReasonOr<Ok>
IonBuilder::improveTypesAtNullOrUndefinedCompare(MCompare* ins, bool trueBranch, MTest* test)
{
    MOZ_ASSERT(ins->compareType() == MCompare::Compare_Undefined ||
               ins->compareType() == MCompare::Compare_Null);

    // 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 Ok();
        inputTypes = &tmp;
        tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc());
    }

    if (inputTypes->unknown())
        return Ok();

    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 abort(AbortReason::Alloc);

    return replaceTypeSet(subject, type, test);
}

AbortReasonOr<Ok>
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::Opcode::Not:
        return improveTypesAtTest(ins->toNot()->getOperand(0), !trueBranch, test);
      case MDefinition::Opcode::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 Ok();
            oldType = &tmp;
            tmp.addType(TypeSet::PrimitiveType(ValueTypeFromMIRType(subject->type())), alloc_->lifoAlloc());
        }

        if (oldType->unknown())
            return Ok();

        TemporaryTypeSet* type = nullptr;
        if (trueBranch)
            type = oldType->cloneObjectsOnly(alloc_->lifoAlloc());
        else
            type = oldType->cloneWithoutObjects(alloc_->lifoAlloc());

        if (!type)
            return abort(AbortReason::Alloc);

        return replaceTypeSet(subject, type, test);
      }
      case MDefinition::Opcode::Phi: {
        bool branchIsAnd = true;
        if (!detectAndOrStructure(ins->toPhi(), &branchIsAnd)) {
            // 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) {
                MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(0), true, test));
                MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(1), true, test));
            }
        } 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) {
                MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(0), false, test));
                MOZ_TRY(improveTypesAtTest(ins->toPhi()->getOperand(1), false, test));
            }
        }
        return Ok();
      }

      case MDefinition::Opcode::Compare:
        return improveTypesAtCompare(ins->toCompare(), trueBranch, test);

      default:
        break;
    }

    // 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 Ok();
        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 Ok();

    // 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());
    }

    if (!type)
        return abort(AbortReason::Alloc);

    return replaceTypeSet(ins, type, test);
}

AbortReasonOr<Ok>
IonBuilder::jsop_dup2()
{
    uint32_t lhsSlot = current->stackDepth() - 2;
    uint32_t rhsSlot = current->stackDepth() - 1;
    current->pushSlot(lhsSlot);
    current->pushSlot(rhsSlot);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitTest(CFGTest* test)
{
    MDefinition* ins = test->mustKeepCondition() ? current->peek(-1) : current->pop();

    // Create true and false branches.
    MBasicBlock* ifTrue;
    MOZ_TRY_VAR(ifTrue, newBlock(current, test->trueBranch()->startPc()));
    MBasicBlock* ifFalse;
    MOZ_TRY_VAR(ifFalse, newBlock(current, test->falseBranch()->startPc()));

    MTest* mir = newTest(ins, ifTrue, ifFalse);
    current->end(mir);

    // Filter the types in the true branch.
    MOZ_TRY(setCurrentAndSpecializePhis(ifTrue));
    MOZ_TRY(improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ true, mir));

    blockWorklist[test->trueBranch()->id()] = ifTrue;

    // Filter the types in the false branch.
    // Note: sometimes the false branch is used as merge point. As a result
    // reuse the ifFalse block as a type improvement block and create a new
    // ifFalse which we can use for the merge.
    MBasicBlock* filterBlock = ifFalse;
    ifFalse = nullptr;
    graph().addBlock(filterBlock);

    MOZ_TRY(setCurrentAndSpecializePhis(filterBlock));
    MOZ_TRY(improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ false, mir));

    MOZ_TRY_VAR(ifFalse, newBlock(filterBlock, test->falseBranch()->startPc()));
    filterBlock->end(MGoto::New(alloc(), ifFalse));

    if (filterBlock->pc() && script()->hasScriptCounts())
        filterBlock->setHitCount(script()->getHitCount(filterBlock->pc()));

    blockWorklist[test->falseBranch()->id()] = ifFalse;

    current = nullptr;

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitCompare(CFGCompare* compare)
{
    MDefinition* rhs = current->peek(-1);
    MDefinition* lhs = current->peek(-2);

    // Execute the compare operation.
    MOZ_TRY(jsop_compare(JSOP_STRICTEQ));
    MInstruction* cmpResult = current->pop()->toInstruction();
    MOZ_ASSERT(!cmpResult->isEffectful());

    // Put the rhs/lhs again on the stack.
    current->push(lhs);
    current->push(rhs);

    // Create true and false branches.
    MBasicBlock* ifTrue;
    MOZ_TRY_VAR(ifTrue, newBlockPopN(current, compare->trueBranch()->startPc(),
                                     compare->truePopAmount()));
    MBasicBlock* ifFalse;
    MOZ_TRY_VAR(ifFalse, newBlockPopN(current, compare->falseBranch()->startPc(),
                                      compare->falsePopAmount()));

    blockWorklist[compare->trueBranch()->id()] = ifTrue;
    blockWorklist[compare->falseBranch()->id()] = ifFalse;

    MTest* mir = newTest(cmpResult, ifTrue, ifFalse);
    current->end(mir);

    // Filter the types in the true branch.
    MOZ_TRY(setCurrentAndSpecializePhis(ifTrue));
    MOZ_TRY(improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ true, mir));

    // Filter the types in the false branch.
    MOZ_TRY(setCurrentAndSpecializePhis(ifFalse));
    MOZ_TRY(improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ false, mir));

    current = nullptr;

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitTry(CFGTry* try_)
{
    // We don't support try-finally. The ControlFlowGenerator should have
    // aborted compilation in this case.

    // 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(AbortReason::Disable, "Try-catch during arguments usage analysis");

    graph().setHasTryBlock();

    MBasicBlock* tryBlock;
    MOZ_TRY_VAR(tryBlock, newBlock(current, try_->tryBlock()->startPc()));

    blockWorklist[try_->tryBlock()->id()] = tryBlock;

    // Connect the code after the try-catch to the graph with an MGotoWithFake
    // instruction that always jumps to the try block. This ensures the
    // successor block always has a predecessor.
    MBasicBlock* successor;
    MOZ_TRY_VAR(successor, newBlock(current, try_->getSuccessor(1)->startPc()));

    blockWorklist[try_->afterTryCatchBlock()->id()] = successor;

    current->end(MGotoWithFake::New(alloc(), tryBlock, successor));

    // The baseline compiler should not attempt to enter the catch block
    // via OSR.
    MOZ_ASSERT(info().osrPc() < try_->catchStartPc() ||
               info().osrPc() >= try_->afterTryCatchBlock()->startPc());

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitReturn(CFGControlInstruction* control)
{
    MDefinition* def;
    switch (control->type()) {
      case CFGControlInstruction::Type_Return:
        // Return the last instruction.
        def = current->pop();
        break;

      case CFGControlInstruction::Type_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 abort(AbortReason::Alloc);

    // Make sure no one tries to use this block now.
    setCurrent(nullptr);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitThrow(CFGThrow *cfgIns)
{
    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);

    MOZ_TRY(resumeAfter(nop));

    MThrow* ins = MThrow::New(alloc(), def);
    current->end(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::visitTableSwitch(CFGTableSwitch* cfgIns)
{
    // Pop input.
    MDefinition* ins = current->pop();

    // Create MIR instruction
    MTableSwitch* tableswitch = MTableSwitch::New(alloc(), ins, cfgIns->low(), cfgIns->high());

#ifdef DEBUG
    MOZ_ASSERT(cfgIns->defaultCase() == cfgIns->getSuccessor(0));
    for (size_t i = 1; i < cfgIns->numSuccessors(); i++) {
        MOZ_ASSERT(cfgIns->getCase(i-1) == cfgIns->getSuccessor(i));
    }
#endif

    // Create the cases
    for (size_t i = 0; i < cfgIns->numSuccessors(); i++) {
        const CFGBlock* cfgblock = cfgIns->getSuccessor(i);

        MBasicBlock* caseBlock;
        MOZ_TRY_VAR(caseBlock, newBlock(current, cfgblock->startPc()));

        blockWorklist[cfgblock->id()] = caseBlock;

        size_t index;
        if (i == 0) {
            if (!tableswitch->addDefault(caseBlock, &index))
                return abort(AbortReason::Alloc);

        } else {
            if (!tableswitch->addSuccessor(caseBlock, &index))
                return abort(AbortReason::Alloc);

            if (!tableswitch->addCase(index))
                return abort(AbortReason::Alloc);

            // If this is an actual case statement, optimize by replacing the
            // input to the switch case with the actual number of the case.
            MConstant* constant = MConstant::New(alloc(), Int32Value(i - 1 + tableswitch->low()));
            caseBlock->add(constant);
            for (uint32_t j = 0; j < caseBlock->stackDepth(); j++) {
                if (ins != caseBlock->getSlot(j))
                    continue;

                constant->setDependency(ins);
                caseBlock->setSlot(j, constant);
            }
            graph().addBlock(caseBlock);

            if (caseBlock->pc() && script()->hasScriptCounts())
                caseBlock->setHitCount(script()->getHitCount(caseBlock->pc()));

            MBasicBlock* merge;
            MOZ_TRY_VAR(merge, newBlock(caseBlock, cfgblock->startPc()));
            if (!merge)
                return abort(AbortReason::Alloc);

            caseBlock->end(MGoto::New(alloc(), merge));
            blockWorklist[cfgblock->id()] = merge;
        }

        MOZ_ASSERT(index == i);
    }

    // Save the MIR instruction as last instruction of this block.
    current->end(tableswitch);
    return Ok();
}

void
IonBuilder::pushConstant(const Value& v)
{
    current->push(constant(v));
}

AbortReasonOr<Ok>
IonBuilder::bitnotTrySpecialized(bool* emitted, MDefinition* input)
{
    MOZ_ASSERT(*emitted == false);

    // 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 Ok();

    MBitNot* ins = MBitNot::New(alloc(), input);
    ins->setSpecialization(MIRType::Int32);

    current->add(ins);
    current->push(ins);

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_bitnot()
{
    bool emitted = false;

    MDefinition* input = current->pop();

    if (!forceInlineCaches()) {
        MOZ_TRY(bitnotTrySpecialized(&emitted, input));
        if(emitted)
            return Ok();
    }

    MOZ_TRY(arithTrySharedStub(&emitted, JSOP_BITNOT, nullptr, input));
    if (emitted)
        return Ok();

    // 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);
}

AbortReasonOr<Ok>
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())
        MOZ_TRY(resumeAfter(ins));

    return Ok();
}

MDefinition::Opcode
JSOpToMDefinition(JSOp op)
{
    switch (op) {
      case JSOP_ADD:
        return MDefinition::Opcode::Add;
      case JSOP_SUB:
        return MDefinition::Opcode::Sub;
      case JSOP_MUL:
        return MDefinition::Opcode::Mul;
      case JSOP_DIV:
        return MDefinition::Opcode::Div;
      case JSOP_MOD:
        return MDefinition::Opcode::Mod;
      default:
        MOZ_CRASH("unexpected binary opcode");
    }
}

AbortReasonOr<Ok>
IonBuilder::binaryArithTryConcat(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
{
    MOZ_ASSERT(*emitted == false);

    // 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 Ok();

    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 Ok();
    }

    // The non-string input (if present) should be atleast easily coercible to string.
    if (right->type() != MIRType::String &&
        (right->mightBeType(MIRType::Symbol) || right->mightBeType(MIRType::Object) ||
         right->mightBeMagicType()))
    {
        trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
        return Ok();
    }
    if (left->type() != MIRType::String &&
        (left->mightBeType(MIRType::Symbol) || left->mightBeType(MIRType::Object) ||
         left->mightBeMagicType()))
    {
        trackOptimizationOutcome(TrackedOutcome::OperandNotEasilyCoercibleToString);
        return Ok();
    }

    MConcat* ins = MConcat::New(alloc(), left, right);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(maybeInsertResume());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::powTrySpecialized(bool* emitted, MDefinition* base, MDefinition* power,
                              MIRType outputType)
{
    // Typechecking.
    MDefinition* output = nullptr;
    MIRType baseType = base->type();
    MIRType powerType = power->type();

    if (outputType != MIRType::Int32 && outputType != MIRType::Double)
        return Ok();
    if (!IsNumberType(baseType))
        return Ok();
    if (!IsNumberType(powerType))
        return Ok();

    if (powerType == MIRType::Float32)
        powerType = MIRType::Double;

    MPow* pow = MPow::New(alloc(), base, power, powerType);
    current->add(pow);
    output = pow;

    // 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 Ok();
}

static inline bool
SimpleArithOperand(MDefinition* op)
{
    return !op->emptyResultTypeSet()
        && !op->mightBeType(MIRType::Object)
        && !op->mightBeType(MIRType::String)
        && !op->mightBeType(MIRType::Symbol)
        && !op->mightBeType(MIRType::MagicOptimizedArguments)
        && !op->mightBeType(MIRType::MagicHole)
        && !op->mightBeType(MIRType::MagicIsConstructing);
}

AbortReasonOr<Ok>
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 Ok();
    }

    // One of the inputs need to be a number.
    if (!IsNumberType(left->type()) && !IsNumberType(right->type())) {
        trackOptimizationOutcome(TrackedOutcome::OperandNotNumber);
        return Ok();
    }

    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());
    MOZ_TRY(maybeInsertResume());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
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::SpeculationOnInputTypesFailed);
        return Ok();
    }

    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());
    MOZ_TRY(maybeInsertResume());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
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 Ok();

    // The actual jsop 'jsop_pos' is not supported yet.
    if (actualOp == JSOP_POS)
        return Ok();

    // 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);

    MOZ_TRY(resumeAfter(stub));

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_binary_arith(JSOp op, MDefinition* left, MDefinition* right)
{
    bool emitted = false;

    startTrackingOptimizations();

    trackTypeInfo(TrackedTypeSite::Operand, left->type(), left->resultTypeSet());
    trackTypeInfo(TrackedTypeSite::Operand, right->type(), right->resultTypeSet());

    if (!forceInlineCaches()) {
        MOZ_TRY(binaryArithTryConcat(&emitted, op, left, right));
        if (emitted)
            return Ok();

        MOZ_TRY(binaryArithTrySpecialized(&emitted, op, left, right));
        if (emitted)
            return Ok();

        MOZ_TRY(binaryArithTrySpecializedOnBaselineInspector(&emitted, op, left, right));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(arithTrySharedStub(&emitted, op, left, right));
    if (emitted)
        return Ok();

    // 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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_binary_arith(JSOp op)
{
    MDefinition* right = current->pop();
    MDefinition* left = current->pop();

    return jsop_binary_arith(op, left, right);
}


AbortReasonOr<Ok>
IonBuilder::jsop_pow()
{
    MDefinition* exponent = current->pop();
    MDefinition* base = current->pop();

    bool emitted = false;

    if (!forceInlineCaches()) {
        MOZ_TRY(powTrySpecialized(&emitted, base, exponent, MIRType::Double));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(arithTrySharedStub(&emitted, JSOP_POW, base, exponent));
    if (emitted)
        return Ok();

    // For now, use MIRType::None as a safe cover-all. See bug 1188079.
    MPow* pow = MPow::New(alloc(), base, exponent, MIRType::None);
    current->add(pow);
    current->push(pow);
    MOZ_ASSERT(pow->isEffectful());
    return resumeAfter(pow);
}

AbortReasonOr<Ok>
IonBuilder::jsop_pos()
{
    if (IsNumberType(current->peek(-1)->type())) {
        // 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 Ok();
    }

    // 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);
}

AbortReasonOr<Ok>
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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_tostring()
{
    if (current->peek(-1)->type() == MIRType::String)
        return Ok();

    MDefinition* value = current->pop();
    MToString* ins = MToString::New(alloc(), value);
    current->add(ins);
    current->push(ins);
    MOZ_ASSERT(!ins->isEffectful());
    return Ok();
}

class AutoAccumulateReturns
{
    MIRGraph& graph_;
    MIRGraphReturns* prev_;

  public:
    AutoAccumulateReturns(MIRGraph& graph, MIRGraphReturns& returns)
      : graph_(graph)
    {
        prev_ = graph_.returnAccumulator();
        graph_.setReturnAccumulator(&returns);
    }
    ~AutoAccumulateReturns() {
        graph_.setReturnAccumulator(prev_);
    }
};

IonBuilder::InliningResult
IonBuilder::inlineScriptedCall(CallInfo& callInfo, JSFunction* target)
{
    MOZ_ASSERT(target->hasScript());
    MOZ_ASSERT(IsIonInlinablePC(pc));

    MBasicBlock::BackupPoint backup(current);
    if (!backup.init(alloc()))
        return abort(AbortReason::Alloc);

    callInfo.setImplicitlyUsedUnchecked();

    // Create new |this| on the caller-side for inlined constructors.
    if (callInfo.constructing()) {
        MDefinition* thisDefn = createThis(target, callInfo.fun(), callInfo.getNewTarget());
        if (!thisDefn)
            return abort(AbortReason::Alloc);
        callInfo.setThis(thisDefn);
    }

    // Capture formals in the outer resume point.
    MOZ_TRY(callInfo.pushCallStack(this, current));

    MResumePoint* outerResumePoint =
        MResumePoint::New(alloc(), current, pc, MResumePoint::Outer);
    if (!outerResumePoint)
        return abort(AbortReason::Alloc);
    current->setOuterResumePoint(outerResumePoint);

    // Pop formals again, except leave |fun| on stack for duration of call.
    callInfo.popCallStack(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 abort(AbortReason::Alloc);
            MTypeBarrier* barrier = MTypeBarrier::New(alloc(), callInfo.thisArg(), clonedTypes);
            current->add(barrier);
            if (barrier->type() == MIRType::Undefined)
                callInfo.setThis(constant(UndefinedValue()));
            else if (barrier->type() == MIRType::Null)
                callInfo.setThis(constant(NullValue()));
            else
                callInfo.setThis(barrier);
        }
    }

    // Start inlining.
    LifoAlloc* lifoAlloc = alloc_->lifoAlloc();
    InlineScriptTree* inlineScriptTree =
        info().inlineScriptTree()->addCallee(alloc_, pc, calleeScript);
    if (!inlineScriptTree)
        return abort(AbortReason::Alloc);
    CompileInfo* info = lifoAlloc->new_<CompileInfo>(runtime, calleeScript, target,
                                                     (jsbytecode*)nullptr,
                                                     this->info().analysisMode(),
                                                     /* needsArgsObj = */ false,
                                                     inlineScriptTree);
    if (!info)
        return abort(AbortReason::Alloc);

    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_);
    AbortReasonOr<Ok> result = inlineBuilder.buildInline(this, outerResumePoint, callInfo);
    if (result.isErr()) {
        if (analysisContext && analysisContext->isExceptionPending()) {
            JitSpew(JitSpew_IonAbort, "Inline builder raised exception.");
            MOZ_ASSERT(result.unwrapErr() == AbortReason::Error);
            return Err(result.unwrapErr());
        }

        // Inlining the callee failed. Mark the callee as uninlineable only if
        // the inlining was aborted for a non-exception reason.
        switch (result.unwrapErr()) {
          case AbortReason::Disable:
            calleeScript->setUninlineable();
            if (!JitOptions.disableInlineBacktracking) {
                current = backup.restore();
                if (!current)
                    return abort(AbortReason::Alloc);
                return InliningStatus_NotInlined;
            }
            return abort(AbortReason::Inlining);

          case AbortReason::PreliminaryObjects: {
            const ObjectGroupVector& groups = inlineBuilder.abortedPreliminaryGroups();
            MOZ_ASSERT(!groups.empty());
            for (size_t i = 0; i < groups.length(); i++)
                addAbortedPreliminaryGroup(groups[i]);
            return Err(result.unwrapErr());
          }

          case AbortReason::Alloc:
          case AbortReason::Inlining:
          case AbortReason::Error:
            return Err(result.unwrapErr());

          case AbortReason::NoAbort:
            MOZ_CRASH("Abort with AbortReason::NoAbort");
            return abort(AbortReason::Error);
        }
    }

    if (returns.empty()) {
        // Inlining of functions that have no exit is not supported.
        calleeScript->setUninlineable();
        if (!JitOptions.disableInlineBacktracking) {
            current = backup.restore();
            if (!current)
                return abort(AbortReason::Alloc);
            return InliningStatus_NotInlined;
        }
        return abort(AbortReason::Inlining);
    }

    // Create return block.
    jsbytecode* postCall = GetNextPc(pc);
    MBasicBlock* returnBlock;
    MOZ_TRY_VAR(returnBlock, newBlock(current->stackDepth(), postCall));
    graph().addBlock(returnBlock);
    returnBlock->setCallerResumePoint(callerResumePoint_);

    // Inherit the slots from current and pop |fun|.
    returnBlock->inheritSlots(current);
    returnBlock->pop();

    // Accumulate return values.
    MDefinition* retvalDefn = patchInlinedReturns(callInfo, returns, returnBlock);
    if (!retvalDefn)
        return abort(AbortReason::Alloc);
    returnBlock->push(retvalDefn);

    // Initialize entry slots now that the stack has been fixed up.
    if (!returnBlock->initEntrySlots(alloc()))
        return abort(AbortReason::Alloc);

    MOZ_TRY(setCurrentAndSpecializePhis(returnBlock));

    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:%zu: 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.

    outerBuilder->inlinedBytecodeLength_ += targetScript->length();

    return InliningDecision_Inline;
}

AbortReasonOr<Ok>
IonBuilder::selectInliningTargets(const InliningTargets& targets, CallInfo& callInfo,
                                  BoolVector& choiceSet, uint32_t* numInlineable)
{
    *numInlineable = 0;
    uint32_t totalSize = 0;

    // For each target, ask whether it may be inlined.
    if (!choiceSet.reserve(targets.length()))
        return abort(AbortReason::Alloc);

    // 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 Ok();

    for (size_t i = 0; i < targets.length(); i++) {
        JSObject* target = targets[i].target;

        trackOptimizationAttempt(TrackedStrategy::Call_Inline);
        trackTypeInfo(TrackedTypeSite::Call_Target, target);

        bool inlineable;
        InliningDecision decision = makeInliningDecision(target, callInfo);
        switch (decision) {
          case InliningDecision_Error:
            return abort(AbortReason::Error);
          case InliningDecision_DontInline:
          case InliningDecision_WarmUpCountTooLow:
            inlineable = false;
            break;
          case InliningDecision_Inline:
            inlineable = true;
            break;
          default:
            MOZ_CRASH("Unhandled InliningDecision value!");
        }

        if (target->is<JSFunction>()) {
            // 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].target->as<JSFunction>().isNative()) {
                trackTypeInfo(callInfo);
                break;
            }
        }
    }

    MOZ_ASSERT(choiceSet.length() == targets.length());
    return Ok();
}

static bool
CanInlineGetPropertyCache(MGetPropertyCache* cache, MDefinition* thisDef)
{
    if (cache->value()->type() != MIRType::Object)
        return false;

    if (cache->value() != thisDef)
        return false;

    InlinePropertyTable* table = cache->propTable();
    if (!table)
        return false;
    if (table->numEntries() == 0)
        return false;
    return true;
}

class WrapMGetPropertyCache
{
    MGetPropertyCache* cache_;

  private:
    void discardPriorResumePoint() {
        if (!cache_)
            return;

        InlinePropertyTable* propTable = cache_->propTable();
        if (!propTable)
            return;
        MResumePoint* rp = propTable->takePriorResumePoint();
        if (!rp)
            return;
        cache_->block()->discardPreAllocatedResumePoint(rp);
    }

  public:
    explicit WrapMGetPropertyCache(MGetPropertyCache* cache)
      : cache_(cache)
    { }

    ~WrapMGetPropertyCache() {
        discardPriorResumePoint();
    }

    MGetPropertyCache* get() {
        return cache_;
    }
    MGetPropertyCache* operator->() {
        return get();
    }

    // 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::InliningResult
IonBuilder::inlineSingleCall(CallInfo& callInfo, JSObject* targetArg)
{
    InliningStatus status;
    if (!targetArg->is<JSFunction>()) {
        MOZ_TRY_VAR(status, inlineNonFunctionCall(callInfo, targetArg));
        trackInlineSuccess(status);
        return status;
    }

    JSFunction* target = &targetArg->as<JSFunction>();
    if (target->isNative()) {
        MOZ_TRY_VAR(status, inlineNativeCall(callInfo, target));
        trackInlineSuccess(status);
        return status;
    }

    // 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::InliningResult
IonBuilder::inlineCallsite(const InliningTargets& 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].target;

        trackOptimizationAttempt(TrackedStrategy::Call_Inline);
        trackTypeInfo(TrackedTypeSite::Call_Target, target);

        InliningDecision decision = makeInliningDecision(target, callInfo);
        switch (decision) {
          case InliningDecision_Error:
            return abort(AbortReason::Error);
          case InliningDecision_DontInline:
            return InliningStatus_NotInlined;
          case InliningDecision_WarmUpCountTooLow:
            return InliningStatus_WarmUpCountTooLow;
          case InliningDecision_Inline:
            break;
        }

        // 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;
    MOZ_TRY(selectInliningTargets(targets, callInfo, choiceSet, &numInlined));
    if (numInlined == 0)
        return InliningStatus_NotInlined;

    // Perform a polymorphic dispatch.
    MOZ_TRY(inlineCalls(callInfo, targets, choiceSet, propCache.get()));

    return InliningStatus_Inlined;
}

AbortReasonOr<Ok>
IonBuilder::inlineGenericFallback(JSFunction* target, CallInfo& callInfo, MBasicBlock* dispatchBlock)
{
    // Generate a new block with all arguments on-stack.
    MBasicBlock* fallbackBlock;
    MOZ_TRY_VAR(fallbackBlock, newBlock(dispatchBlock, pc));
    graph().addBlock(fallbackBlock);

    // Create a new CallInfo to track modified state within this block.
    CallInfo fallbackInfo(alloc(), pc, callInfo.constructing(), callInfo.ignoresReturnValue());
    if (!fallbackInfo.init(callInfo))
        return abort(AbortReason::Alloc);
    fallbackInfo.popCallStack(fallbackBlock);

    // Generate an MCall, which uses stateful |current|.
    MOZ_TRY(setCurrentAndSpecializePhis(fallbackBlock));
    MOZ_TRY(makeCall(target, fallbackInfo));

    // Pass return block to caller as |current|.
    return Ok();
}

AbortReasonOr<Ok>
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(), pc, callInfo.constructing(), callInfo.ignoresReturnValue());
    if (!fallbackInfo.init(callInfo))
        return abort(AbortReason::Alloc);

    // Capture stack prior to the call operation. This captures the function.
    MResumePoint* preCallResumePoint =
        MResumePoint::New(alloc(), dispatchBlock, pc, MResumePoint::ResumeAt);
    if (!preCallResumePoint)
        return abort(AbortReason::Alloc);

    DebugOnly<size_t> preCallFuncIndex = preCallResumePoint->stackDepth() - callInfo.numFormals();
    MOZ_ASSERT(preCallResumePoint->getOperand(preCallFuncIndex) == fallbackInfo.fun());

    // 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;
    MOZ_TRY_VAR(prepBlock, newBlock(dispatchBlock, pc));
    graph().addBlock(prepBlock);
    fallbackInfo.popCallStack(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;
    MOZ_TRY_VAR(getPropBlock, newBlock(prepBlock, propTable->pc(), priorResumePoint));
    graph().addBlock(getPropBlock);

    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->value());

    // 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;
    MOZ_TRY_VAR(preCallBlock, newBlock(getPropBlock, pc, preCallResumePoint));
    graph().addBlock(preCallBlock);
    getPropBlock->end(MGoto::New(alloc(), preCallBlock));

    // Now inline the MCallGeneric, using preCallBlock as the dispatch point.
    MOZ_TRY(inlineGenericFallback(nullptr, fallbackInfo, preCallBlock));

    // inlineGenericFallback() set the return block as |current|.
    preCallBlock->end(MGoto::New(alloc(), current));
    *fallbackTarget = prepBlock;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::inlineCalls(CallInfo& callInfo, const InliningTargets& 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);
    MOZ_ASSERT_IF(maybeCache, maybeCache->value()->type() == MIRType::Object);

    MBasicBlock* dispatchBlock = current;
    callInfo.setImplicitlyUsedUnchecked();
    MOZ_TRY(callInfo.pushCallStack(this, 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->value(), maybeCache->propTable());
        callInfo.fun()->setImplicitlyUsedUnchecked();
    } else {
        dispatch = MFunctionDispatch::New(alloc(), callInfo.fun());
    }

    MOZ_ASSERT(dispatchBlock->stackDepth() >= callInfo.numFormals());
    uint32_t stackDepth = dispatchBlock->stackDepth() - callInfo.numFormals() + 1;

    // Generate a return block to host the rval-collecting MPhi.
    jsbytecode* postCall = GetNextPc(pc);
    MBasicBlock* returnBlock;
    MOZ_TRY_VAR(returnBlock, newBlock(stackDepth, postCall));
    graph().addBlock(returnBlock);
    returnBlock->setCallerResumePoint(callerResumePoint_);

    // Set up stack, used to manually create a post-call resume point.
    returnBlock->inheritSlots(dispatchBlock);
    callInfo.popCallStack(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 abort(AbortReason::Alloc);

    // 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 abort(AbortReason::Alloc);

    // 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].target->as<JSFunction>();
        if (maybeCache && !maybeCache->propTable()->hasFunction(target)) {
            choiceSet[i] = false;
            trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch);
            continue;
        }

        MBasicBlock* inlineBlock;
        MOZ_TRY_VAR(inlineBlock, newBlock(dispatchBlock, pc));
        graph().addBlock(inlineBlock);

        // 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(), pc, callInfo.constructing(), callInfo.ignoresReturnValue());
        if (!inlineInfo.init(callInfo))
            return abort(AbortReason::Alloc);
        inlineInfo.popCallStack(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->value());
            TemporaryTypeSet* thisTypes =
                maybeCache->propTable()->buildTypeSetForFunction(alloc(), target);
            if (!thisTypes)
                return abort(AbortReason::Alloc);

            MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), inlineInfo.thisArg(), thisTypes);
            inlineBlock->add(filter);
            inlineInfo.setThis(filter);
        }

        // Inline the call into the inlineBlock.
        MOZ_TRY(setCurrentAndSpecializePhis(inlineBlock));
        InliningStatus status;
        MOZ_TRY_VAR(status, inlineSingleCall(inlineInfo, target));

        // 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.
        if (!dispatch->addCase(target, targets[i].group, inlineBlock))
            return abort(AbortReason::Alloc);

        MDefinition* retVal = inlineReturnBlock->peek(-1);
        retPhi->addInput(retVal);
        inlineReturnBlock->end(MGoto::New(alloc(), returnBlock));
        if (!returnBlock->addPredecessorWithoutPhis(inlineReturnBlock))
            return abort(AbortReason::Alloc);
    }

    // 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->value()->resultTypeSet();
            for (uint32_t i = 0; i < objectTypes->getObjectCount(); i++) {
                TypeSet::ObjectKey* obj = objectTypes->getObject(i);
                if (!obj)
                    continue;

                if (!obj->isGroup()) {
                    useFallback = true;
                    break;
                }

                if (!propTable->hasObjectGroup(obj->group())) {
                    useFallback = true;
                    break;
                }
            }

            if (!useFallback) {
                // 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;
            MOZ_TRY(inlineObjectGroupFallback(callInfo, dispatchBlock,
                                              dispatch->toObjectGroupDispatch(),
                                              maybeCache, &fallbackTarget));
            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);
                    JSObject* target = targets[i].target;
                    if (target->is<JSFunction>() && target->isSingleton())
                        remaining = &target->as<JSFunction>();
                    break;
                }
            }

            MOZ_TRY(inlineGenericFallback(remaining, callInfo, dispatchBlock));
            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 abort(AbortReason::Alloc);
    }

    // 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;
}

AbortReasonOr<MInstruction*>
IonBuilder::createCallObject(MDefinition* callee, MDefinition* env)
{
    // Get a template CallObject that we'll use to generate inline object
    // creation.
    CallObject* templateObj = inspector->templateCallObject();
    MConstant* templateCst = MConstant::NewConstraintlessObject(alloc(), templateObj);
    current->add(templateCst);

    // 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(), templateCst);
    else
        callObj = MNewCallObject::New(alloc(), templateCst);
    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 abort(AbortReason::Alloc);

        unsigned slot = fi.location().slot();
        unsigned formal = fi.argumentSlot();
        unsigned numFixedSlots = templateObj->numFixedSlots();
        MDefinition* param;
        if (script()->functionHasParameterExprs())
            param = constant(MagicValue(JS_UNINITIALIZED_LEXICAL));
        else
            param = current->getSlot(info().argSlotUnchecked(formal));
        if (slot >= numFixedSlots) {
            if (!slots) {
                slots = MSlots::New(alloc(), callObj);
                current->add(slots);
            }
            current->add(MStoreSlot::New(alloc(), slots, slot - numFixedSlots, param));
        } else {
            current->add(MStoreFixedSlot::New(alloc(), callObj, slot, param));
        }
    }

    return AbortReasonOr<MInstruction*>(callObj);
}

MDefinition*
IonBuilder::createThisScripted(MDefinition* callee, MDefinition* newTarget)
{
    // 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;

    if (target->isBoundFunction() || target->isDerivedClassConstructor())
        return nullptr;

    JSObject* templateObject = inspector->getTemplateObject(pc);
    if (!templateObject)
        return nullptr;
    if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
        return nullptr;

    Shape* shape = target->lookupPure(compartment->runtime()->names().prototype);
    if (!shape || !shape->isDataProperty())
        return nullptr;

    Value protov = target->getSlot(shape->slot());
    if (!protov.isObject())
        return nullptr;

    JSObject* proto = checkNurseryObject(&protov.toObject());
    if (proto != templateObject->staticPrototype())
        return nullptr;

    TypeSet::ObjectKey* templateObjectKey = TypeSet::ObjectKey::get(templateObject->group());
    if (templateObjectKey->hasFlags(constraints(), OBJECT_FLAG_NEW_SCRIPT_CLEARED))
        return nullptr;

    StackTypeSet* thisTypes = TypeScript::ThisTypes(target->nonLazyScript());
    if (!thisTypes || !thisTypes->hasType(TypeSet::ObjectType(templateObject)))
        return nullptr;

    // 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);
}

AbortReasonOr<Ok>
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(), pc, /* constructing = */ false,
                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
        if (!callInfo.init(current, argc))
            return abort(AbortReason::Alloc);
        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(), pc, /* constructing = */ false,
                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
    if (!callInfo.init(current, argc))
        return abort(AbortReason::Alloc);

    // Try to inline the call.
    if (!zeroArguments) {
        InliningDecision decision = makeInliningDecision(target, callInfo);
        switch (decision) {
          case InliningDecision_Error:
            return abort(AbortReason::Error);
          case InliningDecision_DontInline:
          case InliningDecision_WarmUpCountTooLow:
            break;
          case InliningDecision_Inline: {
            InliningStatus status;
            MOZ_TRY_VAR(status, inlineSingleCall(callInfo, target));
            if (status == InliningStatus_Inlined)
                return Ok();
            break;
          }
        }
    }

    // Call without inlining.
    return makeCall(target, callInfo);
}

AbortReasonOr<Ok>
IonBuilder::jsop_funapply(uint32_t argc)
{
    int calleeDepth = -((int)argc + 2);

    TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
    JSFunction* native = getSingleCallTarget(calleeTypes);
    if (argc != 2 || info().analysisMode() == Analysis_ArgumentsUsage) {
        CallInfo callInfo(alloc(), pc, /* constructing = */ false,
                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
        if (!callInfo.init(current, argc))
            return abort(AbortReason::Alloc);
        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(AbortReason::Disable, "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(), pc, /* constructing = */ false,
                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
        if (!callInfo.init(current, argc))
            return abort(AbortReason::Alloc);
        return makeCall(native, callInfo);
    }

    if ((!native || !native->isNative() ||
        native->native() != fun_apply) &&
        info().analysisMode() != Analysis_DefiniteProperties)
    {
        return abort(AbortReason::Disable, "fun.apply speculation failed");
    }

    // Use funapply that definitely uses |arguments|
    return jsop_funapplyarguments(argc);
}

AbortReasonOr<Ok>
IonBuilder::jsop_spreadcall()
{
    // The arguments array is constructed by a JSOP_NEWARRAY and not
    // leaked to user. The complications of spread call iterator behaviour are
    // handled when the user objects are expanded and copied into this hidden
    // array.

#ifdef DEBUG
    // If we know class, ensure it is what we expected
    MDefinition* argument = current->peek(-1);
    if (TemporaryTypeSet* objTypes = argument->resultTypeSet())
        if (const Class* clasp = objTypes->getKnownClass(constraints()))
            MOZ_ASSERT(clasp == &ArrayObject::class_);
#endif

    MDefinition* argArr = current->pop();
    MDefinition* argThis = current->pop();
    MDefinition* argFunc = current->pop();

    // Extract call target.
    TemporaryTypeSet* funTypes = argFunc->resultTypeSet();
    JSFunction* target = getSingleCallTarget(funTypes);
    WrappedFunction* wrappedTarget = target ? new(alloc()) WrappedFunction(target) : nullptr;

    // Dense elements of argument array
    MElements* elements = MElements::New(alloc(), argArr);
    current->add(elements);

    MApplyArray* apply = MApplyArray::New(alloc(), wrappedTarget, argFunc, elements, argThis);
    current->add(apply);
    current->push(apply);
    MOZ_TRY(resumeAfter(apply));

    // TypeBarrier the call result
    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
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);
    MOZ_TRY(resumeAfter(apply));

    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(apply, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
CallInfo::savePriorCallStack(MIRGenerator* mir, MBasicBlock* current, size_t peekDepth)
{
    MOZ_ASSERT(priorArgs_.empty());
    if (!priorArgs_.reserve(peekDepth))
        return mir->abort(AbortReason::Alloc);
    while (peekDepth) {
        priorArgs_.infallibleAppend(current->peek(0 - int32_t(peekDepth)));
        peekDepth--;
    }
    return Ok();
}


AbortReasonOr<Ok>
IonBuilder::jsop_funapplyarguments(uint32_t argc)
{
    // 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);
        MOZ_TRY(resumeAfter(apply));

        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(), pc, /* constructing = */ false,
                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
    MOZ_TRY(callInfo.savePriorCallStack(this, current, 4));

    // Vp
    MDefinition* vp = current->pop();
    vp->setImplicitlyUsedUnchecked();

    // Arguments
    if (inliningDepth_) {
        if (!callInfo.setArgs(inlineCallInfo_->argv()))
            return abort(AbortReason::Alloc);
    }

    // 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 abort(AbortReason::Error);
      case InliningDecision_DontInline:
      case InliningDecision_WarmUpCountTooLow:
        break;
      case InliningDecision_Inline: {
        InliningStatus status;
        MOZ_TRY_VAR(status, inlineSingleCall(callInfo, target));
        if (status == InliningStatus_Inlined)
            return Ok();
      }
    }

    return makeCall(target, callInfo);
}

AbortReasonOr<Ok>
IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue)
{
    startTrackingOptimizations();

    // 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.
    InliningTargets targets(alloc());
    TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
    if (calleeTypes)
        MOZ_TRY(getPolyCallTargets(calleeTypes, constructing, targets, 4));

    CallInfo callInfo(alloc(), pc, constructing, ignoresReturnValue);
    if (!callInfo.init(current, argc))
        return abort(AbortReason::Alloc);

    // Try inlining
    InliningStatus status;
    MOZ_TRY_VAR(status, inlineCallsite(targets, callInfo));
    if (status == InliningStatus_Inlined)
        return Ok();

    // Discard unreferenced & pre-allocated resume points.
    replaceMaybeFallbackFunctionGetter(nullptr);

    // No inline, just make the call.
    JSFunction* target = nullptr;
    if (targets.length() == 1 && targets[0].target->is<JSFunction>())
        target = &targets[0].target->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);
}

AbortReasonOr<bool>
IonBuilder::testShouldDOMCall(TypeSet* inTypes, JSFunction* func, JSJitInfo::OpType opType)
{
    if (!func->isNative() || !func->hasJitInfo())
        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 (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

        if (!key->hasStableClassAndProto(constraints()))
            return false;

        if (!instanceChecker(key->clasp(), jinfo->protoID, jinfo->depth))
            return false;
    }

    return true;
}

static bool
ArgumentTypesMatch(MDefinition* def, StackTypeSet* calleeTypes)
{
    if (!calleeTypes)
        return false;

    if (def->resultTypeSet()) {
        MOZ_ASSERT(def->type() == MIRType::Value || def->mightBeType(def->type()));
        return def->resultTypeSet()->isSubset(calleeTypes);
    }

    if (def->type() == MIRType::Value)
        return false;

    if (def->type() == MIRType::Object)
        return calleeTypes->unknownObject();

    return calleeTypes->mightBeMIRType(def->type());
}

bool
IonBuilder::testNeedsArgumentCheck(JSFunction* target, CallInfo& callInfo)
{
    // If 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;
}

AbortReasonOr<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;
    DOMObjectKind objKind = DOMObjectKind::Unknown;
    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(), &objKind))
        {
            MOZ_TRY_VAR(isDOMCall, testShouldDOMCall(thisTypes, target, JSJitInfo::Method));
        }
    }

    MCall* call = MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(),
                             callInfo.argc(), callInfo.constructing(),
                             callInfo.ignoresReturnValue(), isDOMCall, objKind);
    if (!call)
        return abort(AbortReason::Alloc);

    if (callInfo.constructing())
        call->addArg(targetArgs + 1, callInfo.getNewTarget());

    // 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 abort(AbortReason::Alloc);
        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)
            return abort(AbortReason::Disable, "Failure inlining constructor for call.");

        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();
}

AbortReasonOr<Ok>
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;
    MOZ_TRY_VAR(call, makeCallHelper(target, callInfo));

    current->push(call);
    if (call->isEffectful())
        MOZ_TRY(resumeAfter(call));

    TemporaryTypeSet* types = bytecodeTypes(pc);

    if (call->isCallDOMNative())
        return pushDOMTypeBarrier(call, types, call->getSingleTarget()->rawJSFunction());

    return pushTypeBarrier(call, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
IonBuilder::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, false);

    JSFunction* target = getSingleCallTarget(calleeTypes);
    if (!target)
        return abort(AbortReason::Disable, "No single callee for eval()");

    if (script()->global().valueIsEval(ObjectValue(*target))) {
        if (argc != 1)
            return abort(AbortReason::Disable, "Direct eval with more than one argument");

        if (!info().funMaybeLazy())
            return abort(AbortReason::Disable, "Direct eval in global code");

        if (info().funMaybeLazy()->isArrow())
            return abort(AbortReason::Disable, "Direct eval from arrow function");

        CallInfo callInfo(alloc(), pc, /* constructing = */ false,
                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
        if (!callInfo.init(current, argc))
            return abort(AbortReason::Alloc);
        callInfo.setImplicitlyUsedUnchecked();

        callInfo.fun()->setImplicitlyUsedUnchecked();

        MDefinition* envChain = current->environmentChain();
        MDefinition* string = callInfo.getArg(0);

        // 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);
        }

        MOZ_TRY(jsop_newtarget());
        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(), pc, /* constructing = */ false,
                                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
                if (!evalCallInfo.init(current, /* argc = */ 0))
                    return abort(AbortReason::Alloc);

                return makeCall(nullptr, evalCallInfo);
            }
        }

        MInstruction* ins = MCallDirectEval::New(alloc(), envChain, string,
                                                 newTargetValue, pc);
        current->add(ins);
        current->push(ins);

        TemporaryTypeSet* types = bytecodeTypes(pc);
        MOZ_TRY(resumeAfter(ins));
        return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
    }

    return jsop_call(argc, /* constructing = */ false, false);
}

AbortReasonOr<Ok>
IonBuilder::jsop_compare(JSOp op)
{
    MDefinition* right = current->pop();
    MDefinition* left = current->pop();

    return jsop_compare(op, left, right);
}

AbortReasonOr<Ok>
IonBuilder::jsop_compare(JSOp op, MDefinition* left, MDefinition* right)
{
    bool emitted = false;
    startTrackingOptimizations();

    if (!forceInlineCaches()) {
        MOZ_TRY(compareTrySpecialized(&emitted, op, left, right));
        if (emitted)
            return Ok();
        MOZ_TRY(compareTryBitwise(&emitted, op, left, right));
        if (emitted)
            return Ok();
        MOZ_TRY(compareTrySpecializedOnBaselineInspector(&emitted, op, left, right));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(compareTrySharedStub(&emitted, op, left, right));
    if (emitted)
        return Ok();

    trackOptimizationAttempt(TrackedStrategy::Compare_Call);

    // 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())
        MOZ_TRY(resumeAfter(ins));

    trackOptimizationSuccess();
    return Ok();
}

static bool
ObjectOrSimplePrimitive(MDefinition* op)
{
    // Return true if op is either undefined/null/boolean/int32/symbol or an object.
    return !op->mightBeType(MIRType::String)
        && !op->mightBeType(MIRType::Double)
        && !op->mightBeType(MIRType::Float32)
        && !op->mightBeType(MIRType::MagicOptimizedArguments)
        && !op->mightBeType(MIRType::MagicHole)
        && !op->mightBeType(MIRType::MagicIsConstructing);
}

AbortReasonOr<Ok>
IonBuilder::compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
{
    MOZ_ASSERT(*emitted == false);
    trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedTypes);

    // Try to emit an compare based on the input types.

    MCompare::CompareType type = MCompare::determineCompareType(op, left, right);
    if (type == MCompare::Compare_Unknown) {
        trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
        return Ok();
    }

    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());
    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::compareTryBitwise(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
{
    MOZ_ASSERT(*emitted == false);
    trackOptimizationAttempt(TrackedStrategy::Compare_Bitwise);

    // Try to emit a bitwise compare. Check if a bitwise compare equals the wanted
    // result for all observed operand types.

    // Only allow loose and strict equality.
    if (op != JSOP_EQ && op != JSOP_NE && op != JSOP_STRICTEQ && op != JSOP_STRICTNE) {
        trackOptimizationOutcome(TrackedOutcome::RelationalCompare);
        return Ok();
    }

    // Only primitive (not double/string) or objects are supported.
    // I.e. Undefined/Null/Boolean/Int32/Symbol and Object
    if (!ObjectOrSimplePrimitive(left) || !ObjectOrSimplePrimitive(right)) {
        trackOptimizationOutcome(TrackedOutcome::OperandTypeNotBitwiseComparable);
        return Ok();
    }

    // Objects that emulate undefined are not supported.
    if (left->maybeEmulatesUndefined(constraints()) ||
        right->maybeEmulatesUndefined(constraints()))
    {
        trackOptimizationOutcome(TrackedOutcome::OperandMaybeEmulatesUndefined);
        return Ok();
    }

    // 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)))
        {
            trackOptimizationOutcome(TrackedOutcome::LoosyUndefinedNullCompare);
            return Ok();
        }

        // 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)))
        {
            trackOptimizationOutcome(TrackedOutcome::LoosyInt32BooleanCompare);
            return Ok();
        }

        // For loosy comparison of an object with a Boolean/Number/String/Symbol
        // the valueOf the object is taken. Therefore not supported.
        bool simpleLHS = left->mightBeType(MIRType::Boolean) ||
                         left->mightBeType(MIRType::Int32) ||
                         left->mightBeType(MIRType::Symbol);
        bool simpleRHS = right->mightBeType(MIRType::Boolean) ||
                         right->mightBeType(MIRType::Int32) ||
                         right->mightBeType(MIRType::Symbol);
        if ((left->mightBeType(MIRType::Object) && simpleRHS) ||
            (right->mightBeType(MIRType::Object) && simpleLHS))
        {
            trackOptimizationOutcome(TrackedOutcome::CallsValueOf);
            return Ok();
        }
    }

    MCompare* ins = MCompare::New(alloc(), left, right, op);
    ins->setCompareType(MCompare::Compare_Bitwise);
    ins->cacheOperandMightEmulateUndefined(constraints());

    current->add(ins);
    current->push(ins);

    MOZ_ASSERT(!ins->isEffectful());
    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, MDefinition* left,
                                                     MDefinition* right)
{
    MOZ_ASSERT(*emitted == false);
    trackOptimizationAttempt(TrackedStrategy::Compare_SpecializedOnBaselineTypes);

    // 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) {
        trackOptimizationOutcome(TrackedOutcome::StrictCompare);
        return Ok();
    }

    MCompare::CompareType type = inspector->expectedCompareType(pc);
    if (type == MCompare::Compare_Unknown) {
        trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
        return Ok();
    }

    MCompare* ins = MCompare::New(alloc(), left, right, op);
    ins->setCompareType(type);
    ins->cacheOperandMightEmulateUndefined(constraints());

    current->add(ins);
    current->push(ins);

    MOZ_ASSERT(!ins->isEffectful());
    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::compareTrySharedStub(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
{
    MOZ_ASSERT(*emitted == false);

    // Try to emit a shared stub cache.

    if (JitOptions.disableSharedStubs)
        return Ok();

    if (JSOp(*pc) == JSOP_CASE)
        return Ok();

    trackOptimizationAttempt(TrackedStrategy::Compare_SharedCache);

    MBinarySharedStub* stub = MBinarySharedStub::New(alloc(), left, right);
    current->add(stub);
    current->push(stub);
    MOZ_TRY(resumeAfter(stub));

    MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Boolean, MUnbox::Infallible);
    current->add(unbox);
    current->push(unbox);

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, uint32_t length)
{
    MOZ_ASSERT(*emitted == false);

    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    if (canTrackOptimization)
        trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);

    if (!templateObject) {
        if (canTrackOptimization)
            trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
        return Ok();
    }

    MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);

    size_t arraySlots =
        gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;

    if (length > arraySlots) {
        if (canTrackOptimization)
            trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
        return Ok();
    }

    // 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);

    if (canTrackOptimization)
        trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newArrayTrySharedStub(bool* emitted)
{
    MOZ_ASSERT(*emitted == false);

    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    // Try to emit a shared stub cache.

    if (JitOptions.disableSharedStubs)
        return Ok();

    if (*pc != JSOP_NEWINIT && *pc != JSOP_NEWARRAY)
        return Ok();

    if (canTrackOptimization)
        trackOptimizationAttempt(TrackedStrategy::NewArray_SharedCache);

    MInstruction* stub = MNullarySharedStub::New(alloc());
    current->add(stub);
    current->push(stub);

    MOZ_TRY(resumeAfter(stub));

    MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Object, MUnbox::Infallible);
    current->add(unbox);
    current->push(unbox);

    if (canTrackOptimization)
        trackOptimizationSuccess();

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t length)
{
    MOZ_ASSERT(*emitted == false);

    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    // Emit a VM call.
    if (canTrackOptimization)
        trackOptimizationAttempt(TrackedStrategy::NewArray_Call);

    gc::InitialHeap heap = gc::DefaultHeap;
    MConstant* templateConst = MConstant::New(alloc(), NullValue());

    if (templateObject) {
        heap = templateObject->group()->initialHeap(constraints());
        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
    }

    current->add(templateConst);

    MNewArray* ins = MNewArray::NewVM(alloc(), constraints(), length, templateConst, heap, pc);
    current->add(ins);
    current->push(ins);

    if (canTrackOptimization)
        trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_newarray(uint32_t length)
{
    JSObject* templateObject = inspector->getTemplateObject(pc);
    MOZ_TRY(jsop_newarray(templateObject, length));

    // Improve resulting typeset.
    ObjectGroup* templateGroup = inspector->getTemplateObjectGroup(pc);
    if (templateGroup) {
        TemporaryTypeSet* types = MakeSingletonTypeSet(alloc(), constraints(), templateGroup);
        current->peek(-1)->setResultTypeSet(types);
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_newarray(JSObject* templateObject, uint32_t length)
{
    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    bool emitted = false;
    if (canTrackOptimization)
        startTrackingOptimizations();

    if (!forceInlineCaches()) {
        MOZ_TRY(newArrayTryTemplateObject(&emitted, templateObject, length));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(newArrayTrySharedStub(&emitted));
    if (emitted)
        return Ok();

    MOZ_TRY(newArrayTryVM(&emitted, templateObject, length));
    if (emitted)
        return Ok();

    MOZ_CRASH("newarray should have been emited");
}

AbortReasonOr<Ok>
IonBuilder::jsop_newarray_copyonwrite()
{
    ArrayObject* templateObject = ObjectGroup::getCopyOnWriteObject(script(), pc);

    // 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));


    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
    current->add(templateConst);

    MNewArrayCopyOnWrite* ins =
        MNewArrayCopyOnWrite::New(alloc(), constraints(), templateConst,
                                  templateObject->group()->initialHeap(constraints()));

    current->add(ins);
    current->push(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
{
    MOZ_ASSERT(*emitted == false);

    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    if (canTrackOptimization)
        trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
    if (!templateObject) {
        if (canTrackOptimization)
            trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
        return Ok();
    }

    if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots()) {
        if (canTrackOptimization)
            trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsPlainObjectWithDynamicSlots);
        return Ok();
    }

    // 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);

    MOZ_TRY(resumeAfter(ins));

    if (canTrackOptimization)
        trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newObjectTrySharedStub(bool* emitted)
{
    MOZ_ASSERT(*emitted == false);

    // TODO: Support tracking optimizations for inlining a call and regular
    // optimization tracking at the same time. Currently just drop optimization
    // tracking when that happens.
    bool canTrackOptimization = !IsCallPC(pc);

    // Try to emit a shared stub cache.

    if (JitOptions.disableSharedStubs)
        return Ok();

    if (canTrackOptimization)
        trackOptimizationAttempt(TrackedStrategy::NewObject_SharedCache);

    MInstruction* stub = MNullarySharedStub::New(alloc());
    current->add(stub);
    current->push(stub);

    MOZ_TRY(resumeAfter(stub));

    MUnbox* unbox = MUnbox::New(alloc(), current->pop(), MIRType::Object, MUnbox::Infallible);
    current->add(unbox);
    current->push(unbox);

    if (canTrackOptimization)
        trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
{
    // Emit a VM call.
    MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT);

    trackOptimizationAttempt(TrackedStrategy::NewObject_Call);

    gc::InitialHeap heap = gc::DefaultHeap;
    MConstant* templateConst = MConstant::New(alloc(), NullValue());

    if (templateObject) {
        heap = templateObject->group()->initialHeap(constraints());
        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
    }

    current->add(templateConst);

    MNewObject* ins = MNewObject::NewVM(alloc(), constraints(), templateConst, heap,
                                        MNewObject::ObjectLiteral);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_newobject()
{
    bool emitted = false;
    startTrackingOptimizations();

    JSObject* templateObject = inspector->getTemplateObject(pc);

    if (!forceInlineCaches()) {
        MOZ_TRY(newObjectTryTemplateObject(&emitted, templateObject));
        if (emitted)
            return Ok();
    }
    MOZ_TRY(newObjectTrySharedStub(&emitted));
    if (emitted)
        return Ok();

    MOZ_TRY(newObjectTryVM(&emitted, templateObject));
    if (emitted)
        return Ok();

    MOZ_CRASH("newobject should have been emited");
}

AbortReasonOr<Ok>
IonBuilder::jsop_initelem()
{
    MOZ_ASSERT(*pc == JSOP_INITELEM || *pc == JSOP_INITHIDDENELEM);

    MDefinition* value = current->pop();
    MDefinition* id = current->pop();
    MDefinition* obj = current->peek(-1);

    bool emitted = false;

    if (!forceInlineCaches() && *pc == JSOP_INITELEM) {
        MOZ_TRY(initOrSetElemTryDense(&emitted, obj, id, value, /* writeHole = */ true));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(initOrSetElemTryCache(&emitted, obj, id, value));
    if (emitted)
        return Ok();

    MInitElem* initElem = MInitElem::New(alloc(), obj, id, value);
    current->add(initElem);

    return resumeAfter(initElem);
}

AbortReasonOr<Ok>
IonBuilder::jsop_initelem_inc()
{
    MDefinition* value = current->pop();
    MDefinition* id = current->pop();
    MDefinition* obj = current->peek(-1);

    bool emitted = false;

    MAdd* nextId = MAdd::New(alloc(), id, constantInt(1), MIRType::Int32);
    current->add(nextId);
    current->push(nextId);

    if (!forceInlineCaches()) {
        MOZ_TRY(initOrSetElemTryDense(&emitted, obj, id, value, /* writeHole = */ true));
        if (emitted)
            return Ok();
    }

    MOZ_TRY(initOrSetElemTryCache(&emitted, obj, id, value));
    if (emitted)
        return Ok();

    MCallInitElementArray* initElem = MCallInitElementArray::New(alloc(), obj, id, value);
    current->add(initElem);

    return resumeAfter(initElem);
}

AbortReasonOr<Ok>
IonBuilder::jsop_initelem_array()
{
    MDefinition* value = current->pop();
    MDefinition* obj = current->peek(-1);

    // 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;
    if (shouldAbortOnPreliminaryGroups(obj)) {
        needStub = true;
    } else if (!obj->resultTypeSet() ||
               obj->resultTypeSet()->unknownObject() ||
               obj->resultTypeSet()->getObjectCount() != 1)
    {
        needStub = true;
    } else {
        MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1);
        TypeSet::ObjectKey* initializer = obj->resultTypeSet()->getObject(0);
        if (value->type() == MIRType::MagicHole) {
            if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED))
                needStub = true;
        } else if (!initializer->unknownProperties()) {
            HeapTypeSetKey elemTypes = initializer->property(JSID_VOID);
            if (!TypeSetIncludes(elemTypes.maybeTypes(), value->type(), value->resultTypeSet())) {
                elemTypes.freeze(constraints());
                needStub = true;
            }
        }
    }

    uint32_t index = GET_UINT32(pc);
    if (needStub) {
        MOZ_ASSERT(index <= INT32_MAX,
                   "the bytecode emitter must fail to compile code that would "
                   "produce JSOP_INITELEM_ARRAY with an index exceeding "
                   "int32_t range");
        MCallInitElementArray* store = MCallInitElementArray::New(alloc(), obj,
                                                                  constantInt(index), value);
        current->add(store);
        return resumeAfter(store);
    }

    return initializeArrayElement(obj, index, value, /* addResumePoint = */ true);
}

AbortReasonOr<Ok>
IonBuilder::initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
                                   bool addResumePointAndIncrementInitializedLength)
{
    MConstant* id = MConstant::New(alloc(), Int32Value(index));
    current->add(id);

    // Get the elements vector.
    MElements* elements = MElements::New(alloc(), obj);
    current->add(elements);

    if (needsPostBarrier(value))
        current->add(MPostWriteBarrier::New(alloc(), obj, value));

    if ((obj->isNewArray() && obj->toNewArray()->convertDoubleElements()) ||
        (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);

        MOZ_TRY(resumeAfter(initLength));
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_mutateproto()
{
    MDefinition* value = current->pop();
    MDefinition* obj = current->peek(-1);

    MMutateProto* mutate = MMutateProto::New(alloc(), obj, value);
    current->add(mutate);
    return resumeAfter(mutate);
}

AbortReasonOr<Ok>
IonBuilder::jsop_initprop(PropertyName* name)
{
    bool useFastPath = false;

    MDefinition* obj = current->peek(-2);
    if (obj->isNewObject()) {
        if (JSObject* templateObject = obj->toNewObject()->templateObject()) {
            if (templateObject->is<PlainObject>()) {
                if (templateObject->as<PlainObject>().containsPure(name))
                    useFastPath = true;
            } else {
                MOZ_ASSERT(templateObject->as<UnboxedPlainObject>().layout().lookup(name));
                useFastPath = true;
            }
        }
    }
    MInstruction* last = *current->rbegin();

    if (useFastPath && !forceInlineCaches()) {
        // This is definitely initializing an 'own' property of the object, treat
        // it as an assignment.
        MOZ_TRY(jsop_setprop(name));
    } else {
        MDefinition* value = current->pop();
        MDefinition* obj = current->pop();

        TemporaryTypeSet* objTypes = obj->resultTypeSet();
        bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
                                                 /* canModify = */ true);

        bool emitted = false;
        MOZ_TRY(setPropTryCache(&emitted, obj, name, value, barrier, objTypes));
        MOZ_ASSERT(emitted == true);
    }

    // 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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_initprop_getter_setter(PropertyName* name)
{
    MDefinition* value = current->pop();
    MDefinition* obj = current->peek(-1);

    MInitPropGetterSetter* init = MInitPropGetterSetter::New(alloc(), obj, name, value);
    current->add(init);
    return resumeAfter(init);
}

AbortReasonOr<Ok>
IonBuilder::jsop_initelem_getter_setter()
{
    MDefinition* value = current->pop();
    MDefinition* id = current->pop();
    MDefinition* obj = current->peek(-1);

    MInitElemGetterSetter* init = MInitElemGetterSetter::New(alloc(), obj, id, value);
    current->add(init);
    return resumeAfter(init);
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newBlock(size_t stackDepth, jsbytecode* pc, MBasicBlock* maybePredecessor)
{
    MOZ_ASSERT_IF(maybePredecessor, maybePredecessor->stackDepth() == stackDepth);

    MBasicBlock* block = MBasicBlock::New(graph(), stackDepth, info(), maybePredecessor,
                                          bytecodeSite(pc), MBasicBlock::NORMAL);
    if (!block)
        return abort(AbortReason::Alloc);

    block->setLoopDepth(loopDepth_);
    return block;
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newBlock(MBasicBlock* predecessor, jsbytecode* pc, MResumePoint* priorResumePoint)
{
    MBasicBlock* block = MBasicBlock::NewWithResumePoint(graph(), info(), predecessor,
                                                         bytecodeSite(pc), priorResumePoint);
    if (!block)
        return abort(AbortReason::Alloc);

    block->setLoopDepth(loopDepth_);
    return block;
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newBlockPopN(MBasicBlock* predecessor, jsbytecode* pc, uint32_t popped)
{
    MBasicBlock* block = MBasicBlock::NewPopN(graph(), info(), predecessor, bytecodeSite(pc),
                                              MBasicBlock::NORMAL, popped);
    if (!block)
        return abort(AbortReason::Alloc);

    block->setLoopDepth(loopDepth_);
    return block;
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newBlockAfter(MBasicBlock* at, size_t stackDepth, jsbytecode* pc,
                          MBasicBlock* maybePredecessor)
{
    MOZ_ASSERT_IF(maybePredecessor, maybePredecessor->stackDepth() == stackDepth);

    MBasicBlock* block = MBasicBlock::New(graph(), stackDepth, info(), maybePredecessor,
                                          bytecodeSite(pc), MBasicBlock::NORMAL);
    if (!block)
        return abort(AbortReason::Alloc);

    block->setLoopDepth(loopDepth_);
    block->setHitCount(0); // osr block
    graph().insertBlockAfter(at, block);
    return block;
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newOsrPreheader(MBasicBlock* predecessor, jsbytecode* loopEntry,
                            jsbytecode* beforeLoopEntry)
{
    MOZ_ASSERT(loopEntry == GetNextPc(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;
    MOZ_TRY_VAR(osrBlock, newBlockAfter(*graph().begin(), predecessor->stackDepth(), loopEntry));
    MBasicBlock* preheader;
    MOZ_TRY_VAR(preheader, newBlock(predecessor, loopEntry));

    graph().addBlock(preheader);

    // 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 (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 abort(AbortReason::Alloc);
        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 abort(AbortReason::Alloc);
        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.
    MOZ_TRY(resumeAt(start, loopEntry));

    // 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 abort(AbortReason::Alloc);

    // 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 abort(AbortReason::Alloc);
    graph().setOsrBlock(osrBlock);

    return preheader;
}

AbortReasonOr<MBasicBlock*>
IonBuilder::newPendingLoopHeader(MBasicBlock* predecessor, jsbytecode* pc, bool osr, bool canOsr,
                                 unsigned stackPhiCount)
{
    // 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 (!block)
        return abort(AbortReason::Alloc);

    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 abort(AbortReason::Alloc);
            MIRType type = typeSet->getKnownMIRType();
            if (!phi->addBackedgeType(alloc(), type, typeSet))
                return abort(AbortReason::Alloc);
        }
    }

    return block;
}

MTest*
IonBuilder::newTest(MDefinition* ins, MBasicBlock* ifTrue, MBasicBlock* ifFalse)
{
    MTest* test = MTest::New(alloc(), ins, ifTrue, ifFalse);
    test->cacheOperandMightEmulateUndefined(constraints());
    return test;
}

// 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.
AbortReasonOr<Ok>
IonBuilder::resume(MInstruction* ins, jsbytecode* pc, MResumePoint::Mode mode)
{
    MOZ_ASSERT(ins->isEffectful() || !ins->isMovable());

    MResumePoint* resumePoint = MResumePoint::New(alloc(), ins->block(), pc,
                                                  mode);
    if (!resumePoint)
        return abort(AbortReason::Alloc);
    ins->setResumePoint(resumePoint);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::resumeAt(MInstruction* ins, jsbytecode* pc)
{
    return resume(ins, pc, MResumePoint::ResumeAt);
}

AbortReasonOr<Ok>
IonBuilder::resumeAfter(MInstruction* ins)
{
    return resume(ins, pc, MResumePoint::ResumeAfter);
}

AbortReasonOr<Ok>
IonBuilder::maybeInsertResume()
{
    // 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 Ok();

    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->getOperand(i)->emptyResultTypeSet())
            continue;

        TemporaryTypeSet* types = alloc().lifoAlloc()->new_<TemporaryTypeSet>();
        if (types) {
            ins->setResultTypeSet(types);
            return;
        }
    }
}

// Return whether property lookups can be performed effectlessly on clasp.
static bool
ClassHasEffectlessLookup(const Class* clasp)
{
    return (clasp == &UnboxedPlainObject::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;
    }

    if (JSObject* proto = script()->global().maybeGetPrototype(key))
        return testSingletonProperty(proto, id);

    return nullptr;
}

AbortReasonOr<bool>
IonBuilder::testNotDefinedProperty(MDefinition* obj, jsid id, bool ownProperty /* = false */)
{
    TemporaryTypeSet* types = obj->resultTypeSet();
    if (!types || types->unknownObject() || types->getKnownMIRType() != MIRType::Object)
        return false;

    for (unsigned i = 0, count = types->getObjectCount(); i < count; i++) {
        TypeSet::ObjectKey* key = types->getObject(i);
        if (!key)
            continue;

        while (true) {
            if (!alloc().ensureBallast())
                return abort(AbortReason::Alloc);

            if (!key->hasStableClassAndProto(constraints()) || key->unknownProperties())
                return false;

            const Class* clasp = key->clasp();
            if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, id))
                return 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 false;
            }

            HeapTypeSetKey property = key->property(id);
            if (property.isOwnProperty(constraints()))
                return false;

            // If we only care about own properties don't check the proto.
            if (ownProperty)
                break;

            JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
            if (!proto)
                break;
            key = TypeSet::ObjectKey::get(proto);
        }
    }

    return true;
}

AbortReasonOr<Ok>
IonBuilder::pushTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind)
{
    MOZ_ASSERT(def == current->peek(-1));

    MDefinition* replace = addTypeBarrier(current->pop(), observed, kind);
    if (!replace)
        return abort(AbortReason::Alloc);

    current->push(replace);
    return Ok();
}

// 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;
}

AbortReasonOr<Ok>
IonBuilder::pushDOMTypeBarrier(MInstruction* ins, TemporaryTypeSet* observed, JSFunction* func)
{
    MOZ_ASSERT(func && func->isNative() && func->hasJitInfo());

    const JSJitInfo* jitinfo = func->jitInfo();
    bool barrier = DOMCallNeedsBarrier(jitinfo, observed);
    // 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;
            }
            return def;
        }
        replace = MUnbox::New(alloc(), def, definiteType, MUnbox::Infallible);
        break;
      }
    }

    current->add(replace);
    return replace;
}

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 active 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);
}

AbortReasonOr<Ok>
IonBuilder::getStaticName(bool* emitted, JSObject* staticObject, PropertyName* name,
                          MDefinition* lexicalCheck)
{
    MOZ_ASSERT(*emitted == false);

    jsid id = NameToId(name);

    bool isGlobalLexical = staticObject->is<LexicalEnvironmentObject>() &&
                           staticObject->as<LexicalEnvironmentObject>().isGlobal();
    MOZ_ASSERT(isGlobalLexical ||
               staticObject->is<GlobalObject>() ||
               staticObject->is<CallObject>() ||
               staticObject->is<ModuleEnvironmentObject>());
    MOZ_ASSERT(staticObject->isSingleton());

    // Always emit the lexical check. This could be optimized, but is
    // currently not for simplicity's sake.
    if (lexicalCheck)
        return Ok();

    TypeSet::ObjectKey* staticKey = TypeSet::ObjectKey::get(staticObject);
    if (analysisContext)
        staticKey->ensureTrackedProperty(analysisContext, NameToId(name));

    if (staticKey->unknownProperties())
        return Ok();

    HeapTypeSetKey property = staticKey->property(id);
    if (!property.maybeTypes() ||
        !property.maybeTypes()->definiteProperty() ||
        property.nonData(constraints()))
    {
        // The property has been reconfigured as non-configurable, non-enumerable
        // or non-writable.
        return Ok();
    }

    // Don't optimize global lexical bindings if they aren't initialized at
    // compile time.
    if (isGlobalLexical && IsUninitializedGlobalLexicalSlot(staticObject, name))
        return Ok();

    *emitted = true;

    TemporaryTypeSet* types = bytecodeTypes(pc);
    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), 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 Ok();
            }
        }

        // Try to inline properties that have never been overwritten.
        Value constantValue;
        if (property.constant(constraints(), &constantValue)) {
            pushConstant(constantValue);
            return Ok();
        }
    }

    MOZ_TRY(loadStaticSlot(staticObject, barrier, types, property.maybeTypes()->definiteSlot()));

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::loadStaticSlot(JSObject* staticObject, BarrierKind barrier, TemporaryTypeSet* types,
                           uint32_t slot)
{
    if (barrier == BarrierKind::NoBarrier) {
        // Try to inline properties that can only have one value.
        MIRType knownType = types->getKnownMIRType();
        if (knownType == MIRType::Undefined) {
            pushConstant(UndefinedValue());
            return Ok();
        }
        if (knownType == MIRType::Null) {
            pushConstant(NullValue());
            return Ok();
        }
    }

    MInstruction* obj = constant(ObjectValue(*staticObject));

    MIRType rvalType = types->getKnownMIRType();
    if (barrier != BarrierKind::NoBarrier)
        rvalType = MIRType::Value;

    return loadSlot(obj, slot, NumFixedSlots(staticObject), rvalType, barrier, types);
}

// Whether a write of the given value may need a post-write barrier for GC purposes.
bool
IonBuilder::needsPostBarrier(MDefinition* value)
{
    if (!compartment->zone()->nurseryExists())
        return false;
    return value->mightBeType(MIRType::Object);
}

AbortReasonOr<Ok>
IonBuilder::setStaticName(JSObject* staticObject, PropertyName* name)
{
    jsid id = NameToId(name);

    bool isGlobalLexical = staticObject->is<LexicalEnvironmentObject>() &&
                           staticObject->as<LexicalEnvironmentObject>().isGlobal();
    MOZ_ASSERT(isGlobalLexical ||
               staticObject->is<GlobalObject>() ||
               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 needsPreBarrier = property.needsBarrier(constraints());
    return storeSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject),
                     value, needsPreBarrier, slotType);
}

JSObject*
IonBuilder::testGlobalLexicalBinding(PropertyName* name)
{
    MOZ_ASSERT(JSOp(*pc) == JSOP_BINDGNAME ||
               JSOp(*pc) == JSOP_GETGNAME ||
               JSOp(*pc) == JSOP_SETGNAME ||
               JSOp(*pc) == JSOP_STRICTSETGNAME);

    // 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;
}

AbortReasonOr<Ok>
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 Ok();
    }
    if (name == names().NaN) {
        pushConstant(compartment->runtime()->NaNValue());
        return Ok();
    }
    if (name == names().Infinity) {
        pushConstant(compartment->runtime()->positiveInfinityValue());
        return Ok();
    }

    if (JSObject* obj = testGlobalLexicalBinding(name)) {
        bool emitted = false;
        MOZ_TRY(getStaticName(&emitted, obj, name));
        if (emitted)
            return Ok();

        if (!forceInlineCaches() && obj->is<GlobalObject>()) {
            TemporaryTypeSet* types = bytecodeTypes(pc);
            MDefinition* globalObj = constant(ObjectValue(*obj));
            MOZ_TRY(getPropTryCommonGetter(&emitted, globalObj, name, types));
            if (emitted)
                return Ok();
        }
    }

    return jsop_getname(name);
}

AbortReasonOr<Ok>
IonBuilder::jsop_getname(PropertyName* name)
{
    MDefinition* object;
    if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope())
        object = constant(ObjectValue(script()->global().lexicalEnvironment()));
    else
        object = current->environmentChain();

    MGetNameCache* ins = MGetNameCache::New(alloc(), object);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));

    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
IonBuilder::jsop_intrinsic(PropertyName* name)
{
    TemporaryTypeSet* types = bytecodeTypes(pc);

    Value vp = UndefinedValue();
    // If 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);

        MOZ_TRY(resumeAfter(ins));

        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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_getimport(PropertyName* name)
{
    ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script());
    MOZ_ASSERT(env);

    Shape* shape;
    ModuleEnvironmentObject* targetEnv;
    MOZ_ALWAYS_TRUE(env->lookupImport(NameToId(name), &targetEnv, &shape));

    PropertyName* localName = JSID_TO_STRING(shape->propid())->asAtom().asPropertyName();
    bool emitted = false;
    MOZ_TRY(getStaticName(&emitted, targetEnv, localName));

    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, alloc(), constraints(),
                                                           staticKey, name, types,
                                                           /* updateObserved = */ true);

        MOZ_TRY(loadStaticSlot(targetEnv, barrier, types, shape->slot()));
    }

    // 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;
        MOZ_TRY_VAR(checked, addLexicalCheck(current->pop()));
        current->push(checked);
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_bindname(PropertyName* name)
{
    MDefinition* envChain;
    if (IsGlobalOp(JSOp(*pc)) && !script()->hasNonSyntacticScope())
        envChain = constant(ObjectValue(script()->global().lexicalEnvironment()));
    else
        envChain = current->environmentChain();

    MBindNameCache* ins = MBindNameCache::New(alloc(), envChain, name, script(), pc);
    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_bindvar()
{
    MOZ_ASSERT(usesEnvironmentChain());
    MCallBindVar* ins = MCallBindVar::New(alloc(), current->environmentChain());
    current->add(ins);
    current->push(ins);
    return Ok();
}

static MIRType
GetElemKnownType(bool needsHoleCheck, TemporaryTypeSet* types)
{
    MIRType knownType = types->getKnownMIRType();

    // 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;
}

AbortReasonOr<Ok>
IonBuilder::jsop_getelem()
{
    startTrackingOptimizations();

    MDefinition* index = current->pop();
    MDefinition* obj = current->pop();

    trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());
    trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());

    // 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);

        MOZ_TRY(resumeAfter(ins));

        TemporaryTypeSet* types = bytecodeTypes(pc);
        return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
    }

    bool emitted = false;

    // Handle lazy-arguments first. We have to do this even if forceInlineCaches
    // is true (lazy arguments cannot escape to the IC). Like the code in
    // IonBuilder::jsop_getprop, we only do this if we're not in analysis mode,
    // to avoid unnecessary analysis aborts.
    if (obj->mightBeType(MIRType::MagicOptimizedArguments) && !info().isAnalysis()) {
        trackOptimizationAttempt(TrackedStrategy::GetElem_Arguments);
        MOZ_TRY(getElemTryArguments(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedConstant);
        MOZ_TRY(getElemTryArgumentsInlinedConstant(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedSwitch);
        MOZ_TRY(getElemTryArgumentsInlinedIndex(&emitted, obj, index));
        if (emitted)
            return Ok();

        if (script()->argumentsHasVarBinding()) {
            trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
            return abort(AbortReason::Disable, "Type is not definitely lazy arguments.");
        }
    }

    obj = maybeUnboxForPropertyAccess(obj);
    if (obj->type() == MIRType::Object)
        obj = convertUnboxedObjects(obj);

    if (!forceInlineCaches()) {
        // Note: no trackOptimizationAttempt call is needed, getElemTryGetProp
        // will call it.
        MOZ_TRY(getElemTryGetProp(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_Dense);
        MOZ_TRY(getElemTryDense(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_TypedStatic);
        MOZ_TRY(getElemTryTypedStatic(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_TypedArray);
        MOZ_TRY(getElemTryTypedArray(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_String);
        MOZ_TRY(getElemTryString(&emitted, obj, index));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetElem_TypedObject);
        MOZ_TRY(getElemTryTypedObject(&emitted, obj, index));
        if (emitted)
            return Ok();
    }

    trackOptimizationAttempt(TrackedStrategy::GetElem_InlineCache);
    return getElemAddCache(obj, index);
}

AbortReasonOr<Ok>
IonBuilder::getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    // 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 Ok();

    if (!objPrediction.ofArrayKind())
        return Ok();

    TypedObjectPrediction elemPrediction = objPrediction.arrayElementType();
    if (elemPrediction.isUseless())
        return Ok();

    uint32_t elemSize;
    if (!elemPrediction.hasKnownSize(&elemSize))
        return Ok();

    switch (elemPrediction.kind()) {
      case type::Simd:
        // FIXME (bug 894105): load into a MIRType::float32x4 etc
        trackOptimizationOutcome(TrackedOutcome::GenericFailure);
        return Ok();

      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,
                                          BoundsCheckKind kind)
{
    // 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, kind);

    return indexAsByteOffset->add(index, AssertedCast<int32_t>(elemSize));
}

AbortReasonOr<Ok>
IonBuilder::getElemTryScalarElemOfTypedObject(bool* emitted,
                                              MDefinition* obj,
                                              MDefinition* index,
                                              TypedObjectPrediction objPrediction,
                                              TypedObjectPrediction elemPrediction,
                                              uint32_t elemSize)
{
    MOZ_ASSERT(objPrediction.ofArrayKind());

    // 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,
                                       BoundsCheckKind::IsLoad))
    {
        return Ok();
    }

    trackOptimizationSuccess();
    *emitted = true;

    return pushScalarLoadFromTypedObject(obj, indexAsByteOffset, elemType);
}

AbortReasonOr<Ok>
IonBuilder::getElemTryReferenceElemOfTypedObject(bool* emitted,
                                                 MDefinition* obj,
                                                 MDefinition* index,
                                                 TypedObjectPrediction objPrediction,
                                                 TypedObjectPrediction elemPrediction)
{
    MOZ_ASSERT(objPrediction.ofArrayKind());

    ReferenceTypeDescr::Type elemType = elemPrediction.referenceType();
    uint32_t elemSize = ReferenceTypeDescr::size(elemType);

    LinearSum indexAsByteOffset(alloc());
    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset,
                                       BoundsCheckKind::IsLoad))
    {
        return Ok();
    }

    trackOptimizationSuccess();
    *emitted = true;

    return pushReferenceLoadFromTypedObject(obj, indexAsByteOffset, elemType, nullptr);
}

AbortReasonOr<Ok>
IonBuilder::pushScalarLoadFromTypedObject(MDefinition* obj,
                                          const LinearSum& byteOffset,
                                          ScalarTypeDescr::Type elemType)
{
    uint32_t size = ScalarTypeDescr::size(elemType);
    MOZ_ASSERT(size == ScalarTypeDescr::alignment(elemType));

    // Find location within the owner object.
    MDefinition* elements;
    MDefinition* scaledOffset;
    int32_t adjustment;
    MOZ_TRY(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 Ok();
}

AbortReasonOr<Ok>
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);
    MOZ_TRY(loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset, &adjustment));

    TemporaryTypeSet* observedTypes = bytecodeTypes(pc);

    MInstruction* load = nullptr;  // initialize to silence GCC warning
    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), 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);
}

AbortReasonOr<Ok>
IonBuilder::getElemTryComplexElemOfTypedObject(bool* emitted,
                                               MDefinition* obj,
                                               MDefinition* index,
                                               TypedObjectPrediction objPrediction,
                                               TypedObjectPrediction elemPrediction,
                                               uint32_t elemSize)
{
    MOZ_ASSERT(objPrediction.ofArrayKind());

    MDefinition* type = loadTypedObjectType(obj);
    MDefinition* elemTypeObj = typeObjectForElementFromArrayStructType(type);

    LinearSum indexAsByteOffset(alloc());
    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset,
                                       BoundsCheckKind::IsLoad))
    {
        return Ok();
    }

    return pushDerivedTypedObject(emitted, obj, indexAsByteOffset,
                                  elemPrediction, elemTypeObj);
}

AbortReasonOr<Ok>
IonBuilder::pushDerivedTypedObject(bool* emitted,
                                   MDefinition* obj,
                                   const LinearSum& baseByteOffset,
                                   TypedObjectPrediction derivedPrediction,
                                   MDefinition* derivedTypeObj)
{
    // Find location within the owner object.
    MDefinition* owner;
    LinearSum ownerByteOffset(alloc());
    MOZ_TRY(loadTypedObjectData(obj, &owner, &ownerByteOffset));
    if (!ownerByteOffset.add(baseByteOffset, 1))
        return abort(AbortReason::Disable, "Overflow/underflow on type object offset.");

    MDefinition* offset = ConvertLinearSum(alloc(), current, ownerByteOffset,
                                           /* 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 {
        MOZ_TRY(pushTypeBarrier(derivedTypedObj, observedTypes, BarrierKind::TypeSet));
    }

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
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 Ok();

    if (id != IdToTypeId(id))
        return Ok();

    TemporaryTypeSet* types = bytecodeTypes(pc);

    trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
    MOZ_TRY(getPropTryConstant(emitted, obj, id, types) );
    if (*emitted) {
        index->setImplicitlyUsedUnchecked();
        return Ok();
    }

    trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined);
    MOZ_TRY(getPropTryNotDefined(emitted, obj, id, types) );
    if (*emitted) {
        index->setImplicitlyUsedUnchecked();
        return Ok();
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
        trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
        return Ok();
    }

    // Don't generate a fast path if there have been bounds check failures
    // and this access might be on a sparse property.
    bool hasExtraIndexedProperty;
    MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, obj));
    if (hasExtraIndexedProperty && failedBoundsCheck_) {
        trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
        return Ok();
    }

    // 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 Ok();
    }

    MOZ_TRY(jsop_getelem_dense(obj, index));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<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;
    }

    bool hasExtraIndexedProperty;
    MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, obj));
    if (hasExtraIndexedProperty) {
        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;
}

AbortReasonOr<Ok>
IonBuilder::getElemTryTypedStatic(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    JSObject* tarrObj;
    MOZ_TRY_VAR(tarrObj, getStaticTypedArrayObject(obj, index));
    if (!tarrObj)
        return Ok();

    // LoadTypedArrayElementStatic currently treats uint32 arrays as int32.
    Scalar::Type viewType = tarrObj->as<TypedArrayObject>().type();
    if (viewType == Scalar::Uint32) {
        trackOptimizationOutcome(TrackedOutcome::StaticTypedArrayUint32);
        return Ok();
    }

    MDefinition* ptr = convertShiftToMaskForStaticTypedArray(index, viewType);
    if (!ptr)
        return Ok();

    // 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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryTypedArray(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    Scalar::Type arrayType;
    if (!ElementAccessIsTypedArray(constraints(), obj, index, &arrayType)) {
        trackOptimizationOutcome(TrackedOutcome::AccessNotTypedArray);
        return Ok();
    }

    // Emit typed getelem variant.
    MOZ_TRY(jsop_getelem_typed(obj, index, arrayType));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryString(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    if (obj->type() != MIRType::String || !IsNumberType(index->type())) {
        trackOptimizationOutcome(TrackedOutcome::AccessNotString);
        return Ok();
    }

    // If 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 Ok();
    }

    // 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, BoundsCheckKind::IsLoad);

    MCharCodeAt* charCode = MCharCodeAt::New(alloc(), obj, index);
    current->add(charCode);

    MFromCharCode* result = MFromCharCode::New(alloc(), charCode);
    current->add(result);
    current->push(result);

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    if (inliningDepth_ > 0)
        return Ok();

    if (obj->type() != MIRType::MagicOptimizedArguments)
        return Ok();

    // 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, BoundsCheckKind::IsLoad);

    // Load the argument from the actual arguments.
    bool modifiesArgs = script()->baselineScript()->modifiesArguments();
    MGetFrameArgument* load = MGetFrameArgument::New(alloc(), index, modifiesArgs);
    current->add(load);
    current->push(load);

    TemporaryTypeSet* types = bytecodeTypes(pc);
    MOZ_TRY(pushTypeBarrier(load, types, BarrierKind::TypeSet));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryArgumentsInlinedConstant(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    if (inliningDepth_ == 0)
        return Ok();

    if (obj->type() != MIRType::MagicOptimizedArguments)
        return Ok();

    MConstant* indexConst = index->maybeConstantValue();
    if (!indexConst || indexConst->type() != MIRType::Int32)
        return Ok();

    // Emit inlined arguments.
    obj->setImplicitlyUsedUnchecked();

    MOZ_ASSERT(!info().argsObjAliasesFormals());

    // When the id is constant, we can just return the corresponding inlined argument
    MOZ_ASSERT(inliningDepth_ > 0);

    int32_t id = indexConst->toInt32();
    index->setImplicitlyUsedUnchecked();

    if (id < (int32_t)inlineCallInfo_->argc() && id >= 0)
        current->push(inlineCallInfo_->getArg(id));
    else
        pushConstant(UndefinedValue());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemTryArgumentsInlinedIndex(bool* emitted, MDefinition* obj, MDefinition* index)
{
    MOZ_ASSERT(*emitted == false);

    if (inliningDepth_ == 0)
        return Ok();

    if (obj->type() != MIRType::MagicOptimizedArguments)
        return Ok();

    if (!IsNumberType(index->type()))
        return Ok();

    // Currently, we do not support any arguments vector larger than 10, as this
    // is being translated into code at the call site, and it would be better to
    // store the arguments contiguously on the stack.
    if (inlineCallInfo_->argc() > 10) {
        trackOptimizationOutcome(TrackedOutcome::CantInlineBound);
        return abort(AbortReason::Disable, "NYI get argument element with too many arguments");
    }

    // Emit inlined arguments.
    obj->setImplicitlyUsedUnchecked();

    MOZ_ASSERT(!info().argsObjAliasesFormals());

    // Ensure index is an integer.
    MInstruction* idInt32 = MToInt32::New(alloc(), index);
    current->add(idInt32);
    index = idInt32;

    // Bailout if we read more than the number of actual arguments. This bailout
    // cannot re-enter because reading out of bounds arguments will disable the
    // lazy arguments optimization for this script, when this code would be
    // executed in Baseline. (see GetElemOptimizedArguments)
    index = addBoundsCheck(index, constantInt(inlineCallInfo_->argc()),
                           BoundsCheckKind::IsLoad);

    // Get an instruction to represent the state of the argument vector.
    MInstruction* args = MArgumentState::New(alloc().fallible(), inlineCallInfo_->argv());
    if (!args)
        return abort(AbortReason::Alloc);
    current->add(args);

    // Select a value to pick from a vector.
    MInstruction* load = MLoadElementFromState::New(alloc(), args, index);
    current->add(load);
    current->push(load);

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getElemAddCache(MDefinition* obj, MDefinition* index)
{
    // Emit GetPropertyCache.

    TemporaryTypeSet* types = bytecodeTypes(pc);

    BarrierKind barrier;
    if (obj->type() == MIRType::Object) {
        // Always add a barrier if the index is not an int32 value, so we can
        // attach stubs for particular properties.
        if (index->type() == MIRType::Int32) {
            barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), constraints(), obj,
                                                   nullptr, types);
        } else {
            barrier = BarrierKind::TypeSet;
        }
    } else {
        // PropertyReadNeedsTypeBarrier only accounts for object types, so for
        // now always insert a barrier if the input is not known to be an
        // object.
        barrier = BarrierKind::TypeSet;
    }

    // Ensure we insert a type barrier for reads from typed objects, as type
    // information does not account for the initial undefined/null types.
    if (barrier != BarrierKind::TypeSet && !types->unknown()) {
        MOZ_ASSERT(obj->resultTypeSet());
        switch (obj->resultTypeSet()->forAllClasses(constraints(), IsTypedObjectClass)) {
          case TemporaryTypeSet::ForAllResult::ALL_FALSE:
          case TemporaryTypeSet::ForAllResult::EMPTY:
            break;
          case TemporaryTypeSet::ForAllResult::ALL_TRUE:
          case TemporaryTypeSet::ForAllResult::MIXED:
            barrier = BarrierKind::TypeSet;
            break;
        }
    }

    MGetPropertyCache* ins = MGetPropertyCache::New(alloc(), obj, index,
                                                    barrier == BarrierKind::TypeSet);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));

    // 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);
    }

    MOZ_TRY(pushTypeBarrier(ins, types, barrier));

    trackOptimizationSuccess();
    return Ok();
}

TemporaryTypeSet*
IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id)
{
    if (objTypes->unknownObject() || objTypes->getObjectCount() == 0)
        return nullptr;

    TemporaryTypeSet* acc = nullptr;
    LifoAlloc* lifoAlloc = alloc().lifoAlloc();

    Vector<HeapTypeSetKey, 4, SystemAllocPolicy> properties;
    if (!properties.reserve(objTypes->getObjectCount()))
        return nullptr;

    for (unsigned i = 0; i < objTypes->getObjectCount(); i++) {
        TypeSet::ObjectKey* key = objTypes->getObject(i);

        if (key->unknownProperties())
            return nullptr;

        HeapTypeSetKey property = key->property(id);
        HeapTypeSet* currentSet = property.maybeTypes();

        if (!currentSet || currentSet->unknown())
            return nullptr;

        properties.infallibleAppend(property);

        if (acc) {
            acc = TypeSet::unionSets(acc, currentSet, lifoAlloc);
        } else {
            TemporaryTypeSet empty;
            acc = TypeSet::unionSets(&empty, currentSet, lifoAlloc);
        }

        if (!acc)
            return nullptr;
    }

    // Freeze all the properties associated with the refined type set.
    for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++)
        i->freeze(constraints());

    return acc;
}

AbortReasonOr<Ok>
IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
{
    TemporaryTypeSet* types = bytecodeTypes(pc);

    MOZ_ASSERT(index->type() == MIRType::Int32 || index->type() == MIRType::Double);
    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(alloc(), obj, nullptr, types);
    }

    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), 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 = false;
    if (types->hasType(TypeSet::UndefinedType())) {
        bool hasExtraIndexedProperty;
        MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, obj));
        readOutOfBounds = !hasExtraIndexedProperty;
    }

    MIRType knownType = MIRType::Value;
    if (barrier == BarrierKind::NoBarrier)
        knownType = GetElemKnownType(needsHoleCheck, types);

    // 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);
    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);

    // 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 =
        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, BoundsCheckKind::IsLoad);

        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, 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) {
        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,
                                       BoundsCheckKind boundsCheckKind)
{
    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->zone()->group()->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, boundsCheckKind);

                    *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, boundsCheckKind);

        *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;
}

AbortReasonOr<Ok>
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,
                                   BoundsCheckKind::IsLoad);

        // 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 Ok();
    } 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);
    }
}

AbortReasonOr<Ok>
IonBuilder::jsop_setelem()
{
    bool emitted = false;
    startTrackingOptimizations();

    MDefinition* value = current->pop();
    MDefinition* index = current->pop();
    MDefinition* object = convertUnboxedObjects(current->pop());

    trackTypeInfo(TrackedTypeSite::Receiver, object->type(), object->resultTypeSet());
    trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());
    trackTypeInfo(TrackedTypeSite::Value, value->type(), value->resultTypeSet());

    if (shouldAbortOnPreliminaryGroups(object)) {
        MInstruction* ins = MCallSetElement::New(alloc(), object, index, value, IsStrictSetPC(pc));
        current->add(ins);
        current->push(value);
        return resumeAfter(ins);
    }

    if (!forceInlineCaches()) {
        trackOptimizationAttempt(TrackedStrategy::SetElem_TypedStatic);
        MOZ_TRY(setElemTryTypedStatic(&emitted, object, index, value));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::SetElem_TypedArray);
        MOZ_TRY(setElemTryTypedArray(&emitted, object, index, value));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::SetElem_Dense);
        SetElemICInspector icInspect(inspector->setElemICInspector(pc));
        bool writeHole = icInspect.sawOOBDenseWrite();
        MOZ_TRY(initOrSetElemTryDense(&emitted, object, index, value, writeHole));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::SetElem_Arguments);
        MOZ_TRY(setElemTryArguments(&emitted, object, index, value));
        if (emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::SetElem_TypedObject);
        MOZ_TRY(setElemTryTypedObject(&emitted, object, index, value));
        if (emitted)
            return Ok();
    }

    if (script()->argumentsHasVarBinding() &&
        object->mightBeType(MIRType::MagicOptimizedArguments) &&
        info().analysisMode() != Analysis_ArgumentsUsage)
    {
        return abort(AbortReason::Disable, "Type is not definitely lazy arguments.");
    }

    trackOptimizationAttempt(TrackedStrategy::SetElem_InlineCache);
    MOZ_TRY(initOrSetElemTryCache(&emitted, object, index, value));
    if (emitted)
        return Ok();

    // Emit call.
    MInstruction* ins = MCallSetElement::New(alloc(), object, index, value, IsStrictSetPC(pc));
    current->add(ins);
    current->push(value);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::setElemTryTypedObject(bool* emitted, MDefinition* obj,
                                  MDefinition* index, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    // 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 Ok();

    if (!objPrediction.ofArrayKind())
        return Ok();

    TypedObjectPrediction elemPrediction = objPrediction.arrayElementType();
    if (elemPrediction.isUseless())
        return Ok();

    uint32_t elemSize;
    if (!elemPrediction.hasKnownSize(&elemSize))
        return Ok();

    switch (elemPrediction.kind()) {
      case type::Simd:
        // FIXME (bug 894105): store a MIRType::float32x4 etc
        trackOptimizationOutcome(TrackedOutcome::GenericFailure);
        return Ok();

      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 Ok();
    }

    MOZ_CRASH("Bad kind");
}

AbortReasonOr<Ok>
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,
                                       BoundsCheckKind::IsStore))
    {
        return Ok();
    }

    return setPropTryReferenceTypedObjectValue(emitted, obj, indexAsByteOffset,
                                               elemType, value, nullptr);
}

AbortReasonOr<Ok>
IonBuilder::setElemTryScalarElemOfTypedObject(bool* emitted,
                                              MDefinition* obj,
                                              MDefinition* index,
                                              TypedObjectPrediction objPrediction,
                                              MDefinition* value,
                                              TypedObjectPrediction elemPrediction,
                                              uint32_t elemSize)
{
    // 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,
                                       BoundsCheckKind::IsStore))
    {
        return Ok();
    }

    return setPropTryScalarTypedObjectValue(emitted, obj, indexAsByteOffset, elemType, value);
}

AbortReasonOr<Ok>
IonBuilder::setElemTryTypedStatic(bool* emitted, MDefinition* object,
                                  MDefinition* index, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    JSObject* tarrObj;
    MOZ_TRY_VAR(tarrObj, getStaticTypedArrayObject(object, index));
    if (!tarrObj)
        return Ok();

    SharedMem<void*> viewData = tarrObj->as<TypedArrayObject>().viewDataEither();
    if (tarrObj->zone()->group()->nursery().isInside(viewData))
        return Ok();

    Scalar::Type viewType = tarrObj->as<TypedArrayObject>().type();
    MDefinition* ptr = convertShiftToMaskForStaticTypedArray(index, viewType);
    if (!ptr)
        return Ok();

    // 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);

    MOZ_TRY(resumeAfter(store));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setElemTryTypedArray(bool* emitted, MDefinition* object,
                                 MDefinition* index, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    Scalar::Type arrayType;
    if (!ElementAccessIsTypedArray(constraints(), object, index, &arrayType)) {
        trackOptimizationOutcome(TrackedOutcome::AccessNotTypedArray);
        return Ok();
    }

    // Emit typed setelem variant.
    MOZ_TRY(jsop_setelem_typed(arrayType, object, index, value));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::initOrSetElemTryDense(bool* emitted, MDefinition* object,
                                  MDefinition* index, MDefinition* value, bool writeHole)
{
    MOZ_ASSERT(*emitted == false);

    if (value->type() == MIRType::MagicHole)
    {
        trackOptimizationOutcome(TrackedOutcome::InitHole);
        return Ok();
    }

    if (!ElementAccessIsDenseNative(constraints(), object, index)) {
        trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
        return Ok();
    }

    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                      &object, nullptr, &value, /* canModify = */ true))
    {
        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
        return Ok();
    }

    if (!object->resultTypeSet()) {
        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
        return Ok();
    }

    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 Ok();
    }

    // Don't generate a fast path if there have been bounds check failures
    // and this access might be on a sparse property.
    bool hasExtraIndexedProperty;
    MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, object));
    if (hasExtraIndexedProperty && failedBoundsCheck_) {
        trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
        return Ok();
    }

    // Emit dense setelem variant.
    MOZ_TRY(initOrSetElemDense(conversion, object, index, value, writeHole, emitted));

    if (!*emitted) {
        trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
        return Ok();
    }

    trackOptimizationSuccess();
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setElemTryArguments(bool* emitted, MDefinition* object,
                                MDefinition* index, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    if (object->type() != MIRType::MagicOptimizedArguments)
        return Ok();

    // Arguments are not supported yet.
    return abort(AbortReason::Disable, "NYI arguments[]=");
}

AbortReasonOr<Ok>
IonBuilder::initOrSetElemTryCache(bool* emitted, MDefinition* object,
                                  MDefinition* index, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    if (!object->mightBeType(MIRType::Object)) {
        trackOptimizationOutcome(TrackedOutcome::NotObject);
        return Ok();
    }

    if (value->type() == MIRType::MagicHole)
    {
        trackOptimizationOutcome(TrackedOutcome::InitHole);
        return Ok();
    }

    bool barrier = true;
    if (index->type() == MIRType::Int32 &&
        !PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                       &object, nullptr, &value, /* 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;
    MOZ_TRY_VAR(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);

    // Emit SetPropertyCache.
    bool strict = JSOp(*pc) == JSOP_STRICTSETELEM;
    MSetPropertyCache* ins =
        MSetPropertyCache::New(alloc(), object, index, value, strict, needsPostBarrier(value),
                               barrier, guardHoles);
    current->add(ins);

    // Push value back onto stack. Init ops keep their object on stack.
    if (!IsPropertyInitOp(JSOp(*pc)))
        current->push(value);

    MOZ_TRY(resumeAfter(ins));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::initOrSetElemDense(TemporaryTypeSet::DoubleConversion conversion,
                               MDefinition* obj, MDefinition* id, MDefinition* value,
                               bool writeHole, bool* emitted)
{
    MOZ_ASSERT(*emitted == false);

    MIRType elementType = DenseNativeElementType(constraints(), obj);
    bool packed = ElementAccessIsPacked(constraints(), obj);

    // 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 hasExtraIndexedProperty;
    MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, obj));

    bool mayBeFrozen = ElementAccessMightBeFrozen(constraints(), obj);

    if (mayBeFrozen && hasExtraIndexedProperty) {
        // 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 Ok();
    }

    *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);
    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 && !hasExtraIndexedProperty && !mayBeFrozen) {
        MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue);
        store = ins;
        common = ins;

        current->add(ins);
    } else if (mayBeFrozen) {
        MOZ_ASSERT(!hasExtraIndexedProperty,
                   "FallibleStoreElement codegen assumes no extra indexed properties");

        bool strict = IsStrictSetPC(pc);
        MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id,
                                                                newValue, strict);
        store = ins;
        common = ins;

        current->add(ins);
    } else {
        MInstruction* initLength = initializedLength(obj, elements);

        id = addBoundsCheck(id, initLength, BoundsCheckKind::IsStore);
        bool needsHoleCheck = !packed && hasExtraIndexedProperty;

        MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
        store = ins;
        common = ins;

        current->add(store);
    }

    // Push value back onto stack. Init ops keep their object on stack.
    if (!IsPropertyInitOp(JSOp(*pc)))
        current->push(value);

    MOZ_TRY(resumeAfter(store));

    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 Ok();
}


AbortReasonOr<Ok>
IonBuilder::jsop_setelem_typed(Scalar::Type arrayType,
                               MDefinition* obj, MDefinition* id, MDefinition* value)
{
    SetElemICInspector icInspect(inspector->setElemICInspector(pc));
    bool expectOOB = icInspect.sawOOBTypedArrayWrite();

    if (expectOOB)
        spew("Emitting OOB TypedArray SetElem");

    // 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,
                               BoundsCheckKind::IsStore);

    // 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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_length()
{
    if (jsop_length_fastPath())
        return Ok();

    PropertyName* name = info().getAtom(pc)->asPropertyName();
    return jsop_getprop(name);
}

bool
IonBuilder::jsop_length_fastPath()
{
    TemporaryTypeSet* types = bytecodeTypes(pc);

    if (types->getKnownMIRType() != MIRType::Int32)
        return false;

    MDefinition* obj = current->peek(-1);

    if (shouldAbortOnPreliminaryGroups(obj))
        return false;

    if (obj->mightBeType(MIRType::String)) {
        if (obj->mightBeType(MIRType::Object))
            return false;
        current->pop();
        MStringLength* ins = MStringLength::New(alloc(), obj);
        current->add(ins);
        current->push(ins);
        return true;
    }

    if (obj->mightBeType(MIRType::Object)) {
        TemporaryTypeSet* objTypes = obj->resultTypeSet();

        // 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 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;
}

AbortReasonOr<Ok>
IonBuilder::jsop_arguments()
{
    if (info().needsArgsObj()) {
        current->push(current->argumentsObject());
        return Ok();
    }

    MOZ_ASSERT(hasLazyArguments_);
    MConstant* lazyArg = MConstant::New(alloc(), MagicValue(JS_OPTIMIZED_ARGUMENTS));
    current->add(lazyArg);
    current->push(lazyArg);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_newtarget()
{
    if (!info().funMaybeLazy()) {
        MOZ_ASSERT(!info().script()->isForEval());
        pushConstant(NullValue());
        return Ok();
    }

    MOZ_ASSERT(info().funMaybeLazy());

    if (info().funMaybeLazy()->isArrow()) {
        MArrowNewTarget* arrowNewTarget = MArrowNewTarget::New(alloc(), getCallee());
        current->add(arrowNewTarget);
        current->push(arrowNewTarget);
        return Ok();
    }

    if (inliningDepth_ == 0) {
        MNewTarget* newTarget = MNewTarget::New(alloc());
        current->add(newTarget);
        current->push(newTarget);
        return Ok();
    }

    if (!inlineCallInfo_->constructing()) {
        pushConstant(UndefinedValue());
        return Ok();
    }

    current->push(inlineCallInfo_->getNewTarget());
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_rest()
{
    if (info().analysisMode() == Analysis_ArgumentsUsage) {
        // 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 Ok();
    }

    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 Ok();
    }

    // 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;

    MOZ_TRY(jsop_newarray(numRest));

    if (numRest == 0) {
        // No more updating to do. (Note that in this one case the length from
        // the template object is already correct.)
        return Ok();
    }

    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++) {
        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

        index = MConstant::New(alloc(), Int32Value(i - numFormals));
        current->add(index);

        MDefinition* arg = inlineCallInfo_->argv()[i];
        MStoreElement* store = MStoreElement::New(alloc(), elements, index, arg,
                                                  /* 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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_checkisobj(uint8_t kind)
{
    MDefinition* toCheck = current->peek(-1);

    if (toCheck->type() == MIRType::Object) {
        toCheck->setImplicitlyUsedUnchecked();
        return Ok();
    }

    MCheckIsObj* check = MCheckIsObj::New(alloc(), current->pop(), kind);
    current->add(check);
    current->push(check);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_checkiscallable(uint8_t kind)
{
    MCheckIsCallable* check = MCheckIsCallable::New(alloc(), current->pop(), kind);
    current->add(check);
    current->push(check);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_checkobjcoercible()
{
    MDefinition* toCheck = current->peek(-1);

    if (!toCheck->mightBeType(MIRType::Undefined) &&
        !toCheck->mightBeType(MIRType::Null))
    {
        toCheck->setImplicitlyUsedUnchecked();
        return Ok();
    }

    MOZ_ASSERT(toCheck->type() == MIRType::Value ||
               toCheck->type() == MIRType::Null  ||
               toCheck->type() == MIRType::Undefined);

    // 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, jsid id, uint32_t* pnfixed)
{
    if (!types || types->unknownObject() || !types->objectOrSentinel()) {
        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
        return UINT32_MAX;
    }

    uint32_t slot = UINT32_MAX;

    for (size_t i = 0; i < types->getObjectCount(); i++) {
        TypeSet::ObjectKey* key = types->getObject(i);
        if (!key)
            continue;

        if (key->unknownProperties()) {
            trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
            return UINT32_MAX;
        }

        if (key->isSingleton()) {
            trackOptimizationOutcome(TrackedOutcome::Singleton);
            return UINT32_MAX;
        }

        HeapTypeSetKey property = key->property(id);
        if (!property.maybeTypes() ||
            !property.maybeTypes()->definiteProperty() ||
            property.nonData(constraints()))
        {
            trackOptimizationOutcome(TrackedOutcome::NotFixedSlot);
            return UINT32_MAX;
        }

        // 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, jsid id, JSValueType* punboxedType)
{
    if (!types || types->unknownObject() || !types->objectOrSentinel()) {
        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
        return UINT32_MAX;
    }

    uint32_t offset = UINT32_MAX;

    for (size_t i = 0; i < types->getObjectCount(); i++) {
        TypeSet::ObjectKey* key = types->getObject(i);
        if (!key)
            continue;

        if (key->unknownProperties()) {
            trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
            return UINT32_MAX;
        }

        if (key->isSingleton()) {
            trackOptimizationOutcome(TrackedOutcome::Singleton);
            return UINT32_MAX;
        }

        UnboxedLayout* layout = key->group()->maybeUnboxedLayout();
        if (!layout) {
            trackOptimizationOutcome(TrackedOutcome::NotUnboxed);
            return UINT32_MAX;
        }

        const UnboxedLayout::Property* property = layout->lookup(id);
        if (!property) {
            trackOptimizationOutcome(TrackedOutcome::StructNoField);
            return UINT32_MAX;
        }

        if (layout->nativeGroup()) {
            trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
            return UINT32_MAX;
        }

        key->watchStateChangeForUnboxedConvertedToNative(constraints());

        if (offset == UINT32_MAX) {
            offset = property->offset;
            *punboxedType = property->type;
        } else if (offset != property->offset) {
            trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
            return UINT32_MAX;
        } else if (*punboxedType != property->type) {
            trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
            return UINT32_MAX;
        }
    }

    return offset;
}

AbortReasonOr<Ok>
IonBuilder::jsop_runonce()
{
    MRunOncePrologue* ins = MRunOncePrologue::New(alloc());
    current->add(ins);
    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_not()
{
    MDefinition* value = current->pop();

    MNot* ins = MNot::New(alloc(), value, constraints());
    current->add(ins);
    current->push(ins);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_superbase()
{
    JSFunction* fun = info().funMaybeLazy();
    if (!fun || !fun->allowSuperProperty())
        return abort(AbortReason::Disable, "super only supported directly in methods");

    auto* homeObject = MHomeObject::New(alloc(), getCallee());
    current->add(homeObject);

    auto* superBase = MHomeObjectSuperBase::New(alloc(), homeObject);
    current->add(superBase);
    current->push(superBase);

    MOZ_TRY(resumeAfter(superBase));
    return Ok();
}


AbortReasonOr<Ok>
IonBuilder::jsop_getprop_super(PropertyName* name)
{
    MDefinition* obj = current->pop();
    MDefinition* receiver = current->pop();

    MConstant* id = constant(StringValue(name));
    auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));

    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
IonBuilder::jsop_getelem_super()
{
    MDefinition* obj = current->pop();
    MDefinition* receiver = current->pop();
    MDefinition* id = current->pop();

#if defined(JS_CODEGEN_X86)
    if (instrumentedProfiling())
        return abort(AbortReason::Disable, "profiling functions with GETELEM_SUPER is disabled on x86");
#endif

    auto* ins = MGetPropSuperCache::New(alloc(), obj, receiver, id);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));

    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
}

NativeObject*
IonBuilder::commonPrototypeWithGetterSetter(TemporaryTypeSet* types, PropertyName* name,
                                            bool isGetter, JSFunction* getterOrSetter,
                                            bool* guardGlobal)
{
    // If there's a single object on the proto chain of all objects in |types|
    // that contains a property |name| with |getterOrSetter| getter or setter
    // function, return that object.

    // No sense looking if we don't know what's going on.
    if (!types || types->unknownObject())
        return nullptr;
    *guardGlobal = false;

    NativeObject* foundProto = nullptr;
    for (unsigned i = 0; i < types->getObjectCount(); i++) {
        TypeSet::ObjectKey* key = types->getObject(i);
        if (!key)
            continue;

        while (key) {
            if (key->unknownProperties())
                return nullptr;

            const Class* clasp = key->clasp();
            if (!ClassHasEffectlessLookup(clasp))
                return nullptr;
            JSObject* singleton = key->isSingleton() ? key->singleton() : nullptr;
            if (ObjectHasExtraOwnProperty(compartment, key, NameToId(name))) {
                if (!singleton || !singleton->is<GlobalObject>())
                    return nullptr;
                *guardGlobal = true;
            }

            // Look for a getter/setter on the class itself which may need
            // to be called.
            if (isGetter && clasp->getOpsGetProperty())
                return nullptr;
            if (!isGetter && clasp->getOpsSetProperty())
                return nullptr;

            // If we have a singleton, check if it contains the getter or
            // setter we're looking for. Note that we don't need to add any
            // type constraints as the caller will add a Shape guard for the
            // holder and type constraints for other objects on the proto
            // chain.
            //
            // If the object is not a singleton, we fall back to the code below
            // and check whether the property is missing. That's fine because
            // we're looking for a getter or setter on the proto chain and
            // these objects are singletons.
            if (singleton) {
                if (!singleton->is<NativeObject>())
                    return nullptr;

                NativeObject* singletonNative = &singleton->as<NativeObject>();
                if (Shape* propShape = singletonNative->lookupPure(name)) {
                    // We found a property. Check if it's the getter or setter
                    // we're looking for.
                    Value getterSetterVal = ObjectValue(*getterOrSetter);
                    if (isGetter) {
                        if (propShape->getterOrUndefined() != getterSetterVal)
                            return nullptr;
                    } else {
                        if (propShape->setterOrUndefined() != getterSetterVal)
                            return nullptr;
                    }

                    if (!foundProto)
                        foundProto = singletonNative;
                    else if (foundProto != singletonNative)
                        return nullptr;
                    break;
                }
            }

            // 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 nullptr;
            }
            if (singleton) {
                if (CanHaveEmptyPropertyTypesForOwnProperty(singleton)) {
                    MOZ_ASSERT(singleton->is<GlobalObject>());
                    *guardGlobal = true;
                }
            }

            JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
            if (foundProto && proto == foundProto) {
                // We found an object on the proto chain that's known to have
                // the getter or setter property, so we can stop looking.
                break;
            }

            if (!proto) {
                // The getter or setter being searched for did not show up on
                // the object's prototype chain.
                return nullptr;
            }
            key = TypeSet::ObjectKey::get(proto);
        }
    }

    return foundProto;
}

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, JSFunction* getterOrSetter,
                                   MDefinition** guard,
                                   Shape* globalShape/* = nullptr*/,
                                   MDefinition** globalGuard/* = nullptr */)
{
    MOZ_ASSERT(getterOrSetter);
    MOZ_ASSERT_IF(globalShape, globalGuard);
    bool guardGlobal;

    // Check if all objects being accessed will lookup the name through foundProto.
    NativeObject* foundProto =
        commonPrototypeWithGetterSetter(types, name, isGetter, getterOrSetter, &guardGlobal);
    if (!foundProto || (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 the getter/setter is not configurable we don't have to guard on the
    // proto's shape.
    Shape* propShape = foundProto->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, foundProto->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;
}

AbortReasonOr<Ok>
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 Ok();

    for (unsigned i = 0; i < pushedTypes->getObjectCount(); i++) {
        if (pushedTypes->getGroup(i) != nullptr)
            return Ok();
    }

    // Object's typeset should be a proper object
    if (!objTypes || objTypes->baseFlags() || objTypes->unknownObject())
        return Ok();

    unsigned int objCount = objTypes->getObjectCount();
    if (objCount == 0)
        return Ok();

    InlinePropertyTable* inlinePropTable = getPropCache->initInlinePropertyTable(alloc(), pc);
    if (!inlinePropTable)
        return abort(AbortReason::Alloc);

    // 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 abort(AbortReason::Alloc);
    }

    if (inlinePropTable->numEntries() == 0) {
        getPropCache->clearInlinePropertyTable();
        return Ok();
    }

#ifdef JS_JITSPEW
    if (inlinePropTable->numEntries() > 0)
        JitSpew(JitSpew_Inlining, "Annotated GetPropertyCache with %d/%d inline cases",
                                    (int) inlinePropTable->numEntries(), (int) objCount);
#endif

    // If 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 abort(AbortReason::Alloc);
        inlinePropTable->setPriorResumePoint(resumePoint);
        replaceMaybeFallbackFunctionGetter(getPropCache);
        current->pop();
    }
    return Ok();
}

// 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;
}

AbortReasonOr<Ok>
IonBuilder::loadSlot(MDefinition* obj, size_t slot, size_t nfixed, MIRType rvalType,
                     BarrierKind barrier, TemporaryTypeSet* types)
{
    if (slot < nfixed) {
        MLoadFixedSlot* load = MLoadFixedSlot::New(alloc(), obj, slot);
        current->add(load);
        current->push(load);

        load->setResultType(rvalType);
        return pushTypeBarrier(load, types, barrier);
    }

    MSlots* slots = MSlots::New(alloc(), obj);
    current->add(slots);

    MLoadSlot* load = MLoadSlot::New(alloc(), slots, slot - nfixed);
    current->add(load);
    current->push(load);

    load->setResultType(rvalType);
    return pushTypeBarrier(load, types, barrier);
}

AbortReasonOr<Ok>
IonBuilder::loadSlot(MDefinition* obj, Shape* shape, MIRType rvalType,
                     BarrierKind barrier, TemporaryTypeSet* types)
{
    return loadSlot(obj, shape->slot(), shape->numFixedSlots(), rvalType, barrier, types);
}

AbortReasonOr<Ok>
IonBuilder::storeSlot(MDefinition* obj, size_t slot, size_t nfixed,
                      MDefinition* value, bool needsBarrier,
                      MIRType slotType /* = 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);
}

AbortReasonOr<Ok>
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;
}

AbortReasonOr<Ok>
IonBuilder::jsop_getprop(PropertyName* name)
{
    bool emitted = false;
    startTrackingOptimizations();

    MDefinition* obj = current->pop();
    TemporaryTypeSet* types = bytecodeTypes(pc);

    trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());

    if (!info().isAnalysis()) {
        // 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);
        MOZ_TRY(getPropTryArgumentsLength(&emitted, obj));
        if (emitted)
            return Ok();

        // Try to optimize arguments.callee.
        trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsCallee);
        MOZ_TRY(getPropTryArgumentsCallee(&emitted, obj, name));
        if (emitted)
            return Ok();
    }

    obj = maybeUnboxForPropertyAccess(obj);
    if (obj->type() == MIRType::Object)
        obj = convertUnboxedObjects(obj);

    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), constraints(),
                                                       obj, name, types);

    // Try to optimize to a specific constant.
    trackOptimizationAttempt(TrackedStrategy::GetProp_InferredConstant);
    if (barrier == BarrierKind::NoBarrier) {
        MOZ_TRY(getPropTryInferredConstant(&emitted, obj, name, types));
        if (emitted)
            return Ok();
    } 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()) {
            MOZ_TRY(getPropTryConstant(&emitted, obj, NameToId(name), types));
            if (emitted)
                return Ok();
        }

        current->push(call);
        MOZ_TRY(resumeAfter(call));
        return pushTypeBarrier(call, types, BarrierKind::TypeSet);
    }

    // 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);
    MOZ_TRY(getPropTryInnerize(&emitted, obj, name, types));
    if (emitted)
        return Ok();

    if (!forceInlineCaches()) {
        // Try to hardcode known constants.
        trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
        MOZ_TRY(getPropTryConstant(&emitted, obj, NameToId(name), types));
        if (emitted)
            return Ok();

        // Try to hardcode known not-defined
        trackOptimizationAttempt(TrackedStrategy::GetProp_NotDefined);
        MOZ_TRY(getPropTryNotDefined(&emitted, obj, NameToId(name), types));
        if (emitted)
            return Ok();

        // Try to emit loads from definite slots.
        trackOptimizationAttempt(TrackedStrategy::GetProp_DefiniteSlot);
        MOZ_TRY(getPropTryDefiniteSlot(&emitted, obj, name, barrier, types));
        if (emitted)
            return Ok();

        // Try to emit loads from unboxed objects.
        trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
        MOZ_TRY(getPropTryUnboxed(&emitted, obj, name, barrier, types));
        if (emitted)
            return Ok();

        // Try to inline a common property getter, or make a call.
        trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
        MOZ_TRY(getPropTryCommonGetter(&emitted, obj, name, types));
        if (emitted)
            return Ok();

        // Try to emit a monomorphic/polymorphic access based on baseline caches.
        trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
        MOZ_TRY(getPropTryInlineAccess(&emitted, obj, name, barrier, types));
        if (emitted)
            return Ok();

        // Try to emit a property access on the prototype based on baseline
        // caches.
        trackOptimizationAttempt(TrackedStrategy::GetProp_InlineProtoAccess);
        MOZ_TRY(getPropTryInlineProtoAccess(&emitted, obj, name, types));
        if (emitted)
            return Ok();

        // Try to emit loads from a module namespace.
        trackOptimizationAttempt(TrackedStrategy::GetProp_ModuleNamespace);
        MOZ_TRY(getPropTryModuleNamespace(&emitted, obj, name, barrier, types));
        if (emitted)
            return Ok();

        // Try to emit loads from known binary data blocks
        trackOptimizationAttempt(TrackedStrategy::GetProp_TypedObject);
        MOZ_TRY(getPropTryTypedObject(&emitted, obj, name));
        if (emitted)
            return Ok();
    }

    // Emit a polymorphic cache.
    trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache);
    return getPropAddCache(obj, name, barrier, types);
}

AbortReasonOr<Ok>
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 Ok();
    }

    // Remove null/undefined from the TypeSet.
    TemporaryTypeSet* types = thisDef->resultTypeSet()->cloneObjectsOnly(alloc_->lifoAlloc());
    if (!types)
        return abort(AbortReason::Alloc);

    MFilterTypeSet* filter = MFilterTypeSet::New(alloc(), thisDef, types);
    current->add(filter);
    current->rewriteAtDepth(-2, filter);

    // 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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::checkIsDefinitelyOptimizedArguments(MDefinition* obj, bool* isOptimizedArgs)
{
    if (obj->type() != MIRType::MagicOptimizedArguments) {
        if (script()->argumentsHasVarBinding() &&
            obj->mightBeType(MIRType::MagicOptimizedArguments))
        {
            return abort(AbortReason::Disable, "Type is not definitely lazy arguments.");
        }

        *isOptimizedArgs = false;
        return Ok();
    }

    *isOptimizedArgs = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryInferredConstant(bool* emitted, MDefinition* obj, PropertyName* name,
                                       TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    // Need a result typeset to optimize.
    TemporaryTypeSet* objTypes = obj->resultTypeSet();
    if (!objTypes) {
        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
        return Ok();
    }

    JSObject* singleton = objTypes->maybeSingleton();
    if (!singleton) {
        trackOptimizationOutcome(TrackedOutcome::NotSingleton);
        return Ok();
    }

    TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton);
    if (key->unknownProperties()) {
        trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
        return Ok();
    }

    HeapTypeSetKey property = key->property(NameToId(name));

    Value constantValue = UndefinedValue();
    if (property.constant(constraints(), &constantValue)) {
        spew("Optimized constant property");
        obj->setImplicitlyUsedUnchecked();
        pushConstant(constantValue);
        types->addType(TypeSet::GetValueType(constantValue), alloc_->lifoAlloc());
        trackOptimizationSuccess();
        *emitted = true;
    }

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryArgumentsLength(bool* emitted, MDefinition* obj)
{
    MOZ_ASSERT(*emitted == false);

    if (JSOp(*pc) != JSOP_LENGTH)
        return Ok();

    bool isOptimizedArgs = false;
    MOZ_TRY(checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs));
    if (!isOptimizedArgs)
        return Ok();

    trackOptimizationSuccess();
    *emitted = true;

    obj->setImplicitlyUsedUnchecked();

    // We don't know anything from the callee
    if (inliningDepth_ == 0) {
        MInstruction* ins = MArgumentsLength::New(alloc());
        current->add(ins);
        current->push(ins);
        return Ok();
    }

    // We are inlining and know the number of arguments the callee pushed
    pushConstant(Int32Value(inlineCallInfo_->argv().length()));
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryArgumentsCallee(bool* emitted, MDefinition* obj, PropertyName* name)
{
    MOZ_ASSERT(*emitted == false);

    if (name != names().callee)
        return Ok();

    bool isOptimizedArgs = false;
    MOZ_TRY(checkIsDefinitelyOptimizedArguments(obj, &isOptimizedArgs));
    if (!isOptimizedArgs)
        return Ok();

    MOZ_ASSERT(script()->hasMappedArgsObj());

    obj->setImplicitlyUsedUnchecked();
    current->push(getCallee());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    if (!types->mightBeMIRType(MIRType::Object)) {
        // If we have not observed an object result here, don't look for a
        // singleton constant.
        trackOptimizationOutcome(TrackedOutcome::NotObject);
        return Ok();
    }

    JSObject* singleton = testSingletonPropertyTypes(obj, id);
    if (!singleton) {
        trackOptimizationOutcome(TrackedOutcome::NotSingleton);
        return Ok();
    }

    // Property access is a known constant -- safe to emit.
    obj->setImplicitlyUsedUnchecked();

    pushConstant(ObjectValue(*singleton));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryNotDefined(bool* emitted, MDefinition* obj, jsid id, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    if (!types->mightBeMIRType(MIRType::Undefined)) {
        // Only optimize if we expect this property access to return undefined.
        trackOptimizationOutcome(TrackedOutcome::NotUndefined);
        return Ok();
    }

    bool res;
    MOZ_TRY_VAR(res, testNotDefinedProperty(obj, id));
    if (!res) {
        trackOptimizationOutcome(TrackedOutcome::GenericFailure);
        return Ok();
    }

    obj->setImplicitlyUsedUnchecked();
    pushConstant(UndefinedValue());

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryTypedObject(bool* emitted,
                                  MDefinition* obj,
                                  PropertyName* name)
{
    TypedObjectPrediction fieldPrediction;
    size_t fieldOffset;
    size_t fieldIndex;
    if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex))
        return Ok();

    switch (fieldPrediction.kind()) {
      case type::Simd:
        // FIXME (bug 894104): load into a MIRType::float32x4 etc
        return Ok();

      case type::Struct:
      case type::Array:
        return getPropTryComplexPropOfTypedObject(emitted,
                                                  obj,
                                                  fieldOffset,
                                                  fieldPrediction,
                                                  fieldIndex);

      case type::Reference:
        return getPropTryReferencePropOfTypedObject(emitted,
                                                    obj,
                                                    fieldOffset,
                                                    fieldPrediction,
                                                    name);

      case type::Scalar:
        return getPropTryScalarPropOfTypedObject(emitted,
                                                 obj,
                                                 fieldOffset,
                                                 fieldPrediction);
    }

    MOZ_CRASH("Bad kind");
}

AbortReasonOr<Ok>
IonBuilder::getPropTryScalarPropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                              int32_t fieldOffset,
                                              TypedObjectPrediction fieldPrediction)
{
    // 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 Ok();

    trackOptimizationSuccess();
    *emitted = true;

    LinearSum byteOffset(alloc());
    if (!byteOffset.add(fieldOffset))
        return abort(AbortReason::Disable, "Overflow of field offsets.");

    return pushScalarLoadFromTypedObject(typedObj, byteOffset, fieldType);
}

AbortReasonOr<Ok>
IonBuilder::getPropTryReferencePropOfTypedObject(bool* emitted, MDefinition* typedObj,
                                                 int32_t fieldOffset,
                                                 TypedObjectPrediction fieldPrediction,
                                                 PropertyName* name)
{
    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 Ok();

    trackOptimizationSuccess();
    *emitted = true;

    LinearSum byteOffset(alloc());
    if (!byteOffset.add(fieldOffset))
        return abort(AbortReason::Disable, "Overflow of field offsets.");

    return pushReferenceLoadFromTypedObject(typedObj, byteOffset, fieldType, name);
}

AbortReasonOr<Ok>
IonBuilder::getPropTryComplexPropOfTypedObject(bool* emitted,
                                               MDefinition* typedObj,
                                               int32_t fieldOffset,
                                               TypedObjectPrediction fieldPrediction,
                                               size_t fieldIndex)
{
    // 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 Ok();

    // 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))
        return abort(AbortReason::Disable, "Overflow of field offsets.");

    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;
}

AbortReasonOr<Ok>
IonBuilder::getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
                                   BarrierKind barrier, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    uint32_t nfixed;
    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), NameToId(name), &nfixed);
    if (slot == UINT32_MAX)
        return Ok();

    if (obj->type() != MIRType::Object) {
        MGuardObject* guard = MGuardObject::New(alloc(), obj);
        current->add(guard);
        obj = guard;
    }

    MInstruction* load;
    if (slot < nfixed) {
        load = MLoadFixedSlot::New(alloc(), obj, slot);
    } else {
        MInstruction* slots = MSlots::New(alloc(), obj);
        current->add(slots);

        load = MLoadSlot::New(alloc(), slots, slot - nfixed);
    }

    if (barrier == BarrierKind::NoBarrier)
        load->setResultType(types->getKnownMIRType());

    current->add(load);
    current->push(load);

    MOZ_TRY(pushTypeBarrier(load, types, barrier));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name,
                                      BarrierKind barrier, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    TemporaryTypeSet* objTypes = obj->resultTypeSet();
    if (!objTypes) {
        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
        return Ok();
    }

    JSObject* singleton = objTypes->maybeSingleton();
    if (!singleton) {
        trackOptimizationOutcome(TrackedOutcome::NotSingleton);
        return Ok();
    }

    if (!singleton->is<ModuleNamespaceObject>()) {
        trackOptimizationOutcome(TrackedOutcome::NotModuleNamespace);
        return Ok();
    }

    ModuleNamespaceObject* ns = &singleton->as<ModuleNamespaceObject>();
    ModuleEnvironmentObject* env;
    Shape* shape;
    if (!ns->bindings().lookup(NameToId(name), &env, &shape)) {
        trackOptimizationOutcome(TrackedOutcome::UnknownProperty);
        return Ok();
    }

    obj->setImplicitlyUsedUnchecked();
    MConstant* envConst = constant(ObjectValue(*env));
    uint32_t slot = shape->slot();
    uint32_t nfixed = env->numFixedSlots();

    MIRType rvalType = types->getKnownMIRType();
    if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
        rvalType = MIRType::Value;

    MOZ_TRY(loadSlot(envConst, slot, nfixed, rvalType, barrier, types));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

MInstruction*
IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                BarrierKind barrier, TemporaryTypeSet* types)
{
    // 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;
}

AbortReasonOr<Ok>
IonBuilder::getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
                              BarrierKind barrier, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    JSValueType unboxedType;
    uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), NameToId(name), &unboxedType);
    if (offset == UINT32_MAX)
        return Ok();

    if (obj->type() != MIRType::Object) {
        MGuardObject* guard = MGuardObject::New(alloc(), obj);
        current->add(guard);
        obj = guard;
    }

    MInstruction* load = loadUnboxedProperty(obj, offset, unboxedType, barrier, types);
    current->push(load);

    MOZ_TRY(pushTypeBarrier(load, types, barrier));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

MDefinition*
IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
                const BaselineInspector::ReceiverVector& receivers,
                const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
                bool isOwnProperty)
{
    MOZ_ASSERT(isOwnProperty == !holder);
    MOZ_ASSERT(holderShape);

    obj = convertUnboxedObjects(obj, convertUnboxedGroups);

    if (isOwnProperty) {
        MOZ_ASSERT(receivers.empty());
        return addShapeGuard(obj, holderShape, Bailout_ShapeGuard);
    }

    MDefinition* holderDef = constant(ObjectValue(*holder));
    addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard);

    return addGuardReceiverPolymorphic(obj, receivers);
}

AbortReasonOr<Ok>
IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
                                   TemporaryTypeSet* types, bool innerized)
{
    MOZ_ASSERT(*emitted == false);

    TemporaryTypeSet* objTypes = obj->resultTypeSet();

    JSFunction* commonGetter = nullptr;
    MDefinition* guard = nullptr;
    MDefinition* globalGuard = nullptr;

    {
        Shape* lastProperty = nullptr;
        Shape* globalShape = nullptr;
        JSObject* foundProto = nullptr;
        bool isOwnProperty = false;
        BaselineInspector::ReceiverVector receivers(alloc());
        BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
        if (inspector->commonGetPropFunction(pc, innerized, &foundProto, &lastProperty, &commonGetter,
                                              &globalShape, &isOwnProperty,
                                              receivers, convertUnboxedGroups))
        {
            bool canUseTIForGetter = false;
            if (!isOwnProperty) {
                // If it's not an own property, try to use TI to avoid shape guards.
                // For own properties we use the path below.
                canUseTIForGetter = testCommonGetterSetter(objTypes, name, /* isGetter = */ true,
                                                           commonGetter, &guard,
                                                           globalShape, &globalGuard);
            }
            if (!canUseTIForGetter) {
                // If it's an own property or 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 abort(AbortReason::Alloc);
            }
        } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ true,
                                                              &commonGetter))
        {
            // Try to use TI to guard on this getter.
            if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ true,
                                        commonGetter, &guard))
            {
                return Ok();
            }
        } else {
            // The Baseline IC didn't have any information we can use.
            return Ok();
        }
    }

    DOMObjectKind objKind = DOMObjectKind::Unknown;
    bool isDOM = objTypes && objTypes->isDOMClass(constraints(), &objKind);
    if (isDOM)
        MOZ_TRY_VAR(isDOM, testShouldDOMCall(objTypes, commonGetter, JSJitInfo::Getter));

    if (isDOM) {
        const JSJitInfo* jitinfo = commonGetter->jitInfo();
        // We don't support MGetDOMProperty/MGetDOMMember on things that might
        // be proxies when the value might be in a slot, because the
        // CodeGenerator code for LGetDOMProperty/LGetDOMMember doesn't handle
        // that case correctly.
        if (objKind == DOMObjectKind::Native ||
            (!jitinfo->isAlwaysInSlot && !jitinfo->isLazilyCachedInSlot))
        {
            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 Ok();
                }

                // 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, objKind, obj, guard, globalGuard);
            }
            if (!get)
                return abort(AbortReason::Alloc);
            current->add(get);
            current->push(get);

            if (get->isEffectful())
                MOZ_TRY(resumeAfter(get));

            MOZ_TRY(pushDOMTypeBarrier(get, types, commonGetter));

            trackOptimizationOutcome(TrackedOutcome::DOM);
            *emitted = true;
            return Ok();
        }
    }

    // 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 abort(AbortReason::Alloc);
    current->push(constant(ObjectValue(*commonGetter)));

    current->push(obj);

    CallInfo callInfo(alloc(), pc, /* constructing = */ false,
                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
    if (!callInfo.init(current, 0))
        return abort(AbortReason::Alloc);

    if (commonGetter->isNative()) {
        InliningStatus status;
        MOZ_TRY_VAR(status, inlineNativeGetter(callInfo, commonGetter));
        switch (status) {
          case InliningStatus_WarmUpCountTooLow:
          case InliningStatus_NotInlined:
            break;
          case InliningStatus_Inlined:
            trackOptimizationOutcome(TrackedOutcome::Inlined);
            *emitted = true;
            return Ok();
        }
    }

    // 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 abort(AbortReason::Error);
          case InliningDecision_DontInline:
          case InliningDecision_WarmUpCountTooLow:
            break;
          case InliningDecision_Inline: {
            InliningStatus status;
            MOZ_TRY_VAR(status, inlineScriptedCall(callInfo, commonGetter));
            if (status == InliningStatus_Inlined) {
                *emitted = true;
                return Ok();
            }
            break;
          }
        }
    }

    MOZ_TRY(makeCall(commonGetter, callInfo));

    // If 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 Ok();
}

bool
IonBuilder::canInlinePropertyOpShapes(const BaselineInspector::ReceiverVector& receivers)
{
    if (receivers.empty()) {
        trackOptimizationOutcome(TrackedOutcome::NoShapeInfo);
        return false;
    }

    for (size_t i = 0; i < receivers.length(); i++) {
        // 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;
}

AbortReasonOr<Ok>
IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                   BarrierKind barrier, TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    BaselineInspector::ReceiverVector receivers(alloc());
    BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
    if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
        return abort(AbortReason::Alloc);

    if (!canInlinePropertyOpShapes(receivers))
        return Ok();

    obj = convertUnboxedObjects(obj, convertUnboxedGroups);

    MIRType rvalType = types->getKnownMIRType();
    if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
        rvalType = MIRType::Value;

    if (receivers.length() == 1) {
        if (!receivers[0].group) {
            // 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);

            MOZ_TRY(loadSlot(obj, shape, rvalType, barrier, types));

            trackOptimizationOutcome(TrackedOutcome::Monomorphic);
            *emitted = true;
            return Ok();
        }

        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);

            MOZ_TRY(loadSlot(expando, shape, rvalType, barrier, types));

            trackOptimizationOutcome(TrackedOutcome::Monomorphic);
            *emitted = true;
            return Ok();
        }

        // Monomorphic load from an unboxed object.
        ObjectGroup* group = receivers[0].group;
        if (obj->resultTypeSet() && !obj->resultTypeSet()->hasType(TypeSet::ObjectType(group)))
            return Ok();

        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);

        MOZ_TRY(pushTypeBarrier(load, types, barrier));

        trackOptimizationOutcome(TrackedOutcome::Monomorphic);
        *emitted = true;
        return Ok();
    }

    MOZ_ASSERT(receivers.length() > 1);
    spew("Inlining polymorphic GETPROP");

    if (Shape* propShape = PropertyShapesHaveSameSlot(receivers, NameToId(name))) {
        obj = addGuardReceiverPolymorphic(obj, receivers);
        if (!obj)
            return abort(AbortReason::Alloc);

        MOZ_TRY(loadSlot(obj, propShape, rvalType, barrier, types));

        trackOptimizationOutcome(TrackedOutcome::Polymorphic);
        *emitted = true;
        return Ok();
    }

    MGetPropertyPolymorphic* load = MGetPropertyPolymorphic::New(alloc(), obj, name);
    current->add(load);
    current->push(load);

    for (size_t i = 0; i < receivers.length(); i++) {
        Shape* propShape = nullptr;
        if (receivers[i].shape) {
            propShape = receivers[i].shape->searchLinear(NameToId(name));
            MOZ_ASSERT(propShape);
        }
        if (!load->addReceiver(receivers[i], propShape))
            return abort(AbortReason::Alloc);
    }

    if (failedShapeGuard_)
        load->setNotMovable();

    load->setResultType(rvalType);
    MOZ_TRY(pushTypeBarrier(load, types, barrier));

    trackOptimizationOutcome(TrackedOutcome::Polymorphic);
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropTryInlineProtoAccess(bool* emitted, MDefinition* obj, PropertyName* name,
                                        TemporaryTypeSet* types)
{
    MOZ_ASSERT(*emitted == false);

    BaselineInspector::ReceiverVector receivers(alloc());
    BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
    JSObject* holder = nullptr;
    if (!inspector->maybeInfoForProtoReadSlot(pc, receivers, convertUnboxedGroups, &holder))
        return abort(AbortReason::Alloc);

    if (!canInlinePropertyOpShapes(receivers))
        return Ok();

    MOZ_ASSERT(holder);
    holder = checkNurseryObject(holder);

    BarrierKind barrier;
    MOZ_TRY_VAR(barrier, PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types));

    MIRType rvalType = types->getKnownMIRType();
    if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
        rvalType = MIRType::Value;

    // Guard on the receiver shapes/groups.
    obj = convertUnboxedObjects(obj, convertUnboxedGroups);
    obj = addGuardReceiverPolymorphic(obj, receivers);
    if (!obj)
        return abort(AbortReason::Alloc);

    // Guard on the holder's shape.
    MInstruction* holderDef = constant(ObjectValue(*holder));
    Shape* holderShape = holder->as<NativeObject>().shape();
    holderDef = addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard);

    Shape* propShape = holderShape->searchLinear(NameToId(name));
    MOZ_ASSERT(propShape);

    MOZ_TRY(loadSlot(holderDef, propShape, rvalType, barrier, types));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::getPropAddCache(MDefinition* obj, PropertyName* name,
                            BarrierKind barrier, TemporaryTypeSet* types)
{
    // PropertyReadNeedsTypeBarrier only accounts for object types, so for now
    // always insert a barrier if the input is not known to be an object.
    if (obj->type() != MIRType::Object)
        barrier = BarrierKind::TypeSet;

    // 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) {
        BarrierKind protoBarrier;
        MOZ_TRY_VAR(protoBarrier, PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types));
        if (protoBarrier != BarrierKind::NoBarrier) {
            MOZ_ASSERT(barrier <= protoBarrier);
            barrier = protoBarrier;
        }
    }

    // Ensure we insert a type barrier for reads from typed objects, as type
    // information does not account for the initial undefined/null types.
    if (barrier != BarrierKind::TypeSet && !types->unknown()) {
        MOZ_ASSERT(obj->resultTypeSet());
        switch (obj->resultTypeSet()->forAllClasses(constraints(), IsTypedObjectClass)) {
          case TemporaryTypeSet::ForAllResult::ALL_FALSE:
          case TemporaryTypeSet::ForAllResult::EMPTY:
            break;
          case TemporaryTypeSet::ForAllResult::ALL_TRUE:
          case TemporaryTypeSet::ForAllResult::MIXED:
            barrier = BarrierKind::TypeSet;
            break;
        }
    }

    MConstant* id = constant(StringValue(name));
    MGetPropertyCache* load = MGetPropertyCache::New(alloc(), obj, id,
                                                     barrier == BarrierKind::TypeSet);

    // 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())
        MOZ_TRY(annotateGetPropertyCache(obj, name, load, obj->resultTypeSet(), types));

    current->add(load);
    current->push(load);

    if (load->isEffectful())
        MOZ_TRY(resumeAfter(load));

    MIRType rvalType = types->getKnownMIRType();
    if (barrier != BarrierKind::NoBarrier) {
        rvalType = MIRType::Value;
    } else {
        load->setResultTypeSet(types);
        if (IsNullOrUndefined(rvalType))
            rvalType = MIRType::Value;
    }
    load->setResultType(rvalType);

    if (*pc != JSOP_CALLPROP || !IsNullOrUndefined(obj->type())) {
        // 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.
        MOZ_TRY(pushTypeBarrier(load, types, barrier));
    }

    trackOptimizationSuccess();
    return Ok();
}

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()));
}

AbortReasonOr<Ok>
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 Ok();

    if (!forceInlineCaches()) {
        trackOptimizationAttempt(TrackedStrategy::GetProp_Constant);
        MOZ_TRY(getPropTryConstant(emitted, inner, NameToId(name), types));
        if (*emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetProp_StaticName);
        MOZ_TRY(getStaticName(emitted, &script()->global(), name));
        if (*emitted)
            return Ok();

        trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
        MOZ_TRY(getPropTryCommonGetter(emitted, inner, name, types, /* innerized = */true));
        if (*emitted)
            return Ok();
    }

    // Passing the inner object to GetProperty IC is safe, see the
    // needsOuterizedThisObject check in IsCacheableGetPropCallNative.
    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, alloc(), constraints(),
                                                       inner, name, types);
    trackOptimizationAttempt(TrackedStrategy::GetProp_InlineCache);
    MOZ_TRY(getPropAddCache(inner, name, barrier, types));

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_setprop(PropertyName* name)
{
    MDefinition* value = current->pop();
    MDefinition* obj = convertUnboxedObjects(current->pop());

    bool emitted = false;
    startTrackingOptimizations();
    trackTypeInfo(TrackedTypeSite::Receiver, obj->type(), obj->resultTypeSet());
    trackTypeInfo(TrackedTypeSite::Value, value->type(), value->resultTypeSet());

    // 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);
        MOZ_TRY(setPropTryCommonSetter(&emitted, obj, name, value));
        if (emitted)
            return Ok();

        // Try to emit stores to known binary data blocks
        trackOptimizationAttempt(TrackedStrategy::SetProp_TypedObject);
        MOZ_TRY(setPropTryTypedObject(&emitted, obj, name, value));
        if (emitted)
            return Ok();
    }

    TemporaryTypeSet* objTypes = obj->resultTypeSet();
    bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
                                                 /* canModify = */ true);

    if (!forceInlineCaches()) {
        // Try to emit stores to unboxed objects.
        trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
        MOZ_TRY(setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes));
        if (emitted)
            return Ok();
    }

    if (!forceInlineCaches()) {
        // Try to emit store from definite slots.
        trackOptimizationAttempt(TrackedStrategy::SetProp_DefiniteSlot);
        MOZ_TRY(setPropTryDefiniteSlot(&emitted, obj, name, value, barrier, objTypes));
        if (emitted)
            return Ok();

        // Try to emit a monomorphic/polymorphic store based on baseline caches.
        trackOptimizationAttempt(TrackedStrategy::SetProp_InlineAccess);
        MOZ_TRY(setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes));
        if (emitted)
            return Ok();
    }

    // Emit a polymorphic cache.
    trackOptimizationAttempt(TrackedStrategy::SetProp_InlineCache);
    MOZ_TRY(setPropTryCache(&emitted, obj, name, value, barrier, objTypes));
    MOZ_ASSERT(emitted == true);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
                                   PropertyName* name, MDefinition* value)
{
    MOZ_ASSERT(*emitted == false);

    TemporaryTypeSet* objTypes = obj->resultTypeSet();
    JSFunction* commonSetter = nullptr;
    MDefinition* guard = nullptr;

    {
        Shape* lastProperty = nullptr;
        JSObject* foundProto = nullptr;
        bool isOwnProperty;
        BaselineInspector::ReceiverVector receivers(alloc());
        BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
        if (inspector->commonSetPropFunction(pc, &foundProto, &lastProperty, &commonSetter,
                                              &isOwnProperty,
                                              receivers, convertUnboxedGroups))
        {
            bool canUseTIForSetter = false;
            if (!isOwnProperty) {
                // If it's not an own property, try to use TI to avoid shape guards.
                // For own properties we use the path below.
                canUseTIForSetter = testCommonGetterSetter(objTypes, name, /* isGetter = */ false,
                                                           commonSetter, &guard);
            }
            if (!canUseTIForSetter) {
                // If it's an own property or 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 abort(AbortReason::Alloc);
            }
        } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ false,
                                                              &commonSetter))
        {
            // Try to use TI to guard on this setter.
            if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ false,
                                        commonSetter, &guard))
            {
                return Ok();
            }
        } else {
            // The Baseline IC didn't have any information we can use.
            return Ok();
        }
    }

    // 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.
    MOZ_TRY(setPropTryCommonDOMSetter(emitted, obj, value, commonSetter, objTypes));
    if (*emitted) {
        trackOptimizationOutcome(TrackedOutcome::DOM);
        return Ok();
    }

    // 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 abort(AbortReason::Alloc);

    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(), pc, /* constructing = */ false,
                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
    if (!callInfo.init(current, 1))
        return abort(AbortReason::Alloc);

    // 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 abort(AbortReason::Error);
          case InliningDecision_DontInline:
          case InliningDecision_WarmUpCountTooLow:
            break;
          case InliningDecision_Inline: {
            InliningStatus status;
            MOZ_TRY_VAR(status, inlineScriptedCall(callInfo, commonSetter));
            if (status == InliningStatus_Inlined) {
                *emitted = true;
                return Ok();
            }
          }
        }
    }

    MCall* call;
    MOZ_TRY_VAR(call, makeCallHelper(commonSetter, callInfo));

    current->push(value);
    MOZ_TRY(resumeAfter(call));

    // 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 Ok();
}

AbortReasonOr<Ok>
IonBuilder::setPropTryCommonDOMSetter(bool* emitted, MDefinition* obj,
                                      MDefinition* value, JSFunction* setter,
                                      TemporaryTypeSet* objTypes)
{
    MOZ_ASSERT(*emitted == false);

    DOMObjectKind objKind = DOMObjectKind::Unknown;
    if (!objTypes || !objTypes->isDOMClass(constraints(), &objKind))
        return Ok();

    bool isDOM = false;
    MOZ_TRY_VAR(isDOM, testShouldDOMCall(objTypes, setter, JSJitInfo::Setter));
    if (!isDOM)
        return Ok();

    // Emit SetDOMProperty.
    MOZ_ASSERT(setter->jitInfo()->type() == JSJitInfo::Setter);
    MSetDOMProperty* set = MSetDOMProperty::New(alloc(), setter->jitInfo()->setter, objKind,
                                                obj, value);

    current->add(set);
    current->push(value);

    MOZ_TRY(resumeAfter(set));

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setPropTryTypedObject(bool* emitted, MDefinition* obj,
                                  PropertyName* name, MDefinition* value)
{
    TypedObjectPrediction fieldPrediction;
    size_t fieldOffset;
    size_t fieldIndex;
    if (!typedObjectHasField(obj, name, &fieldOffset, &fieldPrediction, &fieldIndex))
        return Ok();

    switch (fieldPrediction.kind()) {
      case type::Simd:
        // FIXME (bug 894104): store into a MIRType::float32x4 etc
        return Ok();

      case type::Reference:
        return setPropTryReferencePropOfTypedObject(emitted, obj, fieldOffset,
                                                    value, fieldPrediction, name);

      case type::Scalar:
        return setPropTryScalarPropOfTypedObject(emitted, obj, fieldOffset,
                                                 value, fieldPrediction);

      case type::Struct:
      case type::Array:
        return Ok();
    }

    MOZ_CRASH("Unknown kind");
}

AbortReasonOr<Ok>
IonBuilder::setPropTryReferencePropOfTypedObject(bool* emitted,
                                                 MDefinition* obj,
                                                 int32_t fieldOffset,
                                                 MDefinition* value,
                                                 TypedObjectPrediction fieldPrediction,
                                                 PropertyName* name)
{
    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 Ok();

    LinearSum byteOffset(alloc());
    if (!byteOffset.add(fieldOffset))
        return abort(AbortReason::Disable, "Overflow of field offset.");

    return setPropTryReferenceTypedObjectValue(emitted, obj, byteOffset, fieldType, value, name);
}

AbortReasonOr<Ok>
IonBuilder::setPropTryScalarPropOfTypedObject(bool* emitted,
                                              MDefinition* obj,
                                              int32_t fieldOffset,
                                              MDefinition* value,
                                              TypedObjectPrediction fieldPrediction)
{
    // 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 Ok();

    LinearSum byteOffset(alloc());
    if (!byteOffset.add(fieldOffset))
        return abort(AbortReason::Disable, "Overflow of field offet.");

    return setPropTryScalarTypedObjectValue(emitted, obj, byteOffset, fieldType, value);
}

AbortReasonOr<Ok>
IonBuilder::setPropTryDefiniteSlot(bool* emitted, MDefinition* obj,
                                   PropertyName* name, MDefinition* value,
                                   bool barrier, TemporaryTypeSet* objTypes)
{
    MOZ_ASSERT(*emitted == false);

    if (barrier) {
        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
        return Ok();
    }

    uint32_t nfixed;
    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), NameToId(name), &nfixed);
    if (slot == UINT32_MAX)
        return Ok();

    bool writeBarrier = false;
    for (size_t i = 0; i < obj->resultTypeSet()->getObjectCount(); i++) {
        TypeSet::ObjectKey* key = obj->resultTypeSet()->getObject(i);
        if (!key)
            continue;

        HeapTypeSetKey property = key->property(NameToId(name));
        if (property.nonWritable(constraints())) {
            trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
            return Ok();
        }
        writeBarrier |= property.needsBarrier(constraints());
    }

    if (needsPostBarrier(value))
        current->add(MPostWriteBarrier::New(alloc(), obj, value));

    MInstruction* store;
    if (slot < nfixed) {
        store = MStoreFixedSlot::New(alloc(), obj, slot, value);
        if (writeBarrier)
            store->toStoreFixedSlot()->setNeedsBarrier();
    } else {
        MInstruction* slots = MSlots::New(alloc(), obj);
        current->add(slots);

        store = MStoreSlot::New(alloc(), slots, slot - nfixed, value);
        if (writeBarrier)
            store->toStoreSlot()->setNeedsBarrier();
    }

    current->add(store);
    current->push(value);

    MOZ_TRY(resumeAfter(store));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

MInstruction*
IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                 MDefinition* value)
{
    size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
    MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
    current->add(scaledOffset);

    return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(),
                             scaledOffset, unboxedType, value);
}

MInstruction*
IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset,
                              MDefinition* scaledOffset, JSValueType unboxedType,
                              MDefinition* value, bool preBarrier /* = 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;
}

AbortReasonOr<Ok>
IonBuilder::setPropTryUnboxed(bool* emitted, MDefinition* obj,
                              PropertyName* name, MDefinition* value,
                              bool barrier, TemporaryTypeSet* objTypes)
{
    MOZ_ASSERT(*emitted == false);

    if (barrier) {
        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
        return Ok();
    }

    JSValueType unboxedType;
    uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), NameToId(name), &unboxedType);
    if (offset == UINT32_MAX)
        return Ok();

    if (obj->type() != MIRType::Object) {
        MGuardObject* guard = MGuardObject::New(alloc(), obj);
        current->add(guard);
        obj = guard;
    }

    MInstruction* store = storeUnboxedProperty(obj, offset, unboxedType, value);

    current->push(value);

    MOZ_TRY(resumeAfter(store));

    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
                                   PropertyName* name, MDefinition* value,
                                   bool barrier, TemporaryTypeSet* objTypes)
{
    MOZ_ASSERT(*emitted == false);

    if (barrier) {
        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
        return Ok();
    }

    BaselineInspector::ReceiverVector receivers(alloc());
    BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
    if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
        return abort(AbortReason::Alloc);

    if (!canInlinePropertyOpShapes(receivers))
        return Ok();

    obj = convertUnboxedObjects(obj, convertUnboxedGroups);

    if (receivers.length() == 1) {
        if (!receivers[0].group) {
            // 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);

            if (needsPostBarrier(value))
                current->add(MPostWriteBarrier::New(alloc(), obj, value));

            bool needsPreBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
            MOZ_TRY(storeSlot(obj, shape, value, needsPreBarrier));

            trackOptimizationOutcome(TrackedOutcome::Monomorphic);
            *emitted = true;
            return Ok();
        }

        if (receivers[0].shape) {
            // 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);

            if (needsPostBarrier(value))
                current->add(MPostWriteBarrier::New(alloc(), obj, value));

            bool needsPreBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
            MOZ_TRY(storeSlot(expando, shape, value, needsPreBarrier));

            trackOptimizationOutcome(TrackedOutcome::Monomorphic);
            *emitted = true;
            return Ok();
        }

        // Monomorphic store to an unboxed object.
        spew("Inlining monomorphic unboxed SETPROP");

        ObjectGroup* group = receivers[0].group;
        if (!objTypes->hasType(TypeSet::ObjectType(group)))
            return Ok();

        obj = addGroupGuard(obj, group, Bailout_ShapeGuard);

        if (needsPostBarrier(value))
            current->add(MPostWriteBarrier::New(alloc(), obj, value));

        const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name);
        MInstruction* store = storeUnboxedProperty(obj, property->offset, property->type, value);

        current->push(value);

        MOZ_TRY(resumeAfter(store));

        trackOptimizationOutcome(TrackedOutcome::Monomorphic);
        *emitted = true;
        return Ok();
    }

    MOZ_ASSERT(receivers.length() > 1);
    spew("Inlining polymorphic SETPROP");

    if (Shape* propShape = PropertyShapesHaveSameSlot(receivers, NameToId(name))) {
        obj = addGuardReceiverPolymorphic(obj, receivers);
        if (!obj)
            return abort(AbortReason::Alloc);

        if (needsPostBarrier(value))
            current->add(MPostWriteBarrier::New(alloc(), obj, value));

        bool needsPreBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
        MOZ_TRY(storeSlot(obj, propShape, value, needsPreBarrier));

        trackOptimizationOutcome(TrackedOutcome::Polymorphic);
        *emitted = true;
        return Ok();
    }

    if (needsPostBarrier(value))
        current->add(MPostWriteBarrier::New(alloc(), obj, value));

    MSetPropertyPolymorphic* ins = MSetPropertyPolymorphic::New(alloc(), obj, value, name);
    current->add(ins);
    current->push(value);

    for (size_t i = 0; i < receivers.length(); i++) {
        Shape* propShape = nullptr;
        if (receivers[i].shape) {
            propShape = receivers[i].shape->searchLinear(NameToId(name));
            MOZ_ASSERT(propShape);
        }
        if (!ins->addReceiver(receivers[i], propShape))
            return abort(AbortReason::Alloc);
    }

    if (objTypes->propertyNeedsBarrier(constraints(), NameToId(name)))
        ins->setNeedsBarrier();

    MOZ_TRY(resumeAfter(ins));

    trackOptimizationOutcome(TrackedOutcome::Polymorphic);
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::setPropTryCache(bool* emitted, MDefinition* obj,
                            PropertyName* name, MDefinition* value,
                            bool barrier, TemporaryTypeSet* objTypes)
{
    MOZ_ASSERT(*emitted == false);

    bool strict = IsStrictSetPC(pc);

    MConstant* id = constant(StringValue(name));
    MSetPropertyCache* ins = MSetPropertyCache::New(alloc(), obj, id, value, strict,
                                                    needsPostBarrier(value), barrier,
                                                    /* guardHoles = */ false);
    current->add(ins);
    current->push(value);

    MOZ_TRY(resumeAfter(ins));

    trackOptimizationSuccess();
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_delprop(PropertyName* name)
{
    MDefinition* obj = current->pop();

    bool strict = JSOp(*pc) == JSOP_STRICTDELPROP;
    MInstruction* ins = MDeleteProperty::New(alloc(), obj, name, strict);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_delelem()
{
    MDefinition* index = current->pop();
    MDefinition* obj = current->pop();

    bool strict = JSOp(*pc) == JSOP_STRICTDELELEM;
    MDeleteElement* ins = MDeleteElement::New(alloc(), obj, index, strict);
    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_regexp(RegExpObject* reobj)
{
    MOZ_ASSERT(!IsInsideNursery(reobj));

    // Determine this while we're still on the main thread to avoid races.
    bool hasShared = reobj->hasShared();

    MRegExp* regexp = MRegExp::New(alloc(), constraints(), reobj, hasShared);
    current->add(regexp);
    current->push(regexp);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_object(JSObject* obj)
{
    if (options.cloneSingletons()) {
        MCloneLiteral* clone = MCloneLiteral::New(alloc(), constant(ObjectValue(*obj)));
        current->add(clone);
        current->push(clone);
        return resumeAfter(clone);
    }

    compartment->setSingletonsAsValues();
    pushConstant(ObjectValue(*obj));
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_classconstructor()
{
    MClassConstructor* constructor = MClassConstructor::New(alloc(), pc);
    current->add(constructor);
    current->push(constructor);
    return resumeAfter(constructor);
}

AbortReasonOr<Ok>
IonBuilder::jsop_lambda(JSFunction* fun)
{
    MOZ_ASSERT(usesEnvironmentChain());
    MOZ_ASSERT(!fun->isArrow());

    if (IsAsmJSModule(fun))
        return abort(AbortReason::Disable, "Lambda is an asm.js module function");

    MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun);
    current->add(cst);
    MLambda* ins = MLambda::New(alloc(), constraints(), current->environmentChain(), cst);
    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_lambda_arrow(JSFunction* fun)
{
    MOZ_ASSERT(usesEnvironmentChain());
    MOZ_ASSERT(fun->isArrow());
    MOZ_ASSERT(!fun->isNative());

    MDefinition* newTargetDef = current->pop();
    MConstant* cst = MConstant::NewConstraintlessObject(alloc(), fun);
    current->add(cst);
    MLambdaArrow* ins = MLambdaArrow::New(alloc(), constraints(), current->environmentChain(),
                                          newTargetDef, cst);
    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_setfunname(uint8_t prefixKind)
{
    MDefinition* name = current->pop();
    MDefinition* fun = current->pop();
    MOZ_ASSERT(fun->type() == MIRType::Object);

    MSetFunName* ins = MSetFunName::New(alloc(), fun, name, prefixKind);

    current->add(ins);
    current->push(fun);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_pushlexicalenv(uint32_t index)
{
    MOZ_ASSERT(usesEnvironmentChain());

    LexicalScope* scope = &script()->getScope(index)->as<LexicalScope>();
    MNewLexicalEnvironmentObject* ins =
        MNewLexicalEnvironmentObject::New(alloc(), current->environmentChain(), scope);

    current->add(ins);
    current->setEnvironmentChain(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_copylexicalenv(bool copySlots)
{
    MOZ_ASSERT(usesEnvironmentChain());

    MCopyLexicalEnvironmentObject* ins =
        MCopyLexicalEnvironmentObject::New(alloc(), current->environmentChain(), copySlots);

    current->add(ins);
    current->setEnvironmentChain(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_setarg(uint32_t arg)
{
    // 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_IF(script()->hasBaselineScript(),
                  script()->baselineScript()->modifiesArguments());
    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 Ok();
    }

    // :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(AbortReason::Disable, "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 Ok();
    }

    // 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 Ok();
}

AbortReasonOr<Ok>
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(usesEnvironmentChain());

    // Bake the name pointer into the MDefVar.
    MDefVar* defvar = MDefVar::New(alloc(), name, attrs, current->environmentChain());
    current->add(defvar);

    return resumeAfter(defvar);
}

AbortReasonOr<Ok>
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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_deffun(uint32_t index)
{
    MOZ_ASSERT(usesEnvironmentChain());

    MDefFun* deffun = MDefFun::New(alloc(), current->pop(), current->environmentChain());
    current->add(deffun);

    return resumeAfter(deffun);
}

AbortReasonOr<Ok>
IonBuilder::jsop_throwsetconst()
{
    current->peek(-1)->setImplicitlyUsedUnchecked();
    MInstruction* lexicalError = MThrowRuntimeLexicalError::New(alloc(), JSMSG_BAD_CONST_ASSIGN);
    current->add(lexicalError);
    return resumeAfter(lexicalError);
}

AbortReasonOr<Ok>
IonBuilder::jsop_checklexical()
{
    uint32_t slot = info().localSlot(GET_LOCALNO(pc));
    MDefinition* lexical;
    MOZ_TRY_VAR(lexical, addLexicalCheck(current->getSlot(slot)));
    current->setSlot(slot, lexical);
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_checkaliasedlexical(EnvironmentCoordinate ec)
{
    MDefinition* let;
    MOZ_TRY_VAR(let, addLexicalCheck(getAliasedVar(ec)));

    jsbytecode* nextPc = pc + JSOP_CHECKALIASEDLEXICAL_LENGTH;
    MOZ_ASSERT(JSOp(*nextPc) == JSOP_GETALIASEDVAR ||
               JSOp(*nextPc) == JSOP_SETALIASEDVAR ||
               JSOp(*nextPc) == JSOP_THROWSETALIASEDCONST);
    MOZ_ASSERT(ec == EnvironmentCoordinate(nextPc));

    // If 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 Ok();
}

AbortReasonOr<Ok>
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 Ok();
    }

    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 Ok();
    }

    // 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 Ok();
    }

    // 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 Ok();
    }

    // Beyond this point we may need to access non-syntactic global. Ion doesn't
    // currently support this so just abort.
    if (script()->hasNonSyntacticScope())
        return abort(AbortReason::Disable, "JSOP_FUNCTIONTHIS would need non-syntactic global");

    if (IsNullOrUndefined(def->type())) {
        LexicalEnvironmentObject* globalLexical = &script()->global().lexicalEnvironment();
        pushConstant(globalLexical->thisValue());
        return Ok();
    }

    MComputeThis* thisObj = MComputeThis::New(alloc(), def);
    current->add(thisObj);
    current->push(thisObj);

    return resumeAfter(thisObj);
}

AbortReasonOr<Ok>
IonBuilder::jsop_globalthis()
{
    if (script()->hasNonSyntacticScope()) {
        // 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(AbortReason::Disable, "JSOP_GLOBALTHIS in script with non-syntactic scope");
    }

    LexicalEnvironmentObject* globalLexical = &script()->global().lexicalEnvironment();
    pushConstant(globalLexical->thisValue());
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_typeof()
{
    MDefinition* input = current->pop();
    MTypeOf* ins = MTypeOf::New(alloc(), input, input->type());

    ins->cacheInputMaybeCallableOrEmulatesUndefined(constraints());

    current->add(ins);
    current->push(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_toasyncgen()
{
    MDefinition* unwrapped = current->pop();
    MOZ_ASSERT(unwrapped->type() == MIRType::Object);

    MToAsyncGen* ins = MToAsyncGen::New(alloc(), unwrapped);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_toasynciter()
{
    MDefinition* unwrapped = current->pop();
    MOZ_ASSERT(unwrapped->type() == MIRType::Object);

    MToAsyncIter* ins = MToAsyncIter::New(alloc(), unwrapped);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_toid()
{
    // No-op if the index is trivally convertable to an id.
    MIRType type = current->peek(-1)->type();
    if (type == MIRType::Int32 || type == MIRType::String || type == MIRType::Symbol)
        return Ok();

    MDefinition* index = current->pop();
    MToId* ins = MToId::New(alloc(), index);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_iter()
{
    MDefinition* obj = current->pop();
    MInstruction* ins = MGetIteratorCache::New(alloc(), obj);

    if (!outermostBuilder()->iterators_.append(ins))
        return abort(AbortReason::Alloc);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_itermore()
{
    MDefinition* iter = current->peek(-1);
    MInstruction* ins = MIteratorMore::New(alloc(), iter);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_isnoiter()
{
    MDefinition* def = current->peek(-1);
    MOZ_ASSERT(def->isIteratorMore());

    MInstruction* ins = MIsNoIter::New(alloc(), def);
    current->add(ins);
    current->push(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_iterend()
{
    MDefinition* iter = current->pop();
    MInstruction* ins = MIteratorEnd::New(alloc(), iter);

    current->add(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_iternext()
{
    MDefinition* def = current->pop();
    MOZ_ASSERT(def->type() == MIRType::Value);

    // The value must be a string.
    MInstruction* unbox = MUnbox::New(alloc(), def, MIRType::String, MUnbox::Infallible);
    current->add(unbox);
    current->push(unbox);

    return Ok();
}

MDefinition*
IonBuilder::walkEnvironmentChain(unsigned hops)
{
    MDefinition* env = current->getSlot(info().environmentChainSlot());

    for (unsigned i = 0; i < hops; i++) {
        MInstruction* ins = MEnclosingEnvironment::New(alloc(), env);
        current->add(ins);
        env = ins;
    }

    return env;
}

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;
}

AbortReasonOr<Ok>
IonBuilder::jsop_getaliasedvar(EnvironmentCoordinate ec)
{
    JSObject* call = nullptr;
    if (hasStaticEnvironmentObject(ec, &call) && call) {
        PropertyName* name = EnvironmentCoordinateName(envCoordinateNameCache, script(), pc);
        bool emitted = false;
        MOZ_TRY(getStaticName(&emitted, call, name, takeLexicalCheck()));
        if (emitted)
            return Ok();
    }

    // See jsop_checkaliasedlexical.
    MDefinition* load = takeLexicalCheck();
    if (!load)
        load = getAliasedVar(ec);
    current->push(load);

    TemporaryTypeSet* types = bytecodeTypes(pc);
    return pushTypeBarrier(load, types, BarrierKind::TypeSet);
}

AbortReasonOr<Ok>
IonBuilder::jsop_setaliasedvar(EnvironmentCoordinate ec)
{
    JSObject* call = nullptr;
    if (hasStaticEnvironmentObject(ec, &call)) {
        uint32_t depth = current->stackDepth() + 1;
        if (depth > current->nslots()) {
            if (!current->increaseSlots(depth - current->nslots()))
                return abort(AbortReason::Alloc);
        }
        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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_in()
{
    MDefinition* obj = convertUnboxedObjects(current->pop());
    MDefinition* id = current->pop();

    if (!forceInlineCaches()) {
        bool emitted = false;

        MOZ_TRY(inTryDense(&emitted, obj, id));
        if (emitted)
            return Ok();

        MOZ_TRY(hasTryNotDefined(&emitted, obj, id, /* ownProperty = */ false));
        if (emitted)
            return Ok();

        MOZ_TRY(hasTryDefiniteSlotOrUnboxed(&emitted, obj, id));
        if (emitted)
            return Ok();
    }

    MInCache* ins = MInCache::New(alloc(), id, obj);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
{
    MOZ_ASSERT(!*emitted);

    if (shouldAbortOnPreliminaryGroups(obj))
        return Ok();

    if (!ElementAccessIsDenseNative(constraints(), obj, id))
        return Ok();

    bool hasExtraIndexedProperty;
    MOZ_TRY_VAR(hasExtraIndexedProperty, ElementAccessHasExtraIndexedProperty(this, obj));
    if (hasExtraIndexedProperty)
        return Ok();

    *emitted = true;

    bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);

    // 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);
    current->add(elements);

    MInstruction* initLength = initializedLength(obj, elements);

    // If there are no holes, speculate the InArray check will not fail.
    if (!needsHoleCheck && !failedBoundsCheck_) {
        addBoundsCheck(idInt32, initLength, BoundsCheckKind::UnusedIndex);
        pushConstant(BooleanValue(true));
        return Ok();
    }

    // Check if id < initLength and elem[id] not a hole.
    MInArray* ins = MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck);

    current->add(ins);
    current->push(ins);

    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::hasTryNotDefined(bool* emitted, MDefinition* obj, MDefinition* id, bool ownProperty)
{
    // 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 Ok();

    if (propId != IdToTypeId(propId))
        return Ok();

    bool res;
    MOZ_TRY_VAR(res, testNotDefinedProperty(obj, propId, ownProperty));
    if (!res)
        return Ok();

    *emitted = true;

    pushConstant(BooleanValue(false));
    obj->setImplicitlyUsedUnchecked();
    id->setImplicitlyUsedUnchecked();
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::hasTryDefiniteSlotOrUnboxed(bool* emitted, MDefinition* obj, MDefinition* id)
{
    // Fold |id in obj| to |true|, when obj definitely contains a property with
    // that name.
    MOZ_ASSERT(!*emitted);

    if (obj->type() != MIRType::Object)
        return Ok();

    MConstant* idConst = id->maybeConstantValue();
    jsid propId;
    if (!idConst || !ValueToIdPure(idConst->toJSValue(), &propId))
        return Ok();

    if (propId != IdToTypeId(propId))
        return Ok();

    // Try finding a native definite slot first.
    uint32_t nfixed;
    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), propId, &nfixed);
    if (slot == UINT32_MAX) {
        // Check for unboxed object properties next.
        JSValueType unboxedType;
        uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), propId, &unboxedType);
        if (offset == UINT32_MAX)
            return Ok();
    }

    *emitted = true;

    pushConstant(BooleanValue(true));
    obj->setImplicitlyUsedUnchecked();
    id->setImplicitlyUsedUnchecked();
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_hasown()
{
    MDefinition* obj = convertUnboxedObjects(current->pop());
    MDefinition* id = current->pop();

    if (!forceInlineCaches()) {
        bool emitted = false;

        MOZ_TRY(hasTryNotDefined(&emitted, obj, id, /* ownProperty = */ true));
        if (emitted)
            return Ok();

        MOZ_TRY(hasTryDefiniteSlotOrUnboxed(&emitted, obj, id));
        if (emitted)
            return Ok();
    }

    MHasOwnCache* ins = MHasOwnCache::New(alloc(), obj, id);
    current->add(ins);
    current->push(ins);

    MOZ_TRY(resumeAfter(ins));
    return Ok();
}

AbortReasonOr<bool>
IonBuilder::hasOnProtoChain(TypeSet::ObjectKey* key, JSObject* protoObject, bool* onProto)
{
    MOZ_ASSERT(protoObject);

    while (true) {
        if (!alloc().ensureBallast())
            return abort(AbortReason::Alloc);

        if (!key->hasStableClassAndProto(constraints()) || !key->clasp()->isNative())
            return false;

        JSObject* proto = checkNurseryObject(key->proto().toObjectOrNull());
        if (!proto) {
            *onProto = false;
            return true;
        }

        if (proto == protoObject) {
            *onProto = true;
            return true;
        }

        key = TypeSet::ObjectKey::get(proto);
    }

    MOZ_CRASH("Unreachable");
}

AbortReasonOr<Ok>
IonBuilder::tryFoldInstanceOf(bool* emitted, MDefinition* lhs, JSObject* protoObject)
{
    // Try to fold the js::IsDelegate part of the instanceof operation.
    MOZ_ASSERT(*emitted == false);

    if (!lhs->mightBeType(MIRType::Object)) {
        // If the lhs is a primitive, the result is false.
        lhs->setImplicitlyUsedUnchecked();
        pushConstant(BooleanValue(false));
        *emitted = true;
        return Ok();
    }

    TemporaryTypeSet* lhsTypes = lhs->resultTypeSet();
    if (!lhsTypes || lhsTypes->unknownObject())
        return Ok();

    // 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 checkSucceeded;
        bool isInstance;
        MOZ_TRY_VAR(checkSucceeded, hasOnProtoChain(key, protoObject, &isInstance));
        if (!checkSucceeded)
            return Ok();

        if (isFirst) {
            knownIsInstance = isInstance;
            isFirst = false;
        } else if (knownIsInstance != isInstance) {
            // Some of the objects have protoObject on their proto chain and
            // others don't, so we can't optimize this.
            return Ok();
        }
    }

    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);
        *emitted = true;
        return Ok();
    }

    lhs->setImplicitlyUsedUnchecked();
    pushConstant(BooleanValue(knownIsInstance));
    *emitted = true;
    return Ok();
}

AbortReasonOr<Ok>
IonBuilder::jsop_instanceof()
{
    MDefinition* rhs = current->pop();
    MDefinition* obj = current->pop();
    bool emitted = false;

    // 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();

        MOZ_TRY(tryFoldInstanceOf(&emitted, obj, protoObject));
        if (emitted)
            return Ok();

        MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject);

        current->add(ins);
        current->push(ins);

        return resumeAfter(ins);
    } while (false);

    // 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);

        MOZ_TRY(tryFoldInstanceOf(&emitted, obj, protoObject));
        if (emitted)
            return Ok();

        MInstanceOf* ins = MInstanceOf::New(alloc(), obj, protoObject);
        current->add(ins);
        current->push(ins);
        return resumeAfter(ins);
    } while (false);

    MCallInstanceOf* ins = MCallInstanceOf::New(alloc(), obj, rhs);

    current->add(ins);
    current->push(ins);

    return resumeAfter(ins);
}

AbortReasonOr<Ok>
IonBuilder::jsop_debugger()
{
    MDebugger* debugger = MDebugger::New(alloc());
    current->add(debugger);

    // 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);
}

AbortReasonOr<Ok>
IonBuilder::jsop_implicitthis(PropertyName* name)
{
    MOZ_ASSERT(usesEnvironmentChain());

    MImplicitThis* implicitThis = MImplicitThis::New(alloc(), current->environmentChain(), name);
    current->add(implicitThis);
    current->push(implicitThis);

    return resumeAfter(implicitThis);
}

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, BoundsCheckKind kind)
{
    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();

    if (kind == BoundsCheckKind::IsLoad && JitOptions.spectreIndexMasking) {
        check = MSpectreMaskIndex::New(alloc(), check, length);
        current->add(check);
    }

    return check;
}

MInstruction*
IonBuilder::addShapeGuard(MDefinition* obj, Shape* const shape, BailoutKind bailoutKind)
{
    MGuardShape* guard = MGuardShape::New(alloc(), obj, shape, bailoutKind);
    current->add(guard);

    // If 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 || !IsTypedObjectClass(group->clasp()))
            return TypedObjectPrediction();

        if (!TypeSet::ObjectKey::get(group)->hasStableClassAndProto(constraints()))
            return TypedObjectPrediction();

        out.addDescr(group->typeDescr());
    }

    return out;
}

MDefinition*
IonBuilder::loadTypedObjectType(MDefinition* typedObj)
{
    // 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).
AbortReasonOr<Ok>
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))
            return abort(AbortReason::Disable, "Overflow/underflow on type object offset.");

        *owner = ins->owner();
        return Ok();
    }

    *owner = typedObj;
    return Ok();
}

// 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.
AbortReasonOr<Ok>
IonBuilder::loadTypedObjectElements(MDefinition* typedObj,
                                    const LinearSum& baseByteOffset,
                                    uint32_t scale,
                                    MDefinition** ownerElements,
                                    MDefinition** ownerScaledOffset,
                                    int32_t* ownerByteAdjustment)
{
    MDefinition* owner;
    LinearSum ownerByteOffset(alloc());
    MOZ_TRY(loadTypedObjectData(typedObj, &owner, &ownerByteOffset));

    if (!ownerByteOffset.add(baseByteOffset))
        return abort(AbortReason::Disable, "Overflow after adding the base offset.");

    TemporaryTypeSet* ownerTypes = owner->resultTypeSet();
    const Class* clasp = ownerTypes ? ownerTypes->getKnownClass(constraints()) : nullptr;
    if (clasp && IsInlineTypedObjectClass(clasp)) {
        // Perform the load directly from the owner pointer.
        if (!ownerByteOffset.add(InlineTypedObject::offsetOfDataStart()))
            return abort(AbortReason::Disable, "Overflow after adding the data start.");
        *ownerElements = owner;
    } else {
        bool definitelyOutline = clasp && IsOutlineTypedObjectClass(clasp);
        *ownerElements = MTypedObjectElements::New(alloc(), owner, definitelyOutline);
        current->add((*ownerElements)->toInstruction());
    }

    // Extract the constant adjustment from the byte offset.
    *ownerByteAdjustment = ownerByteOffset.constant();
    int32_t negativeAdjustment;
    if (!SafeSub(0, *ownerByteAdjustment, &negativeAdjustment))
        return abort(AbortReason::Disable);
    if (!ownerByteOffset.add(negativeAdjustment))
        return abort(AbortReason::Disable);

    // 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());
    }
    return Ok();
}

// 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;
}

AbortReasonOr<Ok>
IonBuilder::setPropTryScalarTypedObjectValue(bool* emitted,
                                             MDefinition* typedObj,
                                             const LinearSum& byteOffset,
                                             ScalarTypeDescr::Type type,
                                             MDefinition* value)
{
    MOZ_ASSERT(!*emitted);

    // Find location within the owner object.
    MDefinition* elements;
    MDefinition* scaledOffset;
    int32_t adjustment;
    uint32_t alignment = ScalarTypeDescr::alignment(type);
    MOZ_TRY(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);
    current->push(value);

    trackOptimizationSuccess();
    *emitted = true;
    return resumeAfter(store);
}

AbortReasonOr<Ok>
IonBuilder::setPropTryReferenceTypedObjectValue(bool* emitted,
                                                MDefinition* typedObj,
                                                const LinearSum& byteOffset,
                                                ReferenceTypeDescr::Type type,
                                                MDefinition* value,
                                                PropertyName* name)
{
    MOZ_ASSERT(!*emitted);

    // 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 Ok();
        }
    }

    // Find location within the owner object.
    MDefinition* elements;
    MDefinition* scaledOffset;
    int32_t adjustment;
    uint32_t alignment = ReferenceTypeDescr::alignment(type);
    MOZ_TRY(loadTypedObjectElements(typedObj, byteOffset, alignment,
                                    &elements, &scaledOffset, &adjustment));

    MInstruction* store = nullptr;  // 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);
    current->push(value);

    trackOptimizationSuccess();
    *emitted = true;
    return resumeAfter(store);
}

JSObject*
IonBuilder::checkNurseryObject(JSObject* obj)
{
    // If we try to use any nursery pointers during compilation, make sure that
    // the active 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->zone()->setMinorGCShouldCancelIonCompilations();
        IonBuilder* builder = this;
        while (builder) {
            builder->setNotSafeForMinorGC();
            builder = builder->callerBuilder_;
        }
    }

    return obj;
}

MConstant*
IonBuilder::constant(const Value& v)
{
    MOZ_ASSERT(!v.isString() || v.toString()->isAtom(),
               "Handle non-atomized strings outside IonBuilder.");

    if (v.isObject())
        checkNurseryObject(&v.toObject());

    MConstant* c = MConstant::New(alloc(), v, constraints());
    current->add(c);
    return c;
}

MConstant*
IonBuilder::constantInt(int32_t i)
{
    return constant(Int32Value(i));
}

MInstruction*
IonBuilder::initializedLength(MDefinition* obj, MDefinition* elements)
{
    MInstruction* res = MInitializedLength::New(alloc(), elements);
    current->add(res);
    return res;
}

MInstruction*
IonBuilder::setInitializedLength(MDefinition* obj, size_t count)
{
    MOZ_ASSERT(count);

    // MSetInitializedLength takes the index of the last element, rather
    // than the count itself.
    MInstruction* elements = MElements::New(alloc(), obj, /* unboxed = */ false);
    current->add(elements);
    MInstruction* res =
        MSetInitializedLength::New(alloc(), elements, constant(Int32Value(count - 1)));
    current->add(res);
    return res;
}

MDefinition*
IonBuilder::getCallee()
{
    if (inliningDepth_ == 0) {
        MInstruction* callee = MCallee::New(alloc());
        current->add(callee);
        return callee;
    }

    return inlineCallInfo_->fun();
}

AbortReasonOr<MDefinition*>
IonBuilder::addLexicalCheck(MDefinition* input)
{
    MOZ_ASSERT(JSOp(*pc) == JSOP_CHECKLEXICAL ||
               JSOp(*pc) == JSOP_CHECKALIASEDLEXICAL ||
               JSOp(*pc) == JSOP_GETIMPORT);

    MInstruction* lexicalCheck;

    // If 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);
        MOZ_TRY(resumeAfter(lexicalCheck));
        return constant(UndefinedValue());
    }

    if (input->type() == MIRType::Value) {
        lexicalCheck = MLexicalCheck::New(alloc(), input);
        current->add(lexicalCheck);
        if (failedLexicalCheck_)
            lexicalCheck->setNotMovableUnchecked();
        return lexicalCheck;
    }

    return input;
}

MDefinition*
IonBuilder::convertToBoolean(MDefinition* input)
{
    // 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);
}
