Skip to content

Preserve the known rejection reason for discovered but untranslated WASM modules #1617

Closed

Description

@jlucaso1

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.json

Node 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
./repro

The 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

  1. added a commit that references this issue on Oct 21, 2026
    f168402
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions