Looking for the Pain That Justifies a Backend
Mere had four backends, and one of them had no clear job. The LLVM backend overlapped the C one: native code already ran at hand-written-C speed through C, and the web ran on WebAssembly. So rather than delete it on a hunch or keep it on faith, the question was answered the way the language answers everything — by going to find the concrete pain that would force it. Three probes went looking, each expecting to find the justification, and each one dissolved. The finding that a backend isn't needed is itself worth having, and one of the probes turned up something true that would matter later, in a place it wasn't looking.
A language with four backends should be able to say what each one is for. Three of Mere’s answered easily: the interpreter is for iterating quickly, the C backend produces native binaries at the speed of hand-written C, and WebAssembly runs on the web and under the Node host. The fourth, the LLVM backend, could not answer. Whatever it produced, the C backend produced too, and just as fast. It had been carried along as a matter of course, the way one assumes a serious language ought to target LLVM, and it had quietly become a duplicate.
The tidy options were to delete it or to keep defending it on principle. Neither is how this language decides things. A feature earns its place by removing a real, present pain, so the honest move was to go looking for the pain that would force an LLVM backend into being genuinely needed — and to believe the answer even if it came back empty. Three probes went out.
The thousandfold gap that was a mirror
The first probe chased the classic reason to want a just-in-time compiler: a program that receives code at runtime and has to run it fast. Evaluating an expression tree ten million times took twenty seconds when walked by the interpreter, and twenty milliseconds when the same computation was written straight in native code. A thousandfold gap is exactly the shape of a JIT-shaped hole.
Then the probe was dogfooded, as an awk-like tool that evaluates a user-supplied expression against every row of a file, and the gap collapsed. The thousandfold was an artifact of double interpretation — the Mere interpreter was itself walking a tree to walk the expression tree. Compile the tool ahead of time, the ordinary way, and its expression evaluator becomes native code; a runtime expression then runs at essentially native speed with no JIT anywhere. Worse for the hypothesis, a real tool of this kind is bound by input and output, not by arithmetic: across two million rows, whether the expression was a single column or a deeply nested formula, the wall-clock time did not budge, because the evaluation was lost in the noise of parsing and printing. The pain a JIT would relieve was not a pain this kind of program actually had.
The one genuine crack
The second probe found the closest thing to a real reason, and it is worth stating precisely because it is genuine and small at the same time. Deep mutual tail recursion — two functions calling each other ten million times — ran to completion on WebAssembly, ran to completion as a native binary compiled with optimisation, and crashed with a stack overflow as a native binary compiled without. WebAssembly guarantees tail-call elimination at the level of the format; C does not, leaving it to the C compiler’s optimiser, which folds it at high optimisation and abandons it at low. C has no way to demand the elimination. To guarantee tail calls in native code, you would want LLVM’s intermediate representation, which can.
This is the one place LLVM leans in — and even here the lean is slight, because the recommended native build runs with optimisation, where the tail calls do get folded. The guarantee only matters for a workload that genuinely needs it: a continuation-heavy interpreter, a state machine, a proof checker written in a style that ping-pongs forever. Until such a program appears and finds the optimiser’s best-effort insufficient, the machinery to guarantee it is answering a question no one has asked. It is worth flagging, though, that this probe had found something true about tail calls and guarantees — and that the truth would come back to matter, not in native code where the probe was looking, but somewhere else entirely.
The things C cannot express
The third probe reframed the whole question, and the reframing is the real conclusion. The C compiler in use is itself built on LLVM. So everything LLVM is famous for — its optimiser, its vectorisation, its link-time work — the C backend already inherits, for free, by compiling through that compiler. An LLVM backend could only earn its keep by doing something the C backend cannot express at all, and the search narrowed to what that might be.
The candidate that seemed most promising was coroutines — the ability to suspend and
resume a computation — on the theory that resumable effects would demand them. But
Mere’s effects are passed as ordinary capability values; there are no algebraic effect
handlers, no resume, no yield, nothing that suspends, so the demand simply doesn’t
arise from that direction. And the suspending patterns people do want — lazy streams,
generators — turned out to be expressible with ordinary closures and thunks, compiled
natively through C without incident, verified on an infinite stream summed to a finite
answer. The genuine cases where LLVM would be irreplaceable are the ones C truly cannot
reach — emitting GPU kernels, running a tracing garbage collector — and each of those
is a change of what the language is, a pivot Mere has not made. None of them is a
present pain.
The value of a negative
So the answer came back empty, and empty was the answer worth having. There was no pain forcing an LLVM backend into existence, because a C compiler that is secretly LLVM already delivers LLVM’s optimisation, WebAssembly already guarantees the tail calls, and the effect system doesn’t suspend. The backend stays where it is — experimental, a duplicate for now — and the decision not to invest in it is not a shrug but a measured finding, with its triggers named: the day Mere wants to emit GPU code, or the day its effects grow the ability to resume and want compiling to zero-allocation state machines, the probes would come back with a different result.
This is the same restraint an earlier part practised when it declined to build locks and atomics: the discipline that treats not building as an act requiring evidence, exactly as much as building does. The deliverable of three probes was a sentence — don’t invest in LLVM yet, and here is why — backed by measurements rather than taste. And folded inside that negative was one small true thing about guaranteed tail calls, noticed in passing, filed away, and waiting for the part of the story where the compiler would try to compile itself.