/* -*- 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/. */

#ifndef jit_IonCaches_h
#define jit_IonCaches_h

#if defined(JS_CODEGEN_ARM)
# include "jit/arm/Assembler-arm.h"
#elif defined(JS_CODEGEN_ARM64)
# include "jit/arm64/Assembler-arm64.h"
#elif defined(JS_CODEGEN_MIPS32)
# include "jit/mips32/Assembler-mips32.h"
#elif defined(JS_CODEGEN_MIPS64)
# include "jit/mips64/Assembler-mips64.h"
#endif
#include "jit/JitCompartment.h"
#include "jit/Registers.h"
#include "jit/shared/Assembler-shared.h"
#include "js/TrackedOptimizationInfo.h"

#include "vm/TypedArrayCommon.h"

namespace js {
namespace jit {

class LInstruction;

#define IONCACHE_KIND_LIST(_)                                   \
    _(GetProperty)                                              \
    _(SetProperty)                                              \
    _(BindName)                                                 \
    _(Name)

// Forward declarations of Cache kinds.
#define FORWARD_DECLARE(kind) class kind##IC;
IONCACHE_KIND_LIST(FORWARD_DECLARE)
#undef FORWARD_DECLARE

class IonCacheVisitor
{
  public:
#define VISIT_INS(op)                                               \
    virtual void visit##op##IC(CodeGenerator* codegen) {            \
        MOZ_CRASH("NYI: " #op "IC");                                \
    }

    IONCACHE_KIND_LIST(VISIT_INS)
#undef VISIT_INS
};

// Common structure encoding the state of a polymorphic inline cache contained
// in the code for an IonScript. IonCaches are used for polymorphic operations
// where multiple implementations may be required.
//
// Roughly speaking, the cache initially jumps to an out of line fragment
// which invokes a cache function to perform the operation. The cache function
// may generate a stub to perform the operation in certain cases (e.g. a
// particular shape for an input object) and attach the stub to existing
// stubs, forming a daisy chain of tests for how to perform the operation in
// different circumstances.
//
// Eventually, if too many stubs are generated the cache function may disable
// the cache, by generating a stub to make a call and perform the operation
// within the VM.
//
// The caches initially generate a patchable jump to an out of line call
// to the cache function. Stubs are attached by appending: when attaching a
// new stub, we patch the any failure conditions in last generated stub to
// jump to the new stub. Failure conditions in the new stub jump to the cache
// function which may generate new stubs.
//
//        Control flow               Pointers
//      =======#                 ----.     .---->
//             #                     |     |
//             #======>              \-----/
//
// Initial state:
//
//  JIT Code
// +--------+   .---------------.
// |        |   |               |
// |========|   v +----------+  |
// |== IC ==|====>| Cache Fn |  |
// |========|     +----------+  |
// |        |<=#       #        |
// |        |  #=======#        |
// +--------+  Rejoin path      |
//     |________                |
//             |                |
//     IC      |                |
//   Entry     |                |
// +------------+               |
// | lastJump_  |---------------/
// +------------+
// |    ...     |
// +------------+
//
// Attaching stubs:
//
//   Patch the jump pointed to by lastJump_ to jump to the new stub. Update
//   lastJump_ to be the new stub's failure jump. The failure jump of the new
//   stub goes to the fallback label, which is the cache function. In this
//   fashion, new stubs are _appended_ to the chain of stubs, as lastJump_
//   points to the _tail_ of the stub chain.
//
//  JIT Code
// +--------+ #=======================#
// |        | #                       v
// |========| #   +----------+     +------+
// |== IC ==|=#   | Cache Fn |<====| Stub |
// |========|     +----------+  ^  +------+
// |        |<=#      #         |     #
// |        |  #======#=========|=====#
// +--------+      Rejoin path  |
//     |________                |
//             |                |
//     IC      |                |
//   Entry     |                |
// +------------+               |
// | lastJump_  |---------------/
// +------------+
// |    ...     |
// +------------+
//
// While calls may be made to the cache function and other VM functions, the
// cache may still be treated as pure during optimization passes, such that
// LICM and GVN may be performed on operations around the cache as if the
// operation cannot reenter scripted code through an Invoke() or otherwise have
// unexpected behavior. This restricts the sorts of stubs which the cache can
// generate or the behaviors which called functions can have, and if a called
// function performs a possibly impure operation then the operation will be
// marked as such and the calling script will be recompiled.
//
// Similarly, despite the presence of functions and multiple stubs generated
// for a cache, the cache itself may be marked as idempotent and become hoisted
// or coalesced by LICM or GVN. This also constrains the stubs which can be
// generated for the cache.
//
// * IonCache usage
//
// IonCache is the base structure of an inline cache, which generates code stubs
// dynamically and attaches them to an IonScript.
//
// A cache must at least provide a static update function which will usualy have
// a JSContext*, followed by the cache index. The rest of the arguments of the
// update function are usualy corresponding to the register inputs of the cache,
// as it must perform the same operation as any of the stubs that it might
// produce. The update function call is handled by the visit function of
// CodeGenerator corresponding to this IC.
//
// The CodeGenerator visit function, as opposed to other visit functions, has
// two arguments. The first one is the OutOfLineUpdateCache which stores the LIR
// instruction. The second one is the IC object.  This function would be called
// once the IC is registered with the addCache function of CodeGeneratorShared.
//
// To register a cache, you must call the addCache function as follow:
//
//     MyCodeIC cache(inputReg1, inputValueReg2, outputReg);
//     if (!addCache(lir, allocateCache(cache)))
//         return false;
//
// Once the cache is allocated with the allocateCache function, any modification
// made to the cache would be ignored.
//
// The addCache function will produce a patchable jump at the location where
// it is called. This jump will execute generated stubs and fallback on the code
// of the visitMyCodeIC function if no stub match.
//
//   Warning: As the addCache function fallback on a VMCall, calls to
// addCache should not be in the same path as another VMCall or in the same
// path of another addCache as this is not supported by the invalidation
// procedure.
class IonCache
{
  public:
    class StubAttacher;

    enum Kind {
#   define DEFINE_CACHEKINDS(ickind) Cache_##ickind,
        IONCACHE_KIND_LIST(DEFINE_CACHEKINDS)
#   undef DEFINE_CACHEKINDS
        Cache_Invalid
    };

    // Cache testing and cast.
#   define CACHEKIND_CASTS(ickind)                                      \
    bool is##ickind() const {                                           \
        return kind() == Cache_##ickind;                                \
    }                                                                   \
    inline ickind##IC& to##ickind();                                    \
    inline const ickind##IC& to##ickind() const;
    IONCACHE_KIND_LIST(CACHEKIND_CASTS)
#   undef CACHEKIND_CASTS

    virtual Kind kind() const = 0;

    virtual void accept(CodeGenerator* codegen, IonCacheVisitor* visitor) = 0;

  public:

    static const char* CacheName(Kind kind);

  protected:
    bool pure_ : 1;
    bool idempotent_ : 1;
    bool disabled_ : 1;
    size_t stubCount_ : 5;

    CodeLocationLabel fallbackLabel_;

    // Location of this operation, nullptr for idempotent caches.
    JSScript* script_;
    jsbytecode* pc_;

    // Location to use when updating profiler pseudostack when leaving this
    // IC code to enter a callee.
    jsbytecode* profilerLeavePc_;

    CodeLocationJump initialJump_;
    CodeLocationJump lastJump_;
    CodeLocationLabel rejoinLabel_;

  private:
    static const size_t MAX_STUBS;
    void incrementStubCount() {
        // The IC should stop generating stubs before wrapping stubCount.
        stubCount_++;
        MOZ_ASSERT(stubCount_);
    }

  public:

