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
12 changes: 12 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/lib/libcore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2254,8 +2254,7 @@ addToLibrary({
},

$wasmTable__docs: '/** @type {WebAssembly.Table} */',
#if RELOCATABLE
// In RELOCATABLE mode we create the table in JS.
#if IMPORTED_TABLE
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm less sure about this part. Don't you want to define the table externally in the cases of IMPORTED_TABLE?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a little sad to make this change because I was hoping to completely remove this block of code. Since we no longer use -sRELOCTABLE for dynamic linking I was hoping to remove all support for -sRELOCATABLE.

$wasmTable: `=new WebAssembly.Table({
'initial': {{{ toIndexType(INITIAL_TABLE) }}},
#if !ALLOW_TABLE_GROWTH
Expand Down
8 changes: 8 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdio.h>
Expand Down
11 changes: 7 additions & 4 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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]
Expand Down
25 changes: 17 additions & 8 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

INITIAL_TABLE is only ever used in RELOCATABLE mode so I think this part can maybe reverted? (Along with the webassembly.py file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's used when we set IMPORTED_TABLE too so I'm pretty sure the change is necessary.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How about if IMPORTED_TABLE means "user must define the table"? Rather than "emcc defines the table in JS"? Then you could revert the libcore.js change along with this one?

Or, in your use case would you prefer if emcc itself defined the table in JS?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd prefer it if emcc defines the table itself. For instance, otherwise I'd need to implement similar logic that looks at the import section and checks how big the initial size is. I just want wasmTable to be defined during --pre-js execution like the test asserts.

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']
Expand Down Expand Up @@ -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 += [
Expand Down
6 changes: 6 additions & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions tools/webassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down Expand Up @@ -412,26 +412,34 @@ 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()
case ExternType.GLOBAL:
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)
Expand Down