Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ See docs/process.md for more on how version tagging works.
- INITIAL_MEMORY
- wasmMemory
- wasmBinary
Anybody using these will see a clear error in their debug builds signaling
that they now need to be explicitly added to `-sINCOMING_MODULE_JS_API`.
Anybody using these will see a clear error in their debug builds signaling
that they now need to be explicitly added to `-sINCOMING_MODULE_JS_API`.
- Fixed `-sWASM_ESM_INTEGRATION` builds at `-O2` and above, which previously
failed in the JS optimizer's metadce (dead code elimination) pass. metadce now
understands the native ES import/export wasm boundary that this mode emits.
(#27217)

6.0.1 - 06/22/26
----------------
Expand Down
11 changes: 11 additions & 0 deletions test/js_optimizer/applyDCEGraphRemovals-esm-output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function fd_write_impl() {}

function fd_close_impl() {}

function unused_import_impl() {}

import { memory, main as _main, used_export as _used_export, memory as wasmMemory } from "./a.out.wasm";

export { fd_write_impl as fd_write, fd_close_impl as fd_close };

export { _main };
30 changes: 30 additions & 0 deletions test/js_optimizer/applyDCEGraphRemovals-esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// WASM_ESM_INTEGRATION: unused wasm imports are dropped from the `export {..}`
// that sends JS functions to the wasm, and unused wasm exports (including
// internal ones like the indirect function table) from the `import {..}` that
// receives them, keeping the two ES module interfaces in sync.

function fd_write_impl() {
}
function fd_close_impl() {
}
function unused_import_impl() {
}

import {
memory,
__indirect_function_table,
main as _main,
used_export as _used_export,
unused_export as _unused_export,
memory as wasmMemory,
} from './a.out.wasm';

export {
fd_write_impl as fd_write,
fd_close_impl as fd_close,
unused_import_impl as unused_import,
};

export { _main };

// EXTRA_INFO: { "unusedImports": ["unused_import"], "unusedExports": ["unused_export", "__indirect_function_table"] }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function fd_write_impl() {}

function fd_close_impl() {}

import { memory, b as _main, c as _malloc, memory as wasmMemory } from "./a.out.wasm";

export { fd_write_impl as d, fd_close_impl as e };

export { _main };
26 changes: 26 additions & 0 deletions test/js_optimizer/applyImportAndExportNameChanges-esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// WASM_ESM_INTEGRATION: minified wasm import/export names are applied to the
// native ES import/export specifiers. Only the wasm-facing name of each
// specifier is renamed; the JS-local binding name is left intact (including the
// unaliased `memory`, whose local side must survive).

function fd_write_impl() {
}
function fd_close_impl() {
}

import {
memory,
main as _main,
malloc as _malloc,
memory as wasmMemory,
} from './a.out.wasm';

export {
fd_write_impl as fd_write,
fd_close_impl as fd_close,
};

// Re-export of a wasm export: the local name (_main) is never in the mapping.
export { _main };

// EXTRA_INFO: { "mapping": { "main": "b", "malloc": "c", "fd_write": "d", "fd_close": "e" } }
78 changes: 78 additions & 0 deletions test/js_optimizer/emitDCEGraph-esm-output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
[
{
"name": "emcc$defun$fd_close_impl",
"reaches": [
"emcc$defun$helper"
]
},
{
"name": "emcc$defun$fd_write_impl",
"reaches": []
},
{
"name": "emcc$defun$helper",
"reaches": []
},
{
"name": "emcc$defun$unused_import_impl",
"reaches": []
},
{
"name": "emcc$export$__indirect_function_table",
"export": "__indirect_function_table",
"reaches": []
},
{
"name": "emcc$export$_main",
"export": "main",
"reaches": [],
"root": true
},
{
"name": "emcc$export$_unused_export",
"export": "unused_export",
"reaches": []
},
{
"name": "emcc$export$_used_export",
"export": "used_export",
"reaches": [],
"root": true
},
{
"name": "emcc$export$memory",
"export": "memory",
"reaches": [],
"root": true
},
{
"name": "emcc$import$fd_close_impl",
"import": [
"env",
"fd_close"
],
"reaches": [
"emcc$defun$fd_close_impl"
]
},
{
"name": "emcc$import$fd_write_impl",
"import": [
"env",
"fd_write"
],
"reaches": [
"emcc$defun$fd_write_impl"
]
},
{
"name": "emcc$import$unused_import_impl",
"import": [
"env",
"unused_import"
],
"reaches": [
"emcc$defun$unused_import_impl"
]
}
]
43 changes: 43 additions & 0 deletions test/js_optimizer/emitDCEGraph-esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// WASM_ESM_INTEGRATION: the wasm<->JS boundary is expressed with native ES
// import/export syntax rather than the `wasmImports` object and
// `wasmExports['x']` member uses, and emitDCEGraph must build the same graph
// from it.

// JS functions implementing wasm imports.
function fd_write_impl() {
}
function unused_import_impl() {
}

// A JS function only reachable from a wasm import edge.
function helper() {
}
function fd_close_impl() {
helper();
}

// wasm exports received as ES imports. `memory` is imported twice (plain and
// aliased) and must map to a single export node.
import {
memory,
__indirect_function_table,
main as _main,
used_export as _used_export,
unused_export as _unused_export,
memory as wasmMemory,
} from './a.out.wasm';

// JS functions exported to the wasm module (the wasm imports).
export {
fd_write_impl as fd_write,
fd_close_impl as fd_close,
unused_import_impl as unused_import,
};

// Re-export of a wasm export to the JS entry point: a top-level use that should
// root the underlying `main` export, not be treated as a wasm import.
export { _main };

// Top-level uses: root the memory export (via the alias) and one wasm export.
wasmMemory.buffer;
_used_export();
26 changes: 26 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3048,10 +3048,13 @@ def test_extern_prepost(self):
'emitDCEGraph-sig': (['emitDCEGraph', '--no-print'],),
'emitDCEGraph-prefixing': (['emitDCEGraph', '--no-print'],),
'emitDCEGraph-scopes': (['emitDCEGraph', '--no-print'],),
'emitDCEGraph-esm': (['emitDCEGraph', '--no-print', '--export-es6'],),
'minimal-runtime-applyDCEGraphRemovals': (['applyDCEGraphRemovals'],),
'applyDCEGraphRemovals': (['applyDCEGraphRemovals'],),
'applyDCEGraphRemovals-esm': (['applyDCEGraphRemovals', '--export-es6'],),
'applyImportAndExportNameChanges': (['applyImportAndExportNameChanges'],),
'applyImportAndExportNameChanges2': (['applyImportAndExportNameChanges'],),
'applyImportAndExportNameChanges-esm': (['applyImportAndExportNameChanges', '--export-es6'],),
'minimal-runtime-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
'minimal-runtime-2-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
'standalone-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
Expand Down Expand Up @@ -12237,6 +12240,29 @@ def test_metadce_wasm2js_i64(self):
}''')
self.do_runf('src.c', cflags=['-O3', '-sWASM=0'])

def test_metadce_esm_integration(self):
# Regression test for https://github.com/emscripten-core/emscripten/issues/27217.
# Under WASM_ESM_INTEGRATION the wasm<->JS boundary is expressed with native
# ES import/export syntax rather than the `wasmImports` object and
# `wasmExports['x']` member uses. metadce (which runs at -O2 and above) must
# understand that form rather than asserting that it cannot find the
# `wasmImports` assignment.
self.run_process([EMCC, test_file('hello_world.c'), '-O3',
'-sWASM_ESM_INTEGRATION', '-sEXPORT_ES6', '-Wno-experimental',
'-o', 'hello.mjs'])
support = read_file('hello.support.mjs')
# An unused wasm export (here the indirect function table) must be removed
# from both the wasm and the ES import that receives it, so the two module
# interfaces stay in sync.
self.assertNotContained('__indirect_function_table', support)
# The import module name is rewritten to the wasm module and is not minified
# (every import resolves through it), and the re-export of the user `main`
# export is preserved.
self.assertContained('from"./hello.wasm"', support)
self.assertContained('export{_main}', support)
if self.try_require_node_version(25, 0, 0):
self.assertContained('Hello, world!', self.run_js('hello.mjs'))

@crossplatform
def test_deterministic(self):
# test some things that may not be nondeterministic
Expand Down
Loading