leo.blog();

High performance ML-like languages

Notes from a link dump that turned out to be mostly one project. Started 2026-11-04.

The premise was "ML-like languages that compile to something fast". The three HN links are all threads about ATS; MLton and Felix are the other two data points worth keeping.

TL;DR

What it is Compiles to Status Speed evidence
ATS ML-style syntax + dependent and linear types, proofs C Active-ish, niche "As efficient as C/C++" (site claim); disputed
MLton Whole-program Standard ML AMD64/x86, C, LLVM Alive (release 20241230) Strong, 44-benchmark suite vs other SML compilers
Felix OCaml-fronted language with C++ interop C++11 Dormant (last commit Sep 2024) Two microbenchmarks, mixed vs C

The pattern: none of these get speed from a clever VM. They get it by specialising the whole program at compile time and handing a fairly ordinary program to a mature C/C++/LLVM backend.

Sources

ATS

A statically typed language that tries to unify implementation and specification: dependent types for specs, linear types for resources. Compiles to C. The compiler is itself written in ATS.

The interesting claim is "as efficient as C/C++ both time-wise and memory-wise". In the 2019 thread someone notes ATS beat C++ on the Benchmarks Game; the rebuttal is that the ATS entries were basically C code, and C beats C++ there anyway. That's probably why ATS is no longer listed on the Benchmarks Game — the fastest/ats.html page now 404s, though I found no official removal note.

Real datapoint from the threads: vmchale's polyglot, a lines-of-code counter in ATS, claims 134.6 ms on the Rust repo vs loc 139.6 ms, scc 225.4 ms, tokei 262.6 ms, cloc 5.61 s. That's a real, if narrow, win.

Comment highlights:

Verdict for me: fascinating as a type-system rabbit hole, not something to reach for when I want a fast program. The "fast" part comes from compiling to C, not from the types.

MLton

Whole-program Standard ML compiler. Stephen Weeks started it in 1997 at NEC; 1.0 in 1999. Latest release 20241230, and the repo is still alive (a commit in Oct 2026 adds bootstrapping via the Rune SML compiler). License HPND.

Pipeline: defunctorise → monomorphise (duplicate polymorphic values/datatypes per type) → closure conversion via defunctionalisation → SSA optimisations (inlining, unboxing, flattening, contify, DCE) → native AMD64/x86, C, or LLVM backend. GC is a runtime choice between two-space Cheney copying, mark-compact, and generational.

Performance, from its own 44-benchmark suite (vendor-run, old hardware): matrix-multiply 7.4× faster than Poly/ML and 15× faster than SML/NJ; md5 32.6× faster than Poly/ML. It also loses some — even-odd is ~2.4× slower than both, string-concat 1.4× slower than Poly/ML. A 120 KB hello world is normal, so code size isn't the usual whole-program disaster.

The cost is compile time and memory: 140K lines needs ≥512 MB and 5–10 minutes on a 1.6 GHz machine; a 2026 commit measured a self-build at 4:32. No separate compilation, no REPL, no use. This is the classic whole-program trade made explicit.

Build/hello:

mlton hello-world.sml   # print "Hello, world!\n";
./hello-world

Felix

John Skaller's one-person project. OCaml front end, emits C11, then g/clang++/MSVC does the optimising. Whole-program: flatten includes, aggressive inlining, uncurrying, self-tail-call elimination, parallel assignment. Generics largely go through C++ templates rather than an MLton-style monomorphiser. No evidence of defunctionalisation — higher-order values become C++ closures/function objects.

Runtime: custom non-moving mark-and-sweep GC over malloc, Judy1/JudyL bitmaps for heap maps, precise heap shape plus conservative stack scanning, interior pointers supported. Non-moving on purpose, for C/C++ interop. Fibers, synchronous channels, SDL2, GMP/GSL, RE2 bindings.

The README says "the aim is to run faster than C". The evidence is two microbenchmarks (seconds, no hardware/date/variance given): with clang, ack 3.71 vs 3.95 and takfp 6.23 vs 6.29 — slightly faster. With gcc, 2.34 vs 2.25 and 6.60 vs 6.25 — slower on both. So: roughly C-class on microbenchmarks, not reliably faster.

Status is the bigger caveat. Last stable release 2019.01.06, last commit Sep 2024, 818 stars, and the licence file says only "Felix is free for any use" (GitHub reports NOASSERTION, not an OSI licence). No verifiable production users.

Build is heavy: Python 3, OCaml 4.06.1, and a C++ compiler.

. buildscript/macosxsetup.sh
make
flx hello.flx   # println$ "Hello World!";

The shared tricks

  1. Whole-program compilation. No stable ABI between modules means cross-module inlining, dead-code elimination, and devirtualisation all become possible. This is the main source of the wins, and the main source of the pain (compile time, no REPL, no incremental builds).
  2. Monomorphisation. Polymorphism is erased by duplicating code per concrete type instead of boxing and dispatching at runtime.
  3. Defunctionalisation / closure conversion. Higher-order functions become first-order data plus a dispatch, which the backend can then inline.
  4. Unboxing and representation flattening. Avoid heap allocation for tuples, records, numeric values.
  5. Borrow a mature backend. C, C++ or LLVM does instruction selection and register allocation. None of these projects writes a serious native code generator.
  6. Choose the GC to fit. MLton can switch collectors at runtime; Felix refuses to move objects so C++ pointers stay valid.

Caveats worth remembering

To try later

Open questions

Leave a Comment