Letting the compiler own the memory map: WATX, an extended WebAssembly Text

A program written directly in WebAssembly Text has no linker. Every table, arena and buffer in a 512 MB linear memory is a hand-chosen hex address, and a 200,000-line project accumulates more than a thousand of them. Wine-Assembly replaced that with WATX, a small superset of WAT compiled by a vendored compiler, in which regions are declared by size and the compiler places them. This article explains why the hand-placed map became untenable, what WATX adds, and how the migration was verified.

The problem with hand-placed addresses

By mid-2026 the emulator's memory map held about 170 regions: the guest window, the DIB backing store, the threaded-code cache partitions, window records, GDI object tables, DirectX object tables, the API hash table, and dozens of smaller ones. Each was a literal base address in src/01-header.wat, repeated wherever code needed it, and mirrored into JavaScript for the host side.

Three things went wrong repeatedly:

What WATX adds

WATX keeps everything WAT has and adds a few forms the compiler expands:

flowchart LR
    subgraph before["Before"]
        H1["01-header.wat<br/>WND_RECORDS = 0x00E40000<br/>GDI_OBJECTS = 0x00F20000<br/>... 170 literal bases"] --> C1["copies in WAT handlers"]
        H1 --> C2["copies in lib/*.js"]
        C2 -.->|"base moves, JS reads stale memory"| BUG["silent bug"]
    end
    subgraph after["After (WATX)"]
        D["00-regions.wat<br/>region.declare WND_RECORDS size=...<br/>region.declare-fixed GUEST_BASE 0x12000"] --> COMP["tools/build-compile-wat.js<br/>allocates every base"]
        COMP --> WASM["wine-assembly.wasm"]
        COMP --> MIRROR["lib/region-map.generated.js<br/>the one JS mirror"]
        COMP --> GATES["gates: overlaps, freshness,<br/>no JS copies, shake modes pixel-identical"]
    end
    before ~~~ after
    style BUG fill:#f6d6d6,stroke:#a33

The compiler, tools/build-compile-wat.js, is the project's own WAT-to-wasm compiler with these extensions, vendored with a sealed changelog and SHA-256 provenance check so a build cannot pick up an unreviewed compiler change. wat2wasm is not used at all.

How the migration was verified

Moving the map is exactly the kind of change that can look fine and be wrong, so the verification was designed before the cutover. The record is in watx-migration-plan.md and the design in watx-region-safety-design.md:

The cutover happened on August 31, 2026, the busiest day in the project's history, and the legacy build path was deleted the same day rather than kept as a fallback. watx-migration-gaps.md lists what the migration deliberately left for later.

Why it matters beyond this project

Writing a large program in WebAssembly Text is unusual, and most advice assumes a compiler in front of it. The WATX experience is an argument that the missing piece is small: an include mechanism, a region allocator, and a struct-layout declaration recover most of what a linker and a type system provide for memory layout, and the verification methods (an encoder oracle, placement shaking) are cheap enough to keep as permanent build gates. The memory map documentation shows the result, including which regions are guest-visible and which are emulator-private.

Further reading