From e218cf7233f70317a6060cf3a7e0b3e09920895b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 24 Nov 2023 15:32:29 +0100 Subject: [PATCH 01/32] Replace icalls with dllimports for JS import/export. Update javascript bits --- .../src/Interop/Browser/Interop.Runtime.cs | 15 +-- .../JavaScript/JSFunctionBinding.cs | 8 +- src/mono/wasm/runtime/invoke-cs.ts | 102 ++++++++---------- src/mono/wasm/runtime/invoke-js.ts | 33 +++--- 4 files changed, 75 insertions(+), 83 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 03c19b3d03e0..f26ee0dd485a 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; internal static partial class Interop { @@ -11,16 +12,17 @@ internal static partial class Interop // otherwise out parameters could stay un-initialized, when the method is used in inlined context internal static unsafe partial class Runtime { +#pragma warning disable SYSLIB1054 [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern unsafe void BindJSFunction(in string function_name, in string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result); + [DllImport("*", EntryPoint = "mono_wasm_bind_js_function")] + public static extern unsafe void BindJSFunction(string function_name, int function_name_length, string module_name, int module_name_length, void* signature, out IntPtr bound_function_js_handle, out int is_exception); [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); + [DllImport("*", EntryPoint = "mono_wasm_invoke_import")] + public static extern unsafe void InvokeImport(IntPtr fn_handle, void* data); + [DllImport("*", EntryPoint = "mono_wasm_bind_cs_function")] + public static extern unsafe void BindCSFunction(string fully_qualified_name, int fully_qualified_name_length, int signature_hash, void* signature, out int is_exception); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void ResolveOrRejectPromise(void* data); [MethodImpl(MethodImplOptions.InternalCall)] @@ -59,5 +61,6 @@ internal static unsafe partial class Runtime #endregion +#pragma warning restore SYSLIB1054 } } 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 3e4fe41acc05..cb625707b782 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 @@ -226,9 +226,9 @@ internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, var signature = JSHostImplementation.GetMethodSignature(signatures); - Interop.Runtime.BindJSFunction(functionName, moduleName, signature.Header, out IntPtr jsFunctionHandle, out int isException, out object exceptionMessage); + Interop.Runtime.BindJSFunction(functionName, functionName.Length, moduleName, moduleName.Length, signature.Header, out IntPtr jsFunctionHandle, out int isException); if (isException != 0) - throw new JSException((string)exceptionMessage); + throw new JSException("Runtime.BindJSFunction failed"); signature.FnHandle = jsFunctionHandle; @@ -241,10 +241,10 @@ internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQua { var signature = JSHostImplementation.GetMethodSignature(signatures); - Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage); + Interop.Runtime.BindCSFunction(fullyQualifiedName, fullyQualifiedName.Length, signatureHash, signature.Header, out int isException); if (isException != 0) { - throw new JSException((string)exceptionMessage); + throw new JSException("Runtime.BindCSFunction failed"); } JSHostImplementation.FreeMethodSignatureBuffer(signature); diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 65a0f160d7e3..6c331e8f70d9 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -11,45 +11,36 @@ import { 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, mono_wasm_new_root } from "./roots"; +import { mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; -import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; -import { Int32Ptr } from "./types/emscripten"; +import { MonoString, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; +import { CharPtr, Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; -import { assert_bindings, wrap_error_root, wrap_no_error_root } from "./invoke-js"; +import { assert_bindings, wrap_error, wrap_no_error } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; import { assert_synchronization_context } from "./pthreads/shared"; -export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { +export function mono_wasm_bind_cs_function(fully_qualified_name: CharPtr, fully_qualified_name_length: number, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr): void { assert_bindings(); - const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); const mark = startMeasure(); try { const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); - const js_fqn = monoStringToString(fqn_root)!; + const js_fqn = Module.UTF8ToString(fully_qualified_name, fully_qualified_name_length); mono_assert(js_fqn, "fully_qualified_name must be string"); mono_log_debug(`Binding [JSExport] ${js_fqn}`); const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); - - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); - if (!klass) - throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); - - const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; - const method = cwraps.mono_wasm_assembly_find_method(klass, wrapper_name, -1); + + const wrapper_name = fixupSymbolName(`${js_fqn}_${signature_hash}`); + const method = (Module as any)["_" + wrapper_name]; if (!method) - throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); + throw new Error(`Could not find method: ${wrapper_name} in ${js_fqn}`); const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); for (let index = 0; index < args_count; index++) { @@ -111,17 +102,36 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn); - wrap_no_error_root(is_exception, resultRoot); + wrap_no_error(is_exception); } catch (ex: any) { Module.err(ex.toString()); - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - fqn_root.release(); + wrap_error(is_exception, ex); } } +const s_charsToReplace = [".", "-", "+"]; + +function fixupSymbolName(name: string) { + // Sync with JSExportGenerator.FixupSymbolName + let result = ""; + for (let index = 0; index < name.length; index++) { + const b = name[index]; + if ((b >= "0" && b <= "9") || + (b >= "a" && b <= "z") || + (b >= "A" && b <= "Z") || + (b == "_")) { + result += b; + } else if( s_charsToReplace.includes(b)) { + result += "_"; + } else { + result += `_${b.charCodeAt(0).toString(16).toUpperCase()}_`; + } + } + + return result; +} + function bind_fn_0V(closure: BindingClosure) { const method = closure.method; const fqn = closure.fqn; @@ -134,7 +144,7 @@ function bind_fn_0V(closure: BindingClosure) { try { const args = alloc_stack_frame(2); // call C# side - invoke_method_and_handle_exception(method, args); + method(args); // TODO MF: handle exception } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -157,7 +167,7 @@ function bind_fn_1V(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_method_and_handle_exception(method, args); + method(args); // TODO MF: handle exception } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -181,7 +191,7 @@ function bind_fn_1R(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - invoke_method_and_handle_exception(method, args); + method(args); // TODO MF: handle exception const js_result = res_converter(args); return js_result; @@ -210,7 +220,7 @@ function bind_fn_2R(closure: BindingClosure) { marshaler2(args, arg2); // call C# side - invoke_method_and_handle_exception(method, args); + method(args); // TODO MF: handle exception const js_result = res_converter(args); return js_result; @@ -244,7 +254,7 @@ function bind_fn(closure: BindingClosure) { } // call C# side - invoke_method_and_handle_exception(method, args); + method(args); // TODO MF: handle exception if (res_converter) { const js_result = res_converter(args); @@ -260,7 +270,7 @@ function bind_fn(closure: BindingClosure) { type BindingClosure = { fqn: string, args_count: number, - method: MonoMethod, + method: Function, arg_marshalers: (BoundMarshalerToCs)[], res_converter: BoundMarshalerToJs | undefined, isDisposed: boolean, @@ -317,34 +327,10 @@ export async function mono_wasm_get_assembly_exports(assembly: string): Promise< const result = exportsByAssembly.get(assembly); if (!result) { const mark = startMeasure(); - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, runtimeHelpers.runtime_interop_namespace, "__GeneratedInitializer"); - if (klass) { - const method = cwraps.mono_wasm_assembly_find_method(klass, "__Register_", -1); - if (method) { - const outException = mono_wasm_new_root(); - const outResult = mono_wasm_new_root(); - try { - cwraps.mono_wasm_invoke_method_ref(method, MonoObjectRefNull, VoidPtrNull, outException.address, outResult.address); - if (outException.value !== MonoObjectNull) { - const msg = monoStringToString(outResult)!; - throw new Error(msg); - } - } - finally { - outException.release(); - outResult.release(); - } - } - } else { - mono_assert(!MonoWasmThreads, () => `JSExport with multi-threading enabled is not supported with assembly ${assembly} as it was generated with the .NET 7 SDK`); - // this needs to stay here for compatibility with assemblies generated in Net7 - // it doesn't have the __GeneratedInitializer class - cwraps.mono_wasm_runtime_run_module_cctor(asm); - } + + const register = (Module as any)["_" + assembly + "__GeneratedInitializer" + "__Register_"]; + mono_assert(register, `Missing wasm export for JSExport registration function in assembly ${assembly}`); + endMeasure(mark, MeasuredBlock.getAssemblyExports, assembly); } diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 2f75aa181d66..007c803292d4 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -7,12 +7,11 @@ import BuildConfiguration from "consts:configuration"; 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, get_signature_type, imported_js_function_symbol } from "./marshal"; import { setI32, setI32_unchecked, receiveWorkerHeapViews } from "./memory"; -import { monoStringToString, stringToMonoStringRoot } from "./strings"; -import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; -import { Int32Ptr } from "./types/emscripten"; +import { stringToMonoStringRoot } from "./strings"; +import { MonoObject, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; +import { CharPtr, Int32Ptr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_js } from "./marshal-to-js"; -import { mono_wasm_new_external_root } from "./roots"; import { mono_log_debug, mono_wasm_symbolicate_string } from "./logging"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; @@ -21,18 +20,15 @@ import { assert_synchronization_context } from "./pthreads/shared"; export const fn_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached. -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 { +export function mono_wasm_bind_js_function(function_name: CharPtr, function_name_length: number, module_name: CharPtr, module_name_length: number, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr): void { assert_bindings(); - const function_name_root = mono_wasm_new_external_root(function_name), - module_name_root = mono_wasm_new_external_root(module_name), - resultRoot = mono_wasm_new_external_root(result_address); try { const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - const js_function_name = monoStringToString(function_name_root)!; + const js_function_name = Module.UTF8ToString(function_name, function_name_length); const mark = startMeasure(); - const js_module_name = monoStringToString(module_name_root)!; + const js_module_name = Module.UTF8ToString(module_name, module_name_length); mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); @@ -109,15 +105,12 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const fn_handle = fn_wrapper_by_fn_handle.length; fn_wrapper_by_fn_handle.push(bound_fn); setI32(function_js_handle, fn_handle); - wrap_no_error_root(is_exception, resultRoot); + wrap_no_error(is_exception); endMeasure(mark, MeasuredBlock.bindJsFunction, js_function_name); } catch (ex: any) { setI32(function_js_handle, 0); Module.err(ex.toString()); - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - function_name_root.release(); + wrap_error(is_exception, ex); } } @@ -391,6 +384,9 @@ export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: const res = _wrap_error_flag(is_exception, ex); stringToMonoStringRoot(res, result); } +export function wrap_error(is_exception: Int32Ptr | null, ex: any): void { + _wrap_error_flag(is_exception, ex); +} // to set out parameters of icalls export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmRoot): void { @@ -403,6 +399,13 @@ export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmR } } +export function wrap_no_error(is_exception: Int32Ptr | null): void { + if (is_exception) { + receiveWorkerHeapViews(); + setI32_unchecked(is_exception, 0); + } +} + export function assert_bindings(): void { loaderHelpers.assert_runtime_running(); if (MonoWasmThreads) { From cbb8d6f5350074a378afdf01f96c30a8b6cc10bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 14 Dec 2023 15:07:05 +0100 Subject: [PATCH 02/32] Add js import sample --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 8 ++++++++ src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index cdaf3d606e2f..0be5e0fa2fe5 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -9,7 +9,15 @@ static int Main(string[] args) if (args.Length != 3 || args[0] != "A" || args[1] != "B" || args[2] != "C") return 1; + + Console.WriteLine($"Math result is '{Interop.Math(1, 2, 3)}'"); return 100; } + + partial static class Interop + { + [JSImport("interop.math", "main.js")] + internal static partial int Math(int a, int b, int c); + } } \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index 3d91ca60294c..b0e0e2d688d6 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -3,10 +3,16 @@ import { dotnet, exit } from './dotnet.js' -const { runMain } = await dotnet +const { runMain, setModuleImports } = await dotnet .withApplicationArguments("A", "B", "C") .create(); +setModuleImports('main.js', { + interop: { + math: (a, b, c) => a + b * c, + } +}); + var result = await runMain(); console.log(`Exit code ${result}`); exit(result); \ No newline at end of file From 44611b9aed960282b5a7ffda47a3367c6270eb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 15 Dec 2023 10:48:22 +0100 Subject: [PATCH 03/32] Fix typescript build --- src/mono/wasm/runtime/invoke-cs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 6c331e8f70d9..24e93ed6a804 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -13,10 +13,9 @@ import { } from "./marshal"; import { mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; -import { MonoString, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; +import { MonoString, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; import { CharPtr, Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; -import { assembly_load } from "./class-loader"; import { assert_bindings, wrap_error, wrap_no_error } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; From 9f6993e5242674c91fa7f82fd48d0e6867085a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 15 Dec 2023 15:14:16 +0100 Subject: [PATCH 04/32] Fix Program.cs --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 0be5e0fa2fe5..7d430a2c24ce 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices.JavaScript; class Program { From 0f36fbf5ce808b040b703fd00e5550d1871354c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 15 Dec 2023 17:49:35 +0100 Subject: [PATCH 05/32] Fix Program.cs --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 7d430a2c24ce..1f1e22658924 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.InteropServices.JavaScript; -class Program +partial class Program { static int Main(string[] args) { @@ -16,9 +16,9 @@ static int Main(string[] args) return 100; } - partial static class Interop + static partial class Interop { [JSImport("interop.math", "main.js")] internal static partial int Math(int a, int b, int c); } -} \ No newline at end of file +} From b07ad92a1b13cc8fd69a0b1a418ab3f757b74526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Sun, 17 Dec 2023 12:23:25 +0100 Subject: [PATCH 06/32] Try to fix JS interop with explicit project reference --- src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj index 6f412ade2443..f98663fd95ca 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj @@ -7,6 +7,8 @@ true + + From 3f052887b4df4d6fb59bd2d740f76bde15816771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 11:16:20 +0100 Subject: [PATCH 07/32] Check math result --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 1f1e22658924..9fa8f75ee2e1 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -11,7 +11,10 @@ static int Main(string[] args) if (args.Length != 3 || args[0] != "A" || args[1] != "B" || args[2] != "C") return 1; - Console.WriteLine($"Math result is '{Interop.Math(1, 2, 3)}'"); + var mathResult = Interop.Math(1, 2, 3); + Console.WriteLine($"Math result is '{mathResult}'"); + if (mathResult != 7) + return 2; return 100; } From 132bc2375f5d3428b4d389a66a432e8cee632830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 13:13:58 +0100 Subject: [PATCH 08/32] Comment about analyzer references --- src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj index f98663fd95ca..cd14c2a1310c 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj @@ -7,6 +7,7 @@ true + From 3aec51833ffb095b6c8c1d65ba650471c33342e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 14:09:44 +0100 Subject: [PATCH 09/32] Move DllImports to a new file --- .../Browser/Interop.Runtime.NativeAOT.cs | 77 +++++++++++++++++++ .../src/Interop/Browser/Interop.Runtime.cs | 15 ++-- ....Runtime.InteropServices.JavaScript.csproj | 3 +- .../JavaScript/JSFunctionBinding.cs | 8 +- 4 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs new file mode 100644 index 000000000000..0c79db746598 --- /dev/null +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.JavaScript; + +internal static partial class Interop +{ + // WARNING: until https://github.com/dotnet/runtime/issues/37955 is fixed + // make sure that the native side always sets the out parameters + // otherwise out parameters could stay un-initialized, when the method is used in inlined context + internal static unsafe partial class Runtime + { +#pragma warning disable SYSLIB1054 + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); + [DllImport("*", EntryPoint = "mono_wasm_bind_js_function")] + public static extern unsafe void BindJSFunction(string function_name, int function_name_length, string module_name, int module_name_length, void* signature, out IntPtr bound_function_js_handle, out int is_exception); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data); + [DllImport("*", EntryPoint = "mono_wasm_invoke_import")] + public static extern unsafe void InvokeImport(IntPtr fn_handle, void* data); + [DllImport("*", EntryPoint = "mono_wasm_bind_cs_function")] + public static extern unsafe void BindCSFunction(string fully_qualified_name, int fully_qualified_name_length, int signature_hash, void* signature, out int is_exception); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void ResolveOrRejectPromise(void* data); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void DeregisterGCRoot(IntPtr handle); +#pragma warning restore SYSLIB1054 + + public static unsafe void BindJSFunction(string function_name, string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result) + { + BindJSFunction(function_name, function_name.Length, module_name, module_name.Length, signature, out bound_function_js_handle, out is_exception); + if (is_exception != 0) + result = "Runtime.BindJSFunction failed"; + else + result = ""; + } + + public static unsafe void BindCSFunction(in string fully_qualified_name, int signature_hash, void* signature, out int is_exception, out object result) + { + BindCSFunction(fully_qualified_name, fully_qualified_name.Length, signature_hash, signature, out is_exception); + if (is_exception != 0) + result = "Runtime.BindCSFunction failed"; + else + result = ""; + } + + #region Legacy + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void InvokeJSWithArgsRef(IntPtr jsHandle, in string method, in object?[] parms, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void GetObjectPropertyRef(IntPtr jsHandle, in string propertyName, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void SetObjectPropertyRef(IntPtr jsHandle, in string propertyName, in object? value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void GetByIndexRef(IntPtr jsHandle, int index, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void SetByIndexRef(IntPtr jsHandle, int index, in object? value, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void GetGlobalObjectRef(in string? globalName, out int exceptionalResult, out object result); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void TypedArrayToArrayRef(IntPtr jsHandle, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void CreateCSOwnedObjectRef(in string className, in object[] parms, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void TypedArrayFromRef(int arrayPtr, int begin, int end, int bytesPerElement, int type, out int exceptionalResult, out object result); + + #endregion + } +} diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index f26ee0dd485a..03c19b3d03e0 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -3,7 +3,6 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; internal static partial class Interop { @@ -12,17 +11,16 @@ internal static partial class Interop // otherwise out parameters could stay un-initialized, when the method is used in inlined context internal static unsafe partial class Runtime { -#pragma warning disable SYSLIB1054 [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); - [DllImport("*", EntryPoint = "mono_wasm_bind_js_function")] - public static extern unsafe void BindJSFunction(string function_name, int function_name_length, string module_name, int module_name_length, void* signature, out IntPtr bound_function_js_handle, out int is_exception); + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern unsafe void BindJSFunction(in string function_name, in string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data); - [DllImport("*", EntryPoint = "mono_wasm_invoke_import")] - public static extern unsafe void InvokeImport(IntPtr fn_handle, void* data); - [DllImport("*", EntryPoint = "mono_wasm_bind_cs_function")] - public static extern unsafe void BindCSFunction(string fully_qualified_name, int fully_qualified_name_length, int signature_hash, void* signature, out int is_exception); + [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 ResolveOrRejectPromise(void* data); [MethodImpl(MethodImplOptions.InternalCall)] @@ -61,6 +59,5 @@ internal static unsafe partial class Runtime #endregion -#pragma warning restore SYSLIB1054 } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 85fbb6185641..db0590c3e631 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -22,7 +22,8 @@ - + + 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 cb625707b782..3e4fe41acc05 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 @@ -226,9 +226,9 @@ internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, var signature = JSHostImplementation.GetMethodSignature(signatures); - Interop.Runtime.BindJSFunction(functionName, functionName.Length, moduleName, moduleName.Length, signature.Header, out IntPtr jsFunctionHandle, out int isException); + Interop.Runtime.BindJSFunction(functionName, moduleName, signature.Header, out IntPtr jsFunctionHandle, out int isException, out object exceptionMessage); if (isException != 0) - throw new JSException("Runtime.BindJSFunction failed"); + throw new JSException((string)exceptionMessage); signature.FnHandle = jsFunctionHandle; @@ -241,10 +241,10 @@ internal static unsafe JSFunctionBinding BindManagedFunctionImpl(string fullyQua { var signature = JSHostImplementation.GetMethodSignature(signatures); - Interop.Runtime.BindCSFunction(fullyQualifiedName, fullyQualifiedName.Length, signatureHash, signature.Header, out int isException); + Interop.Runtime.BindCSFunction(fullyQualifiedName, signatureHash, signature.Header, out int isException, out object exceptionMessage); if (isException != 0) { - throw new JSException("Runtime.BindCSFunction failed"); + throw new JSException((string)exceptionMessage); } JSHostImplementation.FreeMethodSignatureBuffer(signature); From 6b6739423dc6bb615ae2b5c04ed67884d600d4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 14:36:28 +0100 Subject: [PATCH 10/32] Split mono_wasm_bind_js_function to mono and naot --- src/mono/wasm/runtime/invoke-js.ts | 110 ++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 007c803292d4..61b3b5e1fc62 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 NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; 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, get_signature_type, imported_js_function_symbol } from "./marshal"; import { setI32, setI32_unchecked, receiveWorkerHeapViews } from "./memory"; -import { stringToMonoStringRoot } from "./strings"; -import { MonoObject, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; +import { monoStringToString, stringToMonoStringRoot } from "./strings"; +import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; import { CharPtr, Int32Ptr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_js } from "./marshal-to-js"; +import { mono_wasm_new_external_root } from "./roots"; import { mono_log_debug, mono_wasm_symbolicate_string } from "./logging"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; @@ -20,7 +22,107 @@ import { assert_synchronization_context } from "./pthreads/shared"; export const fn_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached. -export function mono_wasm_bind_js_function(function_name: CharPtr, function_name_length: number, module_name: CharPtr, module_name_length: number, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr): void { +function mono_wasm_bind_js_function_mono(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_bindings(); + const function_name_root = mono_wasm_new_external_root(function_name), + module_name_root = mono_wasm_new_external_root(module_name), + resultRoot = mono_wasm_new_external_root(result_address); + try { + const version = get_signature_version(signature); + mono_assert(version === 2, () => `Signature version ${version} mismatch.`); + + const js_function_name = monoStringToString(function_name_root)!; + const mark = startMeasure(); + const js_module_name = monoStringToString(module_name_root)!; + mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); + + const fn = mono_wasm_lookup_function(js_function_name, js_module_name); + const args_count = get_signature_argument_count(signature); + + 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 sig = get_sig(signature, index + 2); + 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; + } + else if (marshaler_type == MarshalerType.Task) { + assert_synchronization_context(); + } + } + const res_sig = get_sig(signature, 1); + const res_marshaler_type = get_signature_type(res_sig); + if (res_marshaler_type == MarshalerType.Task) { + assert_synchronization_context(); + } + const res_converter = bind_arg_marshal_to_cs(res_sig, res_marshaler_type, 1); + + const closure: BindingClosure = { + fn, + fqn: js_module_name + ":" + js_function_name, + args_count, + arg_marshalers, + res_converter, + has_cleanup, + arg_cleanup, + isDisposed: false, + }; + let bound_fn: Function; + if (args_count == 0 && !res_converter) { + bound_fn = bind_fn_0V(closure); + } + else if (args_count == 1 && !has_cleanup && !res_converter) { + bound_fn = bind_fn_1V(closure); + } + else if (args_count == 1 && !has_cleanup && res_converter) { + bound_fn = bind_fn_1R(closure); + } + else if (args_count == 2 && !has_cleanup && res_converter) { + bound_fn = bind_fn_2R(closure); + } + else { + bound_fn = bind_fn(closure); + } + + // this is just to make debugging easier. + // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds + // in Release configuration, it would be a trimmed by rollup + if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { + try { + bound_fn = new Function("fn", "return (function JSImport_" + js_function_name.replaceAll(".", "_") + "(){ return fn.apply(this, arguments)});")(bound_fn); + } + catch (ex) { + runtimeHelpers.cspPolicy = true; + } + } + + (bound_fn)[imported_js_function_symbol] = closure; + const fn_handle = fn_wrapper_by_fn_handle.length; + fn_wrapper_by_fn_handle.push(bound_fn); + setI32(function_js_handle, fn_handle); + wrap_no_error_root(is_exception, resultRoot); + endMeasure(mark, MeasuredBlock.bindJsFunction, js_function_name); + } catch (ex: any) { + setI32(function_js_handle, 0); + Module.err(ex.toString()); + wrap_error_root(is_exception, ex, resultRoot); + } finally { + resultRoot.release(); + function_name_root.release(); + } +} + +function mono_wasm_bind_js_function_naot(function_name: CharPtr, function_name_length: number, module_name: CharPtr, module_name_length: number, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr): void { assert_bindings(); try { const version = get_signature_version(signature); @@ -114,6 +216,8 @@ export function mono_wasm_bind_js_function(function_name: CharPtr, function_name } } +export const mono_wasm_bind_js_function = NativeAOT ? mono_wasm_bind_js_function_naot : mono_wasm_bind_js_function_mono; + function bind_fn_0V(closure: BindingClosure) { const fn = closure.fn; const fqn = closure.fqn; From 43a538928c69068f653c009eeb2b81d93dd80a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 14:49:43 +0100 Subject: [PATCH 11/32] Split mono_wasm_bind_cs_function --- src/mono/wasm/runtime/invoke-cs.ts | 197 +++++++++++++++++++++++++++-- 1 file changed, 186 insertions(+), 11 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 24e93ed6a804..9ae586abd5d9 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import NativeAOT from "consts:nativeAOT"; import BuildConfiguration from "consts:configuration"; import MonoWasmThreads from "consts:monoWasmThreads"; @@ -11,17 +12,118 @@ import { 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_root } from "./roots"; +import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; -import { MonoString, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; +import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; import { CharPtr, Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; -import { assert_bindings, wrap_error, wrap_no_error } from "./invoke-js"; +import { assembly_load } from "./class-loader"; +import { assert_bindings, wrap_error, wrap_error_root, wrap_no_error, wrap_no_error_root } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; import { assert_synchronization_context } from "./pthreads/shared"; -export function mono_wasm_bind_cs_function(fully_qualified_name: CharPtr, fully_qualified_name_length: number, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr): void { +function mono_wasm_bind_cs_function_mono(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + assert_bindings(); + const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); + const mark = startMeasure(); + try { + const version = get_signature_version(signature); + mono_assert(version === 2, () => `Signature version ${version} mismatch.`); + + const args_count = get_signature_argument_count(signature); + const js_fqn = monoStringToString(fqn_root)!; + mono_assert(js_fqn, "fully_qualified_name must be string"); + + mono_log_debug(`Binding [JSExport] ${js_fqn}`); + + const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); + + const asm = assembly_load(assembly); + if (!asm) + throw new Error("Could not find assembly: " + assembly); + + const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); + if (!klass) + throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); + + const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; + const method = cwraps.mono_wasm_assembly_find_method(klass, wrapper_name, -1); + if (!method) + throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); + + const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); + 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.Task) { + assert_synchronization_context(); + } + 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 res_sig = get_sig(signature, 1); + const res_marshaler_type = get_signature_type(res_sig); + if (res_marshaler_type == MarshalerType.Task) { + assert_synchronization_context(); + } + const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); + + const closure: BindingClosure = { + method, + fqn: js_fqn, + args_count, + arg_marshalers, + res_converter, + isDisposed: false, + }; + let bound_fn: Function; + if (args_count == 0 && !res_converter) { + bound_fn = bind_fn_0V(closure); + } + 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); + } + + // this is just to make debugging easier. + // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds + // in Release configuration, it would be a trimmed by rollup + if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { + try { + bound_fn = new Function("fn", "return (function JSExport_" + methodname + "(){ return fn.apply(this, arguments)});")(bound_fn); + } + catch (ex) { + runtimeHelpers.cspPolicy = true; + } + } + + (bound_fn)[bound_cs_function_symbol] = closure; + + _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); + endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn); + wrap_no_error_root(is_exception, resultRoot); + } + catch (ex: any) { + Module.err(ex.toString()); + wrap_error_root(is_exception, ex, resultRoot); + } finally { + resultRoot.release(); + fqn_root.release(); + } +} + +function mono_wasm_bind_cs_function_naot(fully_qualified_name: CharPtr, fully_qualified_name_length: number, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr): void { assert_bindings(); const mark = startMeasure(); try { @@ -109,6 +211,8 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: CharPtr, fully_ } } +export const mono_wasm_bind_cs_function = NativeAOT ? mono_wasm_bind_cs_function_naot : mono_wasm_bind_cs_function_mono; + const s_charsToReplace = [".", "-", "+"]; function fixupSymbolName(name: string) { @@ -143,7 +247,11 @@ function bind_fn_0V(closure: BindingClosure) { try { const args = alloc_stack_frame(2); // call C# side - method(args); // TODO MF: handle exception + if (NativeAOT) { + (method as any)(args); // TODO MF: handle exception + return; + } + invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -166,7 +274,11 @@ function bind_fn_1V(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - method(args); // TODO MF: handle exception + if (NativeAOT) { + (method as any)(args); // TODO MF: handle exception + return; + } + invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); endMeasure(mark, MeasuredBlock.callCsFunction, fqn); @@ -190,7 +302,13 @@ function bind_fn_1R(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - method(args); // TODO MF: handle exception + if (NativeAOT) { + (method as any)(args); // TODO MF: handle exception + + const js_result = res_converter(args); + return js_result; + } + invoke_method_and_handle_exception(method, args); const js_result = res_converter(args); return js_result; @@ -219,7 +337,13 @@ function bind_fn_2R(closure: BindingClosure) { marshaler2(args, arg2); // call C# side - method(args); // TODO MF: handle exception + if (NativeAOT) { + (method as any)(args); // TODO MF: handle exception + + const js_result = res_converter(args); + return js_result; + } + invoke_method_and_handle_exception(method, args); const js_result = res_converter(args); return js_result; @@ -253,7 +377,16 @@ function bind_fn(closure: BindingClosure) { } // call C# side - method(args); // TODO MF: handle exception + if (NativeAOT) { + (method as any)(args); // TODO MF: handle exception + + if (res_converter) { + const js_result = res_converter(args); + return js_result; + } + return; + } + invoke_method_and_handle_exception(method, args); if (res_converter) { const js_result = res_converter(args); @@ -269,7 +402,7 @@ function bind_fn(closure: BindingClosure) { type BindingClosure = { fqn: string, args_count: number, - method: Function, + method: MonoMethod, arg_marshalers: (BoundMarshalerToCs)[], res_converter: BoundMarshalerToJs | undefined, isDisposed: boolean, @@ -321,7 +454,47 @@ function _walk_exports_to_set_function(assembly: string, namespace: string, clas scope[`${methodname}.${signature_hash}`] = fn; } -export async function mono_wasm_get_assembly_exports(assembly: string): Promise { +async function mono_wasm_get_assembly_exports_mono(assembly: string): Promise { + assert_bindings(); + const result = exportsByAssembly.get(assembly); + if (!result) { + const mark = startMeasure(); + const asm = assembly_load(assembly); + if (!asm) + throw new Error("Could not find assembly: " + assembly); + + const klass = cwraps.mono_wasm_assembly_find_class(asm, runtimeHelpers.runtime_interop_namespace, "__GeneratedInitializer"); + if (klass) { + const method = cwraps.mono_wasm_assembly_find_method(klass, "__Register_", -1); + if (method) { + const outException = mono_wasm_new_root(); + const outResult = mono_wasm_new_root(); + try { + cwraps.mono_wasm_invoke_method_ref(method, MonoObjectRefNull, VoidPtrNull, outException.address, outResult.address); + if (outException.value !== MonoObjectNull) { + const msg = monoStringToString(outResult)!; + throw new Error(msg); + } + } + finally { + outException.release(); + outResult.release(); + } + } + } else { + mono_assert(!MonoWasmThreads, () => `JSExport with multi-threading enabled is not supported with assembly ${assembly} as it was generated with the .NET 7 SDK`); + // this needs to stay here for compatibility with assemblies generated in Net7 + // it doesn't have the __GeneratedInitializer class + cwraps.mono_wasm_runtime_run_module_cctor(asm); + } + + endMeasure(mark, MeasuredBlock.getAssemblyExports, assembly); + } + + return exportsByAssembly.get(assembly) || {}; +} + +async function mono_wasm_get_assembly_exports_naot(assembly: string): Promise { assert_bindings(); const result = exportsByAssembly.get(assembly); if (!result) { @@ -336,6 +509,8 @@ export async function mono_wasm_get_assembly_exports(assembly: string): Promise< return exportsByAssembly.get(assembly) || {}; } +export const mono_wasm_get_assembly_exports = NativeAOT ? mono_wasm_get_assembly_exports_naot : mono_wasm_get_assembly_exports_mono; + export function parseFQN(fqn: string) : { assembly: string, namespace: string, classname: string, methodname: string } { const assembly = fqn.substring(fqn.indexOf("[") + 1, fqn.indexOf("]")).trim(); From 1a666ec988ed7d6057f71b17111742d1f0c43b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 15:14:46 +0100 Subject: [PATCH 12/32] Fix emscripten import names --- src/mono/wasm/runtime/exports-linker.ts | 2 +- src/mono/wasm/runtime/invoke-cs.ts | 1 + src/mono/wasm/runtime/invoke-js.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index dd4ed9ec2233..8d1ce2f4ddc2 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -12,7 +12,7 @@ export function export_linker_indexes_as_code(): string { }; let idx = 0; for (const wi of mono_wasm_imports) { - indexByName.mono_wasm_imports[wi.name] = idx; + indexByName.mono_wasm_imports[(wi as any).originalName ?? wi.name] = idx; idx++; } for (const wi of mono_wasm_threads_imports) { diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 9ae586abd5d9..ce9eabe36908 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -212,6 +212,7 @@ function mono_wasm_bind_cs_function_naot(fully_qualified_name: CharPtr, fully_qu } export const mono_wasm_bind_cs_function = NativeAOT ? mono_wasm_bind_cs_function_naot : mono_wasm_bind_cs_function_mono; +(mono_wasm_bind_cs_function as any).originalName = "mono_wasm_bind_cs_function"; const s_charsToReplace = [".", "-", "+"]; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 61b3b5e1fc62..3343cc8f9792 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -217,6 +217,7 @@ function mono_wasm_bind_js_function_naot(function_name: CharPtr, function_name_l } export const mono_wasm_bind_js_function = NativeAOT ? mono_wasm_bind_js_function_naot : mono_wasm_bind_js_function_mono; +(mono_wasm_bind_js_function as any).originalName = "mono_wasm_bind_js_function"; function bind_fn_0V(closure: BindingClosure) { const fn = closure.fn; From b8621b80ee0a2397e2b170c4912c3fc4efdadb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 15:16:48 +0100 Subject: [PATCH 13/32] Add JSExport to sample --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 9 +++++++++ .../nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 11 +++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 9fa8f75ee2e1..83900dedaa44 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices.JavaScript; +namespace DotnetJsApp; + partial class Program { static int Main(string[] args) @@ -23,5 +25,12 @@ static partial class Interop { [JSImport("interop.math", "main.js")] internal static partial int Math(int a, int b, int c); + + [JSExport] + internal static int Square(int x) + { + Console.WriteLine($"Compting square of {x}"); + return x^2; + } } } diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index b0e0e2d688d6..711bf1e1a34c 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -13,6 +13,13 @@ setModuleImports('main.js', { } }); -var result = await runMain(); +let result = await runMain(); + +const exports = getAssemblyExports("DotnetJs.dll"); +const square = exports.DotnetJsApp.Interop.Square(5); +if (square != 25) { + result = 3; +} + console.log(`Exit code ${result}`); -exit(result); \ No newline at end of file +exit(result); From d2107e41eb63dde5fc37a270d9748e69197eae55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 17:19:53 +0100 Subject: [PATCH 14/32] Use different exit codes than runtime does --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 6 +++--- src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 83900dedaa44..c421cd599959 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -11,12 +11,12 @@ static int Main(string[] args) Console.WriteLine($"Args {String.Join(", ", args)}"); if (args.Length != 3 || args[0] != "A" || args[1] != "B" || args[2] != "C") - return 1; + return 11; var mathResult = Interop.Math(1, 2, 3); Console.WriteLine($"Math result is '{mathResult}'"); if (mathResult != 7) - return 2; + return 12; return 100; } @@ -29,7 +29,7 @@ static partial class Interop [JSExport] internal static int Square(int x) { - Console.WriteLine($"Compting square of {x}"); + Console.WriteLine($"Computing square of {x}"); return x^2; } } diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index 711bf1e1a34c..25ddb4433651 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -18,7 +18,7 @@ let result = await runMain(); const exports = getAssemblyExports("DotnetJs.dll"); const square = exports.DotnetJsApp.Interop.Square(5); if (square != 25) { - result = 3; + result = 13; } console.log(`Exit code ${result}`); From 4ee6148270952f3d4640eba0a4030e9b23556d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 18 Dec 2023 17:21:44 +0100 Subject: [PATCH 15/32] Feedback about Interop.Runtime.cs --- .../src/System.Runtime.InteropServices.JavaScript.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index db0590c3e631..b6ea0a98035b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -22,6 +22,7 @@ + From da24ea2b660119ad71ec694f6937da4ab085f52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 09:40:03 +0100 Subject: [PATCH 16/32] Split typescript imports --- src/mono/wasm/runtime/invoke-cs.ts | 6 ++++-- src/mono/wasm/runtime/invoke-js.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index ce9eabe36908..489be27d1a5b 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -15,10 +15,12 @@ import { import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; -import { CharPtr, Int32Ptr } from "./types/emscripten"; +import { Int32Ptr } from "./types/emscripten"; +import { CharPtr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; -import { assert_bindings, wrap_error, wrap_error_root, wrap_no_error, wrap_no_error_root } from "./invoke-js"; +import { assert_bindings, wrap_error_root, wrap_no_error_root } from "./invoke-js"; +import { wrap_error, wrap_no_error } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; import { assert_synchronization_context } from "./pthreads/shared"; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 3343cc8f9792..7d8da151ee09 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -10,7 +10,8 @@ import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_si import { setI32, setI32_unchecked, receiveWorkerHeapViews } from "./memory"; import { monoStringToString, stringToMonoStringRoot } from "./strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; -import { CharPtr, Int32Ptr } from "./types/emscripten"; +import { Int32Ptr } from "./types/emscripten"; +import { CharPtr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { bind_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; From fce6919bed984ec0bf67c9463e2cc78628f0c677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 10:03:24 +0100 Subject: [PATCH 17/32] Fix sample --- src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index 25ddb4433651..3989935427a5 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -3,7 +3,7 @@ import { dotnet, exit } from './dotnet.js' -const { runMain, setModuleImports } = await dotnet +const { runMain, setModuleImports, getAssemblyExports } = await dotnet .withApplicationArguments("A", "B", "C") .create(); @@ -15,10 +15,10 @@ setModuleImports('main.js', { let result = await runMain(); -const exports = getAssemblyExports("DotnetJs.dll"); const square = exports.DotnetJsApp.Interop.Square(5); if (square != 25) { result = 13; +const exports = await getAssemblyExports("DotnetJs.dll"); } console.log(`Exit code ${result}`); From a4a2a1cc57410232b20ad627200a3fad5a51a094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 10:44:57 +0100 Subject: [PATCH 18/32] Revert typescript split --- src/mono/wasm/runtime/exports-linker.ts | 2 +- src/mono/wasm/runtime/invoke-cs.ts | 143 ++++++------------------ src/mono/wasm/runtime/invoke-js.ts | 126 +++------------------ src/mono/wasm/runtime/roots.ts | 10 ++ 4 files changed, 61 insertions(+), 220 deletions(-) diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 8d1ce2f4ddc2..dd4ed9ec2233 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -12,7 +12,7 @@ export function export_linker_indexes_as_code(): string { }; let idx = 0; for (const wi of mono_wasm_imports) { - indexByName.mono_wasm_imports[(wi as any).originalName ?? wi.name] = idx; + indexByName.mono_wasm_imports[wi.name] = idx; idx++; } for (const wi of mono_wasm_threads_imports) { diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 489be27d1a5b..776868b5d2d7 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +/* eslint-disable prefer-rest-params */ import NativeAOT from "consts:nativeAOT"; import BuildConfiguration from "consts:configuration"; @@ -16,43 +17,55 @@ import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; -import { CharPtr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; import { assert_bindings, wrap_error_root, wrap_no_error_root } from "./invoke-js"; -import { wrap_error, wrap_no_error } from "./invoke-js"; import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; import { mono_log_debug } from "./logging"; import { assert_synchronization_context } from "./pthreads/shared"; -function mono_wasm_bind_cs_function_mono(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { +// function mono_wasm_bind_cs_function_naot(fully_qualified_name: CharPtr, fully_qualified_name_length: number, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr): void +export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { assert_bindings(); const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); const mark = startMeasure(); try { + if (NativeAOT) { + signature_hash = arguments[2]; + signature = arguments[3]; + is_exception = arguments[4]; + } const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); - const js_fqn = monoStringToString(fqn_root)!; + const js_fqn = NativeAOT ? Module.UTF8ToString(arguments[0], arguments[1]) : monoStringToString(fqn_root)!; mono_assert(js_fqn, "fully_qualified_name must be string"); mono_log_debug(`Binding [JSExport] ${js_fqn}`); const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); - const asm = assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); - if (!klass) - throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); - - const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; - const method = cwraps.mono_wasm_assembly_find_method(klass, wrapper_name, -1); - if (!method) - throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); + let method = null; + if (NativeAOT) { + const wrapper_name = fixupSymbolName(`${js_fqn}_${signature_hash}`); + method = (Module as any)["_" + wrapper_name]; + if (!method) + throw new Error(`Could not find method: ${wrapper_name} in ${js_fqn}`); + } else { + const asm = assembly_load(assembly); + if (!asm) + throw new Error("Could not find assembly: " + assembly); + + const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); + if (!klass) + throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); + + const wrapper_name = `__Wrapper_${methodname}_${signature_hash}`; + method = cwraps.mono_wasm_assembly_find_method(klass, wrapper_name, -1); + if (!method) + throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`); + } const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); for (let index = 0; index < args_count; index++) { @@ -125,97 +138,6 @@ function mono_wasm_bind_cs_function_mono(fully_qualified_name: MonoStringRef, si } } -function mono_wasm_bind_cs_function_naot(fully_qualified_name: CharPtr, fully_qualified_name_length: number, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr): void { - assert_bindings(); - const mark = startMeasure(); - try { - const version = get_signature_version(signature); - mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - - const args_count = get_signature_argument_count(signature); - const js_fqn = Module.UTF8ToString(fully_qualified_name, fully_qualified_name_length); - mono_assert(js_fqn, "fully_qualified_name must be string"); - - mono_log_debug(`Binding [JSExport] ${js_fqn}`); - - const { assembly, namespace, classname, methodname } = parseFQN(js_fqn); - - const wrapper_name = fixupSymbolName(`${js_fqn}_${signature_hash}`); - const method = (Module as any)["_" + wrapper_name]; - if (!method) - throw new Error(`Could not find method: ${wrapper_name} in ${js_fqn}`); - - const arg_marshalers: (BoundMarshalerToCs)[] = new Array(args_count); - 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.Task) { - assert_synchronization_context(); - } - 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 res_sig = get_sig(signature, 1); - const res_marshaler_type = get_signature_type(res_sig); - if (res_marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } - const res_converter = bind_arg_marshal_to_js(res_sig, res_marshaler_type, 1); - - const closure: BindingClosure = { - method, - fqn: js_fqn, - args_count, - arg_marshalers, - res_converter, - isDisposed: false, - }; - let bound_fn: Function; - if (args_count == 0 && !res_converter) { - bound_fn = bind_fn_0V(closure); - } - 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); - } - - // this is just to make debugging easier. - // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds - // in Release configuration, it would be a trimmed by rollup - if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { - try { - bound_fn = new Function("fn", "return (function JSExport_" + methodname + "(){ return fn.apply(this, arguments)});")(bound_fn); - } - catch (ex) { - runtimeHelpers.cspPolicy = true; - } - } - - (bound_fn)[bound_cs_function_symbol] = closure; - - _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); - endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn); - wrap_no_error(is_exception); - } - catch (ex: any) { - Module.err(ex.toString()); - wrap_error(is_exception, ex); - } -} - -export const mono_wasm_bind_cs_function = NativeAOT ? mono_wasm_bind_cs_function_naot : mono_wasm_bind_cs_function_mono; -(mono_wasm_bind_cs_function as any).originalName = "mono_wasm_bind_cs_function"; - const s_charsToReplace = [".", "-", "+"]; function fixupSymbolName(name: string) { @@ -503,8 +425,13 @@ async function mono_wasm_get_assembly_exports_naot(assembly: string): Promise[null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached. -function mono_wasm_bind_js_function_mono(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void { +// function mono_wasm_bind_js_function_naot(function_name: CharPtr, function_name_length: number, module_name: CharPtr, module_name_length: number, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr): void +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 { assert_bindings(); const function_name_root = mono_wasm_new_external_root(function_name), module_name_root = mono_wasm_new_external_root(module_name), resultRoot = mono_wasm_new_external_root(result_address); try { + if (NativeAOT) { + signature = arguments[4]; + function_js_handle = arguments[5]; + is_exception = arguments[6]; + } + const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - const js_function_name = monoStringToString(function_name_root)!; + const js_function_name = NativeAOT ? Module.UTF8ToString(arguments[0], arguments[1]) : monoStringToString(function_name_root)!; const mark = startMeasure(); - const js_module_name = monoStringToString(module_name_root)!; + const js_module_name = NativeAOT ? Module.UTF8ToString(arguments[2], arguments[3]) : monoStringToString(module_name_root)!; mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); @@ -123,103 +130,6 @@ function mono_wasm_bind_js_function_mono(function_name: MonoStringRef, module_na } } -function mono_wasm_bind_js_function_naot(function_name: CharPtr, function_name_length: number, module_name: CharPtr, module_name_length: number, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr): void { - assert_bindings(); - try { - const version = get_signature_version(signature); - mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - - const js_function_name = Module.UTF8ToString(function_name, function_name_length); - const mark = startMeasure(); - const js_module_name = Module.UTF8ToString(module_name, module_name_length); - mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); - - const fn = mono_wasm_lookup_function(js_function_name, js_module_name); - const args_count = get_signature_argument_count(signature); - - 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 sig = get_sig(signature, index + 2); - 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; - } - else if (marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } - } - const res_sig = get_sig(signature, 1); - const res_marshaler_type = get_signature_type(res_sig); - if (res_marshaler_type == MarshalerType.Task) { - assert_synchronization_context(); - } - const res_converter = bind_arg_marshal_to_cs(res_sig, res_marshaler_type, 1); - - const closure: BindingClosure = { - fn, - fqn: js_module_name + ":" + js_function_name, - args_count, - arg_marshalers, - res_converter, - has_cleanup, - arg_cleanup, - isDisposed: false, - }; - let bound_fn: Function; - if (args_count == 0 && !res_converter) { - bound_fn = bind_fn_0V(closure); - } - else if (args_count == 1 && !has_cleanup && !res_converter) { - bound_fn = bind_fn_1V(closure); - } - else if (args_count == 1 && !has_cleanup && res_converter) { - bound_fn = bind_fn_1R(closure); - } - else if (args_count == 2 && !has_cleanup && res_converter) { - bound_fn = bind_fn_2R(closure); - } - else { - bound_fn = bind_fn(closure); - } - - // this is just to make debugging easier. - // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds - // in Release configuration, it would be a trimmed by rollup - if (BuildConfiguration === "Debug" && !runtimeHelpers.cspPolicy) { - try { - bound_fn = new Function("fn", "return (function JSImport_" + js_function_name.replaceAll(".", "_") + "(){ return fn.apply(this, arguments)});")(bound_fn); - } - catch (ex) { - runtimeHelpers.cspPolicy = true; - } - } - - (bound_fn)[imported_js_function_symbol] = closure; - const fn_handle = fn_wrapper_by_fn_handle.length; - fn_wrapper_by_fn_handle.push(bound_fn); - setI32(function_js_handle, fn_handle); - wrap_no_error(is_exception); - endMeasure(mark, MeasuredBlock.bindJsFunction, js_function_name); - } catch (ex: any) { - setI32(function_js_handle, 0); - Module.err(ex.toString()); - wrap_error(is_exception, ex); - } -} - -export const mono_wasm_bind_js_function = NativeAOT ? mono_wasm_bind_js_function_naot : mono_wasm_bind_js_function_mono; -(mono_wasm_bind_js_function as any).originalName = "mono_wasm_bind_js_function"; - function bind_fn_0V(closure: BindingClosure) { const fn = closure.fn; const fqn = closure.fqn; @@ -488,11 +398,12 @@ function _wrap_error_flag(is_exception: Int32Ptr | null, ex: any): string { export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: WasmRoot): void { const res = _wrap_error_flag(is_exception, ex); + if (NativeAOT) { + return; + } + stringToMonoStringRoot(res, result); } -export function wrap_error(is_exception: Int32Ptr | null, ex: any): void { - _wrap_error_flag(is_exception, ex); -} // to set out parameters of icalls export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmRoot): void { @@ -505,13 +416,6 @@ export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmR } } -export function wrap_no_error(is_exception: Int32Ptr | null): void { - if (is_exception) { - receiveWorkerHeapViews(); - setI32_unchecked(is_exception, 0); - } -} - export function assert_bindings(): void { loaderHelpers.assert_runtime_running(); if (MonoWasmThreads) { diff --git a/src/mono/wasm/runtime/roots.ts b/src/mono/wasm/runtime/roots.ts index 21f9f18077b7..e34e56f4c1b1 100644 --- a/src/mono/wasm/runtime/roots.ts +++ b/src/mono/wasm/runtime/roots.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import NativeAOT from "consts:nativeAOT"; import cwraps from "./cwraps"; import { Module } from "./globals"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; @@ -60,6 +61,15 @@ export function mono_wasm_new_root_buffer_from_pointer(offset: VoidPtr, capacity * Releasing this root will not de-allocate the root space. You still need to call .release(). */ export function mono_wasm_new_external_root(address: VoidPtr | MonoObjectRef): WasmRoot { + if (NativeAOT) { + return { + // eslint-disable-next-line @typescript-eslint/no-empty-function + release: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + clear: () => {} + } as unknown as WasmRoot; + } + let result: WasmExternalRoot; if (!address) From 2542cc90e40b6ce465bb4dc8acd57ee7b8daab64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 10:46:51 +0100 Subject: [PATCH 19/32] Fix export sample --- src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index 3989935427a5..c07a80d5c21e 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -15,10 +15,10 @@ setModuleImports('main.js', { let result = await runMain(); +const exports = await getAssemblyExports("DotnetJs.dll"); const square = exports.DotnetJsApp.Interop.Square(5); if (square != 25) { result = 13; -const exports = await getAssemblyExports("DotnetJs.dll"); } console.log(`Exit code ${result}`); From eee20bdc17930b9f4628480505e4008dcd66f789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 10:47:40 +0100 Subject: [PATCH 20/32] Remove export sample --- .../nativeaot/SmokeTests/DotnetJs/wwwroot/main.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index c07a80d5c21e..c7d6a808ec35 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -15,11 +15,12 @@ setModuleImports('main.js', { let result = await runMain(); -const exports = await getAssemblyExports("DotnetJs.dll"); -const square = exports.DotnetJsApp.Interop.Square(5); -if (square != 25) { - result = 13; -} +// TODO requires IlcExportUnmanagedEntrypoints +// const exports = await getAssemblyExports("DotnetJs.dll"); +// const square = exports.DotnetJsApp.Interop.Square(5); +// if (square != 25) { +// result = 13; +// } console.log(`Exit code ${result}`); exit(result); From d112a86b7ffdbc10498ccdd68436b2d450aff879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 11:23:40 +0100 Subject: [PATCH 21/32] Make JSExport work --- .../BuildIntegration/DotNetJsApi.targets | 6 ++++-- .../nativeaot/SmokeTests/DotnetJs/Program.cs | 5 +++-- .../SmokeTests/DotnetJs/wwwroot/main.js | 16 ++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets index ae5549d13825..ce16e7ced543 100644 --- a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets +++ b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets @@ -1,7 +1,8 @@ - $(LinkNativeDependsOn);PrepareDotNetJsApiForLinking + $(LinkNativeDependsOn);PrepareDotNetJsApiForLinking $(NativeOutputPath)dotnet.native.js + true @@ -72,12 +73,13 @@ <_DotNetJsLinkerFlag Include="-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$(_EmccExportedLibraryFunction)" Condition="'$(_EmccExportedLibraryFunction)' != ''" /> <_DotNetJsLinkerFlag Include="-s EXPORTED_RUNTIME_METHODS=$(_EmccExportedRuntimeMethods)" /> - <_DotNetJsLinkerFlag Include="-s EXPORTED_FUNCTIONS=$(_EmccExportedFunctions)" /> + <_DotNetJsLinkerFlag Include="-s EXPORTED_FUNCTIONS=$(_EmccExportedFunctions)" Condition="'$(ExportsFile)' == ''" /> <_DotNetJsLinkerFlag Include="$(EmccExtraLDFlags)" /> + diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index c421cd599959..7d8e14dcc377 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -29,8 +29,9 @@ static partial class Interop [JSExport] internal static int Square(int x) { - Console.WriteLine($"Computing square of {x}"); - return x^2; + var result = x * x; + Console.WriteLine($"Computing square of {x} with result {result}"); + return result; } } } diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index c7d6a808ec35..edd189182d75 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -15,12 +15,16 @@ setModuleImports('main.js', { let result = await runMain(); -// TODO requires IlcExportUnmanagedEntrypoints -// const exports = await getAssemblyExports("DotnetJs.dll"); -// const square = exports.DotnetJsApp.Interop.Square(5); -// if (square != 25) { -// result = 13; -// } +try { + const exports = await getAssemblyExports("DotnetJs.dll"); + const square = exports.DotnetJsApp.Program.Interop.Square(5); + if (square != 25) { + result = 13; + } +} catch (e) { + console.log(`Square thrown ${e}`); + result = 14; +} console.log(`Exit code ${result}`); exit(result); From a0cb512cc0db8542f922812199dd277cd2fe1657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 11:31:18 +0100 Subject: [PATCH 22/32] Comments --- src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets index ce16e7ced543..c8af93269b05 100644 --- a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets +++ b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets @@ -2,7 +2,7 @@ $(LinkNativeDependsOn);PrepareDotNetJsApiForLinking $(NativeOutputPath)dotnet.native.js - true + true @@ -85,7 +85,7 @@ <_FilesToCopyToNative Include="$(IlcFrameworkNativePath)\dotnet*.js" /> <_FilesToCopyToNative Include="$(IlcFrameworkNativePath)\dotnet*.map" Condition="'$(WasmEmitSourceMap)' == 'true'" /> - <_FilesToCopyToNative Include="@(WasmExtraFilesToDeploy)" /> + <_FilesToCopyToNative Include="@(WasmExtraFilesToDeploy)" /> From 3b3b7a50af1fcf2bc0c6971f59a4f64da81f7a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 11:35:09 +0100 Subject: [PATCH 23/32] Import DotNetJsApi.targets earlier so IlcExportUnmanagedEntrypoints=true works properly --- .../Microsoft.NETCore.Native.targets | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index db21edcfdc3c..ad030a034ee2 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -56,6 +56,17 @@ The .NET Foundation licenses this file to you under the MIT license. false + + + IlcCompile + CompileWasmObjects + + LinkNativeSingle + LinkNativeLlvm + + + + .obj .o @@ -87,18 +98,12 @@ The .NET Foundation licenses this file to you under the MIT license. .exports $(NativeIntermediateOutputPath)$(TargetName)$(NativeObjectExt) - $(NativeOutputPath)$(TargetName)$(NativeBinaryExt) + $(NativeOutputPath)$(TargetName)$(NativeBinaryExt) true $(NativeIntermediateOutputPath)$(TargetName)$(ExportsFileExt) $(NativeObject) - IlcCompile - CompileWasmObjects - - LinkNativeSingle - LinkNativeLlvm - $(NativeOutputPath) $(NativeIntermediateOutputPath) @@ -492,8 +497,6 @@ The .NET Foundation licenses this file to you under the MIT license. strip -no_code_signature_warning $(_StripFlag) "$(NativeBinary)"" /> - - Date: Tue, 19 Dec 2023 12:01:21 +0100 Subject: [PATCH 24/32] Add exception sample. Move todos to string marshaling in exception handling --- src/mono/wasm/runtime/invoke-cs.ts | 31 +++++++++++++++---- src/mono/wasm/runtime/marshal-to-js.ts | 4 +++ .../nativeaot/SmokeTests/DotnetJs/Program.cs | 3 ++ .../SmokeTests/DotnetJs/wwwroot/main.js | 16 +++++----- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 776868b5d2d7..6ea579f9d5ff 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -173,7 +173,11 @@ function bind_fn_0V(closure: BindingClosure) { const args = alloc_stack_frame(2); // call C# side if (NativeAOT) { - (method as any)(args); // TODO MF: handle exception + (method as any)(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } return; } invoke_method_and_handle_exception(method, args); @@ -200,7 +204,11 @@ function bind_fn_1V(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); // TODO MF: handle exception + (method as any)(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } return; } invoke_method_and_handle_exception(method, args); @@ -228,8 +236,11 @@ function bind_fn_1R(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); // TODO MF: handle exception - + (method as any)(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } const js_result = res_converter(args); return js_result; } @@ -263,7 +274,11 @@ function bind_fn_2R(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); // TODO MF: handle exception + (method as any)(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } const js_result = res_converter(args); return js_result; @@ -303,7 +318,11 @@ function bind_fn(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); // TODO MF: handle exception + (method as any)(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } if (res_converter) { const js_result = res_converter(args); diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index 21af5460110e..cbe9cf748f37 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import BuildConfiguration from "consts:configuration"; @@ -324,6 +325,9 @@ export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null if (type == MarshalerType.None) { return null; } + if (NativeAOT) { + return new Error("C# exception from NativeAOT"); // TODO-LLVM-JSInterop: Marshal exception message + } if (type == MarshalerType.JSException) { // this is JSException roundtrip const js_handle = get_arg_js_handle(arg); diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 7d8e14dcc377..29a2cde3a5f4 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -33,5 +33,8 @@ internal static int Square(int x) Console.WriteLine($"Computing square of {x} with result {result}"); return result; } + + [JSExport] + internal static void Throw() => throw new Exception("This is a test exception"); } } diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js index edd189182d75..9dde8fbe6025 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -15,15 +15,17 @@ setModuleImports('main.js', { let result = await runMain(); +const exports = await getAssemblyExports("DotnetJs.dll"); +const square = exports.DotnetJsApp.Program.Interop.Square(5); +if (square != 25) { + result = 13; +} + try { - const exports = await getAssemblyExports("DotnetJs.dll"); - const square = exports.DotnetJsApp.Program.Interop.Square(5); - if (square != 25) { - result = 13; - } -} catch (e) { - console.log(`Square thrown ${e}`); + exports.DotnetJsApp.Program.Interop.Throw(); result = 14; +} catch (e) { + console.log(`Thrown expected exception: ${e}`); } console.log(`Exit code ${result}`); From 4b81dab33233400d165de5ec1758b7f0ca6cbbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 12:03:22 +0100 Subject: [PATCH 25/32] Feedback --- .../Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs | 3 --- src/mono/wasm/runtime/invoke-cs.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs index 0c79db746598..19ac95fd828f 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs @@ -8,9 +8,6 @@ internal static partial class Interop { - // WARNING: until https://github.com/dotnet/runtime/issues/37955 is fixed - // make sure that the native side always sets the out parameters - // otherwise out parameters could stay un-initialized, when the method is used in inlined context internal static unsafe partial class Runtime { #pragma warning disable SYSLIB1054 diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 6ea579f9d5ff..4f20c06286d1 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -431,7 +431,6 @@ async function mono_wasm_get_assembly_exports_mono(assembly: string): Promise Date: Tue, 19 Dec 2023 12:32:59 +0100 Subject: [PATCH 26/32] Use library import and utf16 --- .../BuildIntegration/DotNetJsApi.targets | 3 ++- .../Browser/Interop.Runtime.NativeAOT.cs | 18 ++++++++---------- src/mono/wasm/runtime/dotnet.d.ts | 1 + src/mono/wasm/runtime/invoke-cs.ts | 2 +- src/mono/wasm/runtime/invoke-js.ts | 4 ++-- src/mono/wasm/runtime/types/emscripten.ts | 1 + 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets index c8af93269b05..e18305f14dbf 100644 --- a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets +++ b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets @@ -25,6 +25,7 @@ + @@ -53,7 +54,7 @@ <_DotNetJsLinkerFlag Include="-Wl,--export,__main_argc_argv" /> <_DotNetJsLinkerFlag Include="-s EXPORT_ES6=1" /> <_DotNetJsLinkerFlag Include="-s MODULARIZE=1" /> - <_DotNetJsLinkerFlag Include="-s INVOKE_RUN=0" /> + <_DotNetJsLinkerFlag Include="-s INVOKE_RUN=0" /> <_DotNetJsLinkerFlag Include="-s EXPORT_NAME="'createDotnetRuntime'"" /> <_DotNetJsLinkerFlag Include="-s ENVIRONMENT="'web,webview,worker,node,shell'"" /> <_DotNetJsLinkerFlag Condition="'$(EmccEnvironment)' != ''" Include="-s ENVIRONMENT="$(EmccEnvironment)"" /> diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs index 19ac95fd828f..0f9817e3f932 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.NativeAOT.cs @@ -10,30 +10,28 @@ internal static partial class Interop { internal static unsafe partial class Runtime { -#pragma warning disable SYSLIB1054 [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void ReleaseCSOwnedObject(IntPtr jsHandle); - [DllImport("*", EntryPoint = "mono_wasm_bind_js_function")] - public static extern unsafe void BindJSFunction(string function_name, int function_name_length, string module_name, int module_name_length, void* signature, out IntPtr bound_function_js_handle, out int is_exception); + [LibraryImport("*", EntryPoint = "mono_wasm_bind_js_function", StringMarshalling = StringMarshalling.Utf16)] + public static unsafe partial void BindJSFunction(string function_name, int function_name_length, string module_name, int module_name_length, void* signature, out IntPtr bound_function_js_handle, out int is_exception); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void InvokeJSFunction(IntPtr bound_function_js_handle, void* data); - [DllImport("*", EntryPoint = "mono_wasm_invoke_import")] - public static extern unsafe void InvokeImport(IntPtr fn_handle, void* data); - [DllImport("*", EntryPoint = "mono_wasm_bind_cs_function")] - public static extern unsafe void BindCSFunction(string fully_qualified_name, int fully_qualified_name_length, int signature_hash, void* signature, out int is_exception); + [LibraryImport("*", EntryPoint = "mono_wasm_invoke_import", StringMarshalling = StringMarshalling.Utf16)] + public static unsafe partial void InvokeImport(IntPtr fn_handle, void* data); + [LibraryImport("*", EntryPoint = "mono_wasm_bind_cs_function", StringMarshalling = StringMarshalling.Utf16)] + public static unsafe partial void BindCSFunction(string fully_qualified_name, int fully_qualified_name_length, int signature_hash, void* signature, out int is_exception); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void ResolveOrRejectPromise(void* data); [MethodImpl(MethodImplOptions.InternalCall)] public static extern IntPtr RegisterGCRoot(IntPtr start, int bytesSize, IntPtr name); [MethodImpl(MethodImplOptions.InternalCall)] public static extern void DeregisterGCRoot(IntPtr handle); -#pragma warning restore SYSLIB1054 public static unsafe void BindJSFunction(string function_name, string module_name, void* signature, out IntPtr bound_function_js_handle, out int is_exception, out object result) { BindJSFunction(function_name, function_name.Length, module_name, module_name.Length, signature, out bound_function_js_handle, out is_exception); if (is_exception != 0) - result = "Runtime.BindJSFunction failed"; + result = "Runtime.BindJSFunction failed"; // TODO-LLVM-JSInterop: Marshal exception message else result = ""; } @@ -42,7 +40,7 @@ public static unsafe void BindCSFunction(in string fully_qualified_name, int sig { BindCSFunction(fully_qualified_name, fully_qualified_name.Length, signature_hash, signature, out is_exception); if (is_exception != 0) - result = "Runtime.BindCSFunction failed"; + result = "Runtime.BindCSFunction failed"; // TODO-LLVM-JSInterop: Marshal exception message else result = ""; } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 5dde3a3e265c..b90c64f7c65e 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -48,6 +48,7 @@ declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + UTF16ToString(ptr: CharPtr, maxBytesToRead?: number): string; stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 4f20c06286d1..b21240ae3862 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -39,7 +39,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); - const js_fqn = NativeAOT ? Module.UTF8ToString(arguments[0], arguments[1]) : monoStringToString(fqn_root)!; + const js_fqn = NativeAOT ? Module.UTF16ToString(arguments[0], arguments[1]) : monoStringToString(fqn_root)!; mono_assert(js_fqn, "fully_qualified_name must be string"); mono_log_debug(`Binding [JSExport] ${js_fqn}`); diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index 5b9ce177c22c..ea0fbade56ba 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -39,9 +39,9 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - const js_function_name = NativeAOT ? Module.UTF8ToString(arguments[0], arguments[1]) : monoStringToString(function_name_root)!; + const js_function_name = NativeAOT ? Module.UTF16ToString(arguments[0], arguments[1]) : monoStringToString(function_name_root)!; const mark = startMeasure(); - const js_module_name = NativeAOT ? Module.UTF8ToString(arguments[2], arguments[3]) : monoStringToString(module_name_root)!; + const js_module_name = NativeAOT ? Module.UTF16ToString(arguments[2], arguments[3]) : monoStringToString(module_name_root)!; mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index 66ad8bb5aad0..45e86752e89b 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -57,6 +57,7 @@ export declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + UTF16ToString(ptr: CharPtr, maxBytesToRead?: number): string; stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; From 79ff965296f6f11f6f404c9778046656b038dccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 12:45:39 +0100 Subject: [PATCH 27/32] utf16ToString instead of Module.UTF16ToString --- src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets | 1 - src/mono/wasm/runtime/invoke-cs.ts | 3 ++- src/mono/wasm/runtime/invoke-js.ts | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets index e18305f14dbf..791646cece26 100644 --- a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets +++ b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets @@ -25,7 +25,6 @@ - diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index b21240ae3862..494688ce5cd7 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -15,6 +15,7 @@ import { } from "./marshal"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; import { monoStringToString } from "./strings"; +import { utf16ToString } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull, MarshalerType } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; @@ -39,7 +40,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, mono_assert(version === 2, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); - const js_fqn = NativeAOT ? Module.UTF16ToString(arguments[0], arguments[1]) : monoStringToString(fqn_root)!; + const js_fqn = NativeAOT ? utf16ToString(arguments[0], arguments[0] + 2 * arguments[1]) : monoStringToString(fqn_root)!; mono_assert(js_fqn, "fully_qualified_name must be string"); mono_log_debug(`Binding [JSExport] ${js_fqn}`); diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index ea0fbade56ba..3e8760be7049 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -10,6 +10,7 @@ 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, get_signature_type, imported_js_function_symbol } from "./marshal"; import { setI32, setI32_unchecked, receiveWorkerHeapViews } from "./memory"; import { monoStringToString, stringToMonoStringRoot } from "./strings"; +import { utf16ToString } from "./strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; @@ -39,9 +40,9 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const version = get_signature_version(signature); mono_assert(version === 2, () => `Signature version ${version} mismatch.`); - const js_function_name = NativeAOT ? Module.UTF16ToString(arguments[0], arguments[1]) : monoStringToString(function_name_root)!; + const js_function_name = NativeAOT ? utf16ToString(arguments[0], arguments[0] + 2 * arguments[1]) : monoStringToString(function_name_root)!; const mark = startMeasure(); - const js_module_name = NativeAOT ? Module.UTF16ToString(arguments[2], arguments[3]) : monoStringToString(module_name_root)!; + const js_module_name = NativeAOT ? utf16ToString(arguments[2], arguments[2] + 2 * arguments[3]) : monoStringToString(module_name_root)!; mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); From bfd9d7623875a0afbf354824341c2dbe20e182c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 12:52:52 +0100 Subject: [PATCH 28/32] Write to ExportsFile only if it doesn't contain required exports --- .../nativeaot/BuildIntegration/DotNetJsApi.targets | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets index 791646cece26..7f64fcbe55e2 100644 --- a/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets +++ b/src/coreclr/nativeaot/BuildIntegration/DotNetJsApi.targets @@ -79,7 +79,14 @@ - + + + + + <_ExportsToAddToExportsFile Include="@(EmccExportedFunction)" /> + <_ExportsToAddToExportsFile Remove="@(_ExistingExports)" /> + + From 4ffd55f520a5cdc4d61a73ceb504b83292952803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 13:51:28 +0100 Subject: [PATCH 29/32] Drop emscripten's UTF16ToString --- src/mono/wasm/runtime/dotnet.d.ts | 1 - src/mono/wasm/runtime/types/emscripten.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index b90c64f7c65e..5dde3a3e265c 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -48,7 +48,6 @@ declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; - UTF16ToString(ptr: CharPtr, maxBytesToRead?: number): string; stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index 45e86752e89b..66ad8bb5aad0 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -57,7 +57,6 @@ export declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; - UTF16ToString(ptr: CharPtr, maxBytesToRead?: number): string; stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; From 7025ec894896bc4a1dc94fb39d80bae558238ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 14:17:34 +0100 Subject: [PATCH 30/32] Quotes around variables --- src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs index 29a2cde3a5f4..46609c2a278e 100644 --- a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -30,7 +30,7 @@ static partial class Interop internal static int Square(int x) { var result = x * x; - Console.WriteLine($"Computing square of {x} with result {result}"); + Console.WriteLine($"Computing square of '{x}' with result '{result}'"); return result; } From 2b48d5a1bdf0e1569f11c365bfddb6c5bd8fcfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 15:11:11 +0100 Subject: [PATCH 31/32] Feedback --- src/mono/wasm/runtime/invoke-cs.ts | 40 ++++++++++-------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 494688ce5cd7..238fcec4173f 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -174,11 +174,7 @@ function bind_fn_0V(closure: BindingClosure) { const args = alloc_stack_frame(2); // call C# side if (NativeAOT) { - (method as any)(args); - if (is_args_exception(args)) { - const exc = get_arg(args, 0); - throw marshal_exception_to_js(exc); - } + invoke_method_and_handle_exception_naot(method as any, args); return; } invoke_method_and_handle_exception(method, args); @@ -205,11 +201,7 @@ function bind_fn_1V(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); - if (is_args_exception(args)) { - const exc = get_arg(args, 0); - throw marshal_exception_to_js(exc); - } + invoke_method_and_handle_exception_naot(method as any, args); return; } invoke_method_and_handle_exception(method, args); @@ -237,11 +229,7 @@ function bind_fn_1R(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); - if (is_args_exception(args)) { - const exc = get_arg(args, 0); - throw marshal_exception_to_js(exc); - } + invoke_method_and_handle_exception_naot(method as any, args); const js_result = res_converter(args); return js_result; } @@ -275,12 +263,7 @@ function bind_fn_2R(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); - if (is_args_exception(args)) { - const exc = get_arg(args, 0); - throw marshal_exception_to_js(exc); - } - + invoke_method_and_handle_exception_naot(method as any, args); const js_result = res_converter(args); return js_result; } @@ -319,12 +302,7 @@ function bind_fn(closure: BindingClosure) { // call C# side if (NativeAOT) { - (method as any)(args); - if (is_args_exception(args)) { - const exc = get_arg(args, 0); - throw marshal_exception_to_js(exc); - } - + invoke_method_and_handle_exception_naot(method as any, args); if (res_converter) { const js_result = res_converter(args); return js_result; @@ -369,6 +347,14 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM } } +export function invoke_method_and_handle_exception_naot(method: Function, args: JSMarshalerArguments): void { + method(args); + if (is_args_exception(args)) { + const exc = get_arg(args, 0); + throw marshal_exception_to_js(exc); + } +} + export const exportsByAssembly: Map = new Map(); function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void { const parts = `${namespace}.${classname}`.replace(/\//g, ".").split("."); From edc82cb72ed417d48e7d11ce042aebe2addca94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 19 Dec 2023 15:54:16 +0100 Subject: [PATCH 32/32] More feedback --- src/mono/wasm/runtime/invoke-cs.ts | 32 ++++-------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 238fcec4173f..5f5719424725 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -173,10 +173,6 @@ function bind_fn_0V(closure: BindingClosure) { try { const args = alloc_stack_frame(2); // call C# side - if (NativeAOT) { - invoke_method_and_handle_exception_naot(method as any, args); - return; - } invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); @@ -200,10 +196,6 @@ function bind_fn_1V(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - if (NativeAOT) { - invoke_method_and_handle_exception_naot(method as any, args); - return; - } invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); @@ -228,11 +220,6 @@ function bind_fn_1R(closure: BindingClosure) { marshaler1(args, arg1); // call C# side - if (NativeAOT) { - invoke_method_and_handle_exception_naot(method as any, args); - const js_result = res_converter(args); - return js_result; - } invoke_method_and_handle_exception(method, args); const js_result = res_converter(args); @@ -262,11 +249,6 @@ function bind_fn_2R(closure: BindingClosure) { marshaler2(args, arg2); // call C# side - if (NativeAOT) { - invoke_method_and_handle_exception_naot(method as any, args); - const js_result = res_converter(args); - return js_result; - } invoke_method_and_handle_exception(method, args); const js_result = res_converter(args); @@ -301,14 +283,6 @@ function bind_fn(closure: BindingClosure) { } // call C# side - if (NativeAOT) { - invoke_method_and_handle_exception_naot(method as any, args); - if (res_converter) { - const js_result = res_converter(args); - return js_result; - } - return; - } invoke_method_and_handle_exception(method, args); if (res_converter) { @@ -331,7 +305,7 @@ type BindingClosure = { isDisposed: boolean, } -export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { +function invoke_method_and_handle_exception_mono(method: MonoMethod, args: JSMarshalerArguments): void { assert_bindings(); const fail_root = mono_wasm_new_root(); try { @@ -347,7 +321,7 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM } } -export function invoke_method_and_handle_exception_naot(method: Function, args: JSMarshalerArguments): void { +function invoke_method_and_handle_exception_naot(method: Function, args: JSMarshalerArguments): void { method(args); if (is_args_exception(args)) { const exc = get_arg(args, 0); @@ -355,6 +329,8 @@ export function invoke_method_and_handle_exception_naot(method: Function, args: } } +export const invoke_method_and_handle_exception: (method: any, args: JSMarshalerArguments) => void = NativeAOT ? invoke_method_and_handle_exception_naot : invoke_method_and_handle_exception_mono; + export const exportsByAssembly: Map = new Map(); function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void { const parts = `${namespace}.${classname}`.replace(/\//g, ".").split(".");