    IonCache()
      : pure_(false),
        idempotent_(false),
        disabled_(false),
        stubCount_(0),
        fallbackLabel_(),
        script_(nullptr),
        pc_(nullptr),
        profilerLeavePc_(nullptr),
        initialJump_(),
        lastJump_(),
        rejoinLabel_()
    {
    }

    void disable();
    inline bool isDisabled() const {
        return disabled_;
    }

    // Set the initial 'out-of-line' jump state of the cache. The fallbackLabel is
    // the location of the out-of-line update (slow) path.  This location will
    // be set to the exitJump of the last generated stub.
    void setFallbackLabel(CodeOffset fallbackLabel) {
        fallbackLabel_ = fallbackLabel;
    }

    void setProfilerLeavePC(jsbytecode* pc) {
        MOZ_ASSERT(pc != nullptr);
        profilerLeavePc_ = pc;
    }

    // Get the address at which IC rejoins the mainline jitcode.
    void* rejoinAddress() const {
        return rejoinLabel_.raw();
    }

    void emitInitialJump(MacroAssembler& masm, RepatchLabel& entry);
    void updateBaseAddress(JitCode* code, MacroAssembler& masm);

    // Reset the cache around garbage collection.
    virtual void reset(ReprotectCode reprotect);

    bool canAttachStub() const {
        return stubCount_ < MAX_STUBS;
    }
    bool empty() const {
        return stubCount_ == 0;
    }

    enum LinkStatus {
        LINK_ERROR,
        CACHE_FLUSHED,
        LINK_GOOD
    };

    // Use the Linker to link the generated code and check if any
    // monitoring/allocation caused an invalidation of the running ion script,
    // this function returns CACHE_FLUSHED. In case of allocation issue this
    // function returns LINK_ERROR.
    LinkStatus linkCode(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher, IonScript* ion,
                        JitCode** code);

    // Fixup variables and update jumps in the list of stubs.  Increment the
    // number of attached stubs accordingly.
    void attachStub(MacroAssembler& masm, StubAttacher& attacher, CodeLocationJump lastJump,
                    Handle<JitCode*> code);

    // Combine both linkStub and attachStub into one function. In addition, it
    // produces a spew augmented with the attachKind string.
    MOZ_MUST_USE bool linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher,
                                        IonScript* ion, const char* attachKind,
                                        JS::TrackedOutcome = JS::TrackedOutcome::ICOptStub_GenericSuccess);

#ifdef DEBUG
    bool isAllocated() {
        return fallbackLabel_.isSet();
    }
#endif

    bool pure() const {
        return pure_;
    }
    bool idempotent() const {
        return idempotent_;
    }
    void setIdempotent() {
        MOZ_ASSERT(!idempotent_);
        MOZ_ASSERT(!script_);
        MOZ_ASSERT(!pc_);
        idempotent_ = true;
    }

    void setScriptedLocation(JSScript* script, jsbytecode* pc) {
        MOZ_ASSERT(!idempotent_);
        script_ = script;
        pc_ = pc;
    }

    void getScriptedLocation(MutableHandleScript pscript, jsbytecode** ppc) const {
        pscript.set(script_);
        *ppc = pc_;
    }

    jsbytecode* pc() const {
        MOZ_ASSERT(pc_);
        return pc_;
    }

    void trace(JSTracer* trc);
};

// Define the cache kind and pre-declare data structures used for calling inline
// caches.
#define CACHE_HEADER(ickind)                                        \
    Kind kind() const {                                             \
        return IonCache::Cache_##ickind;                            \
    }                                                               \
                                                                    \
    void accept(CodeGenerator* codegen, IonCacheVisitor* visitor) { \
        visitor->visit##ickind##IC(codegen);                        \
    }                                                               \
                                                                    \
    static const VMFunction UpdateInfo;

// Subclasses of IonCache for the various kinds of caches. These do not define
// new data members; all caches must be of the same size.

