Skip to content

Commit e476b11

Browse files
committed
CI: WASM smoke test with post-link.mjs runtime verification
Replace the compilation-only WASM smoke test with a full runtime test using GHC's post-link.mjs tool and Node.js WASI support. GHC WASM executables contain ghc_wasm_jsffi custom sections (transitively from ghc-internal via base/Prelude) requiring JavaScript FFI glue at instantiation time. The post-link.mjs tool — an official part of GHC's WASM backend (utils/jsffi/post-link.mjs) — parses these sections and generates JavaScript providing the ghc_wasm_jsffi imports. The smoke test now follows the documented workflow from docs/users_guide/wasm.rst: compile → post-link → instantiate with JSFFI + WASI imports → run main.
1 parent ab7b5e3 commit e476b11

1 file changed

Lines changed: 44 additions & 5 deletions

File tree

.github/workflows/nix-ci.yml

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -829,14 +829,53 @@ jobs:
829829
export PATH=$PATH:$HOME/.ghc-wasm/wasi-sdk/bin
830830
echo 'main = putStrLn "Hello from WASM backend"' > /tmp/hello.hs
831831
_build/dist/bin/wasm32-unknown-wasi-ghc /tmp/hello.hs -o /tmp/hello.wasm
832-
# Verify the .wasm binary was produced. Runtime execution requires
833-
# JavaScript FFI glue (ghc_wasm_jsffi imports from ghc-internal),
834-
# so plain wasmtime can't run it — would need Node.js + GHC WASM
835-
# runtime shim. Compilation + linking is sufficient to validate
836-
# the cross-build.
832+
833+
# Verify the .wasm binary was produced
837834
ls -lh /tmp/hello.wasm
838835
file /tmp/hello.wasm
839836
837+
# Runtime smoke test via post-link.mjs + Node.js
838+
#
839+
# GHC WASM executables contain ghc_wasm_jsffi custom sections
840+
# (transitively from ghc-internal via base/Prelude). These require
841+
# JavaScript FFI glue at instantiation time, so plain wasmtime
842+
# can't run them. The post-link.mjs tool — an official part of
843+
# GHC's WASM backend (installed in libdir, maintained upstream at
844+
# utils/jsffi/post-link.mjs) — parses these custom sections and
845+
# generates a JavaScript ESM module providing the ghc_wasm_jsffi
846+
# imports.
847+
#
848+
# Workflow (from docs/users_guide/wasm.rst §"JSFFI"):
849+
# 1. Compile: wasm32-wasi-ghc hello.hs -o hello.wasm
850+
# 2. Post-link: $(ghc --print-libdir)/post-link.mjs -i hello.wasm -o hello.mjs
851+
# 3. Run: node runner.mjs (instantiate WASM with JSFFI + WASI imports)
852+
#
853+
# See also: utils/jsffi/test-runner.mjs for the reference implementation.
854+
WASM_LIBDIR=_build/dist/lib/targets/wasm32-unknown-wasi/lib
855+
856+
# Generate JavaScript FFI glue from WASM custom sections
857+
node "$WASM_LIBDIR/post-link.mjs" -i /tmp/hello.wasm -o /tmp/hello.mjs
858+
859+
# Instantiate WASM module with JSFFI + WASI imports, then run main.
860+
# This mirrors the knot-tying pattern from docs/users_guide/wasm.rst:
861+
# __exports starts empty, WASM is instantiated with JSFFI imports
862+
# (which capture __exports by reference), then instance exports are
863+
# assigned back into __exports before calling wasi.start().
864+
node --input-type=module -e '
865+
import { readFile } from "node:fs/promises";
866+
import { WASI } from "node:wasi";
867+
const mod = await WebAssembly.compile(await readFile("/tmp/hello.wasm"));
868+
const jsffi = (await import("/tmp/hello.mjs")).default;
869+
const wasi = new WASI({ version: "preview1", args: ["hello.wasm"] });
870+
let __exports = {};
871+
const { instance } = await WebAssembly.instantiate(mod, {
872+
ghc_wasm_jsffi: jsffi(__exports),
873+
wasi_snapshot_preview1: wasi.wasiImport,
874+
});
875+
Object.assign(__exports, instance.exports);
876+
wasi.start(instance);
877+
'
878+
840879
- name: Upload WASM cross artifacts
841880
uses: actions/upload-artifact@v4
842881
if: ${{ !cancelled() }}

0 commit comments

Comments
 (0)