Skip to content

Commit e07a405

Browse files
authored
JavaScript: allow the memory to grow on demand.
2 parents 30e2289 + 2504c95 commit e07a405

5 files changed

Lines changed: 32 additions & 21 deletions

File tree

‎VERSION.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.20261019.0
1+
1.20261020.0

‎src/CMakeLists.txt‎

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,28 @@ if(EMSCRIPTEN)
372372
# Note #3: for linking, we use -O2 (rather than -O3) since our compressed .wasm file is then smaller with no loss of
373373
# performance (and it links faster), -flto to use the LTO version of Emscripten's system libraries and to
374374
# optimise LLVM, which we build as LLVM bitcode (see src/3rdparty/LLVM/CMakeLists.txt; our other object
375-
# files are not LTO ones), -pthread to enable native threading support, -sASSERTIONS=0 to disable
376-
# assertions (they are not needed in production), -sCROSS_ORIGIN=1 to support our .js file being loaded
377-
# from another origin than that of the Web page (see Note #5), -sEXPORT_ES6=1 to export ES6 modules,
378-
# -sEXPORTED_FUNCTIONS to export the functions we need (i.e. those needed by the WebAssembly modules that
379-
# we generate for our models, see initialiseWorkerWasmJS()), -sEXPORTED_RUNTIME_METHODS to export the
380-
# runtime methods we need, -sINITIAL_MEMORY=2GB to have enough memory for "big" models, -sMODULARIZE=1 to
381-
# generate a module factory function, -sSTACK_SIZE=8MB to reserve a reasonable amount of memory for the
382-
# stack of each thread (which is needed for "big" models), --bind to generate the necessary Embind
383-
# bindings, and --emit-tsd to generate a TypeScript type definitions file.
384-
# Note #4: for linking, we used to have -sALLOW_MEMORY_GROWTH=1, -sINITIAL_MEMORY=256MB, and -sMAXIMUM_MEMORY=2GB,
385-
# but we now support threading, which means that our memory is shared, and to allow shared memory to grow
386-
# would make JavaScript's accesses to it (e.g., through HEAPU8) slower. So, now, we use
387-
# -sINITIAL_MEMORY=2GB and don't allow memory growth.
375+
# files are not LTO ones), -pthread to enable native threading support, -Wno-pthreads-mem-growth to silence
376+
# Emscripten's warning about threading and memory growth (see Note #4), -sALLOW_MEMORY_GROWTH=1 to let our
377+
# memory grow on demand (see Note #4), -sASSERTIONS=0 to disable assertions (they are not needed in
378+
# production), -sCROSS_ORIGIN=1 to support our .js file being loaded from another origin than that of the
379+
# Web page (see Note #5), -sEXPORT_ES6=1 to export ES6 modules, -sEXPORTED_FUNCTIONS to export the
380+
# functions we need (i.e. those needed by the WebAssembly modules that we generate for our models, see
381+
# initialiseWorkerWasmJS()), -sEXPORTED_RUNTIME_METHODS to export the runtime methods we need,
382+
# -sMAXIMUM_MEMORY=2GB to let our memory grow up to 2GB (see Note #4), -sMODULARIZE=1 to generate a module
383+
# factory function, -sSTACK_SIZE=8MB to reserve a reasonable amount of memory for the stack of each thread
384+
# (which is needed for "big" models), --bind to generate the necessary Embind bindings, and --emit-tsd to
385+
# generate a TypeScript type definitions file.
386+
# Note #4: our memory starts small (16MB, which is Emscripten's default when, like here, our memory is imported) and
387+
# grows on demand, up to 2GB. Since we support threading, our memory is shared, which means that another
388+
# thread may grow it, so Emscripten precedes each of our JavaScript code's accesses to it (e.g., through
389+
# HEAPU8 in an EM_ASM() block) with a check of whether it has grown. That check makes those accesses slower
390+
# (about 15ns each), hence Emscripten's warning, but it only affects calls that cross the
391+
# JavaScript/WebAssembly boundary (e.g., a call that passes or returns a string), not our WebAssembly code
392+
# (e.g., the simulation of a model). We don't use -sGROWABLE_ARRAYBUFFERS=1|2, which would replace that
393+
# check with a growable SharedArrayBuffer since our results (see toFloat64ArrayJS()) would then be views
394+
# over a growable SharedArrayBuffer, which some Web APIs reject (e.g., structuredClone()). We also don't
395+
# use -sMAXIMUM_MEMORY=4GB since our EM_JS() functions would then get a pointer of 2GB or more as a
396+
# negative number and Embind calls would be slower (since pointers would need to be made unsigned).
388397
# Note #5: a Web browser only creates a worker (and therefore a thread) from a script that has the same origin as
389398
# the Web page, so without -sCROSS_ORIGIN=1, a thread couldn't be created if our .js file was loaded from
390399
# another origin (e.g., a CDN), i.e. startRun() would throw an error (Chrome) or never complete (Firefox).
@@ -404,14 +413,16 @@ if(EMSCRIPTEN)
404413
-O2
405414
-flto
406415
-pthread
416+
-sALLOW_MEMORY_GROWTH=1
407417
-sASSERTIONS=0
408418
-sCROSS_ORIGIN=1
409419
-sEXPORT_ES6=1
410420
-sEXPORTED_FUNCTIONS=[_free,_malloc,_memset,_nlaSolverAddress,_wasmNlaSolve,_pow,_sqrt,_fabs,_exp,_exp2,_log,_log10,_ceil,_floor,_fmin,_fmax,_fmod,_sin,_cos,_tan,_sinh,_cosh,_tanh,_asin,_acos,_atan,_asinh,_acosh,_atanh]
411421
-sEXPORTED_RUNTIME_METHODS=HEAPU8
412-
-sINITIAL_MEMORY=2GB
422+
-sMAXIMUM_MEMORY=2GB
413423
-sMODULARIZE=1
414424
-sSTACK_SIZE=8MB
425+
-Wno-pthreads-mem-growth
415426
--bind
416427
--emit-tsd ${DTS_FILE})
417428

