Skip to content

feat: add wasi32 cross-target#1429

Merged
dhess merged 8 commits intomainfrom
dhess/wasm-3
Apr 22, 2026
Merged

feat: add wasi32 cross-target#1429
dhess merged 8 commits intomainfrom
dhess/wasm-3

Conversation

@dhess
Copy link
Copy Markdown
Member

@dhess dhess commented Apr 20, 2026

Thanks to @georgefst for the ghc-wasm-patches, which we borrow wholesale from https://github.com/georgefst/quadris, along with some other useful haskell.nix bits.

dhess added 2 commits April 20, 2026 17:11
Thanks to @georgefst for the `ghc-wasm-patches`, which we borrow
wholesale from https://github.com/georgefst/quadris, along with some
other useful `haskell.nix` bits.

Signed-off-by: Drew Hess <src@drewhess.com>
Signed-off-by: Drew Hess <src@drewhess.com>
Signed-off-by: Drew Hess <src@drewhess.com>
@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

Huh, this is interesting. If we take out the arch(wasm32) override for the tests' main-is, so that they use the same Test.hs main as the native tests, and then run nix build -L .#checks.aarch64-darwin.wasm32-unknown-wasi:primer:test:primer-test, Cabal will run the tests using wasmtime automatically!

The tests that try to createDirectory fail:

primer-test-primer-test-static-wasm32-unknown-wasi> Running phase: unpackPhase
primer-test-primer-test-static-wasm32-unknown-wasi> unpacking source archive /nix/store/iiyfmang8r3bx75bnm3r5ssfpnl8f17h-source-primer-test-primer-test-root
primer-test-primer-test-static-wasm32-unknown-wasi> source root is source-primer-test-primer-test-root
primer-test-primer-test-static-wasm32-unknown-wasi> Running phase: patchPhase
primer-test-primer-test-static-wasm32-unknown-wasi> Running phase: buildPhase
primer-test-primer-test-static-wasm32-unknown-wasi> /nix/store/390gr5jiaimv8f7n8k1gmnb22c77sinq-primer-test-primer-test-static-wasm32-unknown-wasi-0.7.2.0/bin:
primer-test-primer-test-static-wasm32-unknown-wasi> /nix/store/390gr5jiaimv8f7n8k1gmnb22c77sinq-primer-test-primer-test-static-wasm32-unknown-wasi-0.7.2.0/nix-support:
primer-test-primer-test-static-wasm32-unknown-wasi> patching script interpreter paths in /nix/var/nix/builds/nix-13101-3324069293/tmp.M8SMDeaYz1/bin
primer-test-primer-test-static-wasm32-unknown-wasi> test/Test.hs
primer-test-primer-test-static-wasm32-unknown-wasi>   Tests
primer-test-primer-test-static-wasm32-unknown-wasi>     Action
primer-test-primer-test-static-wasm32-unknown-wasi>       Available
primer-test-primer-test-static-wasm32-unknown-wasi>         M.comprehensive
primer-test-primer-test-static-wasm32-unknown-wasi>           Beginner:Editable:                                           FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Beginner:Editable/' to rerun this test only.
primer-test-primer-test-static-wasm32-unknown-wasi>           Beginner:NonEditable:                                        FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Beginner:NonEditable/' to rerun this test only.
primer-test-primer-test-static-wasm32-unknown-wasi>           Intermediate:Editable:                                       FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Intermediate:Editable/' to rerun this test only.
primer-test-primer-test-static-wasm32-unknown-wasi>           Intermediate:NonEditable:                                    FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Intermediate:NonEditable/' to rerun this test only.
primer-test-primer-test-static-wasm32-unknown-wasi>           Expert:Editable:                                             FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Expert:Editable/' to rerun this test only.
primer-test-primer-test-static-wasm32-unknown-wasi>           Expert:NonEditable:                                          FAIL
primer-test-primer-test-static-wasm32-unknown-wasi>             Exception: ghc-internal:GHC.Internal.IO.Exception.IOException:
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             test: createDirectory: does not exist (No such file or directory)
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             HasCallStack backtrace:
primer-test-primer-test-static-wasm32-unknown-wasi>               ioError, called at libraries/directory/System/Directory/OsPath.hs:323:43 in directory-1.3.10.0-inplace:System.Directory.OsPath
primer-test-primer-test-static-wasm32-unknown-wasi> 
primer-test-primer-test-static-wasm32-unknown-wasi>             Use -p '/Expert:NonEditable/' to rerun this test only.

But somehow the test discovery seems to work. I need to figure out what's going on, exactly, but this is very encouraging.

edit: probably this is the mechanism: https://github.com/input-output-hk/haskell.nix/blob/c179719f50011c64a8639d885f998bc662e4d2d1/overlays/wasm.nix#L49

@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

Need to figure out how to pass arguments to wasmtime and we might be good.

edit: we can use test-wrapper (or testWrapper in haskell.nix).

@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

I've got a fix for the tests that means we don't have to change anything drastic. I think we're finally close to having working Wasm builds with `haskell.nix.

edit: And Wasm benchmarks are working!

@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

There's definitely some Cabal file cleanup we can do, so this PR is a bit of a DRY violation, but I'll leave that for later work.

Signed-off-by: Drew Hess <src@drewhess.com>
This looks like a big change, but it's almost entirely formatting. The
only real change is that we have to evaluate
`stdenv.targetPlatform.isWasm` for the `testWrapper` override in its
own overlay, or else it will never trigger: when compiling for the
`crossPlatforms` target in the flake, Nix thinks the `targetPlatform`
is the current `system`. I don't really understand why, but this
workaround does the job, so it's fine for now.

We might be able to fix this with "real" Nix-style cross support, but
`haskell.nix`'s `crossPlatforms` feature is arguably much nicer, so
we'll keep doing things this way.

