Adding Registers
We now have our triple registered with llc. To compile a program, we need
to add our target details so that llc knows how to generate code.
This chapter will add registers to the Nova backend.
Registers
The CPU has 32 registers (that are 32-bit). The general-purpose registers have the names $0 to $31 and also have aliases.
We add all these registers in the NovaRegisterInfo.td file.
The Register class
First, create the top level Nova.td file. This pulls in all other
tablegen files.
// This is the top tablegen file that pulls in other code// specific to the Nova target.
include "llvm/Target/Target.td"
include "NovaRegisterInfo.td"
def : ProcessorModel<"generic", NoSchedModel, []>;
def Nova : Target {}We have to enumerate our registers as records of the class Register defined in llvm/include/llvm/Target/Target.td.
The fields of interest to us are:
string Namespace- the target namespace (Nova in our case)string AsmName- the assembly name of the register ($zero, $1 etc.)list<string> AltNames- aliases for the registerlist<Register> SubRegs- subregisters of the registerbit isConstant- whether the register holds a constant value
// Nova CPU Registers
class NovaReg<string n> : Register<n> { let Namespace = "Nova";}
class NovaGPRReg<string n> : Register<n> { let Namespace = "Nova";}
let Namespace = "Nova" in { def ZERO : NovaGPRReg<"zero"> { let isConstant = 1; } def AT : NovaGPRReg<"at">; def V0 : NovaGPRReg<"2">;20 collapsed lines
def V1 : NovaGPRReg<"3">; def A0 : NovaGPRReg<"4">; def A1 : NovaGPRReg<"5">; def A2 : NovaGPRReg<"6">; def A3 : NovaGPRReg<"7">; def T0 : NovaGPRReg<"8">; def T1 : NovaGPRReg<"9">; def T2 : NovaGPRReg<"10">; def T3 : NovaGPRReg<"11">; def T4 : NovaGPRReg<"12">; def T5 : NovaGPRReg<"13">; def T6 : NovaGPRReg<"14">; def T7 : NovaGPRReg<"15">; def S0 : NovaGPRReg<"16">; def S1 : NovaGPRReg<"17">; def S2 : NovaGPRReg<"18">; def S3 : NovaGPRReg<"19">; def S4 : NovaGPRReg<"20">; def S5 : NovaGPRReg<"21">; def S6 : NovaGPRReg<"22">; def S7 : NovaGPRReg<"23">; def T8 : NovaGPRReg<"24">; def T9 : NovaGPRReg<"25">; def K0 : NovaGPRReg<"26">; def K1 : NovaGPRReg<"27">; def GP : NovaGPRReg<"gp">; // 28 def SP : NovaGPRReg<"sp">; // 29 def FP : NovaGPRReg<"fp">; // 30 def RA : NovaGPRReg<"ra">; // 31
//== Special registers ==// //=======================// def PC : NovaReg<"pc">;}With this, we can see the tablgen records for the registers.
cd llvm/lib/Target/Nova../../../../build/bin/llvm-tblgen Nova.td -I=../../../include -print-records | grep Novadef A0 { // Register NovaGPRRegstring Namespace = "Nova";string AsmName = "4";list<string> AltNames = [];list<Register> Aliases = [];list<Register> SubRegs = [];
Next, we need to group these registers into register classes.
Register classes (RC)
A register class is a set of registers that can be used interchangeably in an instruction. For example, the GPR register class contains all the general-purpose registers.
-
All virtual registers have a register class assigned.
-
The register allocator also uses the RC to allocate physical registers.
-
A new register class needs to be created for defining register constraints.
Our general purpose registers will go into the GPR32 register
class which is defined as a tablgen record deriving from the
RegisterClass class.
//=== -----------------// Register Classes//=== -----------------
def GPR32 : RegisterClass<"Nova","Nova" is the namespace for the generated definitions by tablgen.
Next, we mention the type of values these registers can hold. For our case, these are all 32-bit integers.
def GPR32 : RegisterClass<"Nova", [i32],The alignment of the register is set to 4 bytes.
def GPR32 : RegisterClass<"Nova", [i32], 32,Now we list all the registers in this class.
def GPR32 : RegisterClass<"Nova", [i32], 32, (add // Reserved ZERO, AT, // Return values and arguments V0, V1, A0, A1, A2, A3, // Caller-saved temporaries (not preserved across calls, clobbered by callee) T0, T1, T2, T3, T4, T5, T6, T7, // Callee-saved (preserved across calls) S0, S1, S2, S3, S4, S5, S6, S7, // More caller-saved temporaries T8, T9, // Kernel registers K0, K1, // Global pointer GP, // Stack pointer SP, // Frmame pointer FP, // Return address (mostly used by instruction "jal") RA)>;Other Register classes
Since we need constraints on allocation ($zero should always be an operand 0),
we need another register class for it.
// Return address (mostly used by instruction "jal") RA)>;
def GPR32ZERO : RegisterClass<"Nova", [i32], 32, (add ZERO)>;For the rest, we need another class. We can be clever here and use the set difference operator instead of listing all of them again.
def GPR32ZERO : RegisterClass<"Nova", [i32], 32, (add ZERO)>;
def GPR32NONZERO : RegisterClass<"Nova", [i32], 32, (sub GPR32, GPR32ZERO)>;Unallocatable registers
Special registers like $ra are not to be allocatd by the register allocator to virtual registers.
def GPR32NONZERO : RegisterClass<"Nova", [i32], 32, (sub GPR32, GPR32ZERO)>;
let isAllocatable = false in { def CPURAReg : RegisterClass<"Nova", [i32], 32, (add RA)>; def CPUSPReg : RegisterClass<"Nova", [i32], 32, (add SP)>;
def GP32 : RegisterClass<"Nova", [i32], 32, (add GP)>;}Generating the .inc file
🚀 We have our basic register definitions in place!
To generate the enum values for our registers, we need to instruct cmake to invoke
llvm-tblgen on our Nova.td file.
TableGen output
To generate our register file into, we use the -gen-register-info TableGen backend.
Let’s see what we get from this backend. Run llvm-tblgen with our top level td file
with the backend enabled.
cd llvm/lib/Target/Nova../../../../build/bin/llvm-tblgen Nova.td -I=../../../include -gen-register-info > reg.h⏭️ We’ll take a look at reg.h to see what gets generated.
LLVM Classes
We see that there is a class named MCRegisterClass that is being forward declared. This class is defined in llvm/include/llvm/MC/MCRegisterInfo.h. Let’s work our way through this file and related files to understand the register classes in LLVM.
All these classes are in the MC layer. The MC layer is the Machine Code layer in LLVM. It is responsible for generating machine code for the target architecture and is used to for the lowest level of code generation.
Representing Registers
Registers are represented as unsigned integers in LLVM. Have a look
at the small MCRegister.h file.
MCPhysRegis a physical register number.MCRegUnitis a register unit number. We’ll see this later.MCRegisterwraps an unsigned integer as a physical register.
The unsigned values are divided into ranges to designate different types of registers:
- 0 stands for no register used. (Not-A-Register)
- [1;2^30) are all physical registers (assigned by TableGen).
- [2^31;2^32) are virtual registers.
Show source
↳ class MCRegister {
constexpr MCRegister(unsigned Val = 0) : Reg(Val) {}
// Register numbers can represent physical registers, virtual registers, and // sometimes stack slots. The unsigned values are divided into these ranges: // // 0 Not a register, can be used as a sentinel. // [1;2^30) Physical registers assigned by TableGen. // [2^30;2^31) Stack slots. (Rarely used.) // [2^31;2^32) Virtual registers assigned by MachineRegisterInfo. // // Further sentinels can be allocated from the small negative integers. // DenseMapInfo<unsigned> uses -1u and -2u. static_assert(std::numeric_limits<decltype(Reg)>::max() >= 0xFFFFFFFF, "Reg isn't large enough to hold full range."); static constexpr unsigned NoRegister = 0u; static constexpr unsigned FirstPhysicalReg = 1u; static constexpr unsigned LastPhysicalReg = (1u << 30) - 1;
/// Return true if the specified register number is inRegister Class
The MCRegisterClass denotes the register class and mirrors the RegisterClass in the Target.td file.
Register Description
The MCRegisterDesc class is used to describe the register.
It contains the Name, SubRegs and SuperRegs along with other details.
MCRegisterInfo
A static array of MCRegisterDesc is used to define all registers in the target.
This class tracks a pointer to that array.
Most of this class is boilerplate code to access the register descriptions, used by the TableGen backend. Some methods in this class are:
/// This method should return the register where the return /// address can be found. MCRegister getRARegister() const;
/// Return the register which is the program counter. MCRegister getProgramCounter() const;
/// Return the number of registers this target has (useful for /// sizing arrays holding per register information) unsigned getNumRegs() const;The generated file
We can now look at reg.h, the generated file.
Enums are behind the GET_REGINFO_* directive in the generated file.
We have the following things generated for us by tablegen:
- The register enums
Nova::AT, Nova::RA, Nova::PCetc. - Register class enums
Nova::GPR32RegClassID, Nova::CPURARegRegClassIDetc. - Under
GET_REGINFO_MC_DESC, we have theInitNovaMCRegisterInfofunction that initializes theMCRegisterInfoinstance for our backend. (these are the definitions, so need to go in a cpp file) - In
GET_REGINFO_HEADER, TheNovaGenRegisterInfoclass definition, and - Register classes
TargetRegisterClass GPR32RegisterClassand others. - In
GET_REGINFO_TARGET_DESC, some functions likegetRegUnitWeight(unsigned RegUnit)and the constructor forNovaGenRegisterInfo. - Other functions like
getFrameLowering(): returns aNovaFrameLoweringobject.
Add to cmake
Tell cmake to generate the register info file.
tablegen(LLVM NovaGenRegisterInfo.inc -gen-register-info)add_public_tablegen_target(NovaCommonTableGen)
add_llvm_component_group(Nova)Adding the NovaRegisterInfo class
First, add the NovaGenRegisterInfo class declaration.
///// NovaRegisterInfo.h//// This contains the Nova implementation of the TargetRegisterInfo class.
#ifndef LLVM_LIB_TARGET_NOVA_NOVAREGISTERINFO_H#define LLVM_LIB_TARGET_NOVA_NOVAREGISTERINFO_H
#include "llvm/CodeGen/TargetRegisterInfo.h"
#define GET_REGINFO_HEADER#include "NovaGenRegisterInfo.inc"
// This includes the following:// #include "llvm/CodeGen/TargetRegisterInfo.h"//// struct NovaGenRegisterInfo : public TargetRegisterInfo;// namespace Nova { // Register classes // extern const TargetRegisterClass GPR32RegClass; // ... and others// }Remember to add this to CMakelists.txt as well.
add_llvm_target(NovaCodeGen NovaTargetMachine.cpp NovaRegisterInfo.cppAdd the NovaRegisterInfo class.
// ... and others// }
namespace llvm {
class NovaRegisterInfo final : public NovaGenRegisterInfo {public: NovaRegisterInfo(); const MCPhysReg* getCalleeSavedRegs(const MachineFunction *MF) const override;
BitVector getReservedRegs(const MachineFunction &MF) const override;
bool eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj, unsigned FIOperandNum, RegScavenger *RS = nullptr) const override;
Register getFrameRegister(const MachineFunction &MF) const override;};
} // end namespace llvm
#endifRevisit this later when we start to lower call and return instructions.
Now we register our RegisterInfo class with our target.
First, remove stand-in method we added earlier and move
it to a new file NovaMCTargetDesc.cpp. This file contains
all the target description classes for registers
instructions.
extern "C" void LLVMInitializeNovaTargetMC() { // TODO: Add initialize target MC}
// static Target &getTheNovaTarget() {/// This file provides Nova-specific target descriptions.#ifndef LLVM_LIB_TARGET_NOVA_MCTARGETDESC_H#define LLVM_LIB_TARGET_NOVA_MCTARGETDESC_H
// Include symbolic names for registers. This includes the enum// for register to register number mapping. (Nova::RA etc) and// the register classes.#define GET_REGINFO_ENUM#include "NovaGenRegisterInfo.inc"
#endifRegister the Nova’s MCRegisterInfo class instance in its corresponding
implementation file.
#include "NovaMCTargetDesc.h"#include "NovaTargetInfo.h"
#include "llvm/MC/MCRegisterInfo.h"#include "llvm/MC/TargetRegistry.h"
using namespace llvm;
#define GET_REGINFO_MC_DESC// Defines the InitNovaMCRegisterInfo function#include "NovaGenRegisterInfo.inc"
static MCRegisterInfo* createNovaMCRegisterInfo(const Triple &TT) { MCRegisterInfo *X = new MCRegisterInfo(); InitNovaMCRegisterInfo(X, Nova::RA); return X;}
extern "C" void LLVMInitializeNovaTargetMC() { Target *T = &getTheNovaTarget(); TargetRegistry::RegisterMCRegInfo(*T, createNovaMCRegisterInfo);}And include the file in our build.
NovaTargetMachine.cpp NovaRegisterInfo.cpp MCTargetDesc/NovaMCTargetDesc.cppMove the target getters to a new file so we can reuse them.
// // TODO: Add initialize target
#include "Nova.h"#include "NovaTargetInfo.h"#include "llvm/MC/TargetRegistry.h"#include "llvm/PassRegistry.h"#ifndef LLVM_LIB_TARGET_NOVA_TARGETINFO_H#define LLVM_LIB_TARGET_NOVA_TARGETINFO_H
namespace llvm {class Target;
Target &getTheNovaTarget();
} // namespace llvm
#endifFix our definition written earlier:
using namespace llvm;
static Target &getTheNovaTarget() {Target &llvm::getTheNovaTarget() { static Target TheNovaTarget; return TheNovaTarget;#include "NovaRegisterInfo.h"#include "MCTargetDesc/NovaMCTargetDesc.h"#include "NovaFrameLowering.h"
#include "llvm/ADT/BitVector.h"#include "llvm/CodeGen/MachineFunction.h"#include "llvm/CodeGen/TargetSubtargetInfo.h"// #include "NovaFrameLowering.h"
using namespace llvm;
#define DEBUG_TYPE "nova-reg-info"
#define GET_REGINFO_TARGET_DESC#include "NovaGenRegisterInfo.inc"
using namespace llvm;
NovaRegisterInfo::NovaRegisterInfo() : NovaGenRegisterInfo(Nova::RA) {}
const MCPhysReg *NovaRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const { static const MCPhysReg CSRList[] = { Nova::SP, // Stack Pointer Nova::FP, // Frame Pointer Nova::S0, // R0 }; return CSRList;}
BitVector NovaRegisterInfo::getReservedRegs(const MachineFunction &MF) const { static const MCPhysReg ReservedRegs[] = { Nova::ZERO, Nova::K0, Nova::K1, Nova::SP, Nova::FP, // reserve this for now, // but this can be used as a general purpose register // if frame pointer is not used in a function. Nova::GP // Reserve only if small section is used. }; BitVector Reserved(getNumRegs()); for (MCPhysReg Reg : ReservedRegs) { Reserved.set(Reg); } return Reserved;}
bool NovaRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj, unsigned FIOperandNum, RegScavenger *RS) const { return true;}
Register NovaRegisterInfo::getFrameRegister(const MachineFunction &MF) const { return Nova::FP;}Since the NovaGenRegisterInfo (which drives from TargetRegisterInfo) refers to NovaFrameLowering,
we need to add that class.
//===-- ----//// This file defines the frame lowering for the Nova target//===---------------------------------------------------------
#ifndef LLVM_LIB_TARGET_NOVA_FRAMELOWERING_H#define LLVM_LIB_TARGET_NOVA_FRAMELOWERING_H
#include "llvm/CodeGen/MachineBasicBlock.h"#include "llvm/CodeGen/TargetFrameLowering.h"#include "llvm/CodeGen/TargetSubtargetInfo.h"
namespace llvm {class NovaFrameLowering : public TargetFrameLowering {public: explicit NovaFrameLowering(const TargetSubtargetInfo &STI, Align Alignment) : TargetFrameLowering(StackGrowsDown, Alignment, 0, Alignment) {}
void emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB) const override {} void emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const override {}
bool hasFPImpl(const MachineFunction &MF) const override { return true; }};
} // end namespace llvm
#endifWith this, you can successfully compile llc for the Nova target.
The build folder should have the tablegen’erated file for the register info.
cmake --build build --target llcfind build -name "NovaGenRegisterInfo.inc"build/lib/Target/Nova/NovaGenRegisterInfo.inc
Let’s move on to add the instructions for Nova.