Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] marshling call to assembly entrypoint via new interop #73156

Merged
merged 20 commits into from
Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
46e620c
- added `JavaScriptExports.CallEntrypoint` replacing legacy `mono_bin…
pavelsavara Aug 1, 2022
f3d1373
wip
pavelsavara Aug 1, 2022
e7ea896
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 1, 2022
378fd4e
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 2, 2022
2a85dd4
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 3, 2022
27f232b
Update src/libraries/System.Runtime.InteropServices.JavaScript/src/Sy…
pavelsavara Aug 3, 2022
0a2156f
feedback
pavelsavara Aug 3, 2022
2d9068e
fix
pavelsavara Aug 3, 2022
bb4d41b
fix
pavelsavara Aug 3, 2022
b26c856
[wasm] Fix Wasm.Build.Tests builds
radical Aug 3, 2022
b487be6
[wasm] Disable failing Wasm.Build.Tests test on windows
radical Aug 3, 2022
cb4ea99
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 3, 2022
ab01424
Merge branch 'wbt-green' into wasm_drop_legacy_entrypoint
pavelsavara Aug 3, 2022
3a90cdf
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 3, 2022
af9bb66
Merge branch 'main' into wasm_drop_legacy_entrypoint
pavelsavara Aug 4, 2022
d6c8330
types cleanup
pavelsavara Aug 4, 2022
f1a77d3
Update src/libraries/System.Runtime.InteropServices.JavaScript/src/Sy…
pavelsavara Aug 4, 2022
3763a30
Update src/libraries/System.Runtime.InteropServices.JavaScript/src/Sy…
pavelsavara Aug 4, 2022
00f8f08
Update src/libraries/System.Runtime.InteropServices.JavaScript/src/Sy…
pavelsavara Aug 4, 2022
b950b9f
feedback
pavelsavara Aug 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace System.Runtime.InteropServices.JavaScript
{
// this maps to src\mono\wasm\runtime\corebindings.ts
// the methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
internal static unsafe partial class JavaScriptExports
{
[MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425
// the marshaled signature is:
// Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer)
{
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_result = ref arguments_buffer[1]; // initialized by caller in alloc_stack_frame()
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
try
{
arg_1.ToManaged(out IntPtr entrypointPtr);
if (entrypointPtr == IntPtr.Zero)
{
throw new MissingMethodException("Missing entrypoint");
}

RuntimeMethodHandle methodHandle = JSHostImplementation.GetMethodHandleFromIntPtr(entrypointPtr);
// this would not work for generic types. But Main() could not be generic, so we are fine.
MethodInfo? method = MethodBase.GetMethodFromHandle(methodHandle) as MethodInfo;
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
if (method == null)
{
throw new InvalidProgramException("Can't resolve entrypoint handle");
}

arg_2.ToManaged(out string?[]? args);
object[] argsToPass= System.Array.Empty<object>();
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
Task<int>? result = null;
var parameterInfos = method.GetParameters();
if (parameterInfos.Length > 0 && parameterInfos[0].ParameterType == typeof(string[]))
{
argsToPass = new object[] { args ?? System.Array.Empty<string>() };
}
if (method.ReturnType == typeof(void))
{
method.Invoke(null, argsToPass);
}
else if (method.ReturnType == typeof(int))
{
int intResult = (int)method.Invoke(null, argsToPass)!;
result = Task.FromResult(intResult);
}
else if (method.ReturnType == typeof(Task))
{
Task result1 = (Task)method.Invoke(null, argsToPass)!;
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
result = tcs.Task;
result1.ContinueWith((t) =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception!);
}
else
{
tcs.SetResult(0);
}
}, TaskScheduler.Default);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
}
else if (method.ReturnType == typeof(Task<int>))
{
result = (Task<int>)method.Invoke(null, argsToPass)!;
}
else
{
throw new InvalidProgramException(method.ReturnType.FullName);
pavelsavara marked this conversation as resolved.
Show resolved Hide resolved
}
arg_result.ToJS(result, (ref JSMarshalerArgument arg, int value) =>
{
arg.ToJS(value);
});
}
catch (Exception ex)
{
arg_exc.ToJS(ex);
}
}


// The JS layer invokes this method when the JS wrapper for a JS owned object
// has been collected by the JS garbage collector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ static void MarshalResult(ref JSMarshalerArgument arg, object? taskResult)
/// Implementation of the argument marshaling.
/// It's used by JSImport code generator and should not be used by developers in source code.
/// </summary>
public void ToJS(Task value)
public void ToJS(Task? value)
{
Task task = value;
Task? task = value;

if (task == null)
{
Expand Down
5 changes: 3 additions & 2 deletions src/mono/wasm/runtime/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.

import Configuration from "consts:configuration";
import { INTERNAL, Module, MONO, runtimeHelpers } from "./imports";
import { INTERNAL, Module, runtimeHelpers } from "./imports";
import { toBase64StringImpl } from "./base64";
import cwraps from "./cwraps";
import { VoidPtr, CharPtr } from "./types/emscripten";
import { MONO } from "./net6-legacy/imports";
const commands_received: any = new Map<number, CommandResponse>();
const wasm_func_map = new Map<number, string>();
commands_received.remove = function (key: number): CommandResponse { const value = this.get(key); this.delete(key); return value; };
Expand Down Expand Up @@ -34,7 +35,7 @@ regexes.push(/(?<replaceSection>[a-z]+:\/\/[^ )]*:wasm-function\[(?<funcNum>\d+)
regexes.push(/(?<replaceSection><[^ >]+>[.:]wasm-function\[(?<funcNum>[0-9]+)\])/);

export function mono_wasm_runtime_ready(): void {
runtimeHelpers.mono_wasm_runtime_is_ready = true;
INTERNAL.mono_wasm_runtime_is_ready = runtimeHelpers.mono_wasm_runtime_is_ready = true;

// FIXME: where should this go?
_next_call_function_res_id = 0;
Expand Down
8 changes: 7 additions & 1 deletion src/mono/wasm/runtime/dotnet.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//!
//! This is generated file, see src/mono/wasm/runtime/rollup.config.js

//! This is not considered public API with backward compatibility guarantees.
//! This is not considered public API with backward compatibility guarantees.

declare interface ManagedPointer {
__brandManagedPointer: "ManagedPointer";
Expand Down Expand Up @@ -392,7 +392,13 @@ declare function getF32(offset: _MemOffset): number;
declare function getF64(offset: _MemOffset): number;
declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr;

/**
* Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line
*/
declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise<void>;
/**
* Possible signatures are described here https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/main-command-line
*/
declare function mono_run_main(main_assembly_name: string, args: string[]): Promise<number>;

declare function mono_wasm_setenv(name: string, value: string): void;
Expand Down
2 changes: 2 additions & 0 deletions src/mono/wasm/runtime/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { export_binding_api, export_mono_api } from "./net6-legacy/exports-legac
import { export_internal } from "./exports-internal";
import { export_linker } from "./exports-linker";
import { init_polyfills } from "./polyfills";
import { set_legacy_exports } from "./net6-legacy/imports";

export const __initializeImportsAndExports: any = initializeImportsAndExports; // don't want to export the type
export let __linker_exports: any = null;
Expand All @@ -35,6 +36,7 @@ function initializeImportsAndExports(

// we want to have same instance of MONO, BINDING and Module in dotnet iffe
set_imports_exports(imports, exports);
set_legacy_exports(exports);
init_polyfills(replacements);

// here we merge methods from the local objects into exported objects
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/gc-handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function teardown_managed_proxy(result: any, gc_handle: GCHandle): void {
}
}
if (gc_handle !== GCHandleNull && _js_owned_object_table.delete(gc_handle)) {
runtimeHelpers.javaScriptExports._release_js_owned_object_by_gc_handle(gc_handle);
runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle(gc_handle);
}
}

Expand Down
26 changes: 2 additions & 24 deletions src/mono/wasm/runtime/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
/* eslint-disable @typescript-eslint/triple-slash-reference */
/// <reference path="./types/v8.d.ts" />

import { BINDINGType, MONOType } from "./net6-legacy/exports-legacy";
import { DotnetModule, EarlyExports, EarlyImports, MonoConfig, RuntimeHelpers } from "./types";
import { DotnetModule, EarlyExports, EarlyImports, RuntimeHelpers } from "./types";
import { EmscriptenModule } from "./types/emscripten";

// these are our public API (except internal)
export let Module: EmscriptenModule & DotnetModule;
export let MONO: MONOType;
export let BINDING: BINDINGType;
export let INTERNAL: any;
export let EXPORTS: any;
export let IMPORTS: any;
Expand All @@ -28,8 +25,6 @@ export function set_imports_exports(
imports: EarlyImports,
exports: EarlyExports,
): void {
MONO = exports.mono;
BINDING = exports.binding;
INTERNAL = exports.internal;
Module = exports.module;

Expand All @@ -46,29 +41,12 @@ export function set_imports_exports(
runtimeHelpers.requirePromise = imports.requirePromise;
}

let monoConfig: MonoConfig = {} as any;
let runtime_is_ready = false;

export const runtimeHelpers: RuntimeHelpers = <any>{
javaScriptExports: {},
mono_wasm_load_runtime_done: false,
mono_wasm_bindings_is_ready: false,
max_parallel_downloads: 16,
get mono_wasm_runtime_is_ready() {
return runtime_is_ready;
},
set mono_wasm_runtime_is_ready(value: boolean) {
runtime_is_ready = value;
INTERNAL.mono_wasm_runtime_is_ready = value;
},
get config() {
return monoConfig;
},
set config(value: MonoConfig) {
monoConfig = value;
MONO.config = value;
Module.config = value;
},
config: {},
diagnostic_tracing: false,
enable_debugging: false,
fetch: null
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/runtime/invoke-cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function _walk_exports_to_set_function(assembly: string, namespace: string, clas
}

export async function mono_wasm_get_assembly_exports(assembly: string): Promise<any> {
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "Expected binding to be initialized later during startup sequence.");
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized.");
const result = exportsByAssembly.get(assembly);
if (!result) {
const asm = assembly_load(assembly);
Expand Down
50 changes: 38 additions & 12 deletions src/mono/wasm/runtime/managed-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ import cwraps from "./cwraps";
import { Module, runtimeHelpers } from "./imports";
import { alloc_stack_frame, get_arg, get_arg_gc_handle, MarshalerToCs, MarshalerToJs, MarshalerType, set_arg_type, set_gc_handle } from "./marshal";
import { invoke_method_and_handle_exception } from "./invoke-cs";
import { marshal_exception_to_cs } from "./marshal-to-cs";
import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs";
import { marshal_int32_to_js, marshal_task_to_js } from "./marshal-to-js";

// in all the exported internals methods, we use the same data structures for stack frame as normal full blow interop
// see src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\Interop\JavaScriptExports.cs
export interface JavaScriptExports {
// the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle)
_release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void;
release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void;
// the marshaled signature is: GCHandle CreateTaskCallback()
_create_task_callback(): GCHandle;
create_task_callback(): GCHandle;
// the marshaled signature is: void CompleteTask<T>(GCHandle holder, Exception? exceptionResult, T? result)
_complete_task(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void;
complete_task(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void;
// the marshaled signature is: TRes? CallDelegate<T1,T2,T3TRes>(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
_call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any,
call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any,
res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): any;
// the marshaled signature is: Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
call_entry_point(entry_point: MonoMethod, args?: string[]): Promise<number>;
}

export function init_managed_exports(): void {
Expand All @@ -36,6 +39,8 @@ export function init_managed_exports(): void {
throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class";


const call_entry_point = get_method("CallEntrypoint");
mono_assert(call_entry_point, "Can't find CallEntrypoint method");
const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle");
mono_assert(release_js_owned_object_by_gc_handle_method, "Can't find ReleaseJSOwnedObjectByGCHandle method");
const create_task_callback_method = get_method("CreateTaskCallback");
Expand All @@ -44,23 +49,42 @@ export function init_managed_exports(): void {
mono_assert(complete_task_method, "Can't find CompleteTask method");
const call_delegate_method = get_method("CallDelegate");
mono_assert(call_delegate_method, "Can't find CallDelegate method");

runtimeHelpers.javaScriptExports._release_js_owned_object_by_gc_handle = (gc_handle: GCHandle) => {
if (!gc_handle) {
Module.printErr("Must be valid gc_handle");
runtimeHelpers.javaScriptExports.call_entry_point = (entry_point: MonoMethod, program_args?: string[]) => {
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(4);
const res = get_arg(args, 1);
const arg1 = get_arg(args, 2);
const arg2 = get_arg(args, 3);
marshal_intptr_to_cs(arg1, entry_point);
if (program_args && program_args.length == 0) {
program_args = undefined;
}
marshal_array_to_cs_impl(arg2, program_args, MarshalerType.String);
invoke_method_and_handle_exception(call_entry_point, args);
const promise = marshal_task_to_js(res, undefined, marshal_int32_to_js);
if (!promise) {
return Promise.resolve(0);
}
return promise;
} finally {
anyModule.stackRestore(sp);
}
};
runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle = (gc_handle: GCHandle) => {
mono_assert(gc_handle, "Must be valid gc_handle");
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(3);
const arg1 = get_arg(args, 2);
set_arg_type(arg1, MarshalerType.Object);
set_gc_handle(arg1, gc_handle);
invoke_method_and_handle_exception(release_js_owned_object_by_gc_handle_method, args);
} finally {
anyModule.stackRestore(sp);
}
};
runtimeHelpers.javaScriptExports._create_task_callback = () => {
runtimeHelpers.javaScriptExports.create_task_callback = () => {
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(2);
Expand All @@ -71,11 +95,12 @@ export function init_managed_exports(): void {
anyModule.stackRestore(sp);
}
};
runtimeHelpers.javaScriptExports._complete_task = (holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => {
runtimeHelpers.javaScriptExports.complete_task = (holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) => {
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(5);
const arg1 = get_arg(args, 2);
set_arg_type(arg1, MarshalerType.Object);
set_gc_handle(arg1, holder_gc_handle);
const arg2 = get_arg(args, 3);
if (error) {
Expand All @@ -91,12 +116,13 @@ export function init_managed_exports(): void {
anyModule.stackRestore(sp);
}
};
runtimeHelpers.javaScriptExports._call_delegate = (callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => {
runtimeHelpers.javaScriptExports.call_delegate = (callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => {
const sp = anyModule.stackSave();
try {
const args = alloc_stack_frame(6);

const arg1 = get_arg(args, 2);
set_arg_type(arg1, MarshalerType.Object);
set_gc_handle(arg1, callback_gc_handle);
// payload arg numbers are shifted by one, the real first is a gc handle of the callback

Expand Down
Loading