The Same Source, Compiled Without a Host
Every web and database workload Mere could run leaned on a Node host to supply its sockets, its HTTP server, its crypto. The native backends could produce a command-line tool but nothing that listened on a port or spoke to a database. Closing that gap did not mean inventing a native runtime from scratch; it meant borrowing the memory model the Wasm backend already used, so the exact same libraries compiled natively unchanged — and the same source, unmodified, now runs either on Node or as a single self-contained binary that talks to Postgres at hand-written-C speed.
By this point Mere had a great deal of reach on the web: an HTTP server, a hand-written Postgres client speaking the real wire protocol, a Redis client, an ORM, all of it usable from Mere source. But every last piece of it ran the same way — as WebAssembly under a Node host, with the host supplying the sockets, the listening server, the cryptographic primitives. The native backends, the ones that produced a real executable, could compile a command-line tool and nothing more. They had no way to open a socket, no way to accept a connection, no way to hash a password. The most capable half of the language could not run a server at all.
This was not a papercut to file away; it was a whole capability the native path lacked, and the reason it had gone unnoticed is the same reason it did in the CLI story. The Node host had always answered these calls, so the language had never been made to answer them itself. The question was how to close the gap without building a second runtime from nothing.
Borrowing a memory model instead of inventing one
The libraries that spoke to databases were written against the Wasm backend’s memory model. A wire protocol is bytes — you allocate a buffer, write a big-endian length prefix, copy a string in, read bytes back out — and on the Wasm side that vocabulary lowered to operations on a single flat region of linear memory addressed by 32-bit offsets. The libraries were full of those operations. Reimplementing them natively in some other idiom would have meant rewriting the libraries too.
So the native runtime didn’t invent its own model; it borrowed the one that already existed. The C backend grew a flat byte arena — a large static buffer addressed by 32-bit offsets, exactly the shape of Wasm’s linear memory — and implemented the same memory vocabulary against it: allocate, read and write bytes and big-endian integers, copy a string in, read a string out. When a program used one of those operations, the backend stopped emitting a reference to an undefined host function and instead emitted a real native implementation. The libraries didn’t change at all. They had been written against a memory model, and the C backend simply supplied the same model in a different place. This is the discipline the series keeps returning to: when a new problem appears, the first move is to lean on machinery already built. The Wasm backend’s memory model, built years of decisions ago, turned out to be the thing that made the native databases possible.
Sockets, under the same names
On top of the arena went the sockets: POSIX tcp_connect, tcp_write, tcp_read,
tcp_close, implemented natively — and, crucially, exported under the same names
the Node host had used. That is the whole trick to parity. A library imports
tcp_write; on the Wasm path the name resolves to a piece of the Node host, and on
the native path it resolves to the C implementation, and the library above never knows
or cares which. One Mere source now compiles two ways: to WebAssembly for the Node
host, or to a native binary, with the same behaviour on both. The first time the
Postgres client was actually run through the C compiler it flushed out a latent bug in
the backend’s string escaping — a carriage return that was being written raw instead
of escaped, corrupting exactly the kind of binary-ish payload a wire protocol carries.
It was fixed on the spot, and worth noting because the same small omission would
resurface, much later and much more expensively, in a place no one expected.
One binary
The point of all this was a single artefact. The blog application — a real web app
with signup, login, logout, sessions, post ownership — was compiled straight through
to a native executable of a couple hundred kilobytes, with no Node and no Wasm anywhere
in the picture. It listens on a socket, parses HTTP requests through a native accept
loop, talks to Postgres over a real TCP connection, hashes passwords with a native
SHA-256, mints session identifiers from the operating system’s randomness, and answers
curl at every endpoint. The flagship sentence the whole arc was aiming at —
write a web-and-database application in Mere and ship it as one binary — was now a
literal, demonstrated fact rather than an aspiration.
Crypto without a tunnel
One piece looked like it would force a dependency on TLS, and then didn’t. Connecting to a Postgres configured for password authentication means completing a SCRAM handshake, which needs real cryptography: SHA-256, HMAC, PBKDF2, base64. Those were implemented natively and checked against reference vectors. The pleasant surprise was that SCRAM is a challenge-response protocol — it proves knowledge of the password without ever sending it — so it needs no transport encryption to be safe. A native binary could authenticate to a password-protected database over a plain TCP connection, with no TLS stack at all. And because the runtime underneath was generic rather than Postgres-specific, the same arena and sockets, plus two small hex helpers, were enough to drive a Redis client natively too. The runtime wasn’t a database feature; it was a foundation the whole client family stood on.
The number
Adding a native path invites the obvious question of whether it is actually fast, and
this was the first time the language had a real answer. A recursive fib(35) compiled
natively ran in about thirty milliseconds. The same computation hand-written in C and
compiled with optimisation ran in about thirty milliseconds. Mere-through-C-through-a-C-compiler,
on ordinary recursive code, produced native code on par with C written by hand — and
across the other benchmarks the native path ran forty to eighty times faster than the
interpreter, with tail calls turned into real loops and allocation-heavy code showing
no pathologies. There were no red flags in the generated code. The story the numbers
tell is simple enough to be the tagline: compile Mere natively, and you get C speed.
What the arc really added was not a feature but a second life for everything already written. The libraries didn’t change; the source didn’t change; the host simply became optional. A language that had lived inside a generous runtime learned to stand on the operating system directly, and it did so not by inventing a new foundation but by carrying an old one to a new place.