diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs index 82d39086e2372c..5b5a8cf9f82fcd 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs @@ -611,20 +611,6 @@ public static void OnceAJSStringIsInternedItIsAlwaysUsedIfPossible() Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); } - [Fact] - public static void ManuallyInternString() - { - HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; - Utils.InvokeJS(@" - var sym = INTERNAL.mono_intern_string(""interned string 3""); - App.call_test_method (""InvokeString"", [ sym ], ""s""); - App.call_test_method (""InvokeString2"", [ sym ], ""s""); - "); - Assert.Equal("interned string 3", HelperMarshal._stringResource); - Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); - Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); - } - [Fact] public static void LargeStringsAreNotAutomaticallyLocatedInInternTable() { diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index a459b110725670..72717b8d45b45e 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -10,6 +10,7 @@ import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { AssetEntryInternal } from "./types/internal"; import { AssetEntry } from "./types"; import { InstantiateWasmSuccessCallback, VoidPtr } from "./types/emscripten"; +import { utf8BufferToString } from "./strings"; // this need to be run only after onRuntimeInitialized event, when the memory is ready export function instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array): void { @@ -157,7 +158,7 @@ export function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): b let manifest; try { - const manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + const manifestContent = utf8BufferToString(data, 8, manifestSize); manifest = JSON.parse(manifestContent); if (!(manifest instanceof Array)) return false; diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 044758caf4a975..0bc38cf21ad9ae 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -7,6 +7,8 @@ import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; import { mono_log_warn } from "./logging"; +import { localHeapViewU8 } from "./memory"; +import { utf8ToString } from "./strings"; const commands_received: any = new Map(); commands_received.remove = function (key: number): CommandResponse { const value = this.get(key); this.delete(key); return value; }; let _call_function_res_cache: any = {}; @@ -39,12 +41,12 @@ export function mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64S } export function mono_wasm_fire_debugger_agent_message_with_data(data: number, len: number): void { - const base64String = toBase64StringImpl(new Uint8Array(Module.HEAPU8.buffer, data, len)); + const base64String = toBase64StringImpl(new Uint8Array(localHeapViewU8().buffer, data, len)); mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64String); } export function mono_wasm_add_dbg_command_received(res_ok: boolean, id: number, buffer: number, buffer_len: number): void { - const dbg_command = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); + const dbg_command = new Uint8Array(localHeapViewU8().buffer, buffer, buffer_len); const base64String = toBase64StringImpl(dbg_command); const buffer_obj = { res_ok, @@ -66,8 +68,9 @@ function mono_wasm_malloc_and_set_debug_buffer(command_parameters: string) { _debugger_buffer = Module._malloc(_debugger_buffer_len); } const byteCharacters = atob(command_parameters); + const heapU8 = localHeapViewU8(); for (let i = 0; i < byteCharacters.length; i++) { - Module.HEAPU8[_debugger_buffer + i] = byteCharacters.charCodeAt(i); + heapU8[_debugger_buffer + i] = byteCharacters.charCodeAt(i); } } @@ -150,7 +153,7 @@ export function mono_wasm_debugger_attached(): void { export function mono_wasm_set_entrypoint_breakpoint(assembly_name: CharPtr, entrypoint_method_token: number): void { //keep these assignments, these values are used by BrowserDebugProxy - _assembly_name_str = Module.UTF8ToString(assembly_name).concat(".dll"); + _assembly_name_str = utf8ToString(assembly_name).concat(".dll"); _entrypoint_method_token = entrypoint_method_token; //keep this console.assert, otherwise optimization will remove the assignments // eslint-disable-next-line no-console @@ -341,7 +344,7 @@ export function mono_wasm_release_object(objectId: string): void { } export function mono_wasm_debugger_log(level: number, message_ptr: CharPtr): void { - const message = Module.UTF8ToString(message_ptr); + const message = utf8ToString(message_ptr); if (INTERNAL["logging"] && typeof INTERNAL.logging["debugger"] === "function") { INTERNAL.logging.debugger(level, message); diff --git a/src/mono/wasm/runtime/diagnostics/index.ts b/src/mono/wasm/runtime/diagnostics/index.ts index e5b154751bccfe..0db99e66fee9b2 100644 --- a/src/mono/wasm/runtime/diagnostics/index.ts +++ b/src/mono/wasm/runtime/diagnostics/index.ts @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import monoWasmThreads from "consts:monoWasmThreads"; +import MonoWasmThreads from "consts:monoWasmThreads"; + import type { DiagnosticOptions, } from "./shared/types"; @@ -14,7 +15,7 @@ import { mono_log_warn } from "../logging"; // called from C on the main thread export function mono_wasm_event_pipe_early_startup_callback(): void { - if (monoWasmThreads) { + if (MonoWasmThreads) { return; } } @@ -37,7 +38,7 @@ let diagnosticsInitialized = false; export async function mono_wasm_init_diagnostics(): Promise { if (diagnosticsInitialized) return; - if (!monoWasmThreads) { + if (!MonoWasmThreads) { mono_log_warn("ignoring diagnostics options because this runtime does not support diagnostics"); return; } else { diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts index 6970fbae7c4a1d..df5c92e8431c49 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts @@ -6,7 +6,7 @@ import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; import { PromiseAndController, assertNever } from "../../types/internal"; import { pthread_self } from "../../pthreads/worker"; -import { Module, createPromiseController } from "../../globals"; +import { createPromiseController } from "../../globals"; import cwraps from "../../cwraps"; import { EventPipeSessionIDImpl } from "../shared/types"; import { CharPtr } from "../../types/emscripten"; @@ -47,6 +47,7 @@ import { createBinaryCommandOKReply, } from "./ipc-protocol/serializer"; import { mono_log_error, mono_log_info, mono_log_debug, mono_log_warn } from "../../logging"; +import { utf8ToString } from "../../strings"; function addOneShotProtocolCommandEventListener(src: EventTarget): Promise { return new Promise((resolve) => { @@ -283,7 +284,7 @@ function parseProtocolCommand(data: ArrayBuffer | BinaryProtocolCommand): ParseC /// Called by the runtime to initialize the diagnostic server workers export function mono_wasm_diagnostic_server_on_server_thread_created(websocketUrlPtr: CharPtr): void { - const websocketUrl = Module.UTF8ToString(websocketUrlPtr); + const websocketUrl = utf8ToString(websocketUrlPtr); mono_log_debug(`mono_wasm_diagnostic_server_on_server_thread_created, url ${websocketUrl}`); let mock: PromiseAndController | undefined = undefined; if (monoDiagnosticsMock && websocketUrl.startsWith("mock:")) { diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/socket-connection.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/socket-connection.ts index 15be79ef232cdb..d02ae16e3403a0 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/socket-connection.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/socket-connection.ts @@ -3,9 +3,9 @@ import { assertNever } from "../../types/internal"; import { VoidPtr } from "../../types/emscripten"; -import { Module } from "../../globals"; import type { CommonSocket } from "./common-socket"; import { mono_log_debug, mono_log_warn } from "../../logging"; +import { localHeapViewU8 } from "../../memory"; enum ListenerState { Sending, Closed, @@ -21,7 +21,7 @@ class SocketGuts { const buf = new ArrayBuffer(size); const view = new Uint8Array(buf); // Can we avoid this copy? - view.set(new Uint8Array(Module.HEAPU8.buffer, data as unknown as number, size)); + view.set(new Uint8Array(localHeapViewU8().buffer, data as unknown as number, size)); this.socket.send(buf); } } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index d5cbed9f3354f7..724d2971747b7b 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -18,14 +18,23 @@ declare interface Int32Ptr extends NativePointer { __brand: "Int32Ptr"; } declare interface EmscriptenModule { + /** @deprecated Please use growableHeapI8() instead.*/ HEAP8: Int8Array; + /** @deprecated Please use growableHeapI16() instead.*/ HEAP16: Int16Array; + /** @deprecated Please use growableHeapI32() instead. */ HEAP32: Int32Array; + /** @deprecated Please use growableHeapI64() instead. */ HEAP64: BigInt64Array; + /** @deprecated Please use growableHeapU8() instead. */ HEAPU8: Uint8Array; + /** @deprecated Please use growableHeapU16() instead. */ HEAPU16: Uint16Array; + /** @deprecated Please use growableHeapU32() instead */ HEAPU32: Uint32Array; + /** @deprecated Please use growableHeapF32() instead */ HEAPF32: Float32Array; + /** @deprecated Please use growableHeapF64() instead. */ HEAPF64: Float64Array; _malloc(size: number): VoidPtr; _free(ptr: VoidPtr): void; @@ -39,6 +48,7 @@ declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; addFunction(fn: Function, signature: string): number; @@ -231,6 +241,15 @@ type APIType = { getHeapI64Big: (offset: NativePointer) => bigint; getHeapF32: (offset: NativePointer) => number; getHeapF64: (offset: NativePointer) => number; + localHeapViewI8: () => Int8Array; + localHeapViewI16: () => Int16Array; + localHeapViewI32: () => Int32Array; + localHeapViewI64Big: () => BigInt64Array; + localHeapViewU8: () => Uint8Array; + localHeapViewU16: () => Uint16Array; + localHeapViewU32: () => Uint32Array; + localHeapViewF32: () => Float32Array; + localHeapViewF64: () => Float64Array; }; type RuntimeAPI = { /** diff --git a/src/mono/wasm/runtime/export-api.ts b/src/mono/wasm/runtime/export-api.ts index 68b5a4f65ba659..1b1b997429ac81 100644 --- a/src/mono/wasm/runtime/export-api.ts +++ b/src/mono/wasm/runtime/export-api.ts @@ -5,7 +5,7 @@ import type { MonoConfig, APIType } from "./types"; import { mono_wasm_get_assembly_exports } from "./invoke-cs"; import { mono_wasm_set_module_imports } from "./invoke-js"; -import { getB32, getF32, getF64, getI16, getI32, getI52, getI64Big, getI8, getU16, getU32, getU52, getU8, setB32, setF32, setF64, setI16, setI32, setI52, setI64Big, setI8, setU16, setU32, setU52, setU8 } from "./memory"; +import { getB32, getF32, getF64, getI16, getI32, getI52, getI64Big, getI8, getU16, getU32, getU52, getU8, localHeapViewF32, localHeapViewF64, localHeapViewI16, localHeapViewI32, localHeapViewI64Big, localHeapViewI8, localHeapViewU16, localHeapViewU32, localHeapViewU8, setB32, setF32, setF64, setI16, setI32, setI52, setI64Big, setI8, setU16, setU32, setU52, setU8 } from "./memory"; import { mono_run_main, mono_run_main_and_exit } from "./run"; import { mono_wasm_setenv } from "./startup"; import { runtimeHelpers } from "./globals"; @@ -44,6 +44,15 @@ export function export_api(): any { getHeapI64Big: getI64Big, getHeapF32: getF32, getHeapF64: getF64, + localHeapViewU8: localHeapViewU8, + localHeapViewU16: localHeapViewU16, + localHeapViewU32: localHeapViewU32, + localHeapViewI8: localHeapViewI8, + localHeapViewI16: localHeapViewI16, + localHeapViewI32: localHeapViewI32, + localHeapViewI64Big: localHeapViewI64Big, + localHeapViewF32: localHeapViewF32, + localHeapViewF64: localHeapViewF64, }; return api; } diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index 497198c88b4375..66540624866639 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -7,7 +7,6 @@ import { mono_wasm_send_dbg_command_with_parms, mono_wasm_send_dbg_command, mono import { http_wasm_supports_streaming_response, http_wasm_create_abort_controler, http_wasm_abort_request, http_wasm_abort_response, http_wasm_fetch, http_wasm_fetch_bytes, http_wasm_get_response_header_names, http_wasm_get_response_header_values, http_wasm_get_response_bytes, http_wasm_get_response_length, http_wasm_get_streamed_response_bytes } from "./http"; import { exportedRuntimeAPI, Module, runtimeHelpers } from "./globals"; import { get_property, set_property, has_property, get_typeof_property, get_global_this, dynamic_import } from "./invoke-js"; -import { mono_intern_string } from "./strings"; import { mono_wasm_stringify_as_error_with_stack } from "./logging"; import { ws_wasm_create, ws_wasm_open, ws_wasm_send, ws_wasm_receive, ws_wasm_close, ws_wasm_abort } from "./web-socket"; import { mono_wasm_get_loaded_files } from "./assets"; @@ -23,7 +22,6 @@ export function export_internal(): any { mono_wasm_profiler_init_aot: cwraps.mono_wasm_profiler_init_aot, mono_wasm_profiler_init_browser: cwraps.mono_wasm_profiler_init_browser, mono_wasm_exec_regression: cwraps.mono_wasm_exec_regression, - mono_intern_string, // MarshalTests.cs // with mono_wasm_debugger_log and mono_wasm_trace_logger logging: undefined, diff --git a/src/mono/wasm/runtime/hybrid-globalization/change-case.ts b/src/mono/wasm/runtime/hybrid-globalization/change-case.ts index fddd80e9823829..6472f819e21264 100644 --- a/src/mono/wasm/runtime/hybrid-globalization/change-case.ts +++ b/src/mono/wasm/runtime/hybrid-globalization/change-case.ts @@ -1,26 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { Module } from "../globals"; -import { setU16 } from "../memory"; import { mono_wasm_new_external_root } from "../roots"; -import { conv_string_root } from "../strings"; +import { monoStringToString, utf16ToStringLoop, stringToUTF16 } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; -export function mono_wasm_change_case_invariant(src: number, srcLength: number, dst: number, dstLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : void{ +export function mono_wasm_change_case_invariant(src: number, srcLength: number, dst: number, dstLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): void { const exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const input = get_utf16_string(src, srcLength); + try { + const input = utf16ToStringLoop(src, src + 2 * srcLength); let result = toUpper ? input.toUpperCase() : input.toLowerCase(); // Unicode defines some codepoints which expand into multiple codepoints, // originally we do not support this expansion if (result.length > dstLength) result = input; - - for (let i = 0; i < result.length; i++) - setU16(dst + i*2, result.charCodeAt(i)); + stringToUTF16(dst, dst + 2 * dstLength, result); wrap_no_error_root(is_exception, exceptionRoot); } catch (ex: any) { @@ -31,20 +27,19 @@ export function mono_wasm_change_case_invariant(src: number, srcLength: number, } } -export function mono_wasm_change_case(culture: MonoStringRef, src: number, srcLength: number, dst: number, destLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : void{ +export function mono_wasm_change_case(culture: MonoStringRef, src: number, srcLength: number, dst: number, dstLength: number, toUpper: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): void { const cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const cultureName = conv_string_root(cultureRoot); + try { + const cultureName = monoStringToString(cultureRoot); if (!cultureName) throw new Error("Cannot change case, the culture name is null."); - const input = get_utf16_string(src, srcLength); + const input = utf16ToStringLoop(src, src + 2 * srcLength); let result = toUpper ? input.toLocaleUpperCase(cultureName) : input.toLocaleLowerCase(cultureName); - if (result.length > destLength) + if (result.length > dstLength) result = input; - for (let i = 0; i < destLength; i++) - setU16(dst + i*2, result.charCodeAt(i)); + stringToUTF16(dst, dst + 2 * dstLength, result); wrap_no_error_root(is_exception, exceptionRoot); } catch (ex: any) { @@ -54,12 +49,4 @@ export function mono_wasm_change_case(culture: MonoStringRef, src: number, srcLe cultureRoot.release(); exceptionRoot.release(); } -} - -function get_utf16_string(ptr: number, length: number): string{ - const view = new Uint16Array(Module.HEAPU16.buffer, ptr, length); - let string = ""; - for (let i = 0; i < length; i++) - string += String.fromCharCode(view[i]); - return string; -} +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/hybrid-globalization/collations.ts b/src/mono/wasm/runtime/hybrid-globalization/collations.ts index 0df5b6dd641488..9ec14b2a31dde9 100644 --- a/src/mono/wasm/runtime/hybrid-globalization/collations.ts +++ b/src/mono/wasm/runtime/hybrid-globalization/collations.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { mono_wasm_new_external_root } from "../roots"; -import { conv_string_root, string_decoder } from "../strings"; +import { monoStringToString, utf16ToString } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; @@ -10,13 +10,13 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; const COMPARISON_ERROR = -2; const INDEXING_ERROR = -1; -export function mono_wasm_compare_string(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{ +export function mono_wasm_compare_string(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const cultureName = conv_string_root(cultureRoot); - const string1 = string_decoder.decode(str1, (str1 + 2*str1Length)); - const string2 = string_decoder.decode(str2, (str2 + 2*str2Length)); + try { + const cultureName = monoStringToString(cultureRoot); + const string1 = utf16ToString(str1, (str1 + 2 * str1Length)); + const string2 = utf16ToString(str2, (str2 + 2 * str2Length)); const casePicker = (options & 0x1f); const locale = cultureName ? cultureName : undefined; wrap_no_error_root(is_exception, exceptionRoot); @@ -32,11 +32,11 @@ export function mono_wasm_compare_string(culture: MonoStringRef, str1: number, s } } -export function mono_wasm_starts_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{ +export function mono_wasm_starts_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const cultureName = conv_string_root(cultureRoot); + try { + const cultureName = monoStringToString(cultureRoot); const prefix = decode_to_clean_string(str2, str2Length); // no need to look for an empty string if (prefix.length == 0) @@ -63,11 +63,11 @@ export function mono_wasm_starts_with(culture: MonoStringRef, str1: number, str1 } } -export function mono_wasm_ends_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{ +export function mono_wasm_ends_with(culture: MonoStringRef, str1: number, str1Length: number, str2: number, str2Length: number, options: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const cultureName = conv_string_root(cultureRoot); + try { + const cultureName = monoStringToString(cultureRoot); const suffix = decode_to_clean_string(str2, str2Length); if (suffix.length == 0) return 1; // true @@ -94,26 +94,24 @@ export function mono_wasm_ends_with(culture: MonoStringRef, str1: number, str1Le } } -export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, needleLength: number, srcPtr: number, srcLength: number, options: number, fromBeginning: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number{ +export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, needleLength: number, srcPtr: number, srcLength: number, options: number, fromBeginning: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(ex_address); try { - const needle = string_decoder.decode(needlePtr, (needlePtr + 2*needleLength)); + const needle = utf16ToString(needlePtr, (needlePtr + 2 * needleLength)); // no need to look for an empty string - if (clean_string(needle).length == 0) - { + if (clean_string(needle).length == 0) { wrap_no_error_root(is_exception, exceptionRoot); return fromBeginning ? 0 : srcLength; } - const source = string_decoder.decode(srcPtr, (srcPtr + 2*srcLength)); + const source = utf16ToString(srcPtr, (srcPtr + 2 * srcLength)); // no need to look in an empty string - if (clean_string(source).length == 0) - { + if (clean_string(source).length == 0) { wrap_no_error_root(is_exception, exceptionRoot); return fromBeginning ? 0 : srcLength; } - const cultureName = conv_string_root(cultureRoot); + const cultureName = monoStringToString(cultureRoot); const locale = cultureName ? cultureName : undefined; const casePicker = (options & 0x1f); @@ -125,8 +123,7 @@ export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, ne let segmentWidth = 0; let index = 0; let nextIndex = 0; - while (!stop) - { + while (!stop) { // we need to restart the iterator in this outer loop because we have shifted it in the inner loop const iteratorSrc = segmenter.segment(source.slice(i, source.length))[Symbol.iterator](); let srcNext = iteratorSrc.next(); @@ -137,19 +134,15 @@ export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, ne let matchFound = check_match_found(srcNext.value.segment, needleSegments[0], locale, casePicker); index = nextIndex; srcNext = iteratorSrc.next(); - if (srcNext.done) - { + if (srcNext.done) { result = matchFound ? index : result; break; } segmentWidth = srcNext.value.index; nextIndex = index + segmentWidth; - if (matchFound) - { - for(let j=1; jstrPtr, (strPtr + 2*strLen)); +function decode_to_clean_string(strPtr: number, strLen: number) { + const str = utf16ToString(strPtr, (strPtr + 2 * strLen)); return clean_string(str); } -function clean_string(str: string) -{ +function clean_string(str: string) { const nStr = str.normalize(); return nStr.replace(/[\u200B-\u200D\uFEFF\0]/g, ""); } diff --git a/src/mono/wasm/runtime/hybrid-globalization/normalization.ts b/src/mono/wasm/runtime/hybrid-globalization/normalization.ts index a3e92e252817f6..2faf9e0b66a992 100644 --- a/src/mono/wasm/runtime/hybrid-globalization/normalization.ts +++ b/src/mono/wasm/runtime/hybrid-globalization/normalization.ts @@ -1,9 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { setU16 } from "../memory"; import { mono_wasm_new_external_root } from "../roots"; -import { conv_string_root } from "../strings"; +import { monoStringToString, stringToUTF16 } from "../strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { Int32Ptr } from "../types/emscripten"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; @@ -11,11 +10,11 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; const NORMALIZATION_FORM_MAP = [undefined, "NFC", "NFD", undefined, undefined, "NFKC", "NFKD"]; const ERROR = -1; -export function mono_wasm_is_normalized(normalizationForm: number, inputStr: MonoStringRef, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{ +export function mono_wasm_is_normalized(normalizationForm: number, inputStr: MonoStringRef, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const inputRoot = mono_wasm_new_external_root(inputStr), exceptionRoot = mono_wasm_new_external_root(ex_address); - try{ - const jsString = conv_string_root(inputRoot); + try { + const jsString = monoStringToString(inputRoot); if (!jsString) throw new Error("Invalid string was received."); @@ -33,11 +32,11 @@ export function mono_wasm_is_normalized(normalizationForm: number, inputStr: Mon } } -export function mono_wasm_normalize_string(normalizationForm: number, inputStr: MonoStringRef, dstPtr: number, dstLength: number, is_exception: Int32Ptr, ex_address: MonoObjectRef) : number{ +export function mono_wasm_normalize_string(normalizationForm: number, inputStr: MonoStringRef, dstPtr: number, dstLength: number, is_exception: Int32Ptr, ex_address: MonoObjectRef): number { const inputRoot = mono_wasm_new_external_root(inputStr), exceptionRoot = mono_wasm_new_external_root(ex_address); try { - const jsString = conv_string_root(inputRoot); + const jsString = monoStringToString(inputRoot); if (!jsString) throw new Error("Invalid string was received."); @@ -47,8 +46,7 @@ export function mono_wasm_normalize_string(normalizationForm: number, inputStr: // increase the dest buffer if (result.length > dstLength) return result.length; - for (let i = 0; i < result.length; i++) - setU16(dstPtr + i*2, result.charCodeAt(i)); + stringToUTF16(dstPtr, dstPtr + 2 * dstLength, result); return result.length; } catch (ex) { wrap_error_root(is_exception, ex, exceptionRoot); diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index fa9099d73bf4b9..c17987ebbf976d 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -12,7 +12,7 @@ import { bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, } from "./marshal"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots"; -import { conv_string_root, string_decoder } from "./strings"; +import { monoStringToString, monoStringToStringUnsafe } from "./strings"; import { MonoObjectRef, MonoStringRef, MonoString, MonoObject, MonoMethod, JSMarshalerArguments, JSFunctionSignature, BoundMarshalerToCs, BoundMarshalerToJs, VoidPtrNull, MonoObjectRefNull, MonoObjectNull } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; @@ -31,7 +31,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, mono_assert(version === 1, () => `Signature version ${version} mismatch.`); const args_count = get_signature_argument_count(signature); - const js_fqn = conv_string_root(fqn_root)!; + const js_fqn = monoStringToString(fqn_root)!; mono_assert(js_fqn, "fully_qualified_name must be string"); mono_log_debug(`Binding [JSExport] ${js_fqn}`); @@ -246,7 +246,7 @@ type BindingClosure = { export function invoke_method_and_handle_exception(method: MonoMethod, args: JSMarshalerArguments): void { assert_synchronization_context(); const fail = cwraps.mono_wasm_invoke_method_bound(method, args); - if (fail) throw new Error("ERR24: Unexpected error: " + string_decoder.copy(fail)); + if (fail) throw new Error("ERR24: Unexpected error: " + monoStringToStringUnsafe(fail)); if (is_args_exception(args)) { const exc = get_arg(args, 0); throw marshal_exception_to_js(exc); @@ -301,7 +301,7 @@ export async function mono_wasm_get_assembly_exports(assembly: string): Promise< try { cwraps.mono_wasm_invoke_method_ref(method, MonoObjectRefNull, VoidPtrNull, outException.address, outResult.address); if (outException.value !== MonoObjectNull) { - const msg = conv_string_root(outResult)!; + const msg = monoStringToString(outResult)!; throw new Error(msg); } } diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index a1fc9ae6e42a90..e1caf2475d754a 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -5,8 +5,8 @@ import BuildConfiguration from "consts:configuration"; import { marshal_exception_to_cs, bind_arg_marshal_to_cs } from "./marshal-to-cs"; import { get_signature_argument_count, bound_js_function_symbol, get_sig, get_signature_version, get_signature_type, imported_js_function_symbol } from "./marshal"; -import { setI32_unchecked } from "./memory"; -import { conv_string_root, js_string_to_mono_string_root } from "./strings"; +import { setI32, setI32_unchecked, receiveWorkerHeapViews } from "./memory"; +import { monoStringToString, stringToMonoStringRoot } from "./strings"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef, JSFunctionSignature, JSMarshalerArguments, WasmRoot, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType } from "./types/internal"; import { Int32Ptr } from "./types/emscripten"; import { INTERNAL, Module } from "./globals"; @@ -29,9 +29,9 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const version = get_signature_version(signature); mono_assert(version === 1, () => `Signature version ${version} mismatch.`); - const js_function_name = conv_string_root(function_name_root)!; + const js_function_name = monoStringToString(function_name_root)!; const mark = startMeasure(); - const js_module_name = conv_string_root(module_name_root)!; + const js_module_name = monoStringToString(module_name_root)!; mono_log_debug(`Binding [JSImport] ${js_function_name} from ${js_module_name} module`); const fn = mono_wasm_lookup_function(js_function_name, js_module_name); @@ -95,11 +95,11 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ (bound_fn)[imported_js_function_symbol] = true; const fn_handle = fn_wrapper_by_fn_handle.length; fn_wrapper_by_fn_handle.push(bound_fn); - setI32_unchecked(function_js_handle, fn_handle); + setI32(function_js_handle, fn_handle); wrap_no_error_root(is_exception, resultRoot); endMeasure(mark, MeasuredBlock.bindJsFunction, js_function_name); } catch (ex: any) { - setI32_unchecked(function_js_handle, 0); + setI32(function_js_handle, 0); Module.err(ex.toString()); wrap_error_root(is_exception, ex, resultRoot); } finally { @@ -362,6 +362,7 @@ function _wrap_error_flag(is_exception: Int32Ptr | null, ex: any): string { res = mono_wasm_symbolicate_string(res); } if (is_exception) { + receiveWorkerHeapViews(); setI32_unchecked(is_exception, 1); } return res; @@ -369,12 +370,13 @@ function _wrap_error_flag(is_exception: Int32Ptr | null, ex: any): string { export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: WasmRoot): void { const res = _wrap_error_flag(is_exception, ex); - js_string_to_mono_string_root(res, result); + stringToMonoStringRoot(res, result); } // to set out parameters of icalls export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmRoot): void { if (is_exception) { + receiveWorkerHeapViews(); setI32_unchecked(is_exception, 0); } if (result) { diff --git a/src/mono/wasm/runtime/jiterpreter-interp-entry.ts b/src/mono/wasm/runtime/jiterpreter-interp-entry.ts index 6f0dac1bcd4e9f..f15bb676d8d11c 100644 --- a/src/mono/wasm/runtime/jiterpreter-interp-entry.ts +++ b/src/mono/wasm/runtime/jiterpreter-interp-entry.ts @@ -16,6 +16,7 @@ import { JiterpreterOptions, getMemberOffset, JiterpMember } from "./jiterpreter-support"; import { mono_log_error, mono_log_info } from "./logging"; +import { utf8ToString } from "./strings"; // Controls miscellaneous diagnostic output. const trace = 0; @@ -166,7 +167,7 @@ export function mono_interp_jit_wasm_entry_trampoline( const info = new TrampolineInfo( imethod, method, argumentCount, pParamTypes, - unbox, hasThisReference, hasReturnValue, Module.UTF8ToString(name), + unbox, hasThisReference, hasReturnValue, utf8ToString(name), defaultImplementation ); if (!fnTable) diff --git a/src/mono/wasm/runtime/jiterpreter-jit-call.ts b/src/mono/wasm/runtime/jiterpreter-jit-call.ts index e3e2f56d8e6478..8c98c06577669b 100644 --- a/src/mono/wasm/runtime/jiterpreter-jit-call.ts +++ b/src/mono/wasm/runtime/jiterpreter-jit-call.ts @@ -5,7 +5,7 @@ import { MonoType, MonoMethod } from "./types/internal"; import { NativePointer, Int32Ptr, VoidPtr } from "./types/emscripten"; import { Module, runtimeHelpers } from "./globals"; import { - getU8, getI32_unaligned, getU32_unaligned, setU32_unchecked + getU8, getI32_unaligned, getU32_unaligned, setU32_unchecked, receiveWorkerHeapViews } from "./memory"; import { WasmOpcode } from "./jiterpreter-opcodes"; import { @@ -18,6 +18,7 @@ import { } from "./jiterpreter-feature-detect"; import cwraps from "./cwraps"; import { mono_log_error, mono_log_info } from "./logging"; +import { utf8ToString } from "./strings"; // Controls miscellaneous diagnostic output. const trace = 0; @@ -149,7 +150,7 @@ class TrampolineInfo { if (useFullNames) { const pMethodName = method ? cwraps.mono_wasm_method_get_full_name(method) : 0; try { - suffix = Module.UTF8ToString(pMethodName); + suffix = utf8ToString(pMethodName); } finally { if (pMethodName) Module._free(pMethodName); @@ -185,6 +186,7 @@ export function mono_interp_invoke_wasm_jit_call_trampoline( try { thunk(ret_sp, sp, ftndesc, thrown); } catch (exc) { + receiveWorkerHeapViews(); setU32_unchecked(thrown, 1); } } @@ -264,6 +266,7 @@ export function mono_jiterp_do_jit_call_indirect( try { jitCallCb(_cb_data); } catch (exc) { + receiveWorkerHeapViews(); setU32_unchecked(_thrown, 1); } }; diff --git a/src/mono/wasm/runtime/jiterpreter-opcodes.ts b/src/mono/wasm/runtime/jiterpreter-opcodes.ts index e18877863ffead..0c41426620c0b4 100644 --- a/src/mono/wasm/runtime/jiterpreter-opcodes.ts +++ b/src/mono/wasm/runtime/jiterpreter-opcodes.ts @@ -3,8 +3,8 @@ // Keep this file in sync with mintops.def. The order and values need to match exactly. -import { Module } from "./globals"; import cwraps from "./cwraps"; +import { utf8ToString } from "./strings"; export const enum MintOpArgType { MintOpNoArgs = 0, @@ -56,7 +56,7 @@ export function getOpcodeName(opcode: number): string { let result = opcodeNameCache[opcode]; if (typeof (result) !== "string") { const pName = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Name); - opcodeNameCache[opcode] = result = Module.UTF8ToString(pName); + opcodeNameCache[opcode] = result = utf8ToString(pName); } return result; } diff --git a/src/mono/wasm/runtime/jiterpreter-support.ts b/src/mono/wasm/runtime/jiterpreter-support.ts index f65c228f2ebe72..e0b62832696e9d 100644 --- a/src/mono/wasm/runtime/jiterpreter-support.ts +++ b/src/mono/wasm/runtime/jiterpreter-support.ts @@ -7,6 +7,8 @@ import { WasmOpcode, WasmSimdOpcode } from "./jiterpreter-opcodes"; import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; import { mono_log_error, mono_log_info } from "./logging"; +import { localHeapViewU8 } from "./memory"; +import { utf8ToString } from "./strings"; export const maxFailures = 2, maxMemsetSize = 64, @@ -903,7 +905,7 @@ export class BlobBuilder { constructor() { this.capacity = 16 * 1024; this.buffer = Module._malloc(this.capacity); - Module.HEAPU8.fill(0, this.buffer, this.buffer + this.capacity); + localHeapViewU8().fill(0, this.buffer, this.buffer + this.capacity); this.size = 0; this.clear(); if (typeof (TextEncoder) === "function") @@ -919,7 +921,7 @@ export class BlobBuilder { throw new Error("Buffer full"); const result = this.size; - Module.HEAPU8[this.buffer + (this.size++)] = value; + localHeapViewU8()[this.buffer + (this.size++)] = value; return result; } @@ -1008,16 +1010,17 @@ export class BlobBuilder { if (typeof (count) !== "number") count = this.size; - Module.HEAPU8.copyWithin(destination.buffer + destination.size, this.buffer, this.buffer + count); + localHeapViewU8().copyWithin(destination.buffer + destination.size, this.buffer, this.buffer + count); destination.size += count; } appendBytes(bytes: Uint8Array, count?: number) { const result = this.size; - if (bytes.buffer === Module.HEAPU8.buffer) { + const heapU8 = localHeapViewU8(); + if (bytes.buffer === heapU8.buffer) { if (typeof (count) !== "number") count = bytes.length; - Module.HEAPU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + count); + heapU8.copyWithin(this.buffer + result, bytes.byteOffset, bytes.byteOffset + count); this.size += count; } else { if (typeof (count) === "number") @@ -1067,7 +1070,7 @@ export class BlobBuilder { } getArrayView(fullCapacity?: boolean) { - return new Uint8Array(Module.HEAPU8.buffer, this.buffer, fullCapacity ? this.capacity : this.size); + return new Uint8Array(localHeapViewU8().buffer, this.buffer, fullCapacity ? this.capacity : this.size); } } @@ -1483,7 +1486,7 @@ export function copyIntoScratchBuffer(src: NativePointer, size: number): NativeP if (size > 64) throw new Error("Scratch buffer size is 64"); - Module.HEAPU8.copyWithin(scratchBuffer, src, src + size); + localHeapViewU8().copyWithin(scratchBuffer, src, src + size); return scratchBuffer; } @@ -1882,7 +1885,7 @@ export function getOptions() { function updateOptions() { const pJson = cwraps.mono_jiterp_get_options_as_json(); - const json = Module.UTF8ToString(pJson); + const json = utf8ToString(pJson); Module._free(pJson); const blob = JSON.parse(json); diff --git a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts index d30e65c858c227..f4866665ac3614 100644 --- a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts +++ b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. import { MonoMethod } from "./types/internal"; -import { Module } from "./globals"; import { NativePointer } from "./types/emscripten"; import { getU16, getI16, - getU32_unaligned, getI32_unaligned, getF32_unaligned, getF64_unaligned, + getU32_unaligned, getI32_unaligned, getF32_unaligned, getF64_unaligned, localHeapViewU8, } from "./memory"; import { WasmOpcode, WasmSimdOpcode, @@ -3056,7 +3055,7 @@ function emit_simd( if (builder.options.enableSimd && getIsWasmSimdSupported()) { builder.local("pLocals"); builder.v128_const( - Module.HEAPU8.slice(ip + 4, ip + 4 + sizeOfV128) + localHeapViewU8().slice(ip + 4, ip + 4 + sizeOfV128) ); append_simd_store(builder, ip); } else { diff --git a/src/mono/wasm/runtime/jiterpreter.ts b/src/mono/wasm/runtime/jiterpreter.ts index 7f25b4f1d22bb6..caad542eeb668d 100644 --- a/src/mono/wasm/runtime/jiterpreter.ts +++ b/src/mono/wasm/runtime/jiterpreter.ts @@ -4,9 +4,7 @@ import { MonoMethod } from "./types/internal"; import { NativePointer } from "./types/emscripten"; import { Module, runtimeHelpers } from "./globals"; -import { - getU16, getU32_unaligned -} from "./memory"; +import { getU16, getU32_unaligned, localHeapViewU8 } from "./memory"; import { WasmOpcode, getOpcodeName } from "./jiterpreter-opcodes"; import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; @@ -22,6 +20,7 @@ import { generateWasmBody } from "./jiterpreter-trace-generator"; import { mono_log_error, mono_log_info, mono_log_warn } from "./logging"; +import { utf8ToString } from "./strings"; // Controls miscellaneous diagnostic output. export const trace = 0; @@ -979,17 +978,17 @@ export function mono_interp_tier_prepare_jiterpreter( let methodFullName: string | undefined; if (mostRecentOptions.estimateHeat || (instrumentedMethodNames.length > 0) || useFullNames) { const pMethodName = cwraps.mono_wasm_method_get_full_name(method); - methodFullName = Module.UTF8ToString(pMethodName); + methodFullName = utf8ToString(pMethodName); Module._free(pMethodName); } - const methodName = Module.UTF8ToString(cwraps.mono_wasm_method_get_name(method)); + const methodName = utf8ToString(cwraps.mono_wasm_method_get_name(method)); info.name = methodFullName || methodName; const imethod = getU32_unaligned(getMemberOffset(JiterpMember.Imethod) + frame); const backBranchCount = getU32_unaligned(getMemberOffset(JiterpMember.BackwardBranchOffsetsCount) + imethod); const pBackBranches = getU32_unaligned(getMemberOffset(JiterpMember.BackwardBranchOffsets) + imethod); let backwardBranchTable = backBranchCount - ? new Uint16Array(Module.HEAPU8.buffer, pBackBranches, backBranchCount) + ? new Uint16Array(localHeapViewU8().buffer, pBackBranches, backBranchCount) : null; // If we're compiling a trace that doesn't start at the beginning of a method, @@ -1086,7 +1085,7 @@ export function jiterpreter_dump_stats(b?: boolean, concise?: boolean) { for (let i = 0, c = Math.min(summaryStatCount, targetPointers.length); i < c; i++) { const targetMethod = Number(targetPointers[i]) | 0; const pMethodName = cwraps.mono_wasm_method_get_full_name(targetMethod); - const targetMethodName = Module.UTF8ToString(pMethodName); + const targetMethodName = utf8ToString(pMethodName); const hitCount = callTargetCounts[targetMethod]; Module._free(pMethodName); mono_log_info(`${targetMethodName} ${hitCount}`); diff --git a/src/mono/wasm/runtime/logging.ts b/src/mono/wasm/runtime/logging.ts index 75f4d83f8b7e6e..305fe6a9e22bab 100644 --- a/src/mono/wasm/runtime/logging.ts +++ b/src/mono/wasm/runtime/logging.ts @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. /* eslint-disable no-console */ -import { INTERNAL, Module, runtimeHelpers } from "./globals"; +import { INTERNAL, runtimeHelpers } from "./globals"; +import { utf8ToString } from "./strings"; import { CharPtr, VoidPtr } from "./types/emscripten"; const prefix = "MONO_WASM: "; @@ -91,11 +92,11 @@ export function mono_wasm_stringify_as_error_with_stack(err: Error | string): st } export function mono_wasm_trace_logger(log_domain_ptr: CharPtr, log_level_ptr: CharPtr, message_ptr: CharPtr, fatal: number, user_data: VoidPtr): void { - const origMessage = Module.UTF8ToString(message_ptr); + const origMessage = utf8ToString(message_ptr); const isFatal = !!fatal; - const domain = Module.UTF8ToString(log_domain_ptr); + const domain = utf8ToString(log_domain_ptr); const dataPtr = user_data; - const log_level = Module.UTF8ToString(log_level_ptr); + const log_level = utf8ToString(log_level_ptr); const message = `[MONO] ${origMessage}`; diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index 8915588d5545e3..9f18be87510275 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.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 monoWasmThreads from "consts:monoWasmThreads"; +import MonoWasmThreads from "consts:monoWasmThreads"; import { isThenable } from "./cancelable-promise"; import cwraps from "./cwraps"; import { assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; @@ -15,8 +15,8 @@ import { set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; -import { _zero_region } from "./memory"; -import { js_string_to_mono_string_root } from "./strings"; +import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; +import { stringToMonoStringRoot } from "./strings"; import { GCHandle, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/eventloop"; @@ -225,7 +225,7 @@ function _marshal_string_to_cs(arg: JSMarshalerArgument, value: string) { function _marshal_string_to_cs_impl(arg: JSMarshalerArgument, value: string) { const root = get_string_root(arg); try { - js_string_to_mono_string_root(value, root); + stringToMonoStringRoot(value, root); } finally { root.release(); @@ -309,16 +309,16 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise, _?: const holder = new TaskCallbackHolder(value); setup_managed_proxy(holder, gc_handle); - if (monoWasmThreads) + if (MonoWasmThreads) addUnsettledPromise(); value.then(data => { - if (monoWasmThreads) + if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs); teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work) }).catch(reason => { - if (monoWasmThreads) + if (MonoWasmThreads) settleUnsettledPromise(); runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined); teardown_managed_proxy(holder, gc_handle); // this holds holder alive for finalizer, until the promise is freed @@ -496,17 +496,17 @@ export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array< } else if (element_type == MarshalerType.Byte) { mono_assert(Array.isArray(value) || value instanceof Uint8Array, "Value is not an Array or Uint8Array"); - const targetView = Module.HEAPU8.subarray(buffer_ptr, buffer_ptr + length); + const targetView = localHeapViewU8().subarray(buffer_ptr, buffer_ptr + length); targetView.set(value); } else if (element_type == MarshalerType.Int32) { mono_assert(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array"); - const targetView = Module.HEAP32.subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); + const targetView = localHeapViewI32().subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); targetView.set(value); } else if (element_type == MarshalerType.Double) { mono_assert(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array"); - const targetView = Module.HEAPF64.subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); + const targetView = localHeapViewF64().subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); targetView.set(value); } else { diff --git a/src/mono/wasm/runtime/marshal-to-js.ts b/src/mono/wasm/runtime/marshal-to-js.ts index eebe3a9d53edc6..c9076b1890c602 100644 --- a/src/mono/wasm/runtime/marshal-to-js.ts +++ b/src/mono/wasm/runtime/marshal-to-js.ts @@ -12,10 +12,11 @@ import { get_signature_res_type, get_arg_u16, array_element_size, get_string_root, ArraySegment, Span, MemoryViewType, get_signature_arg3_type, get_arg_i64_big, get_arg_intptr, get_arg_element_type, JavaScriptMarshalerArgSize } from "./marshal"; -import { conv_string_root } from "./strings"; +import { monoStringToString } from "./strings"; import { JSHandleNull, GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { get_marshaler_to_cs_by_type } from "./marshal-to-cs"; +import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; export function initialize_marshalers_to_js(): void { if (cs_to_js_marshalers.size == 0) { @@ -301,7 +302,7 @@ export function marshal_string_to_js(arg: JSMarshalerArgument): string | null { } const root = get_string_root(arg); try { - const value = conv_string_root(root); + const value = monoStringToString(root); return value; } finally { root.release(); @@ -422,15 +423,15 @@ function _marshal_array_to_js_impl(arg: JSMarshalerArgument, element_type: Marsh } } else if (element_type == MarshalerType.Byte) { - const sourceView = Module.HEAPU8.subarray(buffer_ptr, buffer_ptr + length); + const sourceView = localHeapViewU8().subarray(buffer_ptr, buffer_ptr + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Int32) { - const sourceView = Module.HEAP32.subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); + const sourceView = localHeapViewI32().subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Double) { - const sourceView = Module.HEAPF64.subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); + const sourceView = localHeapViewF64().subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); result = sourceView.slice();//copy } else { diff --git a/src/mono/wasm/runtime/marshal.ts b/src/mono/wasm/runtime/marshal.ts index 9fc17c720bbe86..80fb44f7e9f960 100644 --- a/src/mono/wasm/runtime/marshal.ts +++ b/src/mono/wasm/runtime/marshal.ts @@ -3,7 +3,7 @@ import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; import { Module, runtimeHelpers } from "./globals"; -import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8 } from "./memory"; +import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType } from "./types/internal"; import { CharPtr, TypedArray, VoidPtr } from "./types/emscripten"; @@ -386,9 +386,9 @@ abstract class MemoryView implements IMemoryView { _unsafe_create_view(): TypedArray { // this view must be short lived so that it doesn't fail after wasm memory growth // for that reason we also don't give the view out to end user and provide set/slice/copyTo API instead - const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(Module.HEAPU8.buffer, this._pointer, this._length) - : this._viewType == MemoryViewType.Int32 ? new Int32Array(Module.HEAP32.buffer, this._pointer, this._length) - : this._viewType == MemoryViewType.Double ? new Float64Array(Module.HEAPF64.buffer, this._pointer, this._length) + const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(localHeapViewU8().buffer, this._pointer, this._length) + : this._viewType == MemoryViewType.Int32 ? new Int32Array(localHeapViewI32().buffer, this._pointer, this._length) + : this._viewType == MemoryViewType.Double ? new Float64Array(localHeapViewF64().buffer, this._pointer, this._length) : null; if (!view) throw new Error("NotImplementedException"); return view; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 83e94171a53274..d765d09830da9f 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,11 +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 monoWasmThreads from "consts:monoWasmThreads"; +import MonoWasmThreads from "consts:monoWasmThreads"; + import { MemOffset, NumberOrPointer } from "./types/internal"; import { VoidPtr, CharPtr } from "./types/emscripten"; import cwraps, { I52Error } from "./cwraps"; import { Module, runtimeHelpers } from "./globals"; +import { utf8ToString } from "./strings"; const alloca_stack: Array = []; const alloca_buffer_size = 32 * 1024; @@ -53,10 +55,11 @@ function assert_int_in_range(value: Number, min: Number, max: Number) { } export function _zero_region(byteOffset: VoidPtr, sizeBytes: number): void { - Module.HEAP8.fill(0, byteOffset, byteOffset + sizeBytes); + localHeapViewU8().fill(0, byteOffset, byteOffset + sizeBytes); } export function setB32(offset: MemOffset, value: number | boolean): void { + receiveWorkerHeapViews(); const boolValue = !!value; if (typeof (value) === "number") assert_int_in_range(value, 0, 1); @@ -65,43 +68,58 @@ export function setB32(offset: MemOffset, value: number | boolean): void { export function setU8(offset: MemOffset, value: number): void { assert_int_in_range(value, 0, 0xFF); + receiveWorkerHeapViews(); Module.HEAPU8[offset] = value; } export function setU16(offset: MemOffset, value: number): void { assert_int_in_range(value, 0, 0xFFFF); + receiveWorkerHeapViews(); Module.HEAPU16[offset >>> 1] = value; } +// does not check for growable heap +export function setU16_local(localView: Uint16Array, offset: MemOffset, value: number): void { + assert_int_in_range(value, 0, 0xFFFF); + localView[offset >>> 1] = value; +} + +// does not check for overflow nor growable heap export function setU16_unchecked(offset: MemOffset, value: number): void { Module.HEAPU16[offset >>> 1] = value; } +// does not check for overflow nor growable heap export function setU32_unchecked(offset: MemOffset, value: NumberOrPointer): void { Module.HEAPU32[offset >>> 2] = value; } export function setU32(offset: MemOffset, value: NumberOrPointer): void { assert_int_in_range(value, 0, 0xFFFF_FFFF); + receiveWorkerHeapViews(); Module.HEAPU32[offset >>> 2] = value; } export function setI8(offset: MemOffset, value: number): void { assert_int_in_range(value, -0x80, 0x7F); + receiveWorkerHeapViews(); Module.HEAP8[offset] = value; } export function setI16(offset: MemOffset, value: number): void { assert_int_in_range(value, -0x8000, 0x7FFF); + receiveWorkerHeapViews(); Module.HEAP16[offset >>> 1] = value; } export function setI32_unchecked(offset: MemOffset, value: number): void { + receiveWorkerHeapViews(); Module.HEAP32[offset >>> 2] = value; } export function setI32(offset: MemOffset, value: number): void { assert_int_in_range(value, -0x8000_0000, 0x7FFF_FFFF); + receiveWorkerHeapViews(); Module.HEAP32[offset >>> 2] = value; } @@ -124,6 +142,7 @@ function autoThrowI52(error: I52Error) { */ export function setI52(offset: MemOffset, value: number): void { mono_assert(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`); + receiveWorkerHeapViews(); const error = cwraps.mono_wasm_f64_to_i52(offset, value); autoThrowI52(error); } @@ -134,6 +153,7 @@ export function setI52(offset: MemOffset, value: number): void { export function setU52(offset: MemOffset, value: number): void { mono_assert(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`); mono_assert(value >= 0, "Can't convert negative Number into UInt64"); + receiveWorkerHeapViews(); const error = cwraps.mono_wasm_f64_to_u52(offset, value); autoThrowI52(error); } @@ -147,31 +167,47 @@ export function setI64Big(offset: MemOffset, value: bigint): void { export function setF32(offset: MemOffset, value: number): void { mono_assert(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`); + receiveWorkerHeapViews(); Module.HEAPF32[offset >>> 2] = value; } export function setF64(offset: MemOffset, value: number): void { mono_assert(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`); + receiveWorkerHeapViews(); Module.HEAPF64[offset >>> 3] = value; } export function getB32(offset: MemOffset): boolean { + receiveWorkerHeapViews(); return !!(Module.HEAP32[offset >>> 2]); } export function getU8(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAPU8[offset]; } export function getU16(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAPU16[offset >>> 1]; } +// does not check for growable heap +export function getU16_local(localView: Uint16Array, offset: MemOffset): number { + return localView[offset >>> 1]; +} + export function getU32(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAPU32[offset >>> 2]; } +// does not check for growable heap +export function getU32_local(localView: Uint32Array, offset: MemOffset): number { + return localView[offset >>> 2]; +} + export function getI32_unaligned(offset: MemOffset): number { return cwraps.mono_wasm_get_i32_unaligned(offset); } @@ -189,17 +225,30 @@ export function getF64_unaligned(offset: MemOffset): number { } export function getI8(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAP8[offset]; } export function getI16(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAP16[offset >>> 1]; } +// does not check for growable heap +export function getI16_local(localView: Int16Array, offset: MemOffset): number { + return localView[offset >>> 1]; +} + export function getI32(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAP32[offset >>> 2]; } +// does not check for growable heap +export function getI32_local(localView: Int32Array, offset: MemOffset): number { + return localView[offset >>> 2]; +} + /** * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER */ @@ -221,14 +270,17 @@ export function getU52(offset: MemOffset): number { } export function getI64Big(offset: MemOffset): bigint { + receiveWorkerHeapViews(); return Module.HEAP64[offset >>> 3]; } export function getF32(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAPF32[offset >>> 2]; } export function getF64(offset: MemOffset): number { + receiveWorkerHeapViews(); return Module.HEAPF64[offset >>> 3]; } @@ -253,7 +305,7 @@ export function withStackAlloc(bytesWanted: number, f: (ptr // and it is copied to that location. returns the address of the allocation. export function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr { const memoryOffset = Module._malloc(bytes.length); - const heapBytes = new Uint8Array(Module.HEAPU8.buffer, memoryOffset, bytes.length); + const heapBytes = new Uint8Array(localHeapViewU8().buffer, memoryOffset, bytes.length); heapBytes.set(bytes); return memoryOffset; } @@ -264,7 +316,7 @@ export function getEnv(name: string): string | null { charPtr = cwraps.mono_wasm_getenv(name); if (charPtr === 0) return null; - else return Module.UTF8ToString(charPtr); + else return utf8ToString(charPtr); } finally { if (charPtr) Module._free(charPtr); } @@ -272,15 +324,84 @@ export function getEnv(name: string): string | null { const BuiltinAtomics = globalThis.Atomics; -export const Atomics = monoWasmThreads ? { +export const Atomics = MonoWasmThreads ? { storeI32(offset: MemOffset, value: number): void { - - BuiltinAtomics.store(Module.HEAP32, offset >>> 2, value); + BuiltinAtomics.store(localHeapViewI32(), offset >>> 2, value); }, notifyI32(offset: MemOffset, count: number): void { - BuiltinAtomics.notify(Module.HEAP32, offset >>> 2, count); + BuiltinAtomics.notify(localHeapViewI32(), offset >>> 2, count); } } : { storeI32: setI32, notifyI32: () => { /*empty*/ } }; + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewI8(): Int8Array { + receiveWorkerHeapViews(); + return Module.HEAP8; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewI16(): Int16Array { + receiveWorkerHeapViews(); + return Module.HEAP16; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewI32(): Int32Array { + receiveWorkerHeapViews(); + return Module.HEAP32; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewI64Big(): BigInt64Array { + receiveWorkerHeapViews(); + return Module.HEAP64; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewU8(): Uint8Array { + receiveWorkerHeapViews(); + return Module.HEAPU8; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewU16(): Uint16Array { + receiveWorkerHeapViews(); + return Module.HEAPU16; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewU32(): Uint32Array { + receiveWorkerHeapViews(); + return Module.HEAPU32; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewF32(): Float32Array { + receiveWorkerHeapViews(); + return Module.HEAPF32; +} + +// returns memory view which is valid within current synchronous call stack +export function localHeapViewF64(): Float64Array { + receiveWorkerHeapViews(); + return Module.HEAPF64; +} + +// when we run with multithreading enabled, we need to make sure that the memory views are updated on each worker +// on non-MT build, this will be a no-op trimmed by rollup +export function receiveWorkerHeapViews() { + if (!MonoWasmThreads) return; + if (Module.wasmMemory!.buffer != Module.HEAPU8.buffer) { + runtimeHelpers.updateMemoryViews(); + } +} + +const sharedArrayBufferDefined = typeof SharedArrayBuffer !== "undefined"; +export function isSharedArrayBuffer(buffer: any): buffer is SharedArrayBuffer { + if (!MonoWasmThreads) return false; + // this condition should be eliminated by rollup on non-threading builds + return sharedArrayBufferDefined && buffer[Symbol.toStringTag] === "SharedArrayBuffer"; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/net6-legacy/buffers.ts b/src/mono/wasm/runtime/net6-legacy/buffers.ts index 3e5ca3114b26e7..d2b963ee58fde9 100644 --- a/src/mono/wasm/runtime/net6-legacy/buffers.ts +++ b/src/mono/wasm/runtime/net6-legacy/buffers.ts @@ -1,12 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { Module } from "../globals"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { mono_wasm_new_external_root } from "../roots"; import { MonoArray, MonoObjectRef, MonoObject } from "../types/internal"; import { Int32Ptr, TypedArray } from "../types/emscripten"; import { js_to_mono_obj_root } from "./js-to-cs"; +import { localHeapViewU8 } from "../memory"; // eslint-disable-next-line @typescript-eslint/no-unused-vars 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 { @@ -98,7 +98,7 @@ function typedarray_copy_from(typed_array: TypedArray, pinned_array: MonoArray, // offset index into the view const offset = begin * bytes_per_element; // Set view bytes to value from HEAPU8 - typedarrayBytes.set(Module.HEAPU8.subarray(pinned_array + offset, pinned_array + offset + num_of_bytes)); + typedarrayBytes.set(localHeapViewU8().subarray(pinned_array + offset, pinned_array + offset + num_of_bytes)); return num_of_bytes; } else { diff --git a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts index 28a1294df9b7e7..009aba9fb196f7 100644 --- a/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts +++ b/src/mono/wasm/runtime/net6-legacy/cs-to-js.ts @@ -10,7 +10,7 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { ManagedObject } from "../marshal"; import { getU32, getI32, getF32, getF64, setI32_unchecked } from "../memory"; import { mono_wasm_new_root, mono_wasm_new_external_root } from "../roots"; -import { conv_string_root, string_decoder } from "../strings"; +import { monoStringToString, monoStringToStringUnsafe } from "../strings"; import { legacyManagedExports } from "./corebindings"; import { legacyHelpers } from "./globals"; import { js_to_mono_obj_root } from "./js-to-cs"; @@ -54,7 +54,7 @@ function _unbox_mono_obj_root_with_known_nonprimitive_type_impl(root: WasmRoot(core_name), resultRoot = mono_wasm_new_external_root(result_address); try { - const js_name = conv_string_root(nameRoot); + const js_name = monoStringToString(nameRoot); if (!js_name) { wrap_error_root(is_exception, "Invalid name @" + nameRoot.value, resultRoot); return; @@ -351,5 +351,5 @@ export function get_js_owned_object_by_gc_handle_ref(gc_handle: GCHandle, result */ export function conv_string(mono_obj: MonoString): string | null { assert_legacy_interop(); - return string_decoder.copy(mono_obj); + return monoStringToStringUnsafe(mono_obj); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts index 9be5f370399c19..a6405c4984ad41 100644 --- a/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts +++ b/src/mono/wasm/runtime/net6-legacy/exports-legacy.ts @@ -8,7 +8,7 @@ import { mono_wasm_load_bytes_into_heap, setB32, setI8, setI16, setI32, setI52, import { mono_wasm_new_root_buffer, mono_wasm_new_root, mono_wasm_new_external_root, mono_wasm_release_roots } from "../roots"; import { mono_run_main, mono_run_main_and_exit } from "../run"; import { mono_wasm_setenv } from "../startup"; -import { js_string_to_mono_string, js_string_to_mono_string_root, conv_string_root } from "../strings"; +import { stringToMonoStringRoot, monoStringToString } from "../strings"; import { mono_array_to_js_array, unbox_mono_obj, unbox_mono_obj_root, mono_array_root_to_js_array, conv_string } from "./cs-to-js"; import { js_typed_array_to_array, js_to_mono_obj, js_typed_array_to_array_root, js_to_mono_obj_root } from "./js-to-cs"; import { mono_bind_static_method, mono_call_assembly_entry_point } from "./method-calls"; @@ -17,6 +17,7 @@ import { BINDINGType, MONOType } from "./export-types"; import { mono_wasm_load_data_archive } from "../assets"; import { mono_method_resolve } from "./method-binding"; import { runtimeHelpers } from "../globals"; +import { js_string_to_mono_string } from "./strings"; export function export_mono_api(): MONOType { return { @@ -96,10 +97,10 @@ export function export_binding_api(): BINDINGType { mono_obj_array_new_ref: null, mono_obj_array_set_ref: null, - js_string_to_mono_string_root, + js_string_to_mono_string_root: stringToMonoStringRoot, js_typed_array_to_array_root, js_to_mono_obj_root, - conv_string_root, + conv_string_root: monoStringToString, unbox_mono_obj_root, mono_array_root_to_js_array, }; diff --git a/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts b/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts index 290dd4f68b08ce..83df9bd39b97d2 100644 --- a/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts +++ b/src/mono/wasm/runtime/net6-legacy/js-to-cs.ts @@ -6,9 +6,9 @@ import { legacy_c_functions as cwraps } from "../cwraps"; import { js_owned_gc_handle_symbol, assert_not_disposed, cs_owned_js_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy, mono_wasm_release_cs_owned_object, teardown_managed_proxy, mono_wasm_get_jsobj_from_js_handle } from "../gc-handles"; import { Module } from "../globals"; import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; -import { setI32_unchecked, setU32_unchecked, setF64, setB32 } from "../memory"; +import { setI32_unchecked, setU32_unchecked, setF64, setB32, localHeapViewU8 } from "../memory"; import { mono_wasm_new_root, mono_wasm_release_roots, mono_wasm_new_external_root } from "../roots"; -import { js_string_to_mono_string_root, js_string_to_mono_string_interned_root } from "../strings"; +import { stringToMonoStringRoot, stringToInternedMonoStringRoot } from "../strings"; import { MonoObject, is_nullish, MonoClass, MonoArray, MonoObjectNull, JSHandle, MonoObjectRef, JSHandleNull, JSHandleDisposed, WasmRoot } from "../types/internal"; import { TypedArray, Int32Ptr } from "../types/emscripten"; import { has_backing_array_buffer } from "./buffers"; @@ -89,10 +89,10 @@ export function js_to_mono_obj_root(js_obj: any, result: WasmRoot, s return; } case typeof js_obj === "string": - js_string_to_mono_string_root(js_obj, result); + stringToMonoStringRoot(js_obj, result); return; case typeof js_obj === "symbol": - js_string_to_mono_string_interned_root(js_obj, result); + stringToInternedMonoStringRoot(js_obj, result); return; case typeof js_obj === "boolean": setB32(legacyHelpers._box_buffer, js_obj); @@ -150,10 +150,13 @@ function _extract_mono_obj_root(should_add_in_flight: boolean, js_obj: any, resu // https://github.com/Planeshifter/emscripten-examples/blob/master/01_PassingArrays/sum_post.js function js_typedarray_to_heap(typedArray: TypedArray) { + assert_legacy_interop(); const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT; const ptr = Module._malloc(numBytes); - const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes); + const heapU8 = localHeapViewU8(); + const heapBytes = new Uint8Array(heapU8.buffer, ptr, numBytes); heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, numBytes)); + // WARNING: returned memory view will get stale when linear memory grows on another thread. This is legacy interop so we don't try to fix it. The view will be fine when used in synchronous calls. return heapBytes; } diff --git a/src/mono/wasm/runtime/net6-legacy/method-binding.ts b/src/mono/wasm/runtime/net6-legacy/method-binding.ts index d75ca3ce5feb77..bd1ded599d71d3 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-binding.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-binding.ts @@ -6,7 +6,7 @@ import { Module } from "../globals"; import { parseFQN } from "../invoke-cs"; import { setI32, setU32, setF32, setF64, setU52, setI52, setB32, setI32_unchecked, setU32_unchecked, _zero_region, _create_temp_frame, getB32, getI32, getU32, getF32, getF64 } from "../memory"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "../roots"; -import { js_string_to_mono_string_root, js_string_to_mono_string_interned_root, conv_string_root } from "../strings"; +import { stringToMonoStringRoot, stringToInternedMonoStringRoot, monoStringToString } from "../strings"; import { MonoMethod, MonoObject, VoidPtrNull, MarshalType, MonoString, MonoObjectNull, WasmRootBuffer, WasmRoot } from "../types/internal"; import { VoidPtr } from "../types/emscripten"; import { legacyManagedExports } from "./corebindings"; @@ -81,8 +81,8 @@ function _create_rebindable_named_function(name: string, argumentNames: string[] export function _create_primitive_converters(): void { const result = primitiveConverters; result.set("m", { steps: [{}], size: 0 }); - result.set("s", { steps: [{ convert_root: js_string_to_mono_string_root.bind(Module) }], size: 0, needs_root: true }); - result.set("S", { steps: [{ convert_root: js_string_to_mono_string_interned_root.bind(Module) }], size: 0, needs_root: true }); + result.set("s", { steps: [{ convert_root: stringToMonoStringRoot.bind(Module) }], size: 0, needs_root: true }); + result.set("S", { steps: [{ convert_root: stringToInternedMonoStringRoot.bind(Module) }], 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 @@ -645,7 +645,7 @@ function _convert_exception_for_method_call(result: WasmRoot, except if (exception.value === MonoObjectNull) return null; - const msg = conv_string_root(result); + const msg = monoStringToString(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; diff --git a/src/mono/wasm/runtime/net6-legacy/method-calls.ts b/src/mono/wasm/runtime/net6-legacy/method-calls.ts index 61388647a837ba..e69883bd8f67e1 100644 --- a/src/mono/wasm/runtime/net6-legacy/method-calls.ts +++ b/src/mono/wasm/runtime/net6-legacy/method-calls.ts @@ -7,7 +7,7 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { _release_temp_frame } from "../memory"; import { mono_wasm_new_external_root, mono_wasm_new_root } from "../roots"; import { find_entry_point } from "../run"; -import { conv_string_root, js_string_to_mono_string_root } from "../strings"; +import { monoStringToString, stringToMonoStringRoot } from "../strings"; import { JSHandle, MonoStringRef, MonoObjectRef, MonoArray, MonoString, MonoObject, is_nullish, WasmRoot } from "../types/internal"; import { Int32Ptr, VoidPtr } from "../types/emscripten"; import { mono_array_root_to_js_array, unbox_mono_obj_root } from "./cs-to-js"; @@ -99,7 +99,7 @@ export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_na nameRoot = mono_wasm_new_external_root(method_name), resultRoot = mono_wasm_new_external_root(result_address); try { - const js_name = conv_string_root(nameRoot); + const js_name = monoStringToString(nameRoot); if (!js_name || (typeof (js_name) !== "string")) { wrap_error_root(is_exception, "ERR12: Invalid method name object @" + nameRoot.value, resultRoot); return; @@ -136,7 +136,7 @@ export function mono_wasm_get_object_property_ref(js_handle: JSHandle, property_ const nameRoot = mono_wasm_new_external_root(property_name), resultRoot = mono_wasm_new_external_root(result_address); try { - const js_name = conv_string_root(nameRoot); + const js_name = monoStringToString(nameRoot); if (!js_name) { wrap_error_root(is_exception, "Invalid property name object '" + nameRoot.value + "'", resultRoot); return; @@ -166,7 +166,7 @@ export function mono_wasm_set_object_property_ref(js_handle: JSHandle, property_ resultRoot = mono_wasm_new_external_root(result_address); try { - const property = conv_string_root(nameRoot); + const property = monoStringToString(nameRoot); if (!property) { wrap_error_root(is_exception, "Invalid property name object '" + property_name + "'", resultRoot); return; @@ -255,7 +255,7 @@ export function mono_wasm_get_global_object_ref(global_name: MonoStringRef, is_e const nameRoot = mono_wasm_new_external_root(global_name), resultRoot = mono_wasm_new_external_root(result_address); try { - const js_name = conv_string_root(nameRoot); + const js_name = monoStringToString(nameRoot); let globalObj; @@ -301,7 +301,7 @@ export function mono_wasm_invoke_js_blazor(exceptionMessage: Int32Ptr, callInfo: } catch (ex: any) { const exceptionJsString = ex.message + "\n" + ex.stack; const exceptionRoot = mono_wasm_new_root(); - js_string_to_mono_string_root(exceptionJsString, exceptionRoot); + stringToMonoStringRoot(exceptionJsString, exceptionRoot); exceptionRoot.copy_to_address(exceptionMessage); exceptionRoot.release(); return 0; diff --git a/src/mono/wasm/runtime/net6-legacy/strings.ts b/src/mono/wasm/runtime/net6-legacy/strings.ts new file mode 100644 index 00000000000000..a6c0790515c0df --- /dev/null +++ b/src/mono/wasm/runtime/net6-legacy/strings.ts @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { assert_legacy_interop } from "../pthreads/shared"; +import { mono_wasm_new_root } from "../roots"; +import { stringToMonoStringRoot } from "../strings"; +import { MonoString } from "../types/internal"; + +/** + * @deprecated Not GC or thread safe + */ +export function js_string_to_mono_string(string: string): MonoString { + assert_legacy_interop(); + const temp = mono_wasm_new_root(); + try { + stringToMonoStringRoot(string, temp); + return temp.value; + } finally { + temp.release(); + } +} diff --git a/src/mono/wasm/runtime/profiler.ts b/src/mono/wasm/runtime/profiler.ts index 021273c2f82b97..da627b50ec4a45 100644 --- a/src/mono/wasm/runtime/profiler.ts +++ b/src/mono/wasm/runtime/profiler.ts @@ -1,9 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_WEB, runtimeHelpers } from "./globals"; import { MonoMethod, AOTProfilerOptions, BrowserProfilerOptions } from "./types/internal"; import cwraps from "./cwraps"; +import { utf8ToString } from "./strings"; // Initialize the AOT profiler with OPTIONS. // Requires the AOT profiler to be linked into the app. @@ -91,7 +92,7 @@ export function mono_wasm_profiler_leave(method: MonoMethod): void { let methodName = methodNames.get(method as any); if (!methodName) { const chars = cwraps.mono_wasm_method_get_name(method); - methodName = Module.UTF8ToString(chars); + methodName = utf8ToString(chars); methodNames.set(method as any, methodName); } globalThis.performance.measure(methodName, options); diff --git a/src/mono/wasm/runtime/roots.ts b/src/mono/wasm/runtime/roots.ts index 30d0bdc0554c5e..21f9f18077b7c7 100644 --- a/src/mono/wasm/runtime/roots.ts +++ b/src/mono/wasm/runtime/roots.ts @@ -5,7 +5,7 @@ import cwraps from "./cwraps"; import { Module } from "./globals"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal"; -import { _zero_region } from "./memory"; +import { _zero_region, localHeapViewU32 } from "./memory"; const maxScratchRoots = 8192; let _scratch_root_buffer: WasmRootBuffer | null = null; @@ -217,7 +217,7 @@ export class WasmRootBufferImpl implements WasmRootBuffer { get(index: number): ManagedPointer { this._check_in_range(index); const offset = this.get_address_32(index); - return Module.HEAPU32[offset]; + return localHeapViewU32()[offset]; } set(index: number, value: ManagedPointer): ManagedPointer { @@ -232,7 +232,7 @@ export class WasmRootBufferImpl implements WasmRootBuffer { } _unsafe_get(index: number): number { - return Module.HEAPU32[this.__offset32 + index]; + return localHeapViewU32()[this.__offset32 + index]; } _unsafe_set(index: number, value: ManagedPointer | NativePointer): void { @@ -330,7 +330,7 @@ class WasmJsOwnedRoot implements WasmRoot { // .set performs an expensive write barrier, and that is not necessary in most cases // for clear since clearing a root cannot cause new objects to survive a GC const address32 = this.__buffer.get_address_32(this.__index); - Module.HEAPU32[address32] = 0; + localHeapViewU32()[address32] = 0; } release(): void { @@ -379,7 +379,7 @@ class WasmExternalRoot implements WasmRoot { } get(): T { - const result = Module.HEAPU32[this.__external_address_32]; + const result = localHeapViewU32()[this.__external_address_32]; return result; } @@ -425,7 +425,7 @@ class WasmExternalRoot implements WasmRoot { clear(): void { // .set performs an expensive write barrier, and that is not necessary in most cases // for clear since clearing a root cannot cause new objects to survive a GC - Module.HEAPU32[this.__external_address >>> 2] = 0; + localHeapViewU32()[this.__external_address >>> 2] = 0; } release(): void { diff --git a/src/mono/wasm/runtime/snapshot.ts b/src/mono/wasm/runtime/snapshot.ts index bce9062016d33a..abe4a8d94916f0 100644 --- a/src/mono/wasm/runtime/snapshot.ts +++ b/src/mono/wasm/runtime/snapshot.ts @@ -97,7 +97,7 @@ export async function storeMemorySnapshot(memory: ArrayBuffer) { } const copy = MonoWasmThreads // storing SHaredArrayBuffer in the cache is not working - ? (new Int8Array(memory)).slice(0) + ? (new Uint8Array(memory)).slice(0) : memory; const responseToCache = new Response(copy, { diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index f0bf309b32fd83..bbb7816c94eb73 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -14,7 +14,7 @@ import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; import { init_polyfills_async } from "./polyfills"; import * as pthreads_worker from "./pthreads/worker"; -import { string_decoder } from "./strings"; +import { strings_init, utf8ToString } from "./strings"; import { init_managed_exports } from "./managed-exports"; import { cwraps_internal } from "./exports-internal"; import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; @@ -31,6 +31,7 @@ import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legac import { BINDING, MONO } from "./net6-legacy/globals"; import { mono_log_debug, mono_log_warn } from "./logging"; import { install_synchronization_context } from "./pthreads/shared"; +import { localHeapViewU8 } from "./memory"; // default size if MonoConfig.pthreadPoolSize is undefined @@ -270,11 +271,6 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { bindings_init(); if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); - setTimeout(() => { - // when there are free CPU cycles - string_decoder.init_fields(); - }); - if (runtimeHelpers.config.startupOptions && INTERNAL.resourceLoader) { if (INTERNAL.resourceLoader.bootConfig.debugBuild && INTERNAL.resourceLoader.bootConfig.cacheBootResources) { INTERNAL.resourceLoader.logToConsole(); @@ -517,8 +513,9 @@ async function mono_wasm_before_memory_snapshot() { if (runtimeHelpers.loadedMemorySnapshot) { // get the bytes after we re-sized the memory, so that we don't have too much memory in use at the same time const memoryBytes = await getMemorySnapshot(); - mono_assert(memoryBytes!.byteLength === Module.HEAP8.byteLength, "Loaded memory is not the expected size"); - Module.HEAP8.set(new Int8Array(memoryBytes!), 0); + const heapU8 = localHeapViewU8(); + mono_assert(memoryBytes!.byteLength === heapU8.byteLength, "Loaded memory is not the expected size"); + heapU8.set(new Uint8Array(memoryBytes!), 0); mono_log_debug("Loaded WASM linear memory from browser cache"); // all things below are loaded from the snapshot @@ -551,7 +548,7 @@ async function mono_wasm_before_memory_snapshot() { if (runtimeHelpers.config.startupMemoryCache) { // this would install the mono_jiterp_do_jit_call_indirect cwraps.mono_jiterp_update_jit_call_dispatcher(-1); - await storeMemorySnapshot(Module.HEAP8.buffer); + await storeMemorySnapshot(localHeapViewU8().buffer); runtimeHelpers.storeMemorySnapshotPending = false; } @@ -585,6 +582,7 @@ export function bindings_init(): void { runtimeHelpers.mono_wasm_bindings_is_ready = true; try { const mark = startMeasure(); + strings_init(); init_managed_exports(); if (WasmEnableLegacyJsInterop && !disableLegacyJsInterop && !ENVIRONMENT_IS_PTHREAD) { init_legacy_exports(); @@ -607,14 +605,14 @@ export function mono_wasm_asm_loaded(assembly_name: CharPtr, assembly_ptr: numbe // Only trigger this codepath for assemblies loaded after app is ready if (runtimeHelpers.mono_wasm_runtime_is_ready !== true) return; - - const assembly_name_str = assembly_name !== CharPtrNull ? Module.UTF8ToString(assembly_name).concat(".dll") : ""; - const assembly_data = new Uint8Array(Module.HEAPU8.buffer, assembly_ptr, assembly_len); + const heapU8 = localHeapViewU8(); + const assembly_name_str = assembly_name !== CharPtrNull ? utf8ToString(assembly_name).concat(".dll") : ""; + const assembly_data = new Uint8Array(heapU8.buffer, assembly_ptr, assembly_len); const assembly_b64 = toBase64StringImpl(assembly_data); let pdb_b64; if (pdb_ptr) { - const pdb_data = new Uint8Array(Module.HEAPU8.buffer, pdb_ptr, pdb_len); + const pdb_data = new Uint8Array(heapU8.buffer, pdb_ptr, pdb_len); pdb_b64 = toBase64StringImpl(pdb_data); } diff --git a/src/mono/wasm/runtime/strings.ts b/src/mono/wasm/runtime/strings.ts index 3fd4e012d7de9a..ed488d2b70f532 100644 --- a/src/mono/wasm/runtime/strings.ts +++ b/src/mono/wasm/runtime/strings.ts @@ -2,165 +2,179 @@ // The .NET Foundation licenses this file to you under the MIT license. import { mono_wasm_new_root_buffer } from "./roots"; -import { MonoString, MonoStringNull, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal"; +import { MonoString, MonoStringNull, WasmRoot, WasmRootBuffer } from "./types/internal"; import { Module } from "./globals"; import cwraps from "./cwraps"; import { mono_wasm_new_root } from "./roots"; -import { getI32, getU32 } from "./memory"; +import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16 } from "./memory"; import { NativePointer, CharPtr } from "./types/emscripten"; -import { assert_legacy_interop } from "./pthreads/shared"; -export class StringDecoder { - - private mono_wasm_string_root: any; - private mono_text_decoder: TextDecoder | undefined | null; - private mono_wasm_string_decoder_buffer: NativePointer | undefined; - - 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); +export const interned_js_string_table = new Map(); +export const mono_wasm_empty_string = ""; +let mono_wasm_string_root: any; +let mono_wasm_string_decoder_buffer: NativePointer | undefined; +export const interned_string_table = new Map(); +let _empty_string_ptr: MonoString = 0; +const _interned_string_full_root_buffers = []; +let _interned_string_current_root_buffer: WasmRootBuffer | null = null; +let _interned_string_current_root_buffer_count = 0; +let _text_decoder_utf16: TextDecoder | undefined | null; +let _text_decoder_utf8_relaxed: TextDecoder | undefined = undefined; +let _text_decoder_utf8_validating: TextDecoder | undefined = undefined; +let _text_encoder_utf8: TextEncoder | undefined = undefined; + +export function strings_init(): void { + if (!mono_wasm_string_decoder_buffer) { + if (typeof TextDecoder !== "undefined") { + _text_decoder_utf16 = new TextDecoder("utf-16le"); + _text_decoder_utf8_relaxed = new TextDecoder("utf-8", { fatal: false }); + _text_decoder_utf8_validating = new TextDecoder("utf-8"); + _text_encoder_utf8 = new TextEncoder(); } + mono_wasm_string_root = mono_wasm_new_root(); + 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; +export function stringToUTF8(str: string): Uint8Array { + if (_text_encoder_utf8 === undefined) { + const buffer = new Uint8Array(str.length * 2); + Module.stringToUTF8Array(str, buffer, 0, str.length * 2); + return buffer; } + return _text_encoder_utf8.encode(str); +} - copy_root(root: WasmRoot): string | null { - this.init_fields(); - if (root.value === MonoStringNull) - return null; - - const ppChars = this.mono_wasm_string_decoder_buffer + 0, - pLengthBytes = this.mono_wasm_string_decoder_buffer + 4, - pIsInterned = this.mono_wasm_string_decoder_buffer + 8; - - cwraps.mono_wasm_string_get_data_ref(root.address, ppChars, pLengthBytes, pIsInterned); - - let result = undefined; - const lengthBytes = getI32(pLengthBytes), - pChars = getU32(ppChars), - isInterned = getI32(pIsInterned); - - if (isInterned) - result = interned_string_table.get(root.value)!; - - if (result === undefined) { - if (lengthBytes && pChars) { - result = this.decode(pChars, pChars + lengthBytes); - if (isInterned) - interned_string_table.set(root.value, result); - } else - result = mono_wasm_empty_string; - } +export function utf8ToStringRelaxed(buffer: Uint8Array): string { + if (_text_decoder_utf8_relaxed === undefined) { + return Module.UTF8ArrayToString(buffer, 0, buffer.byteLength); + } + return _text_decoder_utf8_relaxed.decode(buffer); +} - if (result === undefined) - throw new Error(`internal error when decoding string at location ${root.value}`); +export function utf8ToString(ptr: CharPtr): string { + const heapU8 = localHeapViewU8(); + return utf8BufferToString(heapU8, ptr as any, heapU8.length - (ptr as any)); +} - return result; +export function utf8BufferToString(heapOrArray: Uint8Array, idx: number, maxBytesToRead: number): string { + const endIdx = idx + maxBytesToRead; + let endPtr = idx; + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + if (endPtr - idx <= 16) { + return Module.UTF8ArrayToString(heapOrArray, idx, maxBytesToRead); } + if (_text_decoder_utf8_validating === undefined) { + return Module.UTF8ArrayToString(heapOrArray, idx, maxBytesToRead); + } + const view = viewOrCopy(heapOrArray, idx as any, endPtr as any); + return _text_decoder_utf8_validating.decode(view); +} - decode(start: CharPtr, end: CharPtr): string { - let str = ""; - if (this.mono_text_decoder) { - // When threading is enabled, TextDecoder does not accept a view of a - // SharedArrayBuffer, we must make a copy of the array first. - // See https://github.com/whatwg/encoding/issues/172 - const subArray = typeof SharedArrayBuffer !== "undefined" && Module.HEAPU8.buffer instanceof SharedArrayBuffer - ? Module.HEAPU8.slice(start, end) - : Module.HEAPU8.subarray(start, end); - - str = this.mono_text_decoder.decode(subArray); - } else { - for (let i = 0; i < end - start; i += 2) { - const char = Module.getValue(start + i, "i16"); - str += String.fromCharCode(char); - } - } - - return str; +export function utf16ToString(startPtr: number, endPtr: number): string { + if (_text_decoder_utf16) { + const subArray = viewOrCopy(localHeapViewU8(), startPtr as any, endPtr as any); + return _text_decoder_utf16.decode(subArray); + } else { + return utf16ToStringLoop(startPtr, endPtr); } } -const interned_string_table = new Map(); -export const interned_js_string_table = new Map(); -let _empty_string_ptr: MonoString = 0; -const _interned_string_full_root_buffers = []; -let _interned_string_current_root_buffer: WasmRootBuffer | null = null; -let _interned_string_current_root_buffer_count = 0; -export const string_decoder = new StringDecoder(); -export const mono_wasm_empty_string = ""; +export function utf16ToStringLoop(startPtr: number, endPtr: number): string { + let str = ""; + const heapU16 = localHeapViewU16(); + for (let i = startPtr; i < endPtr; i += 2) { + const char = getU16_local(heapU16, i); + str += String.fromCharCode(char); + } + return str; +} -export function conv_string_root(root: WasmRoot): string | null { - return string_decoder.copy_root(root); +export function stringToUTF16(dstPtr: number, endPtr: number, text: string) { + const heapI16 = localHeapViewU16(); + const len = text.length; + for (let i = 0; i < len; i++) { + setU16_local(heapI16, dstPtr, text.charCodeAt(i)); + dstPtr += 2; + if (dstPtr >= endPtr) break; + } } -// 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 (is_nullish(result)) - throw new Error("internal error: interned_string_table did not contain string after js_string_to_mono_string_interned"); +/* @deprecated not GC safe, use monoStringToString */ +export function monoStringToStringUnsafe(mono_string: MonoString): string | null { + if (mono_string === MonoStringNull) + return null; + + mono_wasm_string_root.value = mono_string; + const result = monoStringToString(mono_wasm_string_root); + mono_wasm_string_root.value = MonoStringNull; return result; } -function _store_string_in_intern_table(string: string, root: WasmRoot, internIt: boolean): void { - if (!root.value) - throw new Error("null pointer passed to _store_string_in_intern_table"); +export function monoStringToString(root: WasmRoot): string | null { + if (root.value === MonoStringNull) + return null; - const internBufferSize = 8192; + const ppChars = mono_wasm_string_decoder_buffer + 0, + pLengthBytes = mono_wasm_string_decoder_buffer + 4, + pIsInterned = mono_wasm_string_decoder_buffer + 8; - if (_interned_string_current_root_buffer_count >= internBufferSize) { - _interned_string_full_root_buffers.push(_interned_string_current_root_buffer); - _interned_string_current_root_buffer = null; - } - if (!_interned_string_current_root_buffer) { - _interned_string_current_root_buffer = mono_wasm_new_root_buffer(internBufferSize, "interned strings"); - _interned_string_current_root_buffer_count = 0; - } + cwraps.mono_wasm_string_get_data_ref(root.address, ppChars, pLengthBytes, pIsInterned); - const rootBuffer = _interned_string_current_root_buffer; - const index = _interned_string_current_root_buffer_count++; + let result = undefined; + const heapU32 = localHeapViewU32(); + const lengthBytes = getU32_local(heapU32, pLengthBytes), + pChars = getU32_local(heapU32, ppChars), + isInterned = getU32_local(heapU32, pIsInterned); - // 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) { - cwraps.mono_wasm_intern_string_ref(root.address); - if (!root.value) - throw new Error("mono_wasm_intern_string_ref produced a null pointer"); + if (isInterned) + result = interned_string_table.get(root.value)!; + + if (result === undefined) { + if (lengthBytes && pChars) { + result = utf16ToString(pChars, pChars + lengthBytes); + if (isInterned) + interned_string_table.set(root.value, result); + } else + result = mono_wasm_empty_string; } - interned_js_string_table.set(string, root.value); - interned_string_table.set(root.value, string); + if (result === undefined) + throw new Error(`internal error when decoding string at location ${root.value}`); - if ((string.length === 0) && !_empty_string_ptr) - _empty_string_ptr = root.value; + return result; +} - // 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 stringToMonoStringRoot(string: string, result: WasmRoot): void { + result.clear(); + + if (string === null) + return; + else if (typeof (string) === "symbol") + stringToInternedMonoStringRoot(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 + stringToInternedMonoStringRoot(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; + } + } + + js_string_to_mono_string_new_root(string, result); + } } -export function js_string_to_mono_string_interned_root(string: string | symbol, result: WasmRoot): void { +export function stringToInternedMonoStringRoot(string: string | symbol, result: WasmRoot): void { let text: string | undefined; if (typeof (string) === "symbol") { text = string.description; @@ -193,82 +207,62 @@ export function js_string_to_mono_string_interned_root(string: string | symbol, _store_string_in_intern_table(text, result, true); } -export function js_string_to_mono_string_root(string: string, result: WasmRoot): void { - result.clear(); +function _store_string_in_intern_table(string: string, root: WasmRoot, internIt: boolean): void { + if (!root.value) + throw new Error("null pointer passed to _store_string_in_intern_table"); - if (string === null) - return; - else if (typeof (string) === "symbol") - 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; - } - } + const internBufferSize = 8192; - js_string_to_mono_string_new_root(string, result); + if (_interned_string_current_root_buffer_count >= internBufferSize) { + _interned_string_full_root_buffers.push(_interned_string_current_root_buffer); + _interned_string_current_root_buffer = null; + } + if (!_interned_string_current_root_buffer) { + _interned_string_current_root_buffer = mono_wasm_new_root_buffer(internBufferSize, "interned strings"); + _interned_string_current_root_buffer_count = 0; } -} -export function js_string_to_mono_string_new_root(string: string, result: WasmRoot): void { - const buffer = Module._malloc((string.length + 1) * 2); - const buffer16 = (buffer >>> 1) | 0; - for (let i = 0; i < string.length; i++) - Module.HEAP16[buffer16 + i] = string.charCodeAt(i); - Module.HEAP16[buffer16 + string.length] = 0; - cwraps.mono_wasm_string_from_utf16_ref(buffer, string.length, result.address); - Module._free(buffer); -} + const rootBuffer = _interned_string_current_root_buffer; + const index = _interned_string_current_root_buffer_count++; -/** - * @deprecated Not GC or thread safe - */ -export function js_string_to_mono_string_interned(string: string | symbol): MonoString { - const temp = mono_wasm_new_root(); - try { - js_string_to_mono_string_interned_root(string, temp); - return temp.value; - } finally { - temp.release(); + // 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) { + cwraps.mono_wasm_intern_string_ref(root.address); + if (!root.value) + throw new Error("mono_wasm_intern_string_ref produced a null pointer"); } -} -/** - * @deprecated Not GC or thread safe - */ -export function js_string_to_mono_string(string: string): MonoString { - assert_legacy_interop(); - const temp = mono_wasm_new_root(); - try { - js_string_to_mono_string_root(string, temp); - return temp.value; - } finally { - temp.release(); - } + 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 = root.value; + + // 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); } -/** - * @deprecated Not GC or thread safe - */ -export function js_string_to_mono_string_new(string: string): MonoString { - assert_legacy_interop(); - const temp = mono_wasm_new_root(); - try { - js_string_to_mono_string_new_root(string, temp); - return temp.value; - } finally { - temp.release(); - } +function js_string_to_mono_string_new_root(string: string, result: WasmRoot): void { + const bufferLen = (string.length + 1) * 2; + const buffer = Module._malloc(bufferLen); + stringToUTF16(buffer as any, buffer as any + bufferLen, string); + cwraps.mono_wasm_string_from_utf16_ref(buffer, string.length, result.address); + Module._free(buffer); } + +// When threading is enabled, TextDecoder does not accept a view of a +// SharedArrayBuffer, we must make a copy of the array first. +// See https://github.com/whatwg/encoding/issues/172 +// BEWARE: In some cases, `instanceof SharedArrayBuffer` returns false even though buffer is an SAB. +// Patch adapted from https://github.com/emscripten-core/emscripten/pull/16994 +// See also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag +export function viewOrCopy(view: Uint8Array, start: CharPtr, end: CharPtr): Uint8Array { + // this condition should be eliminated by rollup on non-threading builds + const needsCopy = isSharedArrayBuffer(view.buffer); + return needsCopy + ? view.slice(start, end) + : view.subarray(start, end); +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index bcefbbbb40b9b6..46a2449c6cf7da 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -23,14 +23,23 @@ export declare interface CharPtrPtr extends NativePointer { } export declare interface EmscriptenModule { + /** @deprecated Please use growableHeapI8() instead.*/ HEAP8: Int8Array, + /** @deprecated Please use growableHeapI16() instead.*/ HEAP16: Int16Array; + /** @deprecated Please use growableHeapI32() instead. */ HEAP32: Int32Array; + /** @deprecated Please use growableHeapI64() instead. */ HEAP64: BigInt64Array; + /** @deprecated Please use growableHeapU8() instead. */ HEAPU8: Uint8Array; + /** @deprecated Please use growableHeapU16() instead. */ HEAPU16: Uint16Array; + /** @deprecated Please use growableHeapU32() instead */ HEAPU32: Uint32Array; + /** @deprecated Please use growableHeapF32() instead */ HEAPF32: Float32Array; + /** @deprecated Please use growableHeapF64() instead. */ HEAPF64: Float64Array; // this should match emcc -s EXPORTED_FUNCTIONS @@ -48,6 +57,7 @@ export declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; addFunction(fn: Function, signature: string): number; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 7dfca1e092d65f..12815fa20353b1 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -174,6 +174,8 @@ export type APIType = { getAssemblyExports(assemblyName: string): Promise, setModuleImports(moduleName: string, moduleImports: any): void, getConfig: () => MonoConfig, + + // memory management setHeapB32: (offset: NativePointer, value: number | boolean) => void, setHeapU8: (offset: NativePointer, value: number) => void, setHeapU16: (offset: NativePointer, value: number) => void, @@ -198,6 +200,15 @@ export type APIType = { getHeapI64Big: (offset: NativePointer) => bigint, getHeapF32: (offset: NativePointer) => number, getHeapF64: (offset: NativePointer) => number, + localHeapViewI8: () => Int8Array, + localHeapViewI16: () => Int16Array, + localHeapViewI32: () => Int32Array, + localHeapViewI64Big: () => BigInt64Array, + localHeapViewU8: () => Uint8Array, + localHeapViewU16: () => Uint16Array, + localHeapViewU32: () => Uint32Array, + localHeapViewF32: () => Float32Array, + localHeapViewF64: () => Float64Array, } export type RuntimeAPI = { diff --git a/src/mono/wasm/runtime/web-socket.ts b/src/mono/wasm/runtime/web-socket.ts index a0ec828e1c6094..946d5045be6ad6 100644 --- a/src/mono/wasm/runtime/web-socket.ts +++ b/src/mono/wasm/runtime/web-socket.ts @@ -3,11 +3,12 @@ import { prevent_timer_throttling } from "./scheduling"; import { Queue } from "./queue"; -import { Module, createPromiseController } from "./globals"; -import { setI32 } from "./memory"; +import { createPromiseController } from "./globals"; +import { setI32, localHeapViewU8 } from "./memory"; import { VoidPtr } from "./types/emscripten"; import { PromiseController } from "./types/internal"; import { mono_log_warn } from "./logging"; +import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings"; 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"); @@ -20,8 +21,6 @@ const wasm_ws_pending_send_promises = Symbol.for("wasm ws_pending_send_promises" const wasm_ws_is_aborted = Symbol.for("wasm ws_is_aborted"); const wasm_ws_receive_status_ptr = Symbol.for("wasm ws_receive_status_ptr"); let mono_wasm_web_socket_close_warning = false; -let _text_decoder_utf8: TextDecoder | undefined = undefined; -let _text_encoder_utf8: TextEncoder | undefined = undefined; const ws_send_buffer_blocking_threshold = 65536; const emptyBuffer = new Uint8Array(); @@ -89,7 +88,7 @@ export function ws_wasm_open(ws: WebSocketExtension): Promise | null { mono_assert(!!ws, "ERR17: expected ws instance"); - const buffer_view = new Uint8Array(Module.HEAPU8.buffer, buffer_ptr, buffer_length); + const buffer_view = new Uint8Array(localHeapViewU8().buffer, buffer_ptr, buffer_length); const whole_buffer = _mono_wasm_web_socket_send_buffering(ws, buffer_view, message_type, end_of_message); if (!end_of_message || !whole_buffer) { @@ -234,15 +233,12 @@ function _mono_wasm_web_socket_on_message(ws: WebSocketExtension, event: Message const promise_queue = ws[wasm_ws_pending_receive_promise_queue]; if (typeof event.data === "string") { - if (_text_encoder_utf8 === undefined) { - _text_encoder_utf8 = new TextEncoder(); - } event_queue.enqueue({ type: 0, // WebSocketMessageType.Text // according to the spec https://encoding.spec.whatwg.org/ // - Unpaired surrogates will get replaced with 0xFFFD // - utf8 encode specifically is defined to never throw - data: _text_encoder_utf8.encode(event.data), + data: stringToUTF8(event.data), offset: 0 }); } @@ -274,7 +270,7 @@ function _mono_wasm_web_socket_receive_buffering(ws: WebSocketExtension, event_q const count = Math.min(buffer_length, event.data.length - event.offset); if (count > 0) { const sourceView = event.data.subarray(event.offset, event.offset + count); - const bufferView = new Uint8Array(Module.HEAPU8.buffer, buffer_ptr, buffer_length); + const bufferView = new Uint8Array(localHeapViewU8().buffer, buffer_ptr, buffer_length); bufferView.set(sourceView, 0); event.offset += count; } @@ -336,16 +332,10 @@ function _mono_wasm_web_socket_send_buffering(ws: WebSocketExtension, buffer_vie } if (message_type === 0) { // text, convert from UTF-8 bytes to string, because of bad browser API - if (_text_decoder_utf8 === undefined) { - // we do not validate outgoing data https://github.com/dotnet/runtime/issues/59214 - _text_decoder_utf8 = new TextDecoder("utf-8", { fatal: false }); - } - // See https://github.com/whatwg/encoding/issues/172 - const bytes = typeof SharedArrayBuffer !== "undefined" && buffer instanceof SharedArrayBuffer - ? (buffer).slice(0, offset) - : buffer.subarray(0, offset); - return _text_decoder_utf8.decode(bytes); + const bytes = viewOrCopy(buffer, 0 as any, offset as any); + // we do not validate outgoing data https://github.com/dotnet/runtime/issues/59214 + return utf8ToStringRelaxed(bytes); } else { // binary, view to used part of the buffer return buffer.subarray(0, offset); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 5ecdc88055453b..771208e5133d65 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -193,6 +193,7 @@ + @@ -295,6 +296,9 @@ + + + <_EmccLinkFlags Include="-s TEXTDECODER=0"/>