How to Write an LLVM Backend for a Custom Target Using TableGen
You write an LLVM backend by creating TableGen description files (*.td) that define registers, instructions, and sub-targets, then implementing a C++ TableGen backend that processes these records via the RecordKeeper API to generate C++ source files for the compiler.
The llvm/llvm-project repository separates architectural declarations from code generation logic through TableGen. When you write an LLVM backend for a custom target using TableGen, you declare the target’s properties in domain-specific .td files and provide a C++ emitter that walks the parsed records to produce the actual compiler implementation.
Architecture Overview
An LLVM backend consists of three distinct layers. Understanding this separation is critical before writing any code.
Target Description Layer
This layer uses TableGen syntax to declare everything the compiler needs to know about the hardware. You create .td files that define register classes, instruction encodings, assembly syntax, and sub-target features. The parser turns these into a RecordKeeper object containing every class and definition.
TableGen Backend Layer
This layer is a C++ program that links against LLVM’s TableGen libraries. It receives the RecordKeeper from llvm-tblgen, iterates over the records, and emits C++ source files (such as MyTargetAsmPrinter.cpp or MyTargetInstrInfo.inc). The skeleton implementation in llvm/lib/TableGen/TableGenBackendSkeleton.cpp demonstrates the required boilerplate.
Integration Layer
This layer connects generated and hand-written code. You modify CMakeLists.txt to run the TableGen backend during the build, create a MyTarget.cpp file that registers the target with LLVM’s Target class, and provide any hand-written glue logic that the generator cannot produce.
Step 1 – Create the TableGen Target Description
Start by including the generic Target.td file and defining a Target class that points to your instruction set and assembly handlers.
include "llvm/Target/Target.td"
def MyTarget : Target {
let InstructionSet = MyTargetInstrInfo;
let AssemblyWriters = [MyTargetAsmWriter];
let AssemblyParsers = [MyTargetAsmParser];
}
Real-world targets such as MIPS follow this pattern in llvm/lib/Target/Mips/Mips.td, which demonstrates how to structure predicates and sub-target features.
Defining Registers
Create a separate MyTargetRegisterInfo.td file to declare the register file. Each register needs a namespace and size.
def R0 : Register { let Namespace = "MyTarget"; let Size = 32; }
def R1 : Register { let Namespace = "MyTarget"; let Size = 32; }
Defining Instructions
In MyTargetInstrInfo.td, create a base class for your instructions that inherits from Instruction, then define concrete instructions with operands and assembly strings.
class MyInst<string Inst, list<dag> Operands> : Instruction {
let Inst = Inst;
let Operands = Operands;
let AsmString = Inst;
}
def ADD : MyInst<"add"> {
let Operands = (outs GPR:$dst, in GPR:$src1, in GPR:$src2);
}
Step 2 – Implement the TableGen Backend in C++
The backend is a standard C++ file that uses the TableGen API declared in llvm/TableGen/TableGenBackend.h. According to the source code in llvm/lib/TableGen/TableGenBackendSkeleton.cpp, you must implement an emitter class with a run method and register it so llvm-tblgen can invoke it.
The Emitter Class Structure
Create MyTargetBackend.cpp with the following structure:
#include "llvm/ADT/StringRef.h"
#include "llvm/TableGen/TableGenBackend.h"
using namespace llvm;
namespace {
class MyTargetEmitter {
const RecordKeeper &Records;
public:
MyTargetEmitter(const RecordKeeper &RK) : Records(RK) {}
void run(raw_ostream &OS) {
emitSourceFileHeader("MyTarget generated code", OS);
// Iterate over all classes that inherit from "Instr"
for (auto &ClassPair : Records.getClasses()) {
Record *Rec = ClassPair.second.get();
if (!Rec->isSubClassOf("Instr")) continue;
std::string InstName = Rec->getNameInitAsString();
std::string AsmStr = Rec->getValueAsString("AsmString", "");
// Emit a C++ function for each instruction
OS << "void emit_" << InstName << "(llvm::MCInst &MI) {\n";
OS << " // TODO: translate MCInst to MyTarget machine code\n";
OS << "}\n\n";
}
}
};
} // anonymous namespace
Registering the Backend
Expose your emitter to the TableGen driver using TableGen::Emitter::OptClass. The string passed to the constructor becomes the command-line option.
static TableGen::Emitter::OptClass<MyTargetEmitter>
X("gen-my-target", "Generate MyTarget backend code");
After compiling your backend into llvm-tblgen, generate code by running:
llvm-tblgen -gen-my-target -I path/to/td/files MyTarget.td -o MyTargetGenerated.inc
Step 3 – Integrate with the LLVM Build System
You must wire the generated .inc files into the target’s build rules and provide hand-written registration code.
CMake Integration
Edit llvm/lib/Target/MyTarget/CMakeLists.txt to add a custom command that runs your backend before compilation.
add_llvm_target(MyTarget
MyTarget.cpp
MyTargetAsmPrinter.cpp
MyTargetInstrInfo.cpp
${CMAKE_CURRENT_BINARY_DIR}/MyTargetGenerated.inc
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/MyTargetGenerated.inc
COMMAND llvm-tblgen -gen-my-target -I ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/MyTarget.td
-o ${CMAKE_CURRENT_BINARY_DIR}/MyTargetGenerated.inc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/MyTarget.td
${CMAKE_CURRENT_SOURCE_DIR}/MyTargetInstrInfo.td
COMMENT "Generating MyTarget TableGen backend"
)
Hand-Written Glue Code
Create MyTarget.cpp to implement LLVMInitializeMyTarget(). This function creates the Target object and links together the generated instruction info, register info, and assembly printer. The resulting binary behaves like any built-in LLVM target and can be tested with llc or clang.
Summary
- TableGen descriptions (
*.tdfiles) in directories likellvm/lib/Target/MyTarget/declaratively specify registers, instructions, and sub-target features. - C++ TableGen backends process these descriptions via the
RecordKeeperAPI, iterating withgetClasses()andgetDefs()to emit source code. - The
TableGenBackendSkeleton.cppfile inllvm/lib/TableGen/provides the canonical template for new emitters. - Registration uses
TableGen::Emitter::OptClassto expose a command-line option such as-gen-my-target. - CMake integration requires a custom command that runs
llvm-tblgenand depends on the.tdsource files to ensure incremental builds work correctly. - Hand-written glue code registers the target with LLVM’s
Targetclass and connects generated components.
Frequently Asked Questions
What is the difference between a TableGen description and a TableGen backend?
A TableGen description is a declarative .td file that defines what the target looks like (registers, instructions, encodings). A TableGen backend is a C++ program that reads those definitions and writes the actual C++ implementation files that LLVM compiles. The backend transforms records into executable code.
How do I debug my TableGen backend when it fails to generate code?
Use PrintError and PrintFatalError from the TableGen API to emit source-location-aware diagnostics when records are malformed. You can also print the contents of the RecordKeeper by iterating over Records.getDefs() and dumping field values to verify that the parser correctly read your .td files.
Can I use existing LLVM targets like Mips as a template for my custom target?
Yes. The llvm/lib/Target/Mips/Mips.td file demonstrates real-world usage of sub-target features, instruction predicates, and complex operand types. The TableGenBackendSkeleton.cpp file provides the C++ boilerplate. Copying the structure of an existing target and replacing the architectural details is the standard workflow.
What files must I hand-write versus auto-generate when creating an LLVM backend?
You must hand-write the TableGen description files (.td), the C++ backend emitter, the target registration glue (MyTarget.cpp), and the CMake build rules. You auto-generate the instruction info tables, register encodings, and assembly printer logic by running your backend through llvm-tblgen. The generated files typically have .inc extensions and are treated as normal source files during compilation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →