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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
4 changes: 3 additions & 1 deletion site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 40 additions & 4 deletions src/runtime_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps explain why? We don't do this with other features afaik?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we do do feature detection in some places.

The reason we want both approaches in this case is kind of important. There are two main cases:

  1. For all users or pthreads+memory growth we probably want to use toResizableBuffer when available in all cases. It slightly fast since we don't need to rebuild the views each time the memory grows. There is very little cost to probing for, and using the features if its found.

  2. For users that know that they are only targetting newer browsers they can opt in with -sGROWABLE_ARRAYBUFFERS which means we don't need run the whole growableHeap JS optimizer path (which is the thing that slows down all the HEAP accesses in normal builder). These users see even more of a benefit, but their code won't run on older browsers.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

For example of where we do feature detection you can see many by doing git grep "if .*globalThis" src.

In most cases we don't have setting to opt out of the feature-detection and just assume the feature is present. In some cases we can use the feature matrix to detect when the feature-detection is not needed.

In the long run I think we may just want to delete this setting (or at least make it internal) since it can be driven completely by the feature matrix.

// [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": 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)"
],
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": 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",
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": 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)",
Expand Down
16 changes: 10 additions & 6 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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):
Expand Down
19 changes: 16 additions & 3 deletions test/embind/embind_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,23 @@ 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 resizable = false;
if (!buffer.isUndefined() && !buffer["resizable"].isUndefined()) {
resizable = buffer["resizable"].as<bool>();
}

if (resizable) {
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
24 changes: 21 additions & 3 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
TEST_ROOT,
WEBIDL_BINDER,
RunnerCore,
check_node_version,
copy_asset,
copytree,
create_file,
Expand Down Expand Up @@ -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 <assert.h>
#include <stdlib.h>
#include <string.h>
#include <emscripten/em_asm.h>
#include <emscripten/console.h>

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,),
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading