Internally in LLVM, an instruction is represented by an instance of the MCInst class. An instruction can be emitted as an assembler text or in binary into an object file. The M88kMCCodeEmitter class creates the binary representation of an instruction, while the M88kInstPrinter class emits the textual representation of it.
First, we will implement the M88kMCCodeEmitter class, which is stored in the M88kMCCodeEmitter.cpp file:

  1. Most of the class is generated by TableGen. Therefore, we only need to add some boilerplate code. Note that there is no corresponding header file; the prototype of the factory function will be added to the M88kMCTargetDesc.h file. It begins with setting up a statistic counter for the number of emitted instructions:

using namespace llvm;
define DEBUG_TYPE “mccodeemitter”
STATISTIC(MCNumEmitted,
“Number of MC instructions emitted”);

  1. The M88kMCCodeEmitter class lives in an anonymous namespace. We only need to implement the encodeInstruction() method, which is declared in the base class, and the getMachineOpValue() helper method. The other getBinaryCodeForInstr() method is generated by TableGen from the target description:

namespace {
class M88kMCCodeEmitter : public MCCodeEmitter {
const MCInstrInfo &MCII;
MCContext &Ctx;
public:
M88kMCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx)
: MCII(MCII), Ctx(Ctx) {}
~M88kMCCodeEmitter() override = default;
void encodeInstruction(
const MCInst &MI, raw_ostream &OS,
SmallVectorImpl &Fixups,
const MCSubtargetInfo &STI) const override;
uint64_t getBinaryCodeForInstr(
const MCInst &MI,
SmallVectorImpl &Fixups,
const MCSubtargetInfo &STI) const;
unsigned
getMachineOpValue(const MCInst &MI,
const MCOperand &MO,
SmallVectorImpl &Fixups,
const MCSubtargetInfo &STI) const;
};
} // end anonymous namespace

  1. The encodeInstruction() method just looks up the binary representation of the instruction, increments the statistic counter, and writes the bytes out in big-endian format. Remember that the instructions have a fixed size of 4 bytes, therefore we use the uint32_t type on the endian stream:

void M88kMCCodeEmitter::encodeInstruction(
const MCInst &MI, raw_ostream &OS,
SmallVectorImpl &Fixups,
const MCSubtargetInfo &STI) const {
uint64_t Bits =
getBinaryCodeForInstr(MI, Fixups, STI);
++MCNumEmitted;
support::endian::write(OS, Bits,
support::big);
}

  1. The task of the getMachineOpValue() method is to return the binary representation of operands. In the target description, we defined the bit ranges where the registers used are stored in an instruction. Here, we compute the value, which is stored in these places. The method is called from the generated code. We only support two cases. For a register, the encoding of the register, which we defined in the target description, is returned. For an immediate, the immediate value is returned:

unsigned M88kMCCodeEmitter::getMachineOpValue(
const MCInst &MI, const MCOperand &MO,
SmallVectorImpl &Fixups,
const MCSubtargetInfo &STI) const {
if (MO.isReg())
return Ctx.getRegisterInfo()->getEncodingValue(
MO.getReg());
if (MO.isImm())
return static_cast(MO.getImm());
return 0;
}

  1. And last, we include the generated file and create a factory method for the class:

include “M88kGenMCCodeEmitter.inc”
MCCodeEmitter *
llvm::createM88kMCCodeEmitter(const MCInstrInfo &MCII,
MCContext &Ctx) {
return new M88kMCCodeEmitter(MCII, Ctx);
}

Leave a Reply

Your email address will not be published. Required fields are marked *