From a919d611e832bfee46fc34762f5ded2006c9f16d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 1 Sep 2022 07:19:07 +0200 Subject: [PATCH] [wasm] JS interop without JS code gen - CSP friendly (#74441) - interpret signature data instead of generating the JS code - tests --- .../src/Interop/Browser/Interop.Runtime.cs | 2 + .../JavaScript/JSFunctionBinding.cs | 20 +- .../JavaScript/JSImportExportTest.cs | 14 ++ .../JavaScript/JavaScriptTestHelper.cs | 42 ++++ .../JavaScript/JavaScriptTestHelper.mjs | 25 ++ src/mono/sample/wasm/Directory.Build.targets | 7 +- src/mono/wasm/runtime/corebindings.c | 2 + src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 1 + src/mono/wasm/runtime/exports-linker.ts | 3 +- src/mono/wasm/runtime/invoke-cs.ts | 200 ++++++++++++---- src/mono/wasm/runtime/invoke-js.ts | 223 +++++++++++++----- src/mono/wasm/runtime/marshal-to-cs.ts | 119 +++------- src/mono/wasm/runtime/marshal-to-js.ts | 123 +++------- src/mono/wasm/runtime/marshal.ts | 1 + .../wasm/runtime/net6-legacy/corebindings.ts | 6 +- src/mono/wasm/runtime/types.ts | 9 +- 16 files changed, 501 insertions(+), 296 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 693a88b8e9ff95..27762b7ac5d102 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -15,6 +15,8 @@ internal static unsafe partial class Runtime [MethodImpl(MethodImplOptions.InternalCall)] public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data); [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeImport(IntPtr fn_handle, void* data); + [MethodImpl(MethodImplOptions.InternalCall)] public static extern unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void MarshalPromise(void* data); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs index 8ec27641bcaf03..a7c099a5aa8f81 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSFunctionBinding.cs @@ -20,7 +20,7 @@ public sealed partial class JSFunctionBinding #region intentionally opaque internal structure internal unsafe JSBindingHeader* Header; internal unsafe JSBindingType* Sigs;// points to first arg, not exception, not result - internal JSObject? JSFunction; + internal IntPtr FnHandle; [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct JSBindingHeader @@ -121,7 +121,7 @@ internal unsafe JSBindingType this[int position] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InvokeJS(JSFunctionBinding signature, Span arguments) { - InvokeJSImpl(signature.JSFunction!, arguments); + InvokeImportImpl(signature.FnHandle, arguments); } /// @@ -167,6 +167,20 @@ internal static unsafe void InvokeJSImpl(JSObject jsFunction, Span arguments) + { + fixed (JSMarshalerArgument* ptr = arguments) + { + Interop.Runtime.InvokeImport(fnHandle, ptr); + ref JSMarshalerArgument exceptionArg = ref arguments[0]; + if (exceptionArg.slot.Type != MarshalerType.None) + { + JSHostImplementation.ThrowException(ref exceptionArg); + } + } + } + [MethodImpl(MethodImplOptions.NoInlining)] internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan signatures) { @@ -176,7 +190,7 @@ internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, if (isException != 0) throw new JSException((string)exceptionMessage); - signature.JSFunction = JSHostImplementation.CreateCSOwnedProxy(jsFunctionHandle); + signature.FnHandle = jsFunctionHandle; return signature; } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs index a8966dcaf521d0..dded93a4059ab7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -79,6 +79,20 @@ public unsafe void OutOfRange() Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message); } + [Fact] + public unsafe void OptimizedPaths() + { + JavaScriptTestHelper.optimizedReached = 0; + JavaScriptTestHelper.invoke0V(); + Assert.Equal(1, JavaScriptTestHelper.optimizedReached); + JavaScriptTestHelper.invoke1V(42); + Assert.Equal(43, JavaScriptTestHelper.optimizedReached); + Assert.Equal(124, JavaScriptTestHelper.invoke1R(123)); + Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached); + Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16)); + Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached); + } + #region Get/Set Property diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index b5a89b7f48fe98..cca24202da298d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -45,6 +45,48 @@ public static DateTime Now() return DateTime.Now; } + // the methods in the region have signature for which we have optimized path in the call marshaler + // it's the combination of number of arguments and void vs result + // see mono_wasm_bind_js_function and mono_wasm_bind_cs_function + #region Optimized + + public static int optimizedReached = 0; + [JSExport] + public static void Optimized0V() + { + optimizedReached++; + } + [JSImport("invoke0V", "JavaScriptTestHelper")] + public static partial void invoke0V(); + + [JSExport] + public static void Optimized1V(int a1) + { + optimizedReached+= a1; + } + [JSImport("invoke1V", "JavaScriptTestHelper")] + public static partial void invoke1V(int a1); + + [JSExport] + public static int Optimized1R(int a1) + { + optimizedReached += a1; + return a1 + 1; + } + [JSImport("invoke1R", "JavaScriptTestHelper")] + public static partial int invoke1R(int a1); + + [JSExport] + public static int Optimized2R(int a1, int a2) + { + optimizedReached += a1+ a2; + return a1 + a2 +1; + } + [JSImport("invoke2R", "JavaScriptTestHelper")] + public static partial int invoke2R(int a1, int a2); + + #endregion + [JSImport("create_function", "JavaScriptTestHelper")] [return: JSMarshalAs>] public static partial Func createMath([JSMarshalAs] string a, [JSMarshalAs] string b, [JSMarshalAs] string code); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index 314e715e16b815..db9d18c5adbc35 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -147,6 +147,31 @@ export function getClass1() { return cname; } let dllExports; + +export function invoke0V() { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper['Optimized0V']; + fn(); +} + +export function invoke1V(arg1) { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper['Optimized1V']; + fn(arg1); +} + +export function invoke1R(arg1) { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper['Optimized1R']; + return fn(arg1); +} + +export function invoke2R(arg1, arg2) { + const JavaScriptTestHelper = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper; + const fn = JavaScriptTestHelper['Optimized2R']; + return fn(arg1, arg2); +} + export function invoke1(arg1, name) { if (globalThis.gc) { // console.log('globalThis.gc'); diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index cf7244f6ecb464..c01bfada90c654 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -41,7 +41,12 @@ - + + diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index 1d4267a6fb8db7..c9ebdc01abd23b 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -30,6 +30,7 @@ extern void mono_wasm_typed_array_from_ref (int ptr, int begin, int end, int byt extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString **module_name, void *signature, int* function_js_handle, int *is_exception, MonoObject **result); extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data); +extern void mono_wasm_invoke_import(int fn_handle, void *data); extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result); extern void mono_wasm_marshal_promise(void *data); @@ -49,6 +50,7 @@ void core_initialize_internals () mono_add_internal_call ("Interop/Runtime::BindJSFunction", mono_wasm_bind_js_function); mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function); + mono_add_internal_call ("Interop/Runtime::InvokeImport", mono_wasm_invoke_import); mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function); mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise); mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root); diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index bde650aea36eca..9cf4b57ebb724b 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -88,6 +88,7 @@ const linked_functions = [ "mono_wasm_compile_function_ref", "mono_wasm_bind_js_function", "mono_wasm_invoke_bound_function", + "mono_wasm_invoke_import", "mono_wasm_bind_cs_function", "mono_wasm_marshal_promise", diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 22f8a66cdee89f..3c030f2f4f9307 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -6,7 +6,7 @@ import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_was import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; -import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function } from "./invoke-js"; +import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_invoke_import } from "./invoke-js"; import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers"; import { mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref, @@ -70,6 +70,7 @@ export function export_linker(): any { mono_wasm_typed_array_from_ref, mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, + mono_wasm_invoke_import, mono_wasm_bind_cs_function, mono_wasm_marshal_promise, diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 569f0aa83c34c6..581573c6b63c76 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -2,27 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. import { Module, runtimeHelpers } from "./imports"; -import { generate_arg_marshal_to_cs } from "./marshal-to-cs"; -import { marshal_exception_to_js, generate_arg_marshal_to_js } from "./marshal-to-js"; +import { bind_arg_marshal_to_cs } from "./marshal-to-cs"; +import { marshal_exception_to_js, bind_arg_marshal_to_js } from "./marshal-to-js"; import { - JavaScriptMarshalerArgSize, - JSMarshalerTypeSize, JSMarshalerSignatureHeaderSize, - get_arg, get_sig, - get_signature_argument_count, is_args_exception, bound_cs_function_symbol, get_signature_version, MarshalerType, alloc_stack_frame, + get_arg, get_sig, get_signature_argument_count, is_args_exception, + bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, } from "./marshal"; import { mono_wasm_new_external_root } from "./roots"; import { conv_string, conv_string_root } from "./strings"; -import { mono_assert, MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature } from "./types"; +import { mono_assert, MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs } from "./types"; import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; import { wrap_error_root } from "./invoke-js"; -const exportedMethods = new Map(); - export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); - const anyModule = Module as any; try { const version = get_signature_version(signature); mono_assert(version === 1, () => `Signature version ${version} mismatch.`); @@ -50,54 +45,44 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, if (!method) throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); - const closure: any = { - method, signature, - stackSave: anyModule.stackSave, stackRestore: anyModule.stackRestore, - alloc_stack_frame, - invoke_method_and_handle_exception - }; - const bound_js_function_name = "_bound_cs_" + `${namespace}_${classname}_${methodname}`.replace(/\./g, "_").replace(/\//g, "_"); - let body = `//# sourceURL=https://dotnet.generated.invalid/${bound_js_function_name} \n`; - let bodyToCs = ""; - let converter_names = ""; - + const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); for (let index = 0; index < args_count; index++) { - const arg_offset = (index + 2) * JavaScriptMarshalerArgSize; - const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize; const sig = get_sig(signature, index + 2); - const { converters, call_body } = generate_arg_marshal_to_cs(sig, index + 2, arg_offset, sig_offset, `arguments[${index}]`, closure); - converter_names += converters; - bodyToCs += call_body; + const marshaler_type = get_signature_type(sig); + const arg_marshaler = bind_arg_marshal_to_cs(sig, marshaler_type, index + 2); + mono_assert(arg_marshaler, "ERR43: argument marshaler must be resolved"); + arg_marshalers[index] = arg_marshaler; } - const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_js(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure); - converter_names += res_converters; - body += `const { method, signature, stackSave, stackRestore, alloc_stack_frame, invoke_method_and_handle_exception ${converter_names} } = closure;\n`; - // TODO named arguments instead of arguments keyword - body += `return function ${bound_js_function_name} () {\n`; - body += "const sp = stackSave();\n"; - body += "try {\n"; - body += ` const args = alloc_stack_frame(${(args_count + 2)});\n`; + const res_sig = get_sig(signature, 1); + const res_marshaler_type = get_signature_type(res_sig); + const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); - body += bodyToCs; - - body += " invoke_method_and_handle_exception(method, args);\n"; - if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) { - body += res_call_body; + const closure: BindingClosure = { + method, + args_count, + arg_marshalers, + res_converter, + }; + let bound_fn: Function; + if (args_count == 0 && !res_converter) { + bound_fn = bind_fn_0V(closure); } - - if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) { - body += " return js_result;\n"; + else if (args_count == 1 && !res_converter) { + bound_fn = bind_fn_1V(closure); + } + else if (args_count == 1 && res_converter) { + bound_fn = bind_fn_1R(closure); + } + else if (args_count == 2 && res_converter) { + bound_fn = bind_fn_2R(closure); + } + else { + bound_fn = bind_fn(closure); } - body += "} finally {\n"; - body += " stackRestore(sp);\n"; - body += "}}"; - const factory = new Function("closure", body); - const bound_fn = factory(closure); - bound_fn[bound_cs_function_symbol] = true; + (bound_fn)[bound_cs_function_symbol] = true; - exportedMethods.set(js_fqn, bound_fn); _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); } catch (ex: any) { @@ -109,6 +94,123 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, } } +function bind_fn_0V(closure: BindingClosure) { + const method = closure.method; + (closure) = null; + return function bound_fn_0V() { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(2); + // call C# side + invoke_method_and_handle_exception(method, args); + } finally { + Module.stackRestore(sp); + } + }; +} + +function bind_fn_1V(closure: BindingClosure) { + const method = closure.method; + const marshaler1 = closure.arg_marshalers[0]!; + (closure) = null; + return function bound_fn_1V(arg1: any) { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + marshaler1(args, arg1); + + // call C# side + invoke_method_and_handle_exception(method, args); + } finally { + Module.stackRestore(sp); + } + }; +} + +function bind_fn_1R(closure: BindingClosure) { + const method = closure.method; + const marshaler1 = closure.arg_marshalers[0]!; + const res_converter = closure.res_converter!; + (closure) = null; + return function bound_fn_1R(arg1: any) { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + marshaler1(args, arg1); + + // call C# side + invoke_method_and_handle_exception(method, args); + + const js_result = res_converter(args); + return js_result; + } finally { + Module.stackRestore(sp); + } + }; +} + +function bind_fn_2R(closure: BindingClosure) { + const method = closure.method; + const marshaler1 = closure.arg_marshalers[0]!; + const marshaler2 = closure.arg_marshalers[1]!; + const res_converter = closure.res_converter!; + (closure) = null; + return function bound_fn_2R(arg1: any, arg2: any) { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(4); + marshaler1(args, arg1); + marshaler2(args, arg2); + + // call C# side + invoke_method_and_handle_exception(method, args); + + const js_result = res_converter(args); + return js_result; + } finally { + Module.stackRestore(sp); + } + }; +} + +function bind_fn(closure: BindingClosure) { + const args_count = closure.args_count; + const arg_marshalers = closure.arg_marshalers; + const res_converter = closure.res_converter; + const method = closure.method; + (closure) = null; + return function bound_fn(...js_args: any[]) { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(2 + args_count); + for (let index = 0; index < args_count; index++) { + const marshaler = arg_marshalers[index]; + if (marshaler) { + const js_arg = js_args[index]; + marshaler(args, js_arg); + } + } + + // call C# side + invoke_method_and_handle_exception(method, args); + + if (res_converter) { + const js_result = res_converter(args); + return js_result; + } + } finally { + Module.stackRestore(sp); + } + }; +} + +type BindingClosure = { + args_count: number, + method: MonoMethod, + arg_marshalers: (BoundMarshalerToCs)[], + res_converter: BoundMarshalerToJs | undefined, +} + export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { const fail = cwraps.mono_wasm_invoke_method_bound(method, args); if (fail) throw new Error("ERR24: Unexpected error: " + conv_string(fail)); diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 7e31a296fdb987..b4ef450843e742 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -1,17 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./gc-handles"; -import { marshal_exception_to_cs, generate_arg_marshal_to_cs } from "./marshal-to-cs"; -import { get_signature_argument_count, JavaScriptMarshalerArgSize, bound_js_function_symbol, JSMarshalerTypeSize, get_sig, JSMarshalerSignatureHeaderSize, get_signature_version, MarshalerType, get_signature_type } from "./marshal"; +import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs"; +import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, MarshalerType, get_signature_type, imported_js_function_symbol } from "./marshal"; import { setI32 } from "./memory"; import { conv_string_root, js_string_to_mono_string_root } from "./strings"; -import { mono_assert, JSHandle, MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot } from "./types"; +import { mono_assert, MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle } from "./types"; import { Int32Ptr } from "./types/emscripten"; import { IMPORTS, INTERNAL, Module, runtimeHelpers } from "./imports"; -import { generate_arg_marshal_to_js } from "./marshal-to-js"; +import { bind_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; import { mono_wasm_symbolicate_string } from "./logging"; +import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; + +const fn_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, we never free bound functions export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const function_name_root = mono_wasm_new_external_root(function_name), @@ -29,67 +31,59 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const fn = mono_wasm_lookup_function(js_function_name, js_module_name); const args_count = get_signature_argument_count(signature); - const closure: any = { fn, marshal_exception_to_cs, signature }; - const bound_js_function_name = "_bound_js_" + js_function_name.replace(/\./g, "_"); - let body = `//# sourceURL=https://dotnet.generated.invalid/${bound_js_function_name} \n`; - let converter_names = ""; - - - let bodyToJs = ""; - let pass_args = ""; + const arg_marshalers: (BoundMarshalerToJs)[] = new Array(args_count); + const arg_cleanup: (Function | undefined)[] = new Array(args_count); + let has_cleanup = false; for (let index = 0; index < args_count; index++) { - const arg_offset = (index + 2) * JavaScriptMarshalerArgSize; - const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize; - const arg_name = `arg${index}`; const sig = get_sig(signature, index + 2); - const { converters, call_body } = generate_arg_marshal_to_js(sig, index + 2, arg_offset, sig_offset, arg_name, closure); - converter_names += converters; - bodyToJs += call_body; - if (pass_args === "") { - pass_args += arg_name; - } else { - pass_args += `, ${arg_name}`; + const marshaler_type = get_signature_type(sig); + const arg_marshaler = bind_arg_marshal_to_js(sig, marshaler_type, index + 2); + mono_assert(arg_marshaler, "ERR42: argument marshaler must be resolved"); + arg_marshalers[index] = arg_marshaler; + if (marshaler_type === MarshalerType.Span) { + arg_cleanup[index] = (js_arg: any) => { + if (js_arg) { + js_arg.dispose(); + } + }; + has_cleanup = true; } } - const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_cs(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure); - converter_names += res_converters; - - body += `const { signature, fn, marshal_exception_to_cs ${converter_names} } = closure;\n`; - body += `return function ${bound_js_function_name} (args) { try {\n`; - // body += `console.log("${bound_js_function_name}")\n`; - body += bodyToJs; + const res_sig = get_sig(signature, 1); + const res_marshaler_type = get_signature_type(res_sig); + const res_converter = bind_arg_marshal_to_cs(res_sig, res_marshaler_type, 1); - - if (res_marshaler_type === MarshalerType.Void) { - body += ` const js_result = fn(${pass_args});\n`; - body += ` if (js_result !== undefined) throw new Error('Function ${js_function_name} returned unexpected value, C# signature is void');\n`; + const closure: BindingClosure = { + fn, + args_count, + arg_marshalers, + res_converter, + has_cleanup, + arg_cleanup + }; + let bound_fn: Function; + if (args_count == 0 && !res_converter) { + bound_fn = bind_fn_0V(closure); } - else if (res_marshaler_type === MarshalerType.Discard) { - body += ` fn(${pass_args});\n`; + else if (args_count == 1 && !has_cleanup && !res_converter) { + bound_fn = bind_fn_1V(closure); } - else { - body += ` const js_result = fn(${pass_args});\n`; - body += res_call_body; + else if (args_count == 1 && !has_cleanup && res_converter) { + bound_fn = bind_fn_1R(closure); } - - for (let index = 0; index < args_count; index++) { - const sig = get_sig(signature, index + 2); - const marshaler_type = get_signature_type(sig); - if (marshaler_type == MarshalerType.Span) { - const arg_name = `arg${index}`; - body += ` ${arg_name}.dispose();\n`; - } + else if (args_count == 2 && !has_cleanup && res_converter) { + bound_fn = bind_fn_2R(closure); + } + else { + bound_fn = bind_fn(closure); } - body += "} catch (ex) {\n"; - body += " marshal_exception_to_cs(args, ex);\n"; - body += "}}"; - const factory = new Function("closure", body); - const bound_fn = factory(closure); - bound_fn[bound_js_function_symbol] = true; - const bound_function_handle = mono_wasm_get_js_handle(bound_fn)!; - setI32(function_js_handle, bound_function_handle); - } catch (ex) { + (bound_fn)[imported_js_function_symbol] = true; + const fn_handle = fn_wrapper_by_fn_handle.length; + fn_wrapper_by_fn_handle.push(bound_fn); + setI32(function_js_handle, fn_handle); + } catch (ex: any) { + Module.printErr(ex.toString()); wrap_error_root(is_exception, ex, resultRoot); } finally { resultRoot.release(); @@ -97,12 +91,129 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ } } +function bind_fn_0V(closure: BindingClosure) { + const fn = closure.fn; + (closure) = null; + return function bound_fn_0V(args: JSMarshalerArguments) { + try { + // call user function + fn(); + } catch (ex) { + marshal_exception_to_cs(args, ex); + } + }; +} + +function bind_fn_1V(closure: BindingClosure) { + const fn = closure.fn; + const marshaler1 = closure.arg_marshalers[0]!; + (closure) = null; + return function bound_fn_1V(args: JSMarshalerArguments) { + try { + const arg1 = marshaler1(args); + // call user function + fn(arg1); + } catch (ex) { + marshal_exception_to_cs(args, ex); + } + }; +} + +function bind_fn_1R(closure: BindingClosure) { + const fn = closure.fn; + const marshaler1 = closure.arg_marshalers[0]!; + const res_converter = closure.res_converter!; + (closure) = null; + return function bound_fn_1R(args: JSMarshalerArguments) { + try { + const arg1 = marshaler1(args); + // call user function + const js_result = fn(arg1); + res_converter(args, js_result); + } catch (ex) { + marshal_exception_to_cs(args, ex); + } + }; +} + +function bind_fn_2R(closure: BindingClosure) { + const fn = closure.fn; + const marshaler1 = closure.arg_marshalers[0]!; + const marshaler2 = closure.arg_marshalers[1]!; + const res_converter = closure.res_converter!; + (closure) = null; + return function bound_fn_2R(args: JSMarshalerArguments) { + try { + const arg1 = marshaler1(args); + const arg2 = marshaler2(args); + // call user function + const js_result = fn(arg1, arg2); + res_converter(args, js_result); + } catch (ex) { + marshal_exception_to_cs(args, ex); + } + }; +} + +function bind_fn(closure: BindingClosure) { + const args_count = closure.args_count; + const arg_marshalers = closure.arg_marshalers; + const res_converter = closure.res_converter; + const arg_cleanup = closure.arg_cleanup; + const has_cleanup = closure.has_cleanup; + const fn = closure.fn; + (closure) = null; + return function bound_fn(args: JSMarshalerArguments) { + try { + const js_args = new Array(args_count); + for (let index = 0; index < args_count; index++) { + const marshaler = arg_marshalers[index]!; + const js_arg = marshaler(args); + js_args[index] = js_arg; + } + + // call user function + const js_result = fn(...js_args); + + if (res_converter) { + res_converter(args, js_result); + } + + if (has_cleanup) { + for (let index = 0; index < args_count; index++) { + const cleanup = arg_cleanup[index]; + if (cleanup) { + cleanup(js_args[index]); + } + } + } + } catch (ex) { + marshal_exception_to_cs(args, ex); + } + }; +} + +type BindingClosure = { + fn: Function, + args_count: number, + arg_marshalers: (BoundMarshalerToJs)[], + res_converter: BoundMarshalerToCs | undefined, + has_cleanup: boolean, + arg_cleanup: (Function | undefined)[] +} + export function mono_wasm_invoke_bound_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void { const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle); mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`); bound_fn(args); } +export function mono_wasm_invoke_import(fn_handle: JSFnHandle, args: JSMarshalerArguments): void { + const bound_fn = fn_wrapper_by_fn_handle[fn_handle]; + mono_assert(bound_fn, () => `Imported function handle expected ${fn_handle}`); + bound_fn(args); +} + export function mono_wasm_set_module_imports(module_name: string, moduleImports: any) { importedModules.set(module_name, moduleImports); if (runtimeHelpers.diagnosticTracing) diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index e7cd7cf7e02342..9b3b05d8b558de 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -8,14 +8,15 @@ import { Module, runtimeHelpers } from "./imports"; import { ManagedError, set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_b8, set_arg_date, - set_arg_length, get_arg, get_signature_type, get_signature_arg1_type, get_signature_arg2_type, cs_to_js_marshalers, js_to_cs_marshalers, + set_arg_length, get_arg, get_signature_arg1_type, get_signature_arg2_type, js_to_cs_marshalers, get_signature_res_type, bound_js_function_symbol, set_arg_u16, array_element_size, get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, MarshalerType, set_arg_i64_big, set_arg_intptr, IDisposable, - set_arg_element_type, ManagedObject + set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize } from "./marshal"; +import { get_marshaler_to_js_by_type } from "./marshal-to-js"; import { _zero_region } from "./memory"; import { js_string_to_mono_string_root } from "./strings"; -import { mono_assert, GCHandle, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs } from "./types"; +import { mono_assert, GCHandle, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs } from "./types"; import { TypedArray } from "./types/emscripten"; export function initialize_marshalers_to_cs(): void { @@ -49,103 +50,39 @@ export function initialize_marshalers_to_cs(): void { } } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function generate_arg_marshal_to_cs(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): { - converters: string, - call_body: string, - marshaler_type: MarshalerType -} { - let converters = ""; - let converter_types = ""; - let call_body = ""; - const converter_name = "converter" + index; - let converter_name_arg1 = "null"; - let converter_name_arg2 = "null"; - let converter_name_arg3 = "null"; - let converter_name_res = "null"; - - let marshaler_type = get_signature_type(sig); +export function bind_arg_marshal_to_cs(sig: JSMarshalerType, marshaler_type: MarshalerType, index: number): BoundMarshalerToCs | undefined { if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { - return { - converters, - call_body, - marshaler_type - }; + return undefined; } + let res_marshaler: MarshalerToCs | undefined = undefined; + let arg1_marshaler: MarshalerToJs | undefined = undefined; + let arg2_marshaler: MarshalerToJs | undefined = undefined; + let arg3_marshaler: MarshalerToJs | undefined = undefined; + arg1_marshaler = get_marshaler_to_js_by_type(get_signature_arg1_type(sig)); + arg2_marshaler = get_marshaler_to_js_by_type(get_signature_arg2_type(sig)); + arg3_marshaler = get_marshaler_to_js_by_type(get_signature_arg3_type(sig)); const marshaler_type_res = get_signature_res_type(sig); - if (marshaler_type_res !== MarshalerType.None) { - const converter = js_to_cs_marshalers.get(marshaler_type_res); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`); - - - if (marshaler_type != MarshalerType.Nullable) { - converter_name_res = "converter" + index + "_res"; - converters += ", " + converter_name_res; - converter_types += " " + MarshalerType[marshaler_type_res]; - closure[converter_name_res] = converter; - } - else { - marshaler_type = marshaler_type_res; - } - } - - const marshaler_type_arg1 = get_signature_arg1_type(sig); - if (marshaler_type_arg1 !== MarshalerType.None) { - const converter = cs_to_js_marshalers.get(marshaler_type_arg1); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`); - - converter_name_arg1 = "converter" + index + "_arg1"; - converters += ", " + converter_name_arg1; - converter_types += " " + MarshalerType[marshaler_type_arg1]; - closure[converter_name_arg1] = converter; - } - - const marshaler_type_arg2 = get_signature_arg2_type(sig); - if (marshaler_type_arg2 !== MarshalerType.None) { - const converter = cs_to_js_marshalers.get(marshaler_type_arg2); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`); - - converter_name_arg2 = "converter" + index + "_arg2"; - converters += ", " + converter_name_arg2; - converter_types += " " + MarshalerType[marshaler_type_arg2]; - closure[converter_name_arg2] = converter; + res_marshaler = get_marshaler_to_cs_by_type(marshaler_type_res); + if (marshaler_type === MarshalerType.Nullable) { + // nullable has nested type information, it's stored in res slot of the signature. The marshaler is the same as for non-nullable primitive type. + marshaler_type = marshaler_type_res; } + const converter = get_marshaler_to_cs_by_type(marshaler_type)!; - const marshaler_type_arg3 = get_signature_arg3_type(sig); - if (marshaler_type_arg3 !== MarshalerType.None) { - const converter = cs_to_js_marshalers.get(marshaler_type_arg3); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`); + const arg_offset = index * JavaScriptMarshalerArgSize; + return (args: JSMarshalerArguments, value: any) => { + converter(args + arg_offset, value, sig, res_marshaler, arg1_marshaler, arg2_marshaler, arg3_marshaler); + }; +} - converter_name_arg3 = "converter" + index + "_arg3"; - converters += ", " + converter_name_arg3; - converter_types += " " + MarshalerType[marshaler_type_arg3]; - closure[converter_name_arg3] = converter; +export function get_marshaler_to_cs_by_type(marshaler_type: MarshalerType): MarshalerToCs | undefined { + if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { + return undefined; } - const converter = js_to_cs_marshalers.get(marshaler_type); - - const arg_type_name = MarshalerType[marshaler_type]; - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${arg_type_name} (${marshaler_type}) at ${index} `); - - converters += ", " + converter_name; - converter_types += " " + arg_type_name; - closure[converter_name] = converter; - - - if (marshaler_type == MarshalerType.Task) { - call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`; - } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) { - call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg2}); // ${converter_types} \n`; - } else { - call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}); // ${converter_types} \n`; - } - - return { - converters, - call_body, - marshaler_type - }; + mono_assert(converter && typeof converter === "function", () => `ERR30: Unknown converter for type ${marshaler_type}`); + return converter; } function _marshal_bool_to_cs(arg: JSMarshalerArgument, value: any): void { diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index 318d10580ffec0..b3f469fc05f5ce 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -9,13 +9,14 @@ import { ManagedObject, ManagedError, get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32, get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type, - get_signature_arg2_type, get_signature_arg1_type, get_signature_type, cs_to_js_marshalers, js_to_cs_marshalers, + get_signature_arg2_type, get_signature_arg1_type, cs_to_js_marshalers, get_signature_res_type, get_arg_u16, array_element_size, get_string_root, - ArraySegment, Span, MemoryViewType, get_signature_arg3_type, MarshalerType, get_arg_i64_big, get_arg_intptr, get_arg_element_type + ArraySegment, Span, MemoryViewType, get_signature_arg3_type, MarshalerType, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize } from "./marshal"; import { conv_string_root } from "./strings"; -import { mono_assert, JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs } from "./types"; +import { mono_assert, JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs } from "./types"; import { TypedArray } from "./types/emscripten"; +import { get_marshaler_to_cs_by_type } from "./marshal-to-cs"; export function initialize_marshalers_to_js(): void { if (cs_to_js_marshalers.size == 0) { @@ -48,98 +49,40 @@ export function initialize_marshalers_to_js(): void { } } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function generate_arg_marshal_to_js(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): { - converters: string, - call_body: string, - marshaler_type: MarshalerType -} { - let converters = ""; - let converter_types = ""; - let call_body = ""; - const converter_name = "converter" + index; - let converter_name_arg1 = "null"; - let converter_name_arg2 = "null"; - let converter_name_arg3 = "null"; - let converter_name_res = "null"; - - let marshaler_type = get_signature_type(sig); +export function bind_arg_marshal_to_js(sig: JSMarshalerType, marshaler_type: MarshalerType, index: number): BoundMarshalerToJs | undefined { if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { - return { - converters, - call_body, - marshaler_type - }; - } - - const marshaler_type_res = get_signature_res_type(sig); - if (marshaler_type_res !== MarshalerType.None) { - const converter = cs_to_js_marshalers.get(marshaler_type_res); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`); - - if (marshaler_type != MarshalerType.Nullable) { - converter_name_res = "converter" + index + "_res"; - converters += ", " + converter_name_res; - converter_types += " " + MarshalerType[marshaler_type_res]; - closure[converter_name_res] = converter; - } else { - marshaler_type = marshaler_type_res; - } + return undefined; } - const marshaler_type_arg1 = get_signature_arg1_type(sig); - if (marshaler_type_arg1 !== MarshalerType.None) { - const converter = js_to_cs_marshalers.get(marshaler_type_arg1); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`); + let res_marshaler: MarshalerToJs | undefined = undefined; + let arg1_marshaler: MarshalerToCs | undefined = undefined; + let arg2_marshaler: MarshalerToCs | undefined = undefined; + let arg3_marshaler: MarshalerToCs | undefined = undefined; - converter_name_arg1 = "converter" + index + "_arg1"; - converters += ", " + converter_name_arg1; - converter_types += " " + MarshalerType[marshaler_type_arg1]; - closure[converter_name_arg1] = converter; - } - - const marshaler_type_arg2 = get_signature_arg2_type(sig); - if (marshaler_type_arg2 !== MarshalerType.None) { - const converter = js_to_cs_marshalers.get(marshaler_type_arg2); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`); - - converter_name_arg2 = "converter" + index + "_arg2"; - converters += ", " + converter_name_arg2; - converter_types += " " + MarshalerType[marshaler_type_arg2]; - closure[converter_name_arg2] = converter; + arg1_marshaler = get_marshaler_to_cs_by_type(get_signature_arg1_type(sig)); + arg2_marshaler = get_marshaler_to_cs_by_type(get_signature_arg2_type(sig)); + arg3_marshaler = get_marshaler_to_cs_by_type(get_signature_arg3_type(sig)); + const marshaler_type_res = get_signature_res_type(sig); + res_marshaler = get_marshaler_to_js_by_type(marshaler_type_res); + if (marshaler_type === MarshalerType.Nullable) { + // nullable has nested type information, it's stored in res slot of the signature. The marshaler is the same as for non-nullable primitive type. + marshaler_type = marshaler_type_res; } + const converter = get_marshaler_to_js_by_type(marshaler_type)!; - const marshaler_type_arg3 = get_signature_arg3_type(sig); - if (marshaler_type_arg3 !== MarshalerType.None) { - const converter = js_to_cs_marshalers.get(marshaler_type_arg3); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`); + const arg_offset = index * JavaScriptMarshalerArgSize; + return (args: JSMarshalerArguments) => { + return converter(args + arg_offset, sig, res_marshaler, arg1_marshaler, arg2_marshaler, arg3_marshaler); + }; +} - converter_name_arg3 = "converter" + index + "_arg3"; - converters += ", " + converter_name_arg3; - converter_types += " " + MarshalerType[marshaler_type_arg3]; - closure[converter_name_arg3] = converter; +export function get_marshaler_to_js_by_type(marshaler_type: MarshalerType): MarshalerToJs | undefined { + if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) { + return undefined; } - const converter = cs_to_js_marshalers.get(marshaler_type); - mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type} at ${index} `); - - converters += ", " + converter_name; - converter_types += " " + MarshalerType[marshaler_type]; - closure[converter_name] = converter; - - if (marshaler_type == MarshalerType.Task) { - call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`; - } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) { - call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg3}); // ${converter_types} \n`; - } else { - call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}); // ${converter_types} \n`; - } - - return { - converters, - call_body, - marshaler_type - }; + mono_assert(converter && typeof converter === "function", () => `ERR41: Unknown converter for type ${marshaler_type}`); + return converter; } function _marshal_bool_to_js(arg: JSMarshalerArgument): boolean | null { @@ -266,7 +209,7 @@ export function marshal_task_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType // when we arrived here from _marshal_cs_object_to_js res_converter = cs_to_js_marshalers.get(type); } - mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]} `); + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]} `); // this is already resolved const val = res_converter(arg); @@ -297,7 +240,7 @@ export function marshal_task_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType // when we arrived here from _marshal_cs_object_to_js res_converter = cs_to_js_marshalers.get(type); } - mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]}`); + mono_assert(res_converter, () => `Unknown sub_converter for type ${MarshalerType[type]}`); const js_value = res_converter!(argInner); orig_resolve(js_value); @@ -329,7 +272,7 @@ export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void { else if (value_type !== MarshalerType.Task) { // this is already resolved task const sub_converter = cs_to_js_marshalers.get(value_type); - mono_assert(sub_converter, () => `Unknow sub_converter for type ${MarshalerType[value_type]} `); + mono_assert(sub_converter, () => `Unknown sub_converter for type ${MarshalerType[value_type]} `); const data = sub_converter(arg_value); promise_control.resolve(data); } @@ -438,7 +381,7 @@ function _marshal_cs_object_to_js(arg: JSMarshalerArgument): any { // other types const converter = cs_to_js_marshalers.get(marshaler_type); - mono_assert(converter, () => `Unknow converter for type ${MarshalerType[marshaler_type]}`); + mono_assert(converter, () => `Unknown converter for type ${MarshalerType[marshaler_type]}`); return converter(arg); } diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 77bec6b6aa762f..591f3976292164 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -12,6 +12,7 @@ export const cs_to_js_marshalers = new Map(); export const js_to_cs_marshalers = new Map(); export const bound_cs_function_symbol = Symbol.for("wasm bound_cs_function"); export const bound_js_function_symbol = Symbol.for("wasm bound_js_function"); +export const imported_js_function_symbol = Symbol.for("wasm imported_js_function"); /** * JSFunctionSignature is pointer to [ diff --git a/src/mono/wasm/runtime/net6-legacy/corebindings.ts b/src/mono/wasm/runtime/net6-legacy/corebindings.ts index 759f84e1a307cd..1850aeaa611a5d 100644 --- a/src/mono/wasm/runtime/net6-legacy/corebindings.ts +++ b/src/mono/wasm/runtime/net6-legacy/corebindings.ts @@ -14,9 +14,9 @@ const fn_signatures: SigLine[] = [ [true, "_get_cs_owned_object_by_js_handle_ref", "GetCSOwnedObjectByJSHandleRef", "iim"], [true, "_get_cs_owned_object_js_handle_ref", "GetCSOwnedObjectJSHandleRef", "mi"], [true, "_try_get_cs_owned_object_js_handle_ref", "TryGetCSOwnedObjectJSHandleRef", "mi"], - [false, "_create_cs_owned_proxy_ref", "CreateCSOwnedProxyRef", "iiim"], + [true, "_create_cs_owned_proxy_ref", "CreateCSOwnedProxyRef", "iiim"], - [false, "_get_js_owned_object_by_gc_handle_ref", "GetJSOwnedObjectByGCHandleRef", "im"], + [true, "_get_js_owned_object_by_gc_handle_ref", "GetJSOwnedObjectByGCHandleRef", "im"], [true, "_get_js_owned_object_gc_handle_ref", "GetJSOwnedObjectGCHandleRef", "m"], [true, "_create_tcs", "CreateTaskSource", ""], @@ -30,7 +30,7 @@ const fn_signatures: SigLine[] = [ [true, "_create_date_time_ref", "CreateDateTimeRef", "dm"], [true, "_create_uri_ref", "CreateUriRef", "sm"], [true, "_is_simple_array_ref", "IsSimpleArrayRef", "m"], - [false, "_get_call_sig_ref", "GetCallSignatureRef", "im"], + [true, "_get_call_sig_ref", "GetCallSignatureRef", "im"], ]; export interface LegacyExports { diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index ea01f16a9e5c85..3aa07dd6550ab0 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -10,6 +10,9 @@ export type GCHandle = { export type JSHandle = { __brand: "JSHandle" } +export type JSFnHandle = { + __brand: "JSFnHandle" +} export interface MonoObject extends ManagedPointer { __brandMonoObject: "MonoObject" } @@ -413,8 +416,10 @@ export interface JavaScriptExports { install_synchronization_context(): void; } -export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs) => any; -export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, sig?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs) => void; +export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; +export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, sig?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs, arg3_converter?: MarshalerToJs) => void; +export type BoundMarshalerToJs = (args: JSMarshalerArguments) => any; +export type BoundMarshalerToCs = (args: JSMarshalerArguments, value: any) => void; export interface JSMarshalerArguments extends NativePointer { __brand: "JSMarshalerArguments"