Many architectures define several register banks. A register bank is a set of registers. Typical register banks are general-purpose register banks and floating-point register banks. Why is this information important? Moving a value from one register to another is usually cheap inside a register bank, but copying the value to another register bank can be costly or impossible. Thus, we must select a good register bank for each operand.
The implementation of this class involves an addition to the target description. In the GISel/M88lRegisterbanks.td file, we define our single register bank, referencing the register classes we have defined:
def GRRegBank : RegisterBank<“GRRB”, [GPR, GPR64]>;
From this line, some support code is generated. However, we still need to add some code that could be potentially generated. First, we need to define partial mappings. This tells the framework at which bit index a value begins, how wide it is, and to which register bank it maps. We have two entries, one for each register class:
RegisterBankInfo::PartialMapping
M88kGenRegisterBankInfo::PartMappings[]{
{0, 32, M88k::GRRegBank},
{0, 64, M88k::GRRegBank},
};
To index this array, we must define an enumeration:
enum PartialMappingIdx { PMI_GR32 = 0, PMI_GR64, };
Since we have only three address instructions, we need three partial mappings, one for each operand. We must create an array with all those pointers, with the first entry denoting an invalid mapping:
RegisterBankInfo::ValueMapping
M88kGenRegisterBankInfo::ValMappings[]{
{nullptr, 0},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR32], 1},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR32], 1},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR32], 1},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR64], 1},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR64], 1},
{&M88kGenRegisterBankInfo::PartMappings[PMI_GR64], 1},
};
To access that array, we must define a function:
const RegisterBankInfo::ValueMapping *
M88kGenRegisterBankInfo::getValueMapping(
PartialMappingIdx RBIdx) {
return &ValMappings[1 + 3*RBIdx];
}
When creating these tables, it is easy to make errors. At first glance, all this information can be derived from the target description, and a comment in the source states that this code should be generated by TableGen! However, this hasn’t been implemented yet, so we have to create the code manually.
The most important function we have to implement in the M88kRegisterBankInfo class is getInstrMapping(), which returns the mapped register banks for each operand of the instruction. This now becomes easy because we can look up the array of partial mappings, which we can then pass to the getInstructionMapping() method, which constructs the full instruction mapping:
const RegisterBankInfo::InstructionMapping &
M88kRegisterBankInfo::getInstrMapping(
const MachineInstr &MI) const {
const ValueMapping OperandsMapping = nullptr; switch (MI.getOpcode()) { case TargetOpcode::G_AND: case TargetOpcode::G_OR: case TargetOpcode::G_XOR: OperandsMapping = getValueMapping(PMI_GR32); break; default: if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) MI.dump(); endif return getInvalidInstructionMapping(); } return getInstructionMapping(DefaultMappingID, /Cost=*/1,
OperandsMapping,
MI.getNumOperands());
}
During development, it is common to forget the register bank mapping for a generic instruction. Unfortunately, the error message that’s generated at runtime does not mention for which instruction the mapping failed. The easy fix is to dump the instruction before returning the invalid mapping. However, we need to be careful here because the dump() method is not available in all build types.
After mapping the register banks, we must translate the generic machine instructions into real machine instructions.