From ef10126b1efbed52083dc509e2b0077f8846f54e Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Wed, 10 Jan 2024 00:26:22 +0000 Subject: [PATCH] [wasm64] Update webidl to support wasm64 The changes to `test/webidl/post.js` make debugging easier since they don't silently swallow errors. I'm not sure why those try/catches where in there since all those calls are expected to succeed. --- test/test_core.py | 9 +++-- tools/emscripten.py | 2 + tools/webidl_binder.py | 87 ++++++++++++++++++++++++++++++++---------- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/test/test_core.py b/test/test_core.py index cad06bae631fb..90477dbed15a8 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7627,7 +7627,6 @@ def test_embind_no_rtti_followed_by_rtti(self): self.emcc_args += ['-lembind', '-fno-rtti', '-frtti'] self.do_run(src, '418\ndotest returned: 42\n') - @no_wasm64('webidl not compatible with MEMORY64 yet') @parameterized({ '': ('DEFAULT', False), 'all': ('ALL', False), @@ -7646,8 +7645,12 @@ def test_webidl(self, mode, allow_memory_growth): self.set_setting('WASM_ASYNC_COMPILATION', 0) # Force IDL checks mode + if self.is_wasm64(): + args = ['--wasm64'] + else: + args = [] with env_modify({'IDL_CHECKS': mode}): - self.run_process([WEBIDL_BINDER, test_file('webidl/test.idl'), 'glue']) + self.run_process([WEBIDL_BINDER, test_file('webidl/test.idl'), 'glue'] + args) self.assertExists('glue.cpp') self.assertExists('glue.js') @@ -7667,7 +7670,7 @@ def test_webidl(self, mode, allow_memory_growth): # Export things on "TheModule". This matches the typical use pattern of the bound library # being used as Box2D.* or Ammo.*, and we cannot rely on "Module" being always present (closure may remove it). - self.emcc_args += ['-Wall', '--post-js=glue.js', '--extern-post-js=extern-post.js'] + self.emcc_args += ['--post-js=glue.js', '--extern-post-js=extern-post.js'] if mode == 'ALL': self.emcc_args += ['-sASSERTIONS'] if allow_memory_growth: diff --git a/tools/emscripten.py b/tools/emscripten.py index d531a380ddfa0..24f9095f347d5 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -893,12 +893,14 @@ def create_pointer_conversion_wrappers(metadata): 'stackAlloc': 'pp', 'emscripten_builtin_malloc': 'pp', 'malloc': 'pp', + 'webidl_malloc': 'pp', 'memalign': 'ppp', 'memcmp': '_ppp', 'memcpy': 'pppp', '__getTypeName': 'pp', 'setThrew': '_p', 'free': '_p', + 'webidl_free': '_p', 'stackRestore': '_p', '__cxa_is_pointer_type': '_p', 'stackSave': 'p', diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py index 16bc5520cf2a6..186544582f89c 100644 --- a/tools/webidl_binder.py +++ b/tools/webidl_binder.py @@ -8,6 +8,7 @@ https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html """ +import argparse import os import sys from typing import List @@ -55,8 +56,15 @@ def getExtendedAttribute(self, _name): return None -input_file = sys.argv[1] -output_base = sys.argv[2] +parser = argparse.ArgumentParser() +parser.add_argument('--wasm64', action='store_true', default=False, + help='Build for wasm64') +parser.add_argument('infile') +parser.add_argument('outfile') +options = parser.parse_args() + +input_file = options.infile +output_base = options.outfile cpp_output = output_base + '.cpp' js_output = output_base + '.js' @@ -89,7 +97,7 @@ def getExtendedAttribute(self, _name): #include #include -EM_JS_DEPS(webidl_binder, "$intArrayFromString,$UTF8ToString"); +EM_JS_DEPS(webidl_binder, "$intArrayFromString,$UTF8ToString,$alignMemory"); '''] mid_c = [''' @@ -214,7 +222,7 @@ def build_constructor(name): assert(ensureCache.buffer); var bytes = view.BYTES_PER_ELEMENT; var len = array.length * bytes; - len = (len + 7) & -8; // keep things aligned to 8 byte boundaries + len = alignMemory(len, 8); // keep things aligned to 8 byte boundaries var ret; if (ensureCache.pos + len >= ensureCache.size) { // we failed to allocate in the buffer, ensureCache time around :( @@ -230,13 +238,7 @@ def build_constructor(name): return ret; }, copy(array, view, offset) { - offset >>>= 0; - var bytes = view.BYTES_PER_ELEMENT; - switch (bytes) { - case 2: offset >>>= 1; break; - case 4: offset >>>= 2; break; - case 8: offset >>>= 3; break; - } + offset /= view.BYTES_PER_ELEMENT; for (var i = 0; i < array.length; i++) { view[offset + i] = array[i]; } @@ -415,6 +417,11 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, call_postfix = '' if return_type != 'Void' and not constructor: call_prefix = 'return ' + + ptr_rtn = constructor or return_type in interfaces or return_type == 'String' + if options.wasm64 and ptr_rtn: + call_postfix += ')' + if not constructor: if return_type in interfaces: call_prefix += 'wrapPointer(' @@ -426,10 +433,16 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, call_prefix += '!!(' call_postfix += ')' + if options.wasm64 and ptr_rtn: + call_prefix += 'Number(' + args = [(all_args[i].identifier.name if isinstance(all_args[i], WebIDL.IDLArgument) else ('arg%d' % i)) for i in range(max_args)] if not constructor and not is_static: body = ' var self = this.ptr;\n' - pre_arg = ['self'] + if options.wasm64: + pre_arg = ['BigInt(self)'] + else: + pre_arg = ['self'] else: body = '' pre_arg = [] @@ -437,6 +450,10 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, if any(arg.type.isString() or arg.type.isArray() for arg in all_args): body += ' ensureCache.prepare();\n' + def is_ptr_arg(i): + t = all_args[i].type + return (t.isArray() or t.isAny() or t.isString() or t.isObject() or t.isInterface()) + for i, (js_arg, arg) in enumerate(zip(args, all_args)): if i >= min_args: optional = True @@ -500,9 +517,11 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, if do_default: if not (arg.type.isArray() and not array_attribute): - body += " if ({0} && typeof {0} === 'object') {0} = {0}.ptr;\n".format(js_arg) + body += f" if ({js_arg} && typeof {js_arg} === 'object') {js_arg} = {js_arg}.ptr;\n" if arg.type.isString(): body += " else {0} = ensureString({0});\n".format(js_arg) + if options.wasm64 and is_ptr_arg(i): + body += f' if ({args[i]} === null) {args[i]} = 0;\n' else: # an array can be received here arg_type = arg.type.name @@ -517,18 +536,45 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, elif arg_type == 'Double': body += " if (typeof {0} == 'object') {{ {0} = ensureFloat64({0}); }}\n".format(js_arg) + call_args = pre_arg + + for i, arg in enumerate(args): + if options.wasm64 and is_ptr_arg(i): + arg = f'BigInt({arg})' + call_args.append(arg) + c_names = {} + + def make_call_args(i): + if pre_arg: + i += 1 + return ', '.join(call_args[:i]) + for i in range(min_args, max_args): - c_names[i] = 'emscripten_bind_%s_%d' % (bindings_name, i) - body += ' if (%s === undefined) { %s%s(%s)%s%s }\n' % (args[i], call_prefix, '_' + c_names[i], ', '.join(pre_arg + args[:i]), call_postfix, '' if 'return ' in call_prefix else '; ' + (cache or ' ') + 'return') - c_names[max_args] = 'emscripten_bind_%s_%d' % (bindings_name, max_args) - body += ' %s%s(%s)%s;\n' % (call_prefix, '_' + c_names[max_args], ', '.join(pre_arg + args), call_postfix) + c_names[i] = f'emscripten_bind_{bindings_name}_{i}' + if 'return ' in call_prefix: + after_call = '' + else: + after_call = '; ' + cache + 'return' + args_for_call = make_call_args(i) + body += ' if (%s === undefined) { %s_%s(%s)%s%s }\n' % (args[i], call_prefix, c_names[i], + args_for_call, + call_postfix, after_call) + dbg(call_prefix) + c_names[max_args] = f'emscripten_bind_{bindings_name}_{max_args}' + args_for_call = make_call_args(len(args)) + body += ' %s_%s(%s)%s;\n' % (call_prefix, c_names[max_args], args_for_call, call_postfix) if cache: - body += ' ' + cache + '\n' + body += f' {cache}\n' + + if constructor: + declare_name = ' ' + func_name + else: + declare_name = '' mid_js.append(r'''function%s(%s) { %s }; -''' % ((' ' + func_name) if constructor else '', ', '.join(args), body[:-1])) +''' % (declare_name, ', '.join(args), body[:-1])) # C @@ -538,7 +584,8 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, continue sig = list(map(full_typename, raw)) if array_attribute: - sig = [x.replace('[]', '') for x in sig] # for arrays, ignore that this is an array - our get/set methods operate on the elements + # for arrays, ignore that this is an array - our get/set methods operate on the elements + sig = [x.replace('[]', '') for x in sig] c_arg_types = list(map(type_to_c, sig)) c_class_name = type_to_c(class_name, non_pointing=True)