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

#include "mozilla/DebugOnly.h"

#include "jit/BaselineIC.h"
#include "jit/CacheIRCompiler.h"

#include "jsscriptinlines.h"

#include "vm/EnvironmentObject-inl.h"
#include "vm/ObjectGroup-inl.h"
#include "vm/ReceiverGuard-inl.h"

using namespace js;
using namespace js::jit;

using mozilla::DebugOnly;

bool
SetElemICInspector::sawOOBDenseWrite() const
{
    if (!icEntry_)
        return false;

    // Check for a write hole bit on the SetElem_Fallback stub.
    ICStub* stub = icEntry_->fallbackStub();
    if (stub->isSetElem_Fallback())
        return stub->toSetElem_Fallback()->hasDenseAdd();

    return false;
}

bool
SetElemICInspector::sawOOBTypedArrayWrite() const
{
    if (!icEntry_)
        return false;

    ICStub* stub = icEntry_->fallbackStub();
    if (stub->isSetElem_Fallback())
        return stub->toSetElem_Fallback()->hasTypedArrayOOB();

    return false;
}

template <typename S, typename T>
static bool
VectorAppendNoDuplicate(S& list, T value)
{
    for (size_t i = 0; i < list.length(); i++) {
        if (list[i] == value)
            return true;
    }
    return list.append(value);
}

static bool
AddReceiver(const ReceiverGuard& receiver,
            BaselineInspector::ReceiverVector& receivers,
            BaselineInspector::ObjectGroupVector& convertUnboxedGroups)
{
    if (receiver.group && receiver.group->maybeUnboxedLayout()) {
        if (receiver.group->unboxedLayout().nativeGroup())
            return VectorAppendNoDuplicate(convertUnboxedGroups, receiver.group);
    }
    return VectorAppendNoDuplicate(receivers, receiver);
}

static bool
GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
{
    // We match either:
    //
    //   GuardIsObject 0
    //   GuardShape 0
    //   LoadFixedSlotResult 0 or LoadDynamicSlotResult 0
    //
    // or
    //
    //   GuardIsObject 0
    //   GuardGroup 0
    //   1: GuardAndLoadUnboxedExpando 0
    //   GuardShape 1
    //   LoadFixedSlotResult 1 or LoadDynamicSlotResult 1

    *receiver = ReceiverGuard();
    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    if (reader.matchOp(CacheOp::GuardGroup, objId)) {
        receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());

        if (!reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
            return false;
        objId = reader.objOperandId();
    }

    if (reader.matchOp(CacheOp::GuardShape, objId)) {
        receiver->shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
        return reader.matchOpEither(CacheOp::LoadFixedSlotResult, CacheOp::LoadDynamicSlotResult);
    }

    return false;
}

static bool
GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
{
    // We match:
    //
    //   GuardIsObject 0
    //   GuardGroup 0
    //   LoadUnboxedPropertyResult 0 ..

    *receiver = ReceiverGuard();
    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    if (!reader.matchOp(CacheOp::GuardGroup, objId))
        return false;
    receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());

    return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId);
}

static bool
GetCacheIRReceiverForNativeSetSlot(ICCacheIR_Updated* stub, ReceiverGuard* receiver)
{
    // We match either:
    //
    //   GuardIsObject 0
    //   GuardGroup 0
    //   GuardShape 0
    //   StoreFixedSlot 0 or StoreDynamicSlot 0
    //
    // or
    //
    //   GuardIsObject 0
    //   GuardGroup 0
    //   1: GuardAndLoadUnboxedExpando 0
    //   GuardShape 1
    //   StoreFixedSlot 1 or StoreDynamicSlot 1

    *receiver = ReceiverGuard();
    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    if (!reader.matchOp(CacheOp::GuardGroup, objId))
        return false;
    ObjectGroup* group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());

    if (reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
        objId = reader.objOperandId();

    if (!reader.matchOp(CacheOp::GuardShape, objId))
        return false;
    Shape* shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());

    if (!reader.matchOpEither(CacheOp::StoreFixedSlot, CacheOp::StoreDynamicSlot))
        return false;

    *receiver = ReceiverGuard(group, shape);
    return true;
}

static bool
GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Updated* stub, ReceiverGuard* receiver)
{
    // We match:
    //
    //   GuardIsObject 0
    //   GuardGroup 0
    //   GuardType 1 type | GuardIsObjectOrNull 1
    //   StoreUnboxedProperty 0

    *receiver = ReceiverGuard();
    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    ValOperandId rhsId = ValOperandId(1);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    if (!reader.matchOp(CacheOp::GuardGroup, objId))
        return false;
    ObjectGroup* group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());

    if (reader.matchOp(CacheOp::GuardType, rhsId)) {
        reader.valueType(); // Skip.
    } else {
        if (!reader.matchOp(CacheOp::GuardIsObjectOrNull, rhsId))
            return false;
    }

    if (!reader.matchOp(CacheOp::StoreUnboxedProperty))
        return false;

    *receiver = ReceiverGuard(group, nullptr);
    return true;
}

bool
BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
                                          ObjectGroupVector& convertUnboxedGroups)
{
    // Return a list of the receivers seen by the baseline IC for the current
    // op. Empty lists indicate no receivers are known, or there was an
    // uncacheable access. convertUnboxedGroups is used for unboxed object
    // groups which have been seen, but have had instances converted to native
    // objects and should be eagerly converted by Ion.
    MOZ_ASSERT(receivers.empty());
    MOZ_ASSERT(convertUnboxedGroups.empty());

    if (!hasBaselineScript())
        return true;

    MOZ_ASSERT(isValidPC(pc));
    const ICEntry& entry = icEntryFromPC(pc);

    ICStub* stub = entry.firstStub();
    while (stub->next()) {
        ReceiverGuard receiver;
        if (stub->isCacheIR_Monitored()) {
            if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver) &&
                !GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Monitored(), &receiver))
            {
                receivers.clear();
                return true;
            }
        } else if (stub->isCacheIR_Updated()) {
            if (!GetCacheIRReceiverForNativeSetSlot(stub->toCacheIR_Updated(), &receiver) &&
                !GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Updated(), &receiver))
            {
                receivers.clear();
                return true;
            }
        } else {
            receivers.clear();
            return true;
        }

        if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
            return false;

        stub = stub->next();
    }

    if (stub->isGetProp_Fallback()) {
        if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
            receivers.clear();
    } else {
        if (stub->toSetProp_Fallback()->hadUnoptimizableAccess())
            receivers.clear();
    }

    // Don't inline if there are more than 5 receivers.
    if (receivers.length() > 5)
        receivers.clear();

    return true;
}

ICStub*
BaselineInspector::monomorphicStub(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return nullptr;

    // IonBuilder::analyzeNewLoopTypes may call this (via expectedResultType
    // below) on code that's unreachable, according to BytecodeAnalysis. Use
    // maybeICEntryFromPC to handle this.
    const ICEntry* entry = maybeICEntryFromPC(pc);
    if (!entry)
        return nullptr;

    ICStub* stub = entry->firstStub();
    ICStub* next = stub->next();

    if (!next || !next->isFallback())
        return nullptr;

    return stub;
}

bool
BaselineInspector::dimorphicStub(jsbytecode* pc, ICStub** pfirst, ICStub** psecond)
{
    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);

    ICStub* stub = entry.firstStub();
    ICStub* next = stub->next();
    ICStub* after = next ? next->next() : nullptr;

    if (!after || !after->isFallback())
        return false;

    *pfirst = stub;
    *psecond = next;
    return true;
}

MIRType
BaselineInspector::expectedResultType(jsbytecode* pc)
{
    // Look at the IC entries for this op to guess what type it will produce,
    // returning MIRType::None otherwise. Note that IonBuilder may call this
    // for bytecode ops that are unreachable and don't have a Baseline IC, see
    // comment in monomorphicStub.

    ICStub* stub = monomorphicStub(pc);
    if (!stub)
        return MIRType::None;

    switch (stub->kind()) {
      case ICStub::BinaryArith_Int32:
        if (stub->toBinaryArith_Int32()->allowDouble())
            return MIRType::Double;
        return MIRType::Int32;
      case ICStub::BinaryArith_BooleanWithInt32:
      case ICStub::UnaryArith_Int32:
      case ICStub::BinaryArith_DoubleWithInt32:
        return MIRType::Int32;
      case ICStub::BinaryArith_Double:
      case ICStub::UnaryArith_Double:
        return MIRType::Double;
      case ICStub::BinaryArith_StringConcat:
      case ICStub::BinaryArith_StringObjectConcat:
        return MIRType::String;
      default:
        return MIRType::None;
    }
}

// Whether a baseline stub kind is suitable for a double comparison that
// converts its operands to doubles.
static bool
CanUseDoubleCompare(ICStub::Kind kind)
{
    return kind == ICStub::Compare_Double || kind == ICStub::Compare_NumberWithUndefined;
}

// Whether a baseline stub kind is suitable for an int32 comparison that
// converts its operands to int32.
static bool
CanUseInt32Compare(ICStub::Kind kind)
{
    return kind == ICStub::Compare_Int32 || kind == ICStub::Compare_Int32WithBoolean;
}

