//===-- SystemZMCCodeEmitter.cpp - Convert SystemZ code to machine code ---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the SystemZMCCodeEmitter class.
//
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/SystemZMCFixups.h"
#include "MCTargetDesc/SystemZMCTargetDesc.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCFixup.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <cstdint>

using namespace llvm;

#define DEBUG_TYPE "mccodeemitter"

namespace {

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

public:
  SystemZMCCodeEmitter(const MCInstrInfo &MCII, MCContext &Ctx)
      : MCII(MCII), Ctx(Ctx) {}

  ~SystemZMCCodeEmitter() override = default;

  // 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;
  uint32_t getOperandBitOffset(const MCInst &MI, unsigned OpNum,
                               const MCSubtargetInfo &STI) const;

  // Called by the TableGen code to get the binary encoding of operand
  // MO in MI.  Fixups is the list of fixups against MI.
  uint64_t getMachineOpValue(const MCInst &MI, const MCOperand &MO,
                             SmallVectorImpl<MCFixup> &Fixups,
                             const MCSubtargetInfo &STI) const;

  // Return the encoded immediate value for the OpNum operand. If it is a
  // symbol, add a fixup for it and return 0.
  template <SystemZ::FixupKind Kind>
  uint64_t getImmOpValue(const MCInst &MI, unsigned OpNum,
                         SmallVectorImpl<MCFixup> &Fixups,
                         const MCSubtargetInfo &STI) const;

  // Called by the TableGen code to get the binary encoding of a length value.
  // Length values are encoded by subtracting 1 from the actual value.
  template <SystemZ::FixupKind Kind>
  uint64_t getLenEncoding(const MCInst &MI, unsigned OpNum,
                          SmallVectorImpl<MCFixup> &Fixups,
                          const MCSubtargetInfo &STI) const;

  // Operand OpNum of MI needs a PC-relative fixup of kind Kind at
  // Offset bytes from the start of MI.  Add the fixup to Fixups
  // and return the in-place addend, which since we're a RELA target
  // is always 0.  If AllowTLS is true and optional operand OpNum + 1
  // is present, also emit a TLS call fixup for it.
  uint64_t getPCRelEncoding(const MCInst &MI, unsigned OpNum,
                            SmallVectorImpl<MCFixup> &Fixups,
                            unsigned Kind, int64_t Offset,
                            bool AllowTLS) const;

  uint64_t getPC16DBLEncoding(const MCInst &MI, unsigned OpNum,
                              SmallVectorImpl<MCFixup> &Fixups,
                              const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC16DBL, 2, false);
  }
  uint64_t getPC32DBLEncoding(const MCInst &MI, unsigned OpNum,
                              SmallVectorImpl<MCFixup> &Fixups,
                              const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC32DBL, 2, false);
  }
  uint64_t getPC16DBLTLSEncoding(const MCInst &MI, unsigned OpNum,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC16DBL, 2, true);
  }
  uint64_t getPC32DBLTLSEncoding(const MCInst &MI, unsigned OpNum,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC32DBL, 2, true);
  }
  uint64_t getPC12DBLBPPEncoding(const MCInst &MI, unsigned OpNum,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC12DBL, 1, false);
  }
  uint64_t getPC16DBLBPPEncoding(const MCInst &MI, unsigned OpNum,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC16DBL, 4, false);
  }
  uint64_t getPC24DBLBPPEncoding(const MCInst &MI, unsigned OpNum,
                                 SmallVectorImpl<MCFixup> &Fixups,
                                 const MCSubtargetInfo &STI) const {
    return getPCRelEncoding(MI, OpNum, Fixups,
                            SystemZ::FK_390_PC24DBL, 3, false);
  }
};

} // end anonymous namespace
  //
static void addFixup(SmallVectorImpl<MCFixup> &Fixups, uint32_t Offset,
                     const MCExpr *Value, uint16_t Kind, bool PCRel = false) {
  Fixups.push_back(MCFixup::create(Offset, Value, Kind, PCRel));
}

