/* -*- 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/IonCaches.h"

#include "mozilla/SizePrintfMacros.h"
#include "mozilla/TemplateLib.h"

#include "jstypes.h"

#include "builtin/TypedObject.h"
#include "jit/BaselineIC.h"
#include "jit/Ion.h"
#include "jit/JitcodeMap.h"
#include "jit/JitSpewer.h"
#include "jit/Linker.h"
#include "jit/Lowering.h"
#ifdef JS_ION_PERF
# include "jit/PerfSpewer.h"
#endif
#include "jit/VMFunctions.h"
#include "js/Proxy.h"
#include "vm/Shape.h"

#include "jit/JitFrames-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Shape-inl.h"

using namespace js;
using namespace js::jit;

using mozilla::tl::FloorLog2;

typedef Rooted<TypedArrayObject*> RootedTypedArrayObject;

void
CodeLocationJump::repoint(JitCode* code, MacroAssembler* masm)
{
    MOZ_ASSERT(state_ == Relative);
    size_t new_off = (size_t)raw_;
#ifdef JS_SMALL_BRANCH
    size_t jumpTableEntryOffset = reinterpret_cast<size_t>(jumpTableEntry_);
#endif
    if (masm != nullptr) {
#ifdef JS_CODEGEN_X64
        MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
        new_off = (uintptr_t)raw_;
#ifdef JS_SMALL_BRANCH
        jumpTableEntryOffset = masm->actualIndex(jumpTableEntryOffset);
#endif
    }
    raw_ = code->raw() + new_off;
#ifdef JS_SMALL_BRANCH
    jumpTableEntry_ = Assembler::PatchableJumpAddress(code, (size_t) jumpTableEntryOffset);
#endif
    setAbsolute();
}

void
CodeLocationLabel::repoint(JitCode* code, MacroAssembler* masm)
{
     MOZ_ASSERT(state_ == Relative);
     size_t new_off = (size_t)raw_;
     if (masm != nullptr) {
#ifdef JS_CODEGEN_X64
        MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
        new_off = (uintptr_t)raw_;
     }
     MOZ_ASSERT(new_off < code->instructionsSize());

     raw_ = code->raw() + new_off;
     setAbsolute();
}

void
CodeOffsetJump::fixup(MacroAssembler* masm)
{
#ifdef JS_SMALL_BRANCH
     jumpTableIndex_ = masm->actualIndex(jumpTableIndex_);
#endif
}

const char*
IonCache::CacheName(IonCache::Kind kind)
{
    static const char * const names[] =
    {
#define NAME(x) #x,
        IONCACHE_KIND_LIST(NAME)
#undef NAME
    };
    return names[kind];
}

IonCache::LinkStatus
IonCache::linkCode(JSContext* cx, MacroAssembler& masm, IonScript* ion, JitCode** code)
{
    Linker linker(masm);
    *code = linker.newCode<CanGC>(cx, ION_CODE);
    if (!*code)
        return LINK_ERROR;

    if (ion->invalidated())
        return CACHE_FLUSHED;

    return LINK_GOOD;
}

const size_t IonCache::MAX_STUBS = 16;

// Helper class which encapsulates logic to attach a stub to an IC by hooking
// up rejoins and next stub jumps.
//
// The simplest stubs have a single jump to the next stub and look like the
// following:
//
//    branch guard NEXTSTUB
//    ... IC-specific code ...
//    jump REJOIN
//
// This corresponds to:
//
//    attacher.branchNextStub(masm, ...);
//    ... emit IC-specific code ...
//    attacher.jumpRejoin(masm);
//
// Whether the stub needs multiple next stub jumps look like:
//
//   branch guard FAILURES
//   ... IC-specific code ...
//   branch another-guard FAILURES
//   ... IC-specific code ...
//   jump REJOIN
//   FAILURES:
//   jump NEXTSTUB
//
// This corresponds to:
//
//   Label failures;
//   masm.branchX(..., &failures);
//   ... emit IC-specific code ...
//   masm.branchY(..., failures);
//   ... emit more IC-specific code ...
//   attacher.jumpRejoin(masm);
//   masm.bind(&failures);
//   attacher.jumpNextStub(masm);
//
// A convenience function |branchNextStubOrLabel| is provided in the case that
// the stub sometimes has multiple next stub jumps and sometimes a single
// one. If a non-nullptr label is passed in, a |branchPtr| will be made to
// that label instead of a |branchPtrWithPatch| to the next stub.
class IonCache::StubAttacher
{
  protected:
    bool hasNextStubOffset_ : 1;
    bool hasStubCodePatchOffset_ : 1;

    IonCache& cache_;

    CodeLocationLabel rejoinLabel_;
    CodeOffsetJump nextStubOffset_;
    CodeOffsetJump rejoinOffset_;
    CodeOffset stubCodePatchOffset_;

  public:
    explicit StubAttacher(IonCache& cache)
      : hasNextStubOffset_(false),
        hasStubCodePatchOffset_(false),
        cache_(cache),
        rejoinLabel_(cache.rejoinLabel_),
        nextStubOffset_(),
        rejoinOffset_(),
        stubCodePatchOffset_()
    { }

    // Value used instead of the JitCode self-reference of generated
    // stubs. This value is needed for marking calls made inside stubs. This
    // value would be replaced by the attachStub function after the allocation
    // of the JitCode. The self-reference is used to keep the stub path alive
    // even if the IonScript is invalidated or if the IC is flushed.
    static const ImmPtr STUB_ADDR;

    template <class T1, class T2>
    void branchNextStub(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2) {
        MOZ_ASSERT(!hasNextStubOffset_);
        RepatchLabel nextStub;
        nextStubOffset_ = masm.branchPtrWithPatch(cond, op1, op2, &nextStub);
        hasNextStubOffset_ = true;
        masm.bind(&nextStub);
    }

    template <class T1, class T2>
    void branchNextStubOrLabel(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2,
                               Label* label)
    {
        if (label != nullptr)
            masm.branchPtr(cond, op1, op2, label);
        else
            branchNextStub(masm, cond, op1, op2);
    }

    void jumpRejoin(MacroAssembler& masm) {
        RepatchLabel rejoin;
        rejoinOffset_ = masm.jumpWithPatch(&rejoin);
        masm.bind(&rejoin);
    }

    void jumpNextStub(MacroAssembler& masm) {
        MOZ_ASSERT(!hasNextStubOffset_);
        RepatchLabel nextStub;
        nextStubOffset_ = masm.jumpWithPatch(&nextStub);
        hasNextStubOffset_ = true;
        masm.bind(&nextStub);
    }

    void pushStubCodePointer(MacroAssembler& masm) {
        // Push the JitCode pointer for the stub we're generating.
        // WARNING:
        // WARNING: If JitCode ever becomes relocatable, the following code is incorrect.
        // WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr.
        // WARNING: This location will be patched with the pointer of the generated stub,
        // WARNING: such as it can be marked when a call is made with this stub. Be aware
        // WARNING: that ICs are not marked and so this stub will only be kept alive iff
        // WARNING: it is on the stack at the time of the GC. No ImmGCPtr is needed as the
        // WARNING: stubs are flushed on GC.
        // WARNING:
        MOZ_ASSERT(!hasStubCodePatchOffset_);
        stubCodePatchOffset_ = masm.PushWithPatch(STUB_ADDR);
        hasStubCodePatchOffset_ = true;
    }

    void patchRejoinJump(MacroAssembler& masm, JitCode* code) {
        rejoinOffset_.fixup(&masm);
        CodeLocationJump rejoinJump(code, rejoinOffset_);
        AutoWritableJitCode awjc(code);
        PatchJump(rejoinJump, rejoinLabel_);
    }

    void patchStubCodePointer(JitCode* code) {
        if (hasStubCodePatchOffset_) {
            AutoWritableJitCode awjc(code);
            Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_),
                                               ImmPtr(code), STUB_ADDR);
        }
    }

    void patchNextStubJump(MacroAssembler& masm, JitCode* code) {
        // Patch the previous nextStubJump of the last stub, or the jump from the
        // codeGen, to jump into the newly allocated code.
        PatchJump(cache_.lastJump_, CodeLocationLabel(code), Reprotect);

        // If this path is not taken, we are producing an entry which can no
        // longer go back into the update function.
        if (hasNextStubOffset_) {
            AutoWritableJitCode awjc(code);
            nextStubOffset_.fixup(&masm);
            CodeLocationJump nextStubJump(code, nextStubOffset_);
            PatchJump(nextStubJump, cache_.fallbackLabel_);

            // When the last stub fails, it fallback to the ool call which can
            // produce a stub. Next time we generate a stub, we will patch the
            // nextStub jump to try the new stub.
            cache_.lastJump_ = nextStubJump;
        }
    }
};

const ImmPtr IonCache::StubAttacher::STUB_ADDR = ImmPtr((void*)0xdeadc0de);

void
IonCache::emitInitialJump(MacroAssembler& masm, RepatchLabel& entry)
{
    initialJump_ = masm.jumpWithPatch(&entry);
    lastJump_ = initialJump_;
    Label label;
    masm.bind(&label);
    rejoinLabel_ = CodeOffset(label.offset());
}

void
IonCache::attachStub(MacroAssembler& masm, StubAttacher& attacher, Handle<JitCode*> code)
{
    MOZ_ASSERT(canAttachStub());
    incrementStubCount();

    // Update the success path to continue after the IC initial jump.
    attacher.patchRejoinJump(masm, code);

    // Replace the STUB_ADDR constant by the address of the generated stub, such
    // as it can be kept alive even if the cache is flushed (see
    // MarkJitExitFrame).
    attacher.patchStubCodePointer(code);

    // Update the failure path.
    attacher.patchNextStubJump(masm, code);
}

bool
IonCache::linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher,
                            IonScript* ion, const char* attachKind,
                            JS::TrackedOutcome trackedOutcome)
{
    Rooted<JitCode*> code(cx);
    {
        // Need to exit the AutoFlushICache context to flush the cache
        // before attaching the stub below.
        AutoFlushICache afc("IonCache");
        LinkStatus status = linkCode(cx, masm, ion, code.address());
        if (status != LINK_GOOD)
            return status != LINK_ERROR;
    }

    if (pc_) {
        JitSpew(JitSpew_IonIC, "Cache %p(%s:%" PRIuSIZE "/%" PRIuSIZE ") generated %s %s stub at %p",
                this, script_->filename(), script_->lineno(), script_->pcToOffset(pc_),
                attachKind, CacheName(kind()), code->raw());
    } else {
        JitSpew(JitSpew_IonIC, "Cache %p generated %s %s stub at %p",
                this, attachKind, CacheName(kind()), code->raw());
    }

#ifdef JS_ION_PERF
    writePerfSpewerJitCodeProfile(code, "IonCache");
#endif

    attachStub(masm, attacher, code);

    // Add entry to native => bytecode mapping for this stub if needed.
    if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
        JitcodeGlobalEntry::IonCacheEntry entry;
        entry.init(code, code->raw(), code->rawEnd(), rejoinAddress(), trackedOutcome);

        // Add entry to the global table.
        JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
        if (!globalTable->addEntry(entry, cx->runtime())) {
            entry.destroy();
            ReportOutOfMemory(cx);
            return false;
        }

        // Mark the jitcode as having a bytecode map.
        code->setHasBytecodeMap();
    } else {
        JitcodeGlobalEntry::DummyEntry entry;
        entry.init(code, code->raw(), code->rawEnd());

        // Add entry to the global table.
        JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
        if (!globalTable->addEntry(entry, cx->runtime())) {
            entry.destroy();
            ReportOutOfMemory(cx);
            return false;
        }

        // Mark the jitcode as having a bytecode map.
        code->setHasBytecodeMap();
    }

    // Report masm OOM errors here, so all our callers can:
    // return linkAndAttachStub(...);
    if (masm.oom()) {
        ReportOutOfMemory(cx);
        return false;
    }

    return true;
}

void
IonCache::updateBaseAddress(JitCode* code, MacroAssembler& masm)
{
    fallbackLabel_.repoint(code, &masm);
    initialJump_.repoint(code, &masm);
    lastJump_.repoint(code, &masm);
    rejoinLabel_.repoint(code, &masm);
}

static void*
GetReturnAddressToIonCode(JSContext* cx)
{
    JitFrameIterator iter(cx);
    MOZ_ASSERT(iter.type() == JitFrame_Exit,
               "An exit frame is expected as update functions are called with a VMFunction.");

    void* returnAddr = iter.returnAddress();
#ifdef DEBUG
    ++iter;
    MOZ_ASSERT(iter.isIonJS());
#endif
    return returnAddr;
}

static void
GeneratePrototypeGuards(JSContext* cx, IonScript* ion, MacroAssembler& masm, JSObject* obj,
                        JSObject* holder, Register objectReg, Register scratchReg,
                        Label* failures)
{
    /*
     * The guards here protect against the effects of JSObject::swap(). If the prototype chain
     * is directly altered, then TI will toss the jitcode, so we don't have to worry about
     * it, and any other change to the holder, or adding a shadowing property will result
     * in reshaping the holder, and thus the failure of the shape guard.
     */
    MOZ_ASSERT(obj != holder);

    if (obj->hasUncacheableProto()) {
        // Note: objectReg and scratchReg may be the same register, so we cannot
        // use objectReg in the rest of this function.
        masm.loadPtr(Address(objectReg, JSObject::offsetOfGroup()), scratchReg);
        Address proto(scratchReg, ObjectGroup::offsetOfProto());
        masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->getProto()), failures);
    }

    JSObject* pobj = IsCacheableDOMProxy(obj)
                     ? obj->getTaggedProto().toObjectOrNull()
                     : obj->getProto();
    if (!pobj)
        return;
    while (pobj != holder) {
        if (pobj->hasUncacheableProto()) {
            masm.movePtr(ImmGCPtr(pobj), scratchReg);
            Address groupAddr(scratchReg, JSObject::offsetOfGroup());
            if (pobj->isSingleton()) {
                // Singletons can have their group's |proto| mutated directly.
                masm.loadPtr(groupAddr, scratchReg);
                Address protoAddr(scratchReg, ObjectGroup::offsetOfProto());
                masm.branchPtr(Assembler::NotEqual, protoAddr, ImmGCPtr(pobj->getProto()), failures);
            } else {
                masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), failures);
            }
        }
        pobj = pobj->getProto();
    }
}

// Note: This differs from IsCacheableProtoChain in BaselineIC.cpp in that
// Ion caches can deal with objects on the proto chain that have uncacheable
// prototypes.
static bool
IsCacheableProtoChainForIon(JSObject* obj, JSObject* holder)
{
    while (obj != holder) {
        /*
         * We cannot assume that we find the holder object on the prototype
         * chain and must check for null proto. The prototype chain can be
         * altered during the lookupProperty call.
         */
        JSObject* proto = obj->getProto();
        if (!proto || !proto->isNative())
            return false;
        obj = proto;
    }
    return true;
}

static bool
IsCacheableGetPropReadSlotForIon(JSObject* obj, JSObject* holder, Shape* shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (!shape->hasSlot() || !shape->hasDefaultGetter())
        return false;

    return true;
}

static bool
IsCacheableNoProperty(JSObject* obj, JSObject* holder, Shape* shape, jsbytecode* pc,
                      const TypedOrValueRegister& output)
{
    if (shape)
        return false;

    MOZ_ASSERT(!holder);

    // Just because we didn't find the property on the object doesn't mean it
    // won't magically appear through various engine hacks:
    if (obj->getClass()->getProperty)
        return false;

    // Don't generate missing property ICs if we skipped a non-native object, as
    // lookups may extend beyond the prototype chain (e.g.  for DOMProxy
    // proxies).
    JSObject* obj2 = obj;
    while (obj2) {
        if (!obj2->isNative())
            return false;
        obj2 = obj2->getProto();
    }

    // The pc is nullptr if the cache is idempotent. We cannot share missing
    // properties between caches because TI can only try to prove that a type is
    // contained, but does not attempts to check if something does not exists.
    // So the infered type of getprop would be missing and would not contain
    // undefined, as expected for missing properties.
    if (!pc)
        return false;

    // TI has not yet monitored an Undefined value. The fallback path will
    // monitor and invalidate the script.
    if (!output.hasValue())
        return false;

    return true;
}

static bool
IsOptimizableArgumentsObjectForLength(JSObject* obj)
{
    if (!obj->is<ArgumentsObject>())
        return false;

    if (obj->as<ArgumentsObject>().hasOverriddenLength())
        return false;

    return true;
}

static bool
IsOptimizableArgumentsObjectForGetElem(JSObject* obj, Value idval)
{
    if (!IsOptimizableArgumentsObjectForLength(obj))
        return false;

    ArgumentsObject& argsObj = obj->as<ArgumentsObject>();

    if (argsObj.isAnyElementDeleted())
        return false;

    if (!idval.isInt32())
        return false;

    int32_t idint = idval.toInt32();
    if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
        return false;

    return true;
}

static bool
IsCacheableGetPropCallNative(JSObject* obj, JSObject* holder, Shape* shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (!shape->hasGetterValue() || !shape->getterValue().isObject())
        return false;

    if (!shape->getterValue().toObject().is<JSFunction>())
        return false;

    JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
    if (!getter.isNative())
        return false;

    // Check for a getter that has jitinfo and whose jitinfo says it's
    // OK with both inner and outer objects.
    if (getter.jitInfo() && !getter.jitInfo()->needsOuterizedThisObject())
        return true;

    // For getters that need the WindowProxy (instead of the Window) as this
    // object, don't cache if obj is the Window, since our cache will pass that
    // instead of the WindowProxy.
    return !IsWindow(obj);
}

