Skip to content

Commit

Permalink
Memory growth support for STANDALONE_WASM (emscripten-core#9588)
Browse files Browse the repository at this point in the history
Remove the assertion on memory growth not being allowed in standalone mode.

Add emscripten_notify_memory_growth which is just called to notify. That will
show an error in wasm runtimes that don't support it.

Add a variation on libstandalone that calls that notification if growth is
enabled. That lets programs not using growth still be 100% pure.

Add a test for memory growth in "impure" standalone wasm (i.e., standalone mode
does not emit a 100% pure wasm yet).

Fix some bugs in emscripten_resize_heap and the preamble memory loading code,
that the test uncovered.
  • Loading branch information
kripken authored and belraquib committed Dec 23, 2020
1 parent 895968b commit f9584f4
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 9 deletions.
2 changes: 0 additions & 2 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1786,8 +1786,6 @@ def check_human_readable_list(items):
exit_with_error('STANDALONE_WASM does not support pthreads yet')
if shared.Settings.SIMD:
exit_with_error('STANDALONE_WASM does not support simd yet')
if shared.Settings.ALLOW_MEMORY_GROWTH:
exit_with_error('STANDALONE_WASM does not support memory growth yet')
# the wasm must be runnable without the JS, so there cannot be anything that
# requires JS legalization
shared.Settings.LEGALIZE_JS_FFI = 0
Expand Down
17 changes: 17 additions & 0 deletions site/source/docs/api_reference/emscripten.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1363,3 +1363,20 @@ Functions
This function requires Asyncify - it relies on that option to spill the
local state all the way up the stack. As a result, it will add overhead
to your program.
ABI functions
=============
The following functions are not declared in ``emscripten.h``, but are used
internally in our system libraries. You may care about them if you replace the
Emscripten runtime JS code, or run Emscripten binaries in your own runtime.
.. c:function:: void emscripten_notify_memory_growth(i32 index)
Called when memory has grown. In a JS runtime, this is used to know when
to update the JS views on the wasm memory, which otherwise we would need
to constantly check for after any wasm code runs. See
`this wasi discussion <https://github.com/WebAssembly/WASI/issues/82>`_.
:param i32 index: Which memory has grown.
11 changes: 11 additions & 0 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,17 @@ LibraryManager.library = {
#endif // ALLOW_MEMORY_GROWTH
},

// Called after wasm grows memory. At that time we need to update the views.
// Without this notification, we'd need to check the buffer in JS every time
// we return from any wasm, which adds overhead. See
// https://github.com/WebAssembly/WASI/issues/82
emscripten_notify_memory_growth: function(memoryIndex) {
#if ASSERTIONS
assert(memoryIndex == 0);
#endif
updateGlobalBufferAndViews(wasmMemory.buffer);
},

system__deps: ['__setErrNo'],
system: function(command) {
// int system(const char *command);
Expand Down
3 changes: 2 additions & 1 deletion src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,8 @@ function createWasm() {
// In pure wasm mode the memory is created in the wasm (not imported), and
// then exported.
// TODO: do not create a Memory earlier in JS
updateGlobalBufferAndViews(exports['memory'].buffer);
wasmMemory = exports['memory'];
updateGlobalBufferAndViews(wasmMemory.buffer);
#if ASSERTIONS
writeStackCookie();
#endif
Expand Down
20 changes: 16 additions & 4 deletions system/lib/standalone_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* found in the LICENSE file.
*/

#include <assert.h>
#include <emscripten.h>
#include <errno.h>
#include <stdio.h>
Expand Down Expand Up @@ -70,11 +71,22 @@ void *emscripten_memcpy_big(void *restrict dest, const void *restrict src, size_

static const int WASM_PAGE_SIZE = 65536;

// Note that this does not support memory growth in JS because we don't update the JS
// heaps. Wasm and wasi lack a good API for that.
extern void emscripten_notify_memory_growth(size_t memory_index);

int emscripten_resize_heap(size_t size) {
size_t result = __builtin_wasm_memory_grow(0, (size + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE);
return result != (size_t)-1;
#ifdef __EMSCRIPTEN_MEMORY_GROWTH__
size_t old_size = __builtin_wasm_memory_size(0) * WASM_PAGE_SIZE;
assert(old_size < size);
ssize_t diff = (size - old_size + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE;
size_t result = __builtin_wasm_memory_grow(0, diff);
if (result != (size_t)-1) {

// Success, update JS (see https://github.com/WebAssembly/WASI/issues/82)
emscripten_notify_memory_growth(0);
return 1;
}
#endif
return 0;
}

// C++ ABI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,7 @@ _emscripten_log_js
_emscripten_longjmp
_emscripten_main_browser_thread_id
_emscripten_memcpy_big
_emscripten_notify_memory_growth
_emscripten_pause_main_loop
_emscripten_pc_get_column
_emscripten_pc_get_file
Expand Down
22 changes: 22 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ def decorated(self):
return decorated


# Similar to also_with_standalone_wasm, but suitable for tests that cannot
# run in a wasm VM yet, as they are not 100% standalone. We can still
# run them with the JS code though.
def also_with_impure_standalone_wasm(func):
def decorated(self):
func(self)
# Standalone mode is only supported in the wasm backend, and not in all
# modes there.
if self.is_wasm_backend() and self.get_setting('WASM') and not self.get_setting('SAFE_STACK'):
print('standalone (impure; no wasm runtimes)')
self.set_setting('STANDALONE_WASM', 1)
wasm_engines = shared.WASM_ENGINES
try:
shared.WASM_ENGINES = []
func(self)
finally:
shared.WASM_ENGINES = wasm_engines

return decorated


# A simple check whether the compiler arguments cause optimization.
def is_optimizing(args):
return '-O' in str(args) and '-O0' not in args
Expand Down Expand Up @@ -1929,6 +1950,7 @@ def test_memorygrowth_3(self):
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=0', '-s', 'ABORTING_MALLOC=0', '-s', 'SAFE_HEAP']
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3')

@also_with_impure_standalone_wasm
def test_memorygrowth_wasm_mem_max(self):
if self.has_changed_setting('ALLOW_MEMORY_GROWTH'):
self.skipTest('test needs to modify memory growth')
Expand Down
2 changes: 1 addition & 1 deletion tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8104,7 +8104,7 @@ def test_binaryen_metadce_hello(self, *args):
4, [], [], 8, 0, 0, 0), # noqa; totally empty!
# we don't metadce with linkable code! other modules may want stuff
# don't compare the # of functions in a main module, which changes a lot
'main_module_1': (['-O3', '-s', 'MAIN_MODULE=1'], 1603, [], [], 226403, None, 107, None), # noqa
'main_module_1': (['-O3', '-s', 'MAIN_MODULE=1'], 1604, [], [], 226403, None, 107, None), # noqa
'main_module_2': (['-O3', '-s', 'MAIN_MODULE=2'], 13, [], [], 10017, 13, 9, 20), # noqa
})
@no_wasm_backend()
Expand Down
2 changes: 1 addition & 1 deletion tools/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def get_emscripten_version(path):
# change, increment EMSCRIPTEN_ABI_MINOR if EMSCRIPTEN_ABI_MAJOR == 0
# or the ABI change is backwards compatible, otherwise increment
# EMSCRIPTEN_ABI_MAJOR and set EMSCRIPTEN_ABI_MINOR = 0.
(EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR) = (0, 16)
(EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR) = (0, 17)


def generate_sanity():
Expand Down
27 changes: 27 additions & 0 deletions tools/system_libs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,33 @@ class libstandalonewasm(MuslInternalLibrary):
cflags = ['-Os']
src_dir = ['system', 'lib']

def __init__(self, **kwargs):
self.is_mem_grow = kwargs.pop('is_mem_grow')
super(libstandalonewasm, self).__init__(**kwargs)

def get_base_name(self):
name = super(libstandalonewasm, self).get_base_name()
if self.is_mem_grow:
name += '-memgrow'
return name

def get_cflags(self):
cflags = super(libstandalonewasm, self).get_cflags()
if self.is_mem_grow:
cflags += ['-D__EMSCRIPTEN_MEMORY_GROWTH__=1']
return cflags

@classmethod
def vary_on(cls):
return super(libstandalonewasm, cls).vary_on() + ['is_mem_grow']

@classmethod
def get_default_variation(cls, **kwargs):
return super(libstandalonewasm, cls).get_default_variation(
is_mem_grow=shared.Settings.ALLOW_MEMORY_GROWTH,
**kwargs
)

def get_files(self):
base_files = files_in_path(
path_components=['system', 'lib'],
Expand Down

0 comments on commit f9584f4

Please sign in to comment.