should_compile · accepts
858/ 938
~91% (858 of 938)
programs GHC accepts that Haskelujah also accepts
measured 2026-08-02 @ 8df50557stability · DETERMINISTIC — two independent full passes, byte-identical failing sets
Truth in public
Compatibility against GHC's own test suite — including the axis most compilers don't publish.
There are many ways to claim compatibility, and one way to measure it: GHC's own test corpus, run both directions. A compiler must accept the programs GHC accepts — and it must reject the programs GHC rejects, because a compiler that accepts wrong programs is more dangerous than one that refuses right ones.
These numbers are parsed at build time from measurement artifacts committed to the repository — corpus, method, and every failing file listed. This page cannot claim what the repo cannot prove.
should_compile · accepts
858/ 938
~91% (858 of 938)
programs GHC accepts that Haskelujah also accepts
measured 2026-08-02 @ 8df50557stability · DETERMINISTIC — two independent full passes, byte-identical failing sets
should_fail · rejects
180/ 767
~23% (180 of 767)
programs GHC rejects that Haskelujah also rejects — the honest number, and the current front of the work
measured 2026-08-02 @ 8df50557stability · DETERMINISTIC — two independent full passes, byte-identical sets (the
Method: each axis is a single timed sweep of the corpus, 15-second per-file timeout, counts as committed in the artifacts above. Percentages are floored to the whole, deliberately — a single sweep earns no third significant figure. Each card quotes its artifact's stability line verbatim rather than translating it. When a sweep is re-run and its artifact updated, this page updates with it.
Counted by spec-chirho/stats-chirho.sh on 2026-07-27.
The craft
Follow six lines of Haskell from text to machine. Dumps are illustrative — drawn in the true shape of each intermediate form.
Main.hs
module Main where double :: Int -> Int double n = n + n main :: IO () main = print (double 21)
I · Lex — II · Layout
{ module Main where
{ double :: Int -> Int
; double n = n + n
; main :: IO ()
; main = print (double 21)
} } The lexer streams tokens; the layout algorithm reads indentation and writes the braces and semicolons you never typed.
III · Parse — IV · Lower
Module "Main"
├─ TypeSig double :: Int -> Int
├─ FunBind double
│ └─ Match [VarPat n]
│ └─ InfixApp (Var n) (+) (Var n)
└─ FunBind main
└─ App (Var print)
(App (Var double) (Lit 21)) A lossless concrete syntax tree — every comment and space survives — then lowered to abstract syntax.
V · Resolve — VI · Kind Infer
double ↦ Main.double (+) ↦ GHC.Num.+ Num :: Type -> Constraint print ↦ System.IO.print IO :: Type -> Type Int ↦ GHC.Types.Int Int :: Type
Scopes are resolved, imports are followed, and every type constructor is assigned a kind by unification.
VII · Type Infer — VIII · Exhaustiveness
double :: Int -> Int ✓ inferred ≡ declared main :: IO () ✓ Num Int ✓ solved by instance double n ✓ patterns exhaustive
Hindley–Milner inference meets the declared signatures; class constraints are solved; every match is checked for coverage.
IX · Core — X · Simplify
double = λ(n :: Int) → (+) @Int $fNumInt n n main = print @Int $fShowInt (double (I# 21#)) -- after simplify: inline, then fold main = print @Int $fShowInt (I# 42#)
System FC Core: typeclasses become dictionary arguments. The simplifier inlines double and folds 21 + 21 where it can.
XI · Codegen — XII · Link
_main: ; arm64
mov x0, #42
bl _hlj_print_int
ret
(func $main ;; or wasm
(call $print_i64 (i64.const 42))) Three backends share one Core — LLVM, WebAssembly, Cranelift. Their maturity is tracked honestly in the ledger below.
One binary
Twelve subcommands — compiler to editor — verified against the CLI source, not a brochure.
haskelujah init Scaffold a new project
haskelujah build Compile .cabal projects to native binaries
haskelujah run Execute — or shebang a script with #!/usr/bin/env haskelujah
haskelujah check Type-check without codegen
haskelujah repl Interactive :type, :info, :load, evaluation
haskelujah test Compile and run test suites
haskelujah install Fetch from Hackage; resolve dependencies
haskelujah fmt Format Haskell source
haskelujah lsp Diagnostics and hover for VS Code, Neovim, Zed, Helix
haskelujah mcp Model Context Protocol server for AI assistants
haskelujah edit Built-in editor, GUI or terminal
haskelujah clean Remove build artifacts
Scriptorium
A recorded terminal session, replayed faithfully — init, build, REPL, MCP. Not staged HTML.
Recorded terminal demo: haskelujah init creates a project, check type-checks it, test runs the suite, fmt formats it, and mcp lists AI tools.
Prefer your own terminal? Ten seconds:
cargo install haskelujah && haskelujah repl Begin
One install, no GHC, no Stack, no LLVM toolchain. Then write Haskell like a script.
1 · install
cargo install haskelujah or the script:
2 · write
#!/usr/bin/env haskelujah -- hello.hs — no build step main :: IO () main = putStrLn "Hallelujah."
3 · run
$ chmod +x hello.hs && ./hello.hs Hallelujah.
The same file also builds to a native binary with haskelujah build,
or to WebAssembly — and the execution paths agree on what they print
(receipts in the honest ledger below).
Confessio
An honest ledger, kept current. Trust is built from what a project admits, not what it advertises.
GHC rejects 767 programs in the should_fail corpus; today we correctly reject only a fraction of them (the second number above). A soundness-first effort is underway to close this honestly rather than quietly.
This ledger previously confessed that a corpus file flipped between accepted and rejected across runs of the same binary on the same source. Root cause: signature schemes quantified their type variables in hash-map iteration order. Schemes now quantify in first-appearance order, verified by 100-run determinism loops and a re-measure of both corpora on the fixed binary — failing sets byte-identical, stability lines now read deterministic. Said plainly: no number improved. The coin-flip file now fails every time instead of sometimes; we did not gain a passing file, we stopped pretending a coin flip was a result.
full writeup ↗This ledger previously confessed that a local recursive helper closing over an enclosing pattern-bound argument could take the wrong branch — the canonical primes sieve returned a silently wrong list on the interpreter path, and so did ordinary where-bound helpers. Measured root: captured recursive let groups repatched one shared static placeholder, so later calls redirected earlier calls’ lazy edges. The fix allocates each captured recursive group per invocation (closed groups remain static), verified by focused letrec, STG-lowering, runtime and GC-integrity gates — and the canonical sieve now prints [2,3,5,7,11,13]. Six falsified hypotheses preceded the measured root; the writeup keeps them all.
full writeup ↗This ledger confessed that native code printing a structured value could emit a heap address, a raw constructor tag, or an internal name. The fix threads the Show evidence the type checker already solved into native print — a general mechanism, not per-type patches — with evidence-driven renderers for constructor types. This record closed once, wrongly: an independent check caught Maybe and Either still broken, the entry reopened within minutes, and the completed fix then passed the exact failing forms on three independently built binaries, interpreter, LLVM, and Cranelift agreeing on every case. The six-minute overclaim and its reversal are part of this page’s history on purpose.
full writeup ↗Most rank-2 types work; some deep higher-rank patterns (lens-style LensLike′) still defeat inference.
forall a. C a ⇒ D (f a) in superclass positions is not yet supported. Blocks deepseq.
Token s / Tokens s normalisation is incomplete. Blocks the second stage of megaparsec.
Basic splices and makeLenses work; typed splices, quasi-quoters, and full reify are incomplete.
Basic libc interop (puts, printf, malloc). Full C-header parsing and foreign exports are not yet implemented.
God willing
Reject what GHC rejects. The should_fail number on this page is the scoreboard.
Compile Haskelujah itself to WebAssembly so this page can run the real type checker, live. No fake playgrounds before then — this page shows only what is real.
Closures, thunks, and GC wired into compiled code on every backend.
Compile a non-trivial Haskell frontend for Haskelujah with Haskelujah.
Typed splices, quasi-quoters, reify for every declaration form.
Target selection from the CLI; cost centres, heap and time profiles.