/* 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_SimpleAllocator_h
#define jit_SimpleAllocator_h

#include "mozilla/Array.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"

#include "jit/RegisterAllocator.h"
#include "jit/SparseBitSet.h"
#include "jit/StackSlotAllocator.h"

namespace js {
namespace jit {

class MOZ_STACK_CLASS SimpleAllocator : protected RegisterAllocator {
 public:
  using IsStackAllocated = std::true_type;

 private:
  // Information about a virtual register.
  class VirtualRegister {
    // The definition and the id of the LIR instruction that contains it.
    LDefinition* def_;
    uint32_t insId_;

    // The id of the LIR instruction that's considered the 'last use' of this
    // virtual register. After this instruction we free any register or stack
    // slot allocated for this virtual register and we mark this virtual
    // register as dead.
    uint32_t lastUseInsId_;

    // Either UINT32_MAX (meaning no stack slot has been allocated) or the stack
    // slot offset and width of a slot we allocated for this virtual register.
    static constexpr uint32_t NoStackSlotAndWidth = UINT32_MAX;
    uint32_t stackSlotAndWidth_ = NoStackSlotAndWidth;

    // If this virtual register has fixed register uses, we use this register
    // hint when allocating registers to help avoid moves.
    // This has three states:
    // - Nothing: we didn't see any fixed register uses.
    // - Some(valid AnyRegister): there are fixed register uses and they all use
    //   this register.
    // - Some(Invalid AnyRegister): there are multiple fixed register uses for
    //   different registers and we gave up on assigning a register hint.
    mozilla::Maybe<AnyRegister> fixedUseHint_;

    // The index in the allocator's allocatedRegs_ vector, or NoRegisterIndex if
    // this vreg has no allocated register.
    //
    // In some uncommon cases a vreg can have multiple registers assigned to it
    // and in this case the index is for one of these registers.
    // See hasMultipleRegsForVreg_.
    static constexpr size_t NoRegisterIndex = UINT8_MAX;
    uint8_t registerIndex_ = NoRegisterIndex;

    // Assert registerIndex_ fits in a byte. There must be at most one
    // AllocatedRegister entry for each AnyRegister value.
    static_assert(sizeof(AnyRegister) == sizeof(registerIndex_));
    static_assert(AnyRegister::Invalid == NoRegisterIndex);

    // Whether this virtual register is for a Temp.
    bool isTemp_ : 1;

    // Whether this virtual register has a stack location. This location is
    // either a stack slot we allocated (see stackSlotAndWidth_) or this virtual
    // register came with an assigned stack location (for example for stack
    // arguments).
    bool hasStackLocation_ : 1;

    // Whether this virtual register is for a GC type that needs to be added to
    // safepoints.
    bool isGCType_ : 1;

    // Whether this virtual register is dead. This is set after allocating
    // registers for the lastUseInsId_ instruction.
    bool isDead_ : 1;

   public:
    VirtualRegister() = default;

    VirtualRegister(VirtualRegister&) = delete;
    void operator=(VirtualRegister&) = delete;

    void init(LNode* ins, LDefinition* def, bool isTemp) {
      def_ = def;
      insId_ = ins->id();
      lastUseInsId_ = ins->id();
      isTemp_ = isTemp;
      hasStackLocation_ = false;
      isGCType_ = def->isSafepointGCType(ins);
      isDead_ = false;
    }

    LDefinition* def() const { return def_; }
    uint32_t insId() const { return insId_; }
    uint32_t lastUseInsId() const { return lastUseInsId_; }

    uint8_t registerIndex() const {
      MOZ_ASSERT(hasRegister());
      return registerIndex_;
    }
    bool hasRegister() const {
      MOZ_ASSERT(!isDead());
      return registerIndex_ != NoRegisterIndex;
    }
    void setRegisterIndex(uint8_t registerIndex) {
      MOZ_ASSERT(!isDead());
      MOZ_ASSERT(registerIndex < NoRegisterIndex);
      registerIndex_ = registerIndex;
    }
    void clearRegisterIndex() {
      MOZ_ASSERT(!isDead());
      registerIndex_ = NoRegisterIndex;
    }

    LAllocation stackLocation() const {
      MOZ_ASSERT(hasStackLocation());
      if (hasAllocatedStackSlot()) {
        return LStackSlot(stackSlot());
      }
      MOZ_ASSERT(def()->policy() == LDefinition::FIXED);
      MOZ_ASSERT(!def()->output()->isAnyRegister());
      return *def()->output();
    }
    void setHasStackLocation() {
      MOZ_ASSERT(!isDead());
      MOZ_ASSERT(!isTemp());
      MOZ_ASSERT(!hasStackLocation_);
      MOZ_ASSERT(stackSlotAndWidth_ == NoStackSlotAndWidth);
      hasStackLocation_ = true;
    }

    bool hasAllocatedStackSlot() const {
      MOZ_ASSERT(!isDead());
      MOZ_ASSERT_IF(stackSlotAndWidth_ != NoStackSlotAndWidth,
                    hasStackLocation_);
      return stackSlotAndWidth_ != NoStackSlotAndWidth;
    }
    LStackSlot::SlotAndWidth stackSlot() const {
      MOZ_ASSERT(hasAllocatedStackSlot());
      return LStackSlot::SlotAndWidth::fromData(stackSlotAndWidth_);
    }
    void setAllocatedStackSlot(LStackSlot::SlotAndWidth slot) {
      setHasStackLocation();
      MOZ_ASSERT(stackSlotAndWidth_ == NoStackSlotAndWidth);
      MOZ_ASSERT(slot.data() != NoStackSlotAndWidth);
      stackSlotAndWidth_ = slot.data();
    }

    void markDead() {
      MOZ_ASSERT(!isDead());
      isDead_ = true;
    }

    mozilla::Maybe<AnyRegister> fixedUseHint() const { return fixedUseHint_; }
    void setFixedUseHint(AnyRegister reg) {
      fixedUseHint_ = mozilla::Some(reg);
    }

    bool hasStackLocation() const { return hasStackLocation_; }
    bool isGCType() const { return isGCType_; }
    bool isTemp() const { return isTemp_; }
    bool isDead() const { return isDead_; }

    void updateLastUseId(uint32_t useInsId) {
      MOZ_ASSERT(!isTemp());
      MOZ_ASSERT(useInsId > insId_);
      if (useInsId > lastUseInsId_) {
        lastUseInsId_ = useInsId;
      }
    }
  };

  // Information about virtual registers.
  Vector<VirtualRegister, 0, JitAllocPolicy> vregs_;

  // Information about an allocated register.
  class AllocatedRegister {
    static constexpr size_t VregBits = 24;
    static constexpr size_t RegisterBits = 8;

    // The virtual register.
    uint32_t vregId_ : VregBits;

    // The AnyRegister code. We don't use AnyRegister directly because the
    // compiler adds additional padding bytes on Windows if we do that.
    uint32_t reg_ : RegisterBits;

    // Id of the last LIR instruction that used this allocated register. Used
    // for eviction heuristics.
    uint32_t lastUsedAtInsId_;

    static_assert(MAX_VIRTUAL_REGISTERS <= (1 << VregBits) - 1);
    static_assert(sizeof(AnyRegister::Code) * 8 == RegisterBits);

   public:
    AllocatedRegister(uint32_t vreg, AnyRegister reg, uint32_t lastUsedAtInsId)
        : vregId_(vreg), reg_(reg.code()), lastUsedAtInsId_(lastUsedAtInsId) {}

    uint32_t vregId() const { return vregId_; }
    AnyRegister reg() const { return AnyRegister::FromCode(reg_); }
    uint32_t lastUsedAtInsId() const { return lastUsedAtInsId_; }
    void setLastUsedAtInsId(uint32_t insId) {
      MOZ_ASSERT(insId >= lastUsedAtInsId_);
      lastUsedAtInsId_ = insId;
    }
  };
  static_assert(sizeof(AllocatedRegister) == 2 * sizeof(uint32_t),
                "AllocatedRegister should not have unnecessary padding");

  // Information about allocated physical registers.
  using AllocatedRegisterVector =
      Vector<AllocatedRegister, 16, BackgroundSystemAllocPolicy>;
  AllocatedRegisterVector allocatedRegs_;
  AllocatableRegisterSet availableRegs_;

  // Whether there might be multiple entries in allocatedRegs_ for the same
  // vreg. This can happen for instance if an instruction has multiple fixed
  // register uses for the same vreg.
  //
  // This is uncommon and we try to avoid it, but if it does happen we fix
  // it up at the end of allocateForInstruction.
  bool hasMultipleRegsForVreg_ = false;

  // All registers allocated for the current LIR instruction.
  AllocatableRegisterSet currentInsRegs_;

  // Like currentInsRegs_, but does not include registers that are only used
  // for at-start uses.
  AllocatableRegisterSet currentInsRegsNotAtStart_;

  // Registers required for fixed outputs/temps of the current LIR instruction.
  AllocatableRegisterSet fixedTempRegs_;
  AllocatableRegisterSet fixedOutputAndTempRegs_;

  // The set of live GC things at the start of each basic block. Although the
  // VirtualRegBitSet may contain malloced memory, all are owned by the
  // SimpleAllocator whose destructor will destroy them.
  using VirtualRegBitSet =
      SparseBitSet<BackgroundSystemAllocPolicy, SimpleAllocator>;
  Vector<VirtualRegBitSet, 0, JitAllocPolicy> liveGCIn_;

  // Vector sorted by instructionId in descending order. This is used by
  // freeDeadVregsAfterInstruction to mark virtual registers as dead after their
  // last use.
  struct VregLastUse {
    uint32_t instructionId;
    uint32_t vregId;
    VregLastUse(uint32_t instructionId, uint32_t vregId)
        : instructionId(instructionId), vregId(vregId) {}
  };
  Vector<VregLastUse, 0, JitAllocPolicy> vregLastUses_;

  // Vector used for spilling instruction outputs that are used across blocks.
  Vector<LDefinition*, 4, BackgroundSystemAllocPolicy> eagerSpillOutputs_;

  // Allocator used to allocate and free stack slots.
  StackSlotAllocator stackSlotAllocator_;

  // Register state for a small number of previous blocks. Blocks with a single
  // predecessor block can optionally reuse this state.
  //
  // BlockStateLength should be small enough so that iterating over the array is
  // very cheap. It's currently 4 and this was sufficient for a hit rate of
  // 88.6% on a very large Wasm module.
  static constexpr size_t BlockStateLength = 4;
  struct BlockState {
    uint32_t blockIndex = UINT32_MAX;
    AllocatedRegisterVector allocatedRegs;
    AllocatableRegisterSet availableRegs;
  };
  mozilla::Array<BlockState, BlockStateLength> blockStates_;
  size_t nextBlockStateIndex_ = 0;

  // Data used to insert a move for the input of a MUST_REUSE_INPUT definition.
  struct ReusedInputReg {
    LAllocation* source;
    AnyRegister dest;
    LDefinition::Type type;
    ReusedInputReg(LAllocation* source, AnyRegister dest,
                   LDefinition::Type type)
        : source(source), dest(dest), type(type) {}
  };
  Vector<ReusedInputReg, 4, BackgroundSystemAllocPolicy> reusedInputs_;

  enum class AllocationKind { UseAtStart, Output, UseOrTemp };

  [[nodiscard]] bool addAllocatedReg(LInstruction* ins, uint32_t vregId,
                                     bool usedAtStart, AnyRegister reg) {
    currentInsRegs_.addUnchecked(reg);
    if (!usedAtStart) {
      currentInsRegsNotAtStart_.addUnchecked(reg);
    }
    availableRegs_.take(reg);
    MOZ_ASSERT_IF(vregs_[vregId].hasRegister(), hasMultipleRegsForVreg_);
    vregs_[vregId].setRegisterIndex(allocatedRegs_.length());
    return allocatedRegs_.emplaceBack(vregId, reg, ins->id());
  }
  void markUseOfAllocatedReg(LInstruction* ins, AllocatedRegister& allocated,
                             bool usedAtStart) {
    MOZ_ASSERT(!availableRegs_.has(allocated.reg()));
    allocated.setLastUsedAtInsId(ins->id());
    currentInsRegs_.addUnchecked(allocated.reg());
    if (!usedAtStart) {
      currentInsRegsNotAtStart_.addUnchecked(allocated.reg());
    }
  }

  [[nodiscard]] bool init();
  [[nodiscard]] bool analyzeLiveness();
  [[nodiscard]] bool allocateRegisters();

  void removeAllocatedRegisterAtIndex(size_t index);

  [[nodiscard]] bool tryReuseRegistersFromPredecessor(MBasicBlock* block);
  void saveAndClearAllocatedRegisters(MBasicBlock* block);

  void freeDeadVregsAfterInstruction(VirtualRegBitSet& liveGC, LNode* ins);

  [[nodiscard]] bool allocateForBlockEnd(LBlock* block, LInstruction* ins);

  bool ensureStackLocation(uint32_t vregId, LAllocation* allocation);
  LAllocation registerOrStackLocation(LInstruction* ins, uint32_t vregId,
                                      bool trackRegUse);

  [[nodiscard]] bool spillRegister(LInstruction* ins,
                                   AllocatedRegister allocated);

  [[nodiscard]] bool evictRegister(LInstruction* ins, AnyRegister reg);

  void scanDefinition(LInstruction* ins, LDefinition* def, bool isTemp);

  [[nodiscard]] bool allocateForNonFixedDefOrUse(LInstruction* ins,
                                                 uint32_t vregId,
                                                 AllocationKind kind,
                                                 AnyRegister* reg);
  [[nodiscard]] bool allocateForFixedUse(LInstruction* ins, const LUse* use,
                                         AnyRegister* reg);
  [[nodiscard]] bool allocateForRegisterUse(LInstruction* ins, const LUse* use,
                                            AnyRegister* reg);

  [[nodiscard]] bool allocateForDefinition(uint32_t blockLastId,
                                           LInstruction* ins, LDefinition* def,
                                           bool isTemp);
  [[nodiscard]] bool allocateForInstruction(VirtualRegBitSet& liveGC,
                                            uint32_t blockLastId,
                                            LInstruction* ins);

  [[nodiscard]] bool addLiveRegisterToSafepoint(LSafepoint* safepoint,
                                                AllocatedRegister allocated);
  [[nodiscard]] bool populateSafepoint(VirtualRegBitSet& liveGC,
                                       LInstruction* ins,
                                       LSafepoint* safepoint);

  void assertValidRegisterStateBeforeInstruction() const;

 public:
  SimpleAllocator(MIRGenerator* mir, LIRGenerator* lir, LIRGraph& graph)
      : RegisterAllocator(mir, lir, graph),
        vregs_(mir->alloc()),
        liveGCIn_(mir->alloc()),
        vregLastUses_(mir->alloc()) {}

  [[nodiscard]] bool go();
};

}  // namespace jit
}  // namespace js

#endif /* jit_SimpleAllocator_h */