MCompare::CompareType
BaselineInspector::expectedCompareType(jsbytecode* pc)
{
    ICStub* first = monomorphicStub(pc);
    ICStub* second = nullptr;
    if (!first && !dimorphicStub(pc, &first, &second))
        return MCompare::Compare_Unknown;

    if (ICStub* fallback = second ? second->next() : first->next()) {
        MOZ_ASSERT(fallback->isFallback());
        if (fallback->toCompare_Fallback()->hadUnoptimizableAccess())
            return MCompare::Compare_Unknown;
    }

    if (CanUseInt32Compare(first->kind()) && (!second || CanUseInt32Compare(second->kind()))) {
        ICCompare_Int32WithBoolean* coerce =
            first->isCompare_Int32WithBoolean()
            ? first->toCompare_Int32WithBoolean()
            : ((second && second->isCompare_Int32WithBoolean())
               ? second->toCompare_Int32WithBoolean()
               : nullptr);
        if (coerce) {
            return coerce->lhsIsInt32()
                   ? MCompare::Compare_Int32MaybeCoerceRHS
                   : MCompare::Compare_Int32MaybeCoerceLHS;
        }
        return MCompare::Compare_Int32;
    }

    if (CanUseDoubleCompare(first->kind()) && (!second || CanUseDoubleCompare(second->kind()))) {
        ICCompare_NumberWithUndefined* coerce =
            first->isCompare_NumberWithUndefined()
            ? first->toCompare_NumberWithUndefined()
            : (second && second->isCompare_NumberWithUndefined())
              ? second->toCompare_NumberWithUndefined()
              : nullptr;
        if (coerce) {
            return coerce->lhsIsUndefined()
                   ? MCompare::Compare_DoubleMaybeCoerceLHS
                   : MCompare::Compare_DoubleMaybeCoerceRHS;
        }
        return MCompare::Compare_Double;
    }

    return MCompare::Compare_Unknown;
}

static bool
TryToSpecializeBinaryArithOp(ICStub** stubs,
                             uint32_t nstubs,
                             MIRType* result)
{
    DebugOnly<bool> sawInt32 = false;
    bool sawDouble = false;
    bool sawOther = false;

    for (uint32_t i = 0; i < nstubs; i++) {
        switch (stubs[i]->kind()) {
          case ICStub::BinaryArith_Int32:
            sawInt32 = true;
            break;
          case ICStub::BinaryArith_BooleanWithInt32:
            sawInt32 = true;
            break;
          case ICStub::BinaryArith_Double:
            sawDouble = true;
            break;
          case ICStub::BinaryArith_DoubleWithInt32:
            sawDouble = true;
            break;
          default:
            sawOther = true;
            break;
        }
    }

    if (sawOther)
        return false;

    if (sawDouble) {
        *result = MIRType::Double;
        return true;
    }

    MOZ_ASSERT(sawInt32);
    *result = MIRType::Int32;
    return true;
}

MIRType
BaselineInspector::expectedBinaryArithSpecialization(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return MIRType::None;

    MIRType result;
    ICStub* stubs[2];

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.fallbackStub();
    if (stub->isBinaryArith_Fallback() &&
        stub->toBinaryArith_Fallback()->hadUnoptimizableOperands())
    {
        return MIRType::None;
    }

    stubs[0] = monomorphicStub(pc);
    if (stubs[0]) {
        if (TryToSpecializeBinaryArithOp(stubs, 1, &result))
            return result;
    }

    if (dimorphicStub(pc, &stubs[0], &stubs[1])) {
        if (TryToSpecializeBinaryArithOp(stubs, 2, &result))
            return result;
    }

    return MIRType::None;
}

bool
BaselineInspector::hasSeenNegativeIndexGetElement(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.fallbackStub();

    if (stub->isGetElem_Fallback())
        return stub->toGetElem_Fallback()->hasNegativeIndex();
    return false;
}

bool
BaselineInspector::hasSeenAccessedGetter(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.fallbackStub();

    if (stub->isGetProp_Fallback())
        return stub->toGetProp_Fallback()->hasAccessedGetter();
    return false;
}

bool
BaselineInspector::hasSeenNonStringIterMore(jsbytecode* pc)
{
    MOZ_ASSERT(JSOp(*pc) == JSOP_MOREITER);

    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.fallbackStub();

    return stub->toIteratorMore_Fallback()->hasNonStringResult();
}

bool
BaselineInspector::hasSeenDoubleResult(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.fallbackStub();

    MOZ_ASSERT(stub->isUnaryArith_Fallback() || stub->isBinaryArith_Fallback());

    if (stub->isUnaryArith_Fallback())
        return stub->toUnaryArith_Fallback()->sawDoubleResult();

    return stub->toBinaryArith_Fallback()->sawDoubleResult();
}

JSObject*
BaselineInspector::getTemplateObject(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        switch (stub->kind()) {
          case ICStub::NewArray_Fallback:
            return stub->toNewArray_Fallback()->templateObject();
          case ICStub::NewObject_Fallback:
            return stub->toNewObject_Fallback()->templateObject();
          case ICStub::Rest_Fallback:
            return stub->toRest_Fallback()->templateObject();
          case ICStub::Call_Scripted:
            if (JSObject* obj = stub->toCall_Scripted()->templateObject())
                return obj;
            break;
          default:
            break;
        }
    }

    return nullptr;
}

ObjectGroup*
BaselineInspector::getTemplateObjectGroup(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        switch (stub->kind()) {
          case ICStub::NewArray_Fallback:
            return stub->toNewArray_Fallback()->templateGroup();
          default:
            break;
        }
    }

    return nullptr;
}

JSFunction*
BaselineInspector::getSingleCallee(jsbytecode* pc)
{
    MOZ_ASSERT(*pc == JSOP_NEW);

    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    ICStub* stub = entry.firstStub();

    if (entry.fallbackStub()->toCall_Fallback()->hadUnoptimizableCall())
        return nullptr;

    if (!stub->isCall_Scripted() || stub->next() != entry.fallbackStub())
        return nullptr;

    return stub->toCall_Scripted()->callee();
}

JSObject*
BaselineInspector::getTemplateObjectForNative(jsbytecode* pc, Native native)
{
    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCall_Native() && stub->toCall_Native()->callee()->native() == native)
            return stub->toCall_Native()->templateObject();
    }

    return nullptr;
}

bool
BaselineInspector::isOptimizableConstStringSplit(jsbytecode* pc, JSString** strOut,
                                                 JSString** sepOut, ArrayObject** objOut)
{
    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);

    // If ConstStringSplit stub is attached, must have only one stub attached.
    if (entry.fallbackStub()->numOptimizedStubs() != 1)
        return false;

    ICStub* stub = entry.firstStub();
    if (stub->kind() != ICStub::Call_ConstStringSplit)
        return false;

    *strOut = stub->toCall_ConstStringSplit()->expectedStr();
    *sepOut = stub->toCall_ConstStringSplit()->expectedSep();
    *objOut = stub->toCall_ConstStringSplit()->templateObject();
    return true;
}

JSObject*
BaselineInspector::getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp)
{
    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCall_ClassHook() && stub->toCall_ClassHook()->clasp() == clasp)
            return stub->toCall_ClassHook()->templateObject();
    }

    return nullptr;
}

JSObject*
BaselineInspector::getTemplateObjectForSimdCtor(jsbytecode* pc, SimdType simdType)
{
    if (!hasBaselineScript())
        return nullptr;

    const ICEntry& entry = icEntryFromPC(pc);
    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCall_ClassHook() && stub->toCall_ClassHook()->clasp() == &SimdTypeDescr::class_) {
            JSObject* templateObj = stub->toCall_ClassHook()->templateObject();
            InlineTypedObject& typedObj = templateObj->as<InlineTypedObject>();
            if (typedObj.typeDescr().as<SimdTypeDescr>().type() == simdType)
                return templateObj;
        }
    }

    return nullptr;
}

LexicalEnvironmentObject*
BaselineInspector::templateNamedLambdaObject()
{
    if (!hasBaselineScript())
        return nullptr;

    JSObject* res = baselineScript()->templateEnvironment();
    if (script->bodyScope()->hasEnvironment())
        res = res->enclosingEnvironment();
    MOZ_ASSERT(res);

    return &res->as<LexicalEnvironmentObject>();
}

CallObject*
BaselineInspector::templateCallObject()
{
    if (!hasBaselineScript())
        return nullptr;

    JSObject* res = baselineScript()->templateEnvironment();
    MOZ_ASSERT(res);

    return &res->as<CallObject>();
}

static bool
MatchCacheIRReceiverGuard(CacheIRReader& reader, ICStub* stub, const CacheIRStubInfo* stubInfo,
                          ObjOperandId objId, ReceiverGuard* receiver)
{
    // This matches the CacheIR emitted in TestMatchingReceiver.
    //
    // Either:
    //
    //   GuardShape objId
    //
    // or:
    //
    //   GuardGroup objId
    //   [GuardNoUnboxedExpando objId]
    //
    // or:
    //
    //   GuardGroup objId
    //   expandoId: GuardAndLoadUnboxedExpando
    //   GuardShape expandoId

    *receiver = ReceiverGuard();

    if (reader.matchOp(CacheOp::GuardShape, objId)) {
        // The first case.
        receiver->shape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset());
        return true;
    }

    if (!reader.matchOp(CacheOp::GuardGroup, objId))
        return false;
    receiver->group = stubInfo->getStubField<ObjectGroup*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId)) {
        // Second case, just a group guard.
        reader.matchOp(CacheOp::GuardNoUnboxedExpando, objId);
        return true;
    }

    // Third case.
    ObjOperandId expandoId = reader.objOperandId();
    if (!reader.matchOp(CacheOp::GuardShape, expandoId))
        return false;

    receiver->shape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset());
    return true;
}