static bool
IsCacheableGetPropCallScripted(JSObject* obj, JSObject* holder, Shape* shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (!shape->hasGetterValue() || !shape->getterValue().isObject())
        return false;

    if (!shape->getterValue().toObject().is<JSFunction>())
        return false;

    JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
    if (!getter.hasJITCode())
        return false;

    // See IsCacheableGetPropCallNative.
    return !IsWindow(obj);
}

static bool
IsCacheableGetPropCallPropertyOp(JSObject* obj, JSObject* holder, Shape* shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter())
        return false;

    return true;
}

static void
TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
                     Register object, JSObject* obj, Label* failure,
                     bool alwaysCheckGroup = false)
{
    if (obj->is<UnboxedPlainObject>()) {
        MOZ_ASSERT(failure);

        masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
        Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
        if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
            masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
            Label success;
            masm.push(object);
            masm.loadPtr(expandoAddress, object);
            masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(),
                                    &success);
            masm.pop(object);
            masm.jump(failure);
            masm.bind(&success);
            masm.pop(object);
        } else {
            masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
        }
    } else if (obj->is<UnboxedArrayObject>()) {
        MOZ_ASSERT(failure);
        masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
    } else if (obj->is<TypedObject>()) {
        attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                       Address(object, JSObject::offsetOfGroup()),
                                       ImmGCPtr(obj->group()), failure);
    } else {
        Shape* shape = obj->maybeShape();
        MOZ_ASSERT(shape);

        attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                       Address(object, JSObject::offsetOfShape()),
                                       ImmGCPtr(shape), failure);

        if (alwaysCheckGroup)
            masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
    }
}

static inline void
EmitLoadSlot(MacroAssembler& masm, NativeObject* holder, Shape* shape, Register holderReg,
             TypedOrValueRegister output, Register scratchReg)
{
    MOZ_ASSERT(holder);
    NativeObject::slotsSizeMustNotOverflow();
    if (holder->isFixedSlot(shape->slot())) {
        Address addr(holderReg, NativeObject::getFixedSlotOffset(shape->slot()));
        masm.loadTypedOrValue(addr, output);
    } else {
        masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), scratchReg);

        Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value));
        masm.loadTypedOrValue(addr, output);
    }
}

// Callers are expected to have already guarded on the shape of the
// object, which guarantees the object is a DOM proxy.
static void
CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, JSObject* obj,
                                  jsid id, Register object, Label* stubFailure)
{
    MOZ_ASSERT(IsCacheableDOMProxy(obj));

    // Guard that the object does not have expando properties, or has an expando
    // which is known to not have the desired property.

    // For the remaining code, we need to reserve some registers to load a value.
    // This is ugly, but unvaoidable.
    AllocatableRegisterSet domProxyRegSet(RegisterSet::All());
    domProxyRegSet.take(AnyRegister(object));
    ValueOperand tempVal = domProxyRegSet.takeAnyValue();
    masm.pushValue(tempVal);

    Label failDOMProxyCheck;
    Label domProxyOk;

    Value expandoVal = GetProxyExtra(obj, GetDOMProxyExpandoSlot());

    masm.loadPtr(Address(object, ProxyObject::offsetOfValues()), tempVal.scratchReg());
    masm.loadValue(Address(tempVal.scratchReg(),
                           ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot())),
                   tempVal);

    if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
        masm.branchTestValue(Assembler::NotEqual, tempVal, expandoVal, &failDOMProxyCheck);

        ExpandoAndGeneration* expandoAndGeneration = (ExpandoAndGeneration*)expandoVal.toPrivate();
        masm.movePtr(ImmPtr(expandoAndGeneration), tempVal.scratchReg());

        masm.branch64(Assembler::NotEqual,
                      Address(tempVal.scratchReg(),
                              ExpandoAndGeneration::offsetOfGeneration()),
                      Imm64(expandoAndGeneration->generation),
                      &failDOMProxyCheck);

        expandoVal = expandoAndGeneration->expando;
        masm.loadValue(Address(tempVal.scratchReg(),
                               ExpandoAndGeneration::offsetOfExpando()),
                       tempVal);
    }

    // If the incoming object does not have an expando object then we're sure we're not
    // shadowing.
    masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk);

    if (expandoVal.isObject()) {
        MOZ_ASSERT(!expandoVal.toObject().as<NativeObject>().contains(cx, id));

        // Reference object has an expando object that doesn't define the name. Check that
        // the incoming object has an expando object with the same shape.
        masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
        masm.extractObject(tempVal, tempVal.scratchReg());
        masm.branchPtr(Assembler::Equal,
                       Address(tempVal.scratchReg(), JSObject::offsetOfShape()),
                       ImmGCPtr(expandoVal.toObject().as<NativeObject>().lastProperty()),
                       &domProxyOk);
    }

    // Failure case: restore the tempVal registers and jump to failures.
    masm.bind(&failDOMProxyCheck);
    masm.popValue(tempVal);
    masm.jump(stubFailure);

    // Success case: restore the tempval and proceed.
    masm.bind(&domProxyOk);
    masm.popValue(tempVal);
}

static void
GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                 IonCache::StubAttacher& attacher, JSObject* obj, JSObject* holder,
                 Shape* shape, Register object, TypedOrValueRegister output,
                 Label* failures = nullptr)
{
    // If there's a single jump to |failures|, we can patch the shape guard
    // jump directly. Otherwise, jump to the end of the stub, so there's a
    // common point to patch.
    bool multipleFailureJumps = (obj != holder)
                             || obj->is<UnboxedPlainObject>()
                             || (failures != nullptr && failures->used());

    // If we have multiple failure jumps but didn't get a label from the
    // outside, make one ourselves.
    Label failures_;
    if (multipleFailureJumps && !failures)
        failures = &failures_;

    TestMatchingReceiver(masm, attacher, object, obj, failures);

    // If we need a scratch register, use either an output register or the
    // object register. After this point, we cannot jump directly to
    // |failures| since we may still have to pop the object register.
    bool restoreScratch = false;
    Register scratchReg = Register::FromCode(0); // Quell compiler warning.

    if (obj != holder ||
        obj->is<UnboxedPlainObject>() ||
        !holder->as<NativeObject>().isFixedSlot(shape->slot()))
    {
        if (output.hasValue()) {
            scratchReg = output.valueReg().scratchReg();
        } else if (output.type() == MIRType_Double) {
            scratchReg = object;
            masm.push(scratchReg);
            restoreScratch = true;
        } else {
            scratchReg = output.typedReg().gpr();
        }
    }

    // Fast path: single failure jump, no prototype guards.
    if (!multipleFailureJumps) {
        EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, object, output, scratchReg);
        if (restoreScratch)
            masm.pop(scratchReg);
        attacher.jumpRejoin(masm);
        return;
    }

    // Slow path: multiple jumps; generate prototype guards.
    Label prototypeFailures;
    Register holderReg;
    if (obj != holder) {
        // Note: this may clobber the object register if it's used as scratch.
        GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg,
                                &prototypeFailures);

        if (holder) {
            // Guard on the holder's shape.
            holderReg = scratchReg;
            masm.movePtr(ImmGCPtr(holder), holderReg);
            masm.branchPtr(Assembler::NotEqual,
                           Address(holderReg, JSObject::offsetOfShape()),
                           ImmGCPtr(holder->as<NativeObject>().lastProperty()),
                           &prototypeFailures);
        } else {
            // The property does not exist. Guard on everything in the
            // prototype chain.
            JSObject* proto = obj->getTaggedProto().toObjectOrNull();
            Register lastReg = object;
            MOZ_ASSERT(scratchReg != object);
            while (proto) {
                masm.loadObjProto(lastReg, scratchReg);

                // Guard the shape of the current prototype.
                masm.branchPtr(Assembler::NotEqual,
                               Address(scratchReg, JSObject::offsetOfShape()),
                               ImmGCPtr(proto->as<NativeObject>().lastProperty()),
                               &prototypeFailures);

                proto = proto->getProto();
                lastReg = scratchReg;
            }

            holderReg = InvalidReg;
        }
    } else if (obj->is<UnboxedPlainObject>()) {
        holder = obj->as<UnboxedPlainObject>().maybeExpando();
        holderReg = scratchReg;
        masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg);
    } else {
        holderReg = object;
    }

    // Slot access.
    if (holder)
        EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, holderReg, output, scratchReg);
    else
        masm.moveValue(UndefinedValue(), output.valueReg());

    // Restore scratch on success.
    if (restoreScratch)
        masm.pop(scratchReg);

    attacher.jumpRejoin(masm);

    masm.bind(&prototypeFailures);
    if (restoreScratch)
        masm.pop(scratchReg);
    masm.bind(failures);

    attacher.jumpNextStub(masm);

}

static void
GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                    IonCache::StubAttacher& attacher, JSObject* obj,
                    const UnboxedLayout::Property* property,
                    Register object, TypedOrValueRegister output,
                    Label* failures = nullptr)
{
    // Guard on the group of the object.
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                   Address(object, JSObject::offsetOfGroup()),
                                   ImmGCPtr(obj->group()), failures);

    Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);

    masm.loadUnboxedProperty(address, property->type, output);

    attacher.jumpRejoin(masm);

    if (failures) {
        masm.bind(failures);
        attacher.jumpNextStub(masm);
    }
}

static bool
EmitGetterCall(JSContext* cx, MacroAssembler& masm,
               IonCache::StubAttacher& attacher, JSObject* obj,
               JSObject* holder, HandleShape shape, bool holderIsReceiver,
               LiveRegisterSet liveRegs, Register object,
               TypedOrValueRegister output,
               void* returnAddr)
{
    MOZ_ASSERT(output.hasValue());
    MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);

    MOZ_ASSERT_IF(obj != holder, !holderIsReceiver);

    // Remaining registers should basically be free, but we need to use |object| still
    // so leave it alone.
    AllocatableRegisterSet regSet(RegisterSet::All());
    regSet.take(AnyRegister(object));

    // This is a slower stub path, and we're going to be doing a call anyway.  Don't need
    // to try so hard to not use the stack.  Scratch regs are just taken from the register
    // set not including the input, current value saved on the stack, and restored when
    // we're done with it.
    Register scratchReg = regSet.takeAnyGeneral();

    // Shape has a JSNative, PropertyOp or scripted getter function.
    if (IsCacheableGetPropCallNative(obj, holder, shape)) {
        Register argJSContextReg = regSet.takeAnyGeneral();
        Register argUintNReg     = regSet.takeAnyGeneral();
        Register argVpReg        = regSet.takeAnyGeneral();

        JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
        MOZ_ASSERT(target);
        MOZ_ASSERT(target->isNative());

        // Native functions have the signature:
        //  bool (*)(JSContext*, unsigned, Value* vp)
        // Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward
        // are the function arguments.

        // Construct vp array:
        // Push object value for |this|
        masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
        // Push callee/outparam.
        masm.Push(ObjectValue(*target));

        // Preload arguments into registers.
        masm.loadJSContext(argJSContextReg);
        masm.move32(Imm32(0), argUintNReg);
        masm.moveStackPtrTo(argVpReg);

        // Push marking data for later use.
        masm.Push(argUintNReg);
        attacher.pushStubCodePointer(masm);

        if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
            return false;
        masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);

        // Construct and execute call.
        masm.setupUnalignedABICall(scratchReg);
        masm.passABIArg(argJSContextReg);
        masm.passABIArg(argUintNReg);
        masm.passABIArg(argVpReg);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));

        // Test for failure.
        masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

        // Load the outparam vp[0] into output register(s).
        Address outparam(masm.getStackPointer(), IonOOLNativeExitFrameLayout::offsetOfResult());
        masm.loadTypedOrValue(outparam, output);

        // masm.leaveExitFrame & pop locals
        masm.adjustStack(IonOOLNativeExitFrameLayout::Size(0));
    } else if (IsCacheableGetPropCallPropertyOp(obj, holder, shape)) {
        Register argJSContextReg = regSet.takeAnyGeneral();
        Register argObjReg       = regSet.takeAnyGeneral();
        Register argIdReg        = regSet.takeAnyGeneral();
        Register argVpReg        = regSet.takeAnyGeneral();

        GetterOp target = shape->getterOp();
        MOZ_ASSERT(target);

        // Push stubCode for marking.
        attacher.pushStubCodePointer(masm);

        // JSGetterOp: bool fn(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)

        // Push args on stack first so we can take pointers to make handles.
        masm.Push(UndefinedValue());
        masm.moveStackPtrTo(argVpReg);

        // Push canonical jsid from shape instead of propertyname.
        masm.Push(shape->propid(), scratchReg);
        masm.moveStackPtrTo(argIdReg);

        // Push the holder.
        if (holderIsReceiver) {
            // When the holder is also the current receiver, we just have a shape guard,
            // so we might end up with a random object which is also guaranteed to have
            // this JSGetterOp.
            masm.Push(object);
        } else {
            // If the holder is on the prototype chain, the prototype-guarding
            // only allows objects with the same holder.
            masm.movePtr(ImmGCPtr(holder), scratchReg);
            masm.Push(scratchReg);
        }
        masm.moveStackPtrTo(argObjReg);

        masm.loadJSContext(argJSContextReg);

        if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
            return false;
        masm.enterFakeExitFrame(IonOOLPropertyOpExitFrameLayoutToken);

        // Make the call.
        masm.setupUnalignedABICall(scratchReg);
        masm.passABIArg(argJSContextReg);
        masm.passABIArg(argObjReg);
        masm.passABIArg(argIdReg);
        masm.passABIArg(argVpReg);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));

        // Test for failure.
        masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

        // Load the outparam vp[0] into output register(s).
        Address outparam(masm.getStackPointer(), IonOOLPropertyOpExitFrameLayout::offsetOfResult());
        masm.loadTypedOrValue(outparam, output);

        // masm.leaveExitFrame & pop locals.
        masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
    } else {
        MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape));

        JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
        uint32_t framePushedBefore = masm.framePushed();

        // Construct IonAccessorICFrameLayout.
        uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS);
        attacher.pushStubCodePointer(masm);
        masm.Push(Imm32(descriptor));
        masm.Push(ImmPtr(returnAddr));

        // The JitFrameLayout pushed below will be aligned to JitStackAlignment,
        // so we just have to make sure the stack is aligned after we push the
        // |this| + argument Values.
        uint32_t argSize = (target->nargs() + 1) * sizeof(Value);
        uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
        MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
        MOZ_ASSERT(padding < JitStackAlignment);
        masm.reserveStack(padding);

        for (size_t i = 0; i < target->nargs(); i++)
            masm.Push(UndefinedValue());
        masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));

        masm.movePtr(ImmGCPtr(target), scratchReg);

        descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC);
        masm.Push(Imm32(0)); // argc
        masm.Push(scratchReg);
        masm.Push(Imm32(descriptor));

        // Check stack alignment. Add sizeof(uintptr_t) for the return address.
        MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);

        // The getter has JIT code now and we will only discard the getter's JIT
        // code when discarding all JIT code in the Zone, so we can assume it'll
        // still have JIT code.
        MOZ_ASSERT(target->hasJITCode());
        masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg);
        masm.loadBaselineOrIonRaw(scratchReg, scratchReg, nullptr);
        masm.callJit(scratchReg);
        masm.storeCallResultValue(output);

        masm.freeStack(masm.framePushed() - framePushedBefore);
    }

    masm.icRestoreLive(liveRegs, aic);
    return true;
}

static bool
GenerateCallGetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                   IonCache::StubAttacher& attacher, JSObject* obj,
                   JSObject* holder, HandleShape shape, LiveRegisterSet& liveRegs, Register object,
                   TypedOrValueRegister output, void* returnAddr, Label* failures = nullptr)
{
    MOZ_ASSERT(output.hasValue());

    // Use the passed in label if there was one. Otherwise, we'll have to make our own.
    Label stubFailure;
    failures = failures ? failures : &stubFailure;

    TestMatchingReceiver(masm, attacher, object, obj, failures);

    Register scratchReg = output.valueReg().scratchReg();
    bool spillObjReg = scratchReg == object;
    Label pop1AndFail;
    Label* maybePopAndFail = failures;

    // If we're calling a getter on the global, inline the logic for the
    // 'this' hook on the global lexical scope and manually push the global.
    if (IsGlobalLexicalScope(obj))
        masm.extractObject(Address(object, ScopeObject::offsetOfEnclosingScope()), object);

    // Save off the object register if it aliases the scratchReg
    if (spillObjReg) {
        masm.push(object);
        maybePopAndFail = &pop1AndFail;
    }

    // Note: this may clobber the object register if it's used as scratch.
    if (obj != holder)
        GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, failures);

    // Guard on the holder's shape.
    Register holderReg = scratchReg;
    masm.movePtr(ImmGCPtr(holder), holderReg);
    masm.branchPtr(Assembler::NotEqual,
                   Address(holderReg, JSObject::offsetOfShape()),
                   ImmGCPtr(holder->as<NativeObject>().lastProperty()),
                   maybePopAndFail);

    if (spillObjReg)
        masm.pop(object);

    // Now we're good to go to invoke the native call.
    bool holderIsReceiver = (obj == holder);
    if (!EmitGetterCall(cx, masm, attacher, obj, holder, shape, holderIsReceiver, liveRegs, object,
                        output, returnAddr))
        return false;

    // Rejoin jump.
    attacher.jumpRejoin(masm);

    // Jump to next stub.
    if (spillObjReg) {
        masm.bind(&pop1AndFail);
        masm.pop(object);
    }
    masm.bind(failures);
    attacher.jumpNextStub(masm);

    return true;
}

