/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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_IonIC_h
#define jit_IonIC_h

#include "jit/CacheIR.h"
#include "jit/ICState.h"
#include "jit/shared/Assembler-shared.h"
#include "vm/CompletionKind.h"

namespace js {
namespace jit {

class CacheIRStubInfo;
class CacheIRWriter;
class IonScript;

// An optimized stub attached to an IonIC.
class IonICStub {
  // Code to jump to when this stub fails. This is either the next optimized
  // stub or the OOL fallback path.
  uint8_t* nextCodeRaw_;

  // The next optimized stub in this chain, or nullptr if this is the last
  // one.
  IonICStub* next_;

  // Info about this stub.
  CacheIRStubInfo* stubInfo_;

#ifndef JS_64BIT
 protected:  // Silence Clang warning about unused private fields.
  // Ensure stub data is 8-byte aligned on 32-bit.
  uintptr_t padding_ = 0;
#endif

 public:
  IonICStub(uint8_t* fallbackCode, CacheIRStubInfo* stubInfo)
      : nextCodeRaw_(fallbackCode), next_(nullptr), stubInfo_(stubInfo) {}

  uint8_t* nextCodeRaw() const { return nextCodeRaw_; }
  uint8_t** nextCodeRawPtr() { return &nextCodeRaw_; }
  CacheIRStubInfo* stubInfo() const { return stubInfo_; }
  IonICStub* next() const { return next_; }

  uint8_t* stubDataStart();

  void setNext(IonICStub* next, uint8_t* nextCodeRaw) {
    MOZ_ASSERT(!next_);
    MOZ_ASSERT(next && nextCodeRaw);
    next_ = next;
    nextCodeRaw_ = nextCodeRaw;
  }

  // Null out pointers when we unlink stubs, to ensure we never use
  // discarded stubs.
  void poison() {
    nextCodeRaw_ = nullptr;
    next_ = nullptr;
    stubInfo_ = nullptr;
  }
};

class IonGetPropertyIC;
class IonSetPropertyIC;
class IonGetPropSuperIC;
class IonGetNameIC;
class IonBindNameIC;
class IonGetIteratorIC;
class IonHasOwnIC;
class IonCheckPrivateFieldIC;
class IonInIC;
class IonInstanceOfIC;
class IonCompareIC;
class IonUnaryArithIC;
class IonBinaryArithIC;
class IonToPropertyKeyIC;
class IonOptimizeSpreadCallIC;
class IonCloseIterIC;
class IonOptimizeGetIteratorIC;

class IonIC {
  // This either points at the OOL path for the fallback path, or the code for
  // the first stub.
  uint8_t* codeRaw_;

  // The first optimized stub, or nullptr.
  IonICStub* firstStub_;

  // Location of this IC.
  JSScript* script_;
  jsbytecode* pc_;

  // The offset of the rejoin location in the IonScript's code (stubs jump to
  // this location).
  uint32_t rejoinOffset_;

  // The offset of the OOL path in the IonScript's code that calls the IC's
  // update function.
  uint32_t fallbackOffset_;

  CacheKind kind_;
  ICState state_;

 protected:
  explicit IonIC(CacheKind kind)
      : codeRaw_(nullptr),
        firstStub_(nullptr),
        script_(nullptr),
        pc_(nullptr),
        rejoinOffset_(0),
        fallbackOffset_(0),
        kind_(kind) {}

  void attachStub(IonICStub* newStub, JitCode* code);

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

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

  // Discard all stubs.
  void discardStubs(Zone* zone, IonScript* ionScript);

  // Discard all stubs and reset the ICState.
  void reset(Zone* zone, IonScript* ionScript);

  ICState& state() { return state_; }

  CacheKind kind() const { return kind_; }
  uint8_t** codeRawPtr() { return &codeRaw_; }

  void setFallbackOffset(CodeOffset offset) {
    fallbackOffset_ = offset.offset();
  }
  void setRejoinOffset(CodeOffset offset) { rejoinOffset_ = offset.offset(); }

  void resetCodeRaw(IonScript* ionScript);

