//===-- ColossusMCCodeEmitter.cpp - Convert Colossus code to machine code -===//
//    Copyright (c) 2023 Graphcore Ltd. All Rights Reserved.
//     Licensed under the Apache License, Version 2.0 (the "License");
//     you may not use this file except in compliance with the License.
//     You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//     Unless required by applicable law or agreed to in writing, software
//     distributed under the License is distributed on an "AS IS" BASIS,
//     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//     See the License for the specific language governing permissions and
//     limitations under the License.
// --- LLVM Exceptions to the Apache 2.0 License ----
//
// As an exception, if, as a result of your compiling your source code, portions
// of this Software are embedded into an Object form of such source code, you
// may redistribute such embedded portions in such Object form without complying
// with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
//
// In addition, if you combine or link compiled forms of this Software with
// software that is licensed under the GPLv2 ("Combined Software") and if a
// court of competent jurisdiction determines that the patent provision (Section
// 3), the indemnity provision (Section 9) or other Section of the License
// conflicts with the conditions of the GPLv2, you may retroactively and
// prospectively choose to deem waived or otherwise exclude such Section(s) of
// the License, but only in their entirety and only with respect to the Combined
// Software.
//
//===----------------------------------------------------------------------===//
//
// This file implements the ColossusMCCodeEmitter class.
//
//===----------------------------------------------------------------------===//

#include "Colossus.h"
#include "MCTargetDesc/ColossusMCFixups.h"
#include "MCTargetDesc/ColossusMCInstrInfo.h"
#include "MCTargetDesc/ColossusMCTargetDesc.h"
#include "llvm/ADT/bit.h"
#include "llvm/CodeGen/TargetOpcodes.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/Support/EndianStream.h"

using namespace llvm;

#define DEBUG_TYPE "mccodeemitter"

namespace {
class ColossusMCCodeEmitter : public MCCodeEmitter {
  const MCInstrInfo &MCII;
  MCContext &Ctx;

public:
  ColossusMCCodeEmitter(const MCInstrInfo &mcii, MCContext &ctx)
      : MCII(mcii), Ctx(ctx) {}

  ~ColossusMCCodeEmitter() {}

  // Override MCCodeEmitter.
  void encodeInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
                         SmallVectorImpl<MCFixup> &Fixups,
                         const MCSubtargetInfo &STI) const override;

private:
  // Automatically generated by TableGen.
  uint64_t getBinaryCodeForInstr(const MCInst &MI,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const;

  /// getMachineOpValue - Return binary encoding of operand. If the machine
  /// operand requires relocation, record the relocation and return zero.
  unsigned getMachineOpValue(const MCInst &MI, const MCOperand &MO,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;
  unsigned getOpValue(const MCInst &MI, unsigned OpNo,
                      SmallVectorImpl<MCFixup> &Fixups,
                      const MCSubtargetInfo &STI,
                      llvm::Colossus::Fixups fixup_type) const;
  unsigned getRel19S2OpValue(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;
  unsigned getRunOpValue(const MCInst &MI, unsigned OpNo,
                         SmallVectorImpl<MCFixup> &Fixups,
                         const MCSubtargetInfo &STI) const;
  unsigned getRel8OpValue(const MCInst &MI, unsigned OpNo,
                          SmallVectorImpl<MCFixup> &Fixups,
                          const MCSubtargetInfo &STI) const;
  unsigned getRel16OpValue(const MCInst &MI, unsigned OpNo,
                           SmallVectorImpl<MCFixup> &Fixups,
                           const MCSubtargetInfo &STI) const;
  unsigned getRel20OpValue(const MCInst &MI, unsigned OpNo,
                           SmallVectorImpl<MCFixup> &Fixups,
                           const MCSubtargetInfo &STI) const;

  void EncodeSingleInstruction(const MCInst &MI, SmallVectorImpl<char> &CB,
                               SmallVectorImpl<MCFixup> &Fixups,
                               const MCSubtargetInfo &STI) const;

  unsigned getMEMrrEncoding(const MCInst &MI, unsigned OpNo,
                            SmallVectorImpl<MCFixup> &Fixups,
                            const MCSubtargetInfo &STI) const;

  unsigned getMEMrrrEncoding(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;

  unsigned getMEMrriEncoding(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;
  unsigned getARPairEncoding(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;

  unsigned getARQuadEncoding(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;
};
} // end anonymous namespace

MCCodeEmitter *llvm::createColossusMCCodeEmitter(const MCInstrInfo &MCII,
                                                 MCContext &Ctx) {
  return new ColossusMCCodeEmitter(MCII, Ctx);
}

void ColossusMCCodeEmitter::EncodeSingleInstruction(
    const MCInst &MI, SmallVectorImpl<char> &CB,
    SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
  uint64_t Bits = getBinaryCodeForInstr(MI, Fixups, STI);
  unsigned Size = MCII.get(MI.getOpcode()).getSize();

  if (ColossusMCInstrInfo::isControl(MCII, MI)) {
    auto expr = MCConstantExpr::create(0, Ctx);
    Fixups.push_back(MCFixup::create(
        0, expr, (MCFixupKind)Colossus::fixup_colossus_control));
  } else if (ColossusMCInstrInfo::isSystem(MCII, MI)) {
    auto expr = MCConstantExpr::create(0, Ctx);
    Fixups.push_back(
        MCFixup::create(0, expr, (MCFixupKind)Colossus::fixup_colossus_system));
  }

  if (Size == 4) {
    support::endian::write<uint32_t>(CB, Bits, endianness::little);
  } else {
    assert(Size == 8 && "Unexpected instruction siz when trying to encode");
    support::endian::write<uint64_t>(CB, Bits, endianness::little);
  }
}

void ColossusMCCodeEmitter::encodeInstruction(
    const MCInst &MI, SmallVectorImpl<char> &CB,
    SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {

  if (MI.getOpcode() == TargetOpcode::BUNDLE) {
    unsigned InstOffset = 0;
    for (auto &InstOperand : MI) {
      auto FixupsBeforeEncoding = Fixups.size();
      EncodeSingleInstruction(*InstOperand.getInst(), CB, Fixups, STI);

      // If any fixups have been created for an instruction then we must
      // correct the offset of the fixup for instructions in the bundle.
      if (Fixups.size() > FixupsBeforeEncoding) {
        for (; FixupsBeforeEncoding < Fixups.size(); ++FixupsBeforeEncoding) {
          auto &Fixup = Fixups[FixupsBeforeEncoding];
          Fixup.setOffset(InstOffset);
        }
      }
      InstOffset += 4;
    }
  } else {
    auto expr = MCConstantExpr::create(0, Ctx);
    Fixups.push_back(
        MCFixup::create(0, expr, (MCFixupKind)Colossus::fixup_colossus_single));
    EncodeSingleInstruction(MI, CB, Fixups, STI);
  }
}

unsigned
ColossusMCCodeEmitter::getMEMrrEncoding(const MCInst &MI, unsigned OpNo,
                                        SmallVectorImpl<MCFixup> &Fixups,
                                        const MCSubtargetInfo &STI) const {
  // 4-bit base and offset registers
  // offset[7:4], base[3:0]
  unsigned BaseRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo), Fixups, STI);
  unsigned OffRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo + 1), Fixups, STI);
  return (OffRegBits << 4) | BaseRegBits;
}

unsigned
ColossusMCCodeEmitter::getMEMrrrEncoding(const MCInst &MI, unsigned OpNo,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  // 4-bit base, delta and offset registers
  // offset[11:8], delta[7:4], base[3:0]
  unsigned BaseRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo), Fixups, STI);
  unsigned DeltaRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo + 1), Fixups, STI);
  unsigned OffRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo + 2), Fixups, STI);
  return (OffRegBits << 8) | (DeltaRegBits << 4) | BaseRegBits;
}

unsigned
ColossusMCCodeEmitter::getMEMrriEncoding(const MCInst &MI, unsigned OpNo,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  // 4-bit register base and delta, 12-bit immediate offset.
  // offset[19:8], delta[7:4], base[3:0]
  unsigned BaseRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo), Fixups, STI);
  unsigned DeltaRegBits =
      getMachineOpValue(MI, MI.getOperand(OpNo + 1), Fixups, STI);
  unsigned OffImmBits =
      getMachineOpValue(MI, MI.getOperand(OpNo + 2), Fixups, STI);
  return (OffImmBits << 8) | (DeltaRegBits << 4) | BaseRegBits;
}

/// getMachineOpValue - Return binary encoding of operand. If the machine
/// operand requires relocation, record the relocation and return zero.
unsigned
ColossusMCCodeEmitter::getMachineOpValue(const MCInst &MI, const MCOperand &MO,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  if (MO.isReg())
    return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());

  assert(MO.isImm() && "did not expect relocated expression");
  return static_cast<unsigned>(MO.getImm());
}

unsigned ColossusMCCodeEmitter::getOpValue(
    const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups,
    const MCSubtargetInfo &STI, llvm::Colossus::Fixups fixup_type) const {
  const MCOperand &MO = MI.getOperand(OpNo);
  if (MO.isReg() || MO.isImm())
    return getMachineOpValue(MI, MO, Fixups, STI);

  MCFixupKind FU = (MCFixupKind)fixup_type;
  Fixups.push_back(MCFixup::create(0, MO.getExpr(), FU));
  return 0;
}

unsigned
ColossusMCCodeEmitter::getRel19S2OpValue(const MCInst &MI, unsigned OpNo,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  return getOpValue(MI, OpNo, Fixups, STI, Colossus::fixup_colossus_19_s2);
}

unsigned
ColossusMCCodeEmitter::getRunOpValue(const MCInst &MI, unsigned OpNo,
                                     SmallVectorImpl<MCFixup> &Fixups,
                                     const MCSubtargetInfo &STI) const {
  return getOpValue(MI, OpNo, Fixups, STI, Colossus::fixup_colossus_run);
}

unsigned
ColossusMCCodeEmitter::getRel8OpValue(const MCInst &MI, unsigned OpNo,
                                      SmallVectorImpl<MCFixup> &Fixups,
                                      const MCSubtargetInfo &STI) const {
  // Create a fixup for rpt instructions. This allows us to perform
  // Relaxations to align rpt bodies.
  if (ColossusMCInstrInfo::isRepeat(MI)) {
    //  Fixups require expressions. rpt operand
    auto expr = MCConstantExpr::create(0, Ctx);
    MCFixupKind FU = (MCFixupKind)Colossus::fixup_colossus_rpt;
    Fixups.push_back(MCFixup::create(4, expr, FU));
  }
  return getOpValue(MI, OpNo, Fixups, STI, Colossus::fixup_colossus_8);
}

unsigned
ColossusMCCodeEmitter::getRel16OpValue(const MCInst &MI, unsigned OpNo,
                                       SmallVectorImpl<MCFixup> &Fixups,
                                       const MCSubtargetInfo &STI) const {
  return getOpValue(MI, OpNo, Fixups, STI, Colossus::fixup_colossus_16);
}

unsigned
ColossusMCCodeEmitter::getRel20OpValue(const MCInst &MI, unsigned OpNo,
                                       SmallVectorImpl<MCFixup> &Fixups,
                                       const MCSubtargetInfo &STI) const {
  return getOpValue(MI, OpNo, Fixups, STI, Colossus::fixup_colossus_20);
}

unsigned
ColossusMCCodeEmitter::getARPairEncoding(const MCInst &MI, unsigned OpNo,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  auto reg =
      Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(OpNo).getReg());
  return reg > 6 ? 14 : reg << 1;
}

unsigned
ColossusMCCodeEmitter::getARQuadEncoding(const MCInst &MI, unsigned OpNo,
                                         SmallVectorImpl<MCFixup> &Fixups,
                                         const MCSubtargetInfo &STI) const {
  auto reg =
      Ctx.getRegisterInfo()->getEncodingValue(MI.getOperand(OpNo).getReg());
  return reg > 3 ? 12 : reg << 2;
}

namespace {
template <unsigned N>
unsigned getImmIZValue(const MCInst &MI, unsigned OpNo,
                       SmallVectorImpl<MCFixup> &Fixups,
                       const MCSubtargetInfo &STI) {
  const MCOperand &MO = MI.getOperand(OpNo);
  assert(MO.isImm() && "did not expect relocated expression");
  return static_cast<unsigned>(MO.getImm() >> (32 - N));
}
} // namespace

static unsigned getImm12IZValue(const MCInst &MI, unsigned OpNo,
                                SmallVectorImpl<MCFixup> &Fixups,
                                const MCSubtargetInfo &STI) {
  return getImmIZValue<12>(MI, OpNo, Fixups, STI);
}

#include "ColossusGenMCCodeEmitter.inc"
