//===----- HexagonMCShuffler.cpp - MC bundle shuffling --------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This implements the shuffling of insns inside a bundle according to the
// packet formation rules of the Hexagon ISA.
//
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "hexagon-shuffle"

#include "Hexagon.h"
#include "MCTargetDesc/HexagonMCInstrInfo.h"
#include "MCTargetDesc/HexagonMCShuffler.h"
#include "MCTargetDesc/HexagonMCTargetDesc.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

static bool DisableShuffle = false;

void HexagonMCShuffler::init(MCInst &MCB) {
  if (HexagonMCInstrInfo::isBundle(MCB)) {
    MCInst const *Extender = nullptr;
    // Copy the bundle for the shuffling.
    for (const auto &I : HexagonMCInstrInfo::bundleInstructions(MCB)) {
      assert(!HexagonMCInstrInfo::getDesc(MCII, *I.getInst()).isPseudo());
      MCInst *MI = const_cast<MCInst *>(I.getInst());

      if (!HexagonMCInstrInfo::isImmext(*MI)) {
        append(MI, Extender, HexagonMCInstrInfo::getUnits(MCII, STI, *MI),
               false);
        Extender = nullptr;
      } else
        Extender = MI;
    }
  }

  BundleFlags = MCB.getOperand(0).getImm();
}

void HexagonMCShuffler::init(MCInst &MCB, MCInst const *AddMI,
                             bool bInsertAtFront) {
  if (HexagonMCInstrInfo::isBundle(MCB)) {
    if (bInsertAtFront && AddMI)
      append(AddMI, nullptr, HexagonMCInstrInfo::getUnits(MCII, STI, *AddMI),
             false);
    MCInst const *Extender = nullptr;
    // Copy the bundle for the shuffling.
    for (auto const &I : HexagonMCInstrInfo::bundleInstructions(MCB)) {
      assert(!HexagonMCInstrInfo::getDesc(MCII, *I.getInst()).isPseudo());
      MCInst *MI = const_cast<MCInst *>(I.getInst());
      if (!HexagonMCInstrInfo::isImmext(*MI)) {
        append(MI, Extender, HexagonMCInstrInfo::getUnits(MCII, STI, *MI),
               false);
        Extender = nullptr;
      } else
        Extender = MI;
    }
    if (!bInsertAtFront && AddMI)
      append(AddMI, nullptr, HexagonMCInstrInfo::getUnits(MCII, STI, *AddMI),
             false);
  }

  BundleFlags = MCB.getOperand(0).getImm();
}

void HexagonMCShuffler::copyTo(MCInst &MCB) {
  MCB.clear();
  MCB.addOperand(MCOperand::createImm(BundleFlags));
  // Copy the results into the bundle.
  for (HexagonShuffler::iterator I = begin(); I != end(); ++I) {

    MCInst const *MI = I->getDesc();
    MCInst const *Extender = I->getExtender();
    if (Extender)
      MCB.addOperand(MCOperand::createInst(Extender));
    MCB.addOperand(MCOperand::createInst(MI));
  }
}

bool HexagonMCShuffler::reshuffleTo(MCInst &MCB) {
  if (shuffle()) {
    // Copy the results into the bundle.
    copyTo(MCB);
  }
  //  else
  //    DEBUG(MCB.dump());

  return (!getError());
}

bool llvm::HexagonMCShuffle(MCInstrInfo const &MCII, MCSubtargetInfo const &STI,
                            MCInst &MCB) {
  HexagonMCShuffler MCS(MCII, STI, MCB);

  if (DisableShuffle)
    // Ignore if user chose so.
    return false;

  if (!HexagonMCInstrInfo::bundleSize(MCB)) {
    // There once was a bundle:
    //    BUNDLE %D2<imp-def>, %R4<imp-def>, %R5<imp-def>, %D7<imp-def>, ...
    //      * %D2<def> = IMPLICIT_DEF; flags:
    //      * %D7<def> = IMPLICIT_DEF; flags:
    // After the IMPLICIT_DEFs were removed by the asm printer, the bundle
    // became empty.
    DEBUG(dbgs() << "Skipping empty bundle");
    return false;
  } else if (!HexagonMCInstrInfo::isBundle(MCB)) {
    DEBUG(dbgs() << "Skipping stand-alone insn");
    return false;
  }

  // Reorder the bundle and copy the result.
  if (!MCS.reshuffleTo(MCB)) {
    // Unless there is any error, which should not happen at this point.
    unsigned shuffleError = MCS.getError();
    switch (shuffleError) {
    default:
      llvm_unreachable("unknown error");
    case HexagonShuffler::SHUFFLE_ERROR_INVALID:
      llvm_unreachable("invalid packet");
    case HexagonShuffler::SHUFFLE_ERROR_STORES:
      llvm_unreachable("too many stores");
    case HexagonShuffler::SHUFFLE_ERROR_LOADS:
      llvm_unreachable("too many loads");
    case HexagonShuffler::SHUFFLE_ERROR_BRANCHES:
      llvm_unreachable("too many branches");
    case HexagonShuffler::SHUFFLE_ERROR_NOSLOTS:
      llvm_unreachable("no suitable slot");
    case HexagonShuffler::SHUFFLE_ERROR_SLOTS:
      llvm_unreachable("over-subscribed slots");
    case HexagonShuffler::SHUFFLE_SUCCESS: // Single instruction case.
      return true;
    }
  }

  return true;
}

