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 (*.td files) in directories like llvm/lib/Target/MyTarget/ declaratively specify registers, instructions, and sub-target features.
  • C++ TableGen backends process these descriptions via the RecordKeeper API, iterating with getClasses() and getDefs() to emit source code.
  • The TableGenBackendSkeleton.cpp file in llvm/lib/TableGen/ provides the canonical template for new emitters.
  • Registration uses TableGen::Emitter::OptClass to expose a command-line option such as -gen-my-target.
  • CMake integration requires a custom command that runs llvm-tblgen and depends on the .td source files to ensure incremental builds work correctly.
  • Hand-written glue code registers the target with LLVM’s Target class 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →