From 4730697ad2c218f68dad1f5e788a897ac4fb01ea Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 3 Nov 2020 23:59:21 -0600 Subject: [PATCH] Wasm async entrypoint (#44045) * [browser][bindings] Add support for calling the async Task entry point. - When the entry point of an assembly is async we need to execute that method and return the Task object - The Task object that is return from the execution of the async entry point will be marshaled to a JavaScript Promise. Co-authored-by: Kenneth Pouncey --- src/mono/netcore/sample/wasm/console/Program.cs | 13 +++-- src/mono/wasm/runtime-test.js | 60 +++++++++------------- src/mono/wasm/runtime/driver.c | 28 +++++++++- .../Common/wasm-test-runner/WasmTestRunner.proj | 1 + 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/mono/netcore/sample/wasm/console/Program.cs b/src/mono/netcore/sample/wasm/console/Program.cs index 98cade6..eb79574 100644 --- a/src/mono/netcore/sample/wasm/console/Program.cs +++ b/src/mono/netcore/sample/wasm/console/Program.cs @@ -2,11 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Threading.Tasks; public class Test { - public static void Main (string[] args) + + public static async Task Main(string[] args) { - Console.WriteLine ("Hello, World!"); + await Task.Delay(1); + Console.WriteLine("Hello World!"); + for (int i = 0; i < args.Length; i++) { + Console.WriteLine($"args[{i}] = {args[i]}"); + } + return args.Length; } -} +} \ No newline at end of file diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 579eafa..51d494e 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -338,17 +338,8 @@ var App = { fail_exec ("Error: Missing main executable argument."); return; } - main_assembly = assembly_load (args[1]); - if (main_assembly == 0) { - fail_exec ("Error: Unable to load main executable '" + args[1] + "'"); - return; - } - main_method = assembly_get_entry_point (main_assembly); - if (main_method == 0) { - fail_exec ("Error: Main (string[]) method not found."); - return; - } + main_assembly_name = args[1]; var app_args = string_array_new (args.length - 2); for (var i = 2; i < args.length; ++i) { obj_array_set (app_args, i - 2, string_from_js (args [i])); @@ -365,42 +356,37 @@ var App = { } wasm_set_main_args (main_argc, main_argv); + function isThenable (js_obj) { + // When using an external Promise library the Promise.resolve may not be sufficient + // to identify the object as a Promise. + return Promise.resolve (js_obj) === js_obj || + ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function") + } + try { - var invoke_args = Module._malloc (4); - Module.setValue (invoke_args, app_args, "i32"); - var eh_exc = Module._malloc (4); - Module.setValue (eh_exc, 0, "i32"); - var res = runtime_invoke (main_method, 0, invoke_args, eh_exc); - var eh_res = Module.getValue (eh_exc, "i32"); - if (eh_res != 0) { - print ("Exception:" + string_get_utf8 (res)); - test_exit (1); + // Automatic signature isn't working correctly + let exit_code = Module.mono_call_assembly_entry_point (main_assembly_name, [app_args], "m"); + + if (isThenable (exit_code)) + { + exit_code.then ( + (result) => { + test_exit (result); + }, + (reason) => { + console.error (reason); + test_exit (1); + }); + } else { + test_exit (exit_code); return; } - var exit_code = unbox_int (res); - test_exit (exit_code); } catch (ex) { print ("JS exception: " + ex); print (ex.stack); test_exit (1); return; } - -/* - // For testing tp/timers etc. - while (true) { - // Sleep by busy waiting - var start = performance.now (); - useconds = 1e6 / 10; - while (performance.now() - start < useconds / 1000) { - // Do nothing. - } - - Module.pump_message (); - } -*/ - - return; } else { fail_exec ("Unhandled argument: " + args [0]); } diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index cda362c..1790446 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -624,8 +624,34 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) return NULL; mono_domain_ensure_entry_assembly (root_domain, assembly); + method = mono_get_method (image, entry, NULL); - return mono_get_method (image, entry, NULL); + /* + * If the entry point looks like a compiler generated wrapper around + * an async method in the form "" then try to look up the async method + * "Name" it is wrapping. We do this because the generated sync wrapper will + * call task.GetAwaiter().GetResult() when we actually want to yield + * to the host runtime. + */ + if (mono_method_get_flags (method, NULL) & 0x0800 /* METHOD_ATTRIBUTE_SPECIAL_NAME */) { + const char *name = mono_method_get_name (method); + int name_length = strlen (name); + + if ((*name != '<') || (name [name_length - 1] != '>')) + return method; + + MonoClass *klass = mono_method_get_class (method); + char *async_name = strdup (name); + + async_name [name_length - 1] = '\0'; + + MonoMethodSignature *sig = mono_method_get_signature (method, image, mono_method_get_token (method)); + MonoMethod *async_method = mono_class_get_method_from_name (klass, async_name + 1, mono_signature_get_param_count (sig)); + free (async_name); + if (async_method != NULL) + return async_method; + } + return method; } EMSCRIPTEN_KEEPALIVE char * diff --git a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj index 9d1c5ca..61f2f55 100644 --- a/src/tests/Common/wasm-test-runner/WasmTestRunner.proj +++ b/src/tests/Common/wasm-test-runner/WasmTestRunner.proj @@ -30,6 +30,7 @@ MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackDir)" MainAssembly="$(TestAssembly)" MainJS="$(CORE_ROOT)\runtime-test\runtime-test.js" + ExtraAssemblies="$(CORE_ROOT)\System.Private.Runtime.InteropServices.JavaScript.dll" AssemblySearchPaths="@(AssemblySearchPaths)" SkipMissingAssemblies="True" /> -- 2.7.4