// Helper for idempotent GetPropertyIC location tracking. Declared externally
// to be forward declarable.
//
// Since all the scripts stored in CacheLocations are guaranteed to have been
// Ion compiled, and are kept alive by function objects in jitcode, and since
// the CacheLocations only have the lifespan of the jitcode, there is no need
// to trace or mark any of the scripts. Since JSScripts are always allocated
// tenured, and never moved, we can keep raw pointers, and there is no need
// for GCPtrScripts here.
struct CacheLocation {
    jsbytecode* pc;
    JSScript* script;

    CacheLocation(jsbytecode* pcin, JSScript* scriptin)
        : pc(pcin), script(scriptin)
    { }
};

class GetPropertyIC : public IonCache
{
  protected:
    // Registers live after the cache, excluding output registers. The initial
    // value of these registers must be preserved by the cache.
    LiveRegisterSet liveRegs_;

    Register object_;
    ConstantOrRegister id_;
    TypedOrValueRegister output_;

    // Only valid if idempotent
    size_t locationsIndex_;
    size_t numLocations_;

    static const size_t MAX_FAILED_UPDATES = 16;
    uint16_t failedUpdates_;

    bool monitoredResult_ : 1;
    bool allowDoubleResult_ : 1;
    bool hasTypedArrayLengthStub_ : 1;
    bool hasMappedArgumentsLengthStub_ : 1;
    bool hasUnmappedArgumentsLengthStub_ : 1;
    bool hasMappedArgumentsElementStub_ : 1;
    bool hasUnmappedArgumentsElementStub_ : 1;
    bool hasGenericProxyStub_ : 1;
    bool hasDenseStub_ : 1;

    void emitIdGuard(MacroAssembler& masm, jsid id, Label* fail);

  public:
    GetPropertyIC(LiveRegisterSet liveRegs,
                  Register object, ConstantOrRegister id,
                  TypedOrValueRegister output,
                  bool monitoredResult, bool allowDoubleResult)
      : liveRegs_(liveRegs),
        object_(object),
        id_(id),
        output_(output),
        locationsIndex_(0),
        numLocations_(0),
        failedUpdates_(0),
        monitoredResult_(monitoredResult),
        allowDoubleResult_(allowDoubleResult),
        hasTypedArrayLengthStub_(false),
        hasMappedArgumentsLengthStub_(false),
        hasUnmappedArgumentsLengthStub_(false),
        hasMappedArgumentsElementStub_(false),
        hasUnmappedArgumentsElementStub_(false),
        hasGenericProxyStub_(false),
        hasDenseStub_(false)
    {
    }

    CACHE_HEADER(GetProperty)

    void reset(ReprotectCode reprotect);

    Register object() const {
        return object_;
    }
    ConstantOrRegister id() const {
        return id_;
    }
    TypedOrValueRegister output() const {
        return output_;
    }
    bool monitoredResult() const {
        return monitoredResult_;
    }
    bool hasTypedArrayLengthStub(HandleObject obj) const {
        return hasTypedArrayLengthStub_;
    }
    bool hasArgumentsLengthStub(bool mapped) const {
        return mapped ? hasMappedArgumentsLengthStub_ : hasUnmappedArgumentsLengthStub_;
    }
    bool hasArgumentsElementStub(bool mapped) const {
        return mapped ? hasMappedArgumentsElementStub_ : hasUnmappedArgumentsElementStub_;
    }
    bool hasGenericProxyStub() const {
        return hasGenericProxyStub_;
    }

    bool hasDenseStub() const {
        return hasDenseStub_;
    }
    void setHasDenseStub() {
        MOZ_ASSERT(!hasDenseStub());
        hasDenseStub_ = true;
    }

    void setHasTypedArrayLengthStub(HandleObject obj) {
        MOZ_ASSERT(obj->is<TypedArrayObject>());
        MOZ_ASSERT(!hasTypedArrayLengthStub_);
        hasTypedArrayLengthStub_ = true;
    }