static bool
AddCacheIRGlobalGetter(ICCacheIR_Monitored* stub, bool innerized,
                       JSObject** holder_, Shape** holderShape_,
                       JSFunction** commonGetter, Shape** globalShape_, bool* isOwnProperty,
                       BaselineInspector::ReceiverVector& receivers,
                       BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
                       JSScript* script)
{
    // We are matching on the IR generated by tryAttachGlobalNameGetter:
    //
    //   GuardShape objId
    //   globalId = LoadEnclosingEnvironment objId
    //   GuardShape globalId
    //   <holderId = LoadObject <holder>>
    //   <GuardShape holderId>
    //   CallNativeGetterResult globalId

    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardShape, objId))
        return false;
    Shape* globalLexicalShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::LoadEnclosingEnvironment, objId))
        return false;
    ObjOperandId globalId = reader.objOperandId();

    if (!reader.matchOp(CacheOp::GuardShape, globalId))
        return false;
    Shape* globalShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
    MOZ_ASSERT(globalShape->getObjectClass()->flags & JSCLASS_IS_GLOBAL);

    JSObject* holder = &script->global();
    Shape* holderShape = globalShape;
    if (reader.matchOp(CacheOp::LoadObject)) {
        ObjOperandId holderId = reader.objOperandId();
        holder = stub->stubInfo()->getStubField<JSObject*>(stub, reader.stubOffset()).get();

        if (!reader.matchOp(CacheOp::GuardShape, holderId))
            return false;
        holderShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
    }

    // This guard will always fail, try the next stub.
    if (holder->as<NativeObject>().lastProperty() != holderShape)
        return true;

    if (!reader.matchOp(CacheOp::CallNativeGetterResult, globalId))
        return false;
    size_t offset = reader.stubOffset();
    JSFunction* getter =
        &stub->stubInfo()->getStubField<JSObject*>(stub, offset)->as<JSFunction>();

    ReceiverGuard receiver;
    receiver.shape = globalLexicalShape;
    if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
        return false;

    if (!*commonGetter) {
        *holder_ = holder;
        *holderShape_ = holderShape;
        *commonGetter = getter;
        *globalShape_ = globalShape;

        // This is always false, because the getters never live on the globalLexical.
        *isOwnProperty = false;
    } else if (*isOwnProperty || holderShape != *holderShape_ || globalShape != *globalShape_) {
        return false;
    } else {
        MOZ_ASSERT(*commonGetter == getter);
    }

    return true;
}

static bool
AddCacheIRGetPropFunction(ICCacheIR_Monitored* stub, bool innerized,
                          JSObject** holder, Shape** holderShape,
                          JSFunction** commonGetter, Shape** globalShape, bool* isOwnProperty,
                          BaselineInspector::ReceiverVector& receivers,
                          BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
                          JSScript* script)
{
    // We match either an own getter:
    //
    //   GuardIsObject objId
    //   [..WindowProxy innerization..]
    //   <GuardReceiver objId>
    //   Call(Scripted|Native)GetterResult objId
    //
    // Or a getter on the prototype:
    //
    //   GuardIsObject objId
    //   [..WindowProxy innerization..]
    //   <GuardReceiver objId>
    //   LoadObject holderId
    //   GuardShape holderId
    //   Call(Scripted|Native)GetterResult objId
    //
    // If |innerized| is true, we replaced a WindowProxy with the Window
    // object and we're only interested in Baseline getter stubs that performed
    // the same optimization. This means we expect the following ops for the
    // [..WindowProxy innerization..] above:
    //
    //   GuardClass objId WindowProxy
    //   objId = LoadObject <global>

    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId)) {
        return AddCacheIRGlobalGetter(stub, innerized, holder, holderShape, commonGetter,
                                      globalShape, isOwnProperty, receivers, convertUnboxedGroups,
                                      script);
    }

    if (innerized) {
        if (!reader.matchOp(CacheOp::GuardClass, objId) ||
            reader.guardClassKind() != GuardClassKind::WindowProxy)
        {
            return false;
        }
        if (!reader.matchOp(CacheOp::LoadObject))
            return false;
        objId = reader.objOperandId();
        DebugOnly<JSObject*> obj =
            stub->stubInfo()->getStubField<JSObject*>(stub, reader.stubOffset()).get();
        MOZ_ASSERT(obj->is<GlobalObject>());
    }

    ReceiverGuard receiver;
    if (!MatchCacheIRReceiverGuard(reader, stub, stub->stubInfo(), objId, &receiver))
        return false;

    if (reader.matchOp(CacheOp::CallScriptedGetterResult, objId) ||
        reader.matchOp(CacheOp::CallNativeGetterResult, objId))
    {
        // This is an own property getter, the first case.
        MOZ_ASSERT(receiver.shape);
        MOZ_ASSERT(!receiver.group);

        size_t offset = reader.stubOffset();
        JSFunction* getter =
            &stub->stubInfo()->getStubField<JSObject*>(stub, offset)->as<JSFunction>();

        if (*commonGetter && (!*isOwnProperty || *globalShape || *holderShape != receiver.shape))
            return false;

        MOZ_ASSERT_IF(*commonGetter, *commonGetter == getter);
        *holder = nullptr;
        *holderShape = receiver.shape;
        *commonGetter = getter;
        *isOwnProperty = true;
        return true;
    }

    if (!reader.matchOp(CacheOp::LoadObject))
        return false;
    ObjOperandId holderId = reader.objOperandId();
    JSObject* obj = stub->stubInfo()->getStubField<JSObject*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::GuardShape, holderId))
        return false;
    Shape* objShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::CallScriptedGetterResult, objId) &&
        !reader.matchOp(CacheOp::CallNativeGetterResult, objId))
    {
        return false;
    }

    // A getter on the prototype.
    size_t offset = reader.stubOffset();
    JSFunction* getter =
        &stub->stubInfo()->getStubField<JSObject*>(stub, offset)->as<JSFunction>();

    Shape* thisGlobalShape = nullptr;
    if (getter->isNative() && receiver.shape &&
        (receiver.shape->getObjectClass()->flags & JSCLASS_IS_GLOBAL))
    {
        thisGlobalShape = receiver.shape;
    }

    if (*commonGetter &&
        (*isOwnProperty || *globalShape != thisGlobalShape || *holderShape != objShape))
    {
        return false;
    }

    MOZ_ASSERT_IF(*commonGetter, *commonGetter == getter);

    if (obj->as<NativeObject>().lastProperty() != objShape) {
        // Skip this stub as the shape is no longer correct.
        return true;
    }

    if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
        return false;

    *holder = obj;
    *holderShape = objShape;
    *commonGetter = getter;
    *isOwnProperty = false;
    return true;
}