Signed-off-by: Drew Hess <src@drewhess.com>
@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

82a2da0 is a fix for some Nix (or perhaps haskell.nix) evaluation weirdness. For some reason, in the overlay where we define the primer packages, if we try to predicate the testWrapper override on stdenv.targetPlatform.isWasm, it never triggers, as Nix seems to treat stdenv.targetPlatform as the system it's evaluating the overlay for.

I'm not sure why this is happening. This may be due to the fact that haskell.nix's crossPlatforms feature isn't proper Nix-style cross-compilation. However, IMO it's really a very nice feature, since you get the cross compiler in your shell for interactive development, instead of as an entirely separate package set, so we'll keep doing the workaround that commit introduces, even though it's inelegant.

@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

As part of this change, Primer Wasm tests will always run on PR updates (assuming the update forces a Primer package rebuild, anyway), rather than only in the merge queue. Additionally, as we're currently building a Wasm cross toolset and packages for both x86_64-linux and aarch64-darwin, it means the Primer Wasm tests will run on both of those platforms. (This was previously the case, as well, but more recently we decided to cut out the aarch64-darwin version of those tests.)

The good news is that, unlike in the previous system, where we had a separate Wasm toolchain outside the haskell.nix ecosystem, we couldn't take advantage of any caching, so this means the Wasm tests may run less frequently, and certainly won't take as long to build when they do run. That said, if this becomes problematic, we can dial down the number of iterations in the generative tests when run on Wasm.

@dhess
Copy link
Copy Markdown
Member Author

dhess commented Apr 21, 2026

I'm going to remove the GitHub workflow that publishes benchmarks as part of this PR. We don't push packages to Cachix anymore, so the benchmark publishing action takes a long time to build, and it would even be worse if we started using it to publish Wasm benchmarks, which we clearly want to do ASAP.

Figuring out a new solution will require some work, and I don't want to gate this PR on it, so we'll circle back around to this, hopefully relatively soon.

dhess added 3 commits April 22, 2026 00:21
See
#1429 (comment)
for rationale.

We'll replace this with something better in the near future.

Signed-off-by: Drew Hess <src@drewhess.com>
This is to ensure we don't run into the same issue as we had with
predicating our `testWrapper` override on
`stdenv.targetPlatform.isWasm`.

Signed-off-by: Drew Hess <src@drewhess.com>
We use the `wasm32-unknown-wasi-cabal` linker shim from
https://github.com/georgefst/quadris, which fixes interactive build
issues like this:

```
wasm32-unknown-wasi-cabal build all
Build profile: -w ghc-9.14.1 -O2
In order, the following will be built (use -v for more details):
 - hedgehog-1.7 (lib) (requires build)
 - primer-miso-0.8.0.0 (exe:primer-miso) (first run)
 - tasty-hedgehog-1.4.0.2 (lib) (requires build)
 - primer-0.7.2.0 (lib:primer-testlib) (first run)
 - hedgehog-classes-0.2.5.4 (lib) (requires build)
 - primer-benchmark-0.7.2.0 (lib) (first run)
 - primer-api-0.7.2.0 (lib:primer-api-testlib) (first run)
 - primer-0.7.2.0 (lib:primer-hedgehog) (first run)
 - primer-benchmark-0.7.2.0 (test:primer-benchmark-test) (first run)
 - primer-benchmark-0.7.2.0 (bench:primer-benchmark) (first run)
 - primer-api-0.7.2.0 (test:primer-api-test) (first run)
 - primer-api-0.7.2.0 (lib:primer-api-hedgehog) (first run)
 - primer-0.7.2.0 (test:primer-test) (first run)
Starting     hedgehog-1.7 (lib)
Preprocessing executable 'primer-miso' for primer-miso-0.8.0.0...
Building executable 'primer-miso' for primer-miso-0.8.0.0...
<command line>: cannot satisfy -package-id miso-1.9.0.0-2iwfE70Ir6BGugvi4pWu6A
    (use -v for more information)
Building     hedgehog-1.7 (lib)

Failed to build hedgehog-1.7.
Build log ( /Users/dhess/.cache/cabal/logs/ghc-9.14.1/hdghg-1.7-0da76e4a.log
):
Configuring library for hedgehog-1.7...
Preprocessing library for hedgehog-1.7...
Building library for hedgehog-1.7...
<command line>: cannot satisfy -package-id concurrent-output-1.10.21-4mik95bkUE8CPm1JkyCen9
    (use -v for more information)
Error: [Cabal-7125]
Failed to build hedgehog-1.7 (which is required by test:primer-test from primer-0.7.2.0, test:primer-api-test from primer-api-0.7.2.0 and others). See the build log above for details.
Failed to build exe:primer-miso from primer-miso-0.8.0.0.

make: *** [Makefile:27: build-wasm] Error 1
```

Oddly only interactive builds are affected.

Additionally, we:

- provide a `test-wrapper` for Wasm tests in the Nix shell

- restore most Wasm `Makefile` targets from the old `Makefile.wasm32`
  system

- add all Wasm optimizer tools to the Nix shell

- sync up our `index.js` shim with
  https://github.com/georgefst/quadris, mainly to support a local
  browser Wasi shim, to reduce external dependencies

We drop Wasm `repl` and `watch` targets for now until we can sync up
with the latest ecosystem improvements. (These were broken in the old
non-`haskell.nix` Wasm system, anyway.)

Future work will include bundling the frontend distribution with Nix.

Signed-off-by: Drew Hess <src@drewhess.com>
@dhess dhess enabled auto-merge April 22, 2026 01:35
@dhess dhess added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 7a15604 Apr 22, 2026
58 checks passed
@dhess dhess deleted the dhess/wasm-3 branch April 22, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant