diff options
author | Katelyn Gadd <kg@luminance.org> | 2022-04-06 12:27:12 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-06 12:27:12 +0300 |
commit | c537cc6a45c4c75524ec98f6a8d4c9637438958e (patch) | |
tree | 3774ff98442af18230ed4d168675491f2782f6fc /src/mono/wasm/runtime | |
parent | bea863ba1f3066af624454ff97aef4d86a46d4a6 (diff) |
Introduce write barriers in wasm bindings, migrate to ref/out params, add gc safe regions (#65994)
Introduce write barriers in key parts of the wasm bindings and migrate most code to use ref/out parameters instead of passing raw managed pointers as arguments or return values.
Introduced MonoObjectRef typescript type and corresponding 'R' signature char (for 'ref/out object')
Marked various old/unsafe code as deprecated
Fixed some incorrect rooting in websocket APIs
Introduced 'volatile' attribute on various C pointers in the bindings
Added GC unsafe/safe regions in key parts of the C bindings
Expanded exported APIs
Diffstat (limited to 'src/mono/wasm/runtime')
-rw-r--r-- | src/mono/wasm/runtime/buffers.ts | 72 | ||||
-rw-r--r-- | src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js | 26 | ||||
-rw-r--r-- | src/mono/wasm/runtime/corebindings.c | 110 | ||||
-rw-r--r-- | src/mono/wasm/runtime/corebindings.ts | 63 | ||||
-rw-r--r-- | src/mono/wasm/runtime/cs-to-js.ts | 103 | ||||
-rw-r--r-- | src/mono/wasm/runtime/cwraps.ts | 87 | ||||
-rw-r--r-- | src/mono/wasm/runtime/dotnet.d.ts | 81 | ||||
-rw-r--r-- | src/mono/wasm/runtime/driver.c | 375 | ||||
-rw-r--r-- | src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 30 | ||||
-rw-r--r-- | src/mono/wasm/runtime/exports.ts | 83 | ||||
-rw-r--r-- | src/mono/wasm/runtime/gc-common.h | 49 | ||||
-rw-r--r-- | src/mono/wasm/runtime/gc-handles.ts | 22 | ||||
-rw-r--r-- | src/mono/wasm/runtime/js-to-cs.ts | 218 | ||||
-rw-r--r-- | src/mono/wasm/runtime/memory.ts | 2 | ||||
-rw-r--r-- | src/mono/wasm/runtime/method-binding.ts | 82 | ||||
-rw-r--r-- | src/mono/wasm/runtime/method-calls.ts | 219 | ||||
-rw-r--r-- | src/mono/wasm/runtime/roots.ts | 119 | ||||
-rw-r--r-- | src/mono/wasm/runtime/startup.ts | 12 | ||||
-rw-r--r-- | src/mono/wasm/runtime/strings.ts | 179 | ||||
-rw-r--r-- | src/mono/wasm/runtime/types.ts | 19 | ||||
-rw-r--r-- | src/mono/wasm/runtime/web-socket.ts | 231 |
21 files changed, 1436 insertions, 746 deletions
diff --git a/src/mono/wasm/runtime/buffers.ts b/src/mono/wasm/runtime/buffers.ts index b0437231ccb..f89b663924f 100644 --- a/src/mono/wasm/runtime/buffers.ts +++ b/src/mono/wasm/runtime/buffers.ts @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { JSHandle, MonoArray, MonoObject, MonoString } from "./types"; +import { JSHandle, MonoArray, MonoObject, MonoObjectRef } from "./types"; import { Module } from "./imports"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; -import { wrap_error } from "./method-calls"; -import { _js_to_mono_obj } from "./js-to-cs"; +import { wrap_error_root } from "./method-calls"; +import { js_to_mono_obj_root } from "./js-to-cs"; import { Int32Ptr, TypedArray, VoidPtr } from "./types/emscripten"; +import { mono_wasm_new_external_root } from "./roots"; // Creates a new typed array from pinned array address from pinned_array allocated on the heap to the typed array. // adress of managed pinned array -> copy from heap -> typed array memory @@ -134,33 +135,58 @@ function typedarray_copy_from(typed_array: TypedArray, pinned_array: MonoArray, } } -export function mono_wasm_typed_array_copy_to(js_handle: JSHandle, pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, is_exception: Int32Ptr): MonoObject { - const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!js_obj) { - return wrap_error(is_exception, "ERR07: Invalid JS object handle '" + js_handle + "'"); +export function mono_wasm_typed_array_copy_to_ref(js_handle: JSHandle, pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + try { + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + if (!js_obj) { + wrap_error_root(is_exception, "ERR07: Invalid JS object handle '" + js_handle + "'", resultRoot); + return; + } + + const res = typedarray_copy_to(js_obj, pinned_array, begin, end, bytes_per_element); + // FIXME: We should just return an int + // returns num_of_bytes boxed + js_to_mono_obj_root(res, resultRoot, false); + } catch (exc) { + wrap_error_root(is_exception, String(exc), resultRoot); + } finally { + resultRoot.release(); } - - const res = typedarray_copy_to(js_obj, pinned_array, begin, end, bytes_per_element); - // returns num_of_bytes boxed - return _js_to_mono_obj(false, res); } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function mono_wasm_typed_array_from(pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, type: number, is_exception: Int32Ptr): MonoObject { - const res = typed_array_from(pinned_array, begin, end, bytes_per_element, type); - // returns JS typed array like Int8Array, to be wraped with JSObject proxy - return _js_to_mono_obj(true, res); +export function mono_wasm_typed_array_from_ref(pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, type: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + try { + const res = typed_array_from(pinned_array, begin, end, bytes_per_element, type); + // returns JS typed array like Int8Array, to be wraped with JSObject proxy + js_to_mono_obj_root(res, resultRoot, true); + } catch (exc) { + wrap_error_root(is_exception, String(exc), resultRoot); + } finally { + resultRoot.release(); + } } -export function mono_wasm_typed_array_copy_from(js_handle: JSHandle, pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, is_exception: Int32Ptr): MonoObject | MonoString { - const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!js_obj) { - return wrap_error(is_exception, "ERR08: Invalid JS object handle '" + js_handle + "'"); +export function mono_wasm_typed_array_copy_from_ref(js_handle: JSHandle, pinned_array: MonoArray, begin: number, end: number, bytes_per_element: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + try { + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + if (!js_obj) { + wrap_error_root(is_exception, "ERR08: Invalid JS object handle '" + js_handle + "'", resultRoot); + return; + } + + const res = typedarray_copy_from(js_obj, pinned_array, begin, end, bytes_per_element); + // FIXME: We should just return an int + // returns num_of_bytes boxed + js_to_mono_obj_root(res, resultRoot, false); + } catch (exc) { + wrap_error_root(is_exception, String(exc), resultRoot); + } finally { + resultRoot.release(); } - - const res = typedarray_copy_from(js_obj, pinned_array, begin, end, bytes_per_element); - // returns num_of_bytes boxed - return _js_to_mono_obj(false, res); } export function has_backing_array_buffer(js_obj: TypedArray): boolean { diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js index 664bccf25cd..fd89a822b74 100644 --- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js @@ -12,7 +12,7 @@ const DotnetSupportLib = { $DOTNET__postset: ` let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require}; let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( - { isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)}, + { isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)}, { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, __dotnet_replacements); readAsync = __dotnet_replacements.readAsync; @@ -44,22 +44,22 @@ const linked_functions = [ // corebindings.c "mono_wasm_invoke_js_with_args", - "mono_wasm_get_object_property", - "mono_wasm_set_object_property", - "mono_wasm_get_by_index", - "mono_wasm_set_by_index", - "mono_wasm_get_global_object", - "mono_wasm_create_cs_owned_object", + "mono_wasm_get_object_property_ref", + "mono_wasm_set_object_property_ref", + "mono_wasm_get_by_index_ref", + "mono_wasm_set_by_index_ref", + "mono_wasm_get_global_object_ref", + "mono_wasm_create_cs_owned_object_ref", "mono_wasm_release_cs_owned_object", - "mono_wasm_typed_array_to_array", - "mono_wasm_typed_array_copy_to", - "mono_wasm_typed_array_from", - "mono_wasm_typed_array_copy_from", + "mono_wasm_typed_array_to_array_ref", + "mono_wasm_typed_array_copy_to_ref", + "mono_wasm_typed_array_from_ref", + "mono_wasm_typed_array_copy_from_ref", "mono_wasm_cancel_promise", - "mono_wasm_web_socket_open", + "mono_wasm_web_socket_open_ref", "mono_wasm_web_socket_send", "mono_wasm_web_socket_receive", - "mono_wasm_web_socket_close", + "mono_wasm_web_socket_close_ref", "mono_wasm_web_socket_abort", "mono_wasm_compile_function", diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index c22d5e39d3e..8cc2ff15a1e 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -13,46 +13,48 @@ #include <mono/metadata/object.h> #include <mono/jit/jit.h> +#include "gc-common.h" + //JS funcs extern MonoObject* mono_wasm_invoke_js_with_args (int js_handle, MonoString *method, MonoArray *args, int *is_exception); -extern MonoObject* mono_wasm_get_object_property (int js_handle, MonoString *propertyName, int *is_exception); -extern MonoObject* mono_wasm_get_by_index (int js_handle, int property_index, int *is_exception); -extern MonoObject* mono_wasm_set_object_property (int js_handle, MonoString *propertyName, MonoObject *value, int createIfNotExist, int hasOwnProperty, int *is_exception); -extern MonoObject* mono_wasm_set_by_index (int js_handle, int property_index, MonoObject *value, int *is_exception); -extern MonoObject* mono_wasm_get_global_object (MonoString *global_name, int *is_exception); -extern void* mono_wasm_release_cs_owned_object (int js_handle); -extern MonoObject* mono_wasm_create_cs_owned_object (MonoString *core_name, MonoArray *args, int *is_exception); -extern MonoObject* mono_wasm_typed_array_to_array (int js_handle, int *is_exception); -extern MonoObject* mono_wasm_typed_array_copy_to (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception); -extern MonoObject* mono_wasm_typed_array_from (int ptr, int begin, int end, int bytes_per_element, int type, int *is_exception); -extern MonoObject* mono_wasm_typed_array_copy_from (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception); +extern void mono_wasm_get_object_property_ref (int js_handle, MonoString **propertyName, int *is_exception, MonoObject **result); +extern void mono_wasm_get_by_index_ref (int js_handle, int property_index, int *is_exception, MonoObject **result); +extern void mono_wasm_set_object_property_ref (int js_handle, MonoString **propertyName, MonoObject **value, int createIfNotExist, int hasOwnProperty, int *is_exception, MonoObject **result); +extern void mono_wasm_set_by_index_ref (int js_handle, int property_index, MonoObject **value, int *is_exception, MonoObject **result); +extern void mono_wasm_get_global_object_ref (MonoString **global_name, int *is_exception, MonoObject **result); +extern void mono_wasm_release_cs_owned_object (int js_handle); +extern void mono_wasm_create_cs_owned_object_ref (MonoString **core_name, MonoArray **args, int *is_exception, MonoObject** result); +extern void mono_wasm_typed_array_to_array_ref (int js_handle, int *is_exception, MonoObject **result); +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 MonoString* mono_wasm_cancel_promise (int thenable_js_handle, int *is_exception); -extern MonoObject* mono_wasm_web_socket_open (MonoString *uri, MonoArray *subProtocols, MonoDelegate *on_close, int *web_socket_js_handle, int *thenable_js_handle, int *is_exception); -extern MonoObject* 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); -extern MonoObject* 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); -extern MonoObject* mono_wasm_web_socket_close (int webSocket_js_handle, int code, MonoString * reason, int wait_for_close_received, int *thenable_js_handle, int *is_exception); -extern MonoString* mono_wasm_web_socket_abort (int webSocket_js_handle, int *is_exception); +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 MonoObject* mono_wasm_compile_function (MonoString *str, int *is_exception); void core_initialize_internals () { mono_add_internal_call ("Interop/Runtime::InvokeJSWithArgs", mono_wasm_invoke_js_with_args); - mono_add_internal_call ("Interop/Runtime::GetObjectProperty", mono_wasm_get_object_property); - mono_add_internal_call ("Interop/Runtime::GetByIndex", mono_wasm_get_by_index); - mono_add_internal_call ("Interop/Runtime::SetObjectProperty", mono_wasm_set_object_property); - mono_add_internal_call ("Interop/Runtime::SetByIndex", mono_wasm_set_by_index); - mono_add_internal_call ("Interop/Runtime::GetGlobalObject", mono_wasm_get_global_object); - mono_add_internal_call ("Interop/Runtime::CreateCSOwnedObject", mono_wasm_create_cs_owned_object); + mono_add_internal_call ("Interop/Runtime::GetObjectPropertyRef", mono_wasm_get_object_property_ref); + mono_add_internal_call ("Interop/Runtime::GetByIndexRef", mono_wasm_get_by_index_ref); + mono_add_internal_call ("Interop/Runtime::SetObjectPropertyRef", mono_wasm_set_object_property_ref); + mono_add_internal_call ("Interop/Runtime::SetByIndexRef", mono_wasm_set_by_index_ref); + mono_add_internal_call ("Interop/Runtime::GetGlobalObjectRef", mono_wasm_get_global_object_ref); + mono_add_internal_call ("Interop/Runtime::CreateCSOwnedObjectRef", mono_wasm_create_cs_owned_object_ref); mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object); - mono_add_internal_call ("Interop/Runtime::TypedArrayToArray", mono_wasm_typed_array_to_array); - mono_add_internal_call ("Interop/Runtime::TypedArrayCopyTo", mono_wasm_typed_array_copy_to); - mono_add_internal_call ("Interop/Runtime::TypedArrayFrom", mono_wasm_typed_array_from); - mono_add_internal_call ("Interop/Runtime::TypedArrayCopyFrom", mono_wasm_typed_array_copy_from); + mono_add_internal_call ("Interop/Runtime::TypedArrayToArrayRef", mono_wasm_typed_array_to_array_ref); + 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::CompileFunction", mono_wasm_compile_function); - mono_add_internal_call ("Interop/Runtime::WebSocketOpen", mono_wasm_web_socket_open); + 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::WebSocketClose", mono_wasm_web_socket_close); + 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::CancelPromise", mono_wasm_cancel_promise); } @@ -66,7 +68,8 @@ void core_initialize_internals () // Uint32Array | uint32_t | uint (unsigned integer) // Float32Array | float | float // Float64Array | double | double -// typed array marshaling +// typed array marshalling +// Keep in sync with driver.c #define MARSHAL_ARRAY_BYTE 10 #define MARSHAL_ARRAY_UBYTE 11 #define MARSHAL_ARRAY_UBYTE_C 12 // alias of MARSHAL_ARRAY_UBYTE @@ -77,10 +80,11 @@ void core_initialize_internals () #define MARSHAL_ARRAY_FLOAT 17 #define MARSHAL_ARRAY_DOUBLE 18 -EMSCRIPTEN_KEEPALIVE MonoArray* -mono_wasm_typed_array_new (char *arr, int length, int size, int type) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_typed_array_new_ref (char *arr, int length, int size, int type, PPVOLATILE(MonoArray) result) { - MonoClass *typeClass = mono_get_byte_class(); // default is Byte + MONO_ENTER_GC_UNSAFE; + MonoClass * typeClass = mono_get_byte_class(); // default is Byte switch (type) { case MARSHAL_ARRAY_BYTE: typeClass = mono_get_sbyte_class(); @@ -103,44 +107,62 @@ mono_wasm_typed_array_new (char *arr, int length, int size, int type) case MARSHAL_ARRAY_DOUBLE: typeClass = mono_get_double_class(); break; + case MARSHAL_ARRAY_UBYTE: + case MARSHAL_ARRAY_UBYTE_C: + typeClass = mono_get_byte_class(); + break; + default: + printf ("Invalid marshal type %d in mono_wasm_typed_array_new", type); + abort(); } - MonoArray *buffer; + PVOLATILE(MonoArray) buffer; buffer = mono_array_new (mono_get_root_domain(), typeClass, length); memcpy(mono_array_addr_with_size(buffer, sizeof(char), 0), arr, length * size); - return buffer; + store_volatile((PPVOLATILE(MonoObject))result, (MonoObject *)buffer); + MONO_EXIT_GC_UNSAFE; } +// TODO: Remove - no longer used? If not, convert to ref EMSCRIPTEN_KEEPALIVE int -mono_wasm_unbox_enum (MonoObject *obj) +mono_wasm_unbox_enum (PVOLATILE(MonoObject) obj) { if (!obj) return 0; - - MonoType *type = mono_class_get_type (mono_object_get_class(obj)); - void *ptr = mono_object_unbox (obj); + int result = 0; + MONO_ENTER_GC_UNSAFE; + PVOLATILE(MonoType) type = mono_class_get_type (mono_object_get_class(obj)); + + PVOLATILE(void) ptr = mono_object_unbox (obj); switch (mono_type_get_type(mono_type_get_underlying_type (type))) { case MONO_TYPE_I1: case MONO_TYPE_U1: - return *(unsigned char*)ptr; + result = *(unsigned char*)ptr; + break; case MONO_TYPE_I2: - return *(short*)ptr; + result = *(short*)ptr; + break; case MONO_TYPE_U2: - return *(unsigned short*)ptr; + result = *(unsigned short*)ptr; + break; case MONO_TYPE_I4: - return *(int*)ptr; + result = *(int*)ptr; + break; case MONO_TYPE_U4: - return *(unsigned int*)ptr; + result = *(unsigned int*)ptr; + break; // WASM doesn't support returning longs to JS // case MONO_TYPE_I8: // case MONO_TYPE_U8: default: printf ("Invalid type %d to mono_unbox_enum\n", mono_type_get_type(mono_type_get_underlying_type (type))); - return 0; + break; } + MONO_EXIT_GC_UNSAFE; + return result; } diff --git a/src/mono/wasm/runtime/corebindings.ts b/src/mono/wasm/runtime/corebindings.ts index 8f64ad00dba..82be21ce2a2 100644 --- a/src/mono/wasm/runtime/corebindings.ts +++ b/src/mono/wasm/runtime/corebindings.ts @@ -1,57 +1,58 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { JSHandle, GCHandle, MonoObject } from "./types"; +import { JSHandle, GCHandle, MonoObjectRef } from "./types"; import { PromiseControl } from "./cancelable-promise"; import { runtimeHelpers } from "./imports"; const fn_signatures: [jsname: string, csname: string, signature: string/*ArgsMarshalString*/][] = [ - ["_get_cs_owned_object_by_js_handle", "GetCSOwnedObjectByJSHandle", "ii!"], - ["_get_cs_owned_object_js_handle", "GetCSOwnedObjectJSHandle", "mi"], - ["_try_get_cs_owned_object_js_handle", "TryGetCSOwnedObjectJSHandle", "mi"], - ["_create_cs_owned_proxy", "CreateCSOwnedProxy", "iii!"], + ["_get_cs_owned_object_by_js_handle_ref", "GetCSOwnedObjectByJSHandleRef", "iim"], + ["_get_cs_owned_object_js_handle_ref", "GetCSOwnedObjectJSHandleRef", "mi"], + ["_try_get_cs_owned_object_js_handle_ref", "TryGetCSOwnedObjectJSHandleRef", "mi"], + ["_create_cs_owned_proxy_ref", "CreateCSOwnedProxyRef", "iiim"], - ["_get_js_owned_object_by_gc_handle", "GetJSOwnedObjectByGCHandle", "i!"], - ["_get_js_owned_object_gc_handle", "GetJSOwnedObjectGCHandle", "m"], + ["_get_js_owned_object_by_gc_handle_ref", "GetJSOwnedObjectByGCHandleRef", "im"], + ["_get_js_owned_object_gc_handle_ref", "GetJSOwnedObjectGCHandleRef", "m"], ["_release_js_owned_object_by_gc_handle", "ReleaseJSOwnedObjectByGCHandle", "i"], ["_create_tcs", "CreateTaskSource", ""], - ["_set_tcs_result", "SetTaskSourceResult", "io"], + ["_set_tcs_result_ref", "SetTaskSourceResultRef", "iR"], ["_set_tcs_failure", "SetTaskSourceFailure", "is"], - ["_get_tcs_task", "GetTaskSourceTask", "i!"], - ["_task_from_result", "TaskFromResult", "o!"], - ["_setup_js_cont", "SetupJSContinuation", "mo"], + ["_get_tcs_task_ref", "GetTaskSourceTaskRef", "im"], + ["_task_from_result_ref", "TaskFromResultRef", "Rm"], + ["_setup_js_cont_ref", "SetupJSContinuationRef", "mo"], - ["_object_to_string", "ObjectToString", "m"], - ["_get_date_value", "GetDateValue", "m"], - ["_create_date_time", "CreateDateTime", "d!"], - ["_create_uri", "CreateUri", "s!"], - ["_is_simple_array", "IsSimpleArray", "m"], + ["_object_to_string_ref", "ObjectToStringRef", "m"], + ["_get_date_value_ref", "GetDateValueRef", "m"], + ["_create_date_time_ref", "CreateDateTimeRef", "dm"], + ["_create_uri_ref", "CreateUriRef", "sm"], + ["_is_simple_array_ref", "IsSimpleArrayRef", "m"], ]; export interface t_CSwraps { // BINDING - _get_cs_owned_object_by_js_handle(jsHandle: JSHandle, shouldAddInflight: 0 | 1): MonoObject; - _get_cs_owned_object_js_handle(jsHandle: JSHandle, shouldAddInflight: 0 | 1): JSHandle; - _try_get_cs_owned_object_js_handle(obj: MonoObject, shouldAddInflight: 0 | 1): JSHandle; - _create_cs_owned_proxy(jsHandle: JSHandle, mappedType: number, shouldAddInflight: 0 | 1): MonoObject; + _get_cs_owned_object_by_js_handle_ref(jsHandle: JSHandle, shouldAddInflight: 0 | 1, result: MonoObjectRef): void; + _get_cs_owned_object_js_handle_ref(obj: MonoObjectRef, shouldAddInflight: 0 | 1): JSHandle; + _try_get_cs_owned_object_js_handle_ref(obj: MonoObjectRef, shouldAddInflight: 0 | 1): JSHandle; + _create_cs_owned_proxy_ref(jsHandle: JSHandle, mappedType: number, shouldAddInflight: 0 | 1, result: MonoObjectRef): void; - _get_js_owned_object_by_gc_handle(gcHandle: GCHandle): MonoObject; - _get_js_owned_object_gc_handle(obj: MonoObject): GCHandle + _get_js_owned_object_by_gc_handle_ref(gcHandle: GCHandle, result: MonoObjectRef): void; + _get_js_owned_object_gc_handle_ref(obj: MonoObjectRef): GCHandle _release_js_owned_object_by_gc_handle(gcHandle: GCHandle): void; _create_tcs(): GCHandle; - _set_tcs_result(gcHandle: GCHandle, result: MonoObject): void + _set_tcs_result_ref(gcHandle: GCHandle, result: any): void _set_tcs_failure(gcHandle: GCHandle, result: string): void - _get_tcs_task(gcHandle: GCHandle): MonoObject; - _task_from_result(result: MonoObject): MonoObject - _setup_js_cont(task: MonoObject, continuation: PromiseControl): MonoObject + _get_tcs_task_ref(gcHandle: GCHandle, result: MonoObjectRef): void; + _task_from_result_ref(value: any, result: MonoObjectRef): void; + // FIXME: PromiseControl is a JS object so we can't pass an address directly + _setup_js_cont_ref(task: MonoObjectRef, continuation: PromiseControl): void; - _object_to_string(obj: MonoObject): string; - _get_date_value(obj: MonoObject): number; - _create_date_time(ticks: number): MonoObject; - _create_uri(uri: string): MonoObject; - _is_simple_array(obj: MonoObject): boolean; + _object_to_string_ref(obj: MonoObjectRef): string; + _get_date_value_ref(obj: MonoObjectRef): number; + _create_date_time_ref(ticks: number, result: MonoObjectRef): void; + _create_uri_ref(uri: string, result: MonoObjectRef): void; + _is_simple_array_ref(obj: MonoObjectRef): boolean; } 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 591f654b0b6..dcc67dcd6e5 100644 --- a/src/mono/wasm/runtime/cs-to-js.ts +++ b/src/mono/wasm/runtime/cs-to-js.ts @@ -1,19 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_new_root, WasmRoot } from "./roots"; +import { mono_wasm_new_root, WasmRoot, mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandleDisposed, MarshalError, MarshalType, MonoArray, MonoArrayNull, MonoObject, MonoObjectNull, MonoString, - MonoType, MonoTypeNull + MonoType, MonoTypeNull, MonoObjectRef, MonoStringRef } from "./types"; import { runtimeHelpers } from "./imports"; -import { conv_string } from "./strings"; +import { conv_string_root } from "./strings"; import corebindings from "./corebindings"; import cwraps from "./cwraps"; -import { get_js_owned_object_by_gc_handle, js_owned_gc_handle_symbol, mono_wasm_get_jsobj_from_js_handle, mono_wasm_get_js_handle, _js_owned_object_finalized, _js_owned_object_registry, _lookup_js_owned_object, _register_js_owned_object, _use_finalization_registry } from "./gc-handles"; -import { mono_method_get_call_signature, call_method, wrap_error } from "./method-calls"; -import { _js_to_mono_obj } from "./js-to-cs"; +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, _js_owned_object_finalized, _js_owned_object_registry, _lookup_js_owned_object, _register_js_owned_object, _use_finalization_registry } 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 { getU32, getI32, getF32, getF64 } from "./memory"; import { Int32Ptr, VoidPtr } from "./types/emscripten"; @@ -28,7 +28,7 @@ export function unbox_mono_obj(mono_obj: MonoObject): any { const root = mono_wasm_new_root(mono_obj); try { - return _unbox_mono_obj_root(root); + return unbox_mono_obj_root(root); } finally { root.release(); } @@ -36,7 +36,7 @@ export function unbox_mono_obj(mono_obj: MonoObject): any { function _unbox_cs_owned_root_as_js_object(root: WasmRoot<any>) { // we don't need in-flight reference as we already have it rooted here - const js_handle = corebindings._get_cs_owned_object_js_handle(root.value, 0); + const js_handle = corebindings._get_cs_owned_object_js_handle_ref(root.address, 0); const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); return js_obj; } @@ -45,13 +45,15 @@ function _unbox_cs_owned_root_as_js_object(root: WasmRoot<any>) { function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot<any>, type: MarshalType, typePtr: MonoType, unbox_buffer: VoidPtr): any { //See MARSHAL_TYPE_ defines in driver.c switch (type) { + case MarshalType.NULL: + return null; case MarshalType.INT64: case MarshalType.UINT64: // TODO: Fix this once emscripten offers HEAPI64/HEAPU64 or can return them throw new Error("int64 not available"); case MarshalType.STRING: case MarshalType.STRING_INTERNED: - return conv_string(root.value); + return conv_string_root(root); case MarshalType.VT: throw new Error("no idea on how to unbox value types"); case MarshalType.DELEGATE: @@ -71,40 +73,41 @@ function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot<a case MarshalType.ARRAY_DOUBLE: throw new Error("Marshaling of primitive arrays are not supported."); case <MarshalType>20: // clr .NET DateTime - return new Date(corebindings._get_date_value(root.value)); + return new Date(corebindings._get_date_value_ref(root.address)); case <MarshalType>21: // clr .NET DateTimeOffset - return corebindings._object_to_string(root.value); + return corebindings._object_to_string_ref(root.address); case MarshalType.URI: - return corebindings._object_to_string(root.value); + return corebindings._object_to_string_ref(root.address); case MarshalType.SAFEHANDLE: return _unbox_cs_owned_root_as_js_object(root); case MarshalType.VOID: return undefined; default: - throw new Error(`no idea on how to unbox object of MarshalType ${type} at offset ${root.value} (root address is ${root.get_address()})`); + throw new Error(`no idea on how to unbox object of MarshalType ${type} at offset ${root.value} (root address is ${root.address})`); } } export function _unbox_mono_obj_root_with_known_nonprimitive_type(root: WasmRoot<any>, type: MarshalType, unbox_buffer: VoidPtr): any { if (type >= MarshalError.FIRST) - throw new Error(`Got marshaling error ${type} when attempting to unbox object at address ${root.value} (root located at ${root.get_address()})`); + throw new Error(`Got marshaling error ${type} when attempting to unbox object at address ${root.value} (root located at ${root.address})`); let typePtr = MonoTypeNull; if ((type === MarshalType.VT) || (type == MarshalType.OBJECT)) { typePtr = <MonoType><any>getU32(unbox_buffer); if (<number><any>typePtr < 1024) - throw new Error(`Got invalid MonoType ${typePtr} for object at address ${root.value} (root located at ${root.get_address()})`); + throw new Error(`Got invalid MonoType ${typePtr} for object at address ${root.value} (root located at ${root.address})`); } return _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root, type, typePtr, unbox_buffer); } -export function _unbox_mono_obj_root(root: WasmRoot<any>): any { +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function unbox_mono_obj_root(root: WasmRoot<any>): any { if (root.value === 0) return undefined; const unbox_buffer = runtimeHelpers._unbox_buffer; - const type = cwraps.mono_wasm_try_unbox_primitive_and_get_type(root.value, unbox_buffer, runtimeHelpers._unbox_buffer_size); + const type = cwraps.mono_wasm_try_unbox_primitive_and_get_type_ref(root.address, unbox_buffer, runtimeHelpers._unbox_buffer_size); switch (type) { case MarshalType.INT: return getI32(unbox_buffer); @@ -128,38 +131,43 @@ export function _unbox_mono_obj_root(root: WasmRoot<any>): any { } } +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function mono_array_to_js_array(mono_array: MonoArray): any[] | null { if (mono_array === MonoArrayNull) return null; const arrayRoot = mono_wasm_new_root(mono_array); try { - return _mono_array_root_to_js_array(arrayRoot); + return mono_array_root_to_js_array(arrayRoot); } finally { arrayRoot.release(); } } -function is_nested_array(ele: MonoObject) { - return corebindings._is_simple_array(ele); +function is_nested_array_ref(ele: WasmRoot<MonoObject>) { + return corebindings._is_simple_array_ref(ele.address); } -export function _mono_array_root_to_js_array(arrayRoot: WasmRoot<MonoArray>): any[] | null { +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function mono_array_root_to_js_array(arrayRoot: WasmRoot<MonoArray>): any[] | null { if (arrayRoot.value === MonoArrayNull) return null; + const arrayAddress = arrayRoot.address; const elemRoot = mono_wasm_new_root<MonoObject>(); + const elemAddress = elemRoot.address; try { const len = cwraps.mono_wasm_array_length(arrayRoot.value); const res = new Array(len); for (let i = 0; i < len; ++i) { - elemRoot.value = cwraps.mono_wasm_array_get(arrayRoot.value, i); + // TODO: pass arrayRoot.address and elemRoot.address into new API that copies + cwraps.mono_wasm_array_get_ref(arrayAddress, i, elemAddress); - if (is_nested_array(elemRoot.value)) - res[i] = _mono_array_root_to_js_array(<any>elemRoot); + if (is_nested_array_ref(elemRoot)) + res[i] = mono_array_root_to_js_array(<any>elemRoot); else - res[i] = _unbox_mono_obj_root(elemRoot); + res[i] = unbox_mono_obj_root(elemRoot); } return res; } finally { @@ -172,7 +180,7 @@ export function _wrap_delegate_root_as_function(root: WasmRoot<MonoObject>): Fun return null; // get strong reference to the Delegate - const gc_handle = corebindings._get_js_owned_object_gc_handle(root.value); + const gc_handle = corebindings._get_js_owned_object_gc_handle_ref(root.address); return _wrap_delegate_gc_handle_as_function(gc_handle); } @@ -184,9 +192,11 @@ export function _wrap_delegate_gc_handle_as_function(gc_handle: GCHandle, after_ if (!result) { // note that we do not implement function/delegate roundtrip result = function (...args: any[]) { - const delegateRoot = mono_wasm_new_root(get_js_owned_object_by_gc_handle(gc_handle)); + const delegateRoot = mono_wasm_new_root<MonoObject>(); + get_js_owned_object_by_gc_handle_ref(gc_handle, delegateRoot.address); try { - const res = call_method(result[delegate_invoke_symbol], delegateRoot.value, result[delegate_invoke_signature_symbol], args); + // FIXME: Pass delegateRoot by-ref + const res = call_method_ref(result[delegate_invoke_symbol], delegateRoot, result[delegate_invoke_signature_symbol], args); if (after_listener_callback) { after_listener_callback(); } @@ -197,17 +207,18 @@ export function _wrap_delegate_gc_handle_as_function(gc_handle: GCHandle, after_ }; // bind the method - const delegateRoot = mono_wasm_new_root(get_js_owned_object_by_gc_handle(gc_handle)); + const delegateRoot = mono_wasm_new_root<MonoObject>(); + get_js_owned_object_by_gc_handle_ref(gc_handle, delegateRoot.address); try { if (typeof result[delegate_invoke_symbol] === "undefined") { - result[delegate_invoke_symbol] = cwraps.mono_wasm_get_delegate_invoke(delegateRoot.value); + result[delegate_invoke_symbol] = cwraps.mono_wasm_get_delegate_invoke_ref(delegateRoot.address); if (!result[delegate_invoke_symbol]) { throw new Error("System.Delegate Invoke method can not be resolved."); } } if (typeof result[delegate_invoke_signature_symbol] === "undefined") { - result[delegate_invoke_signature_symbol] = mono_method_get_call_signature(result[delegate_invoke_symbol], delegateRoot.value); + result[delegate_invoke_signature_symbol] = mono_method_get_call_signature_ref(result[delegate_invoke_symbol], delegateRoot); } } finally { delegateRoot.release(); @@ -227,21 +238,25 @@ export function _wrap_delegate_gc_handle_as_function(gc_handle: GCHandle, after_ return result; } -export function mono_wasm_create_cs_owned_object(core_name: MonoString, args: MonoArray, is_exception: Int32Ptr): MonoObject { - const argsRoot = mono_wasm_new_root(args), nameRoot = mono_wasm_new_root(core_name); +export function mono_wasm_create_cs_owned_object_ref(core_name: MonoStringRef, args: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const argsRoot = mono_wasm_new_external_root<MonoArray>(args), + nameRoot = mono_wasm_new_external_root<MonoString>(core_name), + resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); try { - const js_name = conv_string(nameRoot.value); + const js_name = conv_string_root(nameRoot); if (!js_name) { - return wrap_error(is_exception, "Invalid name @" + nameRoot.value); + wrap_error_root(is_exception, "Invalid name @" + nameRoot.value, resultRoot); + return; } const coreObj = (<any>globalThis)[js_name]; if (coreObj === null || typeof coreObj === "undefined") { - return wrap_error(is_exception, "JavaScript host object '" + js_name + "' not found."); + wrap_error_root(is_exception, "JavaScript host object '" + js_name + "' not found.", resultRoot); + return; } try { - const js_args = _mono_array_root_to_js_array(argsRoot); + const js_args = mono_array_root_to_js_array(argsRoot); // This is all experimental !!!!!! const allocator = function (constructor: Function, js_args: any[] | null) { @@ -260,11 +275,13 @@ export function mono_wasm_create_cs_owned_object(core_name: MonoString, args: Mo const js_handle = mono_wasm_get_js_handle(js_obj); // returns boxed js_handle int, because on exception we need to return String on same method signature // here we don't have anything to in-flight reference, as the JSObject doesn't exist yet - return _js_to_mono_obj(false, js_handle); + js_to_mono_obj_root(js_handle, resultRoot, false); } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, resultRoot); + return; } } finally { + resultRoot.release(); argsRoot.release(); nameRoot.release(); } @@ -278,7 +295,7 @@ function _unbox_task_root_as_promise(root: WasmRoot<MonoObject>) { throw new Error("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context."); // get strong reference to Task - const gc_handle = corebindings._get_js_owned_object_gc_handle(root.value); + const gc_handle = corebindings._get_js_owned_object_gc_handle_ref(root.address); // see if we have js owned instance for this gc_handle already let result = _lookup_js_owned_object(gc_handle); @@ -296,7 +313,7 @@ function _unbox_task_root_as_promise(root: WasmRoot<MonoObject>) { result = promise; // register C# side of the continuation - corebindings._setup_js_cont(root.value, promise_control); + corebindings._setup_js_cont_ref(root.address, promise_control); // register for GC of the Task after the JS side is done with the promise if (_use_finalization_registry) { @@ -317,7 +334,7 @@ export function _unbox_ref_type_root_as_js_object(root: WasmRoot<MonoObject>): a // this could be JSObject proxy of a js native object // we don't need in-flight reference as we already have it rooted here - const js_handle = corebindings._try_get_cs_owned_object_js_handle(root.value, 0); + const js_handle = corebindings._try_get_cs_owned_object_js_handle_ref(root.address, 0); if (js_handle) { if (js_handle === JSHandleDisposed) { throw new Error("Cannot access a disposed JSObject at " + root.value); @@ -327,7 +344,7 @@ export function _unbox_ref_type_root_as_js_object(root: WasmRoot<MonoObject>): a // otherwise this is C# only object // get strong reference to Object - const gc_handle = corebindings._get_js_owned_object_gc_handle(root.value); + const gc_handle = corebindings._get_js_owned_object_gc_handle_ref(root.address); // see if we have js owned instance for this gc_handle already let result = _lookup_js_owned_object(gc_handle); diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 7567ac91411..861824f56d7 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -5,16 +5,17 @@ import { assert, MonoArray, MonoAssembly, MonoClass, MonoMethod, MonoObject, MonoString, - MonoType + MonoType, MonoObjectRef, MonoStringRef } from "./types"; import { Module } from "./imports"; -import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr } from "./types/emscripten"; +import { VoidPtr, CharPtrPtr, Int32Ptr, CharPtr, ManagedPointer } from "./types/emscripten"; const fn_signatures: [ident: string, returnType: string | null, argTypes?: string[], opts?: any][] = [ // MONO ["mono_wasm_register_root", "number", ["number", "number", "string"]], ["mono_wasm_deregister_root", null, ["number"]], ["mono_wasm_string_get_data", null, ["number", "number", "number", "number"]], + ["mono_wasm_string_get_data_ref", null, ["number", "number", "number", "number"]], ["mono_wasm_set_is_debugger_attached", "void", ["bool"]], ["mono_wasm_send_dbg_command", "bool", ["number", "number", "number", "number", "number"]], ["mono_wasm_send_dbg_command_with_parms", "bool", ["number", "number", "number", "number", "number", "number", "string"]], @@ -40,26 +41,29 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin ["mono_wasm_assembly_find_type", "number", ["number", "string", "string"]], ["mono_wasm_assembly_find_method", "number", ["number", "string", "number"]], ["mono_wasm_invoke_method", "number", ["number", "number", "number", "number"]], + ["mono_wasm_invoke_method_ref", "void", ["number", "number", "number", "number", "number"]], ["mono_wasm_string_get_utf8", "number", ["number"]], - ["mono_wasm_string_from_utf16", "number", ["number", "number"]], + ["mono_wasm_string_from_utf16_ref", "void", ["number", "number", "number"]], ["mono_wasm_get_obj_type", "number", ["number"]], ["mono_wasm_array_length", "number", ["number"]], ["mono_wasm_array_get", "number", ["number", "number"]], + ["mono_wasm_array_get_ref", "void", ["number", "number", "number"]], ["mono_wasm_obj_array_new", "number", ["number"]], + ["mono_wasm_obj_array_new_ref", "void", ["number", "number"]], ["mono_wasm_obj_array_set", "void", ["number", "number", "number"]], + ["mono_wasm_obj_array_set_ref", "void", ["number", "number", "number"]], ["mono_wasm_register_bundled_satellite_assemblies", "void", []], - ["mono_wasm_try_unbox_primitive_and_get_type", "number", ["number", "number", "number"]], - ["mono_wasm_box_primitive", "number", ["number", "number", "number"]], - ["mono_wasm_intern_string", "number", ["number"]], + ["mono_wasm_try_unbox_primitive_and_get_type_ref", "number", ["number", "number", "number"]], + ["mono_wasm_box_primitive_ref", "void", ["number", "number", "number", "number"]], + ["mono_wasm_intern_string_ref", "void", ["number"]], ["mono_wasm_assembly_get_entry_point", "number", ["number"]], - ["mono_wasm_get_delegate_invoke", "number", ["number"]], - ["mono_wasm_string_array_new", "number", ["number"]], - ["mono_wasm_typed_array_new", "number", ["number", "number", "number", "number"]], + ["mono_wasm_get_delegate_invoke_ref", "number", ["number"]], + ["mono_wasm_string_array_new_ref", "void", ["number", "number"]], + ["mono_wasm_typed_array_new_ref", "void", ["number", "number", "number", "number", "number"]], ["mono_wasm_class_get_type", "number", ["number"]], ["mono_wasm_type_get_class", "number", ["number"]], ["mono_wasm_get_type_name", "string", ["number"]], ["mono_wasm_get_type_aqn", "string", ["number"]], - ["mono_wasm_unbox_rooted", "number", ["number"]], //DOTNET ["mono_wasm_string_from_js", "number", ["string"]], @@ -70,13 +74,15 @@ 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_write_managed_pointer_unsafe", "void", ["number", "number"]], + ["mono_wasm_copy_managed_pointer", "void", ["number", "number"]], ]; export interface t_Cwraps { // MONO mono_wasm_register_root(start: VoidPtr, size: number, name: string): number; mono_wasm_deregister_root(addr: VoidPtr): void; - mono_wasm_string_get_data(string: MonoString, outChars: CharPtrPtr, outLengthBytes: Int32Ptr, outIsInterned: Int32Ptr): void; + mono_wasm_string_get_data_ref(stringRef: MonoStringRef, outChars: CharPtrPtr, outLengthBytes: Int32Ptr, outIsInterned: Int32Ptr): void; mono_wasm_set_is_debugger_attached(value: boolean): void; mono_wasm_send_dbg_command(id: number, command_set: number, command: number, data: VoidPtr, size: number): boolean; mono_wasm_send_dbg_command_with_parms(id: number, command_set: number, command: number, data: VoidPtr, size: number, valtype: number, newvalue: string): boolean; @@ -92,6 +98,11 @@ export interface t_Cwraps { mono_wasm_load_runtime(unused: string, debug_level: number): void; mono_wasm_change_debugger_log_level(value: number): void; + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_string_get_data(string: MonoString, outChars: CharPtrPtr, outLengthBytes: Int32Ptr, outIsInterned: Int32Ptr): void; + // BINDING mono_wasm_get_corlib(): MonoAssembly; mono_wasm_assembly_load(name: string): MonoAssembly; @@ -100,29 +111,55 @@ export interface t_Cwraps { mono_wasm_find_corlib_type(namespace: string, name: string): MonoType; mono_wasm_assembly_find_type(assembly: MonoAssembly, namespace: string, name: string): MonoType; mono_wasm_assembly_find_method(klass: MonoClass, name: string, args: number): MonoMethod; - mono_wasm_invoke_method(method: MonoMethod, this_arg: MonoObject, params: VoidPtr, out_exc: VoidPtr): MonoObject; + mono_wasm_invoke_method_ref(method: MonoMethod, this_arg: MonoObjectRef, params: VoidPtr, out_exc: MonoObjectRef, out_result: MonoObjectRef): void; + /** + * @deprecated Not GC or thread safe + */ mono_wasm_string_get_utf8(str: MonoString): CharPtr; - mono_wasm_string_from_utf16(str: CharPtr, len: number): MonoString; - mono_wasm_get_obj_type(str: MonoObject): number; + mono_wasm_string_from_utf16_ref(str: CharPtr, len: number, result: MonoObjectRef): void; mono_wasm_array_length(array: MonoArray): number; - mono_wasm_array_get(array: MonoArray, idx: number): MonoObject; - mono_wasm_obj_array_new(size: number): MonoArray; - mono_wasm_obj_array_set(array: MonoArray, idx: number, obj: MonoObject): void; + + mono_wasm_array_get_ref(array: MonoObjectRef, idx: number, result: MonoObjectRef): void; + mono_wasm_obj_array_new_ref(size: number, result: MonoObjectRef): void; + mono_wasm_obj_array_set_ref(array: MonoObjectRef, idx: number, obj: MonoObjectRef): void; mono_wasm_register_bundled_satellite_assemblies(): void; - mono_wasm_try_unbox_primitive_and_get_type(obj: MonoObject, buffer: VoidPtr, buffer_size: number): number; - mono_wasm_box_primitive(klass: MonoClass, value: VoidPtr, value_size: number): MonoObject; - mono_wasm_intern_string(str: MonoString): MonoString; + mono_wasm_try_unbox_primitive_and_get_type_ref(obj: MonoObjectRef, buffer: VoidPtr, buffer_size: number): number; + mono_wasm_box_primitive_ref(klass: MonoClass, value: VoidPtr, value_size: number, result: MonoObjectRef): void; + mono_wasm_intern_string_ref(strRef: MonoStringRef): void; mono_wasm_assembly_get_entry_point(assembly: MonoAssembly): MonoMethod; - mono_wasm_get_delegate_invoke(delegate: MonoObject): MonoMethod; - mono_wasm_string_array_new(size: number): MonoArray; - mono_wasm_typed_array_new(arr: VoidPtr, length: number, size: number, type: number): MonoArray; + mono_wasm_string_array_new_ref(size: number, result: MonoObjectRef): void; + mono_wasm_typed_array_new_ref(arr: VoidPtr, length: number, size: number, type: number, result: MonoObjectRef): void; mono_wasm_class_get_type(klass: MonoClass): MonoType; mono_wasm_type_get_class(ty: MonoType): MonoClass; + mono_wasm_get_delegate_invoke_ref(delegate: MonoObjectRef): MonoMethod; mono_wasm_get_type_name(ty: MonoType): string; mono_wasm_get_type_aqn(ty: MonoType): string; - mono_wasm_unbox_rooted(obj: MonoObject): VoidPtr; + + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_get_obj_type(str: MonoObject): number; + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_invoke_method(method: MonoMethod, this_arg: MonoObject, params: VoidPtr, out_exc: MonoObjectRef): MonoObject; + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_obj_array_new(size: number): MonoArray; + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_array_get(array: MonoArray, idx: number): MonoObject; + /** + * @deprecated Not GC or thread safe + */ + mono_wasm_obj_array_set(array: MonoArray, idx: number, obj: MonoObject): void; //DOTNET + /** + * @deprecated Not GC or thread safe + */ mono_wasm_string_from_js(str: string): MonoString; //INTERNAL @@ -131,6 +168,8 @@ 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_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void; + mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): 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 617b572e757..3e5083662d8 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -66,13 +66,18 @@ declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Arra */ declare function mono_wasm_new_root_buffer(capacity: number, name?: string): WasmRootBuffer; /** + * Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack. + * Releasing this root will not de-allocate the root space. You still need to call .release(). + */ +declare function mono_wasm_new_external_root<T extends MonoObject>(address: VoidPtr | MonoObjectRef): WasmRoot<T>; +/** * Allocates temporary storage for a pointer into the managed heap. * Pointers stored here will be visible to the GC, ensuring that the object they point to aren't moved or collected. * If you already have a managed pointer you can pass it as an argument to initialize the temporary storage. * The result object has get() and set(value) methods, along with a .value property. * When you are done using the root you must call its .release() method. */ -declare function mono_wasm_new_root<T extends ManagedPointer | NativePointer>(value?: T | undefined): WasmRoot<T>; +declare function mono_wasm_new_root<T extends MonoObject>(value?: T | undefined): WasmRoot<T>; /** * Releases 1 or more root or root buffer objects. * Multiple objects may be passed on the argument list. @@ -91,23 +96,29 @@ declare class WasmRootBuffer { constructor(offset: VoidPtr, capacity: number, ownsAllocation: boolean, name?: string); _throw_index_out_of_range(): void; _check_in_range(index: number): void; - get_address(index: number): NativePointer; + get_address(index: number): MonoObjectRef; get_address_32(index: number): number; get(index: number): ManagedPointer; set(index: number, value: ManagedPointer): ManagedPointer; + copy_value_from_address(index: number, sourceAddress: MonoObjectRef): void; _unsafe_get(index: number): number; _unsafe_set(index: number, value: ManagedPointer | NativePointer): void; clear(): void; release(): void; toString(): string; } -interface WasmRoot<T extends ManagedPointer | NativePointer> { - get_address(): NativePointer; +interface WasmRoot<T extends MonoObject> { + get_address(): MonoObjectRef; get_address_32(): number; + get address(): MonoObjectRef; get(): T; set(value: T): T; get value(): T; set value(value: T); + copy_from_address(source: MonoObjectRef): void; + copy_to_address(destination: MonoObjectRef): void; + copy_from(source: WasmRoot<T>): void; + copy_to(destination: WasmRoot<T>): void; valueOf(): T; clear(): void; release(): void; @@ -123,6 +134,9 @@ interface MonoString extends MonoObject { interface MonoArray extends MonoObject { __brand: "MonoArray"; } +interface MonoObjectRef extends ManagedPointer { + __brandMonoObjectRef: "MonoObjectRef"; +} declare type MonoConfig = { isError: false; assembly_root: string; @@ -237,21 +251,39 @@ declare function mono_wasm_load_config(configFilePath: string): Promise<void>; declare function mono_wasm_load_icu_data(offset: VoidPtr): boolean; +/** + * @deprecated Not GC or thread safe + */ declare function conv_string(mono_obj: MonoString): string | null; +declare function conv_string_root(root: WasmRoot<MonoString>): string | null; +declare function js_string_to_mono_string_root(string: string, result: WasmRoot<MonoString>): void; +/** + * @deprecated Not GC or thread safe + */ declare function js_string_to_mono_string(string: string): MonoString; +/** + * @deprecated Not GC or thread safe. For blazor use only + */ declare function js_to_mono_obj(js_obj: any): MonoObject; +declare function js_to_mono_obj_root(js_obj: any, result: WasmRoot<MonoObject>, should_add_in_flight: boolean): void; +declare function js_typed_array_to_array_root(js_obj: any, result: WasmRoot<MonoArray>): void; +/** + * @deprecated Not GC or thread safe + */ declare function js_typed_array_to_array(js_obj: any): MonoArray; declare function unbox_mono_obj(mono_obj: MonoObject): any; +declare function unbox_mono_obj_root(root: WasmRoot<any>): any; declare function mono_array_to_js_array(mono_array: MonoArray): any[] | null; +declare function mono_array_root_to_js_array(arrayRoot: WasmRoot<MonoArray>): any[] | null; declare function mono_bind_static_method(fqn: string, signature?: string): Function; declare function mono_call_assembly_entry_point(assembly: string, args?: any[], signature?: string): number; declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr; -declare type _MemOffset = number | VoidPtr | NativePointer; +declare type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer; declare type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; declare function setU8(offset: _MemOffset, value: number): void; declare function setU16(offset: _MemOffset, value: number): void; @@ -285,6 +317,7 @@ declare const MONO: { mono_load_runtime_and_bcl_args: typeof mono_load_runtime_and_bcl_args; mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer; mono_wasm_new_root: typeof mono_wasm_new_root; + mono_wasm_new_external_root: typeof mono_wasm_new_external_root; 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; @@ -313,16 +346,50 @@ declare const MONO: { }; declare type MONOType = typeof MONO; declare const BINDING: { + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_new: (size: number) => MonoArray; + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_set: (array: MonoArray, idx: number, obj: MonoObject) => void; + /** + * @deprecated Not GC or thread safe + */ js_string_to_mono_string: typeof js_string_to_mono_string; + /** + * @deprecated Not GC or thread safe + */ js_typed_array_to_array: typeof js_typed_array_to_array; - js_to_mono_obj: typeof js_to_mono_obj; + /** + * @deprecated Not GC or thread safe + */ mono_array_to_js_array: typeof mono_array_to_js_array; + /** + * @deprecated Not GC or thread safe + */ + js_to_mono_obj: typeof js_to_mono_obj; + /** + * @deprecated Not GC or thread safe + */ conv_string: typeof conv_string; + /** + * @deprecated Not GC or thread safe + */ + unbox_mono_obj: typeof unbox_mono_obj; + /** + * @deprecated Renamed to conv_string_root + */ + conv_string_rooted: typeof conv_string_root; + js_string_to_mono_string_root: typeof js_string_to_mono_string_root; + js_typed_array_to_array_root: typeof js_typed_array_to_array_root; + js_to_mono_obj_root: typeof js_to_mono_obj_root; + conv_string_root: typeof conv_string_root; + unbox_mono_obj_root: typeof unbox_mono_obj_root; + mono_array_root_to_js_array: typeof mono_array_root_to_js_array; bind_static_method: typeof mono_bind_static_method; call_assembly_entry_point: typeof mono_call_assembly_entry_point; - unbox_mono_obj: typeof unbox_mono_obj; }; declare type BINDINGType = typeof BINDING; interface DotnetPublicAPI { diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index c5954cf3ef4..929631ec72e 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -29,6 +29,7 @@ #include <mono/jit/mono-private-unstable.h> #include "pinvoke.h" +#include "gc-common.h" #ifdef CORE_BINDINGS void core_initialize_internals (); @@ -41,7 +42,7 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca void mono_wasm_enable_debugging (int); -int mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type); +static int _marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type); int mono_wasm_register_root (char *start, size_t size, const char *name); void mono_wasm_deregister_root (char *addr); @@ -159,13 +160,19 @@ void mono_gc_deregister_root (char* addr); EMSCRIPTEN_KEEPALIVE int mono_wasm_register_root (char *start, size_t size, const char *name) { - return mono_gc_register_root (start, size, (MonoGCDescriptor)NULL, MONO_ROOT_SOURCE_EXTERNAL, NULL, name ? name : "mono_wasm_register_root"); + int result; + MONO_ENTER_GC_UNSAFE; + result = mono_gc_register_root (start, size, (MonoGCDescriptor)NULL, MONO_ROOT_SOURCE_EXTERNAL, NULL, name ? name : "mono_wasm_register_root"); + MONO_EXIT_GC_UNSAFE; + return result; } -EMSCRIPTEN_KEEPALIVE void +EMSCRIPTEN_KEEPALIVE void mono_wasm_deregister_root (char *addr) { + MONO_ENTER_GC_UNSAFE; mono_gc_deregister_root (addr); + MONO_EXIT_GC_UNSAFE; } #ifdef DRIVER_GEN @@ -382,6 +389,9 @@ icall_table_lookup_symbol (void *func) void* get_native_to_interp (MonoMethod *method, void *extra_arg) { + void *addr; + + MONO_ENTER_GC_UNSAFE; MonoClass *klass = mono_method_get_class (method); MonoImage *image = mono_class_get_image (klass); MonoAssembly *assembly = mono_image_get_assembly (image); @@ -400,7 +410,8 @@ get_native_to_interp (MonoMethod *method, void *extra_arg) key [i] = '_'; } - void *addr = wasm_dl_get_native_to_interp (key, extra_arg); + addr = wasm_dl_get_native_to_interp (key, extra_arg); + MONO_EXIT_GC_UNSAFE; return addr; } @@ -582,72 +593,109 @@ mono_wasm_assembly_load (const char *name) EMSCRIPTEN_KEEPALIVE MonoAssembly* mono_wasm_get_corlib () { - return mono_image_get_assembly (mono_get_corlib()); + MonoAssembly* result; + MONO_ENTER_GC_UNSAFE; + result = mono_image_get_assembly (mono_get_corlib()); + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE MonoClass* mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name) { assert (assembly); - return mono_class_from_name (mono_assembly_get_image (assembly), namespace, name); + MonoClass *result; + MONO_ENTER_GC_UNSAFE; + result = mono_class_from_name (mono_assembly_get_image (assembly), namespace, name); + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE MonoMethod* mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments) { assert (klass); - return mono_class_get_method_from_name (klass, name, arguments); + MonoMethod* result; + MONO_ENTER_GC_UNSAFE; + result = mono_class_get_method_from_name (klass, name, arguments); + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE MonoMethod* -mono_wasm_get_delegate_invoke (MonoObject *delegate) +mono_wasm_get_delegate_invoke_ref (MonoObject **delegate) { - return mono_get_delegate_invoke(mono_object_get_class (delegate)); + MonoMethod * result; + MONO_ENTER_GC_UNSAFE; + result = mono_get_delegate_invoke(mono_object_get_class (*delegate)); + MONO_EXIT_GC_UNSAFE; + return result; } -EMSCRIPTEN_KEEPALIVE MonoObject* -mono_wasm_box_primitive (MonoClass *klass, void *value, int value_size) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_box_primitive_ref (MonoClass *klass, void *value, int value_size, PPVOLATILE(MonoObject) result) { assert (klass); + MONO_ENTER_GC_UNSAFE; MonoType *type = mono_class_get_type (klass); int alignment; - if (mono_type_size (type, &alignment) > value_size) - return NULL; - // TODO: use mono_value_box_checked and propagate error out - return mono_value_box (root_domain, klass, value); + if (mono_type_size (type, &alignment) <= value_size) + // TODO: use mono_value_box_checked and propagate error out + store_volatile(result, mono_value_box (root_domain, klass, value)); + + MONO_EXIT_GC_UNSAFE; } -EMSCRIPTEN_KEEPALIVE MonoObject* -mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[], MonoObject **out_exc) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_invoke_method_ref (MonoMethod *method, MonoObject **this_arg_in, void *params[], MonoObject **_out_exc, MonoObject **out_result) { - MonoObject *exc = NULL; - MonoObject *res; - + PPVOLATILE(MonoObject) out_exc = _out_exc; + PVOLATILE(MonoObject) temp_exc = NULL; if (out_exc) *out_exc = NULL; - res = mono_runtime_invoke (method, this_arg, params, &exc); - if (exc) { - if (out_exc) - *out_exc = exc; + else + out_exc = &temp_exc; - MonoObject *exc2 = NULL; - res = (MonoObject*)mono_object_to_string (exc, &exc2); + MONO_ENTER_GC_UNSAFE; + if (out_result) { + *out_result = NULL; + PVOLATILE(MonoObject) invoke_result = mono_runtime_invoke (method, this_arg_in ? *this_arg_in : NULL, params, (MonoObject **)out_exc); + store_volatile(out_result, invoke_result); + } else { + mono_runtime_invoke (method, this_arg_in ? *this_arg_in : NULL, params, (MonoObject **)out_exc); + } + + if (*out_exc && out_result) { + PVOLATILE(MonoObject) exc2 = NULL; + store_volatile(out_result, (MonoObject*)mono_object_to_string (*out_exc, (MonoObject **)&exc2)); if (exc2) - res = (MonoObject*) mono_string_new (root_domain, "Exception Double Fault"); - return res; + store_volatile(out_result, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault")); } + MONO_EXIT_GC_UNSAFE; +} - MonoMethodSignature *sig = mono_method_signature (method); - MonoType *type = mono_signature_get_return_type (sig); - // If the method return type is void return null - // This gets around a memory access crash when the result return a value when - // a void method is invoked. - if (mono_type_get_type (type) == MONO_TYPE_VOID) - return NULL; +// deprecated +MonoObject* +mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[], MonoObject **out_exc) +{ + PVOLATILE(MonoObject) result = NULL; + mono_wasm_invoke_method_ref (method, &this_arg, params, out_exc, (MonoObject **)&result); + + if (result) { + MONO_ENTER_GC_UNSAFE; + MonoMethodSignature *sig = mono_method_signature (method); + MonoType *type = mono_signature_get_return_type (sig); + // If the method return type is void return null + // This gets around a memory access crash when the result return a value when + // a void method is invoked. + if (mono_type_get_type (type) == MONO_TYPE_VOID) + result = NULL; + MONO_EXIT_GC_UNSAFE; + } - return res; + return result; } EMSCRIPTEN_KEEPALIVE MonoMethod* @@ -656,10 +704,11 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) MonoImage *image; MonoMethod *method; + MONO_ENTER_GC_UNSAFE; image = mono_assembly_get_image (assembly); uint32_t entry = mono_image_get_entry_point (image); if (!entry) - return NULL; + goto end; mono_domain_ensure_entry_assembly (root_domain, assembly); method = mono_get_method (image, entry, NULL); @@ -676,7 +725,7 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) int name_length = strlen (name); if ((*name != '<') || (name [name_length - 1] != '>')) - return method; + goto end; MonoClass *klass = mono_method_get_class (method); assert(klass); @@ -688,7 +737,8 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) MonoMethod *async_method = mono_class_get_method_from_name (klass, async_name, mono_signature_get_param_count (sig)); if (async_method != NULL) { free (async_name); - return async_method; + method = async_method; + goto end; } // look for "Name" by trimming the first and last character of "<Name>" @@ -697,35 +747,48 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) free (async_name); if (async_method != NULL) - return async_method; + method = async_method; } + + end: + MONO_EXIT_GC_UNSAFE; return method; } +// TODO: ref EMSCRIPTEN_KEEPALIVE char * mono_wasm_string_get_utf8 (MonoString *str) { - return mono_string_to_utf8 (str); //XXX JS is responsible for freeing this + char * result; + MONO_ENTER_GC_UNSAFE; + result = mono_string_to_utf8 (str); //XXX JS is responsible for freeing this + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE MonoString * mono_wasm_string_from_js (const char *str) { + PVOLATILE(MonoString) result = NULL; + MONO_ENTER_GC_UNSAFE; if (str) - return mono_string_new (root_domain, str); - else - return NULL; + result = mono_string_new (root_domain, str); + MONO_EXIT_GC_UNSAFE; + return result; } -EMSCRIPTEN_KEEPALIVE MonoString * -mono_wasm_string_from_utf16 (const mono_unichar2 * chars, int length) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_string_from_utf16_ref (const mono_unichar2 * chars, int length, MonoString **result) { assert (length >= 0); - if (chars) - return mono_string_new_utf16 (root_domain, chars, length); - else - return NULL; + MONO_ENTER_GC_UNSAFE; + if (chars) { + mono_gc_wbarrier_generic_store_atomic(result, (MonoObject *)mono_string_new_utf16 (root_domain, chars, length)); + } else { + mono_gc_wbarrier_generic_store_atomic(result, NULL); + } + MONO_EXIT_GC_UNSAFE; } static int @@ -734,18 +797,20 @@ class_is_task (MonoClass *klass) if (!klass) return 0; + int result; + MONO_ENTER_GC_UNSAFE; if (!task_class && !resolved_task_class) { task_class = mono_class_from_name (mono_get_corlib(), "System.Threading.Tasks", "Task"); resolved_task_class = 1; } - if (task_class && (klass == task_class || mono_class_is_subclass_of(klass, task_class, 0))) - return 1; - - return 0; + result = task_class && (klass == task_class || mono_class_is_subclass_of(klass, task_class, 0)); + MONO_EXIT_GC_UNSAFE; + return result; } -MonoClass* mono_get_uri_class(MonoException** exc) +static MonoClass* +_get_uri_class(MonoException** exc) { MonoAssembly* assembly = mono_wasm_assembly_load ("System"); if (!assembly) @@ -754,8 +819,10 @@ MonoClass* mono_get_uri_class(MonoException** exc) return klass; } -void mono_wasm_ensure_classes_resolved () +static void +_ensure_classes_resolved () { + MONO_ENTER_GC_UNSAFE; if (!datetime_class && !resolved_datetime_class) { datetime_class = mono_class_from_name (mono_get_corlib(), "System", "DateTime"); resolved_datetime_class = 1; @@ -765,8 +832,8 @@ void mono_wasm_ensure_classes_resolved () resolved_datetimeoffset_class = 1; } if (!uri_class && !resolved_uri_class) { - MonoException** exc = NULL; - uri_class = mono_get_uri_class(exc); + PVOLATILE(MonoException) exc = NULL; + uri_class = _get_uri_class((MonoException **)&exc); resolved_uri_class = 1; } if (!safehandle_class && !resolved_safehandle_class) { @@ -777,10 +844,12 @@ void mono_wasm_ensure_classes_resolved () voidtaskresult_class = mono_class_from_name (mono_get_corlib(), "System.Threading.Tasks", "VoidTaskResult"); resolved_voidtaskresult_class = 1; } + MONO_EXIT_GC_UNSAFE; } -int -mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type) +// This must be run inside a GC unsafe region +static int +_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type) { switch (mono_type) { // case MONO_TYPE_CHAR: prob should be done not as a number? @@ -843,7 +912,7 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType } } default: - mono_wasm_ensure_classes_resolved (); + _ensure_classes_resolved (); if (klass) { if (klass == datetime_class) @@ -870,6 +939,7 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType } } +// FIXME: Ref EMSCRIPTEN_KEEPALIVE MonoClass * mono_wasm_get_obj_class (MonoObject *obj) { @@ -879,53 +949,50 @@ mono_wasm_get_obj_class (MonoObject *obj) return mono_object_get_class (obj); } -EMSCRIPTEN_KEEPALIVE int -mono_wasm_get_obj_type (MonoObject *obj) +// This code runs inside a gc unsafe region +static int +_wasm_get_obj_type_ref_impl (PPVOLATILE(MonoObject) obj) { - if (!obj) + if (!obj || !*obj) return 0; /* Process obj before calling into the runtime, class_from_name () can invoke managed code */ - MonoClass *klass = mono_object_get_class (obj); + MonoClass *klass = mono_object_get_class (*obj); if (!klass) return MARSHAL_ERROR_NULL_CLASS_POINTER; if ((klass == mono_get_string_class ()) && - mono_string_instance_is_interned ((MonoString *)obj)) + mono_string_instance_is_interned ((MonoString *)*obj)) return MARSHAL_TYPE_STRING_INTERNED; MonoType *type = mono_class_get_type (klass); if (!type) return MARSHAL_ERROR_NULL_TYPE_POINTER; - obj = NULL; int mono_type = mono_type_get_type (type); - return mono_wasm_marshal_type_from_mono_type (mono_type, klass, type); + return _marshal_type_from_mono_type (mono_type, klass, type); } +// FIXME: Ref EMSCRIPTEN_KEEPALIVE int -mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int result_capacity) +mono_wasm_get_obj_type (MonoObject *obj) { + int result; + MONO_ENTER_GC_UNSAFE; + result = _wasm_get_obj_type_ref_impl(&obj); + MONO_EXIT_GC_UNSAFE; + return result; +} + +// This code runs inside a gc unsafe region +static int +_mono_wasm_try_unbox_primitive_and_get_type_ref_impl (PVOLATILE(MonoObject) obj, void *result, int result_capacity) { void **resultP = result; int *resultI = result; int64_t *resultL = result; float *resultF = result; double *resultD = result; - if (result_capacity >= sizeof (int64_t)) - *resultL = 0; - else if (result_capacity >= sizeof (int)) - *resultI = 0; - - if (!result) - return MARSHAL_ERROR_BUFFER_TOO_SMALL; - - if (result_capacity < 16) - return MARSHAL_ERROR_BUFFER_TOO_SMALL; - - if (!obj) - return MARSHAL_TYPE_NULL; - /* Process obj before calling into the runtime, class_from_name () can invoke managed code */ MonoClass *klass = mono_object_get_class (obj); if (!klass) @@ -956,7 +1023,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r if (mono_type_generic_inst_is_valuetype (type)) mono_type = MONO_TYPE_VALUETYPE; } - + // FIXME: We would prefer to unbox once here but it will fail if the value isn't unboxable switch (mono_type) { @@ -1004,7 +1071,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r // Check whether this struct has special-case marshaling // FIXME: Do we need to null out obj before this? - int marshal_type = mono_wasm_marshal_type_from_mono_type (mono_type, klass, original_type); + int marshal_type = _marshal_type_from_mono_type (mono_type, klass, original_type); if (marshal_type != MARSHAL_TYPE_VT) return marshal_type; @@ -1028,21 +1095,47 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r // HACK: Store the class pointer into the result buffer so our caller doesn't // have to call back into the native runtime later to get it *resultP = type; - obj = NULL; - int fallbackResultType = mono_wasm_marshal_type_from_mono_type (mono_type, klass, original_type); + int fallbackResultType = _marshal_type_from_mono_type (mono_type, klass, original_type); assert (fallbackResultType != MARSHAL_TYPE_VT); return fallbackResultType; } // We successfully performed a fast unboxing here so use the type information // matching what we unboxed (i.e. an enum's underlying type instead of its type) - obj = NULL; - int resultType = mono_wasm_marshal_type_from_mono_type (mono_type, klass, type); + int resultType = _marshal_type_from_mono_type (mono_type, klass, type); assert (resultType != MARSHAL_TYPE_VT); return resultType; } EMSCRIPTEN_KEEPALIVE int +mono_wasm_try_unbox_primitive_and_get_type_ref (MonoObject **objRef, void *result, int result_capacity) +{ + if (!result) + return MARSHAL_ERROR_BUFFER_TOO_SMALL; + + int retval; + int *resultI = result; + int64_t *resultL = result; + + if (result_capacity >= sizeof (int64_t)) + *resultL = 0; + else if (result_capacity >= sizeof (int)) + *resultI = 0; + + if (result_capacity < 16) + return MARSHAL_ERROR_BUFFER_TOO_SMALL; + + if (!objRef || !(*objRef)) + return MARSHAL_TYPE_NULL; + + MONO_ENTER_GC_UNSAFE; + retval = _mono_wasm_try_unbox_primitive_and_get_type_ref_impl (*objRef, result, result_capacity); + MONO_EXIT_GC_UNSAFE; + return retval; +} + +// FIXME: Ref +EMSCRIPTEN_KEEPALIVE int mono_wasm_array_length (MonoArray *array) { return mono_array_length (array); @@ -1054,10 +1147,29 @@ mono_wasm_array_get (MonoArray *array, int idx) return mono_array_get (array, MonoObject*, idx); } +EMSCRIPTEN_KEEPALIVE void +mono_wasm_array_get_ref (MonoArray **array, int idx, MonoObject **result) +{ + MONO_ENTER_GC_UNSAFE; + mono_gc_wbarrier_generic_store_atomic(result, mono_array_get (*array, MonoObject*, idx)); + MONO_EXIT_GC_UNSAFE; +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_obj_array_new_ref (int size, MonoArray **result) +{ + MONO_ENTER_GC_UNSAFE; + mono_gc_wbarrier_generic_store_atomic(result, (MonoObject *)mono_array_new (root_domain, mono_get_object_class (), size)); + MONO_EXIT_GC_UNSAFE; +} + +// Deprecated EMSCRIPTEN_KEEPALIVE MonoArray* mono_wasm_obj_array_new (int size) { - return mono_array_new (root_domain, mono_get_object_class (), size); + PVOLATILE(MonoArray) result = NULL; + mono_wasm_obj_array_new_ref(size, (MonoArray **)&result); + return result; } EMSCRIPTEN_KEEPALIVE void @@ -1066,10 +1178,20 @@ mono_wasm_obj_array_set (MonoArray *array, int idx, MonoObject *obj) mono_array_setref (array, idx, obj); } -EMSCRIPTEN_KEEPALIVE MonoArray* -mono_wasm_string_array_new (int size) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_obj_array_set_ref (MonoArray **array, int idx, MonoObject **obj) +{ + MONO_ENTER_GC_UNSAFE; + mono_array_setref (*array, idx, *obj); + MONO_EXIT_GC_UNSAFE; +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_string_array_new_ref (int size, MonoArray **result) { - return mono_array_new (root_domain, mono_get_string_class (), size); + MONO_ENTER_GC_UNSAFE; + mono_gc_wbarrier_generic_store_atomic(result, (MonoObject *)mono_array_new (root_domain, mono_get_string_class (), size)); + MONO_EXIT_GC_UNSAFE; } EMSCRIPTEN_KEEPALIVE int @@ -1108,33 +1230,42 @@ mono_wasm_enable_on_demand_gc (int enable) mono_wasm_enable_gc = enable ? 1 : 0; } -EMSCRIPTEN_KEEPALIVE MonoString * -mono_wasm_intern_string (MonoString *string) +EMSCRIPTEN_KEEPALIVE void +mono_wasm_intern_string_ref (MonoString **string) { - return mono_string_intern (string); + MONO_ENTER_GC_UNSAFE; + mono_gc_wbarrier_generic_store_atomic(string, (MonoObject *)mono_string_intern (*string)); + MONO_EXIT_GC_UNSAFE; } EMSCRIPTEN_KEEPALIVE void -mono_wasm_string_get_data ( - MonoString *string, mono_unichar2 **outChars, int *outLengthBytes, int *outIsInterned +mono_wasm_string_get_data_ref ( + MonoString **string, mono_unichar2 **outChars, int *outLengthBytes, int *outIsInterned ) { - if (!string) { + MONO_ENTER_GC_UNSAFE; + if (!string || !(*string)) { if (outChars) *outChars = 0; if (outLengthBytes) *outLengthBytes = 0; if (outIsInterned) *outIsInterned = 1; - return; + } else { + if (outChars) + *outChars = mono_string_chars (*string); + if (outLengthBytes) + *outLengthBytes = mono_string_length (*string) * 2; + if (outIsInterned) + *outIsInterned = mono_string_instance_is_interned (*string); } + MONO_EXIT_GC_UNSAFE; +} - if (outChars) - *outChars = mono_string_chars (string); - if (outLengthBytes) - *outLengthBytes = mono_string_length (string) * 2; - if (outIsInterned) - *outIsInterned = mono_string_instance_is_interned (string); - return; +EMSCRIPTEN_KEEPALIVE void +mono_wasm_string_get_data ( + MonoString *string, mono_unichar2 **outChars, int *outLengthBytes, int *outIsInterned +) { + mono_wasm_string_get_data_ref(&string, outChars, outLengthBytes, outIsInterned); } EMSCRIPTEN_KEEPALIVE MonoType * @@ -1142,7 +1273,11 @@ mono_wasm_class_get_type (MonoClass *klass) { if (!klass) return NULL; - return mono_class_get_type (klass); + MonoType *result; + MONO_ENTER_GC_UNSAFE; + result = mono_class_get_type (klass); + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE MonoClass * @@ -1150,15 +1285,11 @@ mono_wasm_type_get_class (MonoType *type) { if (!type) return NULL; - return mono_type_get_class (type); -} - -EMSCRIPTEN_KEEPALIVE void * -mono_wasm_unbox_rooted (MonoObject *obj) -{ - if (!obj) - return NULL; - return mono_object_unbox (obj); + MonoClass *result; + MONO_ENTER_GC_UNSAFE; + result = mono_type_get_class (type); + MONO_EXIT_GC_UNSAFE; + return result; } EMSCRIPTEN_KEEPALIVE char * @@ -1171,6 +1302,16 @@ mono_wasm_get_type_aqn (MonoType * typePtr) { return mono_type_get_name_full (typePtr, MONO_TYPE_NAME_FORMAT_ASSEMBLY_QUALIFIED); } +EMSCRIPTEN_KEEPALIVE void +mono_wasm_write_managed_pointer_unsafe (PPVOLATILE(MonoObject) destination, PVOLATILE(MonoObject) source) { + store_volatile(destination, source); +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_copy_managed_pointer (PPVOLATILE(MonoObject) destination, PPVOLATILE(MonoObject) source) { + copy_volatile(destination, source); +} + #ifdef ENABLE_AOT_PROFILER void mono_profiler_init_aot (const char *desc); diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 402dd8b15d6..1b8586b0332 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -8,7 +8,7 @@ const DotnetSupportLib = { $DOTNET: {}, // this line will be placed early on emscripten runtime creation, passing import and export objects into __dotnet_runtime IFFE // Emscripten uses require function for nodeJS even in ES6 module. We need https://nodejs.org/api/module.html#modulecreaterequirefilename - // We use dynamic import because there is no "module" module in the browser. + // We use dynamic import because there is no "module" module in the browser. // This is async init of it, note it would become available only after first tick. // Also fix of scriptDirectory would be delayed // Emscripten's getBinaryPromise is not async for NodeJs, but we would like to have it async, so we replace it. @@ -39,7 +39,7 @@ if (ENVIRONMENT_IS_NODE) { readAsync(wasmBinaryFile, function (response) { resolve(new Uint8Array(/** @type{!ArrayBuffer} */(response))) }, reject) }); } - + } catch (err) { return getBinary(wasmBinaryFile); @@ -49,7 +49,7 @@ if (ENVIRONMENT_IS_NODE) { } } let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( - { isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise }, + { isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise }, { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, __dotnet_replacements); readAsync = __dotnet_replacements.readAsync; @@ -81,22 +81,22 @@ const linked_functions = [ // corebindings.c "mono_wasm_invoke_js_with_args", - "mono_wasm_get_object_property", - "mono_wasm_set_object_property", - "mono_wasm_get_by_index", - "mono_wasm_set_by_index", - "mono_wasm_get_global_object", - "mono_wasm_create_cs_owned_object", + "mono_wasm_get_object_property_ref", + "mono_wasm_set_object_property_ref", + "mono_wasm_get_by_index_ref", + "mono_wasm_set_by_index_ref", + "mono_wasm_get_global_object_ref", + "mono_wasm_create_cs_owned_object_ref", "mono_wasm_release_cs_owned_object", - "mono_wasm_typed_array_to_array", - "mono_wasm_typed_array_copy_to", - "mono_wasm_typed_array_from", - "mono_wasm_typed_array_copy_from", + "mono_wasm_typed_array_to_array_ref", + "mono_wasm_typed_array_copy_to_ref", + "mono_wasm_typed_array_from_ref", + "mono_wasm_typed_array_copy_from_ref", "mono_wasm_cancel_promise", - "mono_wasm_web_socket_open", + "mono_wasm_web_socket_open_ref", "mono_wasm_web_socket_send", "mono_wasm_web_socket_receive", - "mono_wasm_web_socket_close", + "mono_wasm_web_socket_close_ref", "mono_wasm_web_socket_abort", "mono_wasm_compile_function", diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 1c1a5cbee49..37578887dde 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -5,7 +5,7 @@ import ProductVersion from "consts:productVersion"; import Configuration from "consts:configuration"; import { - mono_wasm_new_root, mono_wasm_release_roots, + mono_wasm_new_root, mono_wasm_release_roots, mono_wasm_new_external_root, mono_wasm_new_root_buffer } from "./roots"; import { @@ -38,24 +38,24 @@ import { } from "./startup"; import { mono_set_timeout, schedule_background_exec } from "./scheduling"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; -import { conv_string, js_string_to_mono_string, mono_intern_string } from "./strings"; -import { js_to_mono_obj, js_typed_array_to_array, mono_wasm_typed_array_to_array } from "./js-to-cs"; +import { conv_string, conv_string_root, js_string_to_mono_string, js_string_to_mono_string_root, mono_intern_string } from "./strings"; +import { js_to_mono_obj, js_typed_array_to_array, mono_wasm_typed_array_to_array_ref, js_to_mono_obj_root, js_typed_array_to_array_root } from "./js-to-cs"; import { - mono_array_to_js_array, mono_wasm_create_cs_owned_object, unbox_mono_obj + mono_array_to_js_array, mono_wasm_create_cs_owned_object_ref, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array } from "./cs-to-js"; import { call_static_method, mono_bind_static_method, mono_call_assembly_entry_point, mono_method_resolve, mono_wasm_compile_function, - mono_wasm_get_by_index, mono_wasm_get_global_object, mono_wasm_get_object_property, + mono_wasm_get_by_index_ref, mono_wasm_get_global_object_ref, mono_wasm_get_object_property_ref, mono_wasm_invoke_js, mono_wasm_invoke_js_blazor, - mono_wasm_invoke_js_with_args, mono_wasm_set_by_index, mono_wasm_set_object_property + mono_wasm_invoke_js_with_args, mono_wasm_set_by_index_ref, mono_wasm_set_object_property_ref } from "./method-calls"; -import { mono_wasm_typed_array_copy_to, mono_wasm_typed_array_from, mono_wasm_typed_array_copy_from, mono_wasm_load_bytes_into_heap } from "./buffers"; +import { mono_wasm_typed_array_copy_to_ref, mono_wasm_typed_array_from_ref, mono_wasm_typed_array_copy_from_ref, mono_wasm_load_bytes_into_heap } from "./buffers"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; -import { mono_wasm_web_socket_open, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close, mono_wasm_web_socket_abort } from "./web-socket"; +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 cwraps from "./cwraps"; import { setI8, setI16, setI32, setI64, @@ -79,6 +79,7 @@ const MONO = { mono_load_runtime_and_bcl_args, mono_wasm_new_root_buffer, mono_wasm_new_root, + mono_wasm_new_external_root, mono_wasm_release_roots, mono_run_main, mono_run_main_and_exit, @@ -114,16 +115,54 @@ export type MONOType = typeof MONO; const BINDING = { //current "public" BINDING API + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_new: cwraps.mono_wasm_obj_array_new, + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_set: cwraps.mono_wasm_obj_array_set, + /** + * @deprecated Not GC or thread safe + */ js_string_to_mono_string, + /** + * @deprecated Not GC or thread safe + */ js_typed_array_to_array, - js_to_mono_obj, + /** + * @deprecated Not GC or thread safe + */ mono_array_to_js_array, + /** + * @deprecated Not GC or thread safe + */ + js_to_mono_obj, + /** + * @deprecated Not GC or thread safe + */ conv_string, + /** + * @deprecated Not GC or thread safe + */ + unbox_mono_obj, + /** + * @deprecated Renamed to conv_string_root + */ + conv_string_rooted: conv_string_root, + + mono_obj_array_new_ref: cwraps.mono_wasm_obj_array_new_ref, + mono_obj_array_set_ref: cwraps.mono_wasm_obj_array_set_ref, + js_string_to_mono_string_root, + js_typed_array_to_array_root, + js_to_mono_obj_root, + conv_string_root, + unbox_mono_obj_root, + mono_array_root_to_js_array, + bind_static_method: mono_bind_static_method, call_assembly_entry_point: mono_call_assembly_entry_point, - unbox_mono_obj, }; export type BINDINGType = typeof BINDING; @@ -281,22 +320,22 @@ export const __linker_exports: any = { // also keep in sync with corebindings.c mono_wasm_invoke_js_with_args, - mono_wasm_get_object_property, - mono_wasm_set_object_property, - mono_wasm_get_by_index, - mono_wasm_set_by_index, - mono_wasm_get_global_object, - mono_wasm_create_cs_owned_object, + mono_wasm_get_object_property_ref, + mono_wasm_set_object_property_ref, + mono_wasm_get_by_index_ref, + mono_wasm_set_by_index_ref, + mono_wasm_get_global_object_ref, + mono_wasm_create_cs_owned_object_ref, mono_wasm_release_cs_owned_object, - mono_wasm_typed_array_to_array, - mono_wasm_typed_array_copy_to, - mono_wasm_typed_array_from, - mono_wasm_typed_array_copy_from, + mono_wasm_typed_array_to_array_ref, + mono_wasm_typed_array_copy_to_ref, + mono_wasm_typed_array_from_ref, + mono_wasm_typed_array_copy_from_ref, mono_wasm_cancel_promise, - mono_wasm_web_socket_open, + mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, - mono_wasm_web_socket_close, + mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort, mono_wasm_compile_function, diff --git a/src/mono/wasm/runtime/gc-common.h b/src/mono/wasm/runtime/gc-common.h new file mode 100644 index 00000000000..ce7235fd773 --- /dev/null +++ b/src/mono/wasm/runtime/gc-common.h @@ -0,0 +1,49 @@ +#define PVOLATILE(T) T* volatile +#define PPVOLATILE(T) T* volatile * + +#define gpointer void* + +MONO_API MONO_RT_EXTERNAL_ONLY gpointer +mono_threads_enter_gc_unsafe_region (gpointer* stackdata); + +MONO_API MONO_RT_EXTERNAL_ONLY void +mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer* stackdata); + +MONO_API MONO_RT_EXTERNAL_ONLY void +mono_threads_assert_gc_unsafe_region (void); + +MONO_API MONO_RT_EXTERNAL_ONLY gpointer +mono_threads_enter_gc_safe_region (gpointer *stackdata); + +MONO_API MONO_RT_EXTERNAL_ONLY void +mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata); + +MONO_API void +mono_threads_assert_gc_safe_region (void); +#define MONO_ENTER_GC_UNSAFE \ + do { \ + gpointer __dummy; \ + gpointer __gc_unsafe_cookie = mono_threads_enter_gc_unsafe_region (&__dummy) \ + +#define MONO_EXIT_GC_UNSAFE \ + mono_threads_exit_gc_unsafe_region (__gc_unsafe_cookie, &__dummy); \ + } while (0) + +#define MONO_ENTER_GC_SAFE \ + do { \ + gpointer __dummy; \ + gpointer __gc_safe_cookie = mono_threads_enter_gc_safe_region (&__dummy) \ + +#define MONO_EXIT_GC_SAFE \ + mono_threads_exit_gc_safe_region (__gc_safe_cookie, &__dummy); \ + } while (0) + +static void +store_volatile (PPVOLATILE(MonoObject) destination, PVOLATILE(MonoObject) source) { + mono_gc_wbarrier_generic_store_atomic((void*)destination, (MonoObject*)source); +} + +static void +copy_volatile (PPVOLATILE(MonoObject) destination, PPVOLATILE(MonoObject) source) { + mono_gc_wbarrier_generic_store_atomic((void*)destination, (MonoObject*)(*source)); +} diff --git a/src/mono/wasm/runtime/gc-handles.ts b/src/mono/wasm/runtime/gc-handles.ts index 18e8462021a..8ee1cb3fcc2 100644 --- a/src/mono/wasm/runtime/gc-handles.ts +++ b/src/mono/wasm/runtime/gc-handles.ts @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import corebindings from "./corebindings"; -import { GCHandle, JSHandle, JSHandleDisposed, JSHandleNull, MonoObject, MonoObjectNull } from "./types"; +import { GCHandle, JSHandle, JSHandleDisposed, JSHandleNull, MonoObjectRef } from "./types"; +import { setU32 } from "./memory"; import { create_weak_ref } from "./weak-ref"; export const _use_finalization_registry = typeof globalThis.FinalizationRegistry === "function"; @@ -24,12 +25,13 @@ export const js_owned_gc_handle_symbol = Symbol.for("wasm js_owned_gc_handle"); export const cs_owned_js_handle_symbol = Symbol.for("wasm cs_owned_js_handle"); -export function get_js_owned_object_by_gc_handle(gc_handle: GCHandle): MonoObject { +export function get_js_owned_object_by_gc_handle_ref(gc_handle: GCHandle, result: MonoObjectRef): void { if (!gc_handle) { - return MonoObjectNull; + setU32(result, 0); + return; } // this is always strong gc_handle - return corebindings._get_js_owned_object_by_gc_handle(gc_handle); + corebindings._get_js_owned_object_by_gc_handle_ref(gc_handle, result); } export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any { @@ -40,11 +42,12 @@ export function mono_wasm_get_jsobj_from_js_handle(js_handle: JSHandle): any { // when should_add_in_flight === true, the JSObject would be temporarily hold by Normal gc_handle, so that it would not get collected during transition to the managed stack. // its InFlight gc_handle would be freed when the instance arrives to managed side via Interop.Runtime.ReleaseInFlight -export function get_cs_owned_object_by_js_handle(js_handle: JSHandle, should_add_in_flight: boolean): MonoObject { +export function get_cs_owned_object_by_js_handle_ref(js_handle: JSHandle, should_add_in_flight: boolean, result: MonoObjectRef): void { if (js_handle === JSHandleNull || js_handle === JSHandleDisposed) { - return MonoObjectNull; + setU32(result, 0); + return; } - return corebindings._get_cs_owned_object_by_js_handle(js_handle, should_add_in_flight ? 1 : 0); + corebindings._get_cs_owned_object_by_js_handle_ref(js_handle, should_add_in_flight ? 1 : 0, result); } export function get_js_obj(js_handle: JSHandle): any { @@ -94,13 +97,13 @@ export function mono_wasm_get_js_handle(js_obj: any): JSHandle { return js_handle as JSHandle; } -export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): any { +export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void { const obj = _cs_owned_objects_by_js_handle[<any>js_handle]; if (typeof obj !== "undefined" && obj !== null) { // if this is the global object then do not // unregister it. if (globalThis === obj) - return obj; + return; if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") { obj[cs_owned_js_handle_symbol] = undefined; @@ -109,5 +112,4 @@ export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): any { _cs_owned_objects_by_js_handle[<any>js_handle] = undefined; _js_handle_free_list.push(js_handle); } - return obj; } diff --git a/src/mono/wasm/runtime/js-to-cs.ts b/src/mono/wasm/runtime/js-to-cs.ts index 8bafaec669c..6901c5bacf7 100644 --- a/src/mono/wasm/runtime/js-to-cs.ts +++ b/src/mono/wasm/runtime/js-to-cs.ts @@ -3,131 +3,148 @@ import { Module, runtimeHelpers } from "./imports"; import { - cs_owned_js_handle_symbol, get_cs_owned_object_by_js_handle, get_js_owned_object_by_gc_handle, js_owned_gc_handle_symbol, + cs_owned_js_handle_symbol, get_cs_owned_object_by_js_handle_ref, + 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, mono_wasm_release_cs_owned_object, _js_owned_object_registry, _use_finalization_registry } from "./gc-handles"; import corebindings from "./corebindings"; import cwraps from "./cwraps"; -import { mono_wasm_new_root, mono_wasm_release_roots } from "./roots"; -import { wrap_error } from "./method-calls"; -import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings"; +import { mono_wasm_new_root, mono_wasm_release_roots, WasmRoot, mono_wasm_new_external_root } from "./roots"; +import { wrap_error_root } from "./method-calls"; +import { js_string_to_mono_string_root, js_string_to_mono_string_interned_root } from "./strings"; import { isThenable } from "./cancelable-promise"; import { has_backing_array_buffer } from "./buffers"; -import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, wasm_type_symbol } from "./types"; +import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, wasm_type_symbol, MonoClass, MonoObjectRef } from "./types"; import { setI32, setU32, setF64 } from "./memory"; import { Int32Ptr, TypedArray } from "./types/emscripten"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function _js_to_mono_uri(should_add_in_flight: boolean, js_obj: any): MonoObject { +export function _js_to_mono_uri_root(should_add_in_flight: boolean, js_obj: any, result: WasmRoot<MonoObject>): void { switch (true) { case js_obj === null: case typeof js_obj === "undefined": - return MonoObjectNull; + result.clear(); + return; case typeof js_obj === "symbol": case typeof js_obj === "string": - return corebindings._create_uri(js_obj); + corebindings._create_uri_ref(js_obj, result.address); + return; default: - return _extract_mono_obj(should_add_in_flight, js_obj); + _extract_mono_obj_root(should_add_in_flight, js_obj, result); + return; } } // this is only used from Blazor +/** + * @deprecated Not GC or thread safe. For blazor use only + */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function js_to_mono_obj(js_obj: any): MonoObject { - return _js_to_mono_obj(false, js_obj); + const temp = mono_wasm_new_root<MonoObject>(); + try { + js_to_mono_obj_root(js_obj, temp, false); + return temp.value; + } finally { + temp.release(); + } +} + +/** + * @deprecated Not GC or thread safe + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function _js_to_mono_obj_unsafe(should_add_in_flight: boolean, js_obj: any): MonoObject { + const temp = mono_wasm_new_root<MonoObject>(); + try { + js_to_mono_obj_root(js_obj, temp, should_add_in_flight); + return temp.value; + } finally { + temp.release(); + } } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function _js_to_mono_obj(should_add_in_flight: boolean, js_obj: any): MonoObject { +export function js_to_mono_obj_root(js_obj: any, result: WasmRoot<MonoObject>, should_add_in_flight: boolean): void { switch (true) { case js_obj === null: case typeof js_obj === "undefined": - return MonoObjectNull; + result.clear(); + return; case typeof js_obj === "number": { - let result = null; - if ((js_obj | 0) === js_obj) - result = _box_js_int(js_obj); - else if ((js_obj >>> 0) === js_obj) - result = _box_js_uint(js_obj); - else - result = _box_js_double(js_obj); - - if (!result) - throw new Error(`Boxing failed for ${js_obj}`); - - return result; - } case typeof js_obj === "string": - return <any>js_string_to_mono_string(js_obj); + let box_class : MonoClass; + if ((js_obj | 0) === js_obj) { + setI32(runtimeHelpers._box_buffer, js_obj); + box_class = runtimeHelpers._class_int32; + } else if ((js_obj >>> 0) === js_obj) { + setU32(runtimeHelpers._box_buffer, js_obj); + box_class = runtimeHelpers._class_uint32; + } else { + setF64(runtimeHelpers._box_buffer, js_obj); + box_class = runtimeHelpers._class_double; + } + + cwraps.mono_wasm_box_primitive_ref(box_class, runtimeHelpers._box_buffer, 8, result.address); + return; + } + case typeof js_obj === "string": + js_string_to_mono_string_root(js_obj, <any>result); + return; case typeof js_obj === "symbol": - return <any>js_string_to_mono_string_interned(js_obj); + js_string_to_mono_string_interned_root(js_obj, <any>result); + return; case typeof js_obj === "boolean": - return _box_js_bool(js_obj); + setI32(runtimeHelpers._box_buffer, js_obj ? 1 : 0); + cwraps.mono_wasm_box_primitive_ref(runtimeHelpers._class_boolean, runtimeHelpers._box_buffer, 4, result.address); + return; case isThenable(js_obj) === true: { - const { task_ptr } = _wrap_js_thenable_as_task(js_obj); - // task_ptr above is not rooted, we need to return it to mono without any intermediate mono call which could cause GC - return task_ptr; + _wrap_js_thenable_as_task_root(js_obj, result); + return; } case js_obj.constructor.name === "Date": // getTime() is always UTC - return corebindings._create_date_time(js_obj.getTime()); + corebindings._create_date_time_ref(js_obj.getTime(), result.address); + return; default: - return _extract_mono_obj(should_add_in_flight, js_obj); + _extract_mono_obj_root(should_add_in_flight, js_obj, result); + return; } } -function _extract_mono_obj(should_add_in_flight: boolean, js_obj: any): MonoObject { +function _extract_mono_obj_root(should_add_in_flight: boolean, js_obj: any, result: WasmRoot<MonoObject>): void { + result.clear(); + if (js_obj === null || typeof js_obj === "undefined") - return MonoObjectNull; + return; - let result = null; if (js_obj[js_owned_gc_handle_symbol]) { // for js_owned_gc_handle we don't want to create new proxy // since this is strong gc_handle we don't need to in-flight reference - result = get_js_owned_object_by_gc_handle(js_obj[js_owned_gc_handle_symbol]); - return result; + get_js_owned_object_by_gc_handle_ref(js_obj[js_owned_gc_handle_symbol], result.address); + return; } if (js_obj[cs_owned_js_handle_symbol]) { - result = get_cs_owned_object_by_js_handle(js_obj[cs_owned_js_handle_symbol], should_add_in_flight); + get_cs_owned_object_by_js_handle_ref(js_obj[cs_owned_js_handle_symbol], should_add_in_flight, result.address); // It's possible the managed object corresponding to this JS object was collected, // in which case we need to make a new one. - if (!result) { + // FIXME: This check is not thread safe + if (!result.value) { delete js_obj[cs_owned_js_handle_symbol]; } } - if (!result) { + // FIXME: This check is not thread safe + if (!result.value) { // Obtain the JS -> C# type mapping. const wasm_type = js_obj[wasm_type_symbol]; const wasm_type_id = typeof wasm_type === "undefined" ? 0 : wasm_type; const js_handle = mono_wasm_get_js_handle(js_obj); - result = corebindings._create_cs_owned_proxy(js_handle, wasm_type_id, should_add_in_flight ? 1 : 0); + corebindings._create_cs_owned_proxy_ref(js_handle, wasm_type_id, should_add_in_flight ? 1 : 0, result.address); } - - return result; -} - -function _box_js_int(js_obj: number) { - setI32(runtimeHelpers._box_buffer, js_obj); - return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_int32, runtimeHelpers._box_buffer, 4); -} - -function _box_js_uint(js_obj: number) { - setU32(runtimeHelpers._box_buffer, js_obj); - return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_uint32, runtimeHelpers._box_buffer, 4); -} - -function _box_js_double(js_obj: number) { - setF64(runtimeHelpers._box_buffer, js_obj); - return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_double, runtimeHelpers._box_buffer, 8); -} - -export function _box_js_bool(js_obj: boolean): MonoObject { - setI32(runtimeHelpers._box_buffer, js_obj ? 1 : 0); - return cwraps.mono_wasm_box_primitive(runtimeHelpers._class_boolean, runtimeHelpers._box_buffer, 4); } // https://github.com/Planeshifter/emscripten-examples/blob/master/01_PassingArrays/sum_post.js @@ -140,7 +157,7 @@ function js_typedarray_to_heap(typedArray: TypedArray) { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function js_typed_array_to_array(js_obj: any): MonoArray { +export function js_typed_array_to_array_root(js_obj: any, result: WasmRoot<MonoArray>): void { // JavaScript typed arrays are array-like objects and provide a mechanism for accessing // raw binary data. (...) To achieve maximum flexibility and efficiency, JavaScript typed arrays // split the implementation into buffers and views. A buffer (implemented by the ArrayBuffer object) @@ -152,15 +169,28 @@ export function js_typed_array_to_array(js_obj: any): MonoArray { if (has_backing_array_buffer(js_obj) && js_obj.BYTES_PER_ELEMENT) { const arrayType = js_obj[wasm_type_symbol]; const heapBytes = js_typedarray_to_heap(js_obj); - const bufferArray = cwraps.mono_wasm_typed_array_new(<any>heapBytes.byteOffset, js_obj.length, js_obj.BYTES_PER_ELEMENT, arrayType); + cwraps.mono_wasm_typed_array_new_ref(<any>heapBytes.byteOffset, js_obj.length, js_obj.BYTES_PER_ELEMENT, arrayType, result.address); Module._free(<any>heapBytes.byteOffset); - return bufferArray; } else { throw new Error("Object '" + js_obj + "' is not a typed array"); } } +/** + * @deprecated Not GC or thread safe + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function js_typed_array_to_array(js_obj: any): MonoArray { + const temp = mono_wasm_new_root<MonoArray>(); + try { + js_typed_array_to_array_root(js_obj, temp); + return temp.value; + } finally { + temp.release(); + } +} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types export function js_to_mono_enum(js_obj: any, method: MonoMethod, parmIdx: number): number { if (typeof (js_obj) !== "number") @@ -170,9 +200,14 @@ export function js_to_mono_enum(js_obj: any, method: MonoMethod, parmIdx: number } export function js_array_to_mono_array(js_array: any[], asString: boolean, should_add_in_flight: boolean): MonoArray { - const mono_array = asString ? cwraps.mono_wasm_string_array_new(js_array.length) : cwraps.mono_wasm_obj_array_new(js_array.length); - const arrayRoot = mono_wasm_new_root(mono_array); + const arrayRoot = mono_wasm_new_root<MonoArray>(); + if (asString) + cwraps.mono_wasm_string_array_new_ref(js_array.length, arrayRoot.address); + else + cwraps.mono_wasm_obj_array_new_ref(js_array.length, arrayRoot.address); const elemRoot = mono_wasm_new_root(MonoObjectNull); + const arrayAddress = arrayRoot.address; + const elemAddress = elemRoot.address; try { for (let i = 0; i < js_array.length; ++i) { @@ -180,35 +215,34 @@ export function js_array_to_mono_array(js_array: any[], asString: boolean, shoul if (asString) obj = obj.toString(); - elemRoot.value = _js_to_mono_obj(should_add_in_flight, obj); - cwraps.mono_wasm_obj_array_set(arrayRoot.value, i, elemRoot.value); + js_to_mono_obj_root(obj, elemRoot, should_add_in_flight); + cwraps.mono_wasm_obj_array_set_ref(arrayAddress, i, elemAddress); } - return mono_array; + return arrayRoot.value; } finally { mono_wasm_release_roots(arrayRoot, elemRoot); } } -export function _wrap_js_thenable_as_task(thenable: Promise<any>): { - task_ptr: MonoObject, +export function _wrap_js_thenable_as_task_root(thenable: Promise<any>, resultRoot: WasmRoot<MonoObject>): { then_js_handle: JSHandle, - } { - - if (!thenable) + if (!thenable) { + resultRoot.clear(); return <any>null; + } // hold strong JS reference to thenable while in flight // ideally, this should be hold alive by lifespan of the resulting C# Task, but this is good cheap aproximation const thenable_js_handle = mono_wasm_get_js_handle(thenable); - // Note that we do not implement promise/task roundtrip. + // Note that we do not implement promise/task roundtrip. // With more complexity we could recover original instance when this Task is marshaled back to JS. // TODO optimization: return the tcs.Task on this same call instead of _get_tcs_task const tcs_gc_handle = corebindings._create_tcs(); thenable.then((result) => { - corebindings._set_tcs_result(tcs_gc_handle, result); + corebindings._set_tcs_result_ref(tcs_gc_handle, result); // let go of the thenable reference mono_wasm_release_cs_owned_object(thenable_js_handle); @@ -232,19 +266,29 @@ export function _wrap_js_thenable_as_task(thenable: Promise<any>): { _js_owned_object_registry.register(thenable, tcs_gc_handle); } + corebindings._get_tcs_task_ref(tcs_gc_handle, resultRoot.address); + // returns raw pointer to tcs.Task return { - task_ptr: corebindings._get_tcs_task(tcs_gc_handle), then_js_handle: thenable_js_handle, }; } -export function mono_wasm_typed_array_to_array(js_handle: JSHandle, is_exception: Int32Ptr): MonoArray | MonoString { - const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!js_obj) { - return wrap_error(is_exception, "ERR06: Invalid JS object handle '" + js_handle + "'"); - } +export function mono_wasm_typed_array_to_array_ref(js_handle: JSHandle, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); + try { + const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + if (!js_obj) { + wrap_error_root(is_exception, "ERR06: Invalid JS object handle '" + js_handle + "'", resultRoot); + return; + } - // returns pointer to C# array - return js_typed_array_to_array(js_obj); + // returns pointer to C# array + // FIXME: ref + resultRoot.value = js_typed_array_to_array(js_obj); + } catch (exc) { + wrap_error_root(is_exception, String(exc), resultRoot); + } finally { + resultRoot.release(); + } } diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index c8760a0c7a9..b61b379e87e 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -37,7 +37,7 @@ export function _release_temp_frame(): void { alloca_offset = <VoidPtr>alloca_stack.pop(); } -type _MemOffset = number | VoidPtr | NativePointer; +type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer; type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; export function setU8(offset: _MemOffset, value: number): void { diff --git a/src/mono/wasm/runtime/method-binding.ts b/src/mono/wasm/runtime/method-binding.ts index c7de40542e0..6dc3daec9f7 100644 --- a/src/mono/wasm/runtime/method-binding.ts +++ b/src/mono/wasm/runtime/method-binding.ts @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. import { WasmRoot, WasmRootBuffer, mono_wasm_new_root } from "./roots"; -import { MonoClass, MonoMethod, MonoObject, coerceNull, VoidPtrNull, MonoType, MarshalType } from "./types"; +import { MonoClass, MonoMethod, MonoObject, VoidPtrNull, MonoType, MarshalType } from "./types"; import { BINDING, Module, runtimeHelpers } from "./imports"; -import { js_to_mono_enum, _js_to_mono_obj, _js_to_mono_uri } from "./js-to-cs"; -import { js_string_to_mono_string, js_string_to_mono_string_interned } from "./strings"; +import { js_to_mono_enum, js_to_mono_obj_root, _js_to_mono_uri_root } from "./js-to-cs"; +import { js_string_to_mono_string_root, js_string_to_mono_string_interned_root } from "./strings"; import { _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js"; import { _create_temp_frame, @@ -117,13 +117,15 @@ export function _create_rebindable_named_function(name: string, argumentNames: s export function _create_primitive_converters(): void { const result = primitiveConverters; result.set("m", { steps: [{}], size: 0 }); - result.set("s", { steps: [{ convert: js_string_to_mono_string.bind(BINDING) }], size: 0, needs_root: true }); - result.set("S", { steps: [{ convert: js_string_to_mono_string_interned.bind(BINDING) }], size: 0, needs_root: true }); - // note we also bind first argument to false for both _js_to_mono_obj and _js_to_mono_uri, + result.set("s", { steps: [{ convert_root: js_string_to_mono_string_root.bind(BINDING) }], size: 0, needs_root: true }); + result.set("S", { steps: [{ convert_root: js_string_to_mono_string_interned_root.bind(BINDING) }], size: 0, needs_root: true }); + // note we also bind first argument to false for both _js_to_mono_obj and _js_to_mono_uri, // because we will root the reference, so we don't need in-flight reference // also as those are callback arguments and we don't have platform code which would release the in-flight reference on C# end - result.set("o", { steps: [{ convert: _js_to_mono_obj.bind(BINDING, false) }], size: 0, needs_root: true }); - result.set("u", { steps: [{ convert: _js_to_mono_uri.bind(BINDING, false) }], size: 0, needs_root: true }); + result.set("o", { steps: [{ convert_root: js_to_mono_obj_root.bind(BINDING) }], size: 0, needs_root: true }); + result.set("u", { steps: [{ convert_root: _js_to_mono_uri_root.bind(BINDING, false) }], size: 0, needs_root: true }); + // ref object aka T&& + result.set("R", { steps: [{ convert_root: js_to_mono_obj_root.bind(BINDING), byref: true }], size: 0, needs_root: true }); // result.set ('k', { steps: [{ convert: js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8}); result.set("j", { steps: [{ convert: js_to_mono_enum.bind(BINDING), indirect: "i32" }], size: 8 }); @@ -212,12 +214,12 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args const closure: any = { Module, _malloc: Module._malloc, - mono_wasm_unbox_rooted: wrap_c_function("mono_wasm_unbox_rooted"), setI32, setU32, setF32, setF64, - setI64 + setI64, + scratchValueRoot: converter.scratchValueRoot }; let indirectLocalOffset = 0; @@ -236,25 +238,40 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args const argKey = "arg" + i; argumentNames.push(argKey); - if (step.convert) { + if (step.convert_root) { + body.push("if (!rootBuffer) throw new Error('no root buffer provided');"); + // FIXME: Optimize this!!! + if (!converter.scratchValueRoot) + closure.scratchValueRoot = converter.scratchValueRoot = mono_wasm_new_root<MonoObject>(); + + closure[closureKey] = step.convert_root; + // Convert the object and store the managed reference in our scratch root + body.push(`${closureKey}(${argKey}, scratchValueRoot);`); + // Next, copy that managed reference into the arguments root buffer. This is its new permanent home + // FIXME: It would be ideal if we could skip this step, perhaps by having an external root point into the arguments root buffer + body.push(`let address${i} = rootBuffer.get_address(${i});`); + body.push(`scratchValueRoot.copy_to_address(address${i});`); + // Now that it's copied into the root buffer we can either pass the address of that root to the callee, or, + // if we're feeling particularly GC unsafe and thread hazardous, pass the managed pointer directly. + if (step.byref) { + body.push(`let ${valueKey} = address${i};`); + } else { + // FIXME: This is not GC safe! The object could move between now and the method invocation, even though we have + // prevented it from being GCed by storing the pointer into a root buffer. + body.push(`let ${valueKey} = scratchValueRoot.value;`); + } + } else if (step.convert) { closure[closureKey] = step.convert; body.push(`let ${valueKey} = ${closureKey}(${argKey}, method, ${i});`); } else { body.push(`let ${valueKey} = ${argKey};`); } - if (step.needs_root) { + if (step.needs_root && !step.convert_root) { body.push("if (!rootBuffer) throw new Error('no root buffer provided');"); body.push(`rootBuffer.set (${i}, ${valueKey});`); } - // HACK: needs_unbox indicates that we were passed a pointer to a managed object, and either - // it was already rooted by our caller or (needs_root = true) by us. Now we can unbox it and - // pass the raw address of its boxed value into the callee. - // FIXME: I don't think this is GC safe - if (step.needs_unbox) - body.push(`${valueKey} = mono_wasm_unbox_rooted (${valueKey});`); - if (step.indirect) { const offsetText = `(indirectStart + ${indirectLocalOffset})`; @@ -281,7 +298,7 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args body.push(`setU32(buffer + (${i} * 4), ${offsetText});`); indirectLocalOffset += step.size!; } else { - body.push(`setI32(buffer + (${i} * 4), ${valueKey});`); + body.push(`setU32(buffer + (${i} * 4), ${valueKey});`); indirectLocalOffset += 4; } body.push(""); @@ -367,10 +384,9 @@ export function _decide_if_result_is_marshaled(converter: Converter, argc: numbe } } -export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null, args_marshal: string/*ArgsMarshalString*/, friendly_name: string): Function { +export function mono_bind_method(method: MonoMethod, this_arg: null, args_marshal: string/*ArgsMarshalString*/, friendly_name: string): Function { if (typeof (args_marshal) !== "string") throw new Error("args_marshal argument invalid, expected string"); - this_arg = coerceNull(this_arg); let converter: Converter | null = null; if (typeof (args_marshal) === "string") { @@ -398,11 +414,10 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null _get_buffer_for_method_call, _handle_exception_for_call, _teardown_after_call, - mono_wasm_try_unbox_primitive_and_get_type: wrap_c_function("mono_wasm_try_unbox_primitive_and_get_type"), + mono_wasm_try_unbox_primitive_and_get_type_ref: wrap_c_function("mono_wasm_try_unbox_primitive_and_get_type_ref"), _unbox_mono_obj_root_with_known_nonprimitive_type, - invoke_method: wrap_c_function("mono_wasm_invoke_method"), + invoke_method_ref: wrap_c_function("mono_wasm_invoke_method_ref"), method, - this_arg, token, unbox_buffer, unbox_buffer_size, @@ -475,7 +490,7 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null // The end result is that bound method invocations don't always allocate, so no more nursery GCs. Yay! -kg body.push( "", - "resultRoot.value = invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());", + "invoke_method_ref (method, 0, buffer, exceptionRoot.address, resultRoot.address);", `_handle_exception_for_call (${converterKey}, token, buffer, resultRoot, exceptionRoot, argsRootBuffer);`, "", "let resultPtr = resultRoot.value, result = undefined;" @@ -490,12 +505,12 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null if (!converter.is_result_definitely_unmarshaled) body.push( - "if (is_result_marshaled && (resultPtr !== 0)) {", + "if (is_result_marshaled) {", // For the common scenario where the return type is a primitive, we want to try and unbox it directly // into our existing heap allocation and then read it out of the heap. Doing this all in one operation // means that we only need to enter a gc safe region twice (instead of 3+ times with the normal, // slower check-type-and-then-unbox flow which has extra checks since unbox verifies the type). - " let resultType = mono_wasm_try_unbox_primitive_and_get_type (resultPtr, unbox_buffer, unbox_buffer_size);", + " let resultType = mono_wasm_try_unbox_primitive_and_get_type_ref (resultRoot.address, unbox_buffer, unbox_buffer_size);", " switch (resultType) {", ` case ${MarshalType.INT}:`, " result = getI32(unbox_buffer); break;", @@ -510,6 +525,8 @@ export function mono_bind_method(method: MonoMethod, this_arg: MonoObject | null " result = getI32(unbox_buffer) !== 0; break;", ` case ${MarshalType.CHAR}:`, " result = String.fromCharCode(getI32(unbox_buffer)); break;", + ` case ${MarshalType.NULL}:`, + " result = null; break;", " default:", " result = _unbox_mono_obj_root_with_known_nonprimitive_type (resultRoot, resultType, unbox_buffer); break;", " }", @@ -567,13 +584,16 @@ export type ArgsMarshalString = "" | `${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}`; */ -type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "i64" +type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "i64" | "reference" export type Converter = { steps: { + // (value: any, method: MonoMethod, arg_index: int) convert?: boolean | Function; + // (value: any, result_root: WasmRoot<MonoObject>) + convert_root?: Function; needs_root?: boolean; - needs_unbox?: boolean; + byref?: boolean; indirect?: ConverterStepIndirects; size?: number; }[]; @@ -586,11 +606,11 @@ export type Converter = { key?: string; name?: string; needs_root?: boolean; - needs_unbox?: boolean; compiled_variadic_function?: Function | null; compiled_function?: Function | null; scratchRootBuffer?: WasmRootBuffer | null; scratchBuffer?: VoidPtr; + scratchValueRoot?: WasmRoot<MonoObject>; has_warned_about_signature?: boolean; convert?: Function | null; method?: MonoMethod | null; diff --git a/src/mono/wasm/runtime/method-calls.ts b/src/mono/wasm/runtime/method-calls.ts index b5aa2bb2f3c..52e9d74afee 100644 --- a/src/mono/wasm/runtime/method-calls.ts +++ b/src/mono/wasm/runtime/method-calls.ts @@ -1,23 +1,26 @@ // 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_root_buffer, WasmRoot, WasmRootBuffer } from "./roots"; +import { mono_wasm_new_root, mono_wasm_new_root_buffer, WasmRoot, WasmRootBuffer, mono_wasm_new_external_root } from "./roots"; import { JSHandle, MonoArray, MonoMethod, MonoObject, MonoObjectNull, MonoString, coerceNull as coerceNull, - VoidPtrNull, MonoStringNull + VoidPtrNull, MonoStringNull, MonoObjectRef, + MonoStringRef } from "./types"; import { BINDING, INTERNAL, Module, MONO, runtimeHelpers } from "./imports"; -import { _mono_array_root_to_js_array, _unbox_mono_obj_root } from "./cs-to-js"; +import { mono_array_root_to_js_array, unbox_mono_obj_root } from "./cs-to-js"; import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; -import { js_array_to_mono_array, _box_js_bool, _js_to_mono_obj } from "./js-to-cs"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore used by unsafe export +import { js_array_to_mono_array, _js_to_mono_obj_unsafe, js_to_mono_obj_root } from "./js-to-cs"; import { mono_bind_method, Converter, _compile_converter_for_marshal_string, _decide_if_result_is_marshaled, find_method, BoundMethodToken } from "./method-binding"; -import { conv_string, js_string_to_mono_string } from "./strings"; +import { conv_string, conv_string_root, js_string_to_mono_string, js_string_to_mono_string_root } from "./strings"; import cwraps from "./cwraps"; import { bindings_lazy_init } from "./startup"; import { _create_temp_frame, _release_temp_frame } from "./memory"; @@ -113,17 +116,16 @@ function _release_buffer_from_method_call( Module._free(buffer); } -function _convert_exception_for_method_call(result: MonoString, exception: MonoObject) { - if (exception === MonoObjectNull) +function _convert_exception_for_method_call(result: WasmRoot<MonoString>, exception: WasmRoot<MonoObject>) { + if (exception.value === MonoObjectNull) return null; - const msg = conv_string(result); + const msg = conv_string_root(result); const err = new Error(msg!); //the convention is that invoke_method ToString () any outgoing exception // console.warn (`error ${msg} at location ${err.stack}); return err; } - /* args_marshal is a string with one character per parameter that tells how to marshal it, here are the valid values: @@ -140,15 +142,23 @@ m: raw mono object. Don't use it unless you know what you're doing to suppress marshaling of the return value, place '!' at the end of args_marshal, i.e. 'ii!' instead of 'ii' */ -export function call_method(method: MonoMethod, this_arg: MonoObject | undefined, args_marshal: string/*ArgsMarshalString*/, args: ArrayLike<any>): any { +export function call_method_ref(method: MonoMethod, this_arg: WasmRoot<MonoObject> | MonoObjectRef | undefined, args_marshal: string/*ArgsMarshalString*/, args: ArrayLike<any>): any { // HACK: Sometimes callers pass null or undefined, coerce it to 0 since that's what wasm expects - this_arg = coerceNull(this_arg); + let this_arg_ref : MonoObjectRef | undefined = undefined; + if (typeof (this_arg) === "number") + this_arg_ref = this_arg; + else if (typeof (this_arg) === "object") + this_arg_ref = (<any>this_arg).address; + else + this_arg_ref = <any>coerceNull(this_arg); // Detect someone accidentally passing the wrong type of value to method if (typeof method !== "number") throw new Error(`method must be an address in the native heap, but was '${method}'`); if (!method) throw new Error("no method specified"); + if (typeof (this_arg_ref) !== "number") + throw new Error(`this_arg must be a root instance, the address of a root, or undefined, but was ${this_arg}`); const needs_converter = _verify_args_for_method_call(args_marshal, args); @@ -170,7 +180,7 @@ export function call_method(method: MonoMethod, this_arg: MonoObject | undefined buffer = converter.compiled_variadic_function!(scratchBuffer, argsRootBuffer, method, args); } - return _call_method_with_converted_args(method, this_arg!, converter, null, buffer, is_result_marshaled, argsRootBuffer); + return _call_method_with_converted_args(method, <any>this_arg_ref, converter, null, buffer, is_result_marshaled, argsRootBuffer); } @@ -179,7 +189,7 @@ export function _handle_exception_for_call( buffer: VoidPtr, resultRoot: WasmRoot<MonoString>, exceptionRoot: WasmRoot<MonoObject>, argsRootBuffer?: WasmRootBuffer ): void { - const exc = _convert_exception_for_method_call(resultRoot.value, exceptionRoot.value); + const exc = _convert_exception_for_method_call(resultRoot, exceptionRoot); if (!exc) return; @@ -195,10 +205,12 @@ function _handle_exception_and_produce_result_for_call( ): any { _handle_exception_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer); - let result: any = resultRoot.value; + let result: any; if (is_result_marshaled) - result = _unbox_mono_obj_root(resultRoot); + result = unbox_mono_obj_root(resultRoot); + else + result = resultRoot.value; _teardown_after_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer); return result; @@ -214,14 +226,14 @@ export function _teardown_after_call( _release_buffer_from_method_call(converter, token, buffer); if (resultRoot) { - resultRoot.value = 0; + resultRoot.clear(); if ((token !== null) && (token.scratchResultRoot === null)) token.scratchResultRoot = resultRoot; else resultRoot.release(); } if (exceptionRoot) { - exceptionRoot.value = 0; + exceptionRoot.clear(); if ((token !== null) && (token.scratchExceptionRoot === null)) token.scratchExceptionRoot = exceptionRoot; else @@ -230,12 +242,12 @@ export function _teardown_after_call( } function _call_method_with_converted_args( - method: MonoMethod, this_arg: MonoObject, converter: Converter | undefined, + method: MonoMethod, this_arg_ref: MonoObjectRef, converter: Converter | undefined, token: BoundMethodToken | null, buffer: VoidPtr, is_result_marshaled: boolean, argsRootBuffer?: WasmRootBuffer ): any { const resultRoot = mono_wasm_new_root<MonoString>(), exceptionRoot = mono_wasm_new_root<MonoObject>(); - resultRoot.value = <any>cwraps.mono_wasm_invoke_method(method, this_arg, buffer, <any>exceptionRoot.get_address()); + cwraps.mono_wasm_invoke_method_ref(method, this_arg_ref, buffer, exceptionRoot.address, resultRoot.address); return _handle_exception_and_produce_result_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled); } @@ -245,9 +257,9 @@ export function call_static_method(fqn: string, args: any[], signature: string/* const method = mono_method_resolve(fqn); if (typeof signature === "undefined") - signature = mono_method_get_call_signature(method); + signature = mono_method_get_call_signature_ref(method, undefined); - return call_method(method, undefined, signature, args); + return call_method_ref(method, undefined, signature, args); } export function mono_bind_static_method(fqn: string, signature?: string/*ArgsMarshalString*/): Function { @@ -256,7 +268,7 @@ export function mono_bind_static_method(fqn: string, signature?: string/*ArgsMar const method = mono_method_resolve(fqn); if (typeof signature === "undefined") - signature = mono_method_get_call_signature(method); + signature = mono_method_get_call_signature_ref(method, undefined); return mono_bind_method(method, null, signature!, fqn); } @@ -273,12 +285,12 @@ export function mono_bind_assembly_entry_point(assembly: string, signature?: str throw new Error("Could not find entry point for assembly: " + assembly); if (!signature) - signature = mono_method_get_call_signature(method); + signature = mono_method_get_call_signature_ref(method, undefined); return async function (...args: any[]) { if (args.length > 0 && Array.isArray(args[0])) args[0] = js_array_to_mono_array(args[0], true, false); - return call_method(method, undefined, signature!, args); + return call_method_ref(method, undefined, signature!, args); }; } @@ -292,9 +304,9 @@ export function mono_call_assembly_entry_point(assembly: string, args?: any[], s export function mono_wasm_invoke_js_with_args(js_handle: JSHandle, method_name: MonoString, args: MonoArray, is_exception: Int32Ptr): any { const argsRoot = mono_wasm_new_root(args), nameRoot = mono_wasm_new_root(method_name); try { - const js_name = conv_string(nameRoot.value); + const js_name = conv_string_root(nameRoot); if (!js_name || (typeof (js_name) !== "string")) { - return wrap_error(is_exception, "ERR12: Invalid method name object '" + nameRoot.value + "'"); + return wrap_error(is_exception, "ERR12: Invalid method name object @" + nameRoot.value); } const obj = get_js_obj(js_handle); @@ -302,14 +314,17 @@ export function mono_wasm_invoke_js_with_args(js_handle: JSHandle, method_name: return wrap_error(is_exception, "ERR13: Invalid JS object handle '" + js_handle + "' while invoking '" + js_name + "'"); } - const js_args = _mono_array_root_to_js_array(argsRoot); + const js_args = mono_array_root_to_js_array(argsRoot); try { const m = obj[js_name]; if (typeof m === "undefined") throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'"); const res = m.apply(obj, js_args); - return _js_to_mono_obj(true, res); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore caller is unsafe also + return _js_to_mono_obj_unsafe(true, res); } catch (ex) { return wrap_error(is_exception, ex); } @@ -319,48 +334,53 @@ export function mono_wasm_invoke_js_with_args(js_handle: JSHandle, method_name: } } -export function mono_wasm_get_object_property(js_handle: JSHandle, property_name: MonoString, is_exception: Int32Ptr): any { - const nameRoot = mono_wasm_new_root(property_name); +export function mono_wasm_get_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const nameRoot = mono_wasm_new_external_root<MonoString>(property_name), + resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); try { - const js_name = conv_string(nameRoot.value); + const js_name = conv_string_root(nameRoot); if (!js_name) { - return wrap_error(is_exception, "Invalid property name object '" + nameRoot.value + "'"); + wrap_error_root(is_exception, "Invalid property name object '" + nameRoot.value + "'", resultRoot); + return; } const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); if (!obj) { - return wrap_error(is_exception, "ERR01: Invalid JS object handle '" + js_handle + "' while geting '" + js_name + "'"); + wrap_error_root(is_exception, "ERR01: Invalid JS object handle '" + js_handle + "' while geting '" + js_name + "'", resultRoot); + return; } - try { - const m = obj[js_name]; - - return _js_to_mono_obj(true, m); - } catch (ex) { - return wrap_error(is_exception, ex); - } + const m = obj[js_name]; + js_to_mono_obj_root(m, resultRoot, true); + } catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); } finally { + resultRoot.release(); nameRoot.release(); } } -export function mono_wasm_set_object_property(js_handle: JSHandle, property_name: MonoString, value: MonoObject, createIfNotExist: boolean, hasOwnProperty: boolean, is_exception: Int32Ptr): MonoObject { - const valueRoot = mono_wasm_new_root(value), nameRoot = mono_wasm_new_root(property_name); +export function mono_wasm_set_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, value: MonoObjectRef, createIfNotExist: boolean, hasOwnProperty: boolean, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const valueRoot = mono_wasm_new_external_root<MonoObject>(value), + nameRoot = mono_wasm_new_external_root<MonoString>(property_name), + resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); try { - const property = conv_string(nameRoot.value); + const property = conv_string_root(nameRoot); if (!property) { - return wrap_error(is_exception, "Invalid property name object '" + property_name + "'"); + wrap_error_root(is_exception, "Invalid property name object '" + property_name + "'", resultRoot); + return; } const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); if (!js_obj) { - return wrap_error(is_exception, "ERR02: Invalid JS object handle '" + js_handle + "' while setting '" + property + "'"); + wrap_error_root(is_exception, "ERR02: Invalid JS object handle '" + js_handle + "' while setting '" + property + "'", resultRoot); + return; } let result = false; - const js_value = _unbox_mono_obj_root(valueRoot); + const js_value = unbox_mono_obj_root(valueRoot); if (createIfNotExist) { js_obj[property] = js_value; @@ -369,8 +389,10 @@ export function mono_wasm_set_object_property(js_handle: JSHandle, property_name else { result = false; if (!createIfNotExist) { - if (!Object.prototype.hasOwnProperty.call(js_obj, property)) - return _box_js_bool(false); + if (!Object.prototype.hasOwnProperty.call(js_obj, property)) { + js_to_mono_obj_root(false, resultRoot, false); + return; + } } if (hasOwnProperty === true) { if (Object.prototype.hasOwnProperty.call(js_obj, property)) { @@ -383,52 +405,60 @@ export function mono_wasm_set_object_property(js_handle: JSHandle, property_name result = true; } } - return _box_js_bool(result); + js_to_mono_obj_root(result, resultRoot, false); + } catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); } finally { + resultRoot.release(); nameRoot.release(); valueRoot.release(); } } -export function mono_wasm_get_by_index(js_handle: JSHandle, property_index: number, is_exception: Int32Ptr): MonoObject { - const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!obj) { - return wrap_error(is_exception, "ERR03: Invalid JS object handle '" + js_handle + "' while getting [" + property_index + "]"); - } - +export function mono_wasm_get_by_index_ref(js_handle: JSHandle, property_index: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); try { + const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); + if (!obj) { + wrap_error_root(is_exception, "ERR03: Invalid JS object handle '" + js_handle + "' while getting [" + property_index + "]", resultRoot); + return; + } + const m = obj[property_index]; - return _js_to_mono_obj(true, m); + js_to_mono_obj_root(m, resultRoot, true); } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, resultRoot); + } finally { + resultRoot.release(); } } -export function mono_wasm_set_by_index(js_handle: JSHandle, property_index: number, value: MonoObject, is_exception: Int32Ptr): MonoString | true { - const valueRoot = mono_wasm_new_root(value); +export function mono_wasm_set_by_index_ref(js_handle: JSHandle, property_index: number, value: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const valueRoot = mono_wasm_new_external_root<MonoObject>(value), + resultRoot = mono_wasm_new_external_root<MonoObject>(result_address); try { const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); if (!obj) { - return wrap_error(is_exception, "ERR04: Invalid JS object handle '" + js_handle + "' while setting [" + property_index + "]"); + wrap_error_root(is_exception, "ERR04: Invalid JS object handle '" + js_handle + "' while setting [" + property_index + "]", resultRoot); + return; } - const js_value = _unbox_mono_obj_root(valueRoot); - - try { - obj[property_index] = js_value; - return true;// TODO check - } catch (ex) { - return wrap_error(is_exception, ex); - } + const js_value = unbox_mono_obj_root(valueRoot); + obj[property_index] = js_value; + resultRoot.clear(); + } catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); } finally { + resultRoot.release(); valueRoot.release(); } } -export function mono_wasm_get_global_object(global_name: MonoString, is_exception: Int32Ptr): MonoObject { - const nameRoot = mono_wasm_new_root(global_name); +export function mono_wasm_get_global_object_ref(global_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { + const nameRoot = mono_wasm_new_external_root<MonoString>(global_name), + resultRoot = mono_wasm_new_external_root(result_address); try { - const js_name = conv_string(nameRoot.value); + const js_name = conv_string_root(nameRoot); let globalObj; @@ -441,17 +471,21 @@ export function mono_wasm_get_global_object(global_name: MonoString, is_exceptio // TODO returning null may be useful when probing for browser features if (globalObj === null || typeof globalObj === undefined) { - return wrap_error(is_exception, "Global object '" + js_name + "' not found."); + wrap_error_root(is_exception, "Global object '" + js_name + "' not found.", resultRoot); + return; } - return _js_to_mono_obj(true, globalObj); + js_to_mono_obj_root(globalObj, resultRoot, true); + } catch (ex) { + wrap_error_root(is_exception, ex, resultRoot); } finally { + resultRoot.release(); nameRoot.release(); } } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function wrap_error(is_exception: Int32Ptr | null, ex: any): MonoString { +function _wrap_error_flag(is_exception: Int32Ptr | null, ex: any): string { let res = "unknown exception"; if (ex) { res = ex.toString(); @@ -470,16 +504,29 @@ export function wrap_error(is_exception: Int32Ptr | null, ex: any): MonoString { if (is_exception) { Module.setValue(is_exception, 1, "i32"); } + return res; +} + +/** + * @deprecated Not GC or thread safe + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function wrap_error(is_exception: Int32Ptr | null, ex: any): MonoString { + const res = _wrap_error_flag(is_exception, ex); return js_string_to_mono_string(res)!; } -export function mono_method_get_call_signature(method: MonoMethod, mono_obj?: MonoObject): string/*ArgsMarshalString*/ { - const instanceRoot = mono_wasm_new_root(mono_obj); - try { - return call_method(runtimeHelpers.get_call_sig, undefined, "im", [method, instanceRoot.value]); - } finally { - instanceRoot.release(); - } +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: WasmRoot<MonoObject>): void { + const res = _wrap_error_flag(is_exception, ex); + js_string_to_mono_string_root(res, <any>result); +} + +export function mono_method_get_call_signature_ref(method: MonoMethod, mono_obj?: WasmRoot<MonoObject>): string/*ArgsMarshalString*/ { + return call_method_ref( + runtimeHelpers.get_call_sig_ref, undefined, "im", + [method, mono_obj ? mono_obj.address : runtimeHelpers._null_root.address] + ); } export function parseFQN(fqn: string) @@ -536,8 +583,10 @@ export function mono_wasm_invoke_js_blazor(exceptionMessage: Int32Ptr, callInfo: return blazorExports._internal.invokeJSFromDotNet(callInfo, arg0, arg1, arg2); } catch (ex: any) { const exceptionJsString = ex.message + "\n" + ex.stack; - const exceptionSystemString = cwraps.mono_wasm_string_from_js(exceptionJsString); - Module.setValue(exceptionMessage, <any>exceptionSystemString, "i32"); // *exceptionMessage = exceptionSystemString; + const exceptionRoot = mono_wasm_new_root<MonoString>(); + js_string_to_mono_string_root(exceptionJsString, exceptionRoot); + exceptionRoot.copy_to_address(<any>exceptionMessage); + exceptionRoot.release(); return 0; } } @@ -584,7 +633,9 @@ export function mono_wasm_compile_function(code: MonoString, is_exception: Int32 if (!res || typeof res !== "function") return wrap_error(is_exception, "Code must return an instance of a JavaScript function. Please use `return` statement to return a function."); Module.setValue(is_exception, 0, "i32"); - return _js_to_mono_obj(true, res); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore caller is unsafe also + return _js_to_mono_obj_unsafe(true, res); } catch (ex) { return wrap_error(is_exception, ex); } diff --git a/src/mono/wasm/runtime/roots.ts b/src/mono/wasm/runtime/roots.ts index 2d473fb22ff..9d6ea463acc 100644 --- a/src/mono/wasm/runtime/roots.ts +++ b/src/mono/wasm/runtime/roots.ts @@ -4,6 +4,7 @@ import cwraps from "./cwraps"; import { Module } from "./imports"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; +import { MonoObjectRef, MonoObjectRefNull, MonoObject } from "./types"; const maxScratchRoots = 8192; let _scratch_root_buffer: WasmRootBuffer | null = null; @@ -57,7 +58,7 @@ export function mono_wasm_new_root_buffer_from_pointer(offset: VoidPtr, capacity * Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack. * Releasing this root will not de-allocate the root space. You still need to call .release(). */ -export function mono_wasm_new_external_root<T extends ManagedPointer | NativePointer>(address: VoidPtr): WasmRoot<T> { +export function mono_wasm_new_external_root<T extends MonoObject>(address: VoidPtr | MonoObjectRef): WasmRoot<T> { let result: WasmExternalRoot<T>; if (!address) @@ -80,7 +81,7 @@ export function mono_wasm_new_external_root<T extends ManagedPointer | NativePoi * The result object has get() and set(value) methods, along with a .value property. * When you are done using the root you must call its .release() method. */ -export function mono_wasm_new_root<T extends ManagedPointer | NativePointer>(value: T | undefined = undefined): WasmRoot<T> { +export function mono_wasm_new_root<T extends MonoObject>(value: T | undefined = undefined): WasmRoot<T> { let result: WasmRoot<T>; if (_scratch_root_free_instances.length > 0) { @@ -110,7 +111,7 @@ export function mono_wasm_new_root<T extends ManagedPointer | NativePointer>(val * mono_wasm_new_roots([a, b, ...]) returns an array of new roots initialized with each element. * Each root must be released with its release method, or using the mono_wasm_release_roots API. */ -export function mono_wasm_new_roots<T extends ManagedPointer | NativePointer>(count_or_values: number | T[]): WasmRoot<T>[] { +export function mono_wasm_new_roots<T extends MonoObject>(count_or_values: number | T[]): WasmRoot<T>[] { let result; if (Array.isArray(count_or_values)) { @@ -207,7 +208,7 @@ export class WasmRootBuffer { this._throw_index_out_of_range(); } - get_address(index: number): NativePointer { + get_address(index: number): MonoObjectRef { this._check_in_range(index); return <any>this.__offset + (index * 4); } @@ -223,21 +224,27 @@ export class WasmRootBuffer { get(index: number): ManagedPointer { this._check_in_range(index); const offset = this.get_address_32(index); - return <any>Module.HEAP32[offset]; + return <any>Module.HEAPU32[offset]; } set(index: number, value: ManagedPointer): ManagedPointer { - const offset = this.get_address_32(index); - Module.HEAP32[offset] = <any>value; + const address = this.get_address(index); + cwraps.mono_wasm_write_managed_pointer_unsafe(address, value); return value; } + copy_value_from_address(index: number, sourceAddress: MonoObjectRef): void { + const destinationAddress = this.get_address(index); + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, sourceAddress); + } + _unsafe_get(index: number): number { - return Module.HEAP32[this.__offset32 + index]; + return Module.HEAPU32[this.__offset32 + index]; } _unsafe_set(index: number, value: ManagedPointer | NativePointer): void { - Module.HEAP32[this.__offset32 + index] = <any>value; + const address = <any>this.__offset + index; + cwraps.mono_wasm_write_managed_pointer_unsafe(<VoidPtr><any>address, <ManagedPointer>value); } clear(): void { @@ -260,20 +267,25 @@ export class WasmRootBuffer { } } -export interface WasmRoot<T extends ManagedPointer | NativePointer> { - get_address(): NativePointer; +export interface WasmRoot<T extends MonoObject> { + get_address(): MonoObjectRef; get_address_32(): number; + get address(): MonoObjectRef; get(): T; set(value: T): T; get value(): T; set value(value: T); + copy_from_address(source: MonoObjectRef): void; + copy_to_address(destination: MonoObjectRef): void; + copy_from(source: WasmRoot<T>): void; + copy_to(destination: WasmRoot<T>): void; valueOf(): T; clear(): void; release(): void; toString(): string; } -class WasmJsOwnedRoot<T extends ManagedPointer | NativePointer> implements WasmRoot<T> { +class WasmJsOwnedRoot<T extends MonoObject> implements WasmRoot<T> { private __buffer: WasmRootBuffer; private __index: number; @@ -282,7 +294,7 @@ class WasmJsOwnedRoot<T extends ManagedPointer | NativePointer> implements WasmR this.__index = index; } - get_address(): NativePointer { + get_address(): MonoObjectRef { return this.__buffer.get_address(this.__index); } @@ -290,16 +302,43 @@ class WasmJsOwnedRoot<T extends ManagedPointer | NativePointer> implements WasmR return this.__buffer.get_address_32(this.__index); } + get address(): MonoObjectRef { + return this.__buffer.get_address(this.__index); + } + get(): T { const result = this.__buffer._unsafe_get(this.__index); return <any>result; } set(value: T): T { - this.__buffer._unsafe_set(this.__index, value); + const destinationAddress = this.__buffer.get_address(this.__index); + cwraps.mono_wasm_write_managed_pointer_unsafe(destinationAddress, <ManagedPointer>value); return value; } + copy_from(source: WasmRoot<T>): void { + const sourceAddress = source.address; + const destinationAddress = this.address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, sourceAddress); + } + + copy_to(destination: WasmRoot<T>): void { + const sourceAddress = this.address; + const destinationAddress = destination.address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, sourceAddress); + } + + copy_from_address(source: MonoObjectRef): void { + const destinationAddress = this.address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, source); + } + + copy_to_address(destination: MonoObjectRef): void { + const sourceAddress = this.address; + cwraps.mono_wasm_copy_managed_pointer(destination, sourceAddress); + } + get value(): T { return this.get(); } @@ -309,7 +348,7 @@ class WasmJsOwnedRoot<T extends ManagedPointer | NativePointer> implements WasmR } valueOf(): T { - return this.get(); + throw new Error("Implicit conversion of roots to pointers is no longer supported. Use .value or .address as appropriate"); } clear(): void { @@ -332,25 +371,29 @@ class WasmJsOwnedRoot<T extends ManagedPointer | NativePointer> implements WasmR } toString(): string { - return `[root @${this.get_address()}]`; + return `[root @${this.address}]`; } } -class WasmExternalRoot<T extends ManagedPointer | NativePointer> implements WasmRoot<T> { - private __external_address: NativePointer = <any>undefined; - private __external_address_32: number = <any>undefined; +class WasmExternalRoot<T extends MonoObject> implements WasmRoot<T> { + private __external_address: MonoObjectRef = MonoObjectRefNull; + private __external_address_32: number = <any>0; - constructor(address: NativePointer) { + constructor(address: NativePointer | ManagedPointer) { this._set_address(address); } - _set_address(address: NativePointer): void { - this.__external_address = address; + _set_address(address: NativePointer | ManagedPointer): void { + this.__external_address = <MonoObjectRef><any>address; this.__external_address_32 = <number><any>address >>> 2; } - get_address(): NativePointer { - return this.__external_address; + get address(): MonoObjectRef { + return <MonoObjectRef><any>this.__external_address; + } + + get_address(): MonoObjectRef { + return <MonoObjectRef><any>this.__external_address; } get_address_32(): number { @@ -363,10 +406,32 @@ class WasmExternalRoot<T extends ManagedPointer | NativePointer> implements Wasm } set(value: T): T { - Module.HEAPU32[this.__external_address_32] = <number><any>value; + cwraps.mono_wasm_write_managed_pointer_unsafe(this.__external_address, <ManagedPointer>value); return value; } + copy_from(source: WasmRoot<T>): void { + const sourceAddress = source.address; + const destinationAddress = this.__external_address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, sourceAddress); + } + + copy_to(destination: WasmRoot<T>): void { + const sourceAddress = this.__external_address; + const destinationAddress = destination.address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, sourceAddress); + } + + copy_from_address(source: MonoObjectRef): void { + const destinationAddress = this.__external_address; + cwraps.mono_wasm_copy_managed_pointer(destinationAddress, source); + } + + copy_to_address(destination: MonoObjectRef): void { + const sourceAddress = this.__external_address; + cwraps.mono_wasm_copy_managed_pointer(destination, sourceAddress); + } + get value(): T { return this.get(); } @@ -376,7 +441,7 @@ class WasmExternalRoot<T extends ManagedPointer | NativePointer> implements Wasm } valueOf(): T { - return this.get(); + throw new Error("Implicit conversion of roots to pointers is no longer supported. Use .value or .address as appropriate"); } clear(): void { @@ -390,6 +455,6 @@ class WasmExternalRoot<T extends ManagedPointer | NativePointer> implements Wasm } toString(): string { - return `[external root @${this.get_address()}]`; + return `[external root @${this.address}]`; } }
\ No newline at end of file diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 4f60611e5d5..49427b3539c 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol } from "./types"; +import { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types"; import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; import cwraps from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; @@ -14,6 +14,7 @@ import { find_corlib_class } from "./class-loader"; import { VoidPtr, CharPtr } from "./types/emscripten"; import { DotnetPublicAPI } from "./exports"; import { mono_on_abort } from "./run"; +import { mono_wasm_new_root } from "./roots"; export let runtime_is_initialized_resolve: Function; export let runtime_is_initialized_reject: Function; @@ -403,11 +404,14 @@ export function bindings_lazy_init(): void { if (!runtimeHelpers.wasm_runtime_class) throw "Can't find " + binding_fqn_class + " class"; - runtimeHelpers.get_call_sig = get_method("GetCallSignature"); - if (!runtimeHelpers.get_call_sig) - throw "Can't find GetCallSignature method"; + runtimeHelpers.get_call_sig_ref = get_method("GetCallSignatureRef"); + if (!runtimeHelpers.get_call_sig_ref) + throw "Can't find GetCallSignatureRef method"; _create_primitive_converters(); + + runtimeHelpers._box_root = mono_wasm_new_root<MonoObject>(); + runtimeHelpers._null_root = mono_wasm_new_root<MonoObject>(); } // Initializes the runtime and loads assemblies, debug information, and other files. diff --git a/src/mono/wasm/runtime/strings.ts b/src/mono/wasm/runtime/strings.ts index da38f2c9100..cc322d0d7f6 100644 --- a/src/mono/wasm/runtime/strings.ts +++ b/src/mono/wasm/runtime/strings.ts @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. import { mono_wasm_new_root_buffer, WasmRootBuffer } from "./roots"; -import { MonoString, MonoStringNull, } from "./types"; +import { MonoString, MonoStringNull } from "./types"; import { Module } from "./imports"; import cwraps from "./cwraps"; -import { mono_wasm_new_root } from "./roots"; +import { mono_wasm_new_root, WasmRoot } from "./roots"; import { getI32 } from "./memory"; import { NativePointer, CharPtr } from "./types/emscripten"; @@ -15,22 +15,38 @@ export class StringDecoder { private mono_text_decoder: TextDecoder | undefined | null; private mono_wasm_string_decoder_buffer: NativePointer | undefined; - copy(mono_string: MonoString): string | null { + init_fields(): void { if (!this.mono_wasm_string_decoder_buffer) { this.mono_text_decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-16le") : null; this.mono_wasm_string_root = mono_wasm_new_root(); this.mono_wasm_string_decoder_buffer = Module._malloc(12); } + } + + /** + * @deprecated Not GC or thread safe + */ + copy(mono_string: MonoString): string | null { + this.init_fields(); if (mono_string === MonoStringNull) return null; this.mono_wasm_string_root.value = mono_string; + const result = this.copy_root(this.mono_wasm_string_root); + this.mono_wasm_string_root.value = MonoStringNull; + return result; + } + + copy_root(root: WasmRoot<MonoString>): string | null { + this.init_fields(); + if (root.value === MonoStringNull) + return null; const ppChars = <any>this.mono_wasm_string_decoder_buffer + 0, pLengthBytes = <any>this.mono_wasm_string_decoder_buffer + 4, pIsInterned = <any>this.mono_wasm_string_decoder_buffer + 8; - cwraps.mono_wasm_string_get_data(this.mono_wasm_string_root.value, <any>ppChars, <any>pLengthBytes, <any>pIsInterned); + cwraps.mono_wasm_string_get_data_ref(root.address, <any>ppChars, <any>pLengthBytes, <any>pIsInterned); let result = mono_wasm_empty_string; const lengthBytes = getI32(pLengthBytes), @@ -39,20 +55,19 @@ export class StringDecoder { if (pLengthBytes && pChars) { if (isInterned) { - result = interned_string_table.get(<any>this.mono_wasm_string_root.value)!; + result = interned_string_table.get(root.value)!; + // console.log(`intern table cache hit ${mono_string} ${result.length}`); } - if (!result) { + if (!isInterned || !result) { result = this.decode(<any>pChars, <any>pChars + lengthBytes); if (isInterned) { // console.log("interned", mono_string, result.length); - interned_string_table.set(<any>this.mono_wasm_string_root.value, result); - interned_js_string_table.set(result, <any>this.mono_wasm_string_root.value); + interned_string_table.set(root.value, result); } } } - this.mono_wasm_string_root.value = 0; return result; } @@ -87,26 +102,35 @@ let _interned_string_current_root_buffer_count = 0; export const string_decoder = new StringDecoder(); export const mono_wasm_empty_string = ""; +/** + * @deprecated Not GC or thread safe + */ export function conv_string(mono_obj: MonoString): string | null { return string_decoder.copy(mono_obj); } +export function conv_string_root(root: WasmRoot<MonoString>): string | null { + return string_decoder.copy_root(root); +} + // Ensures the string is already interned on both the managed and JavaScript sides, // then returns the interned string value (to provide fast reference comparisons like C#) export function mono_intern_string(string: string): string { if (string.length === 0) return mono_wasm_empty_string; + // HACK: This would normally be unsafe, but the return value of js_string_to_mono_string_interned is always an + // interned string, so the address will never change and it is safe for us to use the raw pointer. Don't do this though const ptr = js_string_to_mono_string_interned(string); const result = interned_string_table.get(ptr); + if (!result) + throw new Error("internal error: interned_string_table did not contain string after js_string_to_mono_string_interned"); return result!; } -function _store_string_in_intern_table(string: string, ptr: MonoString, internIt: boolean) { - if (!ptr) +function _store_string_in_intern_table(string: string, root: WasmRoot<MonoString>, internIt: boolean): void { + if (!root.value) throw new Error("null pointer passed to _store_string_in_intern_table"); - else if (typeof (ptr) !== "number") - throw new Error(`non-pointer passed to _store_string_in_intern_table: ${typeof (ptr)}`); const internBufferSize = 8192; @@ -121,78 +145,127 @@ function _store_string_in_intern_table(string: string, ptr: MonoString, internIt const rootBuffer = _interned_string_current_root_buffer; const index = _interned_string_current_root_buffer_count++; - rootBuffer.set(index, ptr); // Store the managed string into the managed intern table. This can theoretically // provide a different managed object than the one we passed in, so update our // pointer (stored in the root) with the result. if (internIt) { - ptr = cwraps.mono_wasm_intern_string(ptr); - rootBuffer.set(index, ptr); + cwraps.mono_wasm_intern_string_ref(root.address); + if (!root.value) + throw new Error("mono_wasm_intern_string_ref produced a null pointer"); } - if (!ptr) - throw new Error("mono_wasm_intern_string produced a null pointer"); - - interned_js_string_table.set(string, ptr); - interned_string_table.set(ptr, string); + interned_js_string_table.set(string, root.value); + interned_string_table.set(root.value, string); if ((string.length === 0) && !_empty_string_ptr) - _empty_string_ptr = ptr; + _empty_string_ptr = root.value; - return ptr; + // Copy the final pointer into our interned string root buffer to ensure the string + // remains rooted. TODO: Is this actually necessary? + rootBuffer.copy_value_from_address(index, root.address); } -export function js_string_to_mono_string_interned(string: string | symbol): MonoString { +export function js_string_to_mono_string_interned_root(string: string | symbol, result: WasmRoot<MonoString>): void { const text = (typeof (string) === "symbol") ? (string.description || Symbol.keyFor(string) || "<unknown Symbol>") : string; - if ((text.length === 0) && _empty_string_ptr) - return _empty_string_ptr; + if (typeof(text) !== "string") { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + throw new Error(`Argument to js_string_to_mono_string_interned must be a string but was ${string}`); + } - let ptr = interned_js_string_table.get(text); - if (ptr) - return ptr; + if ((text.length === 0) && _empty_string_ptr) { + result.set(_empty_string_ptr); + return; + } - ptr = js_string_to_mono_string_new(text); - ptr = _store_string_in_intern_table(text, ptr, true); + const ptr = interned_js_string_table.get(text); + if (ptr) { + result.set(ptr); + return; + } - return ptr; + js_string_to_mono_string_new_root(text, result); + _store_string_in_intern_table(text, result, true); } -export function js_string_to_mono_string(string: string): MonoString { +export function js_string_to_mono_string_root(string: string, result: WasmRoot<MonoString>): void { + result.clear(); + if (string === null) - return MonoStringNull; + return; else if (typeof (string) === "symbol") - return js_string_to_mono_string_interned(string); + js_string_to_mono_string_interned_root(string, result); else if (typeof (string) !== "string") throw new Error("Expected string argument, got " + typeof (string)); + else if (string.length === 0) + // Always use an interned pointer for empty strings + js_string_to_mono_string_interned_root(string, result); + else { + // Looking up large strings in the intern table will require the JS runtime to + // potentially hash them and then do full byte-by-byte comparisons, which is + // very expensive. Because we can not guarantee it won't happen, try to minimize + // the cost of this and prevent performance issues for large strings + if (string.length <= 256) { + const interned = interned_js_string_table.get(string); + if (interned) { + result.set(interned); + return; + } + } - // Always use an interned pointer for empty strings - if (string.length === 0) - return js_string_to_mono_string_interned(string); - - // Looking up large strings in the intern table will require the JS runtime to - // potentially hash them and then do full byte-by-byte comparisons, which is - // very expensive. Because we can not guarantee it won't happen, try to minimize - // the cost of this and prevent performance issues for large strings - if (string.length <= 256) { - const interned = interned_js_string_table.get(string); - if (interned) - return interned; + js_string_to_mono_string_new_root(string, result); } - - return js_string_to_mono_string_new(string); } -export function js_string_to_mono_string_new(string: string): MonoString { +export function js_string_to_mono_string_new_root(string: string, result: WasmRoot<MonoString>): void { const buffer = Module._malloc((string.length + 1) * 2); const buffer16 = (<any>buffer >>> 1) | 0; for (let i = 0; i < string.length; i++) Module.HEAP16[buffer16 + i] = string.charCodeAt(i); Module.HEAP16[buffer16 + string.length] = 0; - const result = cwraps.mono_wasm_string_from_utf16(<any>buffer, string.length); + cwraps.mono_wasm_string_from_utf16_ref(<any>buffer, string.length, result.address); Module._free(buffer); - return result; -}
\ No newline at end of file +} + +/** + * @deprecated Not GC or thread safe + */ +export function js_string_to_mono_string_interned(string: string | symbol): MonoString { + const temp = mono_wasm_new_root<MonoString>(); + try { + js_string_to_mono_string_interned_root(string, temp); + return temp.value; + } finally { + temp.release(); + } +} + +/** + * @deprecated Not GC or thread safe + */ +export function js_string_to_mono_string(string: string): MonoString { + const temp = mono_wasm_new_root<MonoString>(); + try { + js_string_to_mono_string_root(string, temp); + return temp.value; + } finally { + temp.release(); + } +} + +/** + * @deprecated Not GC or thread safe + */ +export function js_string_to_mono_string_new(string: string): MonoString { + const temp = mono_wasm_new_root<MonoString>(); + try { + js_string_to_mono_string_new_root(string, temp); + return temp.value; + } finally { + temp.release(); + } +} diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index abf0c048380..2b85e899538 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -16,6 +16,9 @@ export interface MonoObject extends ManagedPointer { export interface MonoString extends MonoObject { __brand: "MonoString" } +export interface MonoInternedString extends MonoString { + __brandString: "MonoInternedString" +} export interface MonoClass extends MonoObject { __brand: "MonoClass" } @@ -31,6 +34,15 @@ export interface MonoArray extends MonoObject { export interface MonoAssembly extends MonoObject { __brand: "MonoAssembly" } +// Pointer to a MonoObject* (i.e. the address of a root) +export interface MonoObjectRef extends ManagedPointer { + __brandMonoObjectRef: "MonoObjectRef" +} +// This exists for signature clarity, we need it to be structurally equivalent +// so that anything requiring MonoObjectRef will work +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface MonoStringRef extends MonoObjectRef { +} export const MonoMethodNull: MonoMethod = <MonoMethod><any>0; export const MonoObjectNull: MonoObject = <MonoObject><any>0; export const MonoArrayNull: MonoArray = <MonoArray><any>0; @@ -38,6 +50,8 @@ export const MonoAssemblyNull: MonoAssembly = <MonoAssembly><any>0; export const MonoClassNull: MonoClass = <MonoClass><any>0; export const MonoTypeNull: MonoType = <MonoType><any>0; export const MonoStringNull: MonoString = <MonoString><any>0; +export const MonoObjectRefNull: MonoObjectRef = <MonoObjectRef><any>0; +export const MonoStringRefNull: MonoStringRef = <MonoStringRef><any>0; export const JSHandleDisposed: JSHandle = <JSHandle><any>-1; export const JSHandleNull: JSHandle = <JSHandle><any>0; export const GCHandleNull: GCHandle = <GCHandle><any>0; @@ -115,7 +129,7 @@ export const enum AssetBehaviours { } export type RuntimeHelpers = { - get_call_sig: MonoMethod; + get_call_sig_ref: MonoMethod; runtime_namespace: string; runtime_classname: string; wasm_runtime_class: MonoClass; @@ -126,6 +140,9 @@ export type RuntimeHelpers = { _box_buffer: VoidPtr; _unbox_buffer: VoidPtr; + _box_root: any; + // A WasmRoot that is guaranteed to contain 0 + _null_root: any; _class_int32: MonoClass; _class_uint32: MonoClass; _class_double: MonoClass; diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index e013e867aef..b0be40a2d0c 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -1,18 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_new_root, WasmRoot } from "./roots"; +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_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 } from "./js-to-cs"; -import { wrap_error } from "./method-calls"; -import { conv_string } from "./strings"; -import { JSHandle, MonoArray, MonoObject, MonoObjectNull, MonoString } from "./types"; +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 } from "./types/emscripten"; +import { Int32Ptr, VoidPtr } from "./types/emscripten"; 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"); @@ -29,17 +30,20 @@ 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(uri: MonoString, subProtocols: MonoArray, on_close: MonoObject, web_socket_js_handle: Int32Ptr, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr): MonoObject { - const uri_root = mono_wasm_new_root(uri); - const sub_root = mono_wasm_new_root(subProtocols); - const on_close_root = mono_wasm_new_root(on_close); +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(uri_root.value); + const js_uri = conv_string_root(uri_root); if (!js_uri) { - return wrap_error(is_exception, "ERR12: Invalid uri '" + uri_root.value + "'"); + wrap_error_root(is_exception, "ERR12: Invalid uri '" + uri_root.value + "'", result_root); + return; } - const js_subs = _mono_array_root_to_js_array(sub_root); + const js_subs = mono_array_root_to_js_array(sub_root); const js_on_close = _wrap_delegate_root_as_function(on_close_root)!; @@ -77,10 +81,10 @@ export function mono_wasm_web_socket_open(uri: MonoString, subProtocols: MonoArr // 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_root = receive_promise_control.response_root; - Module.setValue(<any>response_root.value + 0, 0, "i32");// count - Module.setValue(<any>response_root.value + 4, 2, "i32");// type:close - Module.setValue(<any>response_root.value + 8, 1, "i32");// end_of_message: true + 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); }); }; @@ -91,24 +95,23 @@ export function mono_wasm_web_socket_open(uri: MonoString, subProtocols: MonoArr const ws_js_handle = mono_wasm_get_js_handle(ws); Module.setValue(web_socket_js_handle, <any>ws_js_handle, "i32"); - const { task_ptr, then_js_handle } = _wrap_js_thenable_as_task(promise); + 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"); - - return task_ptr; } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, result_root); } finally { + result_root.release(); uri_root.release(); sub_root.release(); on_close_root.release(); } } -export function mono_wasm_web_socket_send(webSocket_js_handle: JSHandle, buffer_ptr: MonoObject, offset: number, length: number, message_type: number, end_of_message: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr): MonoObject { - const buffer_root = mono_wasm_new_root(buffer_ptr); +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) @@ -118,28 +121,24 @@ export function mono_wasm_web_socket_send(webSocket_js_handle: JSHandle, buffer_ throw new Error("InvalidState: The WebSocket is not connected."); } - const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, buffer_root, offset, length, message_type, end_of_message); + const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, buffer_ptr, offset, length, message_type, end_of_message); if (!end_of_message || !whole_buffer) { - return MonoObjectNull; // we are done buffering synchronously, no promise + result_root.clear(); // we are done buffering synchronously, no promise + return; } - return _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, thenable_js_handle); + _mono_wasm_web_socket_send_and_wait(ws, whole_buffer, thenable_js_handle, result_address); } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, result_root); } finally { - buffer_root.release(); + result_root.release(); } } -export function mono_wasm_web_socket_receive(webSocket_js_handle: JSHandle, buffer_ptr: MonoObject, offset: number, length: number, response_ptr: MonoObject, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr): MonoObject { - const buffer_root = mono_wasm_new_root(buffer_ptr); - const response_root = mono_wasm_new_root(response_ptr); - const release_buffer = () => { - buffer_root.release(); - response_root.release(); - }; +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); try { const ws = mono_wasm_get_jsobj_from_js_handle(webSocket_js_handle); @@ -158,42 +157,46 @@ export function mono_wasm_web_socket_receive(webSocket_js_handle: JSHandle, buff throw new Error("ERR20: Invalid WS state");// assert } // finish synchronously - _mono_wasm_web_socket_receive_buffering(receive_event_queue, buffer_root, offset, length, response_root); - release_buffer(); + _mono_wasm_web_socket_receive_buffering(receive_event_queue, buffer_ptr, offset, length, response_ptr); Module.setValue(thenable_js_handle, 0, "i32"); - return MonoObjectNull; + result_root.clear(); + return; } - const { promise, promise_control } = _create_cancelable_promise(release_buffer, release_buffer); + const { promise, promise_control } = _create_cancelable_promise(undefined, undefined); const receive_promise_control = promise_control as ReceivePromiseControl; - receive_promise_control.buffer_root = buffer_root; + receive_promise_control.buffer_ptr = buffer_ptr; receive_promise_control.buffer_offset = offset; receive_promise_control.buffer_length = length; - receive_promise_control.response_root = response_root; + receive_promise_control.response_ptr = response_ptr; receive_promise_queue.enqueue(receive_promise_control); - const { task_ptr, then_js_handle } = _wrap_js_thenable_as_task(promise); + 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"); - return task_ptr; } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, result_root); + } + finally { + result_root.release(); } } -export function mono_wasm_web_socket_close(webSocket_js_handle: JSHandle, code: number, reason: MonoString, wait_for_close_received: boolean, thenable_js_handle: Int32Ptr, is_exception: Int32Ptr): MonoObject { - const reason_root = mono_wasm_new_root(reason); +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); if (ws.readyState == WebSocket.CLOSED) { - return MonoObjectNull;// no promise + result_root.clear(); + return; } - const js_reason = conv_string(reason_root.value); + const js_reason = conv_string_root(reason_root); if (wait_for_close_received) { const { promise, promise_control } = _create_cancelable_promise(); @@ -205,11 +208,11 @@ export function mono_wasm_web_socket_close(webSocket_js_handle: JSHandle, code: ws.close(code); } - const { task_ptr, then_js_handle } = _wrap_js_thenable_as_task(promise); + 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"); - return task_ptr; + return; } else { if (!mono_wasm_web_socket_close_warning) { @@ -222,18 +225,21 @@ export function mono_wasm_web_socket_close(webSocket_js_handle: JSHandle, code: ws.close(code); } Module.setValue(thenable_js_handle, 0, "i32"); - return MonoObjectNull;// no promise + result_root.clear(); + return; } } catch (ex) { - return wrap_error(is_exception, 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): MonoObject { +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) @@ -258,61 +264,68 @@ export function mono_wasm_web_socket_abort(webSocket_js_handle: JSHandle, is_exc // this is different from Managed implementation ws.close(1000, "Connection was aborted."); - return MonoObjectNull; + result_root.clear(); } catch (ex) { - return wrap_error(is_exception, ex); + wrap_error_root(is_exception, ex, result_root); } -} - -function _mono_wasm_web_socket_send_and_wait(ws: WebSocketExtension, buffer: Uint8Array | string, thenable_js_handle: Int32Ptr): MonoObject { - // 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) { - return MonoObjectNull; // no promise + finally { + result_root.release(); } +} - // 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); +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; - 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); + // 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; } - // remove from pending - const index = pending.indexOf(promise_control); - if (index > -1) { - pending.splice(index, 1); - } - }; - globalThis.setTimeout(polling_check, 0); + // 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); + } + }; - const { task_ptr, then_js_handle } = _wrap_js_thenable_as_task(promise); - // 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"); + globalThis.setTimeout(polling_check, 0); - return task_ptr; + 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(); + } } function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: MessageEvent) { @@ -348,19 +361,19 @@ 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_root, promise_control.buffer_offset, promise_control.buffer_length, - promise_control.response_root); + promise_control.buffer_ptr, promise_control.buffer_offset, promise_control.buffer_length, + promise_control.response_ptr); promise_control.resolve(null); } prevent_timer_throttling(); } -function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, buffer_root: WasmRoot<MonoObject>, buffer_offset: number, buffer_length: number, response_root: WasmRoot<MonoObject>) { +function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, buffer_ptr: VoidPtr, buffer_offset: number, buffer_length: number, response_ptr: VoidPtr) { const event = event_queue.peek(); const count = Math.min(buffer_length, event.data.length - event.offset); if (count > 0) { - const targetView = Module.HEAPU8.subarray(<any>buffer_root.value + buffer_offset, <any>buffer_root.value + buffer_offset + buffer_length); + 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); event.offset += count; @@ -369,15 +382,15 @@ function _mono_wasm_web_socket_receive_buffering(event_queue: Queue<any>, buffer if (end_of_message) { event_queue.dequeue(); } - Module.setValue(<any>response_root.value + 0, count, "i32"); - Module.setValue(<any>response_root.value + 4, event.type, "i32"); - Module.setValue(<any>response_root.value + 8, end_of_message, "i32"); + setI32(<any>response_ptr + 0, count); + setI32(<any>response_ptr + 4, event.type); + setI32(<any>response_ptr + 8, end_of_message); } -function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_root: WasmRoot<MonoObject>, buffer_offset: number, length: number, message_type: number, end_of_message: boolean): Uint8Array | string | null { +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 { let buffer = ws[wasm_ws_pending_send_buffer]; let offset = 0; - const message_ptr = <any>buffer_root.value + buffer_offset; + const message_ptr = <any>buffer_ptr + buffer_offset; if (buffer) { offset = ws[wasm_ws_pending_send_buffer_offset]; @@ -456,8 +469,8 @@ type WebSocketExtension = WebSocket & { } type ReceivePromiseControl = PromiseControl & { - response_root: WasmRoot<MonoObject> - buffer_root: WasmRoot<MonoObject> + response_ptr: VoidPtr + buffer_ptr: VoidPtr buffer_offset: number buffer_length: number } |