static bool
GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                    JSObject* obj, Register object, TypedOrValueRegister output, Label* failures)
{
    MOZ_ASSERT(obj->is<ArrayObject>());

    // Guard object is a dense array.
    RootedShape shape(cx, obj->as<ArrayObject>().lastProperty());
    if (!shape)
        return false;
    masm.branchTestObjShape(Assembler::NotEqual, object, shape, failures);

    // Load length.
    Register outReg;
    if (output.hasValue()) {
        outReg = output.valueReg().scratchReg();
    } else {
        MOZ_ASSERT(output.type() == MIRType_Int32);
        outReg = output.typedReg().gpr();
    }

    masm.loadPtr(Address(object, NativeObject::offsetOfElements()), outReg);
    masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg);

    // The length is an unsigned int, but the value encodes a signed int.
    MOZ_ASSERT(object != outReg);
    masm.branchTest32(Assembler::Signed, outReg, outReg, failures);

    if (output.hasValue())
        masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());

    /* Success. */
    attacher.jumpRejoin(masm);

    /* Failure. */
    masm.bind(failures);
    attacher.jumpNextStub(masm);

    return true;
}

static void
GenerateUnboxedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                           JSObject* array, Register object, TypedOrValueRegister output,
                           Label* failures)
{
    Register outReg;
    if (output.hasValue()) {
        outReg = output.valueReg().scratchReg();
    } else {
        MOZ_ASSERT(output.type() == MIRType_Int32);
        outReg = output.typedReg().gpr();
    }
    MOZ_ASSERT(object != outReg);

    TestMatchingReceiver(masm, attacher, object, array, failures);

    // Load length.
    masm.load32(Address(object, UnboxedArrayObject::offsetOfLength()), outReg);

    // Check for a length that fits in an int32.
    masm.branchTest32(Assembler::Signed, outReg, outReg, failures);

    if (output.hasValue())
        masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(failures);
    attacher.jumpNextStub(masm);
}

// In this case, the code for TypedArray and SharedTypedArray is not the same,
// because the code embeds pointers to the respective class arrays.  Code that
// caches the stub code must distinguish between the two cases.
static void
GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                         Register object, TypedOrValueRegister output, Label* failures)
{
    Register tmpReg;
    if (output.hasValue()) {
        tmpReg = output.valueReg().scratchReg();
    } else {
        MOZ_ASSERT(output.type() == MIRType_Int32);
        tmpReg = output.typedReg().gpr();
    }
    MOZ_ASSERT(object != tmpReg);

    // Implement the negated version of JSObject::isTypedArray predicate.
    masm.loadObjClass(object, tmpReg);
    masm.branchPtr(Assembler::Below, tmpReg, ImmPtr(&TypedArrayObject::classes[0]),
                   failures);
    masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
                   ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
                   failures);

    // Load length.
    masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);

    /* Success. */
    attacher.jumpRejoin(masm);

    /* Failure. */
    masm.bind(failures);
    attacher.jumpNextStub(masm);
}

static bool
IsCacheableArrayLength(JSContext* cx, HandleObject obj, TypedOrValueRegister output)
{
    if (!obj->is<ArrayObject>())
        return false;

    if (output.type() != MIRType_Value && output.type() != MIRType_Int32) {
        // The stub assumes that we always output Int32, so make sure our output
        // is equipped to handle that.
        return false;
    }

    // The emitted stub can only handle int32 lengths. If the length of the
    // actual object does not fit in an int32 then don't attach a stub, as if
    // the cache is idempotent we won't end up invalidating the compiled script
    // otherwise.
    if (obj->as<ArrayObject>().length() > INT32_MAX)
        return false;

    return true;
}

template <class GetPropCache>
static GetPropertyIC::NativeGetPropCacheability
CanAttachNativeGetProp(JSContext* cx, const GetPropCache& cache,
                       HandleObject obj, HandleId id,
                       MutableHandleNativeObject holder, MutableHandleShape shape,
                       bool skipArrayLen = false)
{
    MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));

    if (!obj)
        return GetPropertyIC::CanAttachNone;

    // The lookup needs to be universally pure, otherwise we risk calling hooks out
    // of turn. We don't mind doing this even when purity isn't required, because we
    // only miss out on shape hashification, which is only a temporary perf cost.
    // The limits were arbitrarily set, anyways.
    JSObject* baseHolder = nullptr;
    if (!LookupPropertyPure(cx, obj, id, &baseHolder, shape.address()))
        return GetPropertyIC::CanAttachNone;

    MOZ_ASSERT(!holder);
    if (baseHolder) {
        if (!baseHolder->isNative())
            return GetPropertyIC::CanAttachNone;
        holder.set(&baseHolder->as<NativeObject>());
    }

    RootedScript script(cx);
    jsbytecode* pc;
    cache.getScriptedLocation(&script, &pc);
    if (IsCacheableGetPropReadSlotForIon(obj, holder, shape) ||
        IsCacheableNoProperty(obj, holder, shape, pc, cache.output()))
    {
        return GetPropertyIC::CanAttachReadSlot;
    }

    // |length| is a non-configurable getter property on ArrayObjects. Any time this
    // check would have passed, we can install a getter stub instead. Allow people to
    // make that decision themselves with skipArrayLen
    if (!skipArrayLen && JSID_IS_ATOM(id, cx->names().length) && cache.allowArrayLength(cx) &&
        IsCacheableArrayLength(cx, obj, cache.output()))
    {
        // The array length property is non-configurable, which means both that
        // checking the class of the object and the name of the property is enough
        // and that we don't need to worry about monitoring, since we know the
        // return type statically.
        return GetPropertyIC::CanAttachArrayLength;
    }

    // IonBuilder guarantees that it's impossible to generate a GetPropertyIC with
    // allowGetters() true and cache.output().hasValue() false. If this isn't true,
    // we will quickly assert during stub generation.
    //
    // Be careful when adding support for other getters here: for outer window
    // proxies, IonBuilder can innerize and pass us the inner window (the global),
    // see IonBuilder::getPropTryInnerize. This is fine for native/scripted getters
    // because IsCacheableGetPropCallNative and IsCacheableGetPropCallScripted
    // handle this.
    if (cache.allowGetters() &&
        (IsCacheableGetPropCallNative(obj, holder, shape) ||
         IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
         IsCacheableGetPropCallScripted(obj, holder, shape)))
    {
        // Don't enable getter call if cache is idempotent, since they can be
        // effectful. This is handled by allowGetters()
        return GetPropertyIC::CanAttachCallGetter;
    }

    return GetPropertyIC::CanAttachNone;
}

static bool
EqualStringsHelper(JSString* str1, JSString* str2)
{
    MOZ_ASSERT(str1->isAtom());
    MOZ_ASSERT(!str2->isAtom());
    MOZ_ASSERT(str1->length() == str2->length());

    JSLinearString* str2Linear = str2->ensureLinear(nullptr);
    if (!str2Linear)
        return false;

    return EqualChars(&str1->asLinear(), str2Linear);
}

static void
EmitIdGuard(MacroAssembler& masm, jsid id, TypedOrValueRegister idReg, Register objReg,
            Register scratchReg, Label* failures)
{
    MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));

    MOZ_ASSERT(idReg.type() == MIRType_String ||
               idReg.type() == MIRType_Symbol ||
               idReg.type() == MIRType_Value);

    Register payloadReg;
    if (idReg.type() == MIRType_Value) {
        ValueOperand val = idReg.valueReg();
        if (JSID_IS_SYMBOL(id)) {
            masm.branchTestSymbol(Assembler::NotEqual, val, failures);
        } else {
            MOZ_ASSERT(JSID_IS_STRING(id));
            masm.branchTestString(Assembler::NotEqual, val, failures);
        }
        masm.unboxNonDouble(val, scratchReg);
        payloadReg = scratchReg;
    } else {
        payloadReg = idReg.typedReg().gpr();
    }

    if (JSID_IS_SYMBOL(id)) {
        // For symbols, we can just do a pointer comparison.
        masm.branchPtr(Assembler::NotEqual, payloadReg, ImmGCPtr(JSID_TO_SYMBOL(id)), failures);
    } else {
        PropertyName* name = JSID_TO_ATOM(id)->asPropertyName();

        Label equal;
        masm.branchPtr(Assembler::Equal, payloadReg, ImmGCPtr(name), &equal);

        // The pointers are not equal, so if the input string is also an atom it
        // must be a different string.
        masm.branchTest32(Assembler::NonZero, Address(payloadReg, JSString::offsetOfFlags()),
                          Imm32(JSString::ATOM_BIT), failures);

        // Check the length.
        masm.branch32(Assembler::NotEqual, Address(payloadReg, JSString::offsetOfLength()),
                      Imm32(name->length()), failures);

        // We have a non-atomized string with the same length. For now call a helper
        // function to do the comparison.
        LiveRegisterSet volatileRegs(RegisterSet::Volatile());
        masm.PushRegsInMask(volatileRegs);

        if (!volatileRegs.has(objReg))
            masm.push(objReg);

        masm.setupUnalignedABICall(objReg);
        masm.movePtr(ImmGCPtr(name), objReg);
        masm.passABIArg(objReg);
        masm.passABIArg(payloadReg);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, EqualStringsHelper));
        masm.mov(ReturnReg, scratchReg);

        if (!volatileRegs.has(objReg))
            masm.pop(objReg);

        LiveRegisterSet ignore;
        ignore.add(scratchReg);
        masm.PopRegsInMaskIgnore(volatileRegs, ignore);

        masm.branchIfFalseBool(scratchReg, failures);
        masm.bind(&equal);
    }
}

void
GetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
{
    if (this->id().constant())
        return;

    Register scratch = output().valueReg().scratchReg();
    EmitIdGuard(masm, id, this->id().reg(), object(), scratch, fail);
}

void
SetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
{
    if (this->id().constant())
        return;

    EmitIdGuard(masm, id, this->id().reg(), object(), temp(), fail);
}

bool
GetPropertyIC::allowArrayLength(JSContext* cx) const
{
    if (!idempotent())
        return true;

    uint32_t locationIndex, numLocations;
    getLocationInfo(&locationIndex, &numLocations);

    IonScript* ion = GetTopJitJSScript(cx)->ionScript();
    CacheLocation* locs = ion->getCacheLocs(locationIndex);
    for (size_t i = 0; i < numLocations; i++) {
        CacheLocation& curLoc = locs[i];
        StackTypeSet* bcTypes = TypeScript::BytecodeTypes(curLoc.script, curLoc.pc);

        if (!bcTypes->hasType(TypeSet::Int32Type()))
            return false;
    }

    return true;
}

bool
GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
                               HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(outerScript->ionScript() == ion);

    RootedShape shape(cx);
    RootedNativeObject holder(cx);

    NativeGetPropCacheability type =
        CanAttachNativeGetProp(cx, *this, obj, id, &holder, &shape);
    if (type == CanAttachNone)
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

    StubAttacher attacher(*this);
    const char* attachKind;

    JS::TrackedOutcome outcome = JS::TrackedOutcome::ICOptStub_GenericSuccess;

    Label failures;
    emitIdGuard(masm, id, &failures);
    Label* maybeFailures = failures.used() ? &failures : nullptr;

    switch (type) {
      case CanAttachReadSlot:
        GenerateReadSlot(cx, ion, masm, attacher, obj, holder,
                         shape, object(), output(), maybeFailures);
        attachKind = idempotent() ? "idempotent reading"
                                    : "non idempotent reading";
        outcome = JS::TrackedOutcome::ICGetPropStub_ReadSlot;
        break;
      case CanAttachCallGetter:
        if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape,
                                liveRegs_, object(), output(), returnAddr, maybeFailures))
        {
            return false;
        }
        attachKind = "getter call";
        outcome = JS::TrackedOutcome::ICGetPropStub_CallGetter;
        break;
      case CanAttachArrayLength:
        if (!GenerateArrayLength(cx, masm, attacher, obj, object(), output(), &failures))
            return false;

        attachKind = "array length";
        outcome = JS::TrackedOutcome::ICGetPropStub_ArrayLength;
        break;
      default:
        MOZ_CRASH("Bad NativeGetPropCacheability");
    }
    return linkAndAttachStub(cx, masm, attacher, ion, attachKind, outcome);
}

bool
GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(outerScript->ionScript() == ion);

    if (!obj->is<UnboxedPlainObject>())
        return true;
    const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
    if (!property)
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

    Label failures;
    emitIdGuard(masm, id, &failures);
    Label* maybeFailures = failures.used() ? &failures : nullptr;

    StubAttacher attacher(*this);
    GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures);
    return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed",
                             JS::TrackedOutcome::ICGetPropStub_UnboxedRead);
}

bool
GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(outerScript->ionScript() == ion);

    if (!obj->is<UnboxedPlainObject>())
        return true;
    Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
    if (!expando)
        return true;

    Shape* shape = expando->lookup(cx, id);
    if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

    Label failures;
    emitIdGuard(masm, id, &failures);
    Label* maybeFailures = failures.used() ? &failures : nullptr;

    StubAttacher attacher(*this);
    GenerateReadSlot(cx, ion, masm, attacher, obj, obj,
                     shape, object(), output(), maybeFailures);
    return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando",
                             JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
}

bool
GetPropertyIC::tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                           HandleObject obj, HandleId id, void* returnAddr,
                                           bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(outerScript->ionScript() == ion);

    if (!obj->is<UnboxedArrayObject>())
        return true;

    if (!JSID_IS_ATOM(id, cx->names().length))
        return true;

    if (obj->as<UnboxedArrayObject>().length() > INT32_MAX)
        return true;

    if (!allowArrayLength(cx))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

    Label failures;
    emitIdGuard(masm, id, &failures);

    StubAttacher attacher(*this);
    GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output(), &failures);
    return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length",
                             JS::TrackedOutcome::ICGetPropStub_UnboxedArrayLength);
}

bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                         HandleObject obj, HandleId id, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!IsAnyTypedArray(obj))
        return true;

    if (!JSID_IS_ATOM(id, cx->names().length))
        return true;

    if (hasTypedArrayLengthStub(obj))
        return true;

    if (output().type() != MIRType_Value && output().type() != MIRType_Int32) {
        // The next execution should cause an invalidation because the type
        // does not fit.
        return true;
    }

    if (idempotent())
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, id, &failures);

    GenerateTypedArrayLength(cx, masm, attacher, object(), output(), &failures);

    setHasTypedArrayLengthStub(obj);
    return linkAndAttachStub(cx, masm, attacher, ion, "typed array length",
                             JS::TrackedOutcome::ICGetPropStub_TypedArrayLength);
}

static void
PushObjectOpResult(MacroAssembler& masm)
{
    static_assert(sizeof(ObjectOpResult) == sizeof(uintptr_t),
                  "ObjectOpResult size must match size reserved by masm.Push() here");
    masm.Push(ImmWord(ObjectOpResult::Uninitialized));
}

static bool
ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id, MutableHandleValue vp)
{
    RootedValue receiver(cx, ObjectValue(*proxy));
    return Proxy::get(cx, proxy, receiver, id, vp);
}

static bool
EmitCallProxyGet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                 jsid id, LiveRegisterSet liveRegs, Register object, TypedOrValueRegister output,
                 jsbytecode* pc, void* returnAddr)
{
    MOZ_ASSERT(output.hasValue());
    MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);

    // Remaining registers should be free, but we need to use |object| still
    // so leave it alone.
    AllocatableRegisterSet regSet(RegisterSet::All());
    regSet.take(AnyRegister(object));

    // ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
    //                  MutableHandleValue vp)
    Register argJSContextReg = regSet.takeAnyGeneral();
    Register argProxyReg     = regSet.takeAnyGeneral();
    Register argIdReg        = regSet.takeAnyGeneral();
    Register argVpReg        = regSet.takeAnyGeneral();

    Register scratch         = regSet.takeAnyGeneral();

    // Push stubCode for marking.
    attacher.pushStubCodePointer(masm);

    // Push args on stack first so we can take pointers to make handles.
    masm.Push(UndefinedValue());
    masm.moveStackPtrTo(argVpReg);

    masm.Push(id, scratch);
    masm.moveStackPtrTo(argIdReg);

    // Push the proxy. Also used as receiver.
    masm.Push(object);
    masm.moveStackPtrTo(argProxyReg);

    masm.loadJSContext(argJSContextReg);

    if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
        return false;
    masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);

    // Make the call.
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(argJSContextReg);
    masm.passABIArg(argProxyReg);
    masm.passABIArg(argIdReg);
    masm.passABIArg(argVpReg);
    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxyGetProperty));

    // Test for failure.
    masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

    // Load the outparam vp[0] into output register(s).
    Address outparam(masm.getStackPointer(), IonOOLProxyExitFrameLayout::offsetOfResult());
    masm.loadTypedOrValue(outparam, output);

    // masm.leaveExitFrame & pop locals
    masm.adjustStack(IonOOLProxyExitFrameLayout::Size());

    masm.icRestoreLive(liveRegs, aic);
    return true;
}

bool
GetPropertyIC::tryAttachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                         HandleObject obj, HandleId id, void* returnAddr,
                                         bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(IsCacheableDOMProxy(obj));
    MOZ_ASSERT(monitoredResult());
    MOZ_ASSERT(output().hasValue());

    if (idempotent())
        return true;

    *emitted = true;

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    // Guard on the shape of the object.
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                   Address(object(), JSObject::offsetOfShape()),
                                   ImmGCPtr(obj->maybeShape()),
                                   &failures);

    // No need for more guards: we know this is a DOM proxy, since the shape
    // guard enforces a given JSClass, so just go ahead and emit the call to
    // ProxyGet.

    if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
                          pc(), returnAddr))
    {
        return false;
    }

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "list base shadowed get",
                             JS::TrackedOutcome::ICGetPropStub_DOMProxyShadowed);
}