  uint8_t* fallbackAddr(IonScript* ionScript) const;
  uint8_t* rejoinAddr(IonScript* ionScript) const;

  IonGetPropertyIC* asGetPropertyIC() {
    MOZ_ASSERT(kind_ == CacheKind::GetProp || kind_ == CacheKind::GetElem);
    return (IonGetPropertyIC*)this;
  }
  IonSetPropertyIC* asSetPropertyIC() {
    MOZ_ASSERT(kind_ == CacheKind::SetProp || kind_ == CacheKind::SetElem);
    return (IonSetPropertyIC*)this;
  }
  IonGetPropSuperIC* asGetPropSuperIC() {
    MOZ_ASSERT(kind_ == CacheKind::GetPropSuper ||
               kind_ == CacheKind::GetElemSuper);
    return (IonGetPropSuperIC*)this;
  }
  IonGetNameIC* asGetNameIC() {
    MOZ_ASSERT(kind_ == CacheKind::GetName);
    return (IonGetNameIC*)this;
  }
  IonBindNameIC* asBindNameIC() {
    MOZ_ASSERT(kind_ == CacheKind::BindName);
    return (IonBindNameIC*)this;
  }
  IonGetIteratorIC* asGetIteratorIC() {
    MOZ_ASSERT(kind_ == CacheKind::GetIterator);
    return (IonGetIteratorIC*)this;
  }
  IonOptimizeSpreadCallIC* asOptimizeSpreadCallIC() {
    MOZ_ASSERT(kind_ == CacheKind::OptimizeSpreadCall);
    return (IonOptimizeSpreadCallIC*)this;
  }
  IonHasOwnIC* asHasOwnIC() {
    MOZ_ASSERT(kind_ == CacheKind::HasOwn);
    return (IonHasOwnIC*)this;
  }
  IonCheckPrivateFieldIC* asCheckPrivateFieldIC() {
    MOZ_ASSERT(kind_ == CacheKind::CheckPrivateField);
    return (IonCheckPrivateFieldIC*)this;
  }
  IonInIC* asInIC() {
    MOZ_ASSERT(kind_ == CacheKind::In);
    return (IonInIC*)this;
  }
  IonInstanceOfIC* asInstanceOfIC() {
    MOZ_ASSERT(kind_ == CacheKind::InstanceOf);
    return (IonInstanceOfIC*)this;
  }
  IonCompareIC* asCompareIC() {
    MOZ_ASSERT(kind_ == CacheKind::Compare);
    return (IonCompareIC*)this;
  }
  IonUnaryArithIC* asUnaryArithIC() {
    MOZ_ASSERT(kind_ == CacheKind::UnaryArith);
    return (IonUnaryArithIC*)this;
  }
  IonBinaryArithIC* asBinaryArithIC() {
    MOZ_ASSERT(kind_ == CacheKind::BinaryArith);
    return (IonBinaryArithIC*)this;
  }
  IonToPropertyKeyIC* asToPropertyKeyIC() {
    MOZ_ASSERT(kind_ == CacheKind::ToPropertyKey);
    return (IonToPropertyKeyIC*)this;
  }
  IonCloseIterIC* asCloseIterIC() {
    MOZ_ASSERT(kind_ == CacheKind::CloseIter);
    return (IonCloseIterIC*)this;
  }
  IonOptimizeGetIteratorIC* asOptimizeGetIteratorIC() {
    MOZ_ASSERT(kind_ == CacheKind::OptimizeGetIterator);
    return (IonOptimizeGetIteratorIC*)this;
  }

  // Returns the Register to use as scratch when entering IC stubs. This
  // should either be an output register or a temp.
  Register scratchRegisterForEntryJump();

  void trace(JSTracer* trc, IonScript* ionScript);

  void attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
                         CacheKind kind, IonScript* ionScript, bool* attached);
};

class IonGetPropertyIC : public IonIC {
 private:
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister value_;
  ConstantOrRegister id_;
  ValueOperand output_;

 public:
  IonGetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs,
                   TypedOrValueRegister value, const ConstantOrRegister& id,
                   ValueOperand output)
      : IonIC(kind),
        liveRegs_(liveRegs),
        value_(value),
        id_(id),
        output_(output) {}

  TypedOrValueRegister value() const { return value_; }
  ConstantOrRegister id() const { return id_; }
  ValueOperand output() const { return output_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonGetPropertyIC* ic, HandleValue val,
                                   HandleValue idVal, MutableHandleValue res);
};

class IonGetPropSuperIC : public IonIC {
  LiveRegisterSet liveRegs_;

  Register object_;
  TypedOrValueRegister receiver_;
  ConstantOrRegister id_;
  ValueOperand output_;

 public:
  IonGetPropSuperIC(CacheKind kind, LiveRegisterSet liveRegs, Register object,
                    TypedOrValueRegister receiver, const ConstantOrRegister& id,
                    ValueOperand output)
      : IonIC(kind),
        liveRegs_(liveRegs),
        object_(object),
        receiver_(receiver),
        id_(id),
        output_(output) {}

  Register object() const { return object_; }
  TypedOrValueRegister receiver() const { return receiver_; }
  ConstantOrRegister id() const { return id_; }
  ValueOperand output() const { return output_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonGetPropSuperIC* ic, HandleObject obj,
                                   HandleValue receiver, HandleValue idVal,
                                   MutableHandleValue res);
};

class IonSetPropertyIC : public IonIC {
  LiveRegisterSet liveRegs_;

  Register object_;
  Register temp_;
  ConstantOrRegister id_;
  ConstantOrRegister rhs_;
  bool strict_ : 1;

 public:
  IonSetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, Register object,
                   Register temp, const ConstantOrRegister& id,
                   const ConstantOrRegister& rhs, bool strict)
      : IonIC(kind),
        liveRegs_(liveRegs),
        object_(object),
        temp_(temp),
        id_(id),
        rhs_(rhs),
        strict_(strict) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  Register object() const { return object_; }
  ConstantOrRegister id() const { return id_; }
  ConstantOrRegister rhs() const { return rhs_; }

  Register temp() const { return temp_; }

  bool strict() const { return strict_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonSetPropertyIC* ic, HandleObject obj,
                                   HandleValue idVal, HandleValue rhs);
};

class IonGetNameIC : public IonIC {
  LiveRegisterSet liveRegs_;

  Register environment_;
  ValueOperand output_;
  Register temp_;

 public:
  IonGetNameIC(LiveRegisterSet liveRegs, Register environment,
               ValueOperand output, Register temp)
      : IonIC(CacheKind::GetName),
        liveRegs_(liveRegs),
        environment_(environment),
        output_(output),
        temp_(temp) {}

  Register environment() const { return environment_; }
  ValueOperand output() const { return output_; }
  Register temp() const { return temp_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonGetNameIC* ic, HandleObject envChain,
                                   MutableHandleValue res);
};

class IonBindNameIC : public IonIC {
  LiveRegisterSet liveRegs_;

  Register environment_;
  Register output_;
  Register temp_;

 public:
  IonBindNameIC(LiveRegisterSet liveRegs, Register environment, Register output,
                Register temp)
      : IonIC(CacheKind::BindName),
        liveRegs_(liveRegs),
        environment_(environment),
        output_(output),
        temp_(temp) {}

  Register environment() const { return environment_; }
  Register output() const { return output_; }
  Register temp() const { return temp_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  static JSObject* update(JSContext* cx, HandleScript outerScript,
                          IonBindNameIC* ic, HandleObject envChain);
};

class IonGetIteratorIC : public IonIC {
  LiveRegisterSet liveRegs_;
  TypedOrValueRegister value_;
  Register output_;
  Register temp1_;
  Register temp2_;

 public:
  IonGetIteratorIC(LiveRegisterSet liveRegs, TypedOrValueRegister value,
                   Register output, Register temp1, Register temp2)
      : IonIC(CacheKind::GetIterator),
        liveRegs_(liveRegs),
        value_(value),
        output_(output),
        temp1_(temp1),
        temp2_(temp2) {}

