Wasm async entrypoint (#44045)
authorLarry Ewing <lewing@microsoft.com>
Wed, 4 Nov 2020 05:59:21 +0000 (23:59 -0600)
committerGitHub <noreply@github.com>
Wed, 4 Nov 2020 05:59:21 +0000 (23:59 -0600)
* [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 <kjpou@pt.lu>
src/mono/netcore/sample/wasm/console/Program.cs
src/mono/wasm/runtime-test.js
src/mono/wasm/runtime/driver.c
src/tests/Common/wasm-test-runner/WasmTestRunner.proj

index 98cade6..eb79574 100644 (file)
@@ -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<int> 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
index 579eafa..51d494e 100644 (file)
@@ -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]);
                }
index cda362c..1790446 100644 (file)
@@ -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 "<Name>" 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 *
index 9d1c5ca..61f2f55 100644 (file)
@@ -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"
       />