Skip to content

Experimental TPDE backend

Spice ships with the LLVM code generator as its default and stable compilation backend. Alongside it, an experimental alternative backend is available: TPDE, a fast compiler back-end framework that consumes LLVM IR directly and emits ELF objects at roughly -O0 code quality but 10-20× faster than clang -O0 — useful for tight edit/compile iteration loops.

This backend is not a drop-in replacement for LLVM CodeGen. It performs no optimization and supports only a narrow slice of the targets LLVM does. It is opt-in at runtime. The Linux release packages of Spice come with it; for other builds, it is opt-in at build time as well.

Experimental

The TPDE backend is under active development and disabled by default. Expect rough edges, unsupported language features, and possible codegen bugs. Do not use for production builds. Bug reports welcome.

Supported configurations

Property Value
Host operating systems Linux (ELF only)
Target architectures x86_64, aarch64
Code model small only (--code-model default or small)
Relocation model PIC only (--relocation-model pic or pie)
Optimization none — -O flags are accepted but ignored
Link-time optimization not supported (-lto is rejected)
No red zone (x86_64) not supported (--no-red-zone is rejected)
CPU features not supported (--cpu-features is rejected)

Building Spice with TPDE support

The Linux release packages of Spice (x86_64 and aarch64) are built with TPDE support, so this is only needed when building Spice from source.

TPDE is pulled in as a git submodule at deps/tpde/. Build its libraries into the std, then build Spice:

Bash
python setup-deps.py --tpde
python build.py

The compiler emits objects via the std TPDE bindings (see Using TPDE from Spice code), so it is only backed by TPDE if the TPDE libraries were available while building it.

Requirements:

  • The same LLVM version Spice is built against must be one of TPDE's supported versions. Spice's regular LLVM build already satisfies this on modern systems.
  • A matching clang binary must be discoverable; TPDE uses it to compile its encoding templates. dev-setup.py installs a suitable one alongside LLVM.

Selecting the backend

Once built with TPDE support, choose the backend per compilation with --backend:

Bash
1
2
3
4
5
6
7
# Default — LLVM CodeGen
spice build main.spice
spice build --backend=llvm main.spice

# Experimental TPDE backend
spice build --backend=tpde main.spice
spice run   --backend=tpde main.spice

If the compiler was built without the TPDE libraries, passing --backend=tpde produces a CLI error.

Limitations you may hit

  • --dump-assembly and --dump-object-file are not meaningful under TPDE; they return placeholder text.
  • Any Spice code that requires wide vectors, exception handling, or non-small code model will fail to compile.
  • Debug info emission (-g) works but produces -O0-quality info regardless of the --build-mode.
  • TPDE does not support inline assembly, so the asm builtin and the syscall builtin are rejected with a compile error.

Using TPDE from Spice code

Besides being a backend of the Spice compiler, TPDE can also be used from Spice programs, which build LLVM modules with the LLVM bindings (std/bindings/llvm). The TPDE bindings (std/bindings/tpde) compile such a module to an ELF object, in a file or in memory:

Spice
import "std/bindings/llvm/llvm" as llvm;
import "std/bindings/tpde/tpde" as tpde;

f<int> main() {
    llvm::LLVMContext context;
    llvm::Module module = llvm::Module("example", context);
    // ... build the module with the LLVM bindings ...

    tpde::Compiler compiler = tpde::Compiler(llvm::getDefaultTargetTriple());
    if !compiler.isValid() { return 1; } // TPDE does not support the target or is not available
    String errorMessage;
    if compiler.compileToFile(module, "example.o", errorMessage) {
        printf("%s\n", errorMessage.getRaw());
        return 1;
    }
}

TPDE only offers a C++ API, so the bindings compile a small C API wrapper (std/bindings/tpde/tpde-wrapper.cpp) along with the program. They need the TPDE libraries and headers, in addition to what the LLVM bindings need.

The Linux release packages ship them inside the std, at std/bindings/tpde/lib and std/bindings/tpde/include, and the compiler points the bindings to them on its own. The shipped libraries are built against the LLVM version of the release (see LLVM_VERSION in the release workflow), so the LLVM the program links against has to be the same version.

When building Spice from source on Linux, build TPDE with python setup-deps.py --tpde. It builds the deps/tpde submodule with its own CMake project, against the LLVM from LLVM_DIR (<llvm>/lib/cmake/llvm) or llvm-config, and installs the libraries, headers and licenses into std/bindings/tpde, like the release packages ship them. The compiler then finds them on its own. It needs a clang matching the LLVM version, to compile TPDE's encoding templates.

The TPDE_FLAGS environment variable takes precedence over the libraries in the std. It holds the include flag for the TPDE headers and the paths of the TPDE static libraries, space-separated:

Bash
1
2
3
4
TPDE_FLAGS="-I<spice-src>/std/bindings/tpde/include -Wl,--start-group \
  <spice-src>/std/bindings/tpde/lib/libtpde_llvm.a <spice-src>/std/bindings/tpde/lib/libtpde.a \
  <spice-src>/std/bindings/tpde/lib/libfadec.a <spice-src>/std/bindings/tpde/lib/libdisarm64.a \
  <spice-src>/std/bindings/tpde/lib/libspdlog.a -Wl,--end-group"

Without TPDE libraries, or on platforms other than Linux, the bindings still compile and link, but tpde::isAvailable() returns false and every compilation fails with an error message.