Architecture

RepliBuild compiles C/C++ source through an LLVM 21+ / MLIR pipeline, introspects DWARF debug metadata emitted by the compiler itself, and generates type-safe Julia bindings with correct struct layout, enum definitions, and calling conventions. Functions requiring non-trivial ABI handling are automatically routed through a custom MLIR dialect and JIT tier.

This page documents the full system architecture. For the public API surface see API Reference, and for per-module internals see Internals.

System overview

+------------------------------------------------------------------------+
|                      User API (3 core functions)                       |
|                                                                        |
|  discover("path/")         build("replibuild.toml")                    |
|  --- scan & configure ---  --- compile & link ---                      |
|                                                                        |
|                            wrap("replibuild.toml")                     |
|                            --- introspect & emit Julia module ---      |
+------------+--------------------------+--------------------------------+
             |                          |
             v                          v
+------------------------+  +--------------------------------------------+
|  Configuration Layer   |  |          Compiler Pipeline                  |
|                        |  |                                            |
|  Discovery.jl          |  |  Compiler.jl -> BuildBridge.jl -> Linker   |
|  ConfigurationManager  |  |  DependencyResolver.jl                     |
|  LLVMEnvironment.jl    |  |  LLVMEnvironment.jl                        |
|  EnvironmentDoctor.jl  |  |                                            |
+------------+-----------+  +---------------------+----------------------+
             |                                    |
             |    replibuild.toml                 |  .so + DWARF + .ll
             v                                    v
+------------------------------------------------------------------------+
|                     Binding Generation                                  |
|                                                                        |
|  DWARFParser.jl --> Wrapper.jl --> Generated Julia Module               |
|       |                |              |                                |
|       |           +----+----+    Tier 1: ccall / llvmcall              |
|       |           | Tier    |    Tier 2: JITManager.invoke()           |
|       |           | Select  |         or AOT thunk ccall               |
|       |           +----+----+                                          |
|       |                |                                               |
|       v                v                                               |
|  JLCSIRGenerator --> MLIRNative --> JITManager                         |
|  (ir_gen/ modules)   (libJLCS.so)  (lock-free cache)                  |
+------------------------------------------------------------------------+

Pipeline stages

The lifecycle of a C/C++ project through RepliBuild proceeds in six stages. Each stage is implemented by one or more Julia modules that can be invoked independently.

Stage 1 — Discovery

Module: src/Discovery.jl

Scans source files, parses the #include graph, resolves external dependencies, and emits a replibuild.toml configuration file. The language (:c or :cpp) is auto-detected from source file extensions and written to wrap.language in the generated config. New projects are automatically registered in the global registry (~/.replibuild/registry/).

Stage 2 — Dependency resolution

Module: src/DependencyResolver.jl

Processes the [dependencies] table in replibuild.toml:

TypeMechanism
gitShallow clone into .replibuild_cache/deps/<name>/; re-fetches when tag changes
localScanned in-place; no copy
systempkg-config --cflags to inject include paths

The exclude list is applied after scanning, allowing you to trim large repositories down to the files you need. Resolved source files merge into the compilation graph before stage 3.

Stage 3 — Compilation

Module: src/Compiler.jl, src/BuildBridge.jl

Each source file is compiled to LLVM IR (.ll text format) via clang (.c) or clang++ (.cpp). Key details:

  • Incremental cache: Per-file mtime tracking in .replibuild_cache/. Only changed files are recompiled.
  • Project content hash: SHA-256 of replibuild.toml + all source + all headers + git HEAD. If the hash matches cached artifacts, build() returns in sub-second time.
  • Parallel dispatch: Enabled by default (compile.parallel = true).
  • Template instantiation: If [types].templates is set, a stub .cpp is auto-generated to force Clang to emit DWARF for the requested template instantiations.
  • Macro shims: If [wrap.macros] is set, typed C/C++ wrapper functions are generated so preprocessor macros appear in the compiled binary's debug metadata.
  • IR sanitization: The compiler strips LLVM 19+ attributes incompatible with Julia's internal LLVM, removes va_start/va_end intrinsics from varargs function bodies (varargs are routed entirely through ccall wrapper generation), and cleans mismatched debug metadata.

Stage 4 — Linking

Module: src/Compiler.jl (link phase), src/BuildBridge.jl