    void setLocationInfo(size_t locationsIndex, size_t numLocations) {
        MOZ_ASSERT(idempotent());
        MOZ_ASSERT(!numLocations_);
        MOZ_ASSERT(numLocations);
        locationsIndex_ = locationsIndex;
        numLocations_ = numLocations;
    }
    void getLocationInfo(uint32_t* index, uint32_t* num) const {
        MOZ_ASSERT(idempotent());
        *index = locationsIndex_;
        *num = numLocations_;
    }

    enum NativeGetPropCacheability {
        CanAttachNone,
        CanAttachReadSlot,
        CanAttachArrayLength,
        CanAttachCallGetter
    };

    // Helpers for CanAttachNativeGetProp
    bool allowArrayLength(JSContext* cx) const;
    bool allowGetters() const {
        return monitoredResult() && !idempotent();
    }

    void maybeDisable(bool emitted);

    // Attach the proper stub, if possible
    MOZ_MUST_USE bool tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                    HandleObject obj, HandleValue idval, bool* emitted);

    MOZ_MUST_USE bool tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject obj, HandleId id, void* returnAddr,
                                     bool* emitted);

    MOZ_MUST_USE bool tryAttachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                            HandleObject obj, HandleId id, void* returnAddr,
                                            bool* emitted);

    MOZ_MUST_USE bool tryAttachDOMProxyShadowed(JSContext* cx, HandleScript outerScript,
                                                IonScript* ion, HandleObject obj, HandleId id,
                                                void* returnAddr, bool* emitted);

    MOZ_MUST_USE bool tryAttachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript,
                                                  IonScript* ion, HandleObject obj, HandleId id,
                                                  bool resetNeeded, void* returnAddr,
                                                  bool* emitted);

    MOZ_MUST_USE bool tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                      HandleObject obj, HandleId id, void* returnAddr,
                                      bool* emitted);

    MOZ_MUST_USE bool tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleId id, void* returnAddr,
                                       bool* emitted);

    MOZ_MUST_USE bool tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
                                              IonScript* ion, HandleObject obj, HandleId id,
                                              void* returnAddr, bool* emitted);

    MOZ_MUST_USE bool tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript,
                                                  IonScript* ion, HandleObject obj, HandleId id,
                                                  void* returnAddr, bool* emitted);

    MOZ_MUST_USE bool tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript,
                                                IonScript* ion, HandleObject obj, HandleId id,
                                                bool* emitted);

    MOZ_MUST_USE bool tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript,
                                               IonScript* ion, HandleObject obj, HandleId id,
                                               bool* emitted);

    MOZ_MUST_USE bool tryAttachArgumentsElement(JSContext* cx, HandleScript outerScript,
                                                IonScript* ion, HandleObject obj, HandleValue idval,
                                                bool* emitted);

    MOZ_MUST_USE bool tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                            HandleObject obj, HandleValue idval, bool* emitted);

    static bool canAttachDenseElementHole(JSObject* obj, HandleValue idval,
                                          TypedOrValueRegister output);
    MOZ_MUST_USE bool tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript,
                                                IonScript* ion, HandleObject obj,
                                                HandleValue idval, bool* emitted);

    static bool canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
                                                    TypedOrValueRegister output);

    MOZ_MUST_USE bool tryAttachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript,
                                                          IonScript* ion, HandleObject obj,
                                                          HandleValue idval, bool* emitted);

    MOZ_MUST_USE bool tryAttachModuleNamespace(JSContext* cx, HandleScript outerScript,
                                               IonScript* ion, HandleObject obj, HandleId id,
                                               void* returnAddr, bool* emitted);

    static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
                                    HandleObject obj, HandleValue id, MutableHandleValue vp);
};

class SetPropertyIC : public IonCache
{
  protected:
    // Registers live after the cache, excluding output registers. The initial
    // value of these registers must be preserved by the cache.
    LiveRegisterSet liveRegs_;

