Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavel Savara <pavel.savara@gmail.com>2022-07-10 22:40:36 +0300
committerGitHub <noreply@github.com>2022-07-10 22:40:36 +0300
commit8aa77a952a285dcd318cf8e6c99ba6f99cec897f (patch)
tree68f905ecf753a40fda259ccc1772d33c202aa455 /src/mono/wasm/runtime
parent31cbf121d396ab0b70a6e7c46cbdfb7cdbd841e2 (diff)
[wasm]: JavaScript interop with [JSImport] and [JSExport] attributes and Roslyn (#66304)
Co-authored-by: Marek FiĊĦera <mara@neptuo.com> Co-authored-by: Katelyn Gadd <kg@luminance.org>
Diffstat (limited to 'src/mono/wasm/runtime')
-rw-r--r--src/mono/wasm/runtime/cancelable-promise.ts52
-rw-r--r--src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js10
-rw-r--r--src/mono/wasm/runtime/corebindings.c25
-rw-r--r--src/mono/wasm/runtime/corebindings.ts4
-rw-r--r--src/mono/wasm/runtime/cs-to-js.ts7
-rw-r--r--src/mono/wasm/runtime/cwraps.ts13
-rw-r--r--src/mono/wasm/runtime/dotnet.d.ts75
-rw-r--r--src/mono/wasm/runtime/driver.c36
-rw-r--r--src/mono/wasm/runtime/es6/dotnet.es6.lib.js10
-rw-r--r--src/mono/wasm/runtime/export-types.ts36
-rw-r--r--src/mono/wasm/runtime/exports.ts50
-rw-r--r--src/mono/wasm/runtime/gc-common.h6
-rw-r--r--src/mono/wasm/runtime/http.ts150
-rw-r--r--src/mono/wasm/runtime/invoke-cs.ts206
-rw-r--r--src/mono/wasm/runtime/invoke-js.ts189
-rw-r--r--src/mono/wasm/runtime/marshal-to-cs.ts651
-rw-r--r--src/mono/wasm/runtime/marshal-to-js.ts581
-rw-r--r--src/mono/wasm/runtime/marshal.ts509
-rw-r--r--src/mono/wasm/runtime/startup.ts17
-rw-r--r--src/mono/wasm/runtime/types.ts3
-rw-r--r--src/mono/wasm/runtime/web-socket.ts484
21 files changed, 2753 insertions, 361 deletions
diff --git a/src/mono/wasm/runtime/cancelable-promise.ts b/src/mono/wasm/runtime/cancelable-promise.ts
index ebd01a86e5b..422d3b1ff66 100644
--- a/src/mono/wasm/runtime/cancelable-promise.ts
+++ b/src/mono/wasm/runtime/cancelable-promise.ts
@@ -1,11 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles";
-import { wrap_error_root } from "./method-calls";
-import { mono_wasm_new_external_root } from "./roots";
-import { JSHandle, MonoObject, MonoObjectRef, MonoString } from "./types";
-import { Int32Ptr } from "./types/emscripten";
+import { _lookup_js_owned_object } from "./gc-handles";
+import { TaskCallbackHolder } from "./marshal-to-cs";
+import { mono_assert, GCHandle } from "./types";
export const _are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");
export const promise_control_symbol = Symbol.for("wasm promise_control");
@@ -18,34 +16,20 @@ export function isThenable(js_obj: any): boolean {
((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function");
}
-export function mono_wasm_cancel_promise_ref(thenable_js_handle: JSHandle, is_exception: Int32Ptr, result_address: MonoObjectRef): void | MonoString {
- const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
- try {
- const promise = mono_wasm_get_jsobj_from_js_handle(thenable_js_handle);
- const promise_control = promise[promise_control_symbol];
- promise_control.reject("OperationCanceledException");
- }
- catch (ex) {
- wrap_error_root(is_exception, ex, resultRoot);
- return;
- }
- finally {
- resultRoot.release();
- }
-}
-
export interface PromiseControl {
isDone: boolean;
resolve: (data?: any) => void;
reject: (reason: any) => void;
+ promise: Promise<any>;
}
-export function _create_cancelable_promise(afterResolve?: () => void, afterReject?: () => void): {
+export function create_cancelable_promise(afterResolve?: () => void, afterReject?: () => void): {
promise: Promise<any>, promise_control: PromiseControl
} {
- let promise_control: PromiseControl | null = null;
+ let promise_control: PromiseControl = <any>null;
const promise = new Promise(function (resolve, reject) {
promise_control = {
+ promise: <any>null,
isDone: false,
resolve: (data: any) => {
if (!promise_control!.isDone) {
@@ -67,6 +51,26 @@ export function _create_cancelable_promise(afterResolve?: () => void, afterRejec
}
};
});
+ promise_control.promise = promise;
(<any>promise)[promise_control_symbol] = promise_control;
return { promise, promise_control: promise_control! };
-} \ No newline at end of file
+}
+
+export function wrap_as_cancelable_promise<T>(fn: () => Promise<T>): Promise<T> {
+ const { promise, promise_control } = create_cancelable_promise();
+ const inner = fn();
+ inner.then((data) => promise_control.resolve(data)).catch((reason) => promise_control.reject(reason));
+ return promise;
+}
+
+export function mono_wasm_cancel_promise(task_holder_gc_handle: GCHandle): void {
+ const holder = _lookup_js_owned_object(task_holder_gc_handle) as TaskCallbackHolder;
+ if (!holder) return; // probably already GC collected
+
+ const promise = holder.promise;
+ mono_assert(!!promise, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`);
+ const promise_control = (<any>promise)[promise_control_symbol];
+ mono_assert(!!promise_control, () => `Expected promise_control for GCHandle ${task_holder_gc_handle}`);
+ promise_control.reject("OperationCanceledException");
+}
+
diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
index 3f8a0c56cda..93aaa3a2e24 100644
--- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
+++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js
@@ -74,13 +74,11 @@ const linked_functions = [
"mono_wasm_typed_array_copy_to_ref",
"mono_wasm_typed_array_from_ref",
"mono_wasm_typed_array_copy_from_ref",
- "mono_wasm_cancel_promise_ref",
- "mono_wasm_web_socket_open_ref",
- "mono_wasm_web_socket_send",
- "mono_wasm_web_socket_receive",
- "mono_wasm_web_socket_close_ref",
- "mono_wasm_web_socket_abort",
"mono_wasm_compile_function_ref",
+ "mono_wasm_bind_js_function",
+ "mono_wasm_invoke_bound_function",
+ "mono_wasm_bind_cs_function",
+ "mono_wasm_marshal_promise",
// pal_icushim_static.c
"mono_wasm_load_icu_data",
diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c
index 2313924d9b9..11bd47ee8a3 100644
--- a/src/mono/wasm/runtime/corebindings.c
+++ b/src/mono/wasm/runtime/corebindings.c
@@ -29,12 +29,12 @@ extern void mono_wasm_typed_array_to_array_ref (int js_handle, int *is_exception
extern void mono_wasm_typed_array_copy_to_ref (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception, MonoObject** result);
extern void mono_wasm_typed_array_from_ref (int ptr, int begin, int end, int bytes_per_element, int type, int *is_exception, MonoObject** result);
extern void mono_wasm_typed_array_copy_from_ref (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception, MonoObject** result);
-extern void mono_wasm_cancel_promise_ref (int thenable_js_handle, int *is_exception, MonoString** result);
-extern void mono_wasm_web_socket_open_ref (MonoString **uri, MonoArray **subProtocols, MonoDelegate **on_close, int *web_socket_js_handle, int *thenable_js_handle, int *is_exception, MonoObject **result);
-extern void mono_wasm_web_socket_send (int webSocket_js_handle, void* buffer_ptr, int offset, int length, int message_type, int end_of_message, int *thenable_js_handle, int *is_exception, MonoObject **result);
-extern void mono_wasm_web_socket_receive (int webSocket_js_handle, void* buffer_ptr, int offset, int length, void* response_ptr, int *thenable_js_handle, int *is_exception, MonoObject **result);
-extern void mono_wasm_web_socket_close_ref (int webSocket_js_handle, int code, MonoString **reason, int wait_for_close_received, int *thenable_js_handle, int *is_exception, MonoObject **result);
-extern void mono_wasm_web_socket_abort (int webSocket_js_handle, int *is_exception, MonoString **result);
+
+extern void mono_wasm_bind_js_function(MonoString **function_name, MonoString **module_name, void *signature, int* function_js_handle, int *is_exception, MonoObject **result);
+extern void mono_wasm_invoke_bound_function(int function_js_handle, void *data);
+extern void mono_wasm_bind_cs_function(MonoString **fully_qualified_name, int signature_hash, void* signatures, int *is_exception, MonoObject **result);
+extern void mono_wasm_marshal_promise(void *data);
+
void core_initialize_internals ()
{
@@ -50,12 +50,13 @@ void core_initialize_internals ()
mono_add_internal_call ("Interop/Runtime::TypedArrayCopyToRef", mono_wasm_typed_array_copy_to_ref);
mono_add_internal_call ("Interop/Runtime::TypedArrayFromRef", mono_wasm_typed_array_from_ref);
mono_add_internal_call ("Interop/Runtime::TypedArrayCopyFromRef", mono_wasm_typed_array_copy_from_ref);
- mono_add_internal_call ("Interop/Runtime::WebSocketOpenRef", mono_wasm_web_socket_open_ref);
- mono_add_internal_call ("Interop/Runtime::WebSocketSend", mono_wasm_web_socket_send);
- mono_add_internal_call ("Interop/Runtime::WebSocketReceive", mono_wasm_web_socket_receive);
- mono_add_internal_call ("Interop/Runtime::WebSocketCloseRef", mono_wasm_web_socket_close_ref);
- mono_add_internal_call ("Interop/Runtime::WebSocketAbort", mono_wasm_web_socket_abort);
- mono_add_internal_call ("Interop/Runtime::CancelPromiseRef", mono_wasm_cancel_promise_ref);
+
+ mono_add_internal_call ("Interop/Runtime::BindJSFunction", mono_wasm_bind_js_function);
+ mono_add_internal_call ("Interop/Runtime::InvokeJSFunction", mono_wasm_invoke_bound_function);
+ mono_add_internal_call ("Interop/Runtime::BindCSFunction", mono_wasm_bind_cs_function);
+ mono_add_internal_call ("Interop/Runtime::MarshalPromise", mono_wasm_marshal_promise);
+ mono_add_internal_call ("Interop/Runtime::RegisterGCRoot", mono_wasm_register_root);
+ mono_add_internal_call ("Interop/Runtime::DeregisterGCRoot", mono_wasm_deregister_root);
}
// Int8Array | int8_t | byte or SByte (signed byte)
diff --git a/src/mono/wasm/runtime/corebindings.ts b/src/mono/wasm/runtime/corebindings.ts
index 82be21ce2a2..bd9c0fb12eb 100644
--- a/src/mono/wasm/runtime/corebindings.ts
+++ b/src/mono/wasm/runtime/corebindings.ts
@@ -5,6 +5,7 @@ import { JSHandle, GCHandle, MonoObjectRef } from "./types";
import { PromiseControl } from "./cancelable-promise";
import { runtimeHelpers } from "./imports";
+// TODO replace all of this with [JSExport]
const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMarshalString*/][] = [
["_get_cs_owned_object_by_js_handle_ref", "GetCSOwnedObjectByJSHandleRef", "iim"],
["_get_cs_owned_object_js_handle_ref", "GetCSOwnedObjectJSHandleRef", "mi"],
@@ -16,6 +17,7 @@ const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMar
["_release_js_owned_object_by_gc_handle", "ReleaseJSOwnedObjectByGCHandle", "i"],
["_create_tcs", "CreateTaskSource", ""],
+ ["_create_task_callback", "CreateTaskCallback", ""],
["_set_tcs_result_ref", "SetTaskSourceResultRef", "iR"],
["_set_tcs_failure", "SetTaskSourceFailure", "is"],
["_get_tcs_task_ref", "GetTaskSourceTaskRef", "im"],
@@ -53,6 +55,8 @@ export interface t_CSwraps {
_create_date_time_ref(ticks: number, result: MonoObjectRef): void;
_create_uri_ref(uri: string, result: MonoObjectRef): void;
_is_simple_array_ref(obj: MonoObjectRef): boolean;
+
+ _create_task_callback(): GCHandle;
}
const wrapped_cs_functions: t_CSwraps = <any>{};
diff --git a/src/mono/wasm/runtime/cs-to-js.ts b/src/mono/wasm/runtime/cs-to-js.ts
index 9f5e583dbdb..0f5392ea880 100644
--- a/src/mono/wasm/runtime/cs-to-js.ts
+++ b/src/mono/wasm/runtime/cs-to-js.ts
@@ -14,9 +14,10 @@ import cwraps from "./cwraps";
import { get_js_owned_object_by_gc_handle_ref, js_owned_gc_handle_symbol, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy, _lookup_js_owned_object } from "./gc-handles";
import { mono_method_get_call_signature_ref, call_method_ref, wrap_error_root } from "./method-calls";
import { js_to_mono_obj_root } from "./js-to-cs";
-import { _are_promises_supported, _create_cancelable_promise } from "./cancelable-promise";
+import { _are_promises_supported, create_cancelable_promise } from "./cancelable-promise";
import { getU32, getI32, getF32, getF64 } from "./memory";
import { Int32Ptr, VoidPtr } from "./types/emscripten";
+import { ManagedObject } from "./marshal";
const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke");
const delegate_invoke_signature_symbol = Symbol.for("wasm delegate_invoke_signature");
@@ -296,7 +297,7 @@ function _unbox_task_root_as_promise(root: WasmRoot<MonoObject>) {
if (!result) {
const explicitFinalization = () => teardown_managed_proxy(result, gc_handle);
- const { promise, promise_control } = _create_cancelable_promise(explicitFinalization, explicitFinalization);
+ const { promise, promise_control } = create_cancelable_promise(explicitFinalization, explicitFinalization);
// note that we do not implement promise/task roundtrip
// With more complexity we could recover original instance when this promise is marshaled back to C#.
@@ -335,7 +336,7 @@ export function _unbox_ref_type_root_as_js_object(root: WasmRoot<MonoObject>): a
// If the JS object for this gc_handle was already collected (or was never created)
if (is_nullish(result)) {
- result = {};
+ result = new ManagedObject();
// keep the gc_handle so that we could easily convert it back to original C# object for roundtrip
result[js_owned_gc_handle_symbol] = gc_handle;
diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts
index c4892f33dbf..b2963790bd2 100644
--- a/src/mono/wasm/runtime/cwraps.ts
+++ b/src/mono/wasm/runtime/cwraps.ts
@@ -8,6 +8,7 @@ import {
MonoType, MonoObjectRef, MonoStringRef
} from "./types";
import { Module } from "./imports";
+import { JSMarshalerArguments } from "./marshal";
import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten";
const fn_signatures: [ident: string, returnType: string | null, argTypes?: string[], opts?: any][] = [
@@ -37,6 +38,7 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin
["mono_wasm_assembly_load", "number", ["string"]],
["mono_wasm_find_corlib_class", "number", ["string", "string"]],
["mono_wasm_assembly_find_class", "number", ["number", "string", "string"]],
+ ["mono_wasm_runtime_run_module_cctor", "void", ["number"]],
["mono_wasm_find_corlib_type", "number", ["string", "string"]],
["mono_wasm_assembly_find_type", "number", ["number", "string", "string"]],
["mono_wasm_assembly_find_method", "number", ["number", "string", "number"]],
@@ -79,6 +81,7 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin
["mono_wasm_enable_on_demand_gc", "void", ["number"]],
["mono_profiler_init_aot", "void", ["number"]],
["mono_wasm_exec_regression", "number", ["number", "string"]],
+ ["mono_wasm_invoke_method_bound", "number", ["number", "number"]],
["mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
["mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
["mono_wasm_i52_to_f64", "number", ["number", "number"]],
@@ -182,12 +185,14 @@ export interface t_Cwraps {
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_profiler_init_aot(desc: string): void;
mono_wasm_exec_regression(verbose_level: number, image: string): number;
+ mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments): MonoString;
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
- mono_wasm_i52_to_f64 (source: VoidPtr, error: Int32Ptr) : number;
- mono_wasm_u52_to_f64 (source: VoidPtr, error: Int32Ptr) : number;
- mono_wasm_f64_to_i52 (destination: VoidPtr, value: number) : I52Error;
- mono_wasm_f64_to_u52 (destination: VoidPtr, value: number) : I52Error;
+ mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;
+ mono_wasm_u52_to_f64(source: VoidPtr, error: Int32Ptr): number;
+ mono_wasm_f64_to_i52(destination: VoidPtr, value: number): I52Error;
+ mono_wasm_f64_to_u52(destination: VoidPtr, value: number): I52Error;
+ mono_wasm_runtime_run_module_cctor(assembly: MonoAssembly): void;
}
const wrapped_c_functions: t_Cwraps = <any>{};
diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts
index 0f2110f53d0..dd58eb3e3f2 100644
--- a/src/mono/wasm/runtime/dotnet.d.ts
+++ b/src/mono/wasm/runtime/dotnet.d.ts
@@ -376,6 +376,49 @@ declare function getF64(offset: _MemOffset): number;
declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise<void>;
declare function mono_run_main(main_assembly_name: string, args: string[]): Promise<number>;
+interface IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+}
+declare class ManagedObject implements IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+ toString(): string;
+}
+declare class ManagedError extends Error implements IDisposable {
+ constructor(message: string);
+ get stack(): string | undefined;
+ dispose(): void;
+ get isDisposed(): boolean;
+ toString(): string;
+}
+declare enum MemoryViewType {
+ Byte = 0,
+ Int32 = 1,
+ Double = 2
+}
+interface IMemoryView {
+ /**
+ * copies elements from provided source to the wasm memory.
+ * target has to have the elements of the same type as the underlying C# array.
+ * same as TypedArray.set()
+ */
+ set(source: TypedArray, targetOffset?: number): void;
+ /**
+ * copies elements from wasm memory to provided target.
+ * target has to have the elements of the same type as the underlying C# array.
+ */
+ copyTo(target: TypedArray, sourceOffset?: number): void;
+ /**
+ * same as TypedArray.slice()
+ */
+ slice(start?: number, end?: number): TypedArray;
+ get length(): number;
+ get byteLength(): number;
+}
+
+declare function mono_wasm_get_assembly_exports(assembly: string): Promise<any>;
+
declare const MONO: {
mono_wasm_setenv: typeof mono_wasm_setenv;
mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap;
@@ -390,6 +433,7 @@ declare const MONO: {
mono_wasm_release_roots: typeof mono_wasm_release_roots;
mono_run_main: typeof mono_run_main;
mono_run_main_and_exit: typeof mono_run_main_and_exit;
+ mono_wasm_get_assembly_exports: typeof mono_wasm_get_assembly_exports;
mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number;
mono_wasm_load_runtime: (unused: string, debug_level: number) => void;
config: MonoConfig | MonoConfigError;
@@ -490,4 +534,33 @@ declare global {
function getDotnetRuntime(runtimeId: number): DotnetPublicAPI | undefined;
}
-export { BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, MONOType, MonoArray, MonoObject, MonoString, VoidPtr, createDotnetRuntime as default };
+/**
+ * Span class is JS wrapper for System.Span<T>. This view doesn't own the memory, nor pin the underlying array.
+ * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory.
+ * It is disposed at the end of the call to JS.
+ */
+declare class Span implements IMemoryView, IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+ set(source: TypedArray, targetOffset?: number | undefined): void;
+ copyTo(target: TypedArray, sourceOffset?: number | undefined): void;
+ slice(start?: number | undefined, end?: number | undefined): TypedArray;
+ get length(): number;
+ get byteLength(): number;
+}
+/**
+ * ArraySegment class is JS wrapper for System.ArraySegment<T>.
+ * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected.
+ * User could dispose it manualy.
+ */
+declare class ArraySegment implements IMemoryView, IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+ set(source: TypedArray, targetOffset?: number | undefined): void;
+ copyTo(target: TypedArray, sourceOffset?: number | undefined): void;
+ slice(start?: number | undefined, end?: number | undefined): TypedArray;
+ get length(): number;
+ get byteLength(): number;
+}
+
+export { ArraySegment, BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, IMemoryView, MONOType, ManagedError, ManagedObject, MemoryViewType, MonoArray, MonoObject, MonoString, Span, VoidPtr, createDotnetRuntime as default };
diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c
index fb0b15101fd..c8ea3e445d5 100644
--- a/src/mono/wasm/runtime/driver.c
+++ b/src/mono/wasm/runtime/driver.c
@@ -447,7 +447,6 @@ get_native_to_interp (MonoMethod *method, void *extra_arg)
void mono_initialize_internals ()
{
mono_add_internal_call ("Interop/Runtime::InvokeJS", mono_wasm_invoke_js);
- // TODO: what happens when two types in different assemblies have the same FQN?
// Blazor specific custom routines - see dotnet_support.js for backing code
mono_add_internal_call ("WebAssembly.JSInterop.InternalCalls::InvokeJS", mono_wasm_invoke_js_blazor);
@@ -655,6 +654,22 @@ mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, co
return result;
}
+extern int mono_runtime_run_module_cctor (MonoImage *image, MonoError *error);
+
+EMSCRIPTEN_KEEPALIVE void
+mono_wasm_runtime_run_module_cctor (MonoAssembly *assembly)
+{
+ assert (assembly);
+ MonoError error;
+ MONO_ENTER_GC_UNSAFE;
+ MonoImage *image = mono_assembly_get_image (assembly);
+ if (!mono_runtime_run_module_cctor(image, &error)) {
+ //g_print ("Failed to run module constructor due to %s\n", mono_error_get_message (error));
+ }
+ MONO_EXIT_GC_UNSAFE;
+}
+
+
EMSCRIPTEN_KEEPALIVE MonoMethod*
mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments)
{
@@ -742,6 +757,25 @@ mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[
return result;
}
+EMSCRIPTEN_KEEPALIVE MonoObject*
+mono_wasm_invoke_method_bound (MonoMethod *method, void* args)// JSMarshalerArguments
+{
+ MonoObject *exc = NULL;
+ MonoObject *res;
+
+ void *invoke_args[1] = { args };
+
+ mono_runtime_invoke (method, NULL, invoke_args, &exc);
+ if (exc) {
+ MonoObject *exc2 = NULL;
+ res = (MonoObject*)mono_object_to_string (exc, &exc2);
+ if (exc2)
+ res = (MonoObject*) mono_string_new (root_domain, "Exception Double Fault");
+ return res;
+ }
+ return NULL;
+}
+
EMSCRIPTEN_KEEPALIVE MonoMethod*
mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint)
{
diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
index 19fd04fecd5..36e8a8162ae 100644
--- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
+++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js
@@ -111,13 +111,11 @@ const linked_functions = [
"mono_wasm_typed_array_copy_to_ref",
"mono_wasm_typed_array_from_ref",
"mono_wasm_typed_array_copy_from_ref",
- "mono_wasm_cancel_promise_ref",
- "mono_wasm_web_socket_open_ref",
- "mono_wasm_web_socket_send",
- "mono_wasm_web_socket_receive",
- "mono_wasm_web_socket_close_ref",
- "mono_wasm_web_socket_abort",
"mono_wasm_compile_function_ref",
+ "mono_wasm_bind_js_function",
+ "mono_wasm_invoke_bound_function",
+ "mono_wasm_bind_cs_function",
+ "mono_wasm_marshal_promise",
// pal_icushim_static.c
"mono_wasm_load_icu_data",
diff --git a/src/mono/wasm/runtime/export-types.ts b/src/mono/wasm/runtime/export-types.ts
index 3cb8c0df090..057ed837709 100644
--- a/src/mono/wasm/runtime/export-types.ts
+++ b/src/mono/wasm/runtime/export-types.ts
@@ -2,8 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { BINDINGType, DotnetPublicAPI, MONOType } from "./exports";
+import { IDisposable, IMemoryView, ManagedError, ManagedObject, MemoryViewType } from "./marshal";
import { DotnetModuleConfig, MonoArray, MonoObject, MonoString } from "./types";
-import { EmscriptenModule, VoidPtr } from "./types/emscripten";
+import { EmscriptenModule, TypedArray, VoidPtr } from "./types/emscripten";
// -----------------------------------------------------------
// this files has all public exports from the dotnet.js module
@@ -20,10 +21,41 @@ declare global {
export default createDotnetRuntime;
+/**
+ * Span class is JS wrapper for System.Span<T>. This view doesn't own the memory, nor pin the underlying array.
+ * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory.
+ * It is disposed at the end of the call to JS.
+ */
+declare class Span implements IMemoryView, IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+ set(source: TypedArray, targetOffset?: number | undefined): void;
+ copyTo(target: TypedArray, sourceOffset?: number | undefined): void;
+ slice(start?: number | undefined, end?: number | undefined): TypedArray;
+ get length(): number;
+ get byteLength(): number;
+}
+
+/**
+ * ArraySegment class is JS wrapper for System.ArraySegment<T>.
+ * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected.
+ * User could dispose it manualy.
+ */
+declare class ArraySegment implements IMemoryView, IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+ set(source: TypedArray, targetOffset?: number | undefined): void;
+ copyTo(target: TypedArray, sourceOffset?: number | undefined): void;
+ slice(start?: number | undefined, end?: number | undefined): TypedArray;
+ get length(): number;
+ get byteLength(): number;
+}
+
export {
VoidPtr,
MonoObject, MonoString, MonoArray,
BINDINGType, MONOType, EmscriptenModule,
- DotnetPublicAPI, DotnetModuleConfig, CreateDotnetRuntimeType
+ DotnetPublicAPI, DotnetModuleConfig, CreateDotnetRuntimeType,
+ IMemoryView, MemoryViewType, ManagedObject, ManagedError, Span, ArraySegment
};
diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts
index 34da483b5ec..34646ca213b 100644
--- a/src/mono/wasm/runtime/exports.ts
+++ b/src/mono/wasm/runtime/exports.ts
@@ -67,7 +67,13 @@ import { create_weak_ref } from "./weak-ref";
import { fetch_like, readAsync_like } from "./polyfills";
import { EmscriptenModule } from "./types/emscripten";
import { mono_run_main, mono_run_main_and_exit } from "./run";
+import { dynamic_import, get_global_this, get_property, get_typeof_property, has_property, mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, set_property } from "./invoke-js";
+import { mono_wasm_bind_cs_function, mono_wasm_get_assembly_exports } from "./invoke-cs";
+import { mono_wasm_marshal_promise } from "./marshal-to-js";
+import { ws_wasm_abort, ws_wasm_close, ws_wasm_create, ws_wasm_open, ws_wasm_receive, ws_wasm_send } from "./web-socket";
+import { http_wasm_abort_request, http_wasm_abort_response, http_wasm_create_abort_controler, http_wasm_fetch, http_wasm_fetch_bytes, http_wasm_get_response_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes, http_wasm_supports_streaming_response } from "./http";
import { diagnostics } from "./diagnostics";
+import { mono_wasm_cancel_promise } from "./cancelable-promise";
import {
dotnet_browser_can_use_subtle_crypto_impl,
dotnet_browser_simple_digest_hash,
@@ -75,8 +81,6 @@ import {
dotnet_browser_encrypt_decrypt,
dotnet_browser_derive_bits,
} from "./crypto-worker";
-import { mono_wasm_cancel_promise_ref } from "./cancelable-promise";
-import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket";
import { mono_wasm_pthread_on_pthread_attached, afterThreadInitTLS } from "./pthreads/worker";
import { afterLoadWasmModuleToWorker } from "./pthreads/browser";
@@ -95,6 +99,7 @@ const MONO = {
mono_wasm_release_roots,
mono_run_main,
mono_run_main_and_exit,
+ mono_wasm_get_assembly_exports,
// for Blazor's future!
mono_wasm_add_assembly: cwraps.mono_wasm_add_assembly,
@@ -393,12 +398,10 @@ export const __linker_exports: any = {
mono_wasm_typed_array_copy_to_ref,
mono_wasm_typed_array_from_ref,
mono_wasm_typed_array_copy_from_ref,
- mono_wasm_cancel_promise_ref,
- mono_wasm_web_socket_open_ref,
- mono_wasm_web_socket_send,
- mono_wasm_web_socket_receive,
- mono_wasm_web_socket_close_ref,
- mono_wasm_web_socket_abort,
+ mono_wasm_bind_js_function,
+ mono_wasm_invoke_bound_function,
+ mono_wasm_bind_cs_function,
+ mono_wasm_marshal_promise,
// also keep in sync with pal_icushim_static.c
mono_wasm_load_icu_data,
@@ -451,6 +454,37 @@ const INTERNAL: any = {
mono_wasm_change_debugger_log_level,
mono_wasm_debugger_attached,
mono_wasm_runtime_is_ready: <boolean>runtimeHelpers.mono_wasm_runtime_is_ready,
+
+ // interop
+ get_property,
+ set_property,
+ has_property,
+ get_typeof_property,
+ get_global_this,
+ get_dotnet_instance,
+ dynamic_import,
+
+ // BrowserWebSocket
+ mono_wasm_cancel_promise,
+ ws_wasm_create,
+ ws_wasm_open,
+ ws_wasm_send,
+ ws_wasm_receive,
+ ws_wasm_close,
+ ws_wasm_abort,
+
+ // BrowserHttpHandler
+ http_wasm_supports_streaming_response,
+ http_wasm_create_abort_controler,
+ http_wasm_abort_request,
+ http_wasm_abort_response,
+ http_wasm_fetch,
+ http_wasm_fetch_bytes,
+ http_wasm_get_response_header_names,
+ http_wasm_get_response_header_values,
+ http_wasm_get_response_bytes,
+ http_wasm_get_response_length,
+ http_wasm_get_streamed_response_bytes,
};
// this represents visibility in the javascript
diff --git a/src/mono/wasm/runtime/gc-common.h b/src/mono/wasm/runtime/gc-common.h
index 4ea2bb06797..92789e641a3 100644
--- a/src/mono/wasm/runtime/gc-common.h
+++ b/src/mono/wasm/runtime/gc-common.h
@@ -61,3 +61,9 @@ static void
copy_volatile (PPVOLATILE(MonoObject) destination, PPVOLATILE(MonoObject) source) {
mono_gc_wbarrier_generic_store_atomic((void*)destination, (MonoObject*)(*source));
}
+
+EMSCRIPTEN_KEEPALIVE int
+mono_wasm_register_root (char *start, size_t size, const char *name);
+
+EMSCRIPTEN_KEEPALIVE void
+mono_wasm_deregister_root (char *addr); \ No newline at end of file
diff --git a/src/mono/wasm/runtime/http.ts b/src/mono/wasm/runtime/http.ts
new file mode 100644
index 00000000000..d8249fad0b9
--- /dev/null
+++ b/src/mono/wasm/runtime/http.ts
@@ -0,0 +1,150 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { wrap_as_cancelable_promise } from "./cancelable-promise";
+import { MemoryViewType, Span } from "./marshal";
+import { mono_assert } from "./types";
+import { VoidPtr } from "./types/emscripten";
+
+export function http_wasm_supports_streaming_response(): boolean {
+ return typeof Response !== "undefined" && "body" in Response.prototype && typeof ReadableStream === "function";
+}
+
+export function http_wasm_create_abort_controler(): AbortController {
+ return new AbortController();
+}
+
+export function http_wasm_abort_request(abort_controller: AbortController): void {
+ abort_controller.abort();
+}
+
+export function http_wasm_abort_response(res: ResponseExtension): void {
+ res.__abort_controller.abort();
+ if (res.__reader) {
+ res.__reader.cancel();
+ }
+}
+
+export function http_wasm_fetch_bytes(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, bodyPtr: VoidPtr, bodyLength: number): Promise<ResponseExtension> {
+ // the bufferPtr is pinned by the caller
+ const view = new Span(bodyPtr, bodyLength, MemoryViewType.Byte);
+ const copy = view.slice() as Uint8Array;
+ return http_wasm_fetch(url, header_names, header_values, option_names, option_values, abort_controller, copy);
+}
+
+export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: string | Uint8Array | null): Promise<ResponseExtension> {
+ mono_assert(url && typeof url === "string", "expected url string");
+ mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays");
+ mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays");
+ const headers = new Headers();
+ for (let i = 0; i < header_names.length; i++) {
+ headers.append(header_names[i], header_values[i]);
+ }
+ const options: any = {
+ body,
+ headers,
+ signal: abort_controller.signal
+ };
+ for (let i = 0; i < option_names.length; i++) {
+ options[option_names[i]] = option_values[i];
+ }
+
+ return wrap_as_cancelable_promise(async () => {
+ const res = await fetch(url, options) as ResponseExtension;
+ res.__abort_controller = abort_controller;
+ return res;
+ });
+}
+
+function get_response_headers(res: ResponseExtension): void {
+ if (!res.__headerNames) {
+ res.__headerNames = [];
+ res.__headerValues = [];
+ const entries: Iterable<string[]> = (<any>res.headers).entries();
+
+ for (const pair of entries) {
+ res.__headerNames.push(pair[0]);
+ res.__headerValues.push(pair[1]);
+ }
+ }
+}
+
+export function http_wasm_get_response_header_names(res: ResponseExtension): string[] {
+ get_response_headers(res);
+ return res.__headerNames;
+}
+
+export function http_wasm_get_response_header_values(res: ResponseExtension): string[] {
+ get_response_headers(res);
+ return res.__headerValues;
+}
+
+export function http_wasm_get_response_length(res: ResponseExtension): Promise<number> {
+ return wrap_as_cancelable_promise(async () => {
+ const buffer = await res.arrayBuffer();
+ res.__buffer = buffer;
+ res.__source_offset = 0;
+ return buffer.byteLength;
+ });
+}
+
+export function http_wasm_get_response_bytes(res: ResponseExtension, view: Span): number {
+ mono_assert(res.__buffer, "expected resoved arrayBuffer");
+ if (res.__source_offset == res.__buffer!.byteLength) {
+ return 0;
+ }
+ const source_view = new Uint8Array(res.__buffer!, res.__source_offset);
+ view.set(source_view, 0);
+ const bytes_read = Math.min(view.byteLength, source_view.byteLength);
+ res.__source_offset += bytes_read;
+ return bytes_read;
+}
+
+export async function http_wasm_get_streamed_response_bytes(res: ResponseExtension, bufferPtr: VoidPtr, bufferLength: number): Promise<number> {
+ // the bufferPtr is pinned by the caller
+ const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte);
+ return wrap_as_cancelable_promise(async () => {
+ if (!res.__chunk && res.body) {
+ res.__reader = res.body.getReader();
+ res.__chunk = await res.__reader.read();
+ res.__source_offset = 0;
+ }
+
+ let target_offset = 0;
+ let bytes_read = 0;
+ // loop until end of browser stream or end of C# buffer
+ while (res.__reader && res.__chunk && !res.__chunk.done) {
+ const remaining_source = res.__chunk.value.byteLength - res.__source_offset;
+ if (remaining_source === 0) {
+ res.__chunk = await res.__reader.read();
+ res.__source_offset = 0;
+ continue;// are we done yet
+ }
+
+ const remaining_target = view.byteLength - target_offset;
+ const bytes_copied = Math.min(remaining_source, remaining_target);
+ const source_view = res.__chunk.value.subarray(res.__source_offset, res.__source_offset + bytes_copied);
+
+ // copy available bytes
+ view.set(source_view, target_offset);
+ target_offset += bytes_copied;
+ bytes_read += bytes_copied;
+ res.__source_offset += bytes_copied;
+
+ if (target_offset == view.byteLength) {
+ return bytes_read;
+ }
+ }
+ return bytes_read;
+ });
+}
+
+interface ResponseExtension extends Response {
+ __buffer?: ArrayBuffer
+ __reader?: ReadableStreamDefaultReader<Uint8Array>
+ __chunk?: ReadableStreamDefaultReadResult<Uint8Array>
+ __source_offset: number
+ __abort_controller: AbortController
+ __headerNames: string[];
+ __headerValues: string[];
+} \ No newline at end of file
diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts
new file mode 100644
index 00000000000..0201be0d2d9
--- /dev/null
+++ b/src/mono/wasm/runtime/invoke-cs.ts
@@ -0,0 +1,206 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { MonoObject, MonoString } from "./export-types";
+import { EXPORTS, Module, runtimeHelpers } from "./imports";
+import { generate_arg_marshal_to_cs } from "./marshal-to-cs";
+import { marshal_exception_to_js, generate_arg_marshal_to_js } from "./marshal-to-js";
+import {
+ JSMarshalerArguments, JavaScriptMarshalerArgSize, JSFunctionSignature,
+ JSMarshalerTypeSize, JSMarshalerSignatureHeaderSize,
+ get_arg, get_sig, set_arg_type,
+ get_signature_argument_count, is_args_exception, bound_cs_function_symbol, get_signature_version, MarshalerType,
+} from "./marshal";
+import { parseFQN, wrap_error_root } from "./method-calls";
+import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots";
+import { conv_string, conv_string_root } from "./strings";
+import { mono_assert, MonoObjectRef, MonoStringRef } from "./types";
+import { Int32Ptr } from "./types/emscripten";
+import cwraps, { wrap_c_function } from "./cwraps";
+import { find_method } from "./method-binding";
+import { assembly_load } from "./class-loader";
+
+const exportedMethods = new Map<string, Function>();
+
+export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
+ const fqn_root = mono_wasm_new_external_root<MonoString>(fully_qualified_name), resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
+ const anyModule = Module as any;
+ try {
+ const version = get_signature_version(signature);
+ mono_assert(version === 1, () => `Signature version ${version} mismatch.`);
+
+ const args_count = get_signature_argument_count(signature);
+ const js_fqn = conv_string_root(fqn_root)!;
+ mono_assert(js_fqn, "fully_qualified_name must be string");
+
+ if (runtimeHelpers.config.diagnostic_tracing) {
+ console.trace(`MONO_WASM: 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 = find_method(klass, wrapper_name, -1);
+ if (!method)
+ throw new Error(`Could not find method: ${wrapper_name} in ${klass} [${assembly}]`);
+
+ const closure: any = {
+ method, get_arg, signature,
+ stackSave: anyModule.stackSave, stackAlloc: anyModule.stackAlloc, stackRestore: anyModule.stackRestore,
+ conv_string,
+ mono_wasm_new_root, init_void, init_result, /*init_argument,*/ marshal_exception_to_js, is_args_exception,
+ mono_wasm_invoke_method_bound: wrap_c_function("mono_wasm_invoke_method_bound"),
+ };
+ const bound_js_function_name = "_bound_cs_" + `${namespace}_${classname}_${methodname}`.replace(/\./g, "_");
+ let body = `//# sourceURL=https://mono-wasm.invalid/${bound_js_function_name} \n`;
+ let bodyToCs = "";
+ let converter_names = "";
+
+ for (let index = 0; index < args_count; index++) {
+ const arg_offset = (index + 2) * JavaScriptMarshalerArgSize;
+ const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize;
+ const sig = get_sig(signature, index + 2);
+ const { converters, call_body } = generate_arg_marshal_to_cs(sig, index + 2, arg_offset, sig_offset, `arguments[${index}]`, closure);
+ converter_names += converters;
+ bodyToCs += call_body;
+ }
+ const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_js(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure);
+ converter_names += res_converters;
+
+ body += `const { method, get_arg, signature, stackSave, stackAlloc, stackRestore, mono_wasm_new_root, conv_string, init_void, init_result, init_argument, marshal_exception_to_js, is_args_exception, mono_wasm_invoke_method_bound ${converter_names} } = closure;\n`;
+ // TODO named arguments instead of arguments keyword
+ body += `return function ${bound_js_function_name} () {\n`;
+ if (res_marshaler_type === MarshalerType.String) {
+ body += "let root = null;\n";
+ }
+ body += "const sp = stackSave();\n";
+ body += "try {\n";
+ body += ` const args = stackAlloc(${(args_count + 2) * JavaScriptMarshalerArgSize});\n`;
+ if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) {
+ if (res_marshaler_type === MarshalerType.String) {
+ body += " root = mono_wasm_new_root(0);\n";
+ body += " init_result(args);\n";
+ }
+ else {
+ body += " init_result(args);\n";
+ }
+ } else {
+ body += " init_void(args);\n";
+ }
+
+ body += bodyToCs;
+
+ body += " const fail = mono_wasm_invoke_method_bound(method, args);\n";
+ body += " if (fail) throw new Error(\"ERR22: Unexpected error: \" + conv_string(fail));\n";
+ body += " if (is_args_exception(args)) throw marshal_exception_to_js(get_arg(args, 0));\n";
+ if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) {
+ body += res_call_body;
+ }
+
+ if (res_marshaler_type !== MarshalerType.Void && res_marshaler_type !== MarshalerType.Discard) {
+ body += "return js_result;\n";
+ }
+
+ body += "} finally {\n";
+ body += " stackRestore(sp);\n";
+ if (res_marshaler_type === MarshalerType.String) {
+ body += " if(root) root.release()\n";
+ }
+ body += "}}";
+ const factory = new Function("closure", body);
+ const bound_fn = factory(closure);
+ bound_fn[bound_cs_function_symbol] = true;
+
+ exportedMethods.set(js_fqn, bound_fn);
+ _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn);
+ }
+ catch (ex) {
+ wrap_error_root(is_exception, ex, resultRoot);
+ } finally {
+ resultRoot.release();
+ fqn_root.release();
+ }
+}
+
+function init_void(args: JSMarshalerArguments) {
+ mono_assert(args && (<any>args) % 8 == 0, "Arg alignment");
+ const exc = get_arg(args, 0);
+ set_arg_type(exc, MarshalerType.None);
+
+ const res = get_arg(args, 1);
+ set_arg_type(res, MarshalerType.None);
+}
+
+function init_result(args: JSMarshalerArguments) {
+ mono_assert(args && (<any>args) % 8 == 0, "Arg alignment");
+ const exc = get_arg(args, 0);
+ set_arg_type(exc, MarshalerType.None);
+
+ const res = get_arg(args, 1);
+ set_arg_type(res, MarshalerType.None);
+}
+
+export const exportsByAssembly: Map<string, any> = new Map();
+
+function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void {
+ let scope: any = EXPORTS;
+ let parts = `${namespace}.${classname}`.split(".");
+
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+ let newscope = scope[part];
+ if (!newscope) {
+ newscope = {};
+ scope[part] = newscope;
+ }
+ mono_assert(newscope, () => `${part} not found while looking up ${classname}`);
+ scope = newscope;
+ }
+
+ if (!scope[methodname]) {
+ scope[methodname] = fn;
+ }
+ scope[`${methodname}.${signature_hash}`] = fn;
+
+ // do it again for per assemly scope
+ let assemblyScope = exportsByAssembly.get(assembly);
+ if (!assemblyScope) {
+ assemblyScope = {};
+ exportsByAssembly.set(assembly, assemblyScope);
+ exportsByAssembly.set(assembly + ".dll", assemblyScope);
+ }
+ scope = assemblyScope;
+ parts = `${namespace}.${classname}`.split(".");
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+ let newscope = scope[part];
+ if (!newscope) {
+ newscope = {};
+ scope[part] = newscope;
+ }
+ mono_assert(newscope, () => `${part} not found while looking up ${classname}`);
+ scope = newscope;
+ }
+
+ if (!scope[methodname]) {
+ scope[methodname] = fn;
+ }
+ scope[`${methodname}.${signature_hash}`] = fn;
+}
+
+export async function mono_wasm_get_assembly_exports(assembly: string): Promise<any> {
+ const asm = assembly_load(assembly);
+ if (!asm)
+ throw new Error("Could not find assembly: " + assembly);
+ cwraps.mono_wasm_runtime_run_module_cctor(asm);
+
+ return exportsByAssembly.get(assembly) || {};
+} \ No newline at end of file
diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts
new file mode 100644
index 00000000000..0f6b64010b3
--- /dev/null
+++ b/src/mono/wasm/runtime/invoke-js.ts
@@ -0,0 +1,189 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./gc-handles";
+import { marshal_exception_to_cs, generate_arg_marshal_to_cs } from "./marshal-to-cs";
+import { get_signature_argument_count, JSMarshalerArguments as JSMarshalerArguments, JavaScriptMarshalerArgSize, JSFunctionSignature as JSFunctionSignature, bound_js_function_symbol, JSMarshalerTypeSize, get_sig, JSMarshalerSignatureHeaderSize, get_signature_version, MarshalerType, get_signature_type } from "./marshal";
+import { setI32 } from "./memory";
+import { wrap_error_root } from "./method-calls";
+import { conv_string_root } from "./strings";
+import { mono_assert, JSHandle, MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "./types";
+import { Int32Ptr } from "./types/emscripten";
+import { IMPORTS, INTERNAL, runtimeHelpers } from "./imports";
+import { generate_arg_marshal_to_js } from "./marshal-to-js";
+import { mono_wasm_new_external_root } from "./roots";
+
+export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_name: MonoStringRef, signature: JSFunctionSignature, function_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
+ const function_name_root = mono_wasm_new_external_root<MonoString>(function_name),
+ module_name_root = mono_wasm_new_external_root<MonoString>(module_name),
+ resultRoot = mono_wasm_new_external_root<MonoObject>(result_address);
+ try {
+ const version = get_signature_version(signature);
+ mono_assert(version === 1, () => `Signature version ${version} mismatch.`);
+
+ const js_function_name = conv_string_root(function_name_root)!;
+ const js_module_name = conv_string_root(module_name_root)!;
+ if (runtimeHelpers.config.diagnostic_tracing) {
+ console.trace(`MONO_WASM: Binding [JSImport] ${js_function_name} from ${js_module_name}`);
+ }
+ const fn = mono_wasm_lookup_function(js_function_name, js_module_name);
+ const args_count = get_signature_argument_count(signature);
+
+ const closure: any = { fn, marshal_exception_to_cs, signature };
+ const bound_js_function_name = "_bound_js_" + js_function_name.replace(/\./g, "_");
+ let body = `//# sourceURL=https://mono-wasm.invalid/${bound_js_function_name} \n`;
+ let converter_names = "";
+
+
+ let bodyToJs = "";
+ let pass_args = "";
+ for (let index = 0; index < args_count; index++) {
+ const arg_offset = (index + 2) * JavaScriptMarshalerArgSize;
+ const sig_offset = (index + 2) * JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize;
+ const arg_name = `arg${index}`;
+ const sig = get_sig(signature, index + 2);
+ const { converters, call_body } = generate_arg_marshal_to_js(sig, index + 2, arg_offset, sig_offset, arg_name, closure);
+ converter_names += converters;
+ bodyToJs += call_body;
+ if (pass_args === "") {
+ pass_args += arg_name;
+ } else {
+ pass_args += `, ${arg_name}`;
+ }
+ }
+ const { converters: res_converters, call_body: res_call_body, marshaler_type: res_marshaler_type } = generate_arg_marshal_to_cs(get_sig(signature, 1), 1, JavaScriptMarshalerArgSize, JSMarshalerTypeSize + JSMarshalerSignatureHeaderSize, "js_result", closure);
+ converter_names += res_converters;
+
+ body += `const { signature, fn, marshal_exception_to_cs ${converter_names} } = closure;\n`;
+ body += `return function ${bound_js_function_name} (args) { try {\n`;
+ // body += `console.log("${bound_js_function_name}")\n`;
+ body += bodyToJs;
+
+
+ if (res_marshaler_type === MarshalerType.Void) {
+ body += ` const js_result = fn(${pass_args});\n`;
+ body += ` if (js_result !== undefined) throw new Error('Function ${js_function_name} returned unexpected value, C# signature is void');\n`;
+ }
+ else if (res_marshaler_type === MarshalerType.Discard) {
+ body += ` fn(${pass_args});\n`;
+ }
+ else {
+ body += ` const js_result = fn(${pass_args});\n`;
+ body += res_call_body;
+ }
+
+ for (let index = 0; index < args_count; index++) {
+ const sig = get_sig(signature, index + 2);
+ const marshaler_type = get_signature_type(sig);
+ if (marshaler_type == MarshalerType.Span) {
+ const arg_name = `arg${index}`;
+ body += ` ${arg_name}.dispose();\n`;
+ }
+ }
+
+ body += "} catch (ex) {\n";
+ body += " marshal_exception_to_cs(args, ex);\n";
+ body += "}}";
+ const factory = new Function("closure", body);
+ const bound_fn = factory(closure);
+ bound_fn[bound_js_function_symbol] = true;
+ const bound_function_handle = mono_wasm_get_js_handle(bound_fn)!;
+ setI32(function_js_handle, <any>bound_function_handle);
+ } catch (ex) {
+ wrap_error_root(is_exception, ex, resultRoot);
+ } finally {
+ resultRoot.release();
+ function_name_root.release();
+ }
+}
+
+export function mono_wasm_invoke_bound_function(bound_function_js_handle: JSHandle, args: JSMarshalerArguments): void {
+ const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle);
+ mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`);
+ bound_fn(args);
+}
+
+function mono_wasm_lookup_function(function_name: string, js_module_name: string): Function {
+ mono_assert(function_name && typeof function_name === "string", "function_name must be string");
+
+ let scope: any = IMPORTS;
+ const parts = function_name.split(".");
+ if (js_module_name) {
+ scope = importedModules.get(js_module_name);
+ mono_assert(scope, () => `ES6 module ${js_module_name} was not imported yet, please call JSHost.Import() first.`);
+ }
+ else if (parts[0] === "INTERNAL") {
+ scope = INTERNAL;
+ parts.shift();
+ }
+ else if (parts[0] === "globalThis") {
+ scope = globalThis;
+ parts.shift();
+ }
+
+ for (let i = 0; i < parts.length - 1; i++) {
+ const part = parts[i];
+ const newscope = scope[part];
+ mono_assert(newscope, () => `${part} not found while looking up ${function_name}`);
+ scope = newscope;
+ }
+
+ const fname = parts[parts.length - 1];
+ const fn = scope[fname];
+
+ mono_assert(typeof (fn) === "function", () => `${function_name} must be a Function but was ${typeof fn}`);
+ return fn;
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function set_property(self: any, name: string, value: any): void {
+ mono_assert(self, "Null reference");
+ self[name] = value;
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function get_property(self: any, name: string): any {
+ mono_assert(self, "Null reference");
+ return self[name];
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function has_property(self: any, name: string): boolean {
+ mono_assert(self, "Null reference");
+ return name in self;
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function get_typeof_property(self: any, name: string): string {
+ mono_assert(self, "Null reference");
+ return typeof self[name];
+}
+
+export function get_global_this(): any {
+ return globalThis;
+}
+
+export const importedModulesPromises: Map<string, Promise<any>> = new Map();
+export const importedModules: Map<string, Promise<any>> = new Map();
+
+export async function dynamic_import(module_name: string, module_url: string): Promise<any> {
+ mono_assert(module_name, "Invalid module_name");
+ mono_assert(module_url, "Invalid module_name");
+ let promise = importedModulesPromises.get(module_name);
+ const newPromise = !promise;
+ if (newPromise) {
+ if (runtimeHelpers.config.diagnostic_tracing)
+ console.trace(`MONO_WASM: importing ES6 module '${module_name}' from '${module_url}'`);
+ promise = import(module_url);
+ importedModulesPromises.set(module_name, promise);
+ }
+ const module = await promise;
+ if (newPromise) {
+ importedModules.set(module_name, module);
+ if (runtimeHelpers.config.diagnostic_tracing)
+ console.trace(`MONO_WASM: imported ES6 module '${module_name}' from '${module_url}'`);
+ }
+ return module;
+}
+
+
diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts
new file mode 100644
index 00000000000..fff46ba6043
--- /dev/null
+++ b/src/mono/wasm/runtime/marshal-to-cs.ts
@@ -0,0 +1,651 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { isThenable } from "./cancelable-promise";
+import wrapped_cs_functions from "./corebindings";
+import cwraps from "./cwraps";
+import { assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles";
+import { Module, runtimeHelpers } from "./imports";
+import {
+ JSMarshalerArgument, ManagedError,
+ set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_b8, set_arg_date,
+ set_arg_length, get_arg, is_args_exception, JavaScriptMarshalerArgSize, get_signature_type, get_signature_arg1_type, get_signature_arg2_type, cs_to_js_marshalers, js_to_cs_marshalers,
+ MarshalerToCs, MarshalerToJs, get_signature_res_type, JSMarshalerArguments, bound_js_function_symbol, set_arg_u16, JSMarshalerType, array_element_size, get_string_root, Span, ArraySegment, MemoryViewType, get_signature_arg3_type, MarshalerType, set_arg_i64_big, set_arg_intptr, IDisposable, set_arg_element_type, ManagedObject
+} from "./marshal";
+import { marshal_exception_to_js } from "./marshal-to-js";
+import { _zero_region } from "./memory";
+import { conv_string, js_string_to_mono_string_root } from "./strings";
+import { mono_assert, GCHandle, GCHandleNull } from "./types";
+import { TypedArray } from "./types/emscripten";
+
+export function initialize_marshalers_to_cs(): void {
+ if (js_to_cs_marshalers.size == 0) {
+ js_to_cs_marshalers.set(MarshalerType.Array, _marshal_array_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Span, _marshal_span_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Byte, _marshal_byte_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Char, _marshal_char_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Int16, _marshal_int16_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Int32, _marshal_int32_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Int52, _marshal_int52_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.BigInt64, _marshal_bigint64_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Double, _marshal_double_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Single, _marshal_float_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.DateTime, _marshal_date_time_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.DateTimeOffset, _marshal_date_time_offset_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.String, _marshal_string_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Exception, marshal_exception_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.JSException, marshal_exception_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Task, _marshal_task_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Action, _marshal_function_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.Function, _marshal_function_to_cs);
+ js_to_cs_marshalers.set(MarshalerType.None, _marshal_null_to_cs);// also void
+ js_to_cs_marshalers.set(MarshalerType.Discard, _marshal_null_to_cs);// also void
+ js_to_cs_marshalers.set(MarshalerType.Void, _marshal_null_to_cs);// also void
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function generate_arg_marshal_to_cs(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): {
+ converters: string,
+ call_body: string,
+ marshaler_type: MarshalerType
+} {
+ let converters = "";
+ let converter_types = "";
+ let call_body = "";
+ const converter_name = "converter" + index;
+ let converter_name_arg1 = "null";
+ let converter_name_arg2 = "null";
+ let converter_name_arg3 = "null";
+ let converter_name_res = "null";
+
+ let marshaler_type = get_signature_type(sig);
+ if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) {
+ return {
+ converters,
+ call_body,
+ marshaler_type
+ };
+ }
+
+ const marshaler_type_res = get_signature_res_type(sig);
+ if (marshaler_type_res !== MarshalerType.None) {
+ const converter = js_to_cs_marshalers.get(marshaler_type_res);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`);
+
+
+ if (marshaler_type != MarshalerType.Nullable) {
+ converter_name_res = "converter" + index + "_res";
+ converters += ", " + converter_name_res;
+ converter_types += " " + MarshalerType[marshaler_type_res];
+ closure[converter_name_res] = converter;
+ }
+ else {
+ marshaler_type = marshaler_type_res;
+ }
+ }
+
+ const marshaler_type_arg1 = get_signature_arg1_type(sig);
+ if (marshaler_type_arg1 !== MarshalerType.None) {
+ const converter = cs_to_js_marshalers.get(marshaler_type_arg1);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`);
+
+ converter_name_arg1 = "converter" + index + "_arg1";
+ converters += ", " + converter_name_arg1;
+ converter_types += " " + MarshalerType[marshaler_type_arg1];
+ closure[converter_name_arg1] = converter;
+ }
+
+ const marshaler_type_arg2 = get_signature_arg2_type(sig);
+ if (marshaler_type_arg2 !== MarshalerType.None) {
+ const converter = cs_to_js_marshalers.get(marshaler_type_arg2);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`);
+
+ converter_name_arg2 = "converter" + index + "_arg2";
+ converters += ", " + converter_name_arg2;
+ converter_types += " " + MarshalerType[marshaler_type_arg2];
+ closure[converter_name_arg2] = converter;
+ }
+
+ const marshaler_type_arg3 = get_signature_arg3_type(sig);
+ if (marshaler_type_arg3 !== MarshalerType.None) {
+ const converter = cs_to_js_marshalers.get(marshaler_type_arg3);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`);
+
+ converter_name_arg3 = "converter" + index + "_arg3";
+ converters += ", " + converter_name_arg3;
+ converter_types += " " + MarshalerType[marshaler_type_arg3];
+ closure[converter_name_arg3] = converter;
+ }
+
+ const converter = js_to_cs_marshalers.get(marshaler_type);
+
+ const arg_type_name = MarshalerType[marshaler_type];
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${arg_type_name} (${marshaler_type}) at ${index} `);
+
+ converters += ", " + converter_name;
+ converter_types += " " + arg_type_name;
+ closure[converter_name] = converter;
+
+
+ if (marshaler_type == MarshalerType.Task) {
+ call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`;
+ } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) {
+ call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg2}); // ${converter_types} \n`;
+ } else {
+ call_body = ` ${converter_name}(args + ${arg_offset}, ${jsname}, signature + ${sig_offset}); // ${converter_types} \n`;
+ }
+
+ return {
+ converters,
+ call_body,
+ marshaler_type
+ };
+}
+
+function _marshal_bool_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Boolean);
+ set_arg_b8(arg, value);
+ }
+}
+
+function _marshal_byte_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Byte);
+ set_arg_u8(arg, value);
+ }
+}
+
+function _marshal_char_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Char);
+ set_arg_u16(arg, value);
+ }
+}
+
+function _marshal_int16_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Int16);
+ set_arg_i16(arg, value);
+ }
+}
+
+function _marshal_int32_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Int32);
+ set_arg_i32(arg, value);
+ }
+}
+
+function _marshal_int52_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Int52);
+ set_arg_i52(arg, value);
+ }
+}
+
+function _marshal_bigint64_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.BigInt64);
+ set_arg_i64_big(arg, value);
+ }
+}
+
+function _marshal_double_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Double);
+ set_arg_f64(arg, value);
+ }
+}
+
+function _marshal_float_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.Single);
+ set_arg_f32(arg, value);
+ }
+}
+
+function _marshal_intptr_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.IntPtr);
+ set_arg_intptr(arg, value);
+ }
+}
+
+function _marshal_date_time_to_cs(arg: JSMarshalerArgument, value: Date): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ mono_assert(value instanceof Date, "Value is not a Date");
+ set_arg_type(arg, MarshalerType.DateTime);
+ set_arg_date(arg, value);
+ }
+}
+
+function _marshal_date_time_offset_to_cs(arg: JSMarshalerArgument, value: Date): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ mono_assert(value instanceof Date, "Value is not a Date");
+ set_arg_type(arg, MarshalerType.DateTimeOffset);
+ set_arg_date(arg, value);
+ }
+}
+
+function _marshal_string_to_cs(arg: JSMarshalerArgument, value: string) {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ set_arg_type(arg, MarshalerType.String);
+ mono_assert(typeof value === "string", "Value is not a String");
+ _marshal_string_to_cs_impl(arg, value);
+ }
+}
+
+function _marshal_string_to_cs_impl(arg: JSMarshalerArgument, value: string) {
+ const root = get_string_root(arg);
+ try {
+ js_string_to_mono_string_root(value, root);
+ }
+ finally {
+ root.release();
+ }
+}
+
+function _marshal_null_to_cs(arg: JSMarshalerArgument) {
+ set_arg_type(arg, MarshalerType.None);
+}
+
+function _marshal_function_to_cs(arg: JSMarshalerArgument, value: Function, _?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs, arg3_converter?: MarshalerToJs): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ return;
+ }
+ mono_assert(value && value instanceof Function, "Value is not a Function");
+
+ // TODO: we could try to cache value -> exising JSHandle
+ const marshal_function_to_cs_wrapper: any = (args: JSMarshalerArguments) => {
+ const exc = get_arg(args, 0);
+ const res = get_arg(args, 1);
+ const arg1 = get_arg(args, 2);
+ const arg2 = get_arg(args, 3);
+ const arg3 = get_arg(args, 4);
+
+ try {
+ let arg1_js: any = undefined;
+ let arg2_js: any = undefined;
+ let arg3_js: any = undefined;
+ if (arg1_converter) {
+ arg1_js = arg1_converter(arg1);
+ }
+ if (arg2_converter) {
+ arg2_js = arg2_converter(arg2);
+ }
+ if (arg3_converter) {
+ arg3_js = arg3_converter(arg3);
+ }
+ const res_js = value(arg1_js, arg2_js, arg3_js);
+ if (res_converter) {
+ res_converter(res, res_js);
+ }
+
+ } catch (ex) {
+ marshal_exception_to_cs(exc, ex);
+ }
+ };
+
+ marshal_function_to_cs_wrapper[bound_js_function_symbol] = true;
+ const bound_function_handle = mono_wasm_get_js_handle(marshal_function_to_cs_wrapper)!;
+ set_js_handle(arg, bound_function_handle);
+ set_arg_type(arg, MarshalerType.Function);//TODO or action ?
+}
+
+export class TaskCallbackHolder implements IDisposable {
+ public promise: Promise<any>
+
+ public constructor(promise: Promise<any>) {
+ this.promise = promise;
+ }
+
+ dispose(): void {
+ teardown_managed_proxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull;
+ }
+}
+
+function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?: JSMarshalerType, res_converter?: MarshalerToCs) {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ return;
+ }
+ mono_assert(isThenable(value), "Value is not a Promise");
+
+ const anyModule = Module as any;
+ const gc_handle: GCHandle = wrapped_cs_functions._create_task_callback();
+ set_gc_handle(arg, gc_handle);
+ set_arg_type(arg, MarshalerType.Task);
+ const holder = new TaskCallbackHolder(value);
+ setup_managed_proxy(holder, gc_handle);
+
+ value.then(data => {
+ const sp = anyModule.stackSave();
+ try {
+ const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 3);
+ const exc = get_arg(args, 0);
+ set_arg_type(exc, MarshalerType.None);
+ const res = get_arg(args, 1);
+ set_arg_type(res, MarshalerType.None);
+ set_gc_handle(res, <any>gc_handle);
+ const arg1 = get_arg(args, 2);
+ if (!res_converter) {
+ _marshal_cs_object_to_cs(arg1, data);
+ } else {
+ res_converter(arg1, data);
+ }
+ const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.complete_task_method, args);
+ if (fail) throw new Error("ERR22: Unexpected error: " + conv_string(fail));
+ if (is_args_exception(args)) throw marshal_exception_to_js(exc);
+ } finally {
+ anyModule.stackRestore(sp);
+ }
+ teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work)
+ }).catch(reason => {
+ const sp = anyModule.stackSave();
+ try {
+ const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 3);
+ const res = get_arg(args, 1);
+ set_arg_type(res, MarshalerType.None);
+ set_gc_handle(res, gc_handle);
+ const exc = get_arg(args, 0);
+ if (typeof reason === "string" || reason === null || reason === undefined) {
+ reason = new Error(reason || "");
+ }
+ marshal_exception_to_cs(exc, reason);
+ const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.complete_task_method, args);
+ if (fail) throw new Error("ERR24: Unexpected error: " + conv_string(fail));
+ if (is_args_exception(args)) throw marshal_exception_to_js(exc);
+ } finally {
+ anyModule.stackRestore(sp);
+ }
+ teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed
+ });
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function marshal_exception_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else if (value instanceof ManagedError) {
+ set_arg_type(arg, MarshalerType.Exception);
+ // this is managed exception round-trip
+ const gc_handle = assert_not_disposed(value);
+ set_gc_handle(arg, gc_handle);
+ }
+ else {
+ mono_assert(typeof value === "object" || typeof value === "string", () => `Value is not an Error ${typeof value}`);
+ set_arg_type(arg, MarshalerType.JSException);
+ const message = value.toString();
+ _marshal_string_to_cs_impl(arg, message);
+
+ const known_js_handle = value[cs_owned_js_handle_symbol];
+ if (known_js_handle) {
+ set_js_handle(arg, known_js_handle);
+ }
+ else {
+ const js_handle = mono_wasm_get_js_handle(value)!;
+ set_js_handle(arg, js_handle);
+ }
+ }
+}
+
+function _marshal_js_object_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === undefined || value === null) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ // if value was ManagedObject, it would be double proxied, but the C# signature requires that
+ mono_assert(value[js_owned_gc_handle_symbol] === undefined, "JSObject proxy of ManagedObject proxy is not supported");
+ mono_assert(typeof value === "function" || typeof value === "object", () => `JSObject proxy of ${typeof value} is not supported`);
+
+ set_arg_type(arg, MarshalerType.JSObject);
+ const js_handle = mono_wasm_get_js_handle(value)!;
+ set_js_handle(arg, js_handle);
+ }
+}
+
+function _marshal_cs_object_to_cs(arg: JSMarshalerArgument, value: any): void {
+ if (value === undefined || value === null) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ const gc_handle = value[js_owned_gc_handle_symbol];
+ const js_type = typeof (value);
+ if (gc_handle === undefined) {
+ if (js_type === "string" || js_type === "symbol") {
+ set_arg_type(arg, MarshalerType.String);
+ _marshal_string_to_cs_impl(arg, value);
+ }
+ else if (js_type === "number") {
+ set_arg_type(arg, MarshalerType.Double);
+ set_arg_f64(arg, value);
+ }
+ else if (js_type === "bigint") {
+ // we do it because not all bigint values could fit into Int64
+ throw new Error("NotImplementedException: bigint");
+ }
+ else if (js_type === "boolean") {
+ set_arg_type(arg, MarshalerType.Boolean);
+ set_arg_b8(arg, value);
+ }
+ else if (value instanceof Date) {
+ set_arg_type(arg, MarshalerType.DateTime);
+ set_arg_date(arg, value);
+ }
+ else if (value instanceof Error) {
+ set_arg_type(arg, MarshalerType.JSException);
+ const js_handle = mono_wasm_get_js_handle(value);
+ set_js_handle(arg, js_handle);
+ }
+ else if (value instanceof Uint8Array) {
+ _marshal_array_to_cs_impl(arg, value, MarshalerType.Byte);
+ }
+ else if (value instanceof Float64Array) {
+ _marshal_array_to_cs_impl(arg, value, MarshalerType.Double);
+ }
+ else if (value instanceof Int32Array) {
+ _marshal_array_to_cs_impl(arg, value, MarshalerType.Int32);
+ }
+ else if (Array.isArray(value)) {
+ _marshal_array_to_cs_impl(arg, value, MarshalerType.Object);
+ }
+ else if (value instanceof Int16Array
+ || value instanceof Int8Array
+ || value instanceof Uint8ClampedArray
+ || value instanceof Uint16Array
+ || value instanceof Uint32Array
+ || value instanceof Float32Array
+ ) {
+ throw new Error("NotImplementedException: TypedArray");
+ }
+ else if (isThenable(value)) {
+ _marshal_task_to_cs(arg, value);
+ }
+ else if (value instanceof Span) {
+ throw new Error("NotImplementedException: Span");
+ }
+ else if (js_type == "object") {
+ const js_handle = mono_wasm_get_js_handle(value);
+ set_arg_type(arg, MarshalerType.JSObject);
+ set_js_handle(arg, js_handle);
+ }
+ else {
+ throw new Error(`JSObject proxy is not supported for ${js_type} ${value}`);
+ }
+ }
+ else {
+ assert_not_disposed(value);
+ if (value instanceof ArraySegment) {
+ throw new Error("NotImplementedException: ArraySegment");
+ }
+ else if (value instanceof ManagedError) {
+ set_arg_type(arg, MarshalerType.Exception);
+ set_gc_handle(arg, gc_handle);
+ }
+ else if (value instanceof ManagedObject) {
+ set_arg_type(arg, MarshalerType.Object);
+ set_gc_handle(arg, gc_handle);
+ } else {
+ throw new Error("NotImplementedException " + js_type);
+ }
+ }
+ }
+}
+
+function _marshal_array_to_cs(arg: JSMarshalerArgument, value: Array<any> | TypedArray, sig?: JSMarshalerType): void {
+ mono_assert(!!sig, "Expected valid sig paramater");
+ const element_type = get_signature_arg1_type(sig);
+ _marshal_array_to_cs_impl(arg, value, element_type);
+}
+
+function _marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array<any> | TypedArray, element_type: MarshalerType): void {
+ if (value === null || value === undefined) {
+ set_arg_type(arg, MarshalerType.None);
+ }
+ else {
+ const element_size = array_element_size(element_type);
+ mono_assert(element_size != -1, () => `Element type ${MarshalerType[element_type]} not supported`);
+ const length = value.length;
+ const buffer_length = element_size * length;
+ const buffer_ptr = <any>Module._malloc(buffer_length);
+ if (element_type == MarshalerType.String) {
+ mono_assert(Array.isArray(value), "Value is not an Array");
+ _zero_region(buffer_ptr, buffer_length);
+ cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(<any>buffer_ptr, index);
+ _marshal_string_to_cs(element_arg, value[index]);
+ }
+ }
+ else if (element_type == MarshalerType.Object) {
+ mono_assert(Array.isArray(value), "Value is not an Array");
+ _zero_region(buffer_ptr, buffer_length);
+ cwraps.mono_wasm_register_root(buffer_ptr, buffer_length, "marshal_array_to_cs");
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(<any>buffer_ptr, index);
+ _marshal_cs_object_to_cs(element_arg, value[index]);
+ }
+ }
+ else if (element_type == MarshalerType.JSObject) {
+ mono_assert(Array.isArray(value), "Value is not an Array");
+ _zero_region(buffer_ptr, buffer_length);
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(buffer_ptr, index);
+ _marshal_js_object_to_cs(element_arg, value[index]);
+ }
+ }
+ else if (element_type == MarshalerType.Byte) {
+ mono_assert(Array.isArray(value) || value instanceof Uint8Array, "Value is not an Array or Uint8Array");
+ const targetView = Module.HEAPU8.subarray(<any>buffer_ptr, buffer_ptr + length);
+ targetView.set(value);
+ }
+ else if (element_type == MarshalerType.Int32) {
+ mono_assert(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array");
+ const targetView = Module.HEAP32.subarray(<any>buffer_ptr >> 2, (buffer_ptr >> 2) + length);
+ targetView.set(value);
+ }
+ else if (element_type == MarshalerType.Double) {
+ mono_assert(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array");
+ const targetView = Module.HEAPF64.subarray(<any>buffer_ptr >> 3, (buffer_ptr >> 3) + length);
+ targetView.set(value);
+ }
+ else {
+ throw new Error("not implemented");
+ }
+ set_arg_intptr(arg, buffer_ptr);
+ set_arg_type(arg, MarshalerType.Array);
+ set_arg_element_type(arg, element_type);
+ set_arg_length(arg, value.length);
+ }
+}
+
+function _marshal_span_to_cs(arg: JSMarshalerArgument, value: Span, sig?: JSMarshalerType): void {
+ mono_assert(!!sig, "Expected valid sig paramater");
+ mono_assert(!value.isDisposed, "ObjectDisposedException");
+ checkViewType(sig, value._viewType);
+
+ set_arg_type(arg, MarshalerType.Span);
+ set_arg_intptr(arg, value._pointer);
+ set_arg_length(arg, value.length);
+}
+
+// this only supports round-trip
+function _marshal_array_segment_to_cs(arg: JSMarshalerArgument, value: ArraySegment, sig?: JSMarshalerType): void {
+ mono_assert(!!sig, "Expected valid sig paramater");
+ const gc_handle = assert_not_disposed(value);
+ mono_assert(gc_handle, "Only roundtrip of ArraySegment instance created by C#");
+ checkViewType(sig, value._viewType);
+ set_arg_type(arg, MarshalerType.ArraySegment);
+ set_arg_intptr(arg, value._pointer);
+ set_arg_length(arg, value.length);
+ set_gc_handle(arg, gc_handle);
+}
+
+function checkViewType(sig: JSMarshalerType, viewType: MemoryViewType) {
+ const element_type = get_signature_arg1_type(sig);
+ if (element_type == MarshalerType.Byte) {
+ mono_assert(MemoryViewType.Byte == viewType, "Expected MemoryViewType.Byte");
+ }
+ else if (element_type == MarshalerType.Int32) {
+ mono_assert(MemoryViewType.Int32 == viewType, "Expected MemoryViewType.Int32");
+ }
+ else if (element_type == MarshalerType.Double) {
+ mono_assert(MemoryViewType.Double == viewType, "Expected MemoryViewType.Double");
+ }
+ else {
+ throw new Error(`NotImplementedException ${MarshalerType[element_type]} `);
+ }
+}
+
diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts
new file mode 100644
index 00000000000..47d240f1f13
--- /dev/null
+++ b/src/mono/wasm/runtime/marshal-to-js.ts
@@ -0,0 +1,581 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { PromiseControl, promise_control_symbol, create_cancelable_promise } from "./cancelable-promise";
+import cwraps from "./cwraps";
+import { _lookup_js_owned_object, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, setup_managed_proxy } from "./gc-handles";
+import { Module, runtimeHelpers } from "./imports";
+import {
+ ManagedObject, JSMarshalerArgument, ManagedError, JSMarshalerArguments, MarshalerToCs, MarshalerToJs, JSMarshalerType,
+ get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32,
+ get_arg_b8, get_arg_date, get_arg_length, set_js_handle, get_arg, set_arg_type,
+ get_signature_arg2_type, get_signature_arg1_type, get_signature_type, cs_to_js_marshalers, js_to_cs_marshalers,
+ get_signature_res_type, JavaScriptMarshalerArgSize, set_gc_handle, is_args_exception, get_arg_u16, array_element_size, get_string_root, ArraySegment, Span, MemoryViewType, get_signature_arg3_type, MarshalerType, get_arg_i64_big, get_arg_intptr, get_arg_element_type
+} from "./marshal";
+import { conv_string, conv_string_root } from "./strings";
+import { mono_assert, JSHandleNull, GCHandleNull } from "./types";
+import { TypedArray } from "./types/emscripten";
+
+export function initialize_marshalers_to_js(): void {
+ if (cs_to_js_marshalers.size == 0) {
+ cs_to_js_marshalers.set(MarshalerType.Array, _marshal_array_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Span, _marshal_span_to_js);
+ cs_to_js_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Byte, _marshal_byte_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Char, _marshal_char_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Int16, _marshal_int16_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Int32, _marshal_int32_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Int52, _marshal_int52_to_js);
+ cs_to_js_marshalers.set(MarshalerType.BigInt64, _marshal_bigint64_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Single, _marshal_float_to_js);
+ cs_to_js_marshalers.set(MarshalerType.IntPtr, _marshal_intptr_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Double, _marshal_double_to_js);
+ cs_to_js_marshalers.set(MarshalerType.String, _marshal_string_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Exception, marshal_exception_to_js);
+ cs_to_js_marshalers.set(MarshalerType.JSException, marshal_exception_to_js);
+ cs_to_js_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_js);
+ cs_to_js_marshalers.set(MarshalerType.DateTime, _marshal_datetime_to_js);
+ cs_to_js_marshalers.set(MarshalerType.DateTimeOffset, _marshal_datetime_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Task, _marshal_task_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Action, _marshal_delegate_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Function, _marshal_delegate_to_js);
+ cs_to_js_marshalers.set(MarshalerType.None, _marshal_null_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Void, _marshal_null_to_js);
+ cs_to_js_marshalers.set(MarshalerType.Discard, _marshal_null_to_js);
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export function generate_arg_marshal_to_js(sig: JSMarshalerType, index: number, arg_offset: number, sig_offset: number, jsname: string, closure: any): {
+ converters: string,
+ call_body: string,
+ marshaler_type: MarshalerType
+} {
+ let converters = "";
+ let converter_types = "";
+ let call_body = "";
+ const converter_name = "converter" + index;
+ let converter_name_arg1 = "null";
+ let converter_name_arg2 = "null";
+ let converter_name_arg3 = "null";
+ let converter_name_res = "null";
+
+ let marshaler_type = get_signature_type(sig);
+ if (marshaler_type === MarshalerType.None || marshaler_type === MarshalerType.Void) {
+ return {
+ converters,
+ call_body,
+ marshaler_type
+ };
+ }
+
+ const marshaler_type_res = get_signature_res_type(sig);
+ if (marshaler_type_res !== MarshalerType.None) {
+ const converter = cs_to_js_marshalers.get(marshaler_type_res);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_res} at ${index}`);
+
+ if (marshaler_type != MarshalerType.Nullable) {
+ converter_name_res = "converter" + index + "_res";
+ converters += ", " + converter_name_res;
+ converter_types += " " + MarshalerType[marshaler_type_res];
+ closure[converter_name_res] = converter;
+ } else {
+ marshaler_type = marshaler_type_res;
+ }
+ }
+
+ const marshaler_type_arg1 = get_signature_arg1_type(sig);
+ if (marshaler_type_arg1 !== MarshalerType.None) {
+ const converter = js_to_cs_marshalers.get(marshaler_type_arg1);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg1} at ${index}`);
+
+ converter_name_arg1 = "converter" + index + "_arg1";
+ converters += ", " + converter_name_arg1;
+ converter_types += " " + MarshalerType[marshaler_type_arg1];
+ closure[converter_name_arg1] = converter;
+ }
+
+ const marshaler_type_arg2 = get_signature_arg2_type(sig);
+ if (marshaler_type_arg2 !== MarshalerType.None) {
+ const converter = js_to_cs_marshalers.get(marshaler_type_arg2);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg2} at ${index}`);
+
+ converter_name_arg2 = "converter" + index + "_arg2";
+ converters += ", " + converter_name_arg2;
+ converter_types += " " + MarshalerType[marshaler_type_arg2];
+ closure[converter_name_arg2] = converter;
+ }
+
+ const marshaler_type_arg3 = get_signature_arg3_type(sig);
+ if (marshaler_type_arg3 !== MarshalerType.None) {
+ const converter = js_to_cs_marshalers.get(marshaler_type_arg3);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type_arg3} at ${index}`);
+
+ converter_name_arg3 = "converter" + index + "_arg3";
+ converters += ", " + converter_name_arg3;
+ converter_types += " " + MarshalerType[marshaler_type_arg3];
+ closure[converter_name_arg3] = converter;
+ }
+
+ const converter = cs_to_js_marshalers.get(marshaler_type);
+ mono_assert(converter && typeof converter === "function", () => `Unknow converter for type ${marshaler_type} at ${index} `);
+
+ converters += ", " + converter_name;
+ converter_types += " " + MarshalerType[marshaler_type];
+ closure[converter_name] = converter;
+
+ if (marshaler_type == MarshalerType.Task) {
+ call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}); // ${converter_types} \n`;
+ } else if (marshaler_type == MarshalerType.Action || marshaler_type == MarshalerType.Function) {
+ call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}, ${converter_name_res}, ${converter_name_arg1}, ${converter_name_arg2}, ${converter_name_arg3}); // ${converter_types} \n`;
+ } else {
+ call_body = ` const ${jsname} = ${converter_name}(args + ${arg_offset}, signature + ${sig_offset}); // ${converter_types} \n`;
+ }
+
+ return {
+ converters,
+ call_body,
+ marshaler_type
+ };
+}
+
+function _marshal_bool_to_js(arg: JSMarshalerArgument): boolean | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_b8(arg);
+}
+
+function _marshal_byte_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_u8(arg);
+}
+
+function _marshal_char_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_u16(arg);
+}
+
+function _marshal_int16_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_i16(arg);
+}
+
+function _marshal_int32_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_i32(arg);
+}
+
+function _marshal_int52_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_i52(arg);
+}
+
+function _marshal_bigint64_to_js(arg: JSMarshalerArgument): bigint | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_i64_big(arg);
+}
+
+function _marshal_float_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_f32(arg);
+}
+
+function _marshal_double_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_f64(arg);
+}
+
+function _marshal_intptr_to_js(arg: JSMarshalerArgument): number | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return get_arg_intptr(arg);
+}
+
+function _marshal_null_to_js(): null {
+ return null;
+}
+
+function _marshal_datetime_to_js(arg: JSMarshalerArgument): Date | null {
+ const type = get_arg_type(arg);
+ if (type === MarshalerType.None) {
+ return null;
+ }
+ return get_arg_date(arg);
+}
+
+function _marshal_delegate_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): Function | null {
+ const type = get_arg_type(arg);
+ if (type === MarshalerType.None) {
+ return null;
+ }
+
+ const anyModule = Module as any;
+ const gc_handle = get_arg_gc_handle(arg);
+ let result = _lookup_js_owned_object(gc_handle);
+ if (result === null || result === undefined) {
+ // this will create new Function for the C# delegate
+ result = (arg1_js: any, arg2_js: any, arg3_js: any) => {
+
+ const sp = anyModule.stackSave();
+ try {
+ const args = anyModule.stackAlloc(JavaScriptMarshalerArgSize * 5);
+ const exc = get_arg(args, 0);
+ set_arg_type(exc, MarshalerType.None);
+ const res = get_arg(args, 1);
+ set_arg_type(res, MarshalerType.None);
+ set_gc_handle(res, <any>gc_handle);
+ const arg1 = get_arg(args, 2);
+ const arg2 = get_arg(args, 3);
+ const arg3 = get_arg(args, 4);
+
+ if (arg1_converter) {
+ arg1_converter(arg1, arg1_js);
+ }
+ if (arg2_converter) {
+ arg2_converter(arg2, arg2_js);
+ }
+ if (arg3_converter) {
+ arg3_converter(arg3, arg3_js);
+ }
+
+ const fail = cwraps.mono_wasm_invoke_method_bound(runtimeHelpers.call_delegate, args);
+ if (fail) throw new Error("ERR23: Unexpected error: " + conv_string(fail));
+ if (is_args_exception(args)) throw marshal_exception_to_js(exc);
+
+ if (res_converter) {
+ return res_converter(res);
+ }
+
+ } finally {
+ anyModule.stackRestore(sp);
+ }
+ };
+
+ setup_managed_proxy(result, gc_handle);
+ }
+
+ return result;
+}
+
+function _marshal_task_to_js(arg: JSMarshalerArgument, _?: JSMarshalerType, res_converter?: MarshalerToJs): Promise<any> | null {
+ const type = get_arg_type(arg);
+ if (type === MarshalerType.None) {
+ return null;
+ }
+
+ if (type !== MarshalerType.Task) {
+
+ if (!res_converter) {
+ // when we arrived here from _marshal_cs_object_to_js
+ res_converter = cs_to_js_marshalers.get(type);
+ }
+ mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]} `);
+
+ // this is already resolved
+ const val = res_converter(arg);
+ return new Promise((resolve) => resolve(val));
+ }
+
+ const js_handle = get_arg_js_handle(arg);
+ // console.log("_marshal_task_to_js A" + js_handle);
+ if (js_handle == JSHandleNull) {
+ // this is already resolved void
+ return new Promise((resolve) => resolve(undefined));
+ }
+ const promise = mono_wasm_get_jsobj_from_js_handle(js_handle);
+ mono_assert(!!promise, () => `ERR28: promise not found for js_handle: ${js_handle} `);
+ const promise_control = promise[promise_control_symbol] as PromiseControl;
+ mono_assert(!!promise_control, () => `ERR27: promise_control not found for js_handle: ${js_handle} `);
+
+ const orig_resolve = promise_control.resolve;
+ promise_control.resolve = (argInner: JSMarshalerArgument) => {
+ // console.log("_marshal_task_to_js R" + js_handle);
+ const type = get_arg_type(argInner);
+ if (type === MarshalerType.None) {
+ orig_resolve(null);
+ return;
+ }
+
+ if (!res_converter) {
+ // when we arrived here from _marshal_cs_object_to_js
+ res_converter = cs_to_js_marshalers.get(type);
+ }
+ mono_assert(res_converter, () => `Unknow sub_converter for type ${MarshalerType[type]}`);
+
+ const js_value = res_converter!(argInner);
+ orig_resolve(js_value);
+ };
+
+ return promise;
+}
+
+export function mono_wasm_marshal_promise(args: JSMarshalerArguments): void {
+ const exc = get_arg(args, 0);
+ const res = get_arg(args, 1);
+ const arg_handle = get_arg(args, 2);
+ const arg_value = get_arg(args, 3);
+
+ const exc_type = get_arg_type(exc);
+ const value_type = get_arg_type(arg_value);
+ const js_handle = get_arg_js_handle(arg_handle);
+
+ if (js_handle === JSHandleNull) {
+ const { promise, promise_control } = create_cancelable_promise();
+ const js_handle = mono_wasm_get_js_handle(promise)!;
+ set_js_handle(res, js_handle);
+
+ if (exc_type !== MarshalerType.None) {
+ // this is already failed task
+ const reason = marshal_exception_to_js(exc);
+ promise_control.reject(reason);
+ }
+ else if (value_type !== MarshalerType.Task) {
+ // this is already resolved task
+ const sub_converter = cs_to_js_marshalers.get(value_type);
+ mono_assert(sub_converter, () => `Unknow sub_converter for type ${MarshalerType[value_type]} `);
+ const data = sub_converter(arg_value);
+ promise_control.resolve(data);
+ }
+ } else {
+ // resolve existing promise
+ const promise = mono_wasm_get_jsobj_from_js_handle(js_handle);
+ mono_assert(!!promise, () => `ERR25: promise not found for js_handle: ${js_handle} `);
+ const promise_control = promise[promise_control_symbol] as PromiseControl;
+ mono_assert(!!promise_control, () => `ERR26: promise_control not found for js_handle: ${js_handle} `);
+
+ if (exc_type !== MarshalerType.None) {
+ const reason = marshal_exception_to_js(exc);
+ promise_control.reject(reason);
+ }
+ else if (value_type !== MarshalerType.Task) {
+ // here we assume that resolve was wrapped with sub_converter inside _marshal_task_to_js
+ promise_control.resolve(arg_value);
+ }
+ }
+ set_arg_type(res, MarshalerType.Task);
+ set_arg_type(exc, MarshalerType.None);
+}
+
+function _marshal_string_to_js(arg: JSMarshalerArgument): string | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const root = get_string_root(arg);
+ try {
+ const value = conv_string_root(root);
+ return value;
+ } finally {
+ root.release();
+ }
+}
+
+export function marshal_exception_to_js(arg: JSMarshalerArgument): Error | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ if (type == MarshalerType.JSException) {
+ // this is JSException roundtrip
+ const js_handle = get_arg_js_handle(arg);
+ const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle);
+ return js_obj;
+ }
+
+ const gc_handle = get_arg_gc_handle(arg);
+ let result = _lookup_js_owned_object(gc_handle);
+ if (result === null || result === undefined) {
+ // this will create new ManagedError
+ const message = _marshal_string_to_js(arg);
+ result = new ManagedError(message!);
+
+ setup_managed_proxy(result, gc_handle);
+ }
+
+ return result;
+}
+
+function _marshal_js_object_to_js(arg: JSMarshalerArgument): any {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const js_handle = get_arg_js_handle(arg);
+ const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle);
+ return js_obj;
+}
+
+function _marshal_cs_object_to_js(arg: JSMarshalerArgument): any {
+ const marshaler_type = get_arg_type(arg);
+ if (marshaler_type == MarshalerType.None) {
+ return null;
+ }
+ if (marshaler_type == MarshalerType.JSObject) {
+ const js_handle = get_arg_js_handle(arg);
+ const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle);
+ return js_obj;
+ }
+
+ if (marshaler_type == MarshalerType.Array) {
+ const element_type = get_arg_element_type(arg);
+ return _marshal_array_to_js_impl(arg, element_type);
+ }
+
+ if (marshaler_type == MarshalerType.Object) {
+ const gc_handle = get_arg_gc_handle(arg);
+ if (gc_handle === GCHandleNull) {
+ return null;
+ }
+
+ // see if we have js owned instance for this gc_handle already
+ let result = _lookup_js_owned_object(gc_handle);
+
+ // If the JS object for this gc_handle was already collected (or was never created)
+ if (!result) {
+ result = new ManagedObject();
+ setup_managed_proxy(result, gc_handle);
+ }
+
+ return result;
+ }
+
+ // other types
+ const converter = cs_to_js_marshalers.get(marshaler_type);
+ mono_assert(converter, () => `Unknow converter for type ${MarshalerType[marshaler_type]}`);
+ return converter(arg);
+}
+
+function _marshal_array_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): Array<any> | TypedArray | null {
+ mono_assert(!!sig, "Expected valid sig paramater");
+ const element_type = get_signature_arg1_type(sig);
+ return _marshal_array_to_js_impl(arg, element_type);
+}
+
+function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: MarshalerType): Array<any> | TypedArray | null {
+ const type = get_arg_type(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const elementSize = array_element_size(element_type);
+ mono_assert(elementSize != -1, () => `Element type ${MarshalerType[element_type]} not supported`);
+ const buffer_ptr = get_arg_intptr(arg);
+ const length = get_arg_length(arg);
+ let result: Array<any> | TypedArray | null = null;
+ if (element_type == MarshalerType.String) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(<any>buffer_ptr, index);
+ result[index] = _marshal_string_to_js(element_arg);
+ }
+ cwraps.mono_wasm_deregister_root(<any>buffer_ptr);
+ }
+ else if (element_type == MarshalerType.Object) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(<any>buffer_ptr, index);
+ result[index] = _marshal_cs_object_to_js(element_arg);
+ }
+ cwraps.mono_wasm_deregister_root(<any>buffer_ptr);
+ }
+ else if (element_type == MarshalerType.JSObject) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const element_arg = get_arg(<any>buffer_ptr, index);
+ result[index] = _marshal_js_object_to_js(element_arg);
+ }
+ }
+ else if (element_type == MarshalerType.Byte) {
+ const sourceView = Module.HEAPU8.subarray(<any>buffer_ptr, buffer_ptr + length);
+ result = sourceView.slice();//copy
+ }
+ else if (element_type == MarshalerType.Int32) {
+ const sourceView = Module.HEAP32.subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length);
+ result = sourceView.slice();//copy
+ }
+ else if (element_type == MarshalerType.Double) {
+ const sourceView = Module.HEAPF64.subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length);
+ result = sourceView.slice();//copy
+ }
+ else {
+ throw new Error(`NotImplementedException ${MarshalerType[element_type]} `);
+ }
+ Module._free(<any>buffer_ptr);
+ return result;
+}
+
+function _marshal_span_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): Span {
+ mono_assert(!!sig, "Expected valid sig paramater");
+
+ const element_type = get_signature_arg1_type(sig);
+ const buffer_ptr = get_arg_intptr(arg);
+ const length = get_arg_length(arg);
+ let result: Span | null = null;
+ if (element_type == MarshalerType.Byte) {
+ result = new Span(<any>buffer_ptr, length, MemoryViewType.Byte);
+ }
+ else if (element_type == MarshalerType.Int32) {
+ result = new Span(<any>buffer_ptr, length, MemoryViewType.Int32);
+ }
+ else if (element_type == MarshalerType.Double) {
+ result = new Span(<any>buffer_ptr, length, MemoryViewType.Double);
+ }
+ else {
+ throw new Error(`NotImplementedException ${MarshalerType[element_type]} `);
+ }
+ return result;
+}
+
+function _marshal_array_segment_to_js(arg: JSMarshalerArgument, sig?: JSMarshalerType): ArraySegment {
+ mono_assert(!!sig, "Expected valid sig paramater");
+
+ const element_type = get_signature_arg1_type(sig);
+ const buffer_ptr = get_arg_intptr(arg);
+ const length = get_arg_length(arg);
+ let result: ArraySegment | null = null;
+ if (element_type == MarshalerType.Byte) {
+ result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Byte);
+ }
+ else if (element_type == MarshalerType.Int32) {
+ result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Int32);
+ }
+ else if (element_type == MarshalerType.Double) {
+ result = new ArraySegment(<any>buffer_ptr, length, MemoryViewType.Double);
+ }
+ else {
+ throw new Error(`NotImplementedException ${MarshalerType[element_type]} `);
+ }
+ const gc_handle = get_arg_gc_handle(arg);
+ setup_managed_proxy(result, gc_handle);
+
+ return result;
+}
diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts
new file mode 100644
index 00000000000..9a5e6a6dd63
--- /dev/null
+++ b/src/mono/wasm/runtime/marshal.ts
@@ -0,0 +1,509 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles";
+import { Module } from "./imports";
+import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory";
+import { mono_wasm_new_external_root, WasmRoot } from "./roots";
+import { mono_assert, GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull } from "./types";
+import { CharPtr, NativePointer, TypedArray, VoidPtr } from "./types/emscripten";
+
+export const cs_to_js_marshalers = new Map<MarshalerType, MarshalerToJs>();
+export const js_to_cs_marshalers = new Map<MarshalerType, MarshalerToCs>();
+export const bound_cs_function_symbol = Symbol.for("wasm bound_cs_function");
+export const bound_js_function_symbol = Symbol.for("wasm bound_js_function");
+
+/**
+ * JSFunctionSignature is pointer to [
+ * Version: number,
+ * ArgumentCount: number,
+ * exc: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType}
+ * res: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType}
+ * arg1: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType}
+ * arg2: { jsType: JsTypeFlags, type:MarshalerType, restype:MarshalerType, arg1type:MarshalerType, arg2type:MarshalerType, arg3type:MarshalerType}
+ * ...
+ * ]
+ *
+ * Layout of the call stack frame buffers is array of JSMarshalerArgument
+ * JSMarshalerArguments is pointer to [
+ * exc: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* },
+ * res: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* },
+ * arg1: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* },
+ * arg2: {type:MarshalerType, handle: IntPtr, data: Int64|Ref*|Void* },
+ * ...
+ * ]
+ */
+
+
+export const JavaScriptMarshalerArgSize = 16;
+export const JSMarshalerTypeSize = 32;
+export const JSMarshalerSignatureHeaderSize = 4 + 4; // without Exception and Result
+
+export interface JSMarshalerArguments extends NativePointer {
+ __brand: "JSMarshalerArguments"
+}
+
+export interface JSFunctionSignature extends NativePointer {
+ __brand: "JSFunctionSignatures"
+}
+
+export interface JSMarshalerType extends NativePointer {
+ __brand: "JSMarshalerType"
+}
+
+export interface JSMarshalerArgument extends NativePointer {
+ __brand: "JSMarshalerArgument"
+}
+
+export function get_arg(args: JSMarshalerArguments, index: number): JSMarshalerArgument {
+ mono_assert(args, "Null args");
+ return <any>args + (index * JavaScriptMarshalerArgSize);
+}
+
+export function is_args_exception(args: JSMarshalerArguments): boolean {
+ mono_assert(args, "Null args");
+ const exceptionType = get_arg_type(<any>args);
+ return exceptionType !== MarshalerType.None;
+}
+
+export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType {
+ mono_assert(signature, "Null signatures");
+ return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize;
+}
+
+export function get_signature_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(sig);
+}
+
+export function get_signature_res_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 16);
+}
+
+export function get_signature_custom_code(sig: JSMarshalerType): CharPtr {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 8);
+}
+
+export function get_signature_custom_code_len(sig: JSMarshalerType): number {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 12);
+}
+
+export function get_signature_arg1_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 20);
+}
+
+export function get_signature_arg2_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 24);
+}
+
+export function get_signature_arg3_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null sig");
+ return <any>getU32(<any>sig + 28);
+}
+
+export function get_signature_argument_count(signature: JSFunctionSignature): number {
+ mono_assert(signature, "Null signatures");
+ return <any>getI32(<any>signature + 4);
+}
+
+export function get_signature_version(signature: JSFunctionSignature): number {
+ mono_assert(signature, "Null signatures");
+ return <any>getI32(signature);
+}
+
+export function get_sig_type(sig: JSMarshalerType): MarshalerType {
+ mono_assert(sig, "Null signatures");
+ return <any>getU32(sig);
+}
+
+export function get_arg_type(arg: JSMarshalerArgument): MarshalerType {
+ mono_assert(arg, "Null arg");
+ const type = getU32(<any>arg + 12);
+ return <any>type;
+}
+
+export function get_arg_element_type(arg: JSMarshalerArgument): MarshalerType {
+ mono_assert(arg, "Null arg");
+ const type = getU32(<any>arg + 4);
+ return <any>type;
+}
+
+export function set_arg_type(arg: JSMarshalerArgument, type: MarshalerType): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg + 12, type);
+}
+
+export function set_arg_element_type(arg: JSMarshalerArgument, type: MarshalerType): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg + 4, type);
+}
+
+export function get_arg_b8(arg: JSMarshalerArgument): boolean {
+ mono_assert(arg, "Null arg");
+ return !!getU8(<any>arg);
+}
+
+export function get_arg_u8(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getU8(<any>arg);
+}
+
+export function get_arg_u16(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getU16(<any>arg);
+}
+
+export function get_arg_i16(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getI16(<any>arg);
+}
+
+export function get_arg_i32(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getI32(<any>arg);
+}
+
+export function get_arg_intptr(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getU32(<any>arg);
+}
+
+export function get_arg_i52(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ // we know that the range check and conversion from Int64 was be done on C# side
+ return getF64(<any>arg);
+}
+
+export function get_arg_i64_big(arg: JSMarshalerArgument): bigint {
+ mono_assert(arg, "Null arg");
+ return getI64Big(<any>arg);
+}
+
+export function get_arg_date(arg: JSMarshalerArgument): Date {
+ mono_assert(arg, "Null arg");
+ const unixTime = getF64(<any>arg);
+ const date = new Date(unixTime);
+ return date;
+}
+
+export function get_arg_f32(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getF32(<any>arg);
+}
+
+export function get_arg_f64(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return getF64(<any>arg);
+}
+
+export function set_arg_b8(arg: JSMarshalerArgument, value: boolean): void {
+ mono_assert(arg, "Null arg");
+ mono_assert(typeof value === "boolean", () => `Value is not a Boolean: ${value} (${typeof (value)})`);
+ setU8(<any>arg, value ? 1 : 0);
+}
+
+export function set_arg_u8(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setU8(<any>arg, value);
+}
+
+export function set_arg_u16(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setU16(<any>arg, value);
+}
+
+export function set_arg_i16(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setI16(<any>arg, value);
+}
+
+export function set_arg_i32(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setI32(<any>arg, value);
+}
+
+export function set_arg_intptr(arg: JSMarshalerArgument, value: VoidPtr): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg, <any>value);
+}
+
+export function set_arg_i52(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ mono_assert(Number.isSafeInteger(value), () => `Value is not an integer: ${value} (${typeof (value)})`);
+ // we know that conversion to Int64 would be done on C# side
+ setF64(<any>arg, value);
+}
+
+export function set_arg_i64_big(arg: JSMarshalerArgument, value: bigint): void {
+ mono_assert(arg, "Null arg");
+ setI64Big(<any>arg, value);
+}
+
+export function set_arg_date(arg: JSMarshalerArgument, value: Date): void {
+ mono_assert(arg, "Null arg");
+ // getTime() is always UTC
+ const unixTime = value.getTime();
+ setF64(<any>arg, unixTime);
+}
+
+export function set_arg_f64(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setF64(<any>arg, value);
+}
+
+export function set_arg_f32(arg: JSMarshalerArgument, value: number): void {
+ mono_assert(arg, "Null arg");
+ setF32(<any>arg, value);
+}
+
+export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle {
+ mono_assert(arg, "Null arg");
+ return <any>getU32(<any>arg + 4);
+}
+
+export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg + 4, <any>jsHandle);
+}
+
+export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
+ mono_assert(arg, "Null arg");
+ return <any>getU32(<any>arg + 4);
+}
+
+export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg + 4, <any>gcHandle);
+}
+
+export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> {
+ mono_assert(arg, "Null arg");
+ return mono_wasm_new_external_root<MonoString>(<any>arg);
+}
+
+export function get_arg_length(arg: JSMarshalerArgument): number {
+ mono_assert(arg, "Null arg");
+ return <any>getI32(<any>arg + 8);
+}
+
+export function set_arg_length(arg: JSMarshalerArgument, size: number): void {
+ mono_assert(arg, "Null arg");
+ setI32(<any>arg + 8, size);
+}
+
+export function set_root(arg: JSMarshalerArgument, root: WasmRoot<MonoObject>): void {
+ mono_assert(arg, "Null arg");
+ setU32(<any>arg + 0, root.get_address());
+}
+
+export interface IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+}
+
+export class ManagedObject implements IDisposable {
+ dispose(): void {
+ teardown_managed_proxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull;
+ }
+
+ toString(): string {
+ return `CsObject(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`;
+ }
+}
+
+export class ManagedError extends Error implements IDisposable {
+ constructor(message: string) {
+ super(message);
+ }
+
+ get stack(): string | undefined {
+ //todo implement lazy managed stack strace from this[js_owned_gc_handle_symbol]!
+ return super.stack;
+ }
+
+ dispose(): void {
+ teardown_managed_proxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull;
+ }
+
+ toString(): string {
+ return `ManagedError(gc_handle: ${(<any>this)[js_owned_gc_handle_symbol]})`;
+ }
+}
+
+export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs) => any;
+export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, sig?: JSMarshalerType, res_converter?: MarshalerToCs, arg1_converter?: MarshalerToJs, arg2_converter?: MarshalerToJs) => void;
+
+export function get_signature_marshaler(signature: JSFunctionSignature, index: number): JSHandle {
+ mono_assert(signature, "Null signatures");
+ const sig = get_sig(signature, index);
+ return <any>getU32(<any>sig + 8);
+}
+
+
+export function array_element_size(element_type: MarshalerType): number {
+ return element_type == MarshalerType.Byte ? 1
+ : element_type == MarshalerType.Int32 ? 4
+ : element_type == MarshalerType.Int52 ? 8
+ : element_type == MarshalerType.Double ? 8
+ : element_type == MarshalerType.String ? JavaScriptMarshalerArgSize
+ : element_type == MarshalerType.Object ? JavaScriptMarshalerArgSize
+ : element_type == MarshalerType.JSObject ? JavaScriptMarshalerArgSize
+ : -1;
+}
+
+export enum MemoryViewType {
+ Byte,
+ Int32,
+ Double,
+}
+
+abstract class MemoryView implements IMemoryView, IDisposable {
+ protected constructor(public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) {
+ }
+
+ abstract dispose(): void;
+ abstract get isDisposed(): boolean;
+
+ _unsafe_create_view(): TypedArray {
+ // this view must be short lived so that it doesn't fail after wasm memory growth
+ // for that reason we also don't give the view out to end user and provide set/slice/copyTo API instead
+ const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(Module.HEAPU8.buffer, <any>this._pointer, this._length)
+ : this._viewType == MemoryViewType.Int32 ? new Int32Array(Module.HEAP32.buffer, <any>this._pointer, this._length)
+ : this._viewType == MemoryViewType.Double ? new Float64Array(Module.HEAPF64.buffer, <any>this._pointer, this._length)
+ : null;
+ if (!view) throw new Error("NotImplementedException");
+ return view;
+ }
+
+ set(source: TypedArray, targetOffset?: number): void {
+ mono_assert(!this.isDisposed, "ObjectDisposedException");
+ const targetView = this._unsafe_create_view();
+ mono_assert(source && targetView && source.constructor === targetView.constructor, () => `Expected ${targetView.constructor}`);
+ targetView.set(source, targetOffset);
+ // TODO consider memory write barrier
+ }
+
+ copyTo(target: TypedArray, sourceOffset?: number): void {
+ mono_assert(!this.isDisposed, "ObjectDisposedException");
+ const sourceView = this._unsafe_create_view();
+ mono_assert(target && sourceView && target.constructor === sourceView.constructor, () => `Expected ${sourceView.constructor}`);
+ const trimmedSource = sourceView.subarray(sourceOffset);
+ // TODO consider memory read barrier
+ target.set(trimmedSource);
+ }
+
+ slice(start?: number, end?: number): TypedArray {
+ mono_assert(!this.isDisposed, "ObjectDisposedException");
+ const sourceView = this._unsafe_create_view();
+ // TODO consider memory read barrier
+ return sourceView.slice(start, end);
+ }
+
+ get length(): number {
+ mono_assert(!this.isDisposed, "ObjectDisposedException");
+ return this._length;
+ }
+
+ get byteLength(): number {
+ mono_assert(!this.isDisposed, "ObjectDisposedException");
+ return this._viewType == MemoryViewType.Byte ? this._length
+ : this._viewType == MemoryViewType.Int32 ? this._length << 2
+ : this._viewType == MemoryViewType.Double ? this._length << 3
+ : 0;
+ }
+}
+
+export interface IMemoryView {
+ /**
+ * copies elements from provided source to the wasm memory.
+ * target has to have the elements of the same type as the underlying C# array.
+ * same as TypedArray.set()
+ */
+ set(source: TypedArray, targetOffset?: number): void;
+ /**
+ * copies elements from wasm memory to provided target.
+ * target has to have the elements of the same type as the underlying C# array.
+ */
+ copyTo(target: TypedArray, sourceOffset?: number): void;
+ /**
+ * same as TypedArray.slice()
+ */
+ slice(start?: number, end?: number): TypedArray;
+
+ get length(): number;
+ get byteLength(): number;
+}
+
+export class Span extends MemoryView implements IDisposable {
+ private is_disposed = false;
+ public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) {
+ super(pointer, length, viewType);
+ }
+ dispose(): void {
+ this.is_disposed = true;
+ }
+ get isDisposed(): boolean {
+ return this.is_disposed;
+ }
+}
+
+export class ArraySegment extends MemoryView {
+ public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) {
+ super(pointer, length, viewType);
+ }
+
+ dispose(): void {
+ teardown_managed_proxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (<any>this)[js_owned_gc_handle_symbol] === GCHandleNull;
+ }
+}
+
+// please keep in sync with src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\MarshalerType.cs
+export enum MarshalerType {
+ None = 0,
+ Void = 1,
+ Discard,
+ Boolean,
+ Byte,
+ Char,
+ Int16,
+ Int32,
+ Int52,
+ BigInt64,
+ Double,
+ Single,
+ IntPtr,
+ JSObject,
+ Object,
+ String,
+ Exception,
+ DateTime,
+ DateTimeOffset,
+
+ Nullable,
+ Task,
+ Array,
+ ArraySegment,
+ Span,
+ Action,
+ Function,
+
+ // only on runtime
+ JSException,
+} \ No newline at end of file
diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts
index 776a8eef168..3b3ec8f1486 100644
--- a/src/mono/wasm/runtime/startup.ts
+++ b/src/mono/wasm/runtime/startup.ts
@@ -16,6 +16,8 @@ import { find_corlib_class } from "./class-loader";
import { VoidPtr, CharPtr } from "./types/emscripten";
import { DotnetPublicAPI } from "./exports";
import { mono_on_abort } from "./run";
+import { initialize_marshalers_to_cs } from "./marshal-to-cs";
+import { initialize_marshalers_to_js } from "./marshal-to-js";
import { mono_wasm_new_root } from "./roots";
import { init_crypto } from "./crypto-worker";
import { init_polyfills } from "./polyfills";
@@ -452,6 +454,21 @@ export function bindings_lazy_init(): void {
if (!runtimeHelpers.get_call_sig_ref)
throw "Can't find GetCallSignatureRef method";
+ runtimeHelpers.complete_task_method = get_method("CompleteTask");
+ if (!runtimeHelpers.complete_task_method)
+ throw "Can't find CompleteTask method";
+
+ runtimeHelpers.create_task_method = get_method("CreateTaskCallback");
+ if (!runtimeHelpers.create_task_method)
+ throw "Can't find CreateTaskCallback method";
+
+ runtimeHelpers.call_delegate = get_method("CallDelegate");
+ if (!runtimeHelpers.call_delegate)
+ throw "Can't find CallDelegate method";
+
+ initialize_marshalers_to_js();
+ initialize_marshalers_to_cs();
+
_create_primitive_converters();
runtimeHelpers._box_root = mono_wasm_new_root<MonoObject>();
diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts
index 649de856d70..8e3970722aa 100644
--- a/src/mono/wasm/runtime/types.ts
+++ b/src/mono/wasm/runtime/types.ts
@@ -132,6 +132,9 @@ export const enum AssetBehaviours {
export type RuntimeHelpers = {
get_call_sig_ref: MonoMethod;
+ complete_task_method: MonoMethod;
+ create_task_method: MonoMethod;
+ call_delegate: MonoMethod;
runtime_interop_namespace: string;
runtime_interop_exports_classname: string;
runtime_interop_exports_class: MonoClass;
diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts
index caaa9bd3930..feeebf0f449 100644
--- a/src/mono/wasm/runtime/web-socket.ts
+++ b/src/mono/wasm/runtime/web-socket.ts
@@ -1,19 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import { mono_wasm_new_root, mono_wasm_new_external_root } from "./roots";
-import { setI32 } from "./memory";
import { prevent_timer_throttling } from "./scheduling";
import { Queue } from "./queue";
-import { PromiseControl, _create_cancelable_promise } from "./cancelable-promise";
-import { mono_array_root_to_js_array, _wrap_delegate_root_as_function } from "./cs-to-js";
-import { mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle } from "./gc-handles";
-import { _wrap_js_thenable_as_task_root } from "./js-to-cs";
-import { wrap_error_root } from "./method-calls";
-import { conv_string_root } from "./strings";
-import { JSHandle, MonoArray, MonoObject, MonoString, MonoObjectRef } from "./types";
-import { Module } from "./imports";
-import { Int32Ptr, VoidPtr } from "./types/emscripten";
+import { PromiseControl, create_cancelable_promise } from "./cancelable-promise";
+import { mono_assert } from "./types";
+import { ArraySegment, IDisposable } from "./marshal";
const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer");
const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset");
@@ -30,302 +22,210 @@ let _text_encoder_utf8: TextEncoder | undefined = undefined;
const ws_send_buffer_blocking_threshold = 65536;
const emptyBuffer = new Uint8Array();
-export function mono_wasm_web_socket_open_ref(uri_address: MonoObjectRef, subProtocols: MonoObjectRef, on_close: MonoObjectRef, web_socket_js_handle: Int32Ptr, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
- const uri_root = mono_wasm_new_external_root<MonoString>(uri_address);
- const sub_root = mono_wasm_new_external_root<MonoArray>(subProtocols);
- const on_close_root = mono_wasm_new_root();
- on_close_root.copy_from_address(on_close);
- try {
- const js_uri = conv_string_root(uri_root);
- if (!js_uri) {
- wrap_error_root(is_exception, "ERR12: Invalid uri '" + uri_root.value + "'", result_root);
- return;
- }
+export function ws_wasm_create(uri: string, sub_protocols: string[] | null, onClosed: (code: number, reason: string) => void): WebSocketExtension {
+ mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
+
+ const ws = new globalThis.WebSocket(uri, sub_protocols || undefined) as WebSocketExtension;
+ const { promise_control: open_promise_control } = create_cancelable_promise();
+
+ ws[wasm_ws_pending_receive_event_queue] = new Queue();
+ ws[wasm_ws_pending_receive_promise_queue] = new Queue();
+ ws[wasm_ws_pending_open_promise] = open_promise_control;
+ ws[wasm_ws_pending_send_promises] = [];
+ ws[wasm_ws_pending_close_promises] = [];
+ ws.binaryType = "arraybuffer";
+ const local_on_open = () => {
+ if (ws[wasm_ws_is_aborted]) return;
+ open_promise_control.resolve(ws);
+ prevent_timer_throttling();
+ };
+ const local_on_message = (ev: MessageEvent) => {
+ if (ws[wasm_ws_is_aborted]) return;
+ _mono_wasm_web_socket_on_message(ws, ev);
+ prevent_timer_throttling();
+ };
+ const local_on_close = (ev: CloseEvent) => {
+ ws.removeEventListener("message", local_on_message);
+ if (ws[wasm_ws_is_aborted]) return;
+ if (onClosed) onClosed(ev.code, ev.reason);
+
+ // this reject would not do anything if there was already "open" before it.
+ open_promise_control.reject(ev.reason);
- const js_subs = mono_array_root_to_js_array(sub_root);
-
- const js_on_close = _wrap_delegate_root_as_function(on_close_root)!;
-
- const ws = new globalThis.WebSocket(js_uri, <any>js_subs) as WebSocketExtension;
- const { promise, promise_control: open_promise_control } = _create_cancelable_promise();
-
- ws[wasm_ws_pending_receive_event_queue] = new Queue();
- ws[wasm_ws_pending_receive_promise_queue] = new Queue();
- ws[wasm_ws_pending_open_promise] = open_promise_control;
- ws[wasm_ws_pending_send_promises] = [];
- ws[wasm_ws_pending_close_promises] = [];
- ws.binaryType = "arraybuffer";
- const local_on_open = () => {
- if (ws[wasm_ws_is_aborted]) return;
- open_promise_control.resolve(null);
- prevent_timer_throttling();
- };
- const local_on_message = (ev: MessageEvent) => {
- if (ws[wasm_ws_is_aborted]) return;
- _mono_wasm_web_socket_on_message(ws, ev);
- prevent_timer_throttling();
- };
- const local_on_close = (ev: CloseEvent) => {
- ws.removeEventListener("message", local_on_message);
- if (ws[wasm_ws_is_aborted]) return;
- js_on_close(ev.code, ev.reason);
-
- // this reject would not do anything if there was already "open" before it.
- open_promise_control.reject(ev.reason);
-
- for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
- close_promise_control.resolve();
- }
+ for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
+ close_promise_control.resolve();
+ }
- // send close to any pending receivers, to wake them
- const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue];
- receive_promise_queue.drain((receive_promise_control) => {
- const response_ptr = receive_promise_control.response_ptr;
- setI32(<any>response_ptr + 0, 0);// count
- setI32(<any>response_ptr + 4, 2);// type:close
- setI32(<any>response_ptr + 8, 1);// end_of_message: true
- receive_promise_control.resolve(null);
- });
- };
- const local_on_error = (ev: any) => {
- open_promise_control.reject(ev.message);
- };
- ws.addEventListener("message", local_on_message);
- ws.addEventListener("open", local_on_open, { once: true });
- ws.addEventListener("close", local_on_close, { once: true });
- ws.addEventListener("error", local_on_error, { once: true });
- const ws_js_handle = mono_wasm_get_js_handle(ws);
- Module.setValue(web_socket_js_handle, <any>ws_js_handle, "i32");
-
- const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root);
- // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC
- Module.setValue(thenable_js_handle, <any>then_js_handle, "i32");
- }
- catch (ex) {
- wrap_error_root(is_exception, ex, result_root);
- }
- finally {
- result_root.release();
- uri_root.release();
- sub_root.release();
- on_close_root.release();
- }
+ // send close to any pending receivers, to wake them
+ const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue];
+ receive_promise_queue.drain((receive_promise_control) => {
+
+ const response = new Int32Array([
+ 0,// count
+ 2, // type:close
+ 1]);// end_of_message: true
+ receive_promise_control.responseView.set(response);
+ receive_promise_control.resolve(null);
+ });
+ };
+ const local_on_error = (ev: any) => {
+ open_promise_control.reject(ev.message || "WebSocket error");
+ };
+ ws.addEventListener("message", local_on_message);
+ ws.addEventListener("open", local_on_open, { once: true });
+ ws.addEventListener("close", local_on_close, { once: true });
+ ws.addEventListener("error", local_on_error, { once: true });
+
+ return ws;
}
-export function mono_wasm_web_socket_send(webSocket_js_handle: JSHandle, buffer_ptr: VoidPtr, offset: number, length: number, message_type: number, end_of_message: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
- try {
- const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle);
- if (!ws)
- throw new Error("ERR17: Invalid JS object handle " + webSocket_js_handle);
+export function ws_wasm_open(ws: WebSocketExtension): Promise<void> | null {
+ mono_assert(!!ws, "ERR17: expected ws instance");
+ const open_promise_control = ws[wasm_ws_pending_open_promise];
+ return open_promise_control.promise;
+}
- if (ws.readyState != WebSocket.OPEN) {
- throw new Error("InvalidState: The WebSocket is not connected.");
- }
+export function ws_wasm_send(ws: WebSocketExtension, bufferView: ArraySegment, message_type: number, end_of_message: boolean): Promise<void> | null {
+ mono_assert(!!ws, "ERR17: expected ws instance");
- const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, buffer_ptr, offset, length, message_type, end_of_message);
+ const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, bufferView, message_type, end_of_message);
- if (!end_of_message || !whole_buffer) {
- result_root.clear(); // we are done buffering synchronously, no promise
- return;
- }
- _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, thenable_js_handle, result_address);
- }
- catch (ex) {
- wrap_error_root(is_exception, ex, result_root);
- }
- finally {
- result_root.release();
+ if (!end_of_message || !whole_buffer) {
+ return null;
}
+
+ return _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, bufferView);
}
-export function mono_wasm_web_socket_receive(webSocket_js_handle: JSHandle, buffer_ptr: VoidPtr, offset: number, length: number, response_ptr: VoidPtr, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
+export function ws_wasm_receive(ws: WebSocketExtension, bufferView: ArraySegment, responseView: ArraySegment): Promise<void> | null {
+ mono_assert(!!ws, "ERR18: expected ws instance");
- try {
- const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle);
- if (!ws)
- throw new Error("ERR18: Invalid JS object handle " + webSocket_js_handle);
- const receive_event_queue = ws[wasm_ws_pending_receive_event_queue];
- const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue];
+ const receive_event_queue = ws[wasm_ws_pending_receive_event_queue];
+ const receive_promise_queue = ws[wasm_ws_pending_receive_promise_queue];
- const readyState = ws.readyState;
- if (readyState != WebSocket.OPEN && readyState != WebSocket.CLOSING) {
- throw new Error("InvalidState: The WebSocket is not connected.");
- }
+ const readyState = ws.readyState;
+ if (readyState != WebSocket.OPEN && readyState != WebSocket.CLOSING) {
+ throw new Error("InvalidState: The WebSocket is not connected.");
+ }
- if (receive_event_queue.getLength()) {
- if (receive_promise_queue.getLength() != 0) {
- throw new Error("ERR20: Invalid WS state");// assert
- }
- // finish synchronously
- _mono_wasm_web_socket_receive_buffering(receive_event_queue, buffer_ptr, offset, length, response_ptr);
+ if (receive_event_queue.getLength()) {
+ mono_assert(receive_promise_queue.getLength() == 0, "ERR20: Invalid WS state");
- Module.setValue(thenable_js_handle, 0, "i32");
- result_root.clear();
- return;
- }
- const { promise, promise_control } = _create_cancelable_promise(undefined, undefined);
- const receive_promise_control = promise_control as ReceivePromiseControl;
- receive_promise_control.buffer_ptr = buffer_ptr;
- receive_promise_control.buffer_offset = offset;
- receive_promise_control.buffer_length = length;
- receive_promise_control.response_ptr = response_ptr;
- receive_promise_queue.enqueue(receive_promise_control);
-
- const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root);
- // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC
- Module.setValue(thenable_js_handle, <any>then_js_handle, "i32");
- }
- catch (ex) {
- wrap_error_root(is_exception, ex, result_root);
- }
- finally {
- result_root.release();
+ // finish synchronously
+ _mono_wasm_web_socket_receive_buffering(receive_event_queue, bufferView, responseView);
+
+ return null;
}
-}
+ const { promise, promise_control } = create_cancelable_promise(undefined, undefined);
+ const receive_promise_control = promise_control as ReceivePromiseControl;
+ receive_promise_control.bufferView = bufferView;
+ receive_promise_control.responseView = responseView;
+ receive_promise_queue.enqueue(receive_promise_control);
-export function mono_wasm_web_socket_close_ref(webSocket_js_handle: JSHandle, code: number, reason: MonoObjectRef, wait_for_close_received: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
- const reason_root = mono_wasm_new_external_root<MonoString>(reason);
- try {
- const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle);
- if (!ws)
- throw new Error("ERR19: Invalid JS object handle " + webSocket_js_handle);
+ return promise;
+}
- if (ws.readyState == WebSocket.CLOSED) {
- result_root.clear();
- return;
- }
+export function ws_wasm_close(ws: WebSocketExtension, code: number, reason: string | null, wait_for_close_received: boolean): Promise<void> | null {
+ mono_assert(!!ws, "ERR19: expected ws instance");
- const js_reason = conv_string_root(reason_root);
- if (wait_for_close_received) {
- const { promise, promise_control } = _create_cancelable_promise();
- ws[wasm_ws_pending_close_promises].push(promise_control);
+ if (ws.readyState == WebSocket.CLOSED) {
+ return null;
+ }
- if (typeof (js_reason) === "string") {
- ws.close(code, js_reason);
- } else {
- ws.close(code);
- }
+ if (wait_for_close_received) {
+ const { promise, promise_control } = create_cancelable_promise();
+ ws[wasm_ws_pending_close_promises].push(promise_control);
- const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root);
- // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC
- Module.setValue(thenable_js_handle, <any>then_js_handle, "i32");
- }
- else {
- if (!mono_wasm_web_socket_close_warning) {
- mono_wasm_web_socket_close_warning = true;
- console.warn("WARNING: Web browsers do not support closing the output side of a WebSocket. CloseOutputAsync has closed the socket and discarded any incoming messages.");
- }
- if (typeof (js_reason) === "string") {
- ws.close(code, js_reason);
- } else {
- ws.close(code);
- }
- Module.setValue(thenable_js_handle, 0, "i32");
- result_root.clear();
+ if (typeof reason === "string") {
+ ws.close(code, reason);
+ } else {
+ ws.close(code);
}
+ return promise;
}
- catch (ex) {
- wrap_error_root(is_exception, ex, result_root);
- }
- finally {
- result_root.release();
- reason_root.release();
- }
-}
-
-export function mono_wasm_web_socket_abort(webSocket_js_handle: JSHandle, is_exception: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
- try {
- const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle) as WebSocketExtension;
- if (!ws)
- throw new Error("ERR18: Invalid JS object handle " + webSocket_js_handle);
-
- ws[wasm_ws_is_aborted] = true;
- const open_promise_control = ws[wasm_ws_pending_open_promise];
- if (open_promise_control) {
- open_promise_control.reject("OperationCanceledException");
- }
- for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
- close_promise_control.reject("OperationCanceledException");
+ else {
+ if (!mono_wasm_web_socket_close_warning) {
+ mono_wasm_web_socket_close_warning = true;
+ console.warn("WARNING: Web browsers do not support closing the output side of a WebSocket. CloseOutputAsync has closed the socket and discarded any incoming messages.");
}
- for (const send_promise_control of ws[wasm_ws_pending_send_promises]) {
- send_promise_control.reject("OperationCanceledException");
+ if (typeof reason === "string") {
+ ws.close(code, reason);
+ } else {
+ ws.close(code);
}
+ return null;
+ }
+}
- ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => {
- receive_promise_control.reject("OperationCanceledException");
- });
-
- // this is different from Managed implementation
- ws.close(1000, "Connection was aborted.");
+export function ws_wasm_abort(ws: WebSocketExtension): void {
+ mono_assert(!!ws, "ERR18: expected ws instance");
- result_root.clear();
+ ws[wasm_ws_is_aborted] = true;
+ const open_promise_control = ws[wasm_ws_pending_open_promise];
+ if (open_promise_control) {
+ open_promise_control.reject("OperationCanceledException");
}
- catch (ex) {
- wrap_error_root(is_exception, ex, result_root);
+ for (const close_promise_control of ws[wasm_ws_pending_close_promises]) {
+ close_promise_control.reject("OperationCanceledException");
}
- finally {
- result_root.release();
+ for (const send_promise_control of ws[wasm_ws_pending_send_promises]) {
+ send_promise_control.reject("OperationCanceledException");
}
+
+ ws[wasm_ws_pending_receive_promise_queue].drain(receive_promise_control => {
+ receive_promise_control.reject("OperationCanceledException");
+ });
+
+ // this is different from Managed implementation
+ ws.close(1000, "Connection was aborted.");
}
-function _mono_wasm_web_socket_send_and_wait(ws: WebSocketExtension, buffer: Uint8Array | string, thenable_js_handle: Int32Ptr, result_address: MonoObjectRef): void {
- const result_root = mono_wasm_new_external_root<MonoObject>(result_address);
- try {
- // send and return promise
- ws.send(buffer);
- ws[wasm_ws_pending_send_buffer] = null;
-
- // if the remaining send buffer is small, we don't block so that the throughput doesn't suffer.
- // Otherwise we block so that we apply some backpresure to the application sending large data.
- // this is different from Managed implementation
- if (ws.bufferedAmount < ws_send_buffer_blocking_threshold) {
- result_root.clear();
- return;
- }
+function _mono_wasm_web_socket_send_and_wait(ws: WebSocketExtension, buffer: Uint8Array | string, managedBuffer: IDisposable): Promise<void> | null {
+ // send and return promise
+ ws.send(buffer);
+ managedBuffer.dispose();
+ ws[wasm_ws_pending_send_buffer] = null;
+
+ // if the remaining send buffer is small, we don't block so that the throughput doesn't suffer.
+ // Otherwise we block so that we apply some backpresure to the application sending large data.
+ // this is different from Managed implementation
+ if (ws.bufferedAmount < ws_send_buffer_blocking_threshold) {
+ return null;
+ }
- // block the promise/task until the browser passed the buffer to OS
- const { promise, promise_control } = _create_cancelable_promise();
- const pending = ws[wasm_ws_pending_send_promises];
- pending.push(promise_control);
+ // block the promise/task until the browser passed the buffer to OS
+ const { promise, promise_control } = create_cancelable_promise();
+ const pending = ws[wasm_ws_pending_send_promises];
+ pending.push(promise_control);
- let nextDelay = 1;
- const polling_check = () => {
- // was it all sent yet ?
- if (ws.bufferedAmount === 0) {
- promise_control.resolve(null);
- }
- else if (ws.readyState != WebSocket.OPEN) {
- // only reject if the data were not sent
- // bufferedAmount does not reset to zero once the connection closes
- promise_control.reject("InvalidState: The WebSocket is not connected.");
- }
- else if (!promise_control.isDone) {
- globalThis.setTimeout(polling_check, nextDelay);
- // exponentially longer delays, up to 1000ms
- nextDelay = Math.min(nextDelay * 1.5, 1000);
- return;
- }
- // remove from pending
- const index = pending.indexOf(promise_control);
- if (index > -1) {
- pending.splice(index, 1);
- }
- };
+ let nextDelay = 1;
+ const polling_check = () => {
+ // was it all sent yet ?
+ if (ws.bufferedAmount === 0) {
+ promise_control.resolve(null);
+ }
+ else if (ws.readyState != WebSocket.OPEN) {
+ // only reject if the data were not sent
+ // bufferedAmount does not reset to zero once the connection closes
+ promise_control.reject("InvalidState: The WebSocket is not connected.");
+ }
+ else if (!promise_control.isDone) {
+ globalThis.setTimeout(polling_check, nextDelay);
+ // exponentially longer delays, up to 1000ms
+ nextDelay = Math.min(nextDelay * 1.5, 1000);
+ return;
+ }
+ // remove from pending
+ const index = pending.indexOf(promise_control);
+ if (index > -1) {
+ pending.splice(index, 1);
+ }
+ };
- globalThis.setTimeout(polling_check, 0);
+ globalThis.setTimeout(polling_check, 0);
- const { then_js_handle } = _wrap_js_thenable_as_task_root(promise, result_root);
- // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC
- Module.setValue(thenable_js_handle, <any>then_js_handle, "i32");
- } finally {
- result_root.release();
- }
+ return promise;
}
function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: MessageEvent) {
@@ -361,36 +261,37 @@ function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: Message
while (promise_queue.getLength() && event_queue.getLength()) {
const promise_control = promise_queue.dequeue()!;
_mono_wasm_web_socket_receive_buffering(event_queue,
- promise_control.buffer_ptr, promise_control.buffer_offset, promise_control.buffer_length,
- promise_control.response_ptr);
+ promise_control.bufferView, promise_control.responseView);
promise_control.resolve(null);
}
prevent_timer_throttling();
}
-function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, buffer_ptr: VoidPtr, buffer_offset: number, buffer_length: number, response_ptr: VoidPtr) {
+function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, bufferView: ArraySegment, responseView: ArraySegment) {
const event = event_queue.peek();
- const count = Math.min(buffer_length, event.data.length - event.offset);
+ const count = Math.min(bufferView.length, event.data.length - event.offset);
if (count > 0) {
- const targetView = Module.HEAPU8.subarray(<any>buffer_ptr + buffer_offset, <any>buffer_ptr + buffer_offset + buffer_length);
const sourceView = event.data.subarray(event.offset, event.offset + count);
- targetView.set(sourceView, 0);
+ bufferView.set(sourceView, 0);
event.offset += count;
}
const end_of_message = event.data.length === event.offset ? 1 : 0;
if (end_of_message) {
event_queue.dequeue();
}
- setI32(<any>response_ptr + 0, count);
- setI32(<any>response_ptr + 4, event.type);
- setI32(<any>response_ptr + 8, end_of_message);
+
+ const response = new Int32Array([count, event.type, end_of_message]);
+ responseView.set(response);
+
+ bufferView.dispose();
+ responseView.dispose();
}
-function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr: VoidPtr, buffer_offset: number, length: number, message_type: number, end_of_message: boolean): Uint8Array | string | null {
+function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, bufferView: ArraySegment, message_type: number, end_of_message: boolean): Uint8Array | string | null {
let buffer = ws[wasm_ws_pending_send_buffer];
let offset = 0;
- const message_ptr = <any>buffer_ptr + buffer_offset;
+ const length = bufferView.length;
if (buffer) {
offset = ws[wasm_ws_pending_send_buffer_offset];
@@ -398,15 +299,14 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr
message_type = ws[wasm_ws_pending_send_buffer_type];
// if not empty message, append to existing buffer
if (length !== 0) {
- const view = Module.HEAPU8.subarray(message_ptr, message_ptr + length);
if (offset + length > buffer.length) {
const newbuffer = new Uint8Array((offset + length + 50) * 1.5); // exponential growth
newbuffer.set(buffer, 0);// copy previous buffer
- newbuffer.set(view, offset);// append copy at the end
+ bufferView.copyTo(newbuffer.subarray(offset));// append copy at the end
ws[wasm_ws_pending_send_buffer] = buffer = newbuffer;
}
else {
- buffer.set(view, offset);// append copy at the end
+ bufferView.copyTo(buffer.subarray(offset));// append copy at the end
}
offset += length;
ws[wasm_ws_pending_send_buffer_offset] = offset;
@@ -415,8 +315,7 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr
else if (!end_of_message) {
// create new buffer
if (length !== 0) {
- const view = Module.HEAPU8.subarray(message_ptr, message_ptr + length);
- buffer = new Uint8Array(view); // copy
+ buffer = <Uint8Array>bufferView.slice(); // copy
offset = length;
ws[wasm_ws_pending_send_buffer_offset] = offset;
ws[wasm_ws_pending_send_buffer] = buffer;
@@ -424,10 +323,9 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_ptr
ws[wasm_ws_pending_send_buffer_type] = message_type;
}
else {
- // use the buffer only localy
if (length !== 0) {
- const memoryView = Module.HEAPU8.subarray(message_ptr, message_ptr + length);
- buffer = memoryView; // send will make a copy
+ // we could use the unsafe view, because it will be immediately used in ws.send()
+ buffer = <Uint8Array>bufferView._unsafe_create_view();
offset = length;
}
}
@@ -469,10 +367,8 @@ type WebSocketExtension = WebSocket & {
}
type ReceivePromiseControl = PromiseControl & {
- response_ptr: VoidPtr
- buffer_ptr: VoidPtr
- buffer_offset: number
- buffer_length: number
+ bufferView: ArraySegment,
+ responseView: ArraySegment
}
type Message = {