Sanitized per-file IR is merged via llvm-link, optimized by llvm-opt, and linked into the target shared library (.so/.dylib/.dll).

Optional artifacts:

ArtifactConditionPurpose
<name>_lto.bcenable_lto = trueLLVM bitcode for Base.llvmcall embedding (Tier 1). Assembled via Clang_unified_jll to guarantee LLVM version match with Julia.
<name>_thunks.soaot_thunks = truePre-compiled MLIR thunks for Tier 2 dispatch without JIT startup cost.

Stage 5 — Wrapping

Module: src/Wrapper.jl, src/Wrapper/ subpackages, src/DWARFParser.jl, src/ASTWalker.jl

DWARF metadata is extracted from the compiled binary via llvm-dwarfdump. The parser builds structured types (ClassInfo, VtableInfo, MemberInfo, VirtualMethod) that feed into the wrapper generator.

The wrapper generator emits a complete, loadable Julia module containing:

  • Struct definitions with correct field order, alignment padding (_pad_N), and topological sort for circular references
  • @enum definitions with correct underlying types (extracted by the Clang.jl AST walker)
  • Union representations as NTuple{N,UInt8} with typed getter/setter accessors
  • Bitfield accessors with bit-shift extraction
  • Function wrappers dispatched to the appropriate tier (see Three-tier dispatch)
  • Idiomatic mutable struct wrappers with GC-managed finalizers for factory/destructor pairs
  • Global variable accessors via cglobal

Two independent generator tracks exist (src/Wrapper/C/GeneratorC.jl and src/Wrapper/Cpp/GeneratorCpp.jl), selected automatically by wrap.language.

Stage 6 — JIT initialization (on demand)

Modules: src/JLCSIRGenerator.jl, src/MLIRNative.jl, src/JITManager.jl

When the generated Julia module calls a Tier 2 function for the first time, the JIT subsystem:

  1. Generates MLIR IR in the JLCS dialect from cached DWARF metadata
  2. Parses the IR via MLIRNative.parse_module()
  3. Lowers jlcsfuncllvm dialect → native LLVM IR → machine code
  4. Caches the compiled symbol pointer in a lock-free dictionary

Subsequent calls to the same symbol are a single unsynchronized Dict read — no locks, no allocation, no JIT overhead.

If aot_thunks = true, thunks are pre-compiled at build time into _thunks.so and loaded via ccall at module parse time. No JIT initialization occurs at all.

Three-tier dispatch

Every exported function is analyzed by the wrapper generator and routed to one of three calling tiers based on ABI complexity. Tier selection is fully automatic — no user annotation is required.

Tier 1 — ccall and llvmcall (LTO)

For functions with simple, POD-safe signatures. Zero overhead.

Conditions (all must hold):

  • No STL container types in parameters or return
  • Return type is: primitive, pointer, void, or small aligned struct (16 bytes or fewer)
  • All parameters are: primitive, pointer, or small struct — NOT unions, NOT packed structs, NOT non-POD classes

When enable_lto = true, eligible Tier 1 functions are upgraded to Base.llvmcall. The C/C++ LLVM bitcode is embedded as a module-level constant and passed directly to Julia's JIT compiler, enabling cross-language inlining — Julia can inline C++ code into hot loops, apply vectorization, and propagate through AD frameworks like Enzyme.jl.

# Generated wrapper with LTO path
function add(a::Cint, b::Cint)::Cint
    if !isempty(LTO_IR)
        return Base.llvmcall((LTO_IR, "_Z3addii"), Cint, Tuple{Cint, Cint}, a, b)
    else
        return ccall((:_Z3addii, LIBRARY_PATH), Cint, (Cint, Cint), a, b)
    end
end

Additional LTO eligibility constraints (beyond Tier 1):

  • NOT a virtual method
  • Does NOT return a struct by value
  • No Cstring parameters or return (llvmcall does not auto-convert like ccall)

Tier 2 — MLIR JIT or AOT thunks

For functions requiring complex ABI marshalling: packed structs, unions, large struct returns, C++ virtual dispatch.

ModeConfigMechanismStartup costPer-call cost
JITaot_thunks = falseJITManager.invoke() at runtimeFirst-call JIT compilationLock-free after first call
AOTaot_thunks = truePre-compiled _thunks.so + ccallZero (pre-compiled)Same as ccall

The JLCS MLIR dialect models the C ABI contract (struct layout, field offsets, vtable dispatch) as first-class IR operations, which are lowered through MLIR's standard pipeline to native machine code. See MLIR / JLCS Dialect for the full dialect specification.

Tier 3 — ccall fallback

Direct ccall with zero setup. Used when LTO bitcode is unavailable (e.g., enable_lto = false builds or stripped binaries). This is the unconditional fallback that always works.

Tier selection flow

                  +--------------+
                  |  Function    |
                  |  Signature   |
                  +------+-------+
                         |
                  +------v-------+
                  | is_ccall_    |
                  | safe()?      |
                  +--+-------+---+
                  yes|       |no
                     |       |
            +--------v--+  +-v----------------+
            |  Tier 1   |  | aot_thunks?      |
            |  ccall    |  +--+------------+---+
            +-----+-----+  yes|            |no
                  |           |            |
            +-----v--+  +----v-----+ +----v-----------+
            | LTO?   |  | Tier 2   | | Tier 2         |
            +--+--+--+  | ccall    | | JITManager     |
            yes|  |no   | thunks   | | .invoke()      |
               |  |     | .so      | | (runtime JIT)  |
        +------v+ |     +----------+ +----------------+
        |llvm   | |
        |call   | |
        +-------+ |
            +------v--+
            | ccall   |
            | (std)   |
            +---------+

The decision function is_ccall_safe() in src/Wrapper.jl inspects each function's DWARF metadata to determine ABI safety. It checks for STL container types, struct return sizes, packed struct layout mismatches (DWARF size vs Julia aligned size), union parameters, and non-POD class types.

DWARF extraction

Module: src/DWARFParser.jl

DWARF debug metadata is the single source of truth for all type information. Rather than parsing C/C++ headers (which miss ABI details like padding, vtable layout, and actual sizes), RepliBuild compiles with -g and reads the debug metadata that the compiler itself emitted.

Extraction flow

llvm-dwarfdump binary.so
    |
    +-- DW_TAG_class_type / DW_TAG_structure_type
    |      +-- DW_AT_name, DW_AT_byte_size
    |      +-- DW_TAG_member -> MemberInfo (name, type, DW_AT_data_member_location)
    |      +-- DW_TAG_subprogram [virtual] -> VirtualMethod (name, mangled, slot)
    |      +-- DW_TAG_inheritance -> base_classes
    |
    +-- DW_TAG_enumeration_type -> Enum definitions
    +-- DW_TAG_union_type -> Union layout
    +-- DW_TAG_variable -> Global variables
    +-- DW_TAG_typedef -> Type aliases

Data structures

The parser produces structured Julia types that feed into both the wrapper generator and the MLIR IR generator:

TypeFieldsRole
ClassInfoname, vtable_ptr_offset, base_classes, virtual_methods, members, sizeComplete class/struct description
VtableInfoclasses, vtable_addresses, method_addressesAll class metadata for a binary
VirtualMethodname, mangled_name, slot, return_type, parametersSingle virtual method descriptor
MemberInfoname, type_name, offsetStruct field with byte offset

Caching strategy

RepliBuild uses four independent caching layers to minimize rebuild time:

1. Per-file IR cache (mtime-based)

Each source file's compiled LLVM IR is cached in .replibuild_cache/ keyed by filepath. On recompilation, only files with changed mtime are recompiled.

2. Project-level content hash (SHA-256)

A hash of replibuild.toml + all source contents + all header contents + git HEAD. If the hash matches the cached artifacts, build() returns in sub-second time without invoking any compiler.

3. Global registry cache

~/.replibuild/builds/<hash>/ stores full build artifacts. The use() function checks this cache first, enabling instant loads of previously built packages across projects.

4. Toolchain cache

~/.replibuild/toolchain.toml caches the result of LLVM/Clang environment probing with a 24-hour TTL, avoiding repeated filesystem searches for the toolchain.

Generated output layout

<project>/
+-- replibuild.toml                    # Configuration (generated by discover(), hand-editable)
+-- build/                             # LLVM IR files (.ll), intermediate objects
+-- julia/
|   +-- <LibName>.so                   # Compiled shared library
|   +-- <LibName>_lto.bc               # LTO bitcode (if enable_lto = true)
|   +-- <LibName>_thunks.so            # AOT thunks (if aot_thunks = true)
|   +-- compilation_metadata.json      # Symbol + DWARF metadata
|   +-- <ModuleName>.jl                # Generated Julia wrapper module
+-- .replibuild_cache/                 # Incremental compile cache

The generated Julia module contains:

SectionContents
Module constantsLIBRARY_PATH, LTO_IR (embedded bitcode), THUNKS_LTO_IR
Struct definitionsCorrect field order, alignment padding, forward declarations for circular references, base class member flattening
Enum definitions@enum with correct underlying types
Union representationsNTuple{N,UInt8} backing with typed getter/setter accessors
Function wrappersTier 1 (ccall/llvmcall), Tier 2 (JITManager.invoke() or AOT thunk ccall), variadic overloads, global variable accessors
Idiomatic wrappersmutable struct types with GC finalizers, multiple-dispatch method proxies, Base.unsafe_convert for pointer passing
Bitfield accessorsBit-shift extraction functions

Performance characteristics

Per-call overhead vs bare ccall

ScenarioTierMedianvs bare ccall
scalar_addPure Julia30 ns1.0x
scalar_addBare ccall30 ns1.0x (baseline)
scalar_addWrapper ccall40 ns1.33x
scalar_addLTO llvmcall30 ns1.0x
pack_record (packed struct)Bare ccall (unsafe)crashes–-
pack_record (packed struct)Wrapper ccall (DWARF)80 nssafe

Hot loop (1M iterations)

Tierns/iterNote
Pure Julia0.677@inbounds native loop
Bare ccall1.800Hand-written FFI
Wrapper ccall2.026Generated (LTO disabled)
LTO llvmcall0.677Julia JIT inlines C++ IR
Whole loop in C++0.997Single ccall to C++ loop

The LTO path matches pure Julia performance because Julia's LLVM JIT sees the C++ bitcode and optimizes across the FFI boundary — the language boundary ceases to exist at the IR level.

Key design decisions

Source-based, not binary-based. RepliBuild compiles C/C++ source locally rather than wrapping pre-compiled binaries (JLLs / BinaryBuilder). This gives it perfect DWARF metadata, enables LTO across the FFI boundary, and allows binaries tailored to the host machine. The tradeoff is requiring a local LLVM 21+ toolchain.

DWARF as the source of truth. Rather than parsing headers (which miss ABI details like padding, vtable layout, and actual sizes), RepliBuild compiles with -g and reads the debug metadata that the compiler itself emitted. Struct layout, inheritance hierarchies, and virtual dispatch tables are always ABI-correct.

Custom MLIR dialect over ad-hoc codegen. The JLCS dialect provides a principled intermediate representation for C++ interop. Operations like vcall and get_field encode ABI semantics that would be error-prone to emit as raw LLVM IR directly. The MLIR framework handles lowering, optimization, and JIT compilation through its standard pass infrastructure.

Lock-free hot path. The JIT manager uses a double-check caching pattern: after the first call to any JIT function, subsequent calls are a single unsynchronized Dict lookup — no locks, no allocation, no JIT overhead. Julia's Dict is safe for concurrent reads under a single-writer pattern.

Graceful degradation. If libJLCS.so is not built, Tier 1 (ccall) still works for all POD-safe functions. If LTO is disabled, wrappers fall back to standard ccall. If the toolchain is incomplete, check_environment() reports exactly what is missing with OS-specific install instructions.

Module map

For detailed documentation of each internal module, see Internals.

Core API

ModuleSourceResponsibility
RepliBuildsrc/RepliBuild.jlTop-level module. Exports discover, build, wrap, use, check_environment.
ConfigurationManagersrc/ConfigurationManager.jlLoad, validate, merge replibuild.toml into a typed RepliBuildConfig.
LLVMEnvironmentsrc/LLVMEnvironment.jlDetect system LLVM/Clang toolchain; fall back to LLVM_full_jll.
EnvironmentDoctorsrc/EnvironmentDoctor.jlcheck_environment() — validates LLVM 21+, Clang, mlir-tblgen, CMake, libJLCS.so.

Discovery and dependencies

ModuleSourceResponsibility
Discoverysrc/Discovery.jlWalk a project directory, resolve #include graph, emit replibuild.toml.
DependencyResolversrc/DependencyResolver.jlFetch git/local/system deps from [dependencies], filter excludes, inject into compile graph.
PackageRegistrysrc/PackageRegistry.jlGlobal ~/.replibuild/registry/use(), register(), list_registry(), unregister().

