Skip to content

Teach metadce about the WASM_ESM_INTEGRATION module boundary#27218

Open
guybedford wants to merge 1 commit into
emscripten-core:mainfrom
guybedford:metadce-esm-integration
Open

Teach metadce about the WASM_ESM_INTEGRATION module boundary#27218
guybedford wants to merge 1 commit into
emscripten-core:mainfrom
guybedford:metadce-esm-integration

Conversation

@guybedford

Copy link
Copy Markdown
Collaborator

This makes -sWASM_ESM_INTEGRATION work at -O2 and above. Previously such builds failed in the JS optimizer's metadce (dead code elimination) pass with:

AssertionError: could not find the assignment to "wasmImports".

The reason is that metadce reconstructs the wasm⇄JS def/use graph by pattern-matching the idioms a normal build emits — the wasmImports object literal and wasmExports['x'] member uses. Under ESM integration those don't exist: the boundary is expressed with native ES module syntax instead, which is exactly the simpler, tooling-visible def/use graph this mode was designed to produce:

// wasm exports received by the JS:
import { main as _main, memory as wasmMemory } from './a.out.wasm';
// JS functions exported to the wasm:
export { _fd_write as fd_write };

So rather than synthesise the old shapes, metadce now reads the real module bindings directly.

  • emitDCEGraph recognises import {..} from './x.wasm' as wasm export nodes (collapsing aliases such as memory/wasmMemory onto a single node) and export { js as wasmName } as wasm import edges, while leaving re-exports (export { _main }) to root naturally.

  • applyDCEGraphRemovals prunes unused specifiers from those same import/export statements.

  • applyImportAndExportNameChanges applies binaryen's minified names to the wasm-facing side of each specifier, e.g.

    import { malloc as _malloc } from './a.out.wasm';
    export { _fd_write as fd_write };

    becomes

    import { c as _malloc } from './a.out.wasm';
    export { _fd_write as d };

    The local binding is preserved (an unaliased memory becomes b as memory, not a broken b).

Two supporting changes keep the two module interfaces in sync:

  • building.py — under ESM integration any export binaryen drops (including internal ones like the indirect function table) is also dropped from the JS import, so the JS never imports something the wasm no longer exports.
  • link.py — import/export name minification stays on for ESM, but import module name minification is disabled, since every import is rewritten to the single support-module source by create_esm_wrapper anyway (and minifying enva would break that rewrite).

Test coverage:

  • New js_optimizer fixtures exercising each pass on ESM-shaped input: emitDCEGraph-esm, applyDCEGraphRemovals-esm, applyImportAndExportNameChanges-esm.
  • New end-to-end other.test_metadce_esm_integration building hello_world.c with -O3 -sWASM_ESM_INTEGRATION, asserting the dangling table import is removed and names are minified consistently (the run is guarded on Node 25).

Resolves #27217.

Made with AI assistance under my review

Building with -sWASM_ESM_INTEGRATION at -O2 or above failed in the JS
optimizer's metadce pass with 'could not find the assignment to
"wasmImports"'. In this mode the wasm<->JS boundary is expressed with
native ES import/export syntax rather than the wasmImports object and
wasmExports['x'] member uses that metadce pattern-matches.

Teach the three metadce-related acorn-optimizer passes about the ES form:
- emitDCEGraph reads 'import {..} from "./x.wasm"' as wasm export nodes
  (collapsing aliases such as memory/wasmMemory to one node) and
  'export { js as wasmName }' as wasm import edges, leaving re-exports
  (export { _main }) to root naturally.
- applyDCEGraphRemovals prunes unused specifiers from those statements.
- applyImportAndExportNameChanges applies minified names to the
  wasm-facing side of each specifier.

building.py also drops dropped exports (including internal ones like the
indirect function table) from the JS import to keep the two interfaces
in sync, and link.py keeps import/export name minification on for ESM
while disabling the now-pointless import module name minification.
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.

-sWASM_ESM_INTEGRATION incompatibility with metadce

1 participant