unsigned
llvm::HexagonMCShuffle(MCInstrInfo const &MCII, MCSubtargetInfo const &STI,
                       MCContext &Context, MCInst &MCB,
                       SmallVector<DuplexCandidate, 8> possibleDuplexes) {

  if (DisableShuffle)
    return HexagonShuffler::SHUFFLE_SUCCESS;

  if (!HexagonMCInstrInfo::bundleSize(MCB)) {
    // There once was a bundle:
    //    BUNDLE %D2<imp-def>, %R4<imp-def>, %R5<imp-def>, %D7<imp-def>, ...
    //      * %D2<def> = IMPLICIT_DEF; flags:
    //      * %D7<def> = IMPLICIT_DEF; flags:
    // After the IMPLICIT_DEFs were removed by the asm printer, the bundle
    // became empty.
    DEBUG(dbgs() << "Skipping empty bundle");
    return HexagonShuffler::SHUFFLE_SUCCESS;
  } else if (!HexagonMCInstrInfo::isBundle(MCB)) {
    DEBUG(dbgs() << "Skipping stand-alone insn");
    return HexagonShuffler::SHUFFLE_SUCCESS;
  }

  bool doneShuffling = false;
  unsigned shuffleError;
  while (possibleDuplexes.size() > 0 && (!doneShuffling)) {
    // case of Duplex Found
    DuplexCandidate duplexToTry = possibleDuplexes.pop_back_val();
    MCInst Attempt(MCB);
    HexagonMCInstrInfo::replaceDuplex(Context, Attempt, duplexToTry);
    HexagonMCShuffler MCS(MCII, STI, Attempt); // copy packet to the shuffler
    if (MCS.size() == 1) {                     // case of one duplex
      // copy the created duplex in the shuffler to the bundle
      MCS.copyTo(MCB);
      doneShuffling = true;
      return HexagonShuffler::SHUFFLE_SUCCESS;
    }
    // try shuffle with this duplex
    doneShuffling = MCS.reshuffleTo(MCB);
    shuffleError = MCS.getError();

    if (doneShuffling)
      break;
  }

  if (doneShuffling == false) {
    HexagonMCShuffler MCS(MCII, STI, MCB);
    doneShuffling = MCS.reshuffleTo(MCB); // shuffle
    shuffleError = MCS.getError();
  }
  if (!doneShuffling)
    return shuffleError;

  return HexagonShuffler::SHUFFLE_SUCCESS;
}

bool llvm::HexagonMCShuffle(MCInstrInfo const &MCII, MCSubtargetInfo const &STI,
                            MCInst &MCB, MCInst const *AddMI, int fixupCount) {
  if (!HexagonMCInstrInfo::isBundle(MCB) || !AddMI)
    return false;

  // if fixups present, make sure we don't insert too many nops that would
  // later prevent an extender from being inserted.
  unsigned int bundleSize = HexagonMCInstrInfo::bundleSize(MCB);
  if (bundleSize >= HEXAGON_PACKET_SIZE)
    return false;
  if (fixupCount >= 2) {
    return false;
  } else {
    if (bundleSize == HEXAGON_PACKET_SIZE - 1 && fixupCount)
      return false;
  }

  if (DisableShuffle)
    return false;

  HexagonMCShuffler MCS(MCII, STI, MCB, AddMI);
  if (!MCS.reshuffleTo(MCB)) {
    unsigned shuffleError = MCS.getError();
    switch (shuffleError) {
    default:
      return false;
    case HexagonShuffler::SHUFFLE_SUCCESS: // single instruction case
      return true;
    }
  }

  return true;
}