Compilation and linking

ModuleSourceResponsibility
Compilersrc/Compiler.jlPer-file C/C++ to LLVM IR compilation. Incremental mtime cache, parallel dispatch, template instantiation, IR sanitization.
BuildBridgesrc/BuildBridge.jlShell out to clang, llvm-link, llvm-opt, nm. Low-level compiler driver.

DWARF extraction

ModuleSourceResponsibility
DWARFParsersrc/DWARFParser.jlParse llvm-dwarfdump output. Extract ClassInfo, VtableInfo, MemberInfo, VirtualMethod. Handles unions, bitfields, globals, typedefs.
ASTWalkersrc/ASTWalker.jlClang.jl-based AST walker for enum extraction.
ClangJLBridgesrc/ClangJLBridge.jlClang.jl integration for header parsing.

Wrapper generation

ModuleSourceResponsibility
Wrappersrc/Wrapper.jlOrchestrates Julia wrapper module generation. Contains is_ccall_safe() tier selection logic.
Wrapper.Generatorsrc/Wrapper/Generator.jlTop-level wrap_library() entry point; dispatches to C or C++ generator.
Wrapper.C.GeneratorCsrc/Wrapper/C/GeneratorC.jlFull C wrapper generator (structs, enums, functions, LTO, thunks).
Wrapper.Cpp.GeneratorCppsrc/Wrapper/Cpp/GeneratorCpp.jlFull C++ wrapper generator (same features + virtual dispatch).
STLWrapperssrc/STLWrappers.jlSTL container type detection and accessor generation.

MLIR and JIT

ModuleSourceResponsibility
JLCSIRGeneratorsrc/JLCSIRGenerator.jlEmit MLIR JLCS dialect IR from VtableInfo. Orchestrates src/ir_gen/ submodules.
ir_gen/TypeUtilssrc/ir_gen/TypeUtils.jlC++ to MLIR type mapping (double to f64, int* to !llvm.ptr, etc.).
ir_gen/StructGensrc/ir_gen/StructGen.jlGenerate jlcs.type_info operations from ClassInfo. Topological sort for inheritance.
ir_gen/FunctionGensrc/ir_gen/FunctionGen.jlGenerate func.func thunks for virtual methods and regular functions.
ir_gen/STLContainerGensrc/ir_gen/STLContainerGen.jlGenerate MLIR thunks for STL container accessors.
MLIRNativesrc/MLIRNative.jlLow-level ccall bindings to libJLCS.so: context management, module parsing, JIT engine, lower_to_llvm, lookup.
JITManagersrc/JITManager.jlSingleton GLOBAL_JIT. Lock-free symbol cache, arity-specialized invoke (0-4 args), @generated ABI dispatch.

Introspection toolkit

ModuleSourceResponsibility
Introspectsrc/Introspect.jlUmbrella module for binary analysis and diagnostics.
Introspect.Binarysrc/Introspect/Binary.jlsymbols(), dwarf_info(), disassemble() — binary artifact analysis.
Introspect.Juliasrc/Introspect/Julia.jlJulia IR introspection (code_lowered, code_typed, code_llvm, code_native).
Introspect.LLVMsrc/Introspect/LLVM.jlLLVM pass tooling, IR optimization, pass comparison.
Introspect.Benchmarkingsrc/Introspect/Benchmarking.jlbenchmark() with configurable samples, suite execution.
Introspect.DataExportsrc/Introspect/DataExport.jlExport results to JSON and CSV.

JLCS MLIR dialect (C++)

FileRole
src/mlir/JLCSDialect.tdDialect registration and namespace definition
src/mlir/JLCSOps.tdOperation definitions: type_info, get_field, set_field, vcall, load_array_element, store_array_element, ffe_call
src/mlir/Types.tdType definitions: !jlcs.c_struct<>, !jlcs.array_view<>
src/mlir/JLInterfaces.tdInterface definitions
src/mlir/CMakeLists.txtBuild config: TableGen processing, whole-archive JIT linking
src/mlir/build.shBuild script. Produces src/mlir/build/libJLCS.so
src/mlir/impl/C++ implementation files for dialect operations and lowering passes