diff --git a/site/source/docs/tools_reference/settings_reference.rst b/site/source/docs/tools_reference/settings_reference.rst index 8941a9d44b207..1246b1dc90258 100644 --- a/site/source/docs/tools_reference/settings_reference.rst +++ b/site/source/docs/tools_reference/settings_reference.rst @@ -3244,6 +3244,18 @@ depend on being able to define the memory in JavaScript: Default value: false +.. _imported_table: + +IMPORTED_TABLE +============== + +Set to 1 to define the WebAssembly.Table object outside of the wasm module. +By default the wasm module defines the table and exports it to JavaScript. +Use of the `RELOCATABLE` setting will enable this setting since it depends +on defining the table in JavaScript. + +Default value: false + .. _split_module: SPLIT_MODULE diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 1fb5e90f2597b..95e7c31b72955 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -2254,8 +2254,7 @@ addToLibrary({ }, $wasmTable__docs: '/** @type {WebAssembly.Table} */', -#if RELOCATABLE - // In RELOCATABLE mode we create the table in JS. +#if IMPORTED_TABLE $wasmTable: `=new WebAssembly.Table({ 'initial': {{{ toIndexType(INITIAL_TABLE) }}}, #if !ALLOW_TABLE_GROWTH diff --git a/src/settings.js b/src/settings.js index 9a755c2b50bb4..e5ce11377df58 100644 --- a/src/settings.js +++ b/src/settings.js @@ -2142,6 +2142,14 @@ var PURE_WASI = false; // [link] var IMPORTED_MEMORY = false; +// Set to 1 to define the WebAssembly.Table object outside of the wasm module. +// By default the wasm module defines the table and exports it to JavaScript. +// Use of the `RELOCATABLE` setting will enable this setting since it depends +// on defining the table in JavaScript. +// +// [link] +var IMPORTED_TABLE = false; + // Generate code to load split wasm modules. // This option will automatically generate two wasm files as output, one // with the ``.orig`` suffix and one without. The default file (without diff --git a/test/test_core.py b/test/test_core.py index 03aed366e18c4..a2b5a56f1b002 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -2258,6 +2258,36 @@ def test_module_wasm_memory(self): self.set_setting('INCOMING_MODULE_JS_API', ['wasmMemory']) self.do_runf('core/test_module_wasm_memory.c', 'success', cflags=['--pre-js', test_file('core/test_module_wasm_memory.js')]) + @no_wasm2js('no WebAssembly.Table()') + def test_imported_table(self): + create_file('pre.js', ''' + Module['preInit'] = () => { + assert(typeof wasmTable === 'object', 'wasmTable should be defined'); + console.log('table check passed'); + }; + ''') + # Without IMPORTED_TABLE, wasmTable is not yet defined when preInit is run + self.do_runf('hello_world.c', 'wasmTable should be defined', + cflags=['--pre-js=pre.js'], assert_returncode=NON_ZERO) + # With IMPORTED_TABLE, wasmTable is available + self.set_setting('IMPORTED_TABLE') + self.do_runf('hello_world.c', 'table check passed', cflags=['--pre-js=pre.js']) + + @no_wasm2js('no WebAssembly.Memory()') + def test_imported_memory(self): + create_file('pre.js', ''' + Module['preInit'] = () => { + assert(typeof wasmMemory === 'object', 'wasmMemory should be defined'); + console.log('memory check passed'); + }; + ''') + # Without IMPORTED_MEMORY, wasmMemory is not yet defined when preInit is run + self.do_runf('hello_world.c', 'wasmMemory should be defined', + cflags=['--pre-js=pre.js'], assert_returncode=NON_ZERO) + # With IMPORTED_MEMORY, wasmMemory is available + self.set_setting('IMPORTED_MEMORY') + self.do_runf('hello_world.c', 'memory check passed', cflags=['--pre-js=pre.js']) + def test_ssr(self): # struct self-ref src = ''' #include diff --git a/tools/building.py b/tools/building.py index 182fb3ea311e5..d90a77cae36bb 100644 --- a/tools/building.py +++ b/tools/building.py @@ -176,6 +176,13 @@ def lld_flags_for_executable(external_symbols): if settings.IMPORTED_MEMORY: cmd.append('--import-memory') + if settings.IMPORTED_TABLE: + cmd.append('--import-table') + else: + cmd.append('--export-table') + if settings.ALLOW_TABLE_GROWTH: + cmd.append('--growable-table') + if settings.SHARED_MEMORY: cmd.append('--shared-memory') @@ -235,10 +242,6 @@ def lld_flags_for_executable(external_symbols): cmd.append('-pie') if not settings.LINKABLE: cmd.append('--no-export-dynamic') - else: - cmd.append('--export-table') - if settings.ALLOW_TABLE_GROWTH: - cmd.append('--growable-table') if not settings.SIDE_MODULE: cmd += ['-z', 'stack-size=%s' % settings.STACK_SIZE] diff --git a/tools/emscripten.py b/tools/emscripten.py index 2f3a484a8b7c0..cf9cac1d4be91 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -429,11 +429,19 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat set_memory(static_bump) logger.debug('stack_low: %d, stack_high: %d, heap_base: %d', settings.STACK_LOW, settings.STACK_HIGH, settings.HEAP_BASE) - # When building relocatable output (e.g. MAIN_MODULE) the reported table - # size does not include the reserved slot at zero for the null pointer. - # So we need to offset the elements by 1. - if settings.INITIAL_TABLE == -1: - settings.INITIAL_TABLE = dylink_sec.table_size + 1 + if settings.IMPORTED_TABLE and settings.INITIAL_TABLE == -1: + # For builds with IMPORTED_TABLE, get the table size from the wasm module's + # table import. + with webassembly.Module(in_wasm) as module: + table_import = module.get_function_table_import() + if not table_import: + exit_with_error('IMPORTED_TABLE requires a table import in the wasm module') + settings.INITIAL_TABLE = table_import.limits.initial + if settings.RELOCATABLE: + # When building relocatable output (e.g. MAIN_MODULE) the reported table + # size does not include the reserved slot at zero for the null pointer. + # So we need to offset the elements by 1. + settings.INITIAL_TABLE += 1 if metadata.invoke_funcs: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getWasmTableEntry'] @@ -823,10 +831,11 @@ def add_standard_wasm_imports(send_items_map): if settings.IMPORTED_MEMORY: send_items_map['memory'] = 'wasmMemory' - if settings.RELOCATABLE: + if settings.IMPORTED_TABLE: send_items_map['__indirect_function_table'] = 'wasmTable' - if settings.MEMORY64: - send_items_map['__table_base32'] = '___table_base32' + + if settings.RELOCATABLE and settings.MEMORY64: + send_items_map['__table_base32'] = '___table_base32' if settings.AUTODEBUG: extra_sent_items += [ diff --git a/tools/link.py b/tools/link.py index 03be0d432ed06..f6a1857d47ce5 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1167,6 +1167,9 @@ def limit_incoming_module_api(): settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$wasmMemory') + if settings.IMPORTED_TABLE: + settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$wasmTable') + if 'noExitRuntime' in settings.INCOMING_MODULE_JS_API: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$noExitRuntime') @@ -1599,6 +1602,9 @@ def limit_incoming_module_api(): if settings.PTHREADS or settings.WASM_WORKERS or settings.RELOCATABLE: settings.IMPORTED_MEMORY = 1 + if settings.RELOCATABLE: + settings.IMPORTED_TABLE = 1 + set_initial_memory() # When not declaring wasm module exports in outer scope one by one, disable minifying diff --git a/tools/webassembly.py b/tools/webassembly.py index 12aa31d884997..f30c3c4df6114 100644 --- a/tools/webassembly.py +++ b/tools/webassembly.py @@ -178,7 +178,7 @@ class InvalidWasmError(BaseException): Section = namedtuple('Section', ['type', 'size', 'offset', 'name']) Limits = namedtuple('Limits', ['flags', 'initial', 'maximum']) -Import = namedtuple('Import', ['kind', 'module', 'field', 'type']) +Import = namedtuple('Import', ['kind', 'module', 'field', 'type', 'limits']) Export = namedtuple('Export', ['name', 'kind', 'index']) Global = namedtuple('Global', ['type', 'mutable', 'init']) Dylink = namedtuple('Dylink', ['mem_size', 'mem_align', 'table_size', 'table_align', 'needed', 'export_info', 'import_info', 'runtime_paths']) @@ -412,6 +412,7 @@ def get_imports(self): field = self.read_string() kind = ExternType(self.read_byte()) type_ = None + limits = None match kind: case ExternType.FUNC: type_ = self.read_uleb() @@ -419,19 +420,26 @@ def get_imports(self): type_ = self.read_sleb() self.read_byte() # mutable case ExternType.MEMORY: - self.read_limits() # limits + limits = self.read_limits() # limits case ExternType.TABLE: type_ = self.read_sleb() - self.read_limits() # limits + limits = self.read_limits() # limits case ExternType.TAG: self.read_byte() # attribute type_ = self.read_uleb() case _: raise AssertionError() - imports.append(Import(kind, mod, field, type_)) + imports.append(Import(kind, mod, field, type_, limits)) return imports + @memoize + def get_function_table_import(self): + for import_ in self.get_imports(): + if import_.module == 'env' and import_.field == '__indirect_function_table': + return import_ + return None + @memoize def get_globals(self): global_section = self.get_section(SecType.GLOBAL)