diff --git a/.circleci/config.yml b/.circleci/config.yml index cdf414a8c7c00..43bea6f53b48a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -985,6 +985,7 @@ jobs: other.test_node_emscripten_num_logical_cores other.test_js_base64_api other.test_pthread_growth* + other.test_memory_growth core2.test_hello_world core2.test_pthread_create core2.test_i64_invoke_bigint diff --git a/ChangeLog.md b/ChangeLog.md index ec027ce631917..bf316164f070c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,6 +33,9 @@ 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 auto-detect and use the `toResizableBuffer` method to + avoid the need for heap view resizing on memory growth. This was previously + only enabled when `GROWABLE_ARRAYBUFFERS` was explicitly enabled. (#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..bd2e301133cb5 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -3344,7 +3344,9 @@ GROWABLE_ARRAYBUFFERS Enable support for GrowableSharedArrayBuffer. This feature has only recently become available across major browser engines -and Node.js. +and Node.js. Even when this setting is disabled, Emscripten will still use this +feature when available, but it include the fall back code for handling +non-growable memories. Default value: false diff --git a/src/runtime_common.js b/src/runtime_common.js index 23a3915cb14cc..700f023c4bc15 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -108,17 +108,53 @@ 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) + // BUG REFERENCE HERE + if (!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 + } +#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?.growable) return; +#else + if (HEAP8?.resizeable) 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..8390a0bb0d723 100644 --- a/src/settings.js +++ b/src/settings.js @@ -2195,7 +2195,9 @@ var JS_BASE64_API = false; // Enable support for GrowableSharedArrayBuffer. // This feature has only recently become available across major browser engines -// and Node.js. +// and Node.js. Even when this setting is disabled, Emscripten will still use this +// feature when available, but it include the fall back code for handling +// non-growable memories. // [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..72125e37daacd 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": 4604, + "a.out.js.gz": 2220, "a.out.nodebug.wasm": 5261, "a.out.nodebug.wasm.gz": 2419, - "total": 9802, - "total_gz": 4612, + "total": 9865, + "total_gz": 4639, "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..249811b81c74a 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": 4069, + "a.out.js.gz": 1962, "a.out.nodebug.wasm": 5641, "a.out.nodebug.wasm.gz": 2659, - "total": 9653, - "total_gz": 4592, + "total": 9710, + "total_gz": 4621, "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..77b1abd737e49 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": 7493, + "a.out.js.gz": 3682, "a.out.nodebug.wasm": 19064, "a.out.nodebug.wasm.gz": 8804, - "total": 26421, - "total_gz": 12431, + "total": 26557, + "total_gz": 12486, "sent": [ "a (memory)", "b (exit)", diff --git a/test/common.py b/test/common.py index ca1afe9cd8c42..98e71bcac81b1 100644 --- a/test/common.py +++ b/test/common.py @@ -332,6 +332,14 @@ def get_deno(): return get_engine(engine_is_deno) +def check_node_version(major, minor=0, revision=0): + nodejs = get_nodejs() + if not nodejs: + return False + version = shared.get_node_version(nodejs) + return version >= (major, minor, revision) + + def clean_js_output(output): """Cleanup the JS output prior to running verification steps on it. @@ -543,14 +551,10 @@ def require_wasm64(self): self.fail('either d8, node >= 24 or deno required to run wasm64 tests. Use EMTEST_SKIP_WASM64 to skip') def try_require_node_version(self, major, minor=0, revision=0): - nodejs = get_nodejs() - if not nodejs: - return False - version = shared.get_node_version(nodejs) - if version < (major, minor, revision): + if not check_node_version(major, minor, revision): return False - self.require_engine(nodejs) + self.require_engine(get_nodejs()) return True def require_wasm_legacy_eh(self): diff --git a/test/embind/embind_test.cpp b/test/embind/embind_test.cpp index a0862f653cfe6..1d3b949e24e32 100644 --- a/test/embind/embind_test.cpp +++ b/test/embind/embind_test.cpp @@ -228,10 +228,23 @@ 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 resizable = false; + if (!buffer.isUndefined() && !buffer["resizable"].isUndefined()) { + resizable = buffer["resizable"].as(); + } + + if (resizable) { + 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']) diff --git a/test/test_other.py b/test/test_other.py index 37334dcf5dfcd..970d91503295c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -42,6 +42,7 @@ TEST_ROOT, WEBIDL_BINDER, RunnerCore, + check_node_version, copy_asset, copytree, create_file, @@ -6425,22 +6426,39 @@ def test_file_packager_huge(self): self.assertContained(MESSAGE, err) self.clear() + @crossplatform @also_with_wasm2js def test_memory_growth(self): create_file('main.c', r''' #include #include +#include +#include +#include int main() { + // Report whether `toResizableBuffer` is availble on the wasm memory. + EM_ASM(out('resizable memory buffers:', Boolean(wasmMemory.toResizableBuffer))); + void* x = malloc(10 * 1024 * 1024); assert(x != NULL); + // Have JS use some of the memory in the expanded range + char* str = (char*)x - 100; + strcpy(str, "Hello, world!"); + emscripten_out(str); return 0; } ''') - output = self.do_runf('main.c', cflags=['-sALLOW_MEMORY_GROWTH', '-sINITIAL_HEAP=1mb']) + output = self.do_runf('main.c', 'Hello, world!\n', cflags=['-sALLOW_MEMORY_GROWTH', '-sINITIAL_HEAP=1mb']) if self.is_wasm2js(): self.assertContained('Warning: Enlarging memory arrays, this is not fast! 1179648,10616832\n', output) + # Node versions older than 26 do not support toResizableBuffer + if self.is_wasm2js() or (self.engine_is_node() and not check_node_version(26)): + self.assertContained('resizable memory buffers: false\n', output) + else: + self.assertContained('resizable memory buffers: true\n', output) + @also_with_wasm2js @parameterized({ '': (False,), @@ -8558,7 +8576,7 @@ def test_exceptions_stack_trace_and_message(self): ''' self.cflags += ['-g'] - if '-fwasm-exceptions' in self.cflags and engine_is_node(self.js_engines[0]): + if '-fwasm-exceptions' in self.cflags and self.engine_is_node(): if not self.try_require_node_version(24): # Node versions prior to v24 do not implement the new 'traceStack' option in # WebAssembly.Exception constructor. @@ -8678,7 +8696,7 @@ def test_exceptions_rethrow_stack_trace_and_message(self): return 0; } ''' - if '-fwasm-exceptions' in self.cflags and engine_is_node(self.js_engines[0]): + if '-fwasm-exceptions' in self.cflags and self.engine_is_node(): if not self.try_require_node_version(24): # Node versions prior to v24 do not implement the new 'traceStack' option in # WebAssembly.Exception constructor.