‎src/bindings/javascript/file.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ void fileApi()
4646
}
4747

4848
// Note: we avoid using emscripten::typed_memory_view() as an intermediate step since it creates a
49-
// JavaScript's TypedArray view directly backed by wasmMemory.buffer. This is fragile and would break
50-
// if ALLOW_MEMORY_GROWTH was to be enabled. By performing the copy entirely within a single EM_ASM()
49+
// JavaScript's TypedArray view directly backed by wasmMemory.buffer. This is fragile since our memory
50+
// can grow (see src/CMakeLists.txt). By performing the copy entirely within a single EM_ASM()
5151
// block, HEAPU8 is fetched and used atomically.
5252

5353
return emscripten::val::take_ownership(static_cast<emscripten::EM_VAL>(EM_ASM_PTR({

‎src/misc/compiler.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ std::string patchWasmSharedMemory(UnsignedChars &pWasmModule)
342342
// Memory import: patch its flags to mark it as shared and give it a maximum size, if it doesn't
343343
// already have one. That maximum size must be at least that of the memory of our main WebAssembly
344344
// module (i.e. the memory that we import), which we retrieve at runtime rather than hard-code it
345-
// since it depends on -sINITIAL_MEMORY, etc. (see src/CMakeLists.txt).
345+
// since it depends on -sMAXIMUM_MEMORY, etc. (see src/CMakeLists.txt).
346346
// Note: LLVM currently emits a 32-bit, non-shared memory import without a maximum size (i.e. flags
347347
// 0x00), but we handle a memory import with a maximum size (i.e. flags 0x01) and/or that is
348348
// already shared (i.e. flags 0x03) in case a future version of LLVM does so. Any other

‎src/sed/sedinstancetask.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ EM_JS(intptr_t, toFloat64ArrayJS, (const void* data, size_t size), {
4343
}
4444

4545
return Emval.toHandle(new Float64Array(HEAPU8.subarray(data, data + 8 * size).buffer, data, size));
46-
// Note: we use HEAPU8.subarray() to create a view over the WASM heap's Float64Array buffer because it is safer than
47-
// accessing HEAPU8.buffer directly in case the WASM heap was ever to grow (we don't allow this to happen, but
48-
// it is still safer to use HEAPU8.subarray()) since it creates a view with the correct byte offset.
46+
// Note: our memory can grow (see src/CMakeLists.txt), but HEAPU8 is always up to date when we access it and our
47+
// memory is shared, i.e. backed by a SharedArrayBuffer, which never gets detached when our memory grows. So,
48+
// our view remains valid even after our memory has grown (as long as our results don't get reallocated).
4949
}); // clang-format on
5050

5151
static emscripten::val toFloat64Array(std::span<const double> data)

0 commit comments

Comments
 (0)