bool
BaselineInspector::commonGetPropFunction(jsbytecode* pc, bool innerized,
                                         JSObject** holder, Shape** holderShape,
                                         JSFunction** commonGetter, Shape** globalShape,
                                         bool* isOwnProperty,
                                         ReceiverVector& receivers,
                                         ObjectGroupVector& convertUnboxedGroups)
{
    if (!hasBaselineScript())
        return false;

    MOZ_ASSERT(receivers.empty());
    MOZ_ASSERT(convertUnboxedGroups.empty());

    *globalShape = nullptr;
    *commonGetter = nullptr;
    const ICEntry& entry = icEntryFromPC(pc);

    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCacheIR_Monitored()) {
            if (!AddCacheIRGetPropFunction(stub->toCacheIR_Monitored(), innerized,
                                           holder, holderShape,
                                           commonGetter, globalShape, isOwnProperty, receivers,
                                           convertUnboxedGroups, script))
            {
                return false;
            }
        } else if (stub->isGetProp_Fallback()) {
            // If we have an unoptimizable access, don't try to optimize.
            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
                return false;
        } else if (stub->isGetName_Fallback()) {
            if (stub->toGetName_Fallback()->hadUnoptimizableAccess())
                return false;
        } else {
            return false;
        }
    }

    if (!*commonGetter)
        return false;

    MOZ_ASSERT(*isOwnProperty == !*holder);
    MOZ_ASSERT(*isOwnProperty == (receivers.empty() && convertUnboxedGroups.empty()));
    return true;
}

static JSFunction*
GetMegamorphicGetterSetterFunction(ICStub* stub, const CacheIRStubInfo* stubInfo, bool isGetter)
{
    // We match:
    //
    //   GuardIsObject objId
    //   GuardHasGetterSetter objId propShape
    //
    // propShape has the getter/setter we're interested in.

    CacheIRReader reader(stubInfo);

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return nullptr;

    if (!reader.matchOp(CacheOp::GuardHasGetterSetter, objId))
        return nullptr;
    Shape* propShape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset());

    JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject();
    return &obj->as<JSFunction>();
}

bool
BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter,
                                                   JSFunction** getterOrSetter)
{
    if (!hasBaselineScript())
        return false;

    *getterOrSetter = nullptr;
    const ICEntry& entry = icEntryFromPC(pc);

    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCacheIR_Monitored()) {
            MOZ_ASSERT(isGetter);
            JSFunction* getter =
                GetMegamorphicGetterSetterFunction(stub,
                                                   stub->toCacheIR_Monitored()->stubInfo(),
                                                   isGetter);
            if (!getter || (*getterOrSetter && *getterOrSetter != getter))
                return false;
            *getterOrSetter = getter;
            continue;
        }
        if (stub->isCacheIR_Updated()) {
            MOZ_ASSERT(!isGetter);
            JSFunction* setter =
                GetMegamorphicGetterSetterFunction(stub,
                                                   stub->toCacheIR_Updated()->stubInfo(),
                                                   isGetter);
            if (!setter || (*getterOrSetter && *getterOrSetter != setter))
                return false;
            *getterOrSetter = setter;
            continue;
        }
        if (stub->isGetProp_Fallback()) {
            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
                return false;
            if (stub->toGetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic)
                return false;
            continue;
        }
        if (stub->isSetProp_Fallback()) {
            if (stub->toSetProp_Fallback()->hadUnoptimizableAccess())
                return false;
            if (stub->toSetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic)
                return false;
            continue;
        }

        return false;
    }

    if (!*getterOrSetter)
        return false;

    return true;
}

