// This file is part of AsmJit project <https://asmjit.com>
//
// See <asmjit/core.h> or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib

#ifndef ASMJIT_CORE_RAASSIGNMENT_P_H_INCLUDED
#define ASMJIT_CORE_RAASSIGNMENT_P_H_INCLUDED

#include "../core/api-config.h"
#ifndef ASMJIT_NO_COMPILER

#include "../core/radefs_p.h"

ASMJIT_BEGIN_NAMESPACE

//! \cond INTERNAL
//! \addtogroup asmjit_ra
//! \{

//! Holds the current register assignment.
//!
//! Has two purposes:
//!
//!   1. Holds register assignment of a local register allocator (see \ref RALocalAllocator).
//!   2. Holds register assignment of the entry of basic blocks (see \ref RABlock).
class RAAssignment {
public:
  ASMJIT_NONCOPYABLE(RAAssignment)

  enum Ids : uint32_t {
    kPhysNone = 0xFF,
    kWorkNone = RAWorkReg::kIdNone
  };

  enum DirtyBit : uint32_t {
    kClean = 0,
    kDirty = 1
  };

  struct Layout {
    //! Index of architecture registers per group.
    RARegIndex physIndex;
    //! Count of architecture registers per group.
    RARegCount physCount;
    //! Count of physical registers of all groups.
    uint32_t physTotal;
    //! Count of work registers.
    uint32_t workCount;
    //! WorkRegs data (vector).
    const RAWorkRegs* workRegs;

    inline void reset() noexcept {
      physIndex.reset();
      physCount.reset();
      physTotal = 0;
      workCount = 0;
      workRegs = nullptr;
    }
  };

  struct PhysToWorkMap {
    //! Assigned registers (each bit represents one physical reg).
    RARegMask assigned;
    //! Dirty registers (spill slot out of sync or no spill slot).
    RARegMask dirty;
    //! PhysReg to WorkReg mapping.
    uint32_t workIds[1 /* ... */];

    [[nodiscard]]
    static ASMJIT_INLINE_NODEBUG size_t sizeOf(size_t count) noexcept {
      return Support::alignUp(sizeof(PhysToWorkMap) - sizeof(uint32_t) + count * sizeof(uint32_t), Globals::kZoneAlignment);
    }

    ASMJIT_INLINE void reset(size_t count) noexcept {
      assigned.reset();
      dirty.reset();

      for (size_t i = 0; i < count; i++) {
        workIds[i] = kWorkNone;
      }
    }

    ASMJIT_INLINE void copyFrom(const PhysToWorkMap* other, size_t count) noexcept {
      size_t size = sizeOf(count);
      memcpy(this, other, size);
    }

    ASMJIT_INLINE void unassign(RegGroup group, uint32_t physId, uint32_t indexInWorkIds) noexcept {
      assigned.clear(group, Support::bitMask(physId));
      dirty.clear(group, Support::bitMask(physId));
      workIds[indexInWorkIds] = kWorkNone;
    }
  };

  struct WorkToPhysMap {
    //! WorkReg to PhysReg mapping
    uint8_t physIds[1 /* ... */];

    [[nodiscard]]
    static ASMJIT_INLINE_NODEBUG size_t sizeOf(size_t count) noexcept {
      return Support::alignUp(size_t(count) * sizeof(uint8_t), Globals::kZoneAlignment);
    }

    ASMJIT_INLINE void reset(size_t count) noexcept {
      for (size_t i = 0; i < count; i++) {
        physIds[i] = kPhysNone;
      }
    }

    ASMJIT_INLINE void copyFrom(const WorkToPhysMap* other, size_t count) noexcept {
      size_t size = sizeOf(count);
      if (ASMJIT_LIKELY(size)) {
        memcpy(this, other, size);
      }
    }
  };

  //! \name Members
  //! \{

  //! Physical registers layout.
  Layout _layout;
  //! WorkReg to PhysReg mapping.
  WorkToPhysMap* _workToPhysMap;
  //! PhysReg to WorkReg mapping and assigned/dirty bits.
  PhysToWorkMap* _physToWorkMap;
  //! Optimization to translate PhysRegs to WorkRegs faster.
  Support::Array<uint32_t*, Globals::kNumVirtGroups> _physToWorkIds;

  //! \}

  //! \name Construction & Destruction
  //! \{

  inline RAAssignment() noexcept {
    _layout.reset();
    resetMaps();
  }

