From 6f35636761e20d636bfc363ea092c7bef34b2a92 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 10 Jun 2026 18:16:23 -0700 Subject: [PATCH] Automatically use toResizableBuffer when available at runtime Since this feature is now supported by all major browsers, we want to take advantage of it whenever possible to improve memory growth efficiency. We keep the `GROWABLE_ARRAYBUFFERS` compile-time setting to allow forcing its use and removing the fallback code, which saves some code size and avoids runtime checks. Fixes #27084 --- ChangeLog.md | 5 ++ .../tools_reference/settings_reference.rst | 11 +++-- src/runtime_common.js | 46 +++++++++++++++++-- src/settings.js | 11 +++-- test/codesize/test_codesize_mem_O3_grow.json | 8 ++-- .../test_codesize_mem_O3_grow_standalone.json | 8 ++-- ...t_codesize_minimal_pthreads_memgrowth.json | 8 ++-- test/embind/embind_test.cpp | 23 ++++++++-- test/test_browser.py | 1 - 9 files changed, 95 insertions(+), 26 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ec027ce631917..81f3a08e4d1f6 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,6 +33,11 @@ See docs/process.md for more on how version tagging works. out to `wasm-bindgen` in the users's path and integrate the wasm-bindgen JS with the normal Emscripten JS. Some wasm-bindgen features may not yet be fully supported. (#23493) +- Emscripten will now automatically use the `Memory.toResizableBuffer` method + when available at runtime. This was previously only used when + `GROWABLE_ARRAYBUFFERS` was explicitly enabled. Enabling + `GROWABLE_ARRAYBUFFERS` can still by useful as it removes the runtime fallback + code, avoiding its overhead in multi-threaded builds. (#27096) 6.0.0 - 06/04/26 ---------------- diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index f26b0aa33b9a9..46909927a7d36 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -3342,9 +3342,14 @@ Default value: false GROWABLE_ARRAYBUFFERS ===================== -Enable support for GrowableSharedArrayBuffer. -This feature has only recently become available across major browser engines -and Node.js. +Enable unconditional support for growable views of Wasm memory. +This is a recent Web platform feature that can make growing the Wasm memory +more efficient, especially in multi-threaded builds. +Note that Emscripten will always take advantage of this feature when it is +available, regardless of this setting. Enabling this setting effectively +removes the fallback code (which adds overhead in multi-threaded builds). +Only enable this setting if you know that all of your target browser +engines support this feature. Default value: false diff --git a/src/runtime_common.js b/src/runtime_common.js index 23a3915cb14cc..c905bb932f778 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -108,17 +108,55 @@ var runtimeExited = false; }; }}} + +#if ALLOW_MEMORY_GROWTH +// When ALLOW_MEMORY_GROWTH is enabled, the conversion from Wasm +// memory to ArrayBuffer requires some additional logic. +function getMemoryBuffer() { +#if GROWABLE_ARRAYBUFFERS + return wasmMemory.toResizableBuffer(); +#else +#if SHARED_MEMORY && (MIN_FIREFOX_VERSION != TARGET_NOT_SUPPORTED) + // Using `toResizableBuffer` on a shared memory is currently broken on Firefox + // See: https://github.com/emscripten-core/emscripten/issues/27118 + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=2021136 + if (!globalThis.navigator?.userAgent?.match(/firefox/i)) { +#endif + try { + // This method may be missing or could fail with `Memory must have a maximum` + var b = wasmMemory.toResizableBuffer(); +#if SHARED_MEMORY + growMemViews = () => {}; +#endif + return b; + + } catch {} +#if SHARED_MEMORY && (MIN_FIREFOX_VERSION != TARGET_NOT_SUPPORTED) + } +#endif + return wasmMemory.buffer; +#endif // GROWABLE_ARRAYBUFFERS +} +#endif // ALLOW_MEMORY_GROWTH + function updateMemoryViews() { #if RUNTIME_DEBUG dbg(`updateMemoryViews: first=${!HEAP8} size=${wasmMemory.buffer.byteLength}`); #endif -#if !ALLOW_MEMORY_GROWTH && ASSERTIONS +#if ALLOW_MEMORY_GROWTH + // If we already have a heap that is resizeable/growable buffer we don't + // need to do anything in updateMemoryViews. +#if SHARED_MEMORY + if (HEAP8?.buffer?.growable) return; +#else + if (HEAP8?.buffer?.resizable) return; +#endif + var b = getMemoryBuffer(); +#else +#if ASSERTIONS // When memory growth is disabled this function should be called exactly once. assert(!HEAP8, 'updateMemoryViews should only be called once when ALLOW_MEMORY_GROWTH=0'); #endif -#if GROWABLE_ARRAYBUFFERS - var b = wasmMemory.toResizableBuffer(); -#else var b = wasmMemory.buffer; #endif {{{ maybeExportHeap('HEAP8') }}}HEAP8 = new Int8Array(b); diff --git a/src/settings.js b/src/settings.js index 1f280f49d91af..21a804b1b6389 100644 --- a/src/settings.js +++ b/src/settings.js @@ -2193,9 +2193,14 @@ var WASM_ESM_INTEGRATION = false; // [link] var JS_BASE64_API = false; -// Enable support for GrowableSharedArrayBuffer. -// This feature has only recently become available across major browser engines -// and Node.js. +// Enable unconditional support for growable views of Wasm memory. +// This is a recent Web platform feature that can make growing the Wasm memory +// more efficient, especially in multi-threaded builds. +// Note that Emscripten will always take advantage of this feature when it is +// available, regardless of this setting. Enabling this setting effectively +// removes the fallback code (which adds overhead in multi-threaded builds). +// Only enable this setting if you know that all of your target browser +// engines support this feature. // [link] var GROWABLE_ARRAYBUFFERS = false; diff --git a/test/codesize/test_codesize_mem_O3_grow.json b/test/codesize/test_codesize_mem_O3_grow.json index 7a4aeb02b1239..6a62acd90c502 100644 --- a/test/codesize/test_codesize_mem_O3_grow.json +++ b/test/codesize/test_codesize_mem_O3_grow.json @@ -1,10 +1,10 @@ { - "a.out.js": 4541, - "a.out.js.gz": 2193, + "a.out.js": 4612, + "a.out.js.gz": 2224, "a.out.nodebug.wasm": 5261, "a.out.nodebug.wasm.gz": 2419, - "total": 9802, - "total_gz": 4612, + "total": 9873, + "total_gz": 4643, "sent": [ "a (emscripten_resize_heap)" ], diff --git a/test/codesize/test_codesize_mem_O3_grow_standalone.json b/test/codesize/test_codesize_mem_O3_grow_standalone.json index 7d36e73aa01a5..ef8a619b7ab86 100644 --- a/test/codesize/test_codesize_mem_O3_grow_standalone.json +++ b/test/codesize/test_codesize_mem_O3_grow_standalone.json @@ -1,10 +1,10 @@ { - "a.out.js": 4012, - "a.out.js.gz": 1933, + "a.out.js": 4077, + "a.out.js.gz": 1966, "a.out.nodebug.wasm": 5641, "a.out.nodebug.wasm.gz": 2659, - "total": 9653, - "total_gz": 4592, + "total": 9718, + "total_gz": 4625, "sent": [ "args_get", "args_sizes_get", diff --git a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json index ad0524a4dd8a1..a8ac15b6a830c 100644 --- a/test/codesize/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/codesize/test_codesize_minimal_pthreads_memgrowth.json @@ -1,10 +1,10 @@ { - "a.out.js": 7357, - "a.out.js.gz": 3627, + "a.out.js": 7514, + "a.out.js.gz": 3685, "a.out.nodebug.wasm": 19064, "a.out.nodebug.wasm.gz": 8804, - "total": 26421, - "total_gz": 12431, + "total": 26578, + "total_gz": 12489, "sent": [ "a (memory)", "b (exit)", diff --git a/test/embind/embind_test.cpp b/test/embind/embind_test.cpp index a0862f653cfe6..1c8ca6e937419 100644 --- a/test/embind/embind_test.cpp +++ b/test/embind/embind_test.cpp @@ -228,10 +228,27 @@ void force_memory_growth() { assert(val::global("oldheap")["byteLength"].as() == old_size); emscripten_resize_heap(old_size + EMSCRIPTEN_PAGE_SIZE); assert(emscripten_get_heap_size() > old_size); - // HEAP8 on the module should now be rebound, and our oldheap should be - // detached + // HEAP8 on the module should always be correct after the resize. + // Our oldheap reference may be detached, depending on whether the + // buffer is resizable. assert(val::module_property("HEAP8")["byteLength"].as() > old_size); - assert(val::global("oldheap")["byteLength"].as() == 0); + + val oldheap = val::global("oldheap"); + val buffer = oldheap["buffer"]; + bool growable = false; + if (!buffer.isUndefined()) { + if (!buffer["resizable"].isUndefined()) { + growable = buffer["resizable"].as(); + } else if (!buffer["growable"].isUndefined()) { + growable = buffer["growable"].as(); + } + } + + if (growable) { + assert(oldheap["byteLength"].as() == emscripten_get_heap_size()); + } else { + assert(oldheap["byteLength"].as() == 0); + } } std::string emval_test_take_and_return_const_char_star(const char* str) { diff --git a/test/test_browser.py b/test/test_browser.py index a0fe347344b2b..8f395e5d04ab6 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -5696,7 +5696,6 @@ def test_binary_encode(self, extra): def test_shell_minimal(self, args): self.btest_exit('browser_test_hello_world.c', cflags=['--shell-file', path_from_root('html/shell_minimal.html')] + args) - @no_chrome('https://github.com/emscripten-core/emscripten/issues/27084') def test_pthread_memgrowth_stale_views(self): self.btest_exit('test_pthread_memgrowth_stale_views.c', cflags=['-pthread', '-sALLOW_MEMORY_GROWTH', '-Wno-pthreads-mem-growth'])