bool
GetPropertyIC::tryAttachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                           HandleObject obj, HandleId id, bool resetNeeded,
                                           void* returnAddr, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(IsCacheableDOMProxy(obj));
    MOZ_ASSERT(monitoredResult());
    MOZ_ASSERT(output().hasValue());

    RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull());
    RootedNativeObject holder(cx);
    RootedShape shape(cx);

    NativeGetPropCacheability canCache =
        CanAttachNativeGetProp(cx, *this, checkObj, id, &holder, &shape,
                               /* skipArrayLen = */true);
    MOZ_ASSERT(canCache != CanAttachArrayLength);

    if (canCache == CanAttachNone)
        return true;

    // Make sure we observe our invariants if we're gonna deoptimize.
    if (!holder && idempotent())
        return true;

    *emitted = true;

    if (resetNeeded) {
        // If we know that we have a DoesntShadowUnique object, then
        // we reset the cache to clear out an existing IC for the object
        // (if there is one). The generation is a constant in the generated
        // code and we will not have the same generation again for this
        // object, so the generation check in the existing IC would always
        // fail anyway.
        reset(Reprotect);
    }

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    // Guard on the shape of the object.
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                   Address(object(), JSObject::offsetOfShape()),
                                   ImmGCPtr(obj->maybeShape()),
                                   &failures);

    // Guard that our expando object hasn't started shadowing this property.
    CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);

    if (holder) {
        // Found the property on the prototype chain. Treat it like a native
        // getprop.
        Register scratchReg = output().valueReg().scratchReg();
        GeneratePrototypeGuards(cx, ion, masm, obj, holder, object(), scratchReg, &failures);

        // Rename scratch for clarity.
        Register holderReg = scratchReg;

        // Guard on the holder of the property
        masm.movePtr(ImmGCPtr(holder), holderReg);
        masm.branchPtr(Assembler::NotEqual,
                    Address(holderReg, JSObject::offsetOfShape()),
                    ImmGCPtr(holder->lastProperty()),
                    &failures);

        if (canCache == CanAttachReadSlot) {
            EmitLoadSlot(masm, holder, shape, holderReg, output(), scratchReg);
        } else {
            // EmitGetterCall() expects |obj| to be the object the property is
            // on to do some checks. Since we actually looked at checkObj, and
            // no extra guards will be generated, we can just pass that instead.
            // The holderIsReceiver check needs to use |obj| though.
            MOZ_ASSERT(canCache == CanAttachCallGetter);
            MOZ_ASSERT(!idempotent());
            bool holderIsReceiver = (obj == holder);
            if (!EmitGetterCall(cx, masm, attacher, checkObj, holder, shape, holderIsReceiver,
                                liveRegs_, object(), output(), returnAddr))
            {
                return false;
            }
        }
    } else {
        // Property was not found on the prototype chain. Deoptimize down to
        // proxy get call
        MOZ_ASSERT(!idempotent());
        if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
                              pc(), returnAddr))
        {
            return false;
        }
    }

    attacher.jumpRejoin(masm);
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "unshadowed proxy get",
                             JS::TrackedOutcome::ICGetPropStub_DOMProxyUnshadowed);
}

bool
GetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                              HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!obj->is<ProxyObject>())
        return true;

    // TI can't be sure about our properties, so make sure anything
    // we return can be monitored directly.
    if (!monitoredResult())
        return true;

    // Skim off DOM proxies.
    if (IsCacheableDOMProxy(obj)) {
        DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
        if (shadows == ShadowCheckFailed)
            return false;
        if (DOMProxyIsShadowing(shadows))
            return tryAttachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr, emitted);

        MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
        return tryAttachDOMProxyUnshadowed(cx, outerScript, ion, obj, id,
                                           shadows == DoesntShadowUnique, returnAddr, emitted);
    }

    return tryAttachGenericProxy(cx, outerScript, ion, obj, id, returnAddr, emitted);
}

bool
GetPropertyIC::tryAttachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject obj, HandleId id, void* returnAddr,
                                     bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(obj->is<ProxyObject>());
    MOZ_ASSERT(monitoredResult());
    MOZ_ASSERT(output().hasValue());

    if (hasGenericProxyStub())
        return true;

    if (idempotent())
        return true;

    *emitted = true;

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    Register scratchReg = output().valueReg().scratchReg();

    masm.branchTestObjectIsProxy(false, object(), scratchReg, &failures);

    // Ensure that the incoming object is not a DOM proxy, so that we can get to
    // the specialized stubs
    masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), scratchReg,
                                      GetDOMProxyHandlerFamily(), &failures);

    if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
                          pc(), returnAddr))
    {
        return false;
    }

    attacher.jumpRejoin(masm);

    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    MOZ_ASSERT(!hasGenericProxyStub_);
    hasGenericProxyStub_ = true;

    return linkAndAttachStub(cx, masm, attacher, ion, "Generic Proxy get",
                             JS::TrackedOutcome::ICGetPropStub_GenericProxy);
}

bool
GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                        HandleObject obj, HandleId id, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!JSID_IS_ATOM(id, cx->names().length))
        return true;
    if (!IsOptimizableArgumentsObjectForLength(obj))
        return true;

    MIRType outputType = output().type();
    if (!(outputType == MIRType_Value || outputType == MIRType_Int32))
        return true;

    if (hasArgumentsLengthStub(obj->is<MappedArgumentsObject>()))
        return true;

    *emitted = true;

    MOZ_ASSERT(!idempotent());

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    Register tmpReg;
    if (output().hasValue()) {
        tmpReg = output().valueReg().scratchReg();
    } else {
        MOZ_ASSERT(output().type() == MIRType_Int32);
        tmpReg = output().typedReg().gpr();
    }
    MOZ_ASSERT(object() != tmpReg);

    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);

    // Get initial ArgsObj length value, test if length has been overridden.
    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
                      &failures);

    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);

    // If output is Int32, result is already in right place, otherwise box it into output.
    if (output().hasValue())
        masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    if (obj->is<UnmappedArgumentsObject>()) {
        MOZ_ASSERT(!hasUnmappedArgumentsLengthStub_);
        hasUnmappedArgumentsLengthStub_ = true;
        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (unmapped)",
                                 JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
    }

    MOZ_ASSERT(!hasMappedArgumentsLengthStub_);
    hasMappedArgumentsLengthStub_ = true;
    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (mapped)",
                                 JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
}

static void
GenerateReadModuleNamespace(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                            IonCache::StubAttacher& attacher, ModuleNamespaceObject* ns,
                            ModuleEnvironmentObject* env, Shape* shape, Register object,
                            TypedOrValueRegister output, Label* failures)
{
    MOZ_ASSERT(ns);
    MOZ_ASSERT(env);

    // If we have multiple failure jumps but didn't get a label from the
    // outside, make one ourselves.
    Label failures_;
    if (!failures)
        failures = &failures_;

    // Check for the specific namespace object.
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, object, ImmGCPtr(ns), failures);

    // If we need a scratch register, use either an output register or the
    // object register.
    bool restoreScratch = false;
    Register scratchReg = InvalidReg; // Quell compiler warning.

    if (output.hasValue()) {
        scratchReg = output.valueReg().scratchReg();
    } else if (output.type() == MIRType_Double) {
        masm.push(object);
        scratchReg = object;
        restoreScratch = true;
    } else {
        scratchReg = output.typedReg().gpr();
    }

    // Slot access.
    Register envReg = scratchReg;
    masm.movePtr(ImmGCPtr(env), envReg);
    EmitLoadSlot(masm, &env->as<NativeObject>(), shape, envReg, output, scratchReg);

    // Restore scratch on success.
    if (restoreScratch)
        masm.pop(object);

    attacher.jumpRejoin(masm);

    masm.bind(failures);
    attacher.jumpNextStub(masm);
}

bool
GetPropertyIC::tryAttachModuleNamespace(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                        HandleObject obj, HandleId id, void* returnAddr,
                                        bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(outerScript->ionScript() == ion);

    if (!obj->is<ModuleNamespaceObject>())
        return true;

    Rooted<ModuleNamespaceObject*> ns(cx, &obj->as<ModuleNamespaceObject>());

    RootedModuleEnvironmentObject env(cx);
    RootedShape shape(cx);
    if (!ns->bindings().lookup(id, env.address(), shape.address()))
        return true;

    // Don't emit a stub until the target binding has been initialized.
    if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, id, &failures);
    Label* maybeFailures = failures.used() ? &failures : nullptr;

    GenerateReadModuleNamespace(cx, ion, masm, attacher, ns, env,
                                shape, object(), output(), maybeFailures);
    return linkAndAttachStub(cx, masm, attacher, ion, "module namespace",
                             JS::TrackedOutcome::ICGetPropStub_ReadSlot);
}

static bool
ValueToNameOrSymbolId(JSContext* cx, HandleValue idval, MutableHandleId id, bool* nameOrSymbol)
{
    *nameOrSymbol = false;

    if (!idval.isString() && !idval.isSymbol())
        return true;

    if (!ValueToId<CanGC>(cx, idval, id))
        return false;

    if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
        id.set(JSID_VOID);
        return true;
    }

    uint32_t dummy;
    if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
        id.set(JSID_VOID);
        return true;
    }

    *nameOrSymbol = true;
    return true;
}

bool
GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
                             HandleObject obj, HandleValue idval, bool* emitted)
{
    MOZ_ASSERT(!*emitted);

    if (!canAttachStub())
        return true;

    RootedId id(cx);
    bool nameOrSymbol;
    if (!ValueToNameOrSymbolId(cx, idval, &id, &nameOrSymbol))
        return false;

    if (nameOrSymbol) {
        if (!*emitted && !tryAttachArgumentsLength(cx, outerScript, ion, obj, id, emitted))
            return false;

        void* returnAddr = GetReturnAddressToIonCode(cx);

        if (!*emitted && !tryAttachModuleNamespace(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
            return false;

        if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
            return false;
    }

    if (idval.isInt32()) {
        if (!*emitted && !tryAttachArgumentsElement(cx, outerScript, ion, obj, idval, emitted))
            return false;
        if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
            return false;
        if (!*emitted && !tryAttachDenseElementHole(cx, outerScript, ion, obj, idval, emitted))
            return false;
    }

    if (idval.isInt32() || idval.isString()) {
        if (!*emitted && !tryAttachTypedOrUnboxedArrayElement(cx, outerScript, ion, obj, idval, emitted))
            return false;
    }

    if (!*emitted)
        JitSpew(JitSpew_IonIC, "Failed to attach GETPROP cache");

    return true;
}

/* static */ bool
GetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
                      HandleObject obj, HandleValue idval, MutableHandleValue vp)
{
    IonScript* ion = outerScript->ionScript();

    GetPropertyIC& cache = ion->getCache(cacheIndex).toGetProperty();

    // Override the return value if we are invalidated (bug 728188).
    AutoDetectInvalidation adi(cx, vp, ion);

    // If the cache is idempotent, we will redo the op in the interpreter.
    if (cache.idempotent())
        adi.disable();

    // For now, just stop generating new stubs once we hit the stub count
    // limit. Once we can make calls from within generated stubs, a new call
    // stub will be generated instead and the previous stubs unlinked.
    bool emitted = false;
    if (!cache.isDisabled()) {
        if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, &emitted))
            return false;
        cache.maybeDisable(emitted);
    }

    if (cache.idempotent() && !emitted) {
        // Invalidate the cache if the property was not found, or was found on
        // a non-native object. This ensures:
        // 1) The property read has no observable side-effects.
        // 2) There's no need to dynamically monitor the return type. This would
        //    be complicated since (due to GVN) there can be multiple pc's
        //    associated with a single idempotent cache.
        JitSpew(JitSpew_IonIC, "Invalidating from idempotent cache %s:%" PRIuSIZE,
                outerScript->filename(), outerScript->lineno());

        outerScript->setInvalidatedIdempotentCache();

        // Do not re-invalidate if the lookup already caused invalidation.
        if (!outerScript->hasIonScript())
            return true;

        return Invalidate(cx, outerScript);
    }

    jsbytecode* pc = cache.idempotent() ? nullptr : cache.pc();

    if (!pc || *pc == JSOP_GETPROP || *pc == JSOP_CALLPROP || *pc == JSOP_LENGTH) {
        if (!GetProperty(cx, obj, obj, idval.toString()->asAtom().asPropertyName(), vp))
            return false;
    } else {
        MOZ_ASSERT(*pc == JSOP_GETELEM || *pc == JSOP_CALLELEM);
        if (!GetObjectElementOperation(cx, JSOp(*pc), obj, obj, idval, vp))
            return false;
    }

    if (!cache.idempotent()) {
        RootedScript script(cx);
        jsbytecode* pc;
        cache.getScriptedLocation(&script, &pc);

        // Monitor changes to cache entry.
        if (!cache.monitoredResult())
            TypeScript::Monitor(cx, script, pc, vp);
    }

    return true;
}

void
GetPropertyIC::reset(ReprotectCode reprotect)
{
    IonCache::reset(reprotect);
    hasTypedArrayLengthStub_ = false;
    hasMappedArgumentsLengthStub_ = false;
    hasUnmappedArgumentsLengthStub_ = false;
    hasMappedArgumentsElementStub_ = false;
    hasUnmappedArgumentsElementStub_ = false;
    hasGenericProxyStub_ = false;
    hasDenseStub_ = false;
}

void
IonCache::disable()
{
    reset(Reprotect);
    this->disabled_ = 1;
}

void
GetPropertyIC::maybeDisable(bool emitted)
{
    if (emitted) {
        failedUpdates_ = 0;
        return;
    }

    if (!canAttachStub() && id().constant()) {
        // Don't disable the cache (and discard stubs) if we have a GETPROP and
        // attached the maximum number of stubs. This can happen when JS code
        // uses an AST-like data structure and accesses a field of a "base
        // class", like node.nodeType. This should be temporary until we handle
        // this case better, see bug 1107515.
        return;
    }

    if (++failedUpdates_ > MAX_FAILED_UPDATES) {
        JitSpew(JitSpew_IonIC, "Disable inline cache");
        disable();
    }
}

void
IonCache::reset(ReprotectCode reprotect)
{
    this->stubCount_ = 0;
    PatchJump(initialJump_, fallbackLabel_, reprotect);
    lastJump_ = initialJump_;
}

// Jump to failure if a value being written is not a property for obj/id.
static void
CheckTypeSetForWrite(MacroAssembler& masm, JSObject* obj, jsid id,
                     Register scratch, ConstantOrRegister value, Label* failure)
{
    TypedOrValueRegister valReg = value.reg();
    ObjectGroup* group = obj->group();
    MOZ_ASSERT(!group->unknownProperties());

    HeapTypeSet* propTypes = group->maybeGetProperty(id);
    MOZ_ASSERT(propTypes);

    // guardTypeSet can read from type sets without triggering read barriers.
    TypeSet::readBarrier(propTypes);

    masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch, failure);
}

static void
GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                JSObject* obj, Shape* shape, Register object, Register tempReg,
                ConstantOrRegister value, bool needsTypeBarrier, bool checkTypeset,
                Label* failures)
{
    TestMatchingReceiver(masm, attacher, object, obj, failures, needsTypeBarrier);

    // Guard that the incoming value is in the type set for the property
    // if a type barrier is required.
    if (checkTypeset) {
        MOZ_ASSERT(needsTypeBarrier);
        CheckTypeSetForWrite(masm, obj, shape->propid(), tempReg, value, failures);
    }

    NativeObject::slotsSizeMustNotOverflow();

    if (obj->is<UnboxedPlainObject>()) {
        obj = obj->as<UnboxedPlainObject>().maybeExpando();
        masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
        object = tempReg;
    }

    if (obj->as<NativeObject>().isFixedSlot(shape->slot())) {
        Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));

        if (cx->zone()->needsIncrementalBarrier())
            masm.callPreBarrier(addr, MIRType_Value);

        masm.storeConstantOrRegister(value, addr);
    } else {
        masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);

        Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(shape->slot()) * sizeof(Value));

        if (cx->zone()->needsIncrementalBarrier())
            masm.callPreBarrier(addr, MIRType_Value);

        masm.storeConstantOrRegister(value, addr);
    }

    attacher.jumpRejoin(masm);

    masm.bind(failures);
    attacher.jumpNextStub(masm);
}

bool
SetPropertyIC::attachSetSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                             HandleObject obj, HandleShape shape, bool checkTypeset)
{
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, shape->propid(), &failures);

    GenerateSetSlot(cx, masm, attacher, obj, shape, object(), temp(), value(), needsTypeBarrier(),
                    checkTypeset, &failures);

    return linkAndAttachStub(cx, masm, attacher, ion, "setting",
                             JS::TrackedOutcome::ICSetPropStub_Slot);
}

static bool
IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (!shape->hasSetterValue())
        return false;

    if (!shape->setterObject() || !shape->setterObject()->is<JSFunction>())
        return false;

    JSFunction& setter = shape->setterObject()->as<JSFunction>();
    if (!setter.isNative())
        return false;

    if (setter.jitInfo() && !setter.jitInfo()->needsOuterizedThisObject())
        return true;

    return !IsWindow(obj);
}

static bool
IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape)
{
    if (!shape || !IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (IsWindow(obj))
        return false;

    return shape->hasSetterValue() && shape->setterObject() &&
           shape->setterObject()->is<JSFunction>() &&
           shape->setterObject()->as<JSFunction>().hasJITCode();
}

static bool
IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape)
{
    if (!shape)
        return false;

    if (!IsCacheableProtoChainForIon(obj, holder))
        return false;

    if (shape->hasSlot())
        return false;

    if (shape->hasDefaultSetter())
        return false;

    if (shape->hasSetterValue())
        return false;

    // Despite the vehement claims of Shape.h that writable() is only relevant
    // for data descriptors, some SetterOps care desperately about its
    // value. The flag should be always true, apart from these rare instances.
    if (!shape->writable())
        return false;

    return true;
}

static bool
ReportStrictErrorOrWarning(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict,
                           JS::ObjectOpResult& result)
{
    return result.reportStrictErrorOrWarning(cx, obj, id, strict);
}

