From 26eaedc2f8115cab679460827094e8b98e51d391 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 24 Mar 2026 11:24:04 +0100 Subject: [PATCH 1/8] [WasmFS] Make the Node backend optional for web builds Previously, the WasmFS Node backend assumed Node.js was always available. This change allows building binaries that include Node backend stubs, so they can run in web environments. --- src/lib/libwasmfs_node.js | 27 +++++++++++++++++++++++---- test/test_other.py | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 02295c10a164f..5de2c8b8920f2 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -5,7 +5,26 @@ */ addToLibrary({ - $wasmfsNodeIsWindows: !!process.platform.match(/^win/), +#if !ENVIRONMENT_MAY_BE_NODE + _wasmfs_node_readdir: (path_p, vec) => {}, + _wasmfs_node_get_mode: (path_p, mode_p) => {}, + _wasmfs_node_stat_size: (path_p, size_p) => {}, + _wasmfs_node_fstat_size: (fd, size_p) => {}, + _wasmfs_node_insert_file: (path_p, mode) => {}, + _wasmfs_node_insert_directory: (path_p, mode) => {}, + _wasmfs_node_unlink: (path_p) => {}, + _wasmfs_node_rmdir: (path_p) => {}, + _wasmfs_node_truncate: (path_p, len) => {}, + _wasmfs_node_ftruncate: (fd, len) => {}, + _wasmfs_node_open: (path_p, flags_p) => {}, + _wasmfs_node_rename: (from_path_p, to_path_p) => {}, + _wasmfs_node_symlink: (target_path_p, linkpath_path_p) => {}, + _wasmfs_node_readlink: (path_p, target_p, bufsize) => {}, + _wasmfs_node_close: (fd) => {}, + _wasmfs_node_read: (fd, buf_p, len, pos, nread_p) => {}, + _wasmfs_node_write: (fd, buf_p, len, pos, nwritten_p) => {}, +#else + $wasmfsNodeIsWindows: "!!process.platform.match(/^win/)", $wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'], $wasmfsNodeConvertNodeCode: (e) => { @@ -29,8 +48,8 @@ addToLibrary({ $wasmfsNodeFixStat__deps: ['$wasmfsNodeIsWindows'], $wasmfsNodeFixStat: (stat) => { if (wasmfsNodeIsWindows) { - // Node.js on Windows never represents permission bit 'x', so - // propagate read bits to execute bits + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2; } return stat; @@ -222,5 +241,5 @@ addToLibrary({ // implicitly return 0 }); }, - +#endif }); diff --git a/test/test_other.py b/test/test_other.py index f5fab92327817..94acc2d96d1f8 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -9301,6 +9301,26 @@ def test_noderawfs_access_abspath(self): ''') self.do_runf('access.c', cflags=['-sNODERAWFS'], args=[os.path.abspath('foo')]) + def test_wasmfs_nodefs_stubs(self): + # This is equivalent to building with `-sWASMFS -sNODERAWFS`, except that the Wasm + # binary can also be used on the web. Ensure that this use case is supported. + create_file('main.c', r''' + #include + #include + + EM_JS(bool, is_node, (), { return ENVIRONMENT_IS_NODE; }); + + backend_t wasmfs_create_root_dir() { + return is_node() ? wasmfs_create_node_backend("") + : wasmfs_create_memory_backend(); + } + + int main(int argc, char** argv) { + return 0; + } + ''') + self.run_process([EMCC, 'main.c', '-sWASMFS', '-sENVIRONMENT=web']) + def test_noderawfs_readfile_prerun(self): create_file('foo', 'bar') self.add_pre_run("console.log(FS.readFile('foo', { encoding: 'utf8' }));") From d30f7dea17b16ec2d8ccaf437fa68fe4b932a8db Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 18:49:34 +0200 Subject: [PATCH 2/8] Incorporate feedback --- src/lib/libwasmfs_node.js | 50 +++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 5de2c8b8920f2..7d41daab3faee 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -4,26 +4,7 @@ * SPDX-License-Identifier: MIT */ -addToLibrary({ -#if !ENVIRONMENT_MAY_BE_NODE - _wasmfs_node_readdir: (path_p, vec) => {}, - _wasmfs_node_get_mode: (path_p, mode_p) => {}, - _wasmfs_node_stat_size: (path_p, size_p) => {}, - _wasmfs_node_fstat_size: (fd, size_p) => {}, - _wasmfs_node_insert_file: (path_p, mode) => {}, - _wasmfs_node_insert_directory: (path_p, mode) => {}, - _wasmfs_node_unlink: (path_p) => {}, - _wasmfs_node_rmdir: (path_p) => {}, - _wasmfs_node_truncate: (path_p, len) => {}, - _wasmfs_node_ftruncate: (fd, len) => {}, - _wasmfs_node_open: (path_p, flags_p) => {}, - _wasmfs_node_rename: (from_path_p, to_path_p) => {}, - _wasmfs_node_symlink: (target_path_p, linkpath_path_p) => {}, - _wasmfs_node_readlink: (path_p, target_p, bufsize) => {}, - _wasmfs_node_close: (fd) => {}, - _wasmfs_node_read: (fd, buf_p, len, pos, nread_p) => {}, - _wasmfs_node_write: (fd, buf_p, len, pos, nwritten_p) => {}, -#else +var WasmFSNodeLibrary = { $wasmfsNodeIsWindows: "!!process.platform.match(/^win/)", $wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'], @@ -241,5 +222,32 @@ addToLibrary({ // implicitly return 0 }); }, +}; + +#if !ENVIRONMENT_MAY_BE_NODE +function makeStub(x, library) { + if (isJsOnlySymbol(x) || isDecorator(x)) { + return; + } + + var t = library[x]; + if (typeof t == 'string') return; + t = t.toString(); + + delete library[x + '__i53abi']; + delete library[x + '__deps']; + t = modifyJSFunction(t, (args, body) => { + return `(${args}) => {\n` + + (ASSERTIONS ? "abort('wasmfs::NodeBackend is no-op when !ENVIRONMENT_MAY_BE_NODE');\n" : '') + + '}'; + }); + + library[x] = eval(`(${t})`); +} + +for (var x in WasmFSNodeLibrary) { + makeStub(x, WasmFSNodeLibrary); +} #endif -}); + +addToLibrary(WasmFSNodeLibrary); From eea0053e49ca837b154e4eac5dccba617e6aa877 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 19:00:21 +0200 Subject: [PATCH 3/8] Adjust error message --- src/lib/libwasmfs_node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 7d41daab3faee..ddaa45511dce5 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -238,7 +238,7 @@ function makeStub(x, library) { delete library[x + '__deps']; t = modifyJSFunction(t, (args, body) => { return `(${args}) => {\n` + - (ASSERTIONS ? "abort('wasmfs::NodeBackend is no-op when !ENVIRONMENT_MAY_BE_NODE');\n" : '') + + (ASSERTIONS ? "abort('attempt to call Node.js backend function without ENVIRONMENT_MAY_BE_NODE');\n" : '') + '}'; }); From 4143136f6898ba18282728d54873eded052fd5fa Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 19:04:10 +0200 Subject: [PATCH 4/8] Remove unnecessary `eval()` --- src/lib/libwasmfs_node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index ddaa45511dce5..739a6bde034c9 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -242,7 +242,7 @@ function makeStub(x, library) { '}'; }); - library[x] = eval(`(${t})`); + library[x] = t; } for (var x in WasmFSNodeLibrary) { From 4b86c1cb108931c41b3e65985641147ef43f45ba Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 19:40:50 +0200 Subject: [PATCH 5/8] Avoid empty `main()` This avoids filesystem related functions from being DCE'd when building with `-sASSERTIONS=0` or `-O3`. --- test/test_other.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_other.py b/test/test_other.py index 94acc2d96d1f8..b5a762ca20e37 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -9305,6 +9305,10 @@ def test_wasmfs_nodefs_stubs(self): # This is equivalent to building with `-sWASMFS -sNODERAWFS`, except that the Wasm # binary can also be used on the web. Ensure that this use case is supported. create_file('main.c', r''' + #include + #include + #include + #include #include @@ -9316,6 +9320,9 @@ def test_wasmfs_nodefs_stubs(self): } int main(int argc, char** argv) { + printf("testing access to %s\n", argv[1]); + int rtn = access(argv[1], F_OK); + assert(rtn == 0); return 0; } ''') From a2ab1452e00e1534a1ebb5cf744ac2223feb0b58 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 19:41:32 +0200 Subject: [PATCH 6/8] Incorporate feedback --- src/lib/libwasmfs_node.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 739a6bde034c9..69ca3946f031c 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -var WasmFSNodeLibrary = { +var wasmFSNodeLibrary = { $wasmfsNodeIsWindows: "!!process.platform.match(/^win/)", $wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'], @@ -236,18 +236,16 @@ function makeStub(x, library) { delete library[x + '__i53abi']; delete library[x + '__deps']; - t = modifyJSFunction(t, (args, body) => { + library[x] = modifyJSFunction(t, (args, body) => { return `(${args}) => {\n` + (ASSERTIONS ? "abort('attempt to call Node.js backend function without ENVIRONMENT_MAY_BE_NODE');\n" : '') + '}'; }); - - library[x] = t; } -for (var x in WasmFSNodeLibrary) { - makeStub(x, WasmFSNodeLibrary); +for (var x in wasmFSNodeLibrary) { + makeStub(x, wasmFSNodeLibrary); } #endif -addToLibrary(WasmFSNodeLibrary); +addToLibrary(wasmFSNodeLibrary); From 112bd63a0d86dca2f402c1d509d5ec68cdbb7e11 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 19:52:27 +0200 Subject: [PATCH 7/8] Cherry-pick from PR #26609 --- src/lib/libwasmfs_node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/libwasmfs_node.js b/src/lib/libwasmfs_node.js index 69ca3946f031c..66bfb2cce604d 100644 --- a/src/lib/libwasmfs_node.js +++ b/src/lib/libwasmfs_node.js @@ -243,8 +243,8 @@ function makeStub(x, library) { }); } -for (var x in wasmFSNodeLibrary) { - makeStub(x, wasmFSNodeLibrary); +for (const name of Object.keys(wasmFSNodeLibrary)) { + makeStub(name, wasmFSNodeLibrary); } #endif From d0c6c6360d3279f57b4eca04656318847724b73f Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 2 Apr 2026 20:18:46 +0200 Subject: [PATCH 8/8] Remove superfluous test It became superfluous after commit d30f7dea17b16ec2d8ccaf437fa68fe4b932a8db. --- test/test_other.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/test/test_other.py b/test/test_other.py index b5a762ca20e37..f5fab92327817 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -9301,33 +9301,6 @@ def test_noderawfs_access_abspath(self): ''') self.do_runf('access.c', cflags=['-sNODERAWFS'], args=[os.path.abspath('foo')]) - def test_wasmfs_nodefs_stubs(self): - # This is equivalent to building with `-sWASMFS -sNODERAWFS`, except that the Wasm - # binary can also be used on the web. Ensure that this use case is supported. - create_file('main.c', r''' - #include - #include - #include - - #include - #include - - EM_JS(bool, is_node, (), { return ENVIRONMENT_IS_NODE; }); - - backend_t wasmfs_create_root_dir() { - return is_node() ? wasmfs_create_node_backend("") - : wasmfs_create_memory_backend(); - } - - int main(int argc, char** argv) { - printf("testing access to %s\n", argv[1]); - int rtn = access(argv[1], F_OK); - assert(rtn == 0); - return 0; - } - ''') - self.run_process([EMCC, 'main.c', '-sWASMFS', '-sENVIRONMENT=web']) - def test_noderawfs_readfile_prerun(self): create_file('foo', 'bar') self.add_pre_run("console.log(FS.readFile('foo', { encoding: 'utf8' }));")