You’re absolutely right to be skeptical of your compiler binaries. In this post, we’ll explore how a comprehensive C++ reimplementation unlocks DDC verification for OCaml — while delivering meaningful compile-time improvements. ![]()
Wait! Don’t leave yet! The rest of this was typed character by character by a human.
We had some fun here recently talking about an unrelated project, the OCaml runtime ported to Rust, and this led to an interesting side-channel discussion that surfaced a paper on Debootstrapping without Archeology by Nathanaëlle Courant, Julien Lepiller and Gabriel Scherer. I somehow had never come across this before!
Background brief on trusting trust: once upon a time, UNIX god Ken Thompson trolled the world during an acceptance speech by asking “wouldn’t it be hilarious if your beloved compiler authors had introduced a backdoor years (decades) ago that was still lurking today???” (paraphrased). The crux of the matter was that, to compile a C compiler which is implemented in C, you need an existing C compiler binary and that’s an opportunity to introduce malware that wakes up when it recognizes it’s compiling a target program (like login).
Ever since then, the infosec community has been… curious… about trusting trust and they breathed a huge sigh of relief when David A. Wheeler proposed diverse double compilation (DDC) as a mitigation. So, has anyone done this for our favorite language?
Yes! In the Debootstrapping OCaml paper, the authors describe writing an OCaml interpreter in MiniML (an OCaml subset) and a compiler for MiniML in Scheme to do DDC, which proved that OCaml 4.07.1 has no trusting trust backdoors when compiling itself. But notice this part in their paper:
If we assume unlimited work resources, tailored debootstrapping is a trivial problem: just port your programming-language implementation to another language that has already been debootstrapped (for example C). But this is a massive effort that may never happen in practice – especially as you have to first convince your language implementors to work in a different programming language.
Tee hee.
I present this independent OCaml compiler implemented in C++. It runs a OOMs faster than the MiniML/Scheme debootstrapper, solves DDC for OCaml 5.5.1 release (and 5.6+trunk), produces identical byte outputs to the OCaml compiler, and it even runs faster than the official OCaml compiler(!)
NOTE for quick skimmers: this project does not make your OCaml programs faster. It may only let you compile OCaml programs faster.
Regarding byte-identical outputs. There’s a few documented minor divergences that could be closed if there’s interest. The divergences do not preclude DDC and they’re minor enough that I am claiming byte identical.
Most importantly: because we have done DDC, we can conclude the OCaml 5.5.1 (and 5.6+trunk) bootstrap has no back doors when compiling the compiler. Furthermore, because we do produce byte identical outputs as upstream, arguably no back door is lurking outside of the source-available views.[1]
Regrettably, not only is there no backdoor in the OCaml compiler, there isn’t even an easter egg. Sad! I would have at least snuck in something like mb4c4rell4_waz_here, but that’s probably why the good people behind OCaml have commit bits and I don’t.
How to run DDC yourself
Clone the repo git clone https://github.com/mbacarella/cxx-ocaml and follow the instructions in cxx/DDC.md.
Install an opam switch that uses the C++-based OCaml compiler
Do this to get a switch whose ocamlc and ocamlopt are the C++ version.
opam switch create cxx-5.5 --empty
opam pin add -n ocaml-variants.5.5.1 'git+https://github.com/mbacarella/cxx-ocaml#cxx-5.5'
opam pin add -n ocaml-option-cxx.1 'git+https://github.com/mbacarella/cxx-ocaml#cxx-5.5'
opam install ocaml-option-cxx ocaml-variants
opam switch set-invariant --packages=ocaml-variants,ocaml-option-cxx
To check that you’re actually running the C++ version, do ocamlc -cxx-version. The stock compiler stays available in the switch as ocaml*.stock.
Requirements: clang++ 18 or newer, cmake, ninja, and libzstd’s dev files.
Don’t @ me that this is all a big hoax because you’re missing some dependencies. We have all seen how that goes.
Next steps
Minimize the extra dependencies needed above, like swapping clang for gcc to match OCaml upstream.
I’ve only tested this on x86-64 on Linux, so ARM64 is an obvious next addition. Also probably should add support for flambda.
There’s still some low hanging performance-fruit wins that don’t involve doing anything too obscene[2] to go after.
I’m happy to hear your feedback! We can chat here or on the OCaml Discord’s #ai channel.
Let me know what you’d like to see!
Benchmarks
Take these with a grain of salt. Benchmarks are easy to embarass yourself with. That said.
My benchmark host was a Desktop Ryzen 9 7950X with 128GB of RAM running Linux.
I’d prepare an opam switch with each compiler and all project dependencies first.
The benchmark itself is a full build of awso, an AWS library that has coverage for all of 300+ AWS services, including some very huge auto-generated modules with ppx.
It builds byte code, native and assembles a gigantic awso-cli that links every aws library. I timed dune build and took the best of 3.
I think this is a good real world test because it contains about 100,000 build artifacts and it was a major quality of life issue to both do full rebuilds every time I tinkered with something. opam-ci certainly didn’t appreciate how huge it was either.
dune selected up to 32 cores on my host.
Results
- ocaml 5.5.1: 4998.41s user 1016.62s system 1904% cpu 5:15.91 total
- c++ocaml 5.5.1: 3281.87s user 990.02s system 1984% cpu 3:35.30 total
As we can see, both the CPU time and wall clock time are lower in the C++ version. In terms of wall time we are 1.47x faster.
Every build artifact is bitwise identical to upstream: .cmi, .cmo, .cmx, .o, .cma, .cmxa. So are executables built in the same switch (in the C++ switch the stock compiler is ocamlopt.stock and the C++ version is renamed over ocamlopt). The one difference is the .cmt/.cmti files, which are used by editor tooling. They decode to the same data but stock OCaml’s marshalling has a form of structural sharing we didn’t completely reproduce the layout of.[3]
Note there’s more than just the ocaml compiler itself running a build. A ton of time is spent in the gcc toolchain (like as). The improvement in individual artifacts is more substantial than the wall clock time suggests, but it’s also a lot more work to describe. So I’ll leave that for later if anyone is interested.
AI disclosure
I obviously didn’t implement an OCaml compiler in C++ myself. I used Claude Code. This wasn’t easy for Claude either: it has been working on this project for 4 months now, though progress sped up between the releases of Opus 4.8, 5.0 and 5.5, with some guest appearances by Fable.
This is not an April fool’s
Don’t confuse this with Stephen Dolan’s troll, which compiled OCaml into C++ template metaprograms. Game respects game though.
Footnotes
-
This method in this project does not certify that your C/C++ toolchain, other OS tools or your firmware are backdoor free, only that if a backdoor is present it did not originate from the code in this project.
-
I’m not against doing the obscene for even moar performance! Anything especially obscene would go in a separate branch though and be opt-in.
-
Mostly just to save on tokens. We didn’t cheat the benchmarks by doing this. We’re actually slower than stock OCaml at writing .cmt files and that cost shows up in the benchmarks.