template <class FrameLayout>
void
EmitObjectOpResultCheck(MacroAssembler& masm, Label* failure, bool strict,
                        Register scratchReg,
                        Register argJSContextReg,
                        Register argObjReg,
                        Register argIdReg,
                        Register argStrictReg,
                        Register argResultReg)
{
    // if (!result) {
    Label noStrictError;
    masm.branch32(Assembler::Equal,
                  Address(masm.getStackPointer(),
                          FrameLayout::offsetOfObjectOpResult()),
                  Imm32(ObjectOpResult::OkCode),
                  &noStrictError);

    //     if (!ReportStrictErrorOrWarning(cx, obj, id, strict, &result))
    //         goto failure;
    masm.loadJSContext(argJSContextReg);
    masm.computeEffectiveAddress(
        Address(masm.getStackPointer(), FrameLayout::offsetOfObject()),
        argObjReg);
    masm.computeEffectiveAddress(
        Address(masm.getStackPointer(), FrameLayout::offsetOfId()),
        argIdReg);
    masm.move32(Imm32(strict), argStrictReg);
    masm.computeEffectiveAddress(
        Address(masm.getStackPointer(), FrameLayout::offsetOfObjectOpResult()),
        argResultReg);
    masm.setupUnalignedABICall(scratchReg);
    masm.passABIArg(argJSContextReg);
    masm.passABIArg(argObjReg);
    masm.passABIArg(argIdReg);
    masm.passABIArg(argStrictReg);
    masm.passABIArg(argResultReg);
    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ReportStrictErrorOrWarning));
    masm.branchIfFalseBool(ReturnReg, failure);

    // }
    masm.bind(&noStrictError);
}

static bool
ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, bool strict)
{
    RootedValue receiver(cx, ObjectValue(*proxy));
    ObjectOpResult result;
    return Proxy::set(cx, proxy, id, v, receiver, result)
           && result.checkStrictErrorOrWarning(cx, proxy, id, strict);
}

static bool
EmitCallProxySet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                 HandleId propId, LiveRegisterSet liveRegs, Register object,
                 ConstantOrRegister value, void* returnAddr, bool strict)
{
    MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);

    // Remaining registers should be free, but we still need to use |object| so
    // leave it alone.
    //
    // WARNING: We do not take() the register used by |value|, if any, so
    // regSet is going to re-allocate it. Hence the emitted code must not touch
    // any of the registers allocated from regSet until after the last use of
    // |value|. (We can't afford to take it, either, because x86.)
    AllocatableRegisterSet regSet(RegisterSet::All());
    regSet.take(AnyRegister(object));

    // ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
    //                  bool strict);
    Register argJSContextReg = regSet.takeAnyGeneral();
    Register argProxyReg     = regSet.takeAnyGeneral();
    Register argIdReg        = regSet.takeAnyGeneral();
    Register argValueReg     = regSet.takeAnyGeneral();
    Register argStrictReg    = regSet.takeAnyGeneral();

    Register scratch         = regSet.takeAnyGeneral();

    // Push stubCode for marking.
    attacher.pushStubCodePointer(masm);

    // Push args on stack so we can take pointers to make handles.
    // Push value before touching any other registers (see WARNING above).
    masm.Push(value);
    masm.moveStackPtrTo(argValueReg);

    masm.move32(Imm32(strict), argStrictReg);

    masm.Push(propId, scratch);
    masm.moveStackPtrTo(argIdReg);

    // Push object.
    masm.Push(object);
    masm.moveStackPtrTo(argProxyReg);

    masm.loadJSContext(argJSContextReg);

    if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
        return false;
    masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);

    // Make the call.
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(argJSContextReg);
    masm.passABIArg(argProxyReg);
    masm.passABIArg(argIdReg);
    masm.passABIArg(argValueReg);
    masm.passABIArg(argStrictReg);
    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxySetProperty));

    // Test for error.
    masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

    // masm.leaveExitFrame & pop locals
    masm.adjustStack(IonOOLProxyExitFrameLayout::Size());

    masm.icRestoreLive(liveRegs, aic);
    return true;
}

bool
SetPropertyIC::attachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                  HandleId id, void* returnAddr)
{
    MOZ_ASSERT(!hasGenericProxyStub());

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, id, &failures);
    {
        masm.branchTestObjectIsProxy(false, object(), temp(), &failures);

        // Remove the DOM proxies. They'll take care of themselves so this stub doesn't
        // catch too much. The failure case is actually Equal. Fall through to the failure code.
        masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), temp(),
                                          GetDOMProxyHandlerFamily(), &failures);
    }

    if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(), value(),
                          returnAddr, strict()))
    {
        return false;
    }

    attacher.jumpRejoin(masm);

    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    MOZ_ASSERT(!hasGenericProxyStub_);
    hasGenericProxyStub_ = true;

    return linkAndAttachStub(cx, masm, attacher, ion, "generic proxy set",
                             JS::TrackedOutcome::ICSetPropStub_GenericProxy);
}

bool
SetPropertyIC::attachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                      HandleObject obj, HandleId id, void* returnAddr)
{
    MOZ_ASSERT(IsCacheableDOMProxy(obj));

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    // Guard on the shape of the object.
    masm.branchPtr(Assembler::NotEqual,
                   Address(object(), JSObject::offsetOfShape()),
                   ImmGCPtr(obj->maybeShape()), &failures);

    // No need for more guards: we know this is a DOM proxy, since the shape
    // guard enforces a given JSClass, so just go ahead and emit the call to
    // ProxySet.

    if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
                          value(), returnAddr, strict()))
    {
        return false;
    }

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy shadowed set",
                             JS::TrackedOutcome::ICSetPropStub_DOMProxyShadowed);
}

static bool
GenerateCallSetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                   IonCache::StubAttacher& attacher, HandleObject obj, HandleObject holder,
                   HandleShape shape, bool strict, Register object, Register tempReg,
                   ConstantOrRegister value, Label* failure, LiveRegisterSet liveRegs,
                   void* returnAddr)
{
    // Generate prototype guards if needed.
    {
        // Generate prototype/shape guards.
        if (obj != holder)
            GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, tempReg, failure);

        masm.movePtr(ImmGCPtr(holder), tempReg);
        masm.branchPtr(Assembler::NotEqual,
                       Address(tempReg, JSObject::offsetOfShape()),
                       ImmGCPtr(holder->as<NativeObject>().lastProperty()),
                       failure);
    }

    // Good to go for invoking setter.

    MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);

    // Remaining registers should basically be free, but we need to use |object| still
    // so leave it alone.  And of course we need our value, if it's not a constant.
    AllocatableRegisterSet regSet(RegisterSet::All());
    if (!value.constant())
        regSet.take(value.reg());
    bool valueAliasesObject = !regSet.has(object);
    if (!valueAliasesObject)
        regSet.take(object);

    regSet.take(tempReg);

    // This is a slower stub path, and we're going to be doing a call anyway.  Don't need
    // to try so hard to not use the stack.  Scratch regs are just taken from the register
    // set not including the input, current value saved on the stack, and restored when
    // we're done with it.
    //
    // Be very careful not to use any of these before value is pushed, since they
    // might shadow.

    if (IsCacheableSetPropCallNative(obj, holder, shape)) {
        Register argJSContextReg = regSet.takeAnyGeneral();
        Register argVpReg        = regSet.takeAnyGeneral();

        MOZ_ASSERT(shape->hasSetterValue() && shape->setterObject() &&
                   shape->setterObject()->is<JSFunction>());
        JSFunction* target = &shape->setterObject()->as<JSFunction>();

        MOZ_ASSERT(target->isNative());

        Register argUintNReg = regSet.takeAnyGeneral();

        // Set up the call:
        //  bool (*)(JSContext*, unsigned, Value* vp)
        // vp[0] is callee/outparam
        // vp[1] is |this|
        // vp[2] is the value

        // Build vp and move the base into argVpReg.
        masm.Push(value);
        masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
        masm.Push(ObjectValue(*target));
        masm.moveStackPtrTo(argVpReg);

        // Preload other regs
        masm.loadJSContext(argJSContextReg);
        masm.move32(Imm32(1), argUintNReg);

        // Push data for GC marking
        masm.Push(argUintNReg);
        attacher.pushStubCodePointer(masm);

        if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
            return false;
        masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);

        // Make the call
        masm.setupUnalignedABICall(tempReg);
        masm.passABIArg(argJSContextReg);
        masm.passABIArg(argUintNReg);
        masm.passABIArg(argVpReg);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));

        // Test for failure.
        masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

        // masm.leaveExitFrame & pop locals.
        masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1));
    } else if (IsCacheableSetPropCallPropertyOp(obj, holder, shape)) {
        // We can't take all our registers up front, because on x86 we need 2
        // for the value, one for scratch, 5 for the arguments, which makes 8,
        // but we only have 7 to work with.  So only grab the ones we need
        // before we push value and release its reg back into the set.
        Register argResultReg = regSet.takeAnyGeneral();

        SetterOp target = shape->setterOp();
        MOZ_ASSERT(target);

        // JSSetterOp: bool fn(JSContext* cx, HandleObject obj,
        //                     HandleId id, HandleValue value, ObjectOpResult& result);

        // First, allocate an ObjectOpResult on the stack. We push this before
        // the stubCode pointer in order to match the layout of
        // IonOOLSetterOpExitFrameLayout.
        PushObjectOpResult(masm);
        masm.moveStackPtrTo(argResultReg);

        attacher.pushStubCodePointer(masm);

        // Push args on stack so we can take pointers to make handles.
        if (value.constant()) {
            masm.Push(value.value());
        } else {
            masm.Push(value.reg());
            if (!valueAliasesObject)
                regSet.add(value.reg());
        }

        // OK, now we can grab our remaining registers and grab the pointer to
        // what we just pushed into one of them.
        Register argJSContextReg = regSet.takeAnyGeneral();
        Register argValueReg     = regSet.takeAnyGeneral();
        // We can just reuse the "object" register for argObjReg
        Register argObjReg       = object;
        Register argIdReg        = regSet.takeAnyGeneral();
        masm.moveStackPtrTo(argValueReg);

        // push canonical jsid from shape instead of propertyname.
        masm.Push(shape->propid(), argIdReg);
        masm.moveStackPtrTo(argIdReg);

        masm.Push(object);
        masm.moveStackPtrTo(argObjReg);

        masm.loadJSContext(argJSContextReg);

        if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
            return false;
        masm.enterFakeExitFrame(IonOOLSetterOpExitFrameLayoutToken);

        // Make the call.
        masm.setupUnalignedABICall(tempReg);
        masm.passABIArg(argJSContextReg);
        masm.passABIArg(argObjReg);
        masm.passABIArg(argIdReg);
        masm.passABIArg(argValueReg);
        masm.passABIArg(argResultReg);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));

        // Test for error.
        masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());

        // Test for strict failure. We emit the check even in non-strict mode
        // in order to pick up the warning if extraWarnings is enabled.
        EmitObjectOpResultCheck<IonOOLSetterOpExitFrameLayout>(masm, masm.exceptionLabel(),
                                                               strict, tempReg,
                                                               argJSContextReg, argObjReg,
                                                               argIdReg, argValueReg,
                                                               argResultReg);

        // masm.leaveExitFrame & pop locals.
        masm.adjustStack(IonOOLSetterOpExitFrameLayout::Size());
    } else {
        MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));

        JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
        uint32_t framePushedBefore = masm.framePushed();

        // Construct IonAccessorICFrameLayout.
        uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS);
        attacher.pushStubCodePointer(masm);
        masm.Push(Imm32(descriptor));
        masm.Push(ImmPtr(returnAddr));

        // The JitFrameLayout pushed below will be aligned to JitStackAlignment,
        // so we just have to make sure the stack is aligned after we push the
        // |this| + argument Values.
        uint32_t numArgs = Max(size_t(1), target->nargs());
        uint32_t argSize = (numArgs + 1) * sizeof(Value);
        uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
        MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
        MOZ_ASSERT(padding < JitStackAlignment);
        masm.reserveStack(padding);

        for (size_t i = 1; i < target->nargs(); i++)
            masm.Push(UndefinedValue());
        masm.Push(value);
        masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));

        masm.movePtr(ImmGCPtr(target), tempReg);

        descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC);
        masm.Push(Imm32(1)); // argc
        masm.Push(tempReg);
        masm.Push(Imm32(descriptor));

        // Check stack alignment. Add sizeof(uintptr_t) for the return address.
        MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);

        // The setter has JIT code now and we will only discard the setter's JIT
        // code when discarding all JIT code in the Zone, so we can assume it'll
        // still have JIT code.
        MOZ_ASSERT(target->hasJITCode());
        masm.loadPtr(Address(tempReg, JSFunction::offsetOfNativeOrScript()), tempReg);
        masm.loadBaselineOrIonRaw(tempReg, tempReg, nullptr);
        masm.callJit(tempReg);

        masm.freeStack(masm.framePushed() - framePushedBefore);
    }

    masm.icRestoreLive(liveRegs, aic);
    return true;
}

static bool
IsCacheableDOMProxyUnshadowedSetterCall(JSContext* cx, HandleObject obj, HandleId id,
                                        MutableHandleObject holder, MutableHandleShape shape)
{
    MOZ_ASSERT(IsCacheableDOMProxy(obj));

    RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull());
    if (!checkObj)
        return false;

    if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
        return false;

    if (!holder)
        return false;

    return IsCacheableSetPropCallNative(checkObj, holder, shape) ||
           IsCacheableSetPropCallPropertyOp(checkObj, holder, shape) ||
           IsCacheableSetPropCallScripted(checkObj, holder, shape);
}

bool
SetPropertyIC::attachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                        HandleObject obj, HandleId id, void* returnAddr)
{
    MOZ_ASSERT(IsCacheableDOMProxy(obj));

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    emitIdGuard(masm, id, &failures);

    // Guard on the shape of the object.
    masm.branchPtr(Assembler::NotEqual,
                   Address(object(), JSObject::offsetOfShape()),
                   ImmGCPtr(obj->maybeShape()), &failures);

    // Guard that our expando object hasn't started shadowing this property.
    CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);

    RootedObject holder(cx);
    RootedShape shape(cx);
    if (IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, id, &holder, &shape)) {
        if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
                                object(), temp(), value(), &failures, liveRegs_, returnAddr))
        {
            return false;
        }
    } else {
        // Either there was no proto, or the property wasn't appropriately found on it.
        // Drop back to just a call to Proxy::set().
        if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
                            value(), returnAddr, strict()))
        {
            return false;
        }
    }

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy unshadowed set",
                             JS::TrackedOutcome::ICSetPropStub_DOMProxyUnshadowed);
}

bool
SetPropertyIC::attachCallSetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                HandleObject obj, HandleObject holder, HandleShape shape,
                                void* returnAddr)
{
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failure;
    emitIdGuard(masm, shape->propid(), &failure);
    TestMatchingReceiver(masm, attacher, object(), obj, &failure);

    if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
                            object(), temp(), value(), &failure, liveRegs_, returnAddr))
    {
        return false;
    }

    // Rejoin jump.
    attacher.jumpRejoin(masm);

    // Jump to next stub.
    masm.bind(&failure);
    attacher.jumpNextStub(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "setter call",
                             JS::TrackedOutcome::ICSetPropStub_CallSetter);
}

static void
GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                JSObject* obj, Shape* oldShape, ObjectGroup* oldGroup,
                Register object, Register tempReg, ConstantOrRegister value,
                bool checkTypeset, Label* failures)
{
    // Use a modified version of TestMatchingReceiver that uses the old shape and group.
    masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
    if (obj->maybeShape()) {
        masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
    } else {
        MOZ_ASSERT(obj->is<UnboxedPlainObject>());

        Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
        masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);

        masm.loadPtr(expandoAddress, tempReg);
        masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
    }

    Shape* newShape = obj->maybeShape();
    if (!newShape)
        newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();

    // Guard that the incoming value is in the type set for the property
    // if a type barrier is required.
    if (checkTypeset)
        CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);

    // Guard shapes along prototype chain.
    JSObject* proto = obj->getProto();
    Register protoReg = tempReg;
    bool first = true;
    while (proto) {
        Shape* protoShape = proto->as<NativeObject>().lastProperty();

        // Load next prototype.
        masm.loadObjProto(first ? object : protoReg, protoReg);
        first = false;

        // Ensure that its shape matches.
        masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, failures);

        proto = proto->getProto();
    }

    // Call a stub to (re)allocate dynamic slots, if necessary.
    uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
                                  ? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
                                  : obj->as<NativeObject>().numDynamicSlots();
    if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) {
        AllocatableRegisterSet regs(RegisterSet::Volatile());
        LiveRegisterSet save(regs.asLiveSet());
        masm.PushRegsInMask(save);

        // Get 2 temp registers, without clobbering the object register.
        regs.takeUnchecked(object);
        Register temp1 = regs.takeAnyGeneral();
        Register temp2 = regs.takeAnyGeneral();

        if (obj->is<UnboxedPlainObject>()) {
            // Pass the expando object to the stub.
            masm.Push(object);
            masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
        }

        masm.setupUnalignedABICall(temp1);
        masm.loadJSContext(temp1);
        masm.passABIArg(temp1);
        masm.passABIArg(object);
        masm.move32(Imm32(newNumDynamicSlots), temp2);
        masm.passABIArg(temp2);
        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NativeObject::growSlotsDontReportOOM));

        // Branch on ReturnReg before restoring volatile registers, so
        // ReturnReg isn't clobbered.
        uint32_t framePushedAfterCall = masm.framePushed();
        Label allocFailed, allocDone;
        masm.branchIfFalseBool(ReturnReg, &allocFailed);
        masm.jump(&allocDone);

        masm.bind(&allocFailed);
        if (obj->is<UnboxedPlainObject>())
            masm.Pop(object);
        masm.PopRegsInMask(save);
        masm.jump(failures);

        masm.bind(&allocDone);
        masm.setFramePushed(framePushedAfterCall);
        if (obj->is<UnboxedPlainObject>())
            masm.Pop(object);
        masm.PopRegsInMask(save);
    }

    bool popObject = false;

    if (obj->is<UnboxedPlainObject>()) {
        masm.push(object);
        popObject = true;
        obj = obj->as<UnboxedPlainObject>().maybeExpando();
        masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
    }

    // Write the object or expando object's new shape.
    Address shapeAddr(object, JSObject::offsetOfShape());
    if (cx->zone()->needsIncrementalBarrier())
        masm.callPreBarrier(shapeAddr, MIRType_Shape);
    masm.storePtr(ImmGCPtr(newShape), shapeAddr);

    if (oldGroup != obj->group()) {
        MOZ_ASSERT(!obj->is<UnboxedPlainObject>());

        // Changing object's group from a partially to fully initialized group,
        // per the acquired properties analysis. Only change the group if the
        // old group still has a newScript.
        Label noTypeChange, skipPop;

        masm.loadPtr(Address(object, JSObject::offsetOfGroup()), tempReg);
        masm.branchPtr(Assembler::Equal,
                       Address(tempReg, ObjectGroup::offsetOfAddendum()),
                       ImmWord(0),
                       &noTypeChange);

        Address groupAddr(object, JSObject::offsetOfGroup());
        if (cx->zone()->needsIncrementalBarrier())
            masm.callPreBarrier(groupAddr, MIRType_ObjectGroup);
        masm.storePtr(ImmGCPtr(obj->group()), groupAddr);

        masm.bind(&noTypeChange);
    }

    // Set the value on the object. Since this is an add, obj->lastProperty()
    // must be the shape of the property we are adding.
    NativeObject::slotsSizeMustNotOverflow();
    if (obj->as<NativeObject>().isFixedSlot(newShape->slot())) {
        Address addr(object, NativeObject::getFixedSlotOffset(newShape->slot()));
        masm.storeConstantOrRegister(value, addr);
    } else {
        masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);

        Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(newShape->slot()) * sizeof(Value));
        masm.storeConstantOrRegister(value, addr);
    }

    if (popObject)
        masm.pop(object);

    // Success.
    attacher.jumpRejoin(masm);

    // Failure.
    masm.bind(failures);

    attacher.jumpNextStub(masm);
}

