Repository navigation
Preserve the known rejection reason for discovered but untranslated WASM modules #1617
Description
A module that the build finds and rejects for SIMD produces the same runtime explanation as bytes the build never saw. The failure is expected because SIMD is outside the documented WASM feature set. The diagnostic loses the reason the compiler already knows, which makes a feature limitation look like an asset-discovery problem.
The original investigation tested this natively with the official scriptc 0.4.0 Linux x64 glibc archive. Node: 24.19.0; host: Linux x86_64, Debian glibc 2.41. On main 94aa7065f17fdeeb31094ffcb71885b0d671b5ba, the original investigation confirmed the same coverage classification and the unchanged source path that drops this information. No main-ABI native executable was run.
Standalone reproduction
This is a valid 29-byte WASM module with i32.const 0, i8x16.splat, and drop. It has no imports or sidecar files. Save as repro.ts:
const bytes = Buffer.from('AGFzbQEAAAABBAFgAAADAgEACgkBBwBBAP0PGgs=', 'base64')
console.log(WebAssembly.validate(bytes))
new WebAssembly.Module(bytes)In an empty directory, use this package.json and run npm install:
{"private":true,"type":"module","devDependencies":{"scriptc":"0.4.0","@types/node":"26.1.1"}}Use this tsconfig.json:
{"compilerOptions":{"strict":true,"target":"ESNext","module":"ESNext","moduleResolution":"Bundler"}}Then run:
node repro.ts
./node_modules/.bin/scriptc coverage repro.ts --print diagnostics > coverage.json
./node_modules/.bin/scriptc build repro.ts --print native-link-info -o repro.o > link.jsonNode prints true and exits 0. Coverage identifies the exact inline bytes:
{
"sha256": "ad9e35963986d455947710f374671b307caeaec42ba7fb9ee271e2acc3df28a9",
"size": 29,
"status": "unsupported",
"unsupported": ["simd"]
}With clang installed, link the object using the reported inputs and flags:
node --input-type=module <<'JS'
import { readFileSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
const r = JSON.parse(readFileSync('link.json', 'utf8'))
if (r.compiler_version !== '0.4.0' || r.target.name !== 'linux-x64-gnu') {
throw new Error('Unexpected compiler version or target')
}
const link = r.link
const result = spawnSync('clang', [
...link.driver_flags, ...link.input_order,
...link.system_libraries.map(name => '-l' + name), '-o', 'repro'
], { stdio: 'inherit' })
if (result.error) throw result.error
if (result.status !== 0) process.exit(result.status ?? 1)
JS
./reproThe executable prints false, then exits 1 with:
Uncaught CompileError: WebAssembly.Module(): this module was not translated when the program was built (scriptc compiles WebAssembly ahead of time and cannot compile modules whose bytes it did not see; see https://scriptc.dev/limitations)
Here the build did see the bytes. Coverage even gives their hash and unsupported feature.
Cause
In lowerer.ts, discovery returns the module and its status, but the build skips every report whose status is not valid. Only translated modules enter the emitted registry.
At runtime, scr_wasm_lookup searches that registry. A miss reaches the single scr_wasm_not_translated message. It cannot distinguish a known unsupported module from an undiscovered one because the rejected module's metadata was not retained.
Proposed change
Keep a small hash-indexed record for discovered but untranslated modules, including their unsupported features or validation error. On a registry miss, check that record before using the generic unseen-bytes message. For this example the error could say that the module was found during the build but was not translated because it uses SIMD.
This should stay a catchable CompileError, and validate should retain its current result. A package such as undici may intentionally catch the failure and use a scalar fallback, so failing the whole build for every unsupported module would change useful behavior.
A regression should pair this known-SIMD module with a module whose bytes really were unavailable at build time and assert distinct reasons. This investigation establishes discovery, the native error and the code path that loses the metadata. No extra registry was implemented, and no SIMD support is claimed.
The publication recheck covered open and closed issues and PRs for CompileError, not translated, SIMD and unsupported WebAssembly modules. #1395 documents the current fallback behavior, but no equivalent report or pending change was found for preserving this diagnostic reason. The real-world context was oxidezap/baileyrs#176; the reproduction above does not depend on it.
Verification on 2026-10-21
The isolated publication recheck used Linux x86_64 WSL (Arch Linux, glibc 2.44), Node v26.12.0, scriptc@0.4.0 (tag commit 4bc8a4711c358ca09edaa758f5efd296bc6d2730) and @types/node@26.1.1. An unmodified source build of main at 4dd62802c85f77739f7b69dc491758c4cda6f3e5 confirmed the same WASM coverage classification. It was installed with pnpm 11.28.6 using pnpm install --frozen-lockfile --ignore-scripts and built with pnpm -r build; it still reports version 0.4.0, so the SHA identifies it.
Earlier results at 94aa7065f come from the pinned investigation. The v0.4.0 executable linked with clang printed false and exited 1 with the error above. No main native executable was run.
Activity
Name the reason in the CompileError for untranslated WebAssembly modules