diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs index c9b443e9c82ce1..d31f1543ce76b9 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs @@ -48,7 +48,7 @@ internal void AddInFlight() InFlightCounter++; if (InFlightCounter == 1) { - Debug.Assert(InFlight == null); + Debug.Assert(InFlight == null, "InFlight == null"); InFlight = GCHandle.Alloc(this, GCHandleType.Normal); } } @@ -61,12 +61,12 @@ internal void ReleaseInFlight() { lock (this) { - Debug.Assert(InFlightCounter != 0); + Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0"); InFlightCounter--; if (InFlightCounter == 0) { - Debug.Assert(InFlight.HasValue); + Debug.Assert(InFlight.HasValue, "InFlight.HasValue"); InFlight.Value.Free(); InFlight = null; } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs index 60f694e2b7cf39..4b5d072e8ac43e 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs @@ -49,14 +49,15 @@ private struct IntPtrAndHandle internal IntPtr ptr; [FieldOffset(0)] - internal RuntimeMethodHandle handle; + internal RuntimeMethodHandle methodHandle; [FieldOffset(0)] internal RuntimeTypeHandle typeHandle; } // see src/mono/wasm/driver.c MARSHAL_TYPE_xxx - public enum MarshalType : int { + public enum MarshalType : int + { NULL = 0, INT = 1, FP64 = 2, @@ -90,7 +91,8 @@ public enum MarshalType : int { } // see src/mono/wasm/driver.c MARSHAL_ERROR_xxx - public enum MarshalError : int { + public enum MarshalError : int + { BUFFER_TOO_SMALL = 512, NULL_CLASS_POINTER = 513, NULL_TYPE_POINTER = 514, @@ -98,13 +100,12 @@ public enum MarshalError : int { FIRST = BUFFER_TOO_SMALL } - public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeType) + public static string GetCallSignature(IntPtr _methodHandle, object? objForRuntimeType) { - IntPtrAndHandle tmp = default(IntPtrAndHandle); - tmp.ptr = methodHandle; + var methodHandle = GetMethodHandleFromIntPtr(_methodHandle); - MethodBase? mb = objForRuntimeType == null ? MethodBase.GetMethodFromHandle(tmp.handle) : MethodBase.GetMethodFromHandle(tmp.handle, Type.GetTypeHandle(objForRuntimeType)); - if (mb == null) + MethodBase? mb = objForRuntimeType is null ? MethodBase.GetMethodFromHandle(methodHandle) : MethodBase.GetMethodFromHandle(methodHandle, Type.GetTypeHandle(objForRuntimeType)); + if (mb is null) return string.Empty; ParameterInfo[] parms = mb.GetParameters(); @@ -112,66 +113,163 @@ public static string GetCallSignature(IntPtr methodHandle, object objForRuntimeT if (parmsLength == 0) return string.Empty; - char[] res = new char[parmsLength]; + var result = new char[parmsLength]; + for (int i = 0; i < parmsLength; i++) + { + Type t = parms[i].ParameterType; + var mt = GetMarshalTypeFromType(t); + result[i] = GetCallSignatureCharacterForMarshalType(mt, null); + } + + return new string(result); + } + + private static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr) + { + var temp = new IntPtrAndHandle { ptr = ptr }; + return temp.methodHandle; + } + + private static RuntimeTypeHandle GetTypeHandleFromIntPtr(IntPtr ptr) + { + var temp = new IntPtrAndHandle { ptr = ptr }; + return temp.typeHandle; + } + + internal static MarshalType GetMarshalTypeFromType(Type? type) + { + if (type is null) + return MarshalType.VOID; - for (int c = 0; c < parmsLength; c++) + var typeCode = Type.GetTypeCode(type); + if (type.IsEnum) { - Type t = parms[c].ParameterType; - switch (Type.GetTypeCode(t)) + switch (typeCode) + { + case TypeCode.Int32: + case TypeCode.UInt32: + return MarshalType.ENUM; + case TypeCode.Int64: + case TypeCode.UInt64: + return MarshalType.ENUM64; + default: + throw new JSException($"Unsupported enum underlying type {typeCode}"); + } + } + + switch (typeCode) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + return MarshalType.INT; + case TypeCode.UInt32: + return MarshalType.UINT32; + case TypeCode.Boolean: + return MarshalType.BOOL; + case TypeCode.Int64: + return MarshalType.INT64; + case TypeCode.UInt64: + return MarshalType.UINT64; + case TypeCode.Single: + return MarshalType.FP32; + case TypeCode.Double: + return MarshalType.FP64; + case TypeCode.String: + return MarshalType.STRING; + case TypeCode.Char: + return MarshalType.CHAR; + } + + if (type.IsArray) + { + if (!type.IsSZArray) + throw new JSException("Only single-dimensional arrays with a zero lower bound can be marshaled to JS"); + + var elementType = type.GetElementType(); + switch (Type.GetTypeCode(elementType)) { case TypeCode.Byte: + return MarshalType.ARRAY_UBYTE; case TypeCode.SByte: + return MarshalType.ARRAY_BYTE; case TypeCode.Int16: + return MarshalType.ARRAY_SHORT; case TypeCode.UInt16: + return MarshalType.ARRAY_USHORT; case TypeCode.Int32: + return MarshalType.ARRAY_INT; case TypeCode.UInt32: - case TypeCode.Boolean: - // Enums types have the same code as their underlying numeric types - if (t.IsEnum) - res[c] = 'j'; - else - res[c] = 'i'; - break; - case TypeCode.Int64: - case TypeCode.UInt64: - // Enums types have the same code as their underlying numeric types - if (t.IsEnum) - res[c] = 'k'; - else - res[c] = 'l'; - break; + return MarshalType.ARRAY_UINT; case TypeCode.Single: - res[c] = 'f'; - break; + return MarshalType.ARRAY_FLOAT; case TypeCode.Double: - res[c] = 'd'; - break; - case TypeCode.String: - res[c] = 's'; - break; + return MarshalType.ARRAY_DOUBLE; default: - if (t == typeof(IntPtr)) - { - res[c] = 'i'; - } - else if (t == typeof(Uri)) - { - res[c] = 'u'; - } - else if (t == typeof(SafeHandle)) - { - res[c] = 'h'; - } - else - { - if (t.IsValueType) - throw new NotSupportedException(SR.ValueTypeNotSupported); - res[c] = 'o'; - } - break; + throw new JSException($"Unsupported array element type {elementType}"); } } - return new string(res); + else if (type == typeof(IntPtr)) + return MarshalType.POINTER; + else if (type == typeof(UIntPtr)) + return MarshalType.POINTER; + else if (type == typeof(SafeHandle)) + return MarshalType.SAFEHANDLE; + else if (typeof(Delegate).IsAssignableFrom(type)) + return MarshalType.DELEGATE; + else if ((type == typeof(Task)) || typeof(Task).IsAssignableFrom(type)) + return MarshalType.TASK; + else if (typeof(Uri) == type) + return MarshalType.URI; + else if (type.IsPointer) + return MarshalType.POINTER; + + if (type.IsValueType) + return MarshalType.VT; + else + return MarshalType.OBJECT; + } + + internal static char GetCallSignatureCharacterForMarshalType(MarshalType t, char? defaultValue) + { + switch (t) + { + case MarshalType.BOOL: + case MarshalType.INT: + case MarshalType.UINT32: + case MarshalType.POINTER: + return 'i'; + case MarshalType.UINT64: + case MarshalType.INT64: + return 'l'; + case MarshalType.FP32: + return 'f'; + case MarshalType.FP64: + return 'd'; + case MarshalType.STRING: + return 's'; + case MarshalType.URI: + return 'u'; + case MarshalType.SAFEHANDLE: + return 'h'; + case MarshalType.ENUM: + return 'j'; + case MarshalType.ENUM64: + return 'k'; + case MarshalType.TASK: + case MarshalType.DELEGATE: + case MarshalType.OBJECT: + return 'o'; + case MarshalType.VT: + return 'a'; + default: + if (defaultValue.HasValue) + return defaultValue.Value; + else + throw new JSException($"Unsupported marshal type {t}"); + } } /// diff --git a/src/mono/mono/metadata/class-accessors.c b/src/mono/mono/metadata/class-accessors.c index 76065cb45c456f..662c0493553ac9 100644 --- a/src/mono/mono/metadata/class-accessors.c +++ b/src/mono/mono/metadata/class-accessors.c @@ -67,7 +67,9 @@ mono_class_try_get_generic_class (MonoClass *klass) guint32 mono_class_get_flags (MonoClass *klass) { - switch (m_class_get_class_kind (klass)) { + g_assert (klass); + guint32 kind = m_class_get_class_kind (klass); + switch (kind) { case MONO_CLASS_DEF: case MONO_CLASS_GTD: return m_classdef_get_flags ((MonoClassDef*)klass); diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index 284e15b1ade8e3..ad7e0a26478dcb 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -6791,9 +6791,9 @@ mono_string_is_interned_lookup (MonoStringHandle str, gboolean insert, MonoError #ifdef HOST_WASM /** * mono_string_instance_is_interned: - * Searches the interned string table for the provided string instance. + * Checks to see if the string instance has its interned flag set. * \param str String to probe - * \returns TRUE if the string is interned, FALSE otherwise. + * \returns TRUE if the string instance is interned, FALSE otherwise. */ int mono_string_instance_is_interned (MonoString *str) diff --git a/src/mono/wasm/build/README.md b/src/mono/wasm/build/README.md index faed3d05827f68..c6fb0a8681944b 100644 --- a/src/mono/wasm/build/README.md +++ b/src/mono/wasm/build/README.md @@ -132,3 +132,13 @@ them for the new task assembly. - `eng/testing/linker/trimmingTests.targets`, - `src/tests/Common/wasm-test-runner/WasmTestRunner.proj` - `src/tests/Directory.Build.targets` + +## Profiling build performance + +If encountering build performance issues, you can use the rollup `--perf` option and the typescript compiler `--generateCpuProfile` option to get build profile data, like so: + +```../emsdk/node/14.15.5_64bit/bin/npm run rollup --perf -- --perf --environment Configuration:Release,NativeBinDir:./rollup-test-data,ProductVersion:12.3.4``` + +```node node_modules/typescript/lib/tsc.js --generateCpuProfile dotnet-tsc.cpuprofile -p tsconfig.json ``` + +The .cpuprofile file generated by node can be opened in the Performance tab of Chrome or Edge's devtools. \ No newline at end of file diff --git a/src/mono/wasm/runtime/cs-to-js.ts b/src/mono/wasm/runtime/cs-to-js.ts index 2a7646d85973fd..de208efe3c93b2 100644 --- a/src/mono/wasm/runtime/cs-to-js.ts +++ b/src/mono/wasm/runtime/cs-to-js.ts @@ -3,7 +3,7 @@ import { mono_wasm_new_root, WasmRoot } from "./roots"; import { - GCHandle, JSHandleDisposed, MonoArray, + GCHandle, JSHandleDisposed, MarshalError, MarshalType, MonoArray, MonoArrayNull, MonoObject, MonoObjectNull, MonoString, MonoType, MonoTypeNull } from "./types"; @@ -18,49 +18,6 @@ import { _are_promises_supported, _create_cancelable_promise } from "./cancelabl import { getU32, getI32, getF32, getF64 } from "./memory"; import { Int32Ptr, VoidPtr } from "./types/emscripten"; -// see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType -export enum MarshalType { - NULL = 0, - INT = 1, - FP64 = 2, - STRING = 3, - VT = 4, - DELEGATE = 5, - TASK = 6, - OBJECT = 7, - BOOL = 8, - ENUM = 9, - URI = 22, - SAFEHANDLE = 23, - ARRAY_BYTE = 10, - ARRAY_UBYTE = 11, - ARRAY_UBYTE_C = 12, - ARRAY_SHORT = 13, - ARRAY_USHORT = 14, - ARRAY_INT = 15, - ARRAY_UINT = 16, - ARRAY_FLOAT = 17, - ARRAY_DOUBLE = 18, - FP32 = 24, - UINT32 = 25, - INT64 = 26, - UINT64 = 27, - CHAR = 28, - STRING_INTERNED = 29, - VOID = 30, - ENUM64 = 31, - POINTER = 32 -} - -// see src/mono/wasm/driver.c MARSHAL_ERROR_xxx and Runtime.cs -export enum MarshalError { - BUFFER_TOO_SMALL = 512, - NULL_CLASS_POINTER = 513, - NULL_TYPE_POINTER = 514, - UNSUPPORTED_TYPE = 515, - FIRST = BUFFER_TOO_SMALL -} - const delegate_invoke_symbol = Symbol.for("wasm delegate_invoke"); const delegate_invoke_signature_symbol = Symbol.for("wasm delegate_invoke_signature"); diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 98dd8f8e4e9bb5..fab4a0e2645491 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -97,7 +97,7 @@ 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: MonoObject): MonoObject; + mono_wasm_invoke_method(method: MonoMethod, this_arg: MonoObject, params: VoidPtr, out_exc: VoidPtr): MonoObject; 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; diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 8eb60b46e5478e..2aa8f121a55b80 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -179,7 +179,7 @@ export function mono_wasm_call_function_on(request: CallRequest): CFOResponse { const fn_args = request.arguments != undefined ? request.arguments.map(a => JSON.stringify(a.value)) : []; - const fn_body_template = `var fn = ${request.functionDeclaration}; return fn.apply(proxy, [${fn_args}]);`; + const fn_body_template = `const fn = ${request.functionDeclaration}; return fn.apply(proxy, [${fn_args}]);`; const fn_defn = new Function("proxy", fn_body_template); const fn_res = fn_defn(proxy); diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 1f18501c18b597..6d210e51ad7cc1 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -100,10 +100,7 @@ declare class WasmRootBuffer { release(): void; toString(): string; } -declare class WasmRoot { - private __buffer; - private __index; - constructor(buffer: WasmRootBuffer, index: number); +interface WasmRoot { get_address(): NativePointer; get_address_32(): number; get(): T; @@ -253,12 +250,13 @@ declare function mono_call_assembly_entry_point(assembly: string, args?: any[], declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr; declare type _MemOffset = number | VoidPtr | NativePointer; +declare type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; declare function setU8(offset: _MemOffset, value: number): void; declare function setU16(offset: _MemOffset, value: number): void; -declare function setU32(offset: _MemOffset, value: number): void; +declare function setU32(offset: _MemOffset, value: _NumberOrPointer): void; declare function setI8(offset: _MemOffset, value: number): void; declare function setI16(offset: _MemOffset, value: number): void; -declare function setI32(offset: _MemOffset, value: number): void; +declare function setI32(offset: _MemOffset, value: _NumberOrPointer): void; declare function setI64(offset: _MemOffset, value: number): void; declare function setF32(offset: _MemOffset, value: number): void; declare function setF64(offset: _MemOffset, value: number): void; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index dbe9800b9f12de..a84d1d9b0ec041 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -575,7 +575,7 @@ mono_wasm_assembly_load (const char *name) return res; } -EMSCRIPTEN_KEEPALIVE MonoAssembly* +EMSCRIPTEN_KEEPALIVE MonoAssembly* mono_wasm_get_corlib () { return mono_image_get_assembly (mono_get_corlib()); @@ -656,7 +656,7 @@ mono_wasm_assembly_get_entry_point (MonoAssembly *assembly) uint32_t entry = mono_image_get_entry_point (image); if (!entry) return NULL; - + mono_domain_ensure_entry_assembly (root_domain, assembly); method = mono_get_method (image, entry, NULL); @@ -784,7 +784,8 @@ mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType return MARSHAL_TYPE_VOID; case MONO_TYPE_BOOLEAN: return MARSHAL_TYPE_BOOL; - case MONO_TYPE_I: // IntPtr + case MONO_TYPE_I: // IntPtr + case MONO_TYPE_U: // UIntPtr case MONO_TYPE_PTR: return MARSHAL_TYPE_POINTER; case MONO_TYPE_I1: @@ -929,7 +930,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r MonoType *type = mono_class_get_type (klass), *original_type = type; if (!type) return MARSHAL_ERROR_NULL_TYPE_POINTER; - + if ((klass == mono_get_string_class ()) && mono_string_instance_is_interned ((MonoString *)obj)) { *resultL = 0; @@ -939,14 +940,14 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r if (mono_class_is_enum (klass)) type = mono_type_get_underlying_type (type); - + if (!type) return MARSHAL_ERROR_NULL_TYPE_POINTER; - + int mono_type = mono_type_get_type (type); - + if (mono_type == MONO_TYPE_GENERICINST) { - // HACK: While the 'any other type' fallback is valid for classes, it will do the + // HACK: While the 'any other type' fallback is valid for classes, it will do the // wrong thing for structs, so we need to make sure the valuetype handler is used if (mono_type_generic_inst_is_valuetype (type)) mono_type = MONO_TYPE_VALUETYPE; @@ -994,7 +995,7 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result, int r break; case MONO_TYPE_VALUETYPE: { - int obj_size = mono_object_get_size (obj), + int obj_size = mono_object_get_size (obj), required_size = (sizeof (int)) + (sizeof (MonoType *)) + obj_size; // Check whether this struct has special-case marshaling @@ -1104,7 +1105,7 @@ mono_wasm_enable_on_demand_gc (int enable) } EMSCRIPTEN_KEEPALIVE MonoString * -mono_wasm_intern_string (MonoString *string) +mono_wasm_intern_string (MonoString *string) { return mono_string_intern (string); } @@ -1156,12 +1157,12 @@ mono_wasm_unbox_rooted (MonoObject *obj) return mono_object_unbox (obj); } -EMSCRIPTEN_KEEPALIVE char * +EMSCRIPTEN_KEEPALIVE char * mono_wasm_get_type_name (MonoType * typePtr) { return mono_type_get_name_full (typePtr, MONO_TYPE_NAME_FORMAT_REFLECTION); } -EMSCRIPTEN_KEEPALIVE char * +EMSCRIPTEN_KEEPALIVE char * mono_wasm_get_type_aqn (MonoType * typePtr) { return mono_type_get_name_full (typePtr, MONO_TYPE_NAME_FORMAT_ASSEMBLY_QUALIFIED); } diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index e8a89bd11a574a..c8760a0c7a994f 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,36 +1,44 @@ import { Module } from "./imports"; -import { VoidPtr, NativePointer } from "./types/emscripten"; +import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten"; -const _temp_mallocs: Array | null> = []; +const alloca_stack: Array = []; +const alloca_buffer_size = 32 * 1024; +let alloca_base: VoidPtr, alloca_offset: VoidPtr, alloca_limit: VoidPtr; + +function _ensure_allocated(): void { + if (alloca_base) + return; + alloca_base = Module._malloc(alloca_buffer_size); + alloca_offset = alloca_base; + alloca_limit = (alloca_base + alloca_buffer_size); +} export function temp_malloc(size: number): VoidPtr { - if (!_temp_mallocs || !_temp_mallocs.length) + _ensure_allocated(); + if (!alloca_stack.length) throw new Error("No temp frames have been created at this point"); - const frame = _temp_mallocs[_temp_mallocs.length - 1] || []; - const result = Module._malloc(size); - frame.push(result); - _temp_mallocs[_temp_mallocs.length - 1] = frame; + const result = alloca_offset; + alloca_offset += size; + if (alloca_offset >= alloca_limit) + throw new Error("Out of temp storage space"); return result; } export function _create_temp_frame(): void { - _temp_mallocs.push(null); + _ensure_allocated(); + alloca_stack.push(alloca_offset); } export function _release_temp_frame(): void { - if (!_temp_mallocs.length) + if (!alloca_stack.length) throw new Error("No temp frames have been created at this point"); - const frame = _temp_mallocs.pop(); - if (!frame) - return; - - for (let i = 0, l = frame.length; i < l; i++) - Module._free(frame[i]); + alloca_offset = alloca_stack.pop(); } type _MemOffset = number | VoidPtr | NativePointer; +type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; export function setU8(offset: _MemOffset, value: number): void { Module.HEAPU8[offset] = value; @@ -40,8 +48,8 @@ export function setU16(offset: _MemOffset, value: number): void { Module.HEAPU16[offset >>> 1] = value; } -export function setU32(offset: _MemOffset, value: number): void { - Module.HEAPU32[offset >>> 2] = value; +export function setU32 (offset: _MemOffset, value: _NumberOrPointer) : void { + Module.HEAPU32[offset >>> 2] = value; } export function setI8(offset: _MemOffset, value: number): void { @@ -52,8 +60,8 @@ export function setI16(offset: _MemOffset, value: number): void { Module.HEAP16[offset >>> 1] = value; } -export function setI32(offset: _MemOffset, value: number): void { - Module.HEAP32[offset >>> 2] = value; +export function setI32 (offset: _MemOffset, value: _NumberOrPointer) : void { + Module.HEAP32[offset >>> 2] = value; } // NOTE: Accepts a number, not a BigInt, so values over Number.MAX_SAFE_INTEGER will be corrupted diff --git a/src/mono/wasm/runtime/method-binding.ts b/src/mono/wasm/runtime/method-binding.ts index 4827766f88c490..04010b20ab72be 100644 --- a/src/mono/wasm/runtime/method-binding.ts +++ b/src/mono/wasm/runtime/method-binding.ts @@ -2,11 +2,11 @@ // 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 } from "./types"; +import { MonoClass, MonoMethod, MonoObject, coerceNull, 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 { MarshalType, _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js"; +import { _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js"; import { _create_temp_frame, getI32, getU32, getF32, getF64, diff --git a/src/mono/wasm/runtime/roots.ts b/src/mono/wasm/runtime/roots.ts index 010732c23a8940..2d473fb22ff20f 100644 --- a/src/mono/wasm/runtime/roots.ts +++ b/src/mono/wasm/runtime/roots.ts @@ -10,6 +10,7 @@ let _scratch_root_buffer: WasmRootBuffer | null = null; let _scratch_root_free_indices: Int32Array | null = null; let _scratch_root_free_indices_count = 0; const _scratch_root_free_instances: WasmRoot[] = []; +const _external_root_free_instances: WasmExternalRoot[] = []; /** * Allocates a block of memory that can safely contain pointers into the managed heap. @@ -52,6 +53,26 @@ export function mono_wasm_new_root_buffer_from_pointer(offset: VoidPtr, capacity return new WasmRootBuffer(offset, capacity, false, name); } +/** + * 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(address: VoidPtr): WasmRoot { + let result: WasmExternalRoot; + + if (!address) + throw new Error("address must be a location in the native heap"); + + if (_external_root_free_instances.length > 0) { + result = _external_root_free_instances.pop()!; + result._set_address(address); + } else { + result = new WasmExternalRoot(address); + } + + return result; +} + /** * 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. @@ -68,7 +89,7 @@ export function mono_wasm_new_root(val const index = _mono_wasm_claim_scratch_index(); const buffer = _scratch_root_buffer; - result = new WasmRoot(buffer!, index); + result = new WasmJsOwnedRoot(buffer!, index); } if (value !== undefined) { @@ -239,7 +260,20 @@ export class WasmRootBuffer { } } -export class WasmRoot { +export interface WasmRoot { + get_address(): NativePointer; + get_address_32(): number; + get(): T; + set(value: T): T; + get value(): T; + set value(value: T); + valueOf(): T; + clear(): void; + release(): void; + toString(): string; +} + +class WasmJsOwnedRoot implements WasmRoot { private __buffer: WasmRootBuffer; private __index: number; @@ -301,3 +335,61 @@ export class WasmRoot { return `[root @${this.get_address()}]`; } } + +class WasmExternalRoot implements WasmRoot { + private __external_address: NativePointer = undefined; + private __external_address_32: number = undefined; + + constructor(address: NativePointer) { + this._set_address(address); + } + + _set_address(address: NativePointer): void { + this.__external_address = address; + this.__external_address_32 = address >>> 2; + } + + get_address(): NativePointer { + return this.__external_address; + } + + get_address_32(): number { + return this.__external_address_32; + } + + get(): T { + const result = Module.HEAPU32[this.__external_address_32]; + return result; + } + + set(value: T): T { + Module.HEAPU32[this.__external_address_32] = value; + return value; + } + + get value(): T { + return this.get(); + } + + set value(value: T) { + this.set(value); + } + + valueOf(): T { + return this.get(); + } + + clear(): void { + this.set(0); + } + + release(): void { + const maxPooledInstances = 128; + if (_external_root_free_instances.length < maxPooledInstances) + _external_root_free_instances.push(this); + } + + toString(): string { + return `[external root @${this.get_address()}]`; + } +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 02b4ce78e803af..b208aeedcd06a8 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -192,4 +192,48 @@ export function assert(condition: unknown, messsage: string): asserts condition if (!condition) { throw new Error(`Assert failed: ${messsage}`); } +} + +// see src/mono/wasm/driver.c MARSHAL_TYPE_xxx and Runtime.cs MarshalType +export enum MarshalType { + NULL = 0, + INT = 1, + FP64 = 2, + STRING = 3, + VT = 4, + DELEGATE = 5, + TASK = 6, + OBJECT = 7, + BOOL = 8, + ENUM = 9, + URI = 22, + SAFEHANDLE = 23, + ARRAY_BYTE = 10, + ARRAY_UBYTE = 11, + ARRAY_UBYTE_C = 12, + ARRAY_SHORT = 13, + ARRAY_USHORT = 14, + ARRAY_INT = 15, + ARRAY_UINT = 16, + ARRAY_FLOAT = 17, + ARRAY_DOUBLE = 18, + FP32 = 24, + UINT32 = 25, + INT64 = 26, + UINT64 = 27, + CHAR = 28, + STRING_INTERNED = 29, + VOID = 30, + ENUM64 = 31, + POINTER = 32, + SPAN_BYTE = 33, +} + +// see src/mono/wasm/driver.c MARSHAL_ERROR_xxx and Runtime.cs +export enum MarshalError { + BUFFER_TOO_SMALL = 512, + NULL_CLASS_POINTER = 513, + NULL_TYPE_POINTER = 514, + UNSUPPORTED_TYPE = 515, + FIRST = BUFFER_TOO_SMALL } \ No newline at end of file