bool
SetPropertyIC::attachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                             HandleObject obj, HandleId id, HandleShape oldShape,
                             HandleObjectGroup oldGroup, bool checkTypeset)
{
    MOZ_ASSERT_IF(!needsTypeBarrier(), !checkTypeset);

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, id, &failures);

    GenerateAddSlot(cx, masm, attacher, obj, oldShape, oldGroup, object(), temp(), value(),
                    checkTypeset, &failures);
    return linkAndAttachStub(cx, masm, attacher, ion, "adding",
                             JS::TrackedOutcome::ICSetPropStub_AddSlot);
}

static bool
CanInlineSetPropTypeCheck(JSObject* obj, jsid id, ConstantOrRegister val, bool* checkTypeset)
{
    bool shouldCheck = false;
    ObjectGroup* group = obj->group();
    if (!group->unknownProperties()) {
        HeapTypeSet* propTypes = group->maybeGetProperty(id);
        if (!propTypes)
            return false;
        if (!propTypes->unknown()) {
            if (obj->isSingleton() && !propTypes->nonConstantProperty())
                return false;
            shouldCheck = true;
            if (val.constant()) {
                // If the input is a constant, then don't bother if the barrier will always fail.
                if (!propTypes->hasType(TypeSet::GetValueType(val.value())))
                    return false;
                shouldCheck = false;
            } else {
                TypedOrValueRegister reg = val.reg();
                // We can do the same trick as above for primitive types of specialized registers.
                // TIs handling of objects is complicated enough to warrant a runtime
                // check, as we can't statically handle the case where the typeset
                // contains the specific object, but doesn't have ANYOBJECT set.
                if (reg.hasTyped() && reg.type() != MIRType_Object) {
                    JSValueType valType = ValueTypeFromMIRType(reg.type());
                    if (!propTypes->hasType(TypeSet::PrimitiveType(valType)))
                        return false;
                    shouldCheck = false;
                }
            }
        }
    }

    *checkTypeset = shouldCheck;
    return true;
}

static bool
IsPropertySetInlineable(NativeObject* obj, HandleId id, MutableHandleShape pshape,
                        ConstantOrRegister val, bool needsTypeBarrier, bool* checkTypeset)
{
    // CanInlineSetPropTypeCheck assumes obj has a non-lazy group.
    MOZ_ASSERT(!obj->hasLazyGroup());

    // Do a pure non-proto chain climbing lookup. See note in
    // CanAttachNativeGetProp.
    pshape.set(obj->lookupPure(id));

    if (!pshape)
        return false;

    if (!pshape->hasSlot())
        return false;

    if (!pshape->hasDefaultSetter())
        return false;

    if (!pshape->writable())
        return false;

    *checkTypeset = false;
    if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
        return false;

    return true;
}

static bool
PrototypeChainShadowsPropertyAdd(JSContext* cx, JSObject* obj, jsid id)
{
    // Walk up the object prototype chain and ensure that all prototypes
    // are native, and that all prototypes have no getter or setter
    // defined on the property
    for (JSObject* proto = obj->getProto(); proto; proto = proto->getProto()) {
        // If prototype is non-native, don't optimize
        if (!proto->isNative())
            return true;

        // If prototype defines this property in a non-plain way, don't optimize
        Shape* protoShape = proto->as<NativeObject>().lookupPure(id);
        if (protoShape && !protoShape->hasDefaultSetter())
            return true;

        // Otherwise, if there's no such property, watch out for a resolve
        // hook that would need to be invoked and thus prevent inlining of
        // property addition.
        if (ClassMayResolveId(cx->names(), proto->getClass(), id, proto))
             return true;
    }

    return false;
}

static bool
IsPropertyAddInlineable(JSContext* cx, NativeObject* obj, HandleId id, ConstantOrRegister val,
                        HandleShape oldShape, bool needsTypeBarrier, bool* checkTypeset)
{
    // If the shape of the object did not change, then this was not an add.
    if (obj->lastProperty() == oldShape)
        return false;

    Shape* shape = obj->lookupPure(id);
    if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter())
        return false;

    // If we have a shape at this point and the object's shape changed, then
    // the shape must be the one we just added.
    MOZ_ASSERT(shape == obj->lastProperty());

    // Watch out for resolve hooks.
    if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
        return false;

    // Likewise for an addProperty hook, since we'll need to invoke it.
    if (obj->getClass()->addProperty)
        return false;

    if (!obj->nonProxyIsExtensible() || !shape->writable())
        return false;

    if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
        return false;

    // Don't attach if we are adding a property to an object which the new
    // script properties analysis hasn't been performed for yet, as there
    // may be a group change required here afterwards.
    if (obj->group()->newScript() && !obj->group()->newScript()->analyzed())
        return false;

    *checkTypeset = false;
    if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
        return false;

    return true;
}

static SetPropertyIC::NativeSetPropCacheability
CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
                       bool needsTypeBarrier, MutableHandleObject holder,
                       MutableHandleShape shape, bool* checkTypeset)
{
    // See if the property exists on the object.
    if (obj->isNative() && IsPropertySetInlineable(&obj->as<NativeObject>(), id, shape, val,
                                                   needsTypeBarrier, checkTypeset))
    {
        return SetPropertyIC::CanAttachSetSlot;
    }

    // If we couldn't find the property on the object itself, do a full, but
    // still pure lookup for setters.
    if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
        return SetPropertyIC::CanAttachNone;

    // If the object doesn't have the property, we don't know if we can attach
    // a stub to add the property until we do the VM call to add. If the
    // property exists as a data property on the prototype, we should add
    // a new, shadowing property.
    if (obj->isNative() && (!shape || (obj != holder && holder->isNative() &&
                                       shape->hasDefaultSetter() && shape->hasSlot())))
    {
        return SetPropertyIC::MaybeCanAttachAddSlot;
    }

    if (IsImplicitNonNativeProperty(shape))
        return SetPropertyIC::CanAttachNone;

    if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) ||
        IsCacheableSetPropCallNative(obj, holder, shape) ||
        IsCacheableSetPropCallScripted(obj, holder, shape))
    {
        return SetPropertyIC::CanAttachCallSetter;
    }

    return SetPropertyIC::CanAttachNone;
}

static void
GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                   JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
                   Register object, Register tempReg, ConstantOrRegister value, bool checkTypeset,
                   Label* failures)
{
    // Guard on the type of the object.
    masm.branchPtr(Assembler::NotEqual,
                   Address(object, JSObject::offsetOfGroup()),
                   ImmGCPtr(obj->group()), failures);

    if (checkTypeset)
        CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);

    Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);

    if (cx->zone()->needsIncrementalBarrier()) {
        if (unboxedType == JSVAL_TYPE_OBJECT)
            masm.callPreBarrier(address, MIRType_Object);
        else if (unboxedType == JSVAL_TYPE_STRING)
            masm.callPreBarrier(address, MIRType_String);
        else
            MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
    }

    masm.storeUnboxedProperty(address, unboxedType, value, failures);

    attacher.jumpRejoin(masm);

    masm.bind(failures);
    attacher.jumpNextStub(masm);
}

static bool
CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
                    bool needsTypeBarrier, bool* checkTypeset,
                    uint32_t* unboxedOffset, JSValueType* unboxedType)
{
    if (!obj->is<UnboxedPlainObject>())
        return false;

    const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
    if (property) {
        *checkTypeset = false;
        if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
            return false;
        *unboxedOffset = property->offset;
        *unboxedType = property->type;
        return true;
    }

    return false;
}

static bool
CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
                           bool needsTypeBarrier, bool* checkTypeset, Shape** pshape)
{
    if (!obj->is<UnboxedPlainObject>())
        return false;

    Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
    if (!expando)
        return false;

    Shape* shape = expando->lookupPure(id);
    if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable())
        return false;

    *checkTypeset = false;
    if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
        return false;

    *pshape = shape;
    return true;
}

static bool
CanAttachAddUnboxedExpando(JSContext* cx, HandleObject obj, HandleShape oldShape,
                           HandleId id, ConstantOrRegister val,
                           bool needsTypeBarrier, bool* checkTypeset)
{
    if (!obj->is<UnboxedPlainObject>())
        return false;

    Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
    if (!expando || expando->inDictionaryMode())
        return false;

    Shape* newShape = expando->lastProperty();
    if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape)
        return false;

    MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable());

    if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
        return false;

    *checkTypeset = false;
    if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
        return false;

    return true;
}

bool
SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                HandleObject obj, HandleId id, bool* emitted)
{
    MOZ_ASSERT(!*emitted);

    bool checkTypeset = false;
    uint32_t unboxedOffset;
    JSValueType unboxedType;
    if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset,
                             &unboxedOffset, &unboxedType))
    {
        return true;
    }

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    emitIdGuard(masm, id, &failures);

    GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
                       object(), temp(), value(), checkTypeset, &failures);
    return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
                             JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
}

bool
SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                              HandleObject obj, HandleId id, bool* emitted)
{
    MOZ_ASSERT(!*emitted);

    if (!obj->is<ProxyObject>())
        return true;

    void* returnAddr = GetReturnAddressToIonCode(cx);
    if (IsCacheableDOMProxy(obj)) {
        DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
        if (shadows == ShadowCheckFailed)
            return false;

        if (DOMProxyIsShadowing(shadows)) {
            if (!attachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr))
                return false;
            *emitted = true;
            return true;
        }

        MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
        if (shadows == DoesntShadowUnique)
            reset(Reprotect);
        if (!attachDOMProxyUnshadowed(cx, outerScript, ion, obj, id, returnAddr))
            return false;
        *emitted = true;
        return true;
    }

    if (hasGenericProxyStub())
        return true;

    if (!attachGenericProxy(cx, outerScript, ion, id, returnAddr))
        return false;
    *emitted = true;
    return true;
}

bool
SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
                               HandleObject obj, HandleId id, bool* emitted, bool* tryNativeAddSlot)
{
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(!*tryNativeAddSlot);

    RootedShape shape(cx);
    RootedObject holder(cx);
    bool checkTypeset = false;
    NativeSetPropCacheability canCache = CanAttachNativeSetProp(cx, obj, id, value(), needsTypeBarrier(),
                                                                &holder, &shape, &checkTypeset);
    switch (canCache) {
      case CanAttachNone:
        return true;

      case CanAttachSetSlot: {
        RootedNativeObject nobj(cx, &obj->as<NativeObject>());
        if (!attachSetSlot(cx, outerScript, ion, nobj, shape, checkTypeset))
            return false;
        *emitted = true;
        return true;
      }

      case CanAttachCallSetter: {
        void* returnAddr = GetReturnAddressToIonCode(cx);
        if (!attachCallSetter(cx, outerScript, ion, obj, holder, shape, returnAddr))
            return false;
        *emitted = true;
        return true;
      }

      case MaybeCanAttachAddSlot:
        *tryNativeAddSlot = true;
        return true;
    }

    MOZ_CRASH("Unreachable");
}

bool
SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleId id, bool* emitted)
{
    MOZ_ASSERT(!*emitted);

    RootedShape shape(cx);
    bool checkTypeset = false;
    if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(),
                                    &checkTypeset, shape.address()))
    {
        return true;
    }

    if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
        return false;
    *emitted = true;
    return true;
}

bool
SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
                             HandleObject obj, HandleValue idval, HandleValue value,
                             MutableHandleId id, bool* emitted, bool* tryNativeAddSlot)
{
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(!*tryNativeAddSlot);

    if (!canAttachStub() || obj->watched())
        return true;

    bool nameOrSymbol;
    if (!ValueToNameOrSymbolId(cx, idval, id, &nameOrSymbol))
        return false;

    if (nameOrSymbol) {
        if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, emitted))
            return false;

        if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
            return false;

        if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
            return false;

        if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
            return false;
    }

    if (idval.isInt32()) {
        if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
            return false;
        if (!*emitted &&
            !tryAttachTypedArrayElement(cx, outerScript, ion, obj, idval, value, emitted))
        {
            return false;
        }
    }

    return true;
}

bool
SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                HandleObject obj, HandleId id, HandleObjectGroup oldGroup,
                                HandleShape oldShape, bool tryNativeAddSlot, bool* emitted)
{
    MOZ_ASSERT(!*emitted);

    if (!canAttachStub())
        return true;

    if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id))
        return true;

    // A GC may have caused cache.value() to become stale as it is not traced.
    // In this case the IonScript will have been invalidated, so check for that.
    // Assert no further GC is possible past this point.
    JS::AutoAssertNoAlloc nogc;
    if (ion->invalidated())
        return true;

    // The property did not exist before, now we can try to inline the property add.
    bool checkTypeset = false;
    if (tryNativeAddSlot &&
        IsPropertyAddInlineable(cx, &obj->as<NativeObject>(), id, value(), oldShape,
                                needsTypeBarrier(), &checkTypeset))
    {
        if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
            return false;
        *emitted = true;
        return true;
    }

    checkTypeset = false;
    if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
                                   &checkTypeset))
    {
        if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
            return false;
        *emitted = true;
        return true;
    }

    return true;
}

bool
SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj,
                      HandleValue idval, HandleValue value)
{
    IonScript* ion = outerScript->ionScript();
    SetPropertyIC& cache = ion->getCache(cacheIndex).toSetProperty();

    // Remember the old group and shape if we may attach an add-property stub.
    // Also, some code under tryAttachStub depends on obj having a non-lazy
    // group, see for instance CanInlineSetPropTypeCheck.
    RootedObjectGroup oldGroup(cx);
    RootedShape oldShape(cx);
    if (cache.canAttachStub()) {
        oldGroup = obj->getGroup(cx);
        if (!oldGroup)
            return false;

        oldShape = obj->maybeShape();
        if (obj->is<UnboxedPlainObject>()) {
            MOZ_ASSERT(!oldShape);
            if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
                oldShape = expando->lastProperty();
        }
    }

    RootedId id(cx);
    bool emitted = false;
    bool tryNativeAddSlot = false;
    if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, value, &id, &emitted,
                             &tryNativeAddSlot))
    {
        return false;
    }

    // Set/Add the property on the object, the inlined cache are setup for the next execution.
    if (JSOp(*cache.pc()) == JSOP_INITGLEXICAL) {
        RootedScript script(cx);
        jsbytecode* pc;
        cache.getScriptedLocation(&script, &pc);
        MOZ_ASSERT(!script->hasNonSyntacticScope());
        InitGlobalLexicalOperation(cx, &cx->global()->lexicalScope(), script, pc, value);
    } else if (*cache.pc() == JSOP_SETELEM || *cache.pc() == JSOP_STRICTSETELEM) {
        if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
            return false;
    } else {
        RootedPropertyName name(cx, idval.toString()->asAtom().asPropertyName());
        if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
            return false;
    }

    if (!emitted &&
        !cache.tryAttachAddSlot(cx, outerScript, ion, obj, id, oldGroup, oldShape,
                                tryNativeAddSlot, &emitted))
    {
        return false;
    }

    if (!emitted)
        JitSpew(JitSpew_IonIC, "Failed to attach SETPROP cache");

    return true;
}

void
SetPropertyIC::reset(ReprotectCode reprotect)
{
    IonCache::reset(reprotect);
    hasGenericProxyStub_ = false;
    hasDenseStub_ = false;
}

static bool
GenerateDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                     JSObject* obj, const Value& idval, Register object,
                     TypedOrValueRegister index, TypedOrValueRegister output)
{
    Label failures;

    // Guard object's shape.
    RootedShape shape(cx, obj->as<NativeObject>().lastProperty());
    if (!shape)
        return false;
    masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);

    // Ensure the index is an int32 value.
    Register indexReg = InvalidReg;

    if (index.hasValue()) {
        indexReg = output.scratchReg().gpr();
        MOZ_ASSERT(indexReg != InvalidReg);
        ValueOperand val = index.valueReg();

        masm.branchTestInt32(Assembler::NotEqual, val, &failures);

        // Unbox the index.
        masm.unboxInt32(val, indexReg);
    } else {
        MOZ_ASSERT(!index.typedReg().isFloat());
        indexReg = index.typedReg().gpr();
    }

    // Load elements vector.
    masm.push(object);
    masm.loadPtr(Address(object, NativeObject::offsetOfElements()), object);

    Label hole;

    // Guard on the initialized length.
    Address initLength(object, ObjectElements::offsetOfInitializedLength());
    masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);

    // Check for holes & load the value.
    masm.loadElementTypedOrValue(BaseObjectElementIndex(object, indexReg),
                                 output, true, &hole);

    masm.pop(object);
    attacher.jumpRejoin(masm);

    // All failures flow to here.
    masm.bind(&hole);
    masm.pop(object);
    masm.bind(&failures);

    attacher.jumpNextStub(masm);

    return true;
}

bool
GetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject obj, HandleValue idval, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (hasDenseStub())
        return true;

    if (!obj->isNative() || !idval.isInt32())
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);
    if (!GenerateDenseElement(cx, masm, attacher, obj, idval, object(), id().reg(), output()))
        return false;

    setHasDenseStub();
    return linkAndAttachStub(cx, masm, attacher, ion, "dense array",
                             JS::TrackedOutcome::ICGetElemStub_Dense);
}


/* static */ bool
GetPropertyIC::canAttachDenseElementHole(JSObject* obj, HandleValue idval, TypedOrValueRegister output)
{
    if (!idval.isInt32() || idval.toInt32() < 0)
        return false;

    if (!output.hasValue())
        return false;

    if (!obj->isNative())
        return false;

    if (obj->as<NativeObject>().getDenseInitializedLength() == 0)
        return false;

    while (obj) {
        if (obj->isIndexed())
            return false;

        if (ClassCanHaveExtraProperties(obj->getClass()))
            return false;

        JSObject* proto = obj->getProto();
        if (!proto)
            break;

        if (!proto->isNative())
            return false;

        // Make sure objects on the prototype don't have dense elements.
        if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
            return false;

        obj = proto;
    }

    return true;
}

static bool
GenerateDenseElementHole(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                         IonScript* ion, JSObject* obj, HandleValue idval,
                         Register object, TypedOrValueRegister index, TypedOrValueRegister output)
{
    MOZ_ASSERT(GetPropertyIC::canAttachDenseElementHole(obj, idval, output));

    Register scratchReg = output.valueReg().scratchReg();

    // Guard on the shape and group, to prevent non-dense elements from appearing.
    Label failures;
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                   Address(object, JSObject::offsetOfShape()),
                                   ImmGCPtr(obj->as<NativeObject>().lastProperty()), &failures);


    if (obj->hasUncacheableProto()) {
        masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
        Address proto(scratchReg, ObjectGroup::offsetOfProto());
        masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->getProto()), &failures);
    }

    JSObject* pobj = obj->getProto();
    while (pobj) {
        MOZ_ASSERT(pobj->as<NativeObject>().lastProperty());

        masm.movePtr(ImmGCPtr(pobj), scratchReg);
        if (pobj->hasUncacheableProto()) {
            MOZ_ASSERT(!pobj->isSingleton());
            Address groupAddr(scratchReg, JSObject::offsetOfGroup());
            masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
        }

        // Make sure the shape matches, to avoid non-dense elements.
        masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()),
                       ImmGCPtr(pobj->as<NativeObject>().lastProperty()), &failures);

        // Load elements vector.
        masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);

        // Also make sure there are no dense elements.
        Label hole;
        Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
        masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);

        pobj = pobj->getProto();
    }

    // Ensure the index is an int32 value.
    Register indexReg;
    if (index.hasValue()) {
        // Unbox the index.
        ValueOperand val = index.valueReg();
        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
        indexReg = scratchReg;
        masm.unboxInt32(val, indexReg);
    } else {
        MOZ_ASSERT(index.type() == MIRType_Int32);
        indexReg = index.typedReg().gpr();
    }

    // Make sure index is nonnegative.
    masm.branch32(Assembler::LessThan, indexReg, Imm32(0), &failures);

    // Save the object register.
    Register elementsReg = object;
    masm.push(object);

    // Load elements vector.
    masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg);

    // Guard on the initialized length.
    Label hole;
    Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength());
    masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);

    // Load the value.
    Label done;
    masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg());
    masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);

    // Load undefined for the hole.
    masm.bind(&hole);
    masm.moveValue(UndefinedValue(), output.valueReg());

    masm.bind(&done);
    // Restore the object register.
    if (elementsReg == object)
        masm.pop(object);
    attacher.jumpRejoin(masm);

    // All failure flows through here.
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return true;
}

bool
GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                         HandleObject obj, HandleValue idval, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!monitoredResult())
        return true;

    if (!canAttachDenseElementHole(obj, idval, output()))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);
    GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), id().reg(), output());

    return linkAndAttachStub(cx, masm, attacher, ion, "dense hole",
                             JS::TrackedOutcome::ICGetElemStub_DenseHole);
}

/* static */ bool
GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
                                                   TypedOrValueRegister output)
{
    if (!IsAnyTypedArray(obj) && !obj->is<UnboxedArrayObject>())
        return false;

    MOZ_ASSERT(idval.isInt32() || idval.isString());

    // Don't emit a stub if the access is out of bounds. We make to make
    // certain that we monitor the type coming out of the typed array when
    // we generate the stub. Out of bounds accesses will hit the fallback
    // path.
    uint32_t index;
    if (idval.isInt32()) {
        index = idval.toInt32();
    } else {
        index = GetIndexFromString(idval.toString());
        if (index == UINT32_MAX)
            return false;
    }

    if (IsAnyTypedArray(obj)) {
        if (index >= AnyTypedArrayLength(obj))
            return false;

        // The output register is not yet specialized as a float register, the only
        // way to accept float typed arrays for now is to return a Value type.
        uint32_t arrayType = AnyTypedArrayType(obj);
        if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64)
            return output.hasValue();

        return output.hasValue() || !output.typedReg().isFloat();
    }

    if (index >= obj->as<UnboxedArrayObject>().initializedLength())
        return false;

    JSValueType elementType = obj->as<UnboxedArrayObject>().elementType();
    if (elementType == JSVAL_TYPE_DOUBLE)
        return output.hasValue();

    return output.hasValue() || !output.typedReg().isFloat();
}

static void
GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
                                      IonCache::StubAttacher& attacher,
                                      HandleObject array, const Value& idval, Register object,
                                      ConstantOrRegister index, TypedOrValueRegister output,
                                      bool allowDoubleResult)
{
    MOZ_ASSERT(GetPropertyIC::canAttachTypedOrUnboxedArrayElement(array, idval, output));

    Label failures;

    TestMatchingReceiver(masm, attacher, object, array, &failures);

    // Decide to what type index the stub should be optimized
    Register tmpReg = output.scratchReg().gpr();
    MOZ_ASSERT(tmpReg != InvalidReg);
    Register indexReg = tmpReg;
    if (idval.isString()) {
        MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);

        if (index.constant()) {
            MOZ_ASSERT(idval == index.value());
            masm.move32(Imm32(GetIndexFromString(idval.toString())), indexReg);
        } else {
            // Part 1: Get the string into a register
            Register str;
            if (index.reg().hasValue()) {
                ValueOperand val = index.reg().valueReg();
                masm.branchTestString(Assembler::NotEqual, val, &failures);

                str = masm.extractString(val, indexReg);
            } else {
                MOZ_ASSERT(!index.reg().typedReg().isFloat());
                str = index.reg().typedReg().gpr();
            }

            // Part 2: Call to translate the str into index
            AllocatableRegisterSet regs(RegisterSet::Volatile());
            LiveRegisterSet save(regs.asLiveSet());
            masm.PushRegsInMask(save);
            regs.takeUnchecked(str);

            Register temp = regs.takeAnyGeneral();

            masm.setupUnalignedABICall(temp);
            masm.passABIArg(str);
            masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetIndexFromString));
            masm.mov(ReturnReg, indexReg);

            LiveRegisterSet ignore;
            ignore.add(indexReg);
            masm.PopRegsInMaskIgnore(save, ignore);

            masm.branch32(Assembler::Equal, indexReg, Imm32(UINT32_MAX), &failures);
        }
    } else {
        MOZ_ASSERT(idval.isInt32());
        MOZ_ASSERT(!index.constant());

        if (index.reg().hasValue()) {
            ValueOperand val = index.reg().valueReg();
            masm.branchTestInt32(Assembler::NotEqual, val, &failures);

            // Unbox the index.
            masm.unboxInt32(val, indexReg);
        } else {
            MOZ_ASSERT(!index.reg().typedReg().isFloat());
            indexReg = index.reg().typedReg().gpr();
        }
    }

    Label popObjectAndFail;

    if (IsAnyTypedArray(array)) {
        // Guard on the initialized length.
        Address length(object, TypedArrayObject::lengthOffset());
        masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);

        // Save the object register on the stack in case of failure.
        Register elementReg = object;
        masm.push(object);

        // Load elements vector.
        masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);

        // Load the value. We use an invalid register because the destination
        // register is necessary a non double register.
        Scalar::Type arrayType = AnyTypedArrayType(array);
        int width = Scalar::byteSize(arrayType);
        BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
        if (output.hasValue()) {
            masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
                                    elementReg, &popObjectAndFail);
        } else {
            masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
        }
    } else {
        // Save the object register on the stack in case of failure.
        masm.push(object);

        // Guard on the initialized length.
        masm.load32(Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), object);
        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), object);
        masm.branch32(Assembler::BelowOrEqual, object, indexReg, &popObjectAndFail);

        // Load elements vector.
        Register elementReg = object;
        masm.loadPtr(Address(masm.getStackPointer(), 0), object);
        masm.loadPtr(Address(object, UnboxedArrayObject::offsetOfElements()), elementReg);

        JSValueType elementType = array->as<UnboxedArrayObject>().elementType();
        BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
        masm.loadUnboxedProperty(source, elementType, output);
    }

    masm.pop(object);
    attacher.jumpRejoin(masm);

    // Restore the object before continuing to the next stub.
    masm.bind(&popObjectAndFail);
    masm.pop(object);
    masm.bind(&failures);

    attacher.jumpNextStub(masm);
}

bool
GetPropertyIC::tryAttachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript,
                                                   IonScript* ion, HandleObject obj,
                                                   HandleValue idval, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!canAttachTypedOrUnboxedArrayElement(obj, idval, output()))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);
    GenerateGetTypedOrUnboxedArrayElement(cx, masm, attacher, obj, idval, object(), id(),
                                          output(), allowDoubleResult_);
    return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
                             JS::TrackedOutcome::ICGetElemStub_TypedArray);
}

bool
GetPropertyIC::tryAttachArgumentsElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                         HandleObject obj, HandleValue idval, bool* emitted)
{
    MOZ_ASSERT(canAttachStub());
    MOZ_ASSERT(!*emitted);

    if (!IsOptimizableArgumentsObjectForGetElem(obj, idval))
        return true;

    MOZ_ASSERT(obj->is<ArgumentsObject>());

    if (hasArgumentsElementStub(obj->is<MappedArgumentsObject>()))
        return true;

    TypedOrValueRegister index = id().reg();
    if (index.type() != MIRType_Value && index.type() != MIRType_Int32)
        return true;

    MOZ_ASSERT(output().hasValue());

    *emitted = true;

    Label failures;
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Register tmpReg = output().scratchReg().gpr();
    MOZ_ASSERT(tmpReg != InvalidReg);

    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);

    // Get initial ArgsObj length value, test if length has been overridden.
    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
                      &failures);
    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);

    // Decide to what type index the stub should be optimized
    Register indexReg;

    // Check index against length.
    Label failureRestoreIndex;
    if (index.hasValue()) {
        ValueOperand val = index.valueReg();
        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
        indexReg = val.scratchReg();

        masm.unboxInt32(val, indexReg);
        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
    } else {
        MOZ_ASSERT(index.type() == MIRType_Int32);
        indexReg = index.typedReg().gpr();
        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
    }
    // Save indexReg because it needs to be clobbered to check deleted bit.
    Label failurePopIndex;
    masm.push(indexReg);

    // Check if property was deleted on arguments object.
    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
    masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg);

    // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
    const uint32_t shift = FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value;
    MOZ_ASSERT(shift == 5 || shift == 6);
    masm.rshiftPtr(Imm32(shift), indexReg);
    masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg);

    // Don't bother testing specific bit, if any bit is set in the word, fail.
    masm.branchPtr(Assembler::NotEqual, tmpReg, ImmPtr(nullptr), &failurePopIndex);

    // Get the address to load from into tmpReg
    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
    masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);

    // Restore original index register value, to use for indexing element.
    masm.pop(indexReg);
    BaseValueIndex elemIdx(tmpReg, indexReg);

    // Ensure result is not magic value, and type-check result.
    masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);

    masm.loadTypedOrValue(elemIdx, output());

    // indexReg may need to be reconstructed if it was originally a value.
    if (index.hasValue())
        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());

    // Success.
    attacher.jumpRejoin(masm);

    // Restore the object before continuing to the next stub.
    masm.bind(&failurePopIndex);
    masm.pop(indexReg);
    masm.bind(&failureRestoreIndex);
    if (index.hasValue())
        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());
    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    if (obj->is<UnmappedArgumentsObject>()) {
        MOZ_ASSERT(!hasUnmappedArgumentsElementStub_);
        hasUnmappedArgumentsElementStub_ = true;
        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (unmapped)",
                                 JS::TrackedOutcome::ICGetElemStub_ArgsElementUnmapped);
    }

    MOZ_ASSERT(!hasMappedArgumentsElementStub_);
    hasMappedArgumentsElementStub_ = true;
    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (mapped)",
                             JS::TrackedOutcome::ICGetElemStub_ArgsElementMapped);
}

static bool
IsDenseElementSetInlineable(JSObject* obj, const Value& idval, ConstantOrRegister val,
                            bool needsTypeBarrier, bool* checkTypeset)
{
    if (!obj->is<ArrayObject>())
        return false;

    if (obj->watched())
        return false;

    if (!idval.isInt32())
        return false;

    // The object may have a setter definition,
    // either directly, or via a prototype, or via the target object for a prototype
    // which is a proxy, that handles a particular integer write.
    // Scan the prototype and shape chain to make sure that this is not the case.
    JSObject* curObj = obj;
    while (curObj) {
        // Ensure object is native.
        if (!curObj->isNative())
            return false;

        // Ensure all indexed properties are stored in dense elements.
        if (curObj->isIndexed())
            return false;

        curObj = curObj->getProto();
    }

    *checkTypeset = false;
    if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, JSID_VOID, val, checkTypeset))
        return false;

    return true;
}

static bool
IsTypedArrayElementSetInlineable(JSObject* obj, const Value& idval, const Value& value)
{
    // Don't bother attaching stubs for assigning strings, objects or symbols.
    return IsAnyTypedArray(obj) && idval.isInt32() &&
           !value.isString() && !value.isObject() && !value.isSymbol();
}

static void
StoreDenseElement(MacroAssembler& masm, ConstantOrRegister value, Register elements,
                  BaseObjectElementIndex target)
{
    // If the ObjectElements::CONVERT_DOUBLE_ELEMENTS flag is set, int32 values
    // have to be converted to double first. If the value is not int32, it can
    // always be stored directly.

    Address elementsFlags(elements, ObjectElements::offsetOfFlags());
    if (value.constant()) {
        Value v = value.value();
        Label done;
        if (v.isInt32()) {
            Label dontConvert;
            masm.branchTest32(Assembler::Zero, elementsFlags,
                              Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
                              &dontConvert);
            masm.storeValue(DoubleValue(v.toInt32()), target);
            masm.jump(&done);
            masm.bind(&dontConvert);
        }
        masm.storeValue(v, target);
        masm.bind(&done);
        return;
    }

    TypedOrValueRegister reg = value.reg();
    if (reg.hasTyped() && reg.type() != MIRType_Int32) {
        masm.storeTypedOrValue(reg, target);
        return;
    }

    Label convert, storeValue, done;
    masm.branchTest32(Assembler::NonZero, elementsFlags,
                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
                      &convert);
    masm.bind(&storeValue);
    masm.storeTypedOrValue(reg, target);
    masm.jump(&done);

    masm.bind(&convert);
    if (reg.hasValue()) {
        masm.branchTestInt32(Assembler::NotEqual, reg.valueReg(), &storeValue);
        masm.int32ValueToDouble(reg.valueReg(), ScratchDoubleReg);
        masm.storeDouble(ScratchDoubleReg, target);
    } else {
        MOZ_ASSERT(reg.type() == MIRType_Int32);
        masm.convertInt32ToDouble(reg.typedReg().gpr(), ScratchDoubleReg);
        masm.storeDouble(ScratchDoubleReg, target);
    }

    masm.bind(&done);
}