    Register object_;
    Register temp_;
    Register tempToUnboxIndex_;
    FloatRegister tempDouble_;
    FloatRegister tempFloat32_;
    ConstantOrRegister id_;
    ConstantOrRegister value_;
    bool strict_ : 1;
    bool needsTypeBarrier_ : 1;
    bool guardHoles_ : 1;

    bool hasGenericProxyStub_ : 1;
    bool hasDenseStub_ : 1;

    void emitIdGuard(MacroAssembler& masm, jsid id, Label* fail);

  public:
    SetPropertyIC(LiveRegisterSet liveRegs, Register object, Register temp, Register tempToUnboxIndex,
                  FloatRegister tempDouble, FloatRegister tempFloat32, ConstantOrRegister id,
                  ConstantOrRegister value, bool strict, bool needsTypeBarrier, bool guardHoles)
      : liveRegs_(liveRegs),
        object_(object),
        temp_(temp),
        tempToUnboxIndex_(tempToUnboxIndex),
        tempDouble_(tempDouble),
        tempFloat32_(tempFloat32),
        id_(id),
        value_(value),
        strict_(strict),
        needsTypeBarrier_(needsTypeBarrier),
        guardHoles_(guardHoles),
        hasGenericProxyStub_(false),
        hasDenseStub_(false)
    {
    }

    CACHE_HEADER(SetProperty)

    void reset(ReprotectCode reprotect);

    Register object() const {
        return object_;
    }
    Register temp() const {
        return temp_;
    }
    Register tempToUnboxIndex() const {
        return tempToUnboxIndex_;
    }
    FloatRegister tempDouble() const {
        return tempDouble_;
    }
    FloatRegister tempFloat32() const {
        return tempFloat32_;
    }
    ConstantOrRegister id() const {
        return id_;
    }
    ConstantOrRegister value() const {
        return value_;
    }
    bool strict() const {
        return strict_;
    }
    bool needsTypeBarrier() const {
        return needsTypeBarrier_;
    }
    bool guardHoles() const {
        return guardHoles_;
    }
    bool hasGenericProxyStub() const {
        return hasGenericProxyStub_;
    }

    bool hasDenseStub() const {
        return hasDenseStub_;
    }
    void setHasDenseStub() {
        MOZ_ASSERT(!hasDenseStub());
        hasDenseStub_ = true;
    }

    enum NativeSetPropCacheability {
        CanAttachNone,
        CanAttachSetSlot,
        MaybeCanAttachAddSlot,
        CanAttachCallSetter
    };

    MOZ_MUST_USE bool attachSetSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                    HandleObject obj, HandleShape shape, bool checkTypeset);

    MOZ_MUST_USE bool attachCallSetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleObject holder, HandleShape shape,
                                       void* returnAddr);

    MOZ_MUST_USE bool attachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                    HandleObject obj, HandleId id, HandleShape oldShape,
                                    HandleObjectGroup oldGroup, bool checkTypeset);

    MOZ_MUST_USE bool attachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                         HandleId id, void* returnAddr);

    MOZ_MUST_USE bool attachDOMProxyShadowed(JSContext* cx, HandleScript outerScript,
                                             IonScript* ion, HandleObject obj, HandleId id,
                                             void* returnAddr);

    MOZ_MUST_USE bool attachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript,
                                               IonScript* ion, HandleObject obj, HandleId id,
                                               void* returnAddr);

    static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
                                    HandleObject obj, HandleValue idval, HandleValue value);

    MOZ_MUST_USE bool tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                      HandleObject obj, HandleId id, bool* emitted,
                                      bool* tryNativeAddSlot);

    MOZ_MUST_USE bool tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleId id, bool* emitted);

    MOZ_MUST_USE bool tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
                                              IonScript* ion, HandleObject obj, HandleId id,
                                              bool* emitted);

    MOZ_MUST_USE bool tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject obj, HandleId id, bool* emitted);

    MOZ_MUST_USE bool tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                    HandleObject obj, HandleValue idval, HandleValue value,
                                    MutableHandleId id, bool* emitted, bool* tryNativeAddSlot);

    MOZ_MUST_USE bool tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject obj, HandleId id, HandleObjectGroup oldGroup,
                                       HandleShape oldShape, bool tryNativeAddSlot, bool* emitted);

    MOZ_MUST_USE bool tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                            HandleObject obj, const Value& idval, bool* emitted);

    MOZ_MUST_USE bool tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScript,
                                                 IonScript* ion, HandleObject obj,
                                                 HandleValue idval, HandleValue val, bool* emitted);
};

