How to Use LLVM TableGen for Defining Instruction Sets: A Complete Guide

LLVM TableGen is a domain-specific language that lets you define instruction sets in .td files and automatically generate C++ backend code via the llvm-tblgen tool.

LLVM TableGen is the code-generation engine at the heart of the llvm/llvm-project repository, enabling compiler developers to describe target architectures declaratively. Instead of manually writing repetitive C++ boilerplate for instruction opcodes, operand types, and register classes, you define records in TableGen files that the build system transforms into optimized header files. This approach powers production backends like X86 and RISC-V, making it essential for anyone building a new LLVM target.

Understanding the LLVM TableGen Architecture

TableGen operates as a two-stage pipeline: a frontend parser that reads .td files into an in-memory record database, and a backend code generator that emits C++ source. The frontend implementation lives in llvm/utils/TableGen/, while the backend API is exposed through llvm/include/llvm/TableGen/TableGenBackend.h. Common backends such as -gen-instr-info and -gen-asm-writer reside in llvm/lib/TableGen/, each translating specific record types into compiler infrastructure.

Defining Instruction Sets in TableGen Files

TableGen uses a class-based inheritance system where abstract class definitions capture common properties and concrete def records instantiate specific instructions.

Declaring Register Classes

Start by including the core target definitions and defining your register classes. In llvm/lib/Target/Target.td, the base RegisterClass template establishes the contract for register sets.

//===- MyTargetInstrInfo.td - MyTarget instruction definitions -----*- tablegen -*-===//

include "llvm/Target/Target.td"
include "llvm/Target/InstrInfo.td"
include "llvm/Target/RegInfo.td"

// Define a simple register class
def GPR : RegisterClass<"MyTarget", [i32], 32, (add GPR0, GPR1, GPR2, GPR3)>;

The def GPR statement creates a register class named GPR containing four 32-bit general-purpose registers. The include statements pull in generic target definitions from the LLVM core that define the base Instruction and RegisterClass templates.

Abstracting Common Instruction Behavior

Create a base class to capture shared properties across your instruction set. This pattern appears throughout production code, such as in llvm/lib/Target/X86/X86InstrInfo.td.

// Abstract instruction class – all instructions inherit from this
class InstBase<string InstrName, list<Operand> Ops> : Instruction {
  let Name = InstrName;
  let OperandList = Ops;
}

The InstBase class inherits from the core Instruction class defined in InstrInfo.td and parameterizes the instruction name and operand list. The let statements override default field values in the parent class.

Instantiating Concrete Instructions

Concrete instructions use def to inherit from your abstract class and provide specific opcodes and operand layouts.

// Concrete instruction definitions
def ADD : InstBase<"ADD", (outs GPR:$dst, ins GPR:$src1, ins GPR:$src2)> {
  let InstCode = 0x01;
}

def SUB : InstBase<"SUB", (outs GPR:$dst, ins GPR:$src1, ins GPR:$src2)> {
  let InstCode = 0x02;
}

Here, def ADD and def SUB instantiate records that TableGen will emit into the generated code. The (outs ...) and (ins ...) syntax defines the operand constraints, while InstCode assigns the binary opcode encoding.

Generating C++ Headers with llvm-tblgen

Once your .td files are complete, invoke the appropriate backend to generate include files. The llvm-tblgen binary processes your definitions and emits tables, enumerations, and helper functions.

llvm-tblgen -gen-instr-info -o MyTargetGenInstrInfo.inc MyTargetInstrInfo.td

The -gen-instr-info flag selects the instruction information backend. The generated MyTargetGenInstrInfo.inc contains an enumeration mapping opcodes to names (enum MyTargetInstOpcode { ADD = 0, SUB = 1, ... }), operand descriptor tables, and register class mappings that the backend consumes directly. Other common flags include -gen-asm-writer for assembly printers and -gen-subtarget-info for processor feature flags.

Consuming Generated Code in Your Backend

Include the generated file in your target's implementation to access auto-generated definitions. This eliminates manual synchronization between your instruction definitions and C++ logic.

#include "MyTargetGenInstrInfo.inc"

bool MyTargetInstrInfo::isBranch(MachineInstr &MI) const {
  switch (MI.getOpcode()) {
  case MyTarget::BR:
  case MyTarget::BRCOND:
    return true;
  default:
    return false;
  }
}

The #include statement inserts the TableGen-generated enumerations and tables directly into your compilation unit. The MyTarget:: namespace contains the opcode constants generated from your def statements, ensuring type-safe access to instruction identifiers.

Automating the Build with CMake

Modern LLVM development integrates TableGen through CMake macros defined in llvm/cmake/modules/TableGen.cmake. The add_tablegen_target function creates custom commands that invoke llvm-tblgen with the correct generator flags and dependency tracking.

add_tablegen_target(MyTargetTableGen
  SOURCE MyTargetInstrInfo.td
  TARGETS InstrInfo
)
add_dependencies(MyTargetBackend MyTargetTableGen)

This CMake configuration ensures that modifying MyTargetInstrInfo.td automatically triggers regeneration of MyTargetGenInstrInfo.inc before compiling the backend source. The TableGen.cmake module handles include paths, generator selection, and dependency management across the build tree.

Summary

  • LLVM TableGen uses .td files to declaratively define instruction sets, registers, and target metadata without manual C++ coding.
  • The llvm-tblgen tool processes these files through backends like -gen-instr-info to produce optimized C++ headers containing enumerations and lookup tables.
  • Generated files reside in llvm/lib/TableGen/ and provide the instruction descriptors, assembly strings, and encoding information consumed by backend code in llvm/lib/Target/.
  • Production targets like X86 (X86InstrInfo.td) and RISC-V (RISCVInstrInfo.td) demonstrate scalable patterns for complex instruction set architectures.
  • CMake integration via TableGen.cmake automates the generation pipeline during the build process, ensuring headers stay synchronized with TableGen definitions.

Frequently Asked Questions

What is the difference between a class and a def in TableGen?

A class in TableGen defines an abstract record template with parameters and default values that cannot be instantiated directly in generated code. A def creates a concrete, fully instantiated record that appears in the output. Only def records are emitted into the C++ headers; class definitions serve as reusable blueprints. This distinction is handled by the record database implementation in llvm/include/llvm/TableGen/TableGenBackend.h.

How do I add a new instruction to an existing LLVM target?

Locate the target's instruction definition file, such as llvm/lib/Target/X86/X86InstrInfo.td for X86 or llvm/lib/Target/RISCV/RISCVInstrInfo.td for RISC-V. Create a new def that inherits from the target's base instruction class (often Instruction or a target-specific subclass), specifying the opcode encoding, operand list with register classes, and assembly format string. Rebuild the target to regenerate the instruction info tables via the -gen-instr-info backend.

Where is the llvm-tblgen source code located?

The frontend parser and main driver for llvm-tblgen reside in llvm/utils/TableGen/. The backend implementation classes and generator logic are located in llvm/lib/TableGen/. Header files defining the C++ API for writing custom backends, such as Record and CGRecord, are found in llvm/include/llvm/TableGen/TableGenBackend.h.

Can I use TableGen without building all of LLVM?

Yes. While TableGen is part of the llvm/llvm-project monorepo, you can build only the llvm-tblgen target by running cmake --build . --target llvm-tblgen after configuring the LLVM build with CMake. This produces the standalone tool needed to process .td files for external projects, independent of the full compiler infrastructure.

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 →