static bool
GenerateSetDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                        JSObject* obj, const Value& idval, bool guardHoles, Register object,
                        TypedOrValueRegister index, ConstantOrRegister value, Register tempToUnboxIndex,
                        Register temp, bool needsTypeBarrier, bool checkTypeset)
{
    MOZ_ASSERT(obj->isNative());
    MOZ_ASSERT(idval.isInt32());

    Label failures;

    // Guard object is a dense array.
    Shape* shape = obj->as<NativeObject>().lastProperty();
    if (!shape)
        return false;
    masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);

    // Guard that the incoming value is in the type set for the property
    // if a type barrier is required.
    if (needsTypeBarrier) {
        masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), &failures);
        if (checkTypeset)
            CheckTypeSetForWrite(masm, obj, JSID_VOID, temp, value, &failures);
    }

    // Ensure the index is an int32 value.
    Register indexReg;
    if (index.hasValue()) {
        ValueOperand val = index.valueReg();
        masm.branchTestInt32(Assembler::NotEqual, val, &failures);

        indexReg = masm.extractInt32(val, tempToUnboxIndex);
    } else {
        MOZ_ASSERT(!index.typedReg().isFloat());
        indexReg = index.typedReg().gpr();
    }

    {
        // Load obj->elements.
        Register elements = temp;
        masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elements);

        // Compute the location of the element.
        BaseObjectElementIndex target(elements, indexReg);

        Label storeElement;

        // If TI cannot help us deal with HOLES by preventing indexed properties
        // on the prototype chain, we have to be very careful to check for ourselves
        // to avoid stomping on what should be a setter call. Start by only allowing things
        // within the initialized length.
        if (guardHoles) {
            Address initLength(elements, ObjectElements::offsetOfInitializedLength());
            masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &failures);
        } else {
            // Guard that we can increase the initialized length.
            Address capacity(elements, ObjectElements::offsetOfCapacity());
            masm.branch32(Assembler::BelowOrEqual, capacity, indexReg, &failures);

            // Guard on the initialized length.
            Address initLength(elements, ObjectElements::offsetOfInitializedLength());
            masm.branch32(Assembler::Below, initLength, indexReg, &failures);

            // if (initLength == index)
            Label inBounds;
            masm.branch32(Assembler::NotEqual, initLength, indexReg, &inBounds);
            {
                // Increase initialize length.
                Int32Key newLength(indexReg);
                masm.bumpKey(&newLength, 1);
                masm.storeKey(newLength, initLength);

                // Increase length if needed.
                Label bumpedLength;
                Address length(elements, ObjectElements::offsetOfLength());
                masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength);
                masm.storeKey(newLength, length);
                masm.bind(&bumpedLength);

                // Restore the index.
                masm.bumpKey(&newLength, -1);
                masm.jump(&storeElement);
            }
            // else
            masm.bind(&inBounds);
        }

        if (cx->zone()->needsIncrementalBarrier())
            masm.callPreBarrier(target, MIRType_Value);

        // Store the value.
        if (guardHoles)
            masm.branchTestMagic(Assembler::Equal, target, &failures);
        else
            masm.bind(&storeElement);
        StoreDenseElement(masm, value, elements, target);
    }
    attacher.jumpRejoin(masm);

    masm.bind(&failures);
    attacher.jumpNextStub(masm);

    return true;
}

bool
SetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject obj, const Value& idval, bool* emitted)
{
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(canAttachStub());

    if (hasDenseStub())
        return true;

    bool checkTypeset = false;
    if (!IsDenseElementSetInlineable(obj, idval, value(), needsTypeBarrier(), &checkTypeset))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);
    if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval,
                                 guardHoles(), object(), id().reg(),
                                 value(), tempToUnboxIndex(), temp(),
                                 needsTypeBarrier(), checkTypeset))
    {
        return false;
    }

    setHasDenseStub();
    const char* message = guardHoles() ?  "dense array (holes)" : "dense array";
    return linkAndAttachStub(cx, masm, attacher, ion, message,
                             JS::TrackedOutcome::ICSetElemStub_Dense);
}

static bool
GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                             HandleObject tarr, Register object, TypedOrValueRegister index,
                             ConstantOrRegister value, Register tempUnbox, Register temp,
                             FloatRegister tempDouble, FloatRegister tempFloat32)
{
    Label failures, done, popObjectAndFail;

    // Guard on the shape.
    Shape* shape = AnyTypedArrayShape(tarr);
    if (!shape)
        return false;
    masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);

    // Ensure the index is an int32.
    Register indexReg;
    if (index.hasValue()) {
        ValueOperand val = index.valueReg();
        masm.branchTestInt32(Assembler::NotEqual, val, &failures);

        indexReg = masm.extractInt32(val, tempUnbox);
    } else {
        MOZ_ASSERT(!index.typedReg().isFloat());
        indexReg = index.typedReg().gpr();
    }

    // Guard on the length.
    Address length(object, TypedArrayObject::lengthOffset());
    masm.unboxInt32(length, temp);
    masm.branch32(Assembler::BelowOrEqual, temp, indexReg, &done);

    // Load the elements vector.
    Register elements = temp;
    masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elements);

    // Set the value.
    Scalar::Type arrayType = AnyTypedArrayType(tarr);
    int width = Scalar::byteSize(arrayType);
    BaseIndex target(elements, indexReg, ScaleFromElemWidth(width));

    if (arrayType == Scalar::Float32) {
        MOZ_ASSERT_IF(hasUnaliasedDouble(), tempFloat32 != InvalidFloatReg);
        FloatRegister tempFloat = hasUnaliasedDouble() ? tempFloat32 : tempDouble;
        if (!masm.convertConstantOrRegisterToFloat(cx, value, tempFloat, &failures))
            return false;
        masm.storeToTypedFloatArray(arrayType, tempFloat, target);
    } else if (arrayType == Scalar::Float64) {
        if (!masm.convertConstantOrRegisterToDouble(cx, value, tempDouble, &failures))
            return false;
        masm.storeToTypedFloatArray(arrayType, tempDouble, target);
    } else {
        // On x86 we only have 6 registers available to use, so reuse the object
        // register to compute the intermediate value to store and restore it
        // afterwards.
        masm.push(object);

        if (arrayType == Scalar::Uint8Clamped) {
            if (!masm.clampConstantOrRegisterToUint8(cx, value, tempDouble, object,
                                                     &popObjectAndFail))
            {
                return false;
            }
        } else {
            if (!masm.truncateConstantOrRegisterToInt32(cx, value, tempDouble, object,
                                                        &popObjectAndFail))
            {
                return false;
            }
        }
        masm.storeToTypedIntArray(arrayType, object, target);

        masm.pop(object);
    }

    // Out-of-bound writes jump here as they are no-ops.
    masm.bind(&done);
    attacher.jumpRejoin(masm);

    if (popObjectAndFail.used()) {
        masm.bind(&popObjectAndFail);
        masm.pop(object);
    }

    masm.bind(&failures);
    attacher.jumpNextStub(masm);
    return true;
}

bool
SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                          HandleObject obj, HandleValue idval, HandleValue val,
                                          bool* emitted)
{
    MOZ_ASSERT(!*emitted);
    MOZ_ASSERT(canAttachStub());

    if (!IsTypedArrayElementSetInlineable(obj, idval, val))
        return true;

    *emitted = true;

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);
    if (!GenerateSetTypedArrayElement(cx, masm, attacher, obj,
                                      object(), id().reg(), value(),
                                      tempToUnboxIndex(), temp(), tempDouble(), tempFloat32()))
    {
        return false;
    }

    return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
                             JS::TrackedOutcome::ICSetElemStub_TypedArray);
}

bool
BindNameIC::attachGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
                         HandleObject scopeChain)
{
    MOZ_ASSERT(scopeChain->is<GlobalObject>());

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    // Guard on the scope chain.
    attacher.branchNextStub(masm, Assembler::NotEqual, scopeChainReg(),
                            ImmGCPtr(scopeChain));
    masm.movePtr(ImmGCPtr(scopeChain), outputReg());

    attacher.jumpRejoin(masm);

    return linkAndAttachStub(cx, masm, attacher, ion, "global");
}

static inline void
GenerateScopeChainGuard(MacroAssembler& masm, JSObject* scopeObj,
                        Register scopeObjReg, Shape* shape, Label* failures)
{
    if (scopeObj->is<CallObject>()) {
        // We can skip a guard on the call object if the script's bindings are
        // guaranteed to be immutable (and thus cannot introduce shadowing
        // variables).
        CallObject* callObj = &scopeObj->as<CallObject>();
        if (!callObj->isForEval()) {
            JSFunction* fun = &callObj->callee();
            // The function might have been relazified under rare conditions.
            // In that case, we pessimistically create the guard, as we'd
            // need to root various pointers to delazify,
            if (fun->hasScript()) {
                JSScript* script = fun->nonLazyScript();
                if (!script->funHasExtensibleScope())
                    return;
            }
        }
    } else if (scopeObj->is<GlobalObject>()) {
        // If this is the last object on the scope walk, and the property we've
        // found is not configurable, then we don't need a shape guard because
        // the shape cannot be removed.
        if (shape && !shape->configurable())
            return;
    }

    Address shapeAddr(scopeObjReg, JSObject::offsetOfShape());
    masm.branchPtr(Assembler::NotEqual, shapeAddr,
                   ImmGCPtr(scopeObj->as<NativeObject>().lastProperty()), failures);
}

static void
GenerateScopeChainGuards(MacroAssembler& masm, JSObject* scopeChain, JSObject* holder,
                         Register outputReg, Label* failures, bool skipLastGuard = false)
{
    JSObject* tobj = scopeChain;

    // Walk up the scope chain. Note that IsCacheableScopeChain guarantees the
    // |tobj == holder| condition terminates the loop.
    while (true) {
        MOZ_ASSERT(IsCacheableNonGlobalScope(tobj) || tobj->is<GlobalObject>());

        if (skipLastGuard && tobj == holder)
            break;

        GenerateScopeChainGuard(masm, tobj, outputReg, nullptr, failures);

        if (tobj == holder)
            break;

        // Load the next link.
        tobj = &tobj->as<ScopeObject>().enclosingScope();
        masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg);
    }
}

bool
BindNameIC::attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
                            HandleObject scopeChain, HandleObject holder)
{
    MOZ_ASSERT(IsCacheableNonGlobalScope(scopeChain));

    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    // Guard on the shape of the scope chain.
    Label failures;
    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                   Address(scopeChainReg(), JSObject::offsetOfShape()),
                                   ImmGCPtr(scopeChain->as<NativeObject>().lastProperty()),
                                   holder != scopeChain ? &failures : nullptr);

    if (holder != scopeChain) {
        JSObject* parent = &scopeChain->as<ScopeObject>().enclosingScope();
        masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg());

        GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures);
    } else {
        masm.movePtr(scopeChainReg(), outputReg());
    }

    // At this point outputReg holds the object on which the property
    // was found, so we're done.
    attacher.jumpRejoin(masm);

    // All failures flow to here, so there is a common point to patch.
    if (holder != scopeChain) {
        masm.bind(&failures);
        attacher.jumpNextStub(masm);
    }

    return linkAndAttachStub(cx, masm, attacher, ion, "non-global");
}

static bool
IsCacheableNonGlobalScopeChain(JSObject* scopeChain, JSObject* holder)
{
    while (true) {
        if (!IsCacheableNonGlobalScope(scopeChain)) {
            JitSpew(JitSpew_IonIC, "Non-cacheable object on scope chain");
            return false;
        }

        if (scopeChain == holder)
            return true;

        scopeChain = &scopeChain->as<ScopeObject>().enclosingScope();
        if (!scopeChain) {
            JitSpew(JitSpew_IonIC, "Scope chain indirect hit");
            return false;
        }
    }

    MOZ_CRASH("Invalid scope chain");
}

JSObject*
BindNameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
                   HandleObject scopeChain)
{
    IonScript* ion = outerScript->ionScript();
    BindNameIC& cache = ion->getCache(cacheIndex).toBindName();
    HandlePropertyName name = cache.name();

    RootedObject holder(cx);
    if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
        return nullptr;

    // Stop generating new stubs once we hit the stub count limit, see
    // GetPropertyCache.
    if (cache.canAttachStub()) {
        if (scopeChain->is<GlobalObject>()) {
            if (!cache.attachGlobal(cx, outerScript, ion, scopeChain))
                return nullptr;
        } else if (IsCacheableNonGlobalScopeChain(scopeChain, holder)) {
            if (!cache.attachNonGlobal(cx, outerScript, ion, scopeChain, holder))
                return nullptr;
        } else {
            JitSpew(JitSpew_IonIC, "BINDNAME uncacheable scope chain");
        }
    }

    return holder;
}

bool
NameIC::attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                       HandleObject scopeChain, HandleObject holderBase,
                       HandleNativeObject holder, HandleShape shape)
{
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    Label failures;
    StubAttacher attacher(*this);

    Register scratchReg = outputReg().valueReg().scratchReg();

    // Don't guard the base of the proto chain the name was found on. It will be guarded
    // by GenerateReadSlot().
    masm.mov(scopeChainReg(), scratchReg);
    GenerateScopeChainGuards(masm, scopeChain, holderBase, scratchReg, &failures,
                             /* skipLastGuard = */true);

    // GenerateScopeChain leaves the last scope chain in scratchReg, even though it
    // doesn't generate the extra guard.
    GenerateReadSlot(cx, ion, masm, attacher, holderBase, holder, shape, scratchReg,
                     outputReg(), failures.used() ? &failures : nullptr);

    return linkAndAttachStub(cx, masm, attacher, ion, "generic",
                             JS::TrackedOutcome::ICNameStub_ReadSlot);
}

static bool
IsCacheableScopeChain(JSObject* scopeChain, JSObject* obj)
{
    JSObject* obj2 = scopeChain;
    while (obj2) {
        if (!IsCacheableNonGlobalScope(obj2) && !obj2->is<GlobalObject>())
            return false;

        // Stop once we hit the global or target obj.
        if (obj2->is<GlobalObject>() || obj2 == obj)
            break;

        obj2 = obj2->enclosingScope();
    }

    return obj == obj2;
}

static bool
IsCacheableNameReadSlot(HandleObject scopeChain, HandleObject obj,
                        HandleObject holder, HandleShape shape, jsbytecode* pc,
                        const TypedOrValueRegister& output)
{
    if (!shape)
        return false;
    if (!obj->isNative())
        return false;

    if (obj->is<GlobalObject>()) {
        // Support only simple property lookups.
        if (!IsCacheableGetPropReadSlotForIon(obj, holder, shape) &&
            !IsCacheableNoProperty(obj, holder, shape, pc, output))
            return false;
    } else if (obj->is<ModuleEnvironmentObject>()) {
        // We don't yet support lookups in a module environment.
        return false;
    } else if (obj->is<CallObject>()) {
        MOZ_ASSERT(obj == holder);
        if (!shape->hasDefaultGetter())
            return false;
    } else {
        // We don't yet support lookups on Block or DeclEnv objects.
        return false;
    }

    return IsCacheableScopeChain(scopeChain, obj);
}

bool
NameIC::attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
                         HandleObject scopeChain, HandleObject obj, HandleObject holder,
                         HandleShape shape, void* returnAddr)
{
    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
    StubAttacher attacher(*this);

    Label failures;
    Register scratchReg = outputReg().valueReg().scratchReg();

    // Don't guard the base of the proto chain the name was found on. It will be guarded
    // by GenerateCallGetter().
    masm.mov(scopeChainReg(), scratchReg);
    GenerateScopeChainGuards(masm, scopeChain, obj, scratchReg, &failures,
                             /* skipLastGuard = */true);

    // GenerateScopeChain leaves the last scope chain in scratchReg, even though it
    // doesn't generate the extra guard.
    if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape, liveRegs_,
                            scratchReg, outputReg(), returnAddr,
                            failures.used() ? &failures : nullptr))
    {
         return false;
    }

    const char* attachKind = "name getter";
    return linkAndAttachStub(cx, masm, attacher, ion, attachKind,
                             JS::TrackedOutcome::ICNameStub_CallGetter);
}

static bool
IsCacheableNameCallGetter(HandleObject scopeChain, HandleObject obj, HandleObject holder,
                          HandleShape shape)
{
    if (!shape)
        return false;
    if (!obj->is<GlobalObject>())
        return false;

    if (!IsCacheableScopeChain(scopeChain, obj))
        return false;

    return IsCacheableGetPropCallNative(obj, holder, shape) ||
        IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
        IsCacheableGetPropCallScripted(obj, holder, shape);
}

bool
NameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject scopeChain,
               MutableHandleValue vp)
{
    IonScript* ion = outerScript->ionScript();

    NameIC& cache = ion->getCache(cacheIndex).toName();
    RootedPropertyName name(cx, cache.name());

    RootedScript script(cx);
    jsbytecode* pc;
    cache.getScriptedLocation(&script, &pc);

    RootedObject obj(cx);
    RootedObject holder(cx);
    RootedShape shape(cx);
    if (!LookupName(cx, name, scopeChain, &obj, &holder, &shape))
        return false;

    // Look first. Don't generate cache entries if the lookup fails.
    if (cache.isTypeOf()) {
        if (!FetchName<true>(cx, obj, holder, name, shape, vp))
            return false;
    } else {
        if (!FetchName<false>(cx, obj, holder, name, shape, vp))
            return false;
    }

    if (cache.canAttachStub()) {
        if (IsCacheableNameReadSlot(scopeChain, obj, holder, shape, pc, cache.outputReg())) {
            if (!cache.attachReadSlot(cx, outerScript, ion, scopeChain, obj,
                                      holder.as<NativeObject>(), shape))
            {
                return false;
            }
        } else if (IsCacheableNameCallGetter(scopeChain, obj, holder, shape)) {
            void* returnAddr = GetReturnAddressToIonCode(cx);
            if (!cache.attachCallGetter(cx, outerScript, ion, scopeChain, obj, holder, shape,
                                        returnAddr))
            {
                return false;
            }
        }
    }

    // Monitor changes to cache entry.
    TypeScript::Monitor(cx, script, pc, vp);

    return true;
}

