//===-- 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/ColossusMCTargetDesc.h"
#include "MCTargetDesc/ColossusMCFixups.h"
#include "MCTargetDesc/ColossusMCInstrInfo.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, raw_ostream &OS,
                         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, raw_ostream &OS,
                               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, raw_ostream &OS,
                  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::Writer(OS, support::little).write<uint32_t>(Bits);
  }
  else {
    assert (Size == 8 && "Unexpected instruction siz when trying to encode");
    support::endian::Writer(OS, support::little).write<uint64_t>(Bits);
  }
}

void ColossusMCCodeEmitter::
encodeInstruction(const MCInst &MI, raw_ostream &OS,
                  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(), OS, 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, OS, 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));
}
}

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

#include "ColossusGenMCCodeEmitter.inc"