  TypedOrValueRegister value() const { return value_; }
  Register output() const { return output_; }
  Register temp1() const { return temp1_; }
  Register temp2() const { return temp2_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  static JSObject* update(JSContext* cx, HandleScript outerScript,
                          IonGetIteratorIC* ic, HandleValue value);
};

class IonOptimizeSpreadCallIC : public IonIC {
  LiveRegisterSet liveRegs_;
  ValueOperand value_;
  ValueOperand output_;
  Register temp_;

 public:
  IonOptimizeSpreadCallIC(LiveRegisterSet liveRegs, ValueOperand value,
                          ValueOperand output, Register temp)
      : IonIC(CacheKind::OptimizeSpreadCall),
        liveRegs_(liveRegs),
        value_(value),
        output_(output),
        temp_(temp) {}

  ValueOperand value() const { return value_; }
  ValueOperand output() const { return output_; }
  Register temp() const { return temp_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  static bool update(JSContext* cx, HandleScript outerScript,
                     IonOptimizeSpreadCallIC* ic, HandleValue value,
                     MutableHandleValue result);
};

class IonHasOwnIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister value_;
  TypedOrValueRegister id_;
  Register output_;

 public:
  IonHasOwnIC(LiveRegisterSet liveRegs, TypedOrValueRegister value,
              TypedOrValueRegister id, Register output)
      : IonIC(CacheKind::HasOwn),
        liveRegs_(liveRegs),
        value_(value),
        id_(id),
        output_(output) {}

  TypedOrValueRegister value() const { return value_; }
  TypedOrValueRegister id() const { return id_; }
  Register output() const { return output_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonHasOwnIC* ic, HandleValue val,
                                   HandleValue idVal, int32_t* res);
};

class IonCheckPrivateFieldIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister value_;
  TypedOrValueRegister id_;
  Register output_;

 public:
  IonCheckPrivateFieldIC(LiveRegisterSet liveRegs, TypedOrValueRegister value,
                         TypedOrValueRegister id, Register output)
      : IonIC(CacheKind::CheckPrivateField),
        liveRegs_(liveRegs),
        value_(value),
        id_(id),
        output_(output) {}

  TypedOrValueRegister value() const { return value_; }
  TypedOrValueRegister id() const { return id_; }
  Register output() const { return output_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonCheckPrivateFieldIC* ic, HandleValue val,
                                   HandleValue idVal, bool* res);
};

class IonInIC : public IonIC {
  LiveRegisterSet liveRegs_;

  ConstantOrRegister key_;
  Register object_;
  Register output_;
  Register temp_;

 public:
  IonInIC(LiveRegisterSet liveRegs, const ConstantOrRegister& key,
          Register object, Register output, Register temp)
      : IonIC(CacheKind::In),
        liveRegs_(liveRegs),
        key_(key),
        object_(object),
        output_(output),
        temp_(temp) {}

  ConstantOrRegister key() const { return key_; }
  Register object() const { return object_; }
  Register output() const { return output_; }
  Register temp() const { return temp_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonInIC* ic, HandleValue key,
                                   HandleObject obj, bool* res);
};

class IonInstanceOfIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister lhs_;
  Register rhs_;
  Register output_;

 public:
  IonInstanceOfIC(LiveRegisterSet liveRegs, TypedOrValueRegister lhs,
                  Register rhs, Register output)
      : IonIC(CacheKind::InstanceOf),
        liveRegs_(liveRegs),
        lhs_(lhs),
        rhs_(rhs),
        output_(output) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  TypedOrValueRegister lhs() const { return lhs_; }
  Register rhs() const { return rhs_; }
  Register output() const { return output_; }

  // This signature mimics that of TryAttachInstanceOfStub in baseline
  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonInstanceOfIC* ic, HandleValue lhs,
                                   HandleObject rhs, bool* attached);
};

class IonCompareIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister lhs_;
  TypedOrValueRegister rhs_;
  Register output_;

 public:
  IonCompareIC(LiveRegisterSet liveRegs, TypedOrValueRegister lhs,
               TypedOrValueRegister rhs, Register output)
      : IonIC(CacheKind::Compare),
        liveRegs_(liveRegs),
        lhs_(lhs),
        rhs_(rhs),
        output_(output) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  TypedOrValueRegister lhs() const { return lhs_; }
  TypedOrValueRegister rhs() const { return rhs_; }
  Register output() const { return output_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonCompareIC* stub, HandleValue lhs,
                                   HandleValue rhs, bool* res);
};

class IonUnaryArithIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister input_;
  ValueOperand output_;

 public:
  IonUnaryArithIC(LiveRegisterSet liveRegs, TypedOrValueRegister input,
                  ValueOperand output)
      : IonIC(CacheKind::UnaryArith),
        liveRegs_(liveRegs),
        input_(input),
        output_(output) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  TypedOrValueRegister input() const { return input_; }
  ValueOperand output() const { return output_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonUnaryArithIC* stub, HandleValue val,
                                   MutableHandleValue res);
};

class IonToPropertyKeyIC : public IonIC {
  LiveRegisterSet liveRegs_;
  ValueOperand input_;
  ValueOperand output_;

 public:
  IonToPropertyKeyIC(LiveRegisterSet liveRegs, ValueOperand input,
                     ValueOperand output)
      : IonIC(CacheKind::ToPropertyKey),
        liveRegs_(liveRegs),
        input_(input),
        output_(output) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  ValueOperand input() const { return input_; }
  ValueOperand output() const { return output_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonToPropertyKeyIC* ic, HandleValue val,
                                   MutableHandleValue res);
};

class IonBinaryArithIC : public IonIC {
  LiveRegisterSet liveRegs_;

  TypedOrValueRegister lhs_;
  TypedOrValueRegister rhs_;
  ValueOperand output_;

 public:
  IonBinaryArithIC(LiveRegisterSet liveRegs, TypedOrValueRegister lhs,
                   TypedOrValueRegister rhs, ValueOperand output)
      : IonIC(CacheKind::BinaryArith),
        liveRegs_(liveRegs),
        lhs_(lhs),
        rhs_(rhs),
        output_(output) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  TypedOrValueRegister lhs() const { return lhs_; }
  TypedOrValueRegister rhs() const { return rhs_; }
  ValueOperand output() const { return output_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonBinaryArithIC* stub, HandleValue lhs,
                                   HandleValue rhs, MutableHandleValue res);
};

class IonCloseIterIC : public IonIC {
  LiveRegisterSet liveRegs_;

  Register iter_;
  Register temp_;
  CompletionKind completionKind_;

 public:
  IonCloseIterIC(LiveRegisterSet liveRegs, Register iter, Register temp,
                 CompletionKind completionKind)
      : IonIC(CacheKind::CloseIter),
        liveRegs_(liveRegs),
        iter_(iter),
        temp_(temp),
        completionKind_(completionKind) {}

  LiveRegisterSet liveRegs() const { return liveRegs_; }
  Register temp() const { return temp_; }
  Register iter() const { return iter_; }
  CompletionKind completionKind() const { return completionKind_; }

  [[nodiscard]] static bool update(JSContext* cx, HandleScript outerScript,
                                   IonCloseIterIC* ic, HandleObject iter);
};

class IonOptimizeGetIteratorIC : public IonIC {
  LiveRegisterSet liveRegs_;
  ValueOperand value_;
  Register output_;
  Register temp_;

 public:
  IonOptimizeGetIteratorIC(LiveRegisterSet liveRegs, ValueOperand value,
                           Register output, Register temp)
      : IonIC(CacheKind::OptimizeGetIterator),
        liveRegs_(liveRegs),
        value_(value),
        output_(output),
        temp_(temp) {}

  ValueOperand value() const { return value_; }
  Register output() const { return output_; }
  Register temp() const { return temp_; }
  LiveRegisterSet liveRegs() const { return liveRegs_; }

  static bool update(JSContext* cx, HandleScript outerScript,
                     IonOptimizeGetIteratorIC* ic, HandleValue value,
                     bool* result);
};

}  // namespace jit
}  // namespace js

#endif /* jit_IonIC_h */
