One Int, Four Machines
A probe aimed at the missing bitwise operators hit something underneath them first: the language's int was a different width on every backend. The interpreter had 63 bits, the C backend had a silent 32, LLVM and Wasm had their own. SHA-256's round constants — thirty-six of them above two billion — truncated on the C backend and produced confidently wrong digests with no diagnostic at all, while the interpreter that the tests ran on computed them correctly. Before bitwise operators could mean anything, the language had to decide how wide an integer is.
This part is a single day of the cheap-probe method from the last one, run until it stopped being cheap. Six releases came out of four small programs, and the thread running through all of them is a sentence that sounds like a bug report and is actually a design statement: when a language has four backends, it has four truths, and they are only the same truth if someone made them so. The first probe found the place where they were not.
The probe that missed its target
The plan was modest — write SHA-256 in the language to measure how badly the missing
bitwise operators hurt, since every AND and XOR had to be faked with division and
remainder. The faking was ugly but it worked on the interpreter, all NIST test vectors
passing. Then the same program, compiled through the C backend, produced a completely
wrong digest. Not slightly wrong — unrelated. And it said nothing: no warning, no crash,
no diagnostic. The bit-faking was correct; the problem was one level down. SHA-256’s
sixty-four round constants include thirty-six values above two billion, and the C
backend’s integer was a plain C int — thirty-two bits. Every one of those constants
silently wrapped to a negative number the moment it was written. The minimal reproduction
is stark: 2147483647 + 1 prints 2147483648 on the interpreter and -2147483648
natively. The documentation had never said which width the C backend used; the tests all
ran on the interpreter, where the arithmetic was 63-bit and fine. Every compiled program
this project had ever shipped was running with different integer semantics than the ones
it was tested against, and only a program that reached past two billion would ever
notice.
Deciding how wide
The fix is not subtle, but the discipline around it is. The C backend’s integer became
64-bit — a long long, with literals carrying the suffix that keeps their arithmetic
from wrapping at 32 bits before any widening, and the print and parse paths widened to
match. The interpreter stays at its native 63. Wasm and LLVM stay at 32, but they stopped
lying about it: an integer literal that doesn’t fit their range is now a compile-time
error with a source location, not a silently truncated bit pattern that surfaces as
garbage or a downstream tool’s complaint. And one boundary was deliberately not
unified — at the foreign-function boundary, where a Mere extern fn names a libc or
POSIX function, int stays the C 32-bit int, because that is the actual width those
functions’ signatures use; widening it there would read undefined bits out of a register.
So the language now has an honest table: four backends, three widths, one documented rule
for each, and a place where the difference is load-bearing rather than accidental. The
point of the episode is not that a bug was fixed. It is that “how wide is an integer” is a
question a language must answer the same way everywhere it can, and visibly where it
cannot — and that the way this project found the answer was to transcribe a specification
that happened to use the exact numbers where the truths diverged. The next episode finally
builds the bitwise operators the probe set out to need — and clocks what they were worth.