static bool
AddCacheIRSetPropFunction(ICCacheIR_Updated* stub, JSObject** holder, Shape** holderShape,
                          JSFunction** commonSetter, bool* isOwnProperty,
                          BaselineInspector::ReceiverVector& receivers,
                          BaselineInspector::ObjectGroupVector& convertUnboxedGroups)
{
    // We match either an own setter:
    //
    //   GuardIsObject objId
    //   <GuardReceiver objId>
    //   Call(Scripted|Native)Setter objId
    //
    // Or a setter on the prototype:
    //
    //   GuardIsObject objId
    //   <GuardReceiver objId>
    //   LoadObject holderId
    //   GuardShape holderId
    //   Call(Scripted|Native)Setter objId

    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    ReceiverGuard receiver;
    if (!MatchCacheIRReceiverGuard(reader, stub, stub->stubInfo(), objId, &receiver))
        return false;

    if (reader.matchOp(CacheOp::CallScriptedSetter, objId) ||
        reader.matchOp(CacheOp::CallNativeSetter, objId))
    {
        // This is an own property setter, the first case.
        MOZ_ASSERT(receiver.shape);
        MOZ_ASSERT(!receiver.group);

        size_t offset = reader.stubOffset();
        JSFunction* setter =
            &stub->stubInfo()->getStubField<JSObject*>(stub, offset)->as<JSFunction>();

        if (*commonSetter && (!*isOwnProperty || *holderShape != receiver.shape))
            return false;

        MOZ_ASSERT_IF(*commonSetter, *commonSetter == setter);
        *holder = nullptr;
        *holderShape = receiver.shape;
        *commonSetter = setter;
        *isOwnProperty = true;
        return true;
    }

    if (!reader.matchOp(CacheOp::LoadObject))
        return false;
    ObjOperandId holderId = reader.objOperandId();
    JSObject* obj = stub->stubInfo()->getStubField<JSObject*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::GuardShape, holderId))
        return false;
    Shape* objShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());

    if (!reader.matchOp(CacheOp::CallScriptedSetter, objId) &&
        !reader.matchOp(CacheOp::CallNativeSetter, objId))
    {
        return false;
    }

    // A setter on the prototype.
    size_t offset = reader.stubOffset();
    JSFunction* setter =
        &stub->stubInfo()->getStubField<JSObject*>(stub, offset)->as<JSFunction>();

    if (*commonSetter && (*isOwnProperty || *holderShape != objShape))
        return false;

    MOZ_ASSERT_IF(*commonSetter, *commonSetter == setter);

    if (obj->as<NativeObject>().lastProperty() != objShape) {
        // Skip this stub as the shape is no longer correct.
        return true;
    }

    if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
        return false;

    *holder = obj;
    *holderShape = objShape;
    *commonSetter = setter;
    *isOwnProperty = false;
    return true;
}

bool
BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                         JSFunction** commonSetter, bool* isOwnProperty,
                                         ReceiverVector& receivers,
                                         ObjectGroupVector& convertUnboxedGroups)
{
    if (!hasBaselineScript())
        return false;

    MOZ_ASSERT(receivers.empty());
    MOZ_ASSERT(convertUnboxedGroups.empty());

    *commonSetter = nullptr;
    const ICEntry& entry = icEntryFromPC(pc);

    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        if (stub->isCacheIR_Updated()) {
            if (!AddCacheIRSetPropFunction(stub->toCacheIR_Updated(),
                                           holder, holderShape,
                                           commonSetter, isOwnProperty, receivers,
                                           convertUnboxedGroups))
            {
                return false;
            }
        } else if (!stub->isSetProp_Fallback() ||
                   stub->toSetProp_Fallback()->hadUnoptimizableAccess())
        {
            // We have an unoptimizable access, so don't try to optimize.
            return false;
        }
    }

    if (!*commonSetter)
        return false;

    MOZ_ASSERT(*isOwnProperty == !*holder);
    return true;
}

