# How to Write an LLVM Backend for a Custom Target Using TableGen

> Learn to write an LLVM backend for a custom target with TableGen. Define registers and instructions in TD files, then use C++ to generate compiler source code.

- Repository: [LLVM/llvm-project](https://github.com/llvm/llvm-project)
- Tags: how-to-guide
- Published: 2026-09-11

---

**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`](https://github.com/llvm/llvm-project/blob/main/MyTargetAsmPrinter.cpp) or `MyTargetInstrInfo.inc`). The skeleton implementation in [`llvm/lib/TableGen/TableGenBackendSkeleton.cpp`](https://github.com/llvm/llvm-project/blob/main/llvm/lib/TableGen/TableGenBackendSkeleton.cpp) demonstrates the required boilerplate.

### Integration Layer

This layer connects generated and hand-written code. You modify [`CMakeLists.txt`](https://github.com/llvm/llvm-project/blob/main/CMakeLists.txt) to run the TableGen backend during the build, create a [`MyTarget.cpp`](https://github.com/llvm/llvm-project/blob/main/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.

```tablegen
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.

```tablegen
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.

```tablegen
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`](https://github.com/llvm/llvm-project/blob/main/llvm/TableGen/TableGenBackend.h). According to the source code in [`llvm/lib/TableGen/TableGenBackendSkeleton.cpp`](https://github.com/llvm/llvm-project/blob/main/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`](https://github.com/llvm/llvm-project/blob/main/MyTargetBackend.cpp) with the following structure:

```cpp
#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.

```cpp
static TableGen::Emitter::OptClass<MyTargetEmitter>
    X("gen-my-target", "Generate MyTarget backend code");

```

After compiling your backend into `llvm-tblgen`, generate code by running:

```bash
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`](https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/MyTarget/CMakeLists.txt) to add a custom command that runs your backend before compilation.

```cmake
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`](https://github.com/llvm/llvm-project/blob/main/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`](https://github.com/llvm/llvm-project/blob/main/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`](https://github.com/llvm/llvm-project/blob/main/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`](https://github.com/llvm/llvm-project/blob/main/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.