Tech News
← Back to articles

Using the TPDE Codegen Back End in LLVM Orc

read original related products more articles

TPDE is a single-pass compiler backend for LLVM that was open-sourced earlier this year by researchers at TUM. The comprehensive documentation walks you through integrating TPDE into custom builds of Clang and Flang. Currently, it supports LLVM 19 and LLVM 20 release versions.

Integration in LLVM ORC JIT

TPDE’s primary strength lies in delivering low-latency code generation while maintaining reasonable -O0 code quality — making it an ideal choice for a baseline JIT compiler. LLVM’s On-Request Compilation (ORC) framework provides a set of libraries for building JIT compilers for LLVM IR. While ORC uses LLVM’s built-in backends by default, its flexible architecture makes it straightforward to swap in TPDE instead!

Let’s say we use the LLJITBuilder interface to instantiate an off-the-shelf JIT:

ExitOnError ExitOnErr ; auto Builder = LLJITBuilder (); std :: unique_ptr < LLJIT > JIT = ExitOnErr ( Builder . create ());

The builder offers several extension points to customize the JIT instance it creates. To integrate TPDE, we’ll override the CreateCompileFunction member, which defines how LLVM IR gets compiled into machine code:

Builder . CreateCompileFunction = []( JITTargetMachineBuilder JTMB ) -> Expected < std :: unique_ptr < IRCompileLayer :: IRCompiler >> { return std :: make_unique < TPDECompiler > ( JTMB ); };

To use TPDE in this context, we need to wrap it in a class that’s compatible with ORC’s interface:

class TPDECompiler : public IRCompileLayer :: IRCompiler { public: TPDECompiler ( JITTargetMachineBuilder JTMB ) : IRCompiler ( irManglingOptionsFromTargetOptions ( JTMB . getOptions ())) { Compiler = tpde_llvm :: LLVMCompiler :: create ( JTMB . getTargetTriple ()); assert ( Compiler != nullptr && "Unknown architecture" ); } Expected < std :: unique_ptr < MemoryBuffer >> operator ()( Module & M ) override ; private: std :: unique_ptr < tpde_llvm :: LLVMCompiler > Compiler ; std :: vector < std :: unique_ptr < std :: vector < uint8_t >>> Buffers ; };

In the constructor, we initialize TPDE with a target triple (like x86_64-pc-linux-gnu ). TPDE currently works on ELF-based systems and supports both 64-bit Intel and ARM architectures ( x86_64 and aarch64 ). For now let’s assume that’s all we need. Now let’s implement the actual wrapper code:

... continue reading