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
5 changes: 5 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
11 changes: 8 additions & 3 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 42 additions & 4 deletions src/runtime_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the below Firefox bug report, I think this comment should be adjusted to something like:

Suggested change
// Using `toResizableBuffer` on a shared memory is currently broken on Firefox
// Deserializing a growable SharedArrayBuffer is currently broken in Firefox.

(i.e. wasmMemory.toResizableBuffer() works as expected, but it cannot be deserialized in web workers.)

// 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);
Expand Down
11 changes: 8 additions & 3 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +2199 to +2203

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about turning this into a three-mode setting, similar to -sTEXTDECODER? That would give us:

  • -sGROWABLE_ARRAYBUFFERS=0 - current default: the generated code will not use a growable SharedArrayBuffer.
  • -sGROWABLE_ARRAYBUFFERS=1 - potential new default: the generated code will use a growable SharedArrayBuffer when available and supported.
  • -sGROWABLE_ARRAYBUFFERS=2 - assumes the WebAssembly.Memory.toResizableBuffer() API is available and usable and omits any JS fallback code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I just noticed this is also mentioned in #27096 (comment).

// [link]
var GROWABLE_ARRAYBUFFERS = false;

Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_mem_O3_grow.json
Original file line number Diff line number Diff line change
@@ -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)"
],
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_mem_O3_grow_standalone.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 4 additions & 4 deletions test/codesize/test_codesize_minimal_pthreads_memgrowth.json
Original file line number Diff line number Diff line change
@@ -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)",
Expand Down
23 changes: 20 additions & 3 deletions test/embind/embind_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,27 @@ void force_memory_growth() {
assert(val::global("oldheap")["byteLength"].as<size_t>() == 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<size_t>() > old_size);
assert(val::global("oldheap")["byteLength"].as<size_t>() == 0);

val oldheap = val::global("oldheap");
val buffer = oldheap["buffer"];
bool growable = false;
if (!buffer.isUndefined()) {
if (!buffer["resizable"].isUndefined()) {
growable = buffer["resizable"].as<bool>();
} else if (!buffer["growable"].isUndefined()) {
growable = buffer["growable"].as<bool>();
}
}

if (growable) {
assert(oldheap["byteLength"].as<size_t>() == emscripten_get_heap_size());
} else {
assert(oldheap["byteLength"].as<size_t>() == 0);
}
}

std::string emval_test_take_and_return_const_char_star(const char* str) {
Expand Down
1 change: 0 additions & 1 deletion test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
Loading