#include "simeng/pipeline/RegisterAliasTable.hh"

#include <cassert>

namespace simeng {
namespace pipeline {

RegisterAliasTable::RegisterAliasTable(
    std::vector<RegisterFileStructure> architecturalStructure,
    std::vector<uint16_t> physicalRegisterCounts)
    : mappingTable_(architecturalStructure.size()),
      historyTable_(architecturalStructure.size()),
      destinationTable_(architecturalStructure.size()),
      freeQueues_(architecturalStructure.size()) {
  assert(architecturalStructure.size() == physicalRegisterCounts.size() &&
         "The number of physical register types does not match the number of "
         "architectural register types");

  for (size_t type = 0; type < architecturalStructure.size(); type++) {
    auto archCount = architecturalStructure[type].quantity;
    auto physCount = physicalRegisterCounts[type];
    assert(archCount <= physCount &&
           "Cannot have fewer physical registers than architectural registers");

    // Set up the initial mapping table state for this register type
    mappingTable_[type].resize(archCount);

    for (size_t tag = 0; tag < archCount; tag++) {
      // Pre-assign a physical register to each architectural register
      mappingTable_[type][tag] = tag;
    }

    // Add remaining physical registers to free queue
    for (size_t tag = archCount; tag < physCount; tag++) {
      freeQueues_[type].push(tag);
    }

    // Set up history/destination tables
    historyTable_[type].resize(physCount);
    destinationTable_[type].resize(physCount);
  }
};

Register RegisterAliasTable::getMapping(Register architectural) const {
  // Asserts to ensure mapping isn't attempted for an out-of-bound index (i.e.
  // mapping of WZR / XZR)
  assert(architectural.type < mappingTable_.size() &&
         "Invalid register type. Cannot find RAT mapping.");
  assert(architectural.type >= 0 &&
         "Invalid register type. Cannot find RAT mapping.");

  auto tag = mappingTable_[architectural.type][architectural.tag];
  return {architectural.type, tag};
}

bool RegisterAliasTable::canAllocate(uint8_t type,
                                     unsigned int quantity) const {
  return (freeQueues_[type].size() >= quantity);
}

bool RegisterAliasTable::canRename(uint8_t type) const {
  // Renaming possible iff there are more physical than architectural registers
  return destinationTable_[type].size() > mappingTable_[type].size();
}

unsigned int RegisterAliasTable::freeRegistersAvailable(uint8_t type) const {
  return freeQueues_[type].size();
}

Register RegisterAliasTable::allocate(Register architectural) {
  std::queue<uint16_t>& freeQueue = freeQueues_[architectural.type];
  assert(freeQueue.size() > 0 &&
         "Attempted to allocate free register when none were available");

  auto tag = freeQueue.front();
  freeQueue.pop();

  // Keep the old physical register in the history table
  historyTable_[architectural.type][tag] =
      mappingTable_[architectural.type][architectural.tag];

  // Update the mapping table with the new tag, and mark the architectural
  // register it replaces in the destination table
  mappingTable_[architectural.type][architectural.tag] = tag;
  destinationTable_[architectural.type][tag] = architectural.tag;

  return {architectural.type, tag};
}

void RegisterAliasTable::commit(Register physical) {
  // Find the register previously mapped to the same architectural register and
  // free it
  auto oldTag = historyTable_[physical.type][physical.tag];
  freeQueues_[physical.type].push(oldTag);
}
void RegisterAliasTable::rewind(Register physical) {
  // Find which architectural tag this referred to
  auto destinationTag = destinationTable_[physical.type][physical.tag];
  // Rewind the mapping table to the old physical tag
  mappingTable_[physical.type][destinationTag] =
      historyTable_[physical.type][physical.tag];
  // Add the rewound physical tag back to the free queue
  freeQueues_[physical.type].push(physical.tag);
}
void RegisterAliasTable::free(Register physical) {
  freeQueues_[physical.type].push(physical.tag);
}

}  // namespace pipeline
}  // namespace simeng
