Multi-classes
This is where we depart from the OOP-ness of TableGen classes. Whereas a class allows you to generate a def, a multi-class allows you to generate multiple defs. That’s pretty much it. Why do we need these? I’ve only ever seen them being useful for low-level code where you want to generate defs for different architectures. Let’s see some simplified examples:
// For now we use an empty instruction.
class Instruction{}
// Multiclass will take a single instruction
// and then create multiple records out of it.
multiclass InstructionM {
// We want to generate two different variations of each instruction.
def _amd : Instruction{}
def _intel : Instruction{}
}
// You can "invoke" a multiclass using defm
defm ADD : InstructionM;
// If you see the output, you will see some defm weirdness.
// So let's go through the generation process step by step.
//
// defm ADD : InstructionM invokes the multiclass.
//
// TableGen then generates new records as expected.
// But the names of the records are prepended with the variable name.
// So instead of generating records _amd, and _intel,
// it will generate
// ADD_amd, and
// ADD_intel
defm MUL : InstructionM;
// Similarly the above will generate
// MUL_amd, and
// MUL_intel
which outputs:
------------- Classes -----------------
class Instruction {
}
------------- Defs -----------------
def ADD_amd { // Instruction
}
def ADD_intel { // Instruction
}
def MUL_amd { // Instruction
}
def MUL_intel { // Instruction
}
Here’s a more involved example.
// Define a class for instructions with a 4-bit opcode
// and an assembly name.
class InstructionWithOpcode {
bits<4> opcode;
string asm;
}
// Define a multiclass that generates two variations
// of each instruction with the specified opcode and assembly name.
multiclass InstructionWithOpcodeM<bits<4> OpcodeA, bits<4> OpcodeB, string Asm> {
def _amd : InstructionWithOpcode {
bits<4> opcode = OpcodeA;
string asm = Asm;
}
def _intel : InstructionWithOpcode {
bits<4> opcode = OpcodeB;
string asm = Asm;
}
}
// Invoke the multiclass to generate the records with
// different opcodes and assembly names.
defm ADD : InstructionWithOpcodeM<0b0000, 0b0001, "add">;
// This will generate records:
// ADD_amd with opcode 0b0000 and asm "add"
// ADD_intel with opcode 0b0001 and asm "add"
defm SUB : InstructionWithOpcodeM<0b0010, 0b0011, "sub">;
// This will generate records:
// SUB_amd with opcode 0b0010 and asm "sub"
// SUB_intel with opcode 0b0011 and asm "sub"
And here's the output:
------------- Classes -----------------
class InstructionWithOpcode {
bits<4> opcode = { ?, ?, ?, ? };
string asm = ?;
}
------------- Defs -----------------
def ADD_amd { // InstructionWithOpcode
bits<4> opcode = { 0, 0, 0, 0 };
string asm = "add";
}
def ADD_intel { // InstructionWithOpcode
bits<4> opcode = { 0, 0, 0, 1 };
string asm = "add";
}
def SUB_amd { // InstructionWithOpcode
bits<4> opcode = { 0, 0, 1, 0 };
string asm = "sub";
}
def SUB_intel { // InstructionWithOpcode
bits<4> opcode = { 0, 0, 1, 1 };
string asm = "sub";
}