class BindNameIC : public IonCache
{
  protected:
    Register scopeChain_;
    PropertyName* name_;
    Register output_;

  public:
    BindNameIC(Register scopeChain, PropertyName* name, Register output)
      : scopeChain_(scopeChain),
        name_(name),
        output_(output)
    {
    }

    CACHE_HEADER(BindName)

    Register scopeChainReg() const {
        return scopeChain_;
    }
    HandlePropertyName name() const {
        return HandlePropertyName::fromMarkedLocation(&name_);
    }
    Register outputReg() const {
        return output_;
    }

    MOZ_MUST_USE bool attachGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                   HandleObject scopeChain);

    MOZ_MUST_USE bool attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                      HandleObject scopeChain, HandleObject holder);

    static JSObject*
    update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject scopeChain);
};

class NameIC : public IonCache
{
  protected:
    // Registers live after the cache, excluding output registers. The initial
    // value of these registers must be preserved by the cache.
    LiveRegisterSet liveRegs_;

    bool typeOf_;
    Register scopeChain_;
    PropertyName* name_;
    TypedOrValueRegister output_;

  public:
    NameIC(LiveRegisterSet liveRegs, bool typeOf,
           Register scopeChain, PropertyName* name,
           TypedOrValueRegister output)
      : liveRegs_(liveRegs),
        typeOf_(typeOf),
        scopeChain_(scopeChain),
        name_(name),
        output_(output)
    {
    }

    CACHE_HEADER(Name)

    Register scopeChainReg() const {
        return scopeChain_;
    }
    HandlePropertyName name() const {
        return HandlePropertyName::fromMarkedLocation(&name_);
    }
    TypedOrValueRegister outputReg() const {
        return output_;
    }
    bool isTypeOf() const {
        return typeOf_;
    }

    MOZ_MUST_USE bool attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                     HandleObject scopeChain, HandleObject holderBase,
                                     HandleNativeObject holder, HandleShape shape);

    MOZ_MUST_USE bool attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                       HandleObject scopeChain, HandleObject obj,
                                       HandleObject holder, HandleShape shape,
                                       void* returnAddr);

    MOZ_MUST_USE bool attachTypeOfNoProperty(JSContext* cx, HandleScript outerScript,
                                             IonScript* ion, HandleObject scopeChain);

    static MOZ_MUST_USE bool
    update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject scopeChain,
           MutableHandleValue vp);
};

#undef CACHE_HEADER

// Implement cache casts now that the compiler can see the inheritance.
#define CACHE_CASTS(ickind)                                             \
    ickind##IC& IonCache::to##ickind()                                  \
    {                                                                   \
        MOZ_ASSERT(is##ickind());                                       \
        return *static_cast<ickind##IC*>(this);                        \
    }                                                                   \
    const ickind##IC& IonCache::to##ickind() const                      \
    {                                                                   \
        MOZ_ASSERT(is##ickind());                                       \
        return *static_cast<const ickind##IC*>(this);                  \
    }
IONCACHE_KIND_LIST(CACHE_CASTS)
#undef OPCODE_CASTS

bool IsCacheableProtoChainForIonOrCacheIR(JSObject* obj, JSObject* holder);
bool IsCacheableGetPropReadSlotForIonOrCacheIR(JSObject* obj, JSObject* holder, Shape* shape);

} // namespace jit
} // namespace js

#endif /* jit_IonCaches_h */
