# \[ANN\] RepliBuild.jl - A full C/C++ interop toolkit for tiered FFI generation. REPL - I - BUILD

**URL:** <https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122>\
**Category:** Package Announcements\
**Created:** [March 10, 2026, 5:42pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122 "2026-03-10T17:42:57Z")\
**Posts on this page:** 10\
**Page:** 2

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [September 20, 2026, 4:51pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/23 "2026-09-20T16:51:17Z")

</div>

![2026-09-20-114513_hyprshot](https://global.discourse-cdn.com/julialang/original/3X/3/f/3f7b7831871c4ebf3e7e9403014ec7b405a06066.png)

MLIR thunk magic, 0 GC, 0 latency, 0 allocations

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [September 20, 2026, 7:13pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/24 "2026-09-20T19:13:46Z")

</div>

![2026-09-20-141154_hyprshot](https://global.discourse-cdn.com/julialang/original/3X/4/f/4ffa3e91568e7ad154edcd72c403ee49ff8847d9.png)  
 ![2026-09-20-141232_hyprshot](https://global.discourse-cdn.com/julialang/original/3X/0/f/0f483e5720fb45c1ff2b04aed79ee0dc56e1a3dd.png)

So the honest claim is: you can drive AOT-compiled GPU kernels from Julia with AOT-compiled host marshalling, at zero allocation, through a C++ API, with no C shim and no C++ host program.

Only in Julia

> <https://github.com/obsidianjulua/RepliBuild-Hub/blob/main/examples%2FImguiDemo%2FREADME.md>

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 17, 2026, 1:54am UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/25 "2026-10-17T01:54:22Z")

</div>

clang 23 keeps inline members at default visibility even with exclude\_from\_explicit\_instantiation

sorry, this isnt a julia or repo issue namespace.h in LLVM23 applies hidden visibility by default to all std functions via a clang pragma, with no MinGW opt-out.

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 17, 2026, 2:25pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/26 "2026-10-17T14:25:26Z")

</div>

Fixed in v4.2.0

- Windows with libc++ 23: STL members reach the DLL export table again, so STL thunks bind.
- Windows: a void C++ function that throws through an AOT thunk raises CxxException instead of ending the process.
- discover works on a machine without a system LLVM.
- Windows: libc\_free frees through the library’s own C runtime, link\_dirs DLLs are preloaded, vtable thunks bind only exported methods, and slice file names survive case folding.
- Pkg.build on Windows no longer says the dialect is Linux-only.
- A leaked file handle that kept IR files locked on Windows.

Upgrading

- A 4.1.1 wrapper keeps working. Build-time fixes need a rebuild (delete .replibuild\_cache/project\_hash); generator fixes need a re-wrap.

Verified on Windows (MSYS2 CLANG64, LLVM/MLIR 23.1.3) with the full test suite. CI runs the toolchain-free suite on Linux and Windows. Tier 2 was not re-run on Linux for this release.

---

<div class="post-metadata">

**Author:** ![cshen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshen/32/217287_2.png) [@cshen](https://discourse.julialang.org/u/cshen)\
**Post date:** [October 17, 2026, 4:20pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/27 "2026-10-17T16:20:11Z")

</div>

Hi, been following from the sidelines for a while.  
Very interesting project. I have a quick question that I couldn’t find explicitly in the docs (maybe I’m just a bad reader)

Does it also work when you only have an header, like Clang.jl? Or do you always require the full source to compile?

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 17, 2026, 5:00pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/28 "2026-10-17T17:00:48Z")

</div>

I use header parsing but Dwarf is the architecture and if debug is in the binary and is c, the pkg can ingest that binary and create ccall bindings that way but thats if the binary has dwarf then no source or compiling is needed just a internalized julia dwarf method into ccall bindings. Thats my equivalent to Clang.jl header parsing. Its called ingest in the docs. Different architecture completely doing mostly the same things in different ways.

You get more out of it when you parse headers, use dwarf, and MLIR for marshalling

but no it doesnt just wrap headers, thats like a few other pkgs that do that really well if a basic binding is all thats required. Anything stl, inheritance, and virtual methods is what im working on.

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 18, 2026, 11:05pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/29 "2026-10-18T23:05:13Z")

</div>

My thinking is if you can parse the headers than RepliBuild can most likely compile them, along with shims. So you can add -g and automate type selection by a compiler. So the workflow is “parse headers → emit minimal shims/instantiation units → compile with debug → extract DWARF → generate bindings” covers almost every library for which header-based tools already work.

but also covers the cxx methods other Pkgs cant

next step to header parsing I guess… it is the step that turns “we can see the declarations” into “we have the ABI the compiler actually used.”

I parse enums from headers basically

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 24, 2026, 11:09pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/30 "2026-10-24T23:09:05Z")

</div>

RepliBuild’s header path

## What the header path gathers today

Only one source-text fact reaches the JLCS lowering: the `noexcept` candidate set, and even that is confirmed by `nounwind` in the linked IR before it routes anything. Everything the thunk generator lays out comes from DWARF, the linked IR, or `nm`. The AST pass proper is one libclang walk for enums, run at wrap time, and it feeds the Julia generators, not the MLIR.

The text pass has no edge into the thunk builder: its enums and typedef signatures stop at the Julia generators, and `is_noexcept` reaches `FunctionGen` only after the linked IR’s `nounwind` has confirmed the regex.

`ASTWalker.jl` never opens an AST. It runs `clang -E -H` for the include tree and regexes for namespaces, classes and function names, and its only product is `dependency_graph.json` and a compilation order for Discovery. The real AST pass is `ClangJLBridge.extract_header_types`. The same file also carries a full Clang.jl Generators path (`generate_bindings_clangjl`, wrapped by `Generator.wrap_with_clang`), and nothing calls it: `wrap_library` goes straight to `wrap_introspective`, and `[wrap] style = "clang"` is validated but never dispatched on. (uneeded after dwarf parser provided the info)

| Fact | How it is read (where) | Lands in | Consumer | Reaches JLCS IR? |
| --- | --- | --- | --- | --- |
| Enum names, members, values | libclang via Clang.jl: `CXCursor_EnumDecl` / `EnumConstantDecl`, descending namespaces and linkage specs, in a child Julia process with `-xc++ -std=c++17` (`ClangJLBridge._extract_enums_via_ast!`, wrap time) | `metadata["header_enums"]` (`Generator.jl`) | GeneratorC and GeneratorCpp emit a supplementary `@enum` (or a bitflag const block) for each enum DWARF lacks; the underlying type is guessed from the value range (`Cint` / `UInt32` / `Int64`) | No |
| Function-pointer typedef signatures | Regex `typedef R (*Name)(params);` over header text (`extract_header_types`) | `metadata["function_pointer_typedefs"]` | `FunctionPointers._callback_typedef_signature`: the `@cfunction` example in a callback’s docstring; DWARF’s `function_pointer_signature` is the fallback | No (docs only) |
| Struct names | Regex `struct X {` | `header_types["structs"]` | Nothing reads it | No |
| `constants`, `typedefs` | Declared in the result dict, never filled | — | — | No |
| `noexcept` candidates | Regex `name(...) noexcept` over the sources and the top level of every include dir (`_scan_noexcept_functions`, build time) | `func["is_noexcept"]`, set only when the mangled definition is also `nounwind` in the linked IR (`_mark_noexcept!`) | `DispatchLogic.is_ccall_safe` tier routing; `FunctionGen` picks `jlcs.try_call` vs `jlcs.ffe_call` and sets `may_throw` on `vcall` and `dtor_call` | Yes, the one that does |
| Include graph, compile order | `clang -E -H` plus regexes (`ASTWalker`, discover time) | `dependency_graph.json` | Discovery | No |
| Macro shims, template instantiations | Generated TUs, from the TOML and not from any header scan: `replibuild_shims.c`/`.cpp` turns each `[wrap.macros.<name>]` entry (user-declared `ret` and `args`) into a typed `replibuild_shim_<macro>` function, and `replibuild_templates.cpp` turns each `[types] templates` entry into `template class T;` plus the `RB_TOUCH` odr-use block, compiled `-fno-inline`. Discovery writes both tables empty and preserves them | Compiled into the library, so DWARF and `nm` cover them | Everything downstream | Yes, through DWARF |

**What the JLCS lowering actually reads.** `JLCSIRGenerator`, `FunctionGen` and `StructGen` consume `functions` (`nm`’s mangled names and the demangler’s `class`; DWARF signatures, `is_vararg`, `has_object_pointer`; `is_noexcept` as above), `struct_definitions` (DWARF members, offsets, `byte_size`, `alignment`; enums stored as ` __enum__ <name>` with `underlying_type` and `byte_size`), `record_abi` (`DW_CC_pass_by_reference`, size, empty), `stl_methods` (`nm`) and `language`. Vtables come from `DWARFParser.parse_vtables` over llvm-dwarfdump, at load for JIT thunks and at wrap’s AOT stage for AOT ones.

SysV and Win64 classification happens in `JLCSPasses.cpp` from the struct body the generator emitted, not from any header fact.

Fable edited this to add markdown and spelling and correct ref from source.

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 30, 2026, 10:42pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/31 "2026-10-30T22:42:55Z")

</div>

![Screenshot 2026-10-30 172959](https://global.discourse-cdn.com/julialang/original/3X/0/5/0537dacebf833eaec543ffef232dc7164c3132f6.png)

I cant keep pace with this so almost everything from this point past the dialect is considered vibe coded, my hand edits and gating are actually holding claude back and even a local llm has surpassed what im able to control and edit manually.

Does this mean handing over architecture over to vibe implantation and testing with guidance or how has anyone else dealt with straight being overwhelmed in the field they produce code in, Im asking because my own implementation was beyond llm training since mlir abi marshalling has yet to pick up except with the clangIR and llvmABI but even spending all my time in this I got passed up in a few things and deeply in others, do I just accept those failings and use them on a kind of trust system or spend months to years verifing by hand, or do I just say Im done and let the AI deal with marhalling and any other abi in IR problem by someone else or another vibe coder with more sense… if I had motivation to beat the ai but I just dont see past me paying for what I want to impliment.

Going from builder to bottleneck is depressing

I still own the JLCS dialect its been unedited mostly for 2 major llvm releases and maintained by me and is probably the thing claude needs to be trained on but oh well. That issue is claude is writting better Julia than me and only new cpp code in a custon dialect to marshal for julia is how i beat claude…

---

<div class="post-metadata">

**Author:** ![obsidianjulua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/obsidianjulua/32/221495_2.png) [@obsidianjulua](https://discourse.julialang.org/u/obsidianjulua)\
**Post date:** [October 31, 2026, 9:05pm UTC](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122/32 "2026-10-31T21:05:29Z")

</div>

RepliBuild.jl can now build a project made of several libraries that depend on each other, and wrap them as one Julia module. Add a [graph] section to replibuild.toml:

```julia-auto
name = "polyshape"

[graph]
source = "src" # the directory with CMakeLists.txt
targets = ["scene"] # what you want; the libraries it links come with it

```

How the graph resolves

CMake says what to build; RepliBuild doesn’t guess. RepliBuild runs only CMake’s configure step and reads its File API. That gives every target with its sources, flags, defines and include path, including the include directories and defines it inherits from the libraries it links.  
RepliBuild compiles and links every library itself, so each wrapper’s types come from debug info that matches the binary it loads.  
A static or object library is built into each shared library that links it, and never a second time into one that only reaches it through another shared library.  
Headers generated at build time come from running only those steps of the project’s own Ninja build.  
C and C++ in one project. Each library is compiled as C (with Julia’s bundled clang) or as C++ (with the system clang), depending on its own sources. A C++ library can sit on top of C libraries in the same project.  
One Julia module per library, plus one that includes them. wrap writes a wrapper for each library, then a small top-level module that includes them in dependency order:

```julia-auto
module Polyshape
include("geom/lib/Geom.jl")
include("shapes/lib/Shapes.jl")
include("scene/lib/Scene.jl")
export Geom, Shapes, Scene
end

```

Each wrapper loads its library after the libraries it links, so each library’s global state exists once, shared by everything that uses it. A library whose name Julia already uses gets an R prefix: Core becomes RCore.

Git submodules. A git dependency can now list just the submodules it needs. For example, it can take libs/math from boostorg/boost instead of cloning the whole superproject.

Current limits

CMake projects configured with Ninja, on Linux and Windows (MSYS2 CLANG64).  
Types are still per library.  
A struct defined in one library and used by another becomes a separate Julia type in each wrapper.  
Passing one library’s value to the other library’s function raises a MethodError, so for now you convert it.  
Sharing one type across libraries is the next piece of work.  
`[graph]` can’t yet take its source from a git dependency, and the per-library [wrap]/[compile] options aren’t in yet.

[Previous page](https://discourse.julialang.org/t/ann-replibuild-jl-a-full-c-c-interop-toolkit-for-tiered-ffi-generation-repl-i-build/136122.md?page=1)
