diff --git a/emcc.py b/emcc.py index ffea4564b27c7..a36adf2644603 100755 --- a/emcc.py +++ b/emcc.py @@ -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 diff --git a/site/source/docs/api_reference/emscripten.h.rst b/site/source/docs/api_reference/emscripten.h.rst index 9925137074ad3..9200b388ad1b7 100644 --- a/site/source/docs/api_reference/emscripten.h.rst +++ b/site/source/docs/api_reference/emscripten.h.rst @@ -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 `_. + + :param i32 index: Which memory has grown. diff --git a/src/library.js b/src/library.js index 45fba9a5c9138..71caf7daddc98 100644 --- a/src/library.js +++ b/src/library.js @@ -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); diff --git a/src/preamble.js b/src/preamble.js index fdd4c258df9ff..4985d327762fd 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -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 diff --git a/system/lib/standalone_wasm.c b/system/lib/standalone_wasm.c index baa68fc09b281..a0b478013ef20 100644 --- a/system/lib/standalone_wasm.c +++ b/system/lib/standalone_wasm.c @@ -5,6 +5,7 @@ * found in the LICENSE file. */ +#include #include #include #include @@ -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 diff --git a/tests/other/metadce/hello_world_fastcomp_O3_MAIN_MODULE.sent b/tests/other/metadce/hello_world_fastcomp_O3_MAIN_MODULE.sent index f4be196c95574..38670f5df16e1 100644 --- a/tests/other/metadce/hello_world_fastcomp_O3_MAIN_MODULE.sent +++ b/tests/other/metadce/hello_world_fastcomp_O3_MAIN_MODULE.sent @@ -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 diff --git a/tests/test_core.py b/tests/test_core.py index 5f4ecfe01f29f..be99770bc6ebb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -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 @@ -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') diff --git a/tests/test_other.py b/tests/test_other.py index 4839e9609fda9..f835e32c2e6be 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -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() diff --git a/tools/shared.py b/tools/shared.py index 03eb93d347db9..67caee9c213ed 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -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(): diff --git a/tools/system_libs.py b/tools/system_libs.py index b0e73289585fd..1cf795e148d96 100755 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -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'],