From a23058a54d506a104e42dfe348e0d53233c409c6 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 3 May 2022 19:15:42 -0700 Subject: [PATCH] Add test for EM_JS and i64 arguments (#15916) Ideally we should error out if WASM_BIGINT is not passed by I have yet to find a good place to do that. See #15871 --- emscripten.py | 19 +++++++++++++++++++ tests/core/test_em_js_i64.c | 14 ++++++++++++++ tests/core/test_em_js_i64.out | 2 ++ tests/test_core.py | 9 +++++++++ tools/extract_metadata.py | 11 +++++++---- tools/shared.py | 1 + tools/webassembly.py | 35 +++++++++++++++++++++++++++++------ 7 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 tests/core/test_em_js_i64.c create mode 100644 tests/core/test_em_js_i64.out diff --git a/emscripten.py b/emscripten.py index eb09c61fd988..57492880ed92 100644 --- a/emscripten.py +++ b/emscripten.py @@ -299,6 +299,25 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile): update_settings_glue(metadata) + if not settings.WASM_BIGINT and metadata['emJsFuncs']: + module = webassembly.Module(in_wasm) + types = module.get_types() + import_map = {} + for imp in module.get_imports(): + import_map[imp.field] = imp + for em_js_func, raw in metadata.get('emJsFuncs', {}).items(): + c_sig = raw.split('<::>')[0].strip('()') + if not c_sig or c_sig == 'void': + c_sig = [] + else: + c_sig = c_sig.split(',') + if em_js_func in import_map: + imp = import_map[em_js_func] + assert(imp.kind == webassembly.ExternType.FUNC) + signature = types[imp.type] + if len(signature.params) != len(c_sig): + diagnostics.warning('em-js-i64', 'using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `%s` (%s, %s)', em_js_func, c_sig, signature.params) + if settings.SIDE_MODULE: if metadata['asmConsts']: exit_with_error('EM_ASM is not supported in side modules') diff --git a/tests/core/test_em_js_i64.c b/tests/core/test_em_js_i64.c new file mode 100644 index 000000000000..513ee2c8a8f6 --- /dev/null +++ b/tests/core/test_em_js_i64.c @@ -0,0 +1,14 @@ +#include +#include + +EM_JS(void, foo, (uint64_t a, int64_t b), { + console.log(typeof a); + console.log(typeof b); + + console.log('a:' + a); + console.log('b:' + b); +}) + +int main() { + foo(42000000000ull, -42ll); +} diff --git a/tests/core/test_em_js_i64.out b/tests/core/test_em_js_i64.out new file mode 100644 index 000000000000..8561b0d412e9 --- /dev/null +++ b/tests/core/test_em_js_i64.out @@ -0,0 +1,2 @@ +a:42000000000 +b:-42 diff --git a/tests/test_core.py b/tests/test_core.py index a54f1e6d3531..2d39c92c61a5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2142,6 +2142,15 @@ def test_em_js(self, args, force_c): self.do_core_test('test_em_js.cpp', force_c=force_c) self.assertContained("no args returning int", read_file('test_em_js.js')) + @no_wasm2js('WASM_BIGINT is not compatible with wasm2js') + def test_em_js_i64(self): + err = self.expect_fail([EMCC, '-Werror', test_file('core/test_em_js_i64.c')]) + self.assertContained('emcc: error: using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `foo`', err) + + self.set_setting('WASM_BIGINT') + self.node_args += ['--experimental-wasm-bigint'] + self.do_core_test('test_em_js_i64.c') + def test_runtime_stacksave(self): self.do_runf(test_file('core/test_runtime_stacksave.c'), 'success') diff --git a/tools/extract_metadata.py b/tools/extract_metadata.py index 0c24453ed3bc..f08600942a10 100644 --- a/tools/extract_metadata.py +++ b/tools/extract_metadata.py @@ -177,10 +177,6 @@ def extract_metadata(filename): for i in imports: if i.kind == webassembly.ExternType.FUNC: - if i.field.startswith('invoke_'): - invoke_funcs.append(i.field) - elif i.field not in em_js_funcs: - declares.append(i.field) imported_funcs += 1 elif i.kind == webassembly.ExternType.GLOBAL: imported_globals += 1 @@ -194,6 +190,13 @@ def extract_metadata(filename): string_address = get_global_value(globl) em_js_funcs[name] = get_string_at(module, string_address) + for i in imports: + if i.kind == webassembly.ExternType.FUNC: + if i.field.startswith('invoke_'): + invoke_funcs.append(i.field) + elif i.field not in em_js_funcs: + declares.append(i.field) + export_names = [e.name for e in exports if e.kind == webassembly.ExternType.FUNC] features = module.parse_features_section() diff --git a/tools/shared.py b/tools/shared.py index 3aef643a20c5..7f56acce1f12 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -73,6 +73,7 @@ diagnostics.add_warning('pthreads-mem-growth') diagnostics.add_warning('transpile') diagnostics.add_warning('limited-postlink-optimizations') +diagnostics.add_warning('em-js-i64') # TODO(sbc): Investigate switching to shlex.quote diff --git a/tools/webassembly.py b/tools/webassembly.py index 0a6beb90ab15..84571399370d 100644 --- a/tools/webassembly.py +++ b/tools/webassembly.py @@ -114,13 +114,14 @@ class InvalidWasmError(BaseException): Section = namedtuple('Section', ['type', 'size', 'offset', 'name']) Limits = namedtuple('Limits', ['flags', 'initial', 'maximum']) -Import = namedtuple('Import', ['kind', 'module', 'field']) +Import = namedtuple('Import', ['kind', 'module', 'field', 'type']) 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']) Table = namedtuple('Table', ['elem_type', 'limits']) FunctionBody = namedtuple('FunctionBody', ['offset', 'size']) DataSegment = namedtuple('DataSegment', ['flags', 'init', 'offset', 'size']) +FuncType = namedtuple('FuncType', ['params', 'returns']) class Module: @@ -210,6 +211,27 @@ def sections(self): yield Section(section_type, section_size, section_offset, name) offset = section_offset + section_size + def get_types(self): + type_section = self.get_section(SecType.TYPE) + if not type_section: + return [] + self.seek(type_section.offset) + num_types = self.readULEB() + types = [] + for i in range(num_types): + params = [] + returns = [] + type_form = self.readByte() + assert type_form == 0x60 + num_params = self.readULEB() + for j in range(num_params): + params.append(self.read_type()) + num_returns = self.readULEB() + for j in range(num_returns): + returns.append(self.read_type()) + types.append(FuncType(params, returns)) + return types + def parse_features_section(self): features = [] sec = self.get_custom_section('target_features') @@ -315,22 +337,23 @@ def get_imports(self): mod = self.readString() field = self.readString() kind = ExternType(self.readByte()) - imports.append(Import(kind, mod, field)) + type_ = None if kind == ExternType.FUNC: - self.readULEB() # sig + type_ = self.readULEB() elif kind == ExternType.GLOBAL: - self.readSLEB() # global type + type_ = self.readSLEB() self.readByte() # mutable elif kind == ExternType.MEMORY: self.read_limits() # limits elif kind == ExternType.TABLE: - self.readSLEB() # table type + type_ = self.readSLEB() self.read_limits() # limits elif kind == ExternType.TAG: self.readByte() # attribute - self.readULEB() # sig + type_ = self.readULEB() else: assert False + imports.append(Import(kind, mod, field, type_)) return imports