static bool
GetCacheIRReceiverForProtoReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* receiver,
                                   JSObject** holderResult)
{
    // We match:
    //
    //   GuardIsObject 0
    //   <ReceiverGuard>
    //   1: LoadObject holder
    //   GuardShape 1
    //   LoadFixedSlotResult 1 or LoadDynamicSlotResult 1

    *receiver = ReceiverGuard();
    CacheIRReader reader(stub->stubInfo());

    ObjOperandId objId = ObjOperandId(0);
    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
        return false;

    if (!MatchCacheIRReceiverGuard(reader, stub, stub->stubInfo(), objId, receiver))
        return false;

    if (!reader.matchOp(CacheOp::LoadObject))
        return false;
    ObjOperandId holderId = reader.objOperandId();
    JSObject* holder = stub->stubInfo()->getStubField<JSObject*>(stub, reader.stubOffset()).get();

    if (!reader.matchOp(CacheOp::GuardShape, holderId))
        return false;
    Shape* holderShape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());

    if (!reader.matchOpEither(CacheOp::LoadFixedSlotResult, CacheOp::LoadDynamicSlotResult))
        return false;
    if (reader.objOperandId() != holderId)
        return false;

    if (holder->maybeShape() != holderShape)
        return false;
    if (*holderResult && *holderResult != holder)
        return false;

    *holderResult = holder;
    return true;
}

bool
BaselineInspector::maybeInfoForProtoReadSlot(jsbytecode* pc, ReceiverVector& receivers,
                                             ObjectGroupVector& convertUnboxedGroups,
                                             JSObject** holder)
{
    // This is like maybeInfoForPropertyOp, but for when the property exists on
    // the prototype.

    MOZ_ASSERT(receivers.empty());
    MOZ_ASSERT(convertUnboxedGroups.empty());
    MOZ_ASSERT(!*holder);

    if (!hasBaselineScript())
        return true;

    MOZ_ASSERT(isValidPC(pc));
    const ICEntry& entry = icEntryFromPC(pc);

    ICStub* stub = entry.firstStub();
    while (stub->next()) {
        ReceiverGuard receiver;
        if (stub->isCacheIR_Monitored()) {
            if (!GetCacheIRReceiverForProtoReadSlot(stub->toCacheIR_Monitored(), &receiver,
                                                    holder))
            {
                receivers.clear();
                return true;
            }
        } else {
            receivers.clear();
            return true;
        }

        if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
            return false;

        stub = stub->next();
    }

    if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
        receivers.clear();

    // Don't inline if there are more than 5 receivers.
    if (receivers.length() > 5)
        receivers.clear();

    MOZ_ASSERT_IF(!receivers.empty(), *holder);
    return true;
}

static MIRType
GetCacheIRExpectedInputType(ICCacheIR_Monitored* stub)
{
    CacheIRReader reader(stub->stubInfo());

    if (reader.matchOp(CacheOp::GuardIsObject, ValOperandId(0)))
        return MIRType::Object;
    if (reader.matchOp(CacheOp::GuardIsString, ValOperandId(0)))
        return MIRType::String;
    if (reader.matchOp(CacheOp::GuardType, ValOperandId(0))) {
        JSValueType type = reader.valueType();
        return MIRTypeFromValueType(type);
    }

    MOZ_ASSERT_UNREACHABLE("Unexpected instruction");
    return MIRType::Value;
}

MIRType
BaselineInspector::expectedPropertyAccessInputType(jsbytecode* pc)
{
    if (!hasBaselineScript())
        return MIRType::Value;

    const ICEntry& entry = icEntryFromPC(pc);
    MIRType type = MIRType::None;

    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
        MIRType stubType;
        switch (stub->kind()) {
          case ICStub::GetProp_Fallback:
            if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
                return MIRType::Value;
            continue;

          case ICStub::GetElem_Fallback:
            if (stub->toGetElem_Fallback()->hadUnoptimizableAccess())
                return MIRType::Value;
            continue;

          case ICStub::CacheIR_Monitored:
            stubType = GetCacheIRExpectedInputType(stub->toCacheIR_Monitored());
            if (stubType == MIRType::Value)
                return MIRType::Value;
            break;

          default:
            MOZ_CRASH("Unexpected stub");
        }

        if (type != MIRType::None) {
            if (type != stubType)
                return MIRType::Value;
        } else {
            type = stubType;
        }
    }

    return (type == MIRType::None) ? MIRType::Value : type;
}

bool
BaselineInspector::instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot,
                                  JSObject** prototypeObject)
{
    MOZ_ASSERT(*pc == JSOP_INSTANCEOF);

    if (!hasBaselineScript())
        return false;

    const ICEntry& entry = icEntryFromPC(pc);

    ICStub* stub = entry.firstStub();
    if (!stub->isInstanceOf_Function() ||
        !stub->next()->isInstanceOf_Fallback() ||
        stub->next()->toInstanceOf_Fallback()->hadUnoptimizableAccess())
    {
        return false;
    }

    ICInstanceOf_Function* optStub = stub->toInstanceOf_Function();
    *shape = optStub->shape();
    *prototypeObject = optStub->prototypeObject();
    *slot = optStub->slot();

    if (IsInsideNursery(*prototypeObject))
        return false;

    return true;
}