  ASMJIT_INLINE void initLayout(const RARegCount& physCount, const RAWorkRegs& workRegs) noexcept {
    // Layout must be initialized before data.
    ASMJIT_ASSERT(_physToWorkMap == nullptr);
    ASMJIT_ASSERT(_workToPhysMap == nullptr);

    _layout.physIndex.buildIndexes(physCount);
    _layout.physCount = physCount;
    _layout.physTotal = uint32_t(_layout.physIndex[RegGroup::kMaxVirt]) +
                        uint32_t(_layout.physCount[RegGroup::kMaxVirt]) ;
    _layout.workCount = workRegs.size();
    _layout.workRegs = &workRegs;
  }

  ASMJIT_INLINE void initMaps(PhysToWorkMap* physToWorkMap, WorkToPhysMap* workToPhysMap) noexcept {
    _physToWorkMap = physToWorkMap;
    _workToPhysMap = workToPhysMap;
    for (RegGroup group : RegGroupVirtValues{}) {
      _physToWorkIds[group] = physToWorkMap->workIds + _layout.physIndex.get(group);
    }
  }

  ASMJIT_INLINE void resetMaps() noexcept {
    _physToWorkMap = nullptr;
    _workToPhysMap = nullptr;
    _physToWorkIds.fill(nullptr);
  }

  //! \}

  //! \name Accessors
  //! \{

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG PhysToWorkMap* physToWorkMap() const noexcept { return _physToWorkMap; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG WorkToPhysMap* workToPhysMap() const noexcept { return _workToPhysMap; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG RARegMask& assigned() noexcept { return _physToWorkMap->assigned; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG const RARegMask& assigned() const noexcept { return _physToWorkMap->assigned; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG uint32_t assigned(RegGroup group) const noexcept { return _physToWorkMap->assigned[group]; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG RARegMask& dirty() noexcept { return _physToWorkMap->dirty; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG const RARegMask& dirty() const noexcept { return _physToWorkMap->dirty; }

  [[nodiscard]]
  ASMJIT_INLINE_NODEBUG RegMask dirty(RegGroup group) const noexcept { return _physToWorkMap->dirty[group]; }

  [[nodiscard]]
  inline uint32_t workToPhysId(RegGroup group, uint32_t workId) const noexcept {
    DebugUtils::unused(group);
    ASMJIT_ASSERT(workId != kWorkNone);
    ASMJIT_ASSERT(workId < _layout.workCount);
    return _workToPhysMap->physIds[workId];
  }

  [[nodiscard]]
  inline uint32_t physToWorkId(RegGroup group, uint32_t physId) const noexcept {
    ASMJIT_ASSERT(physId < Globals::kMaxPhysRegs);
    return _physToWorkIds[group][physId];
  }

  [[nodiscard]]
  inline bool isPhysAssigned(RegGroup group, uint32_t physId) const noexcept {
    ASMJIT_ASSERT(physId < Globals::kMaxPhysRegs);
    return Support::bitTest(_physToWorkMap->assigned[group], physId);
  }

  [[nodiscard]]
  inline bool isPhysDirty(RegGroup group, uint32_t physId) const noexcept {
    ASMJIT_ASSERT(physId < Globals::kMaxPhysRegs);
    return Support::bitTest(_physToWorkMap->dirty[group], physId);
  }

  //! \}

  //! \name Assignment
  //!
  //! These are low-level allocation helpers that are used to update the current mappings between physical and
  //! virt/work registers and also to update masks that represent allocated and dirty registers. These functions
  //! don't emit any code; they are only used to update and keep all mappings in sync.
  //!
  //! \{

  //! Assign [VirtReg/WorkReg] to a physical register.
  inline void assign(RegGroup group, uint32_t workId, uint32_t physId, bool dirty) noexcept {
    ASMJIT_ASSERT(workToPhysId(group, workId) == kPhysNone);
    ASMJIT_ASSERT(physToWorkId(group, physId) == kWorkNone);
    ASMJIT_ASSERT(!isPhysAssigned(group, physId));
    ASMJIT_ASSERT(!isPhysDirty(group, physId));

    _workToPhysMap->physIds[workId] = uint8_t(physId);
    _physToWorkIds[group][physId] = workId;

    RegMask regMask = Support::bitMask(physId);
    _physToWorkMap->assigned[group] |= regMask;
    _physToWorkMap->dirty[group] |= regMask & Support::bitMaskFromBool<RegMask>(dirty);

    verify();
  }

  //! Reassign [VirtReg/WorkReg] to `dstPhysId` from `srcPhysId`.
  inline void reassign(RegGroup group, uint32_t workId, uint32_t dstPhysId, uint32_t srcPhysId) noexcept {
    ASMJIT_ASSERT(dstPhysId != srcPhysId);
    ASMJIT_ASSERT(workToPhysId(group, workId) == srcPhysId);
    ASMJIT_ASSERT(physToWorkId(group, srcPhysId) == workId);
    ASMJIT_ASSERT(isPhysAssigned(group, srcPhysId) == true);
    ASMJIT_ASSERT(isPhysAssigned(group, dstPhysId) == false);

    _workToPhysMap->physIds[workId] = uint8_t(dstPhysId);
    _physToWorkIds[group][srcPhysId] = kWorkNone;
    _physToWorkIds[group][dstPhysId] = workId;

    RegMask srcMask = Support::bitMask(srcPhysId);
    RegMask dstMask = Support::bitMask(dstPhysId);

    bool dirty = (_physToWorkMap->dirty[group] & srcMask) != 0;
    RegMask regMask = dstMask | srcMask;

    _physToWorkMap->assigned[group] ^= regMask;
    _physToWorkMap->dirty[group] ^= regMask & Support::bitMaskFromBool<RegMask>(dirty);

    verify();
  }

  inline void swap(RegGroup group, uint32_t aWorkId, uint32_t aPhysId, uint32_t bWorkId, uint32_t bPhysId) noexcept {
    ASMJIT_ASSERT(aPhysId != bPhysId);
    ASMJIT_ASSERT(workToPhysId(group, aWorkId) == aPhysId);
    ASMJIT_ASSERT(workToPhysId(group, bWorkId) == bPhysId);
    ASMJIT_ASSERT(physToWorkId(group, aPhysId) == aWorkId);
    ASMJIT_ASSERT(physToWorkId(group, bPhysId) == bWorkId);
    ASMJIT_ASSERT(isPhysAssigned(group, aPhysId));
    ASMJIT_ASSERT(isPhysAssigned(group, bPhysId));

    _workToPhysMap->physIds[aWorkId] = uint8_t(bPhysId);
    _workToPhysMap->physIds[bWorkId] = uint8_t(aPhysId);
    _physToWorkIds[group][aPhysId] = bWorkId;
    _physToWorkIds[group][bPhysId] = aWorkId;

    RegMask aMask = Support::bitMask(aPhysId);
    RegMask bMask = Support::bitMask(bPhysId);
    RegMask flipMask = Support::bitMaskFromBool<RegMask>(((_physToWorkMap->dirty[group] & aMask) != 0) ^ ((_physToWorkMap->dirty[group] & bMask) != 0));
    RegMask regMask = aMask | bMask;
    _physToWorkMap->dirty[group] ^= regMask & flipMask;

    verify();
  }

  //! Unassign [VirtReg/WorkReg] from a physical register.
  inline void unassign(RegGroup group, uint32_t workId, uint32_t physId) noexcept {
    ASMJIT_ASSERT(physId < Globals::kMaxPhysRegs);
    ASMJIT_ASSERT(workToPhysId(group, workId) == physId);
    ASMJIT_ASSERT(physToWorkId(group, physId) == workId);
    ASMJIT_ASSERT(isPhysAssigned(group, physId));

    _workToPhysMap->physIds[workId] = kPhysNone;
    _physToWorkIds[group][physId] = kWorkNone;

    RegMask regMask = Support::bitMask(physId);
    _physToWorkMap->assigned[group] &= ~regMask;
    _physToWorkMap->dirty[group] &= ~regMask;

    verify();
  }

  inline void makeClean(RegGroup group, uint32_t workId, uint32_t physId) noexcept {
    DebugUtils::unused(workId);
    RegMask regMask = Support::bitMask(physId);
    _physToWorkMap->dirty[group] &= ~regMask;
  }

  inline void makeDirty(RegGroup group, uint32_t workId, uint32_t physId) noexcept {
    DebugUtils::unused(workId);
    RegMask regMask = Support::bitMask(physId);
    _physToWorkMap->dirty[group] |= regMask;
  }

  //! \}

  //! \name Utilities
  //! \{

  ASMJIT_INLINE void swap(RAAssignment& other) noexcept {
    std::swap(_workToPhysMap, other._workToPhysMap);
    std::swap(_physToWorkMap, other._physToWorkMap);
    _physToWorkIds.swap(other._physToWorkIds);
  }

  inline void assignWorkIdsFromPhysIds() noexcept {
    memset(_workToPhysMap, uint8_t(Reg::kIdBad), WorkToPhysMap::sizeOf(_layout.workCount));

    for (RegGroup group : RegGroupVirtValues{}) {
      uint32_t physBaseIndex = _layout.physIndex[group];
      Support::BitWordIterator<RegMask> it(_physToWorkMap->assigned[group]);

      while (it.hasNext()) {
        uint32_t physId = it.next();
        uint32_t workId = _physToWorkMap->workIds[physBaseIndex + physId];

        ASMJIT_ASSERT(workId != kWorkNone);
        _workToPhysMap->physIds[workId] = uint8_t(physId);
      }
    }
  }

  inline void copyFrom(const PhysToWorkMap* physToWorkMap) noexcept {
    memcpy(_physToWorkMap, physToWorkMap, PhysToWorkMap::sizeOf(_layout.physTotal));
    assignWorkIdsFromPhysIds();
  }

  inline void copyFrom(const PhysToWorkMap* physToWorkMap, const WorkToPhysMap* workToPhysMap) noexcept {
    memcpy(_physToWorkMap, physToWorkMap, PhysToWorkMap::sizeOf(_layout.physTotal));
    memcpy(_workToPhysMap, workToPhysMap, WorkToPhysMap::sizeOf(_layout.workCount));
  }

  inline void copyFrom(const RAAssignment& other) noexcept {
    copyFrom(other.physToWorkMap(), other.workToPhysMap());
  }

  // Not really useful outside of debugging.
  [[nodiscard]]
  bool equals(const RAAssignment& other) const noexcept {
    // Layout should always match.
    if (_layout.physIndex != other._layout.physIndex ||
        _layout.physCount != other._layout.physCount ||
        _layout.physTotal != other._layout.physTotal ||
        _layout.workCount != other._layout.workCount ||
        _layout.workRegs  != other._layout.workRegs)
      return false;

    uint32_t physTotal = _layout.physTotal;
    uint32_t workCount = _layout.workCount;

    for (uint32_t physId = 0; physId < physTotal; physId++) {
      uint32_t thisWorkId = _physToWorkMap->workIds[physId];
      uint32_t otherWorkId = other._physToWorkMap->workIds[physId];
      if (thisWorkId != otherWorkId) {
        return false;
      }
    }

    for (uint32_t workId = 0; workId < workCount; workId++) {
      uint32_t thisPhysId = _workToPhysMap->physIds[workId];
      uint32_t otherPhysId = other._workToPhysMap->physIds[workId];
      if (thisPhysId != otherPhysId) {
        return false;
      }
    }

    if (_physToWorkMap->assigned != other._physToWorkMap->assigned ||
        _physToWorkMap->dirty    != other._physToWorkMap->dirty    )
      return false;

    return true;
  }

#if defined(ASMJIT_BUILD_DEBUG)
  ASMJIT_NOINLINE void verify() noexcept {
    // Verify WorkToPhysMap.
    {
      for (uint32_t workId = 0; workId < _layout.workCount; workId++) {
        uint32_t physId = _workToPhysMap->physIds[workId];
        if (physId != kPhysNone) {
          const RAWorkReg* workReg = _layout.workRegs->at(workId);
          RegGroup group = workReg->group();
          ASMJIT_ASSERT(_physToWorkIds[group][physId] == workId);
        }
      }
    }

    // Verify PhysToWorkMap.
    {
      for (RegGroup group : RegGroupVirtValues{}) {
        uint32_t physCount = _layout.physCount[group];
        for (uint32_t physId = 0; physId < physCount; physId++) {
          uint32_t workId = _physToWorkIds[group][physId];
          if (workId != kWorkNone) {
            ASMJIT_ASSERT(_workToPhysMap->physIds[workId] == physId);
          }
        }
      }
    }
  }
#else
  inline void verify() noexcept {}
#endif

  //! \}
};

//! \}
//! \endcond

ASMJIT_END_NAMESPACE

#endif // !ASMJIT_NO_COMPILER
#endif // ASMJIT_CORE_RAASSIGNMENT_P_H_INCLUDED