void SystemZMCCodeEmitter::encodeInstruction(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();
  // Big-endian insertion of Size bytes.
  unsigned ShiftValue = (Size * 8) - 8;
  for (unsigned I = 0; I != Size; ++I) {
    CB.push_back(uint8_t(Bits >> ShiftValue));
    ShiftValue -= 8;
  }
}

uint64_t SystemZMCCodeEmitter::
getMachineOpValue(const MCInst &MI, const MCOperand &MO,
                  SmallVectorImpl<MCFixup> &Fixups,
                  const MCSubtargetInfo &STI) const {
  if (MO.isReg())
    return Ctx.getRegisterInfo()->getEncodingValue(MO.getReg());
  // SystemZAsmParser::parseAnyRegister() produces KindImm when registers are
  // specified as integers.
  if (MO.isImm())
    return static_cast<uint64_t>(MO.getImm());
  llvm_unreachable("Unexpected operand type!");
}

template <SystemZ::FixupKind Kind>
uint64_t SystemZMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNum,
                                             SmallVectorImpl<MCFixup> &Fixups,
                                             const MCSubtargetInfo &STI) const {
  const MCOperand &MO = MI.getOperand(OpNum);
  if (MO.isImm())
    return static_cast<uint64_t>(MO.getImm());
  if (MO.isExpr()) {
    unsigned MIBitSize = MCII.get(MI.getOpcode()).getSize() * 8;
    uint32_t RawBitOffset = getOperandBitOffset(MI, OpNum, STI);
    unsigned OpBitSize =
        SystemZ::MCFixupKindInfos[Kind - FirstTargetFixupKind].TargetSize;
    uint32_t BitOffset = MIBitSize - RawBitOffset - OpBitSize;
    addFixup(Fixups, BitOffset >> 3, MO.getExpr(), Kind);
    return 0;
  }
  llvm_unreachable("Unexpected operand type!");
}

template <SystemZ::FixupKind Kind>
uint64_t
SystemZMCCodeEmitter::getLenEncoding(const MCInst &MI, unsigned OpNum,
                                     SmallVectorImpl<MCFixup> &Fixups,
                                     const MCSubtargetInfo &STI) const {
  return getImmOpValue<Kind>(MI, OpNum, Fixups, STI) - 1;
}

uint64_t
SystemZMCCodeEmitter::getPCRelEncoding(const MCInst &MI, unsigned OpNum,
                                       SmallVectorImpl<MCFixup> &Fixups,
                                       unsigned Kind, int64_t Offset,
                                       bool AllowTLS) const {
  SMLoc Loc = MI.getLoc();
  const MCOperand &MO = MI.getOperand(OpNum);
  const MCExpr *Expr;
  if (MO.isImm())
    Expr = MCConstantExpr::create(MO.getImm() + Offset, Ctx);
  else {
    Expr = MO.getExpr();
    if (Offset) {
      // The operand value is relative to the start of MI, but the fixup
      // is relative to the operand field itself, which is Offset bytes
      // into MI.  Add Offset to the relocation value to cancel out
      // this difference.
      const MCExpr *OffsetExpr = MCConstantExpr::create(Offset, Ctx);
      Expr = MCBinaryExpr::createAdd(Expr, OffsetExpr, Ctx, Loc);
    }
  }
  addFixup(Fixups, Offset, Expr, Kind, true);

  // Output the fixup for the TLS marker if present.
  if (AllowTLS && OpNum + 1 < MI.getNumOperands()) {
    const MCOperand &MOTLS = MI.getOperand(OpNum + 1);
    addFixup(Fixups, 0, MOTLS.getExpr(), SystemZ::FK_390_TLS_CALL);
  }
  return 0;
}

#define GET_OPERAND_BIT_OFFSET
#include "SystemZGenMCCodeEmitter.inc"

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