Implementing M88k-specific target descriptions – The Target Description-1
By Peggy Johnston / June 18, 2022 / No Comments / Creating the disassembler, Emitting machine instructions, Exams of IT, Implementing M88kSubtarget, ITCertification Exams
In the M88kMCTargetDesc.cpp file, we need to make a couple of additions:
- First, we need a new factory method for the MCInstPrinter class and the MCAsmInfo class:
static MCInstPrinter *createM88kMCInstPrinter(
const Triple &T, unsigned SyntaxVariant,
const MCAsmInfo &MAI, const MCInstrInfo &MII,
const MCRegisterInfo &MRI) {
return new M88kInstPrinter(MAI, MII, MRI);
}
static MCAsmInfo *
createM88kMCAsmInfo(const MCRegisterInfo &MRI,
const Triple &TT,
const MCTargetOptions &Options) {
return new M88kMCAsmInfo(TT);
}
- Finally, within the LLVMInitializeM88kTargetMC() function, we need to add the registration of the factory methods:
extern “C” LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTargetMC() {
// …
TargetRegistry::RegisterMCAsmInfo(
getTheM88kTarget(), createM88kMCAsmInfo);
TargetRegistry::RegisterMCCodeEmitter(
getTheM88kTarget(), createM88kMCCodeEmitter);
TargetRegistry::RegisterMCInstPrinter(
getTheM88kTarget(), createM88kMCInstPrinter);
}
Now we have implemented all required support classes, and we can finally add the assembler parser.
Creating the M88k assembler parser class
There is only an M88kAsmParser.cpp implementation file in the AsmParser directory. The M88kOperand class represents a parsed operand and is used by the generated source code and our assembler parser implementation in class M88kAssembler. Both classes are in an anonymous namespace, only the factory method is globally visible. Let’s take a look at the M88kOperand class first:
- An operand can be a token, a register, or an immediate. We define the OperandKind enumeration to distinguish between these cases. The current kind is stored in the Kind member. We also store the start and the end location of the operand, which is needed to print the error message:
class M88kOperand : public MCParsedAsmOperand {
enum OperandKind { OpKind_Token, OpKind_Reg,
OpKind_Imm };
OperandKind Kind;
SMLoc StartLoc, EndLoc;
- To store the value, we define a union. The token is stored as a StringRef and the register is identified by its number. The immediate is represented by the MCExpr class: union {
StringRef Token;
unsigned RegNo;
const MCExpr *Imm;
}; - The constructor initializes all fields but the union. Furthermore, we define methods to return the value of the start and the end locations:
public:
M88kOperand(OperandKind Kind, SMLoc StartLoc,
SMLoc EndLoc)
: Kind(Kind), StartLoc(StartLoc), EndLoc(EndLoc) {}
SMLoc getStartLoc() const override { return StartLoc; }
SMLoc getEndLoc() const override { return EndLoc; }
- For each operand type, we must define four methods. For a register, the methods are isReg() to check whether the operand is a register, getReg() to return the value, createReg() to create a register operand, and addRegOperands() to add an operant to an instruction. The latter function is called by the generated source code when an instruction is constructed. The methods for the token and the immediate are similar: bool isReg() const override {
return Kind == OpKind_Reg;
}
unsigned getReg() const override { return RegNo; }
static std::unique_ptr
createReg(unsigned Num, SMLoc StartLoc,
SMLoc EndLoc) {
auto Op = std::make_unique(
OpKind_Reg, StartLoc, EndLoc);
Op->RegNo = Num;
return Op;
}
void addRegOperands(MCInst &Inst, unsigned N) const {
assert(N == 1 && “Invalid number of operands”);
Inst.addOperand(MCOperand::createReg(getReg()));
}