[browser][mt] Update memory views after growth, refactor string processing, fix Share...
authorIsabelle Santin <isabelle@chosenfewsoftware.com>
Sun, 28 May 2023 19:57:15 +0000 (14:57 -0500)
committerGitHub <noreply@github.com>
Sun, 28 May 2023 19:57:15 +0000 (21:57 +0200)
Co-authored-by: pavelsavara <pavel.savara@gmail.com>
41 files changed:
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs
src/mono/wasm/runtime/assets.ts
src/mono/wasm/runtime/debug.ts
src/mono/wasm/runtime/diagnostics/index.ts
src/mono/wasm/runtime/diagnostics/server_pthread/index.ts
src/mono/wasm/runtime/diagnostics/server_pthread/socket-connection.ts
src/mono/wasm/runtime/dotnet.d.ts
src/mono/wasm/runtime/export-api.ts
src/mono/wasm/runtime/exports-internal.ts
src/mono/wasm/runtime/hybrid-globalization/change-case.ts
src/mono/wasm/runtime/hybrid-globalization/collations.ts
src/mono/wasm/runtime/hybrid-globalization/normalization.ts
src/mono/wasm/runtime/invoke-cs.ts
src/mono/wasm/runtime/invoke-js.ts
src/mono/wasm/runtime/jiterpreter-interp-entry.ts
src/mono/wasm/runtime/jiterpreter-jit-call.ts
src/mono/wasm/runtime/jiterpreter-opcodes.ts
src/mono/wasm/runtime/jiterpreter-support.ts
src/mono/wasm/runtime/jiterpreter-trace-generator.ts
src/mono/wasm/runtime/jiterpreter.ts
src/mono/wasm/runtime/logging.ts
src/mono/wasm/runtime/marshal-to-cs.ts
src/mono/wasm/runtime/marshal-to-js.ts
src/mono/wasm/runtime/marshal.ts
src/mono/wasm/runtime/memory.ts
src/mono/wasm/runtime/net6-legacy/buffers.ts
src/mono/wasm/runtime/net6-legacy/cs-to-js.ts
src/mono/wasm/runtime/net6-legacy/exports-legacy.ts
src/mono/wasm/runtime/net6-legacy/js-to-cs.ts
src/mono/wasm/runtime/net6-legacy/method-binding.ts
src/mono/wasm/runtime/net6-legacy/method-calls.ts
src/mono/wasm/runtime/net6-legacy/strings.ts [new file with mode: 0644]
src/mono/wasm/runtime/profiler.ts
src/mono/wasm/runtime/roots.ts
src/mono/wasm/runtime/snapshot.ts
src/mono/wasm/runtime/startup.ts
src/mono/wasm/runtime/strings.ts
src/mono/wasm/runtime/types/emscripten.ts
src/mono/wasm/runtime/types/index.ts
src/mono/wasm/runtime/web-socket.ts
src/mono/wasm/wasm.proj

index 82d3908..5b5a8cf 100644 (file)
@@ -612,20 +612,6 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         }
 
         [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()
         {
             HelperMarshal._stringResource = HelperMarshal._stringResource2 = null;
index a459b11..72717b8 100644 (file)
@@ -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;
index 044758c..0bc38cf 100644 (file)
@@ -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<number, CommandResponse>();
 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[<any>_debugger_buffer + i] = byteCharacters.charCodeAt(i);
+        heapU8[<any>_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);
index e5b1547..0db99e6 100644 (file)
@@ -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<void> {
     if (diagnosticsInitialized)
         return;
-    if (!monoWasmThreads) {
+    if (!MonoWasmThreads) {
         mono_log_warn("ignoring diagnostics options because this runtime does not support diagnostics");
         return;
     } else {
index 6970fba..df5c92e 100644 (file)
@@ -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<ProtocolCommandEvent> {
     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<Mock> | undefined = undefined;
     if (monoDiagnosticsMock && websocketUrl.startsWith("mock:")) {
index 15be79e..d02ae16 100644 (file)
@@ -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);
     }
 }
index d5cbed9..724d297 100644 (file)
@@ -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 = {
     /**
index 68b5a4f..1b1b997 100644 (file)
@@ -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;
 }
index 497198c..6654062 100644 (file)
@@ -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,
index fddd80e..6472f81 100644 (file)
@@ -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<MonoObject>(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<MonoString>(culture),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(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
index 0df5b6d..9ec14b2 100644 (file)
@@ -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<MonoString>(culture),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(ex_address);
-    try{
-        const cultureName = conv_string_root(cultureRoot);
-        const string1 = string_decoder.decode(<any>str1, <any>(str1 + 2*str1Length));
-        const string2 = string_decoder.decode(<any>str2, <any>(str2 + 2*str2Length));
+    try {
+        const cultureName = monoStringToString(cultureRoot);
+        const string1 = utf16ToString(<any>str1, <any>(str1 + 2 * str1Length));
+        const string2 = utf16ToString(<any>str2, <any>(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<MonoString>(culture),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(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<MonoString>(culture),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(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<MonoString>(culture),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(ex_address);
     try {
-        const needle = string_decoder.decode(<any>needlePtr, <any>(needlePtr + 2*needleLength));
+        const needle = utf16ToString(<any>needlePtr, <any>(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(<any>srcPtr, <any>(srcPtr + 2*srcLength));
+        const source = utf16ToString(<any>srcPtr, <any>(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; j<needleSegments.length; j++)
-                {
-                    if (srcNext.done)
-                    {
+            if (matchFound) {
+                for (let j = 1; j < needleSegments.length; j++) {
+                    if (srcNext.done) {
                         stop = true;
                         break;
                     }
@@ -163,8 +156,7 @@ export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, ne
                     break;
             }
 
-            if (matchFound)
-            {
+            if (matchFound) {
                 result = index;
                 if (fromBeginning)
                     break;
@@ -183,15 +175,13 @@ export function mono_wasm_index_of(culture: MonoStringRef, needlePtr: number, ne
         exceptionRoot.release();
     }
 
-    function check_match_found(str1: string, str2: string, locale: string | undefined, casePicker: number) : boolean
-    {
+    function check_match_found(str1: string, str2: string, locale: string | undefined, casePicker: number): boolean {
         return compare_strings(str1, str2, locale, casePicker) === 0;
     }
 }
 
-function compare_strings(string1: string, string2: string, locale: string | undefined, casePicker: number) : number{
-    switch (casePicker)
-    {
+function compare_strings(string1: string, string2: string, locale: string | undefined, casePicker: number): number {
+    switch (casePicker) {
         case 0:
             // 0: None - default algorithm for the platform OR
             //    StringSort - for ICU it gives the same result as None, see: https://github.com/dotnet/dotnet-api-docs/issues
@@ -282,14 +272,12 @@ function compare_strings(string1: string, string2: string, locale: string | unde
     }
 }
 
-function decode_to_clean_string(strPtr: number, strLen: number)
-{
-    const str = string_decoder.decode(<any>strPtr, <any>(strPtr + 2*strLen));
+function decode_to_clean_string(strPtr: number, strLen: number) {
+    const str = utf16ToString(<any>strPtr, <any>(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, "");
 }
index a3e92e2..2faf9e0 100644 (file)
@@ -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<MonoString>(inputStr),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(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<MonoString>(inputStr),
         exceptionRoot = mono_wasm_new_external_root<MonoObject>(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);
index fa9099d..c17987e 100644 (file)
@@ -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);
                     }
                 }
index a1fc9ae..e1caf24 100644 (file)
@@ -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_
         (<any>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, <any>fn_handle);
+        setI32(function_js_handle, <any>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<MonoObject>): void {
     const res = _wrap_error_flag(is_exception, ex);
-    js_string_to_mono_string_root(res, <any>result);
+    stringToMonoStringRoot(res, <any>result);
 }
 
 // to set out parameters of icalls
 export function wrap_no_error_root(is_exception: Int32Ptr | null, result?: WasmRoot<MonoObject>): void {
     if (is_exception) {
+        receiveWorkerHeapViews();
         setI32_unchecked(is_exception, 0);
     }
     if (result) {
index 6f0dac1..f15bb67 100644 (file)
@@ -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(<any>name),
+        unbox, hasThisReference, hasReturnValue, utf8ToString(<any>name),
         defaultImplementation
     );
     if (!fnTable)
index e3e2f56..8c98c06 100644 (file)
@@ -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) : <any>0;
             try {
-                suffix = Module.UTF8ToString(pMethodName);
+                suffix = utf8ToString(pMethodName);
             } finally {
                 if (pMethodName)
                     Module._free(<any>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);
         }
     };
index e188778..0c41426 100644 (file)
@@ -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(<any>pName);
+        opcodeNameCache[opcode] = result = utf8ToString(<any>pName);
     }
     return result;
 }
index f65c228..e0b6283 100644 (file)
@@ -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 = <any>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(<any>scratchBuffer, <any>src, <any>src + size);
+    localHeapViewU8().copyWithin(<any>scratchBuffer, <any>src, <any>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(<any>pJson);
+    const json = utf8ToString(<any>pJson);
     Module._free(<any>pJson);
     const blob = JSON.parse(json);
 
index d30e65c..f486666 100644 (file)
@@ -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(<any>ip + 4, <any>ip + 4 + sizeOfV128)
+                    localHeapViewU8().slice(<any>ip + 4, <any>ip + 4 + sizeOfV128)
                 );
                 append_simd_store(builder, ip);
             } else {
index 7f25b4f..caad542 100644 (file)
@@ -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(<any>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) + <any>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(<any>targetMethod);
-                const targetMethodName = Module.UTF8ToString(pMethodName);
+                const targetMethodName = utf8ToString(pMethodName);
                 const hitCount = callTargetCounts[<any>targetMethod];
                 Module._free(<any>pMethodName);
                 mono_log_info(`${targetMethodName} ${hitCount}`);
index 75f4d83..305fe6a 100644 (file)
@@ -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}`;
 
index 8915588..9f18be8 100644 (file)
@@ -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<any>, _?:
     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(<any>buffer_ptr, buffer_ptr + length);
+            const targetView = localHeapViewU8().subarray(<any>buffer_ptr, buffer_ptr + length);
             targetView.set(value);
         }
         else if (element_type == MarshalerType.Int32) {
             mono_assert(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array");
-            const targetView = Module.HEAP32.subarray(<any>buffer_ptr >> 2, (buffer_ptr >> 2) + length);
+            const targetView = localHeapViewI32().subarray(<any>buffer_ptr >> 2, (buffer_ptr >> 2) + length);
             targetView.set(value);
         }
         else if (element_type == MarshalerType.Double) {
             mono_assert(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array");
-            const targetView = Module.HEAPF64.subarray(<any>buffer_ptr >> 3, (buffer_ptr >> 3) + length);
+            const targetView = localHeapViewF64().subarray(<any>buffer_ptr >> 3, (buffer_ptr >> 3) + length);
             targetView.set(value);
         }
         else {
index eebe3a9..c9076b1 100644 (file)
@@ -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(<any>buffer_ptr, buffer_ptr + length);
+        const sourceView = localHeapViewU8().subarray(<any>buffer_ptr, buffer_ptr + length);
         result = sourceView.slice();//copy
     }
     else if (element_type == MarshalerType.Int32) {
-        const sourceView = Module.HEAP32.subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length);
+        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 {
index 9fc17c7..80fb44f 100644 (file)
@@ -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, <any>this._pointer, this._length)
-            : this._viewType == MemoryViewType.Int32 ? new Int32Array(Module.HEAP32.buffer, <any>this._pointer, this._length)
-                : this._viewType == MemoryViewType.Double ? new Float64Array(Module.HEAPF64.buffer, <any>this._pointer, this._length)
+        const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(localHeapViewU8().buffer, <any>this._pointer, this._length)
+            : this._viewType == MemoryViewType.Int32 ? new Int32Array(localHeapViewI32().buffer, <any>this._pointer, this._length)
+                : this._viewType == MemoryViewType.Double ? new Float64Array(localHeapViewF64().buffer, <any>this._pointer, this._length)
                     : null;
         if (!view) throw new Error("NotImplementedException");
         return view;
index 83e9417..d765d09 100644 (file)
@@ -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<VoidPtr> = [];
 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, <any>byteOffset, <any>byteOffset + sizeBytes);
+    localHeapViewU8().fill(0, <any>byteOffset, <any>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[<any>offset] = value;
 }
 
 export function setU16(offset: MemOffset, value: number): void {
     assert_int_in_range(value, 0, 0xFFFF);
+    receiveWorkerHeapViews();
     Module.HEAPU16[<any>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[<any>offset >>> 1] = value;
+}
+
+// does not check for overflow nor growable heap
 export function setU16_unchecked(offset: MemOffset, value: number): void {
     Module.HEAPU16[<any>offset >>> 1] = value;
 }
 
+// does not check for overflow nor growable heap
 export function setU32_unchecked(offset: MemOffset, value: NumberOrPointer): void {
     Module.HEAPU32[<any>offset >>> 2] = <number><any>value;
 }
 
 export function setU32(offset: MemOffset, value: NumberOrPointer): void {
     assert_int_in_range(<any>value, 0, 0xFFFF_FFFF);
+    receiveWorkerHeapViews();
     Module.HEAPU32[<any>offset >>> 2] = <number><any>value;
 }
 
 export function setI8(offset: MemOffset, value: number): void {
     assert_int_in_range(value, -0x80, 0x7F);
+    receiveWorkerHeapViews();
     Module.HEAP8[<any>offset] = value;
 }
 
 export function setI16(offset: MemOffset, value: number): void {
     assert_int_in_range(value, -0x8000, 0x7FFF);
+    receiveWorkerHeapViews();
     Module.HEAP16[<any>offset >>> 1] = value;
 }
 
 export function setI32_unchecked(offset: MemOffset, value: number): void {
+    receiveWorkerHeapViews();
     Module.HEAP32[<any>offset >>> 2] = value;
 }
 
 export function setI32(offset: MemOffset, value: number): void {
     assert_int_in_range(<any>value, -0x8000_0000, 0x7FFF_FFFF);
+    receiveWorkerHeapViews();
     Module.HEAP32[<any>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(<any>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(<any>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[<any>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[<any>offset >>> 3] = value;
 }
 
 
 export function getB32(offset: MemOffset): boolean {
+    receiveWorkerHeapViews();
     return !!(Module.HEAP32[<any>offset >>> 2]);
 }
 
 export function getU8(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAPU8[<any>offset];
 }
 
 export function getU16(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAPU16[<any>offset >>> 1];
 }
 
+// does not check for growable heap
+export function getU16_local(localView: Uint16Array, offset: MemOffset): number {
+    return localView[<any>offset >>> 1];
+}
+
 export function getU32(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAPU32[<any>offset >>> 2];
 }
 
+// does not check for growable heap
+export function getU32_local(localView: Uint32Array, offset: MemOffset): number {
+    return localView[<any>offset >>> 2];
+}
+
 export function getI32_unaligned(offset: MemOffset): number {
     return cwraps.mono_wasm_get_i32_unaligned(<any>offset);
 }
@@ -189,17 +225,30 @@ export function getF64_unaligned(offset: MemOffset): number {
 }
 
 export function getI8(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAP8[<any>offset];
 }
 
 export function getI16(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAP16[<any>offset >>> 1];
 }
 
+// does not check for growable heap
+export function getI16_local(localView: Int16Array, offset: MemOffset): number {
+    return localView[<any>offset >>> 1];
+}
+
 export function getI32(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAP32[<any>offset >>> 2];
 }
 
+// does not check for growable heap
+export function getI32_local(localView: Int32Array, offset: MemOffset): number {
+    return localView[<any>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[<any>offset >>> 3];
 }
 
 export function getF32(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAPF32[<any>offset >>> 2];
 }
 
 export function getF64(offset: MemOffset): number {
+    receiveWorkerHeapViews();
     return Module.HEAPF64[<any>offset >>> 3];
 }
 
@@ -253,7 +305,7 @@ export function withStackAlloc<T1, T2, T3, TResult>(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, <any>memoryOffset, bytes.length);
+    const heapBytes = new Uint8Array(localHeapViewU8().buffer, <any>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 (<any>charPtr === 0)
             return null;
-        else return Module.UTF8ToString(charPtr);
+        else return utf8ToString(charPtr);
     } finally {
         if (charPtr) Module._free(<any>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, <any>offset >>> 2, value);
+        BuiltinAtomics.store(localHeapViewI32(), <any>offset >>> 2, value);
     },
     notifyI32(offset: MemOffset, count: number): void {
-        BuiltinAtomics.notify(Module.HEAP32, <any>offset >>> 2, count);
+        BuiltinAtomics.notify(localHeapViewI32(), <any>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
index 3e5ca31..d2b963e 100644 (file)
@@ -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(<any>pinned_array + offset, <any>pinned_array + offset + num_of_bytes));
+        typedarrayBytes.set(localHeapViewU8().subarray(<any>pinned_array + offset, <any>pinned_array + offset + num_of_bytes));
         return num_of_bytes;
     }
     else {
index 28a1294..009aba9 100644 (file)
@@ -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<a
             throw new Error("int64 not available");
         case MarshalType.STRING:
         case MarshalType.STRING_INTERNED:
-            return conv_string_root(root);
+            return monoStringToString(root);
         case MarshalType.VT:
             throw new Error("no idea on how to unbox value types");
         case MarshalType.DELEGATE:
@@ -228,7 +228,7 @@ export function mono_wasm_create_cs_owned_object_ref(core_name: MonoStringRef, a
         nameRoot = mono_wasm_new_external_root<MonoString>(core_name),
         resultRoot = mono_wasm_new_external_root<MonoObject>(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
index 9be5f37..a6405c4 100644 (file)
@@ -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: <any>null,
         mono_obj_array_set_ref: <any>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,
     };
index 290dd4f..83df9bd 100644 (file)
@@ -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<MonoObject>, s
             return;
         }
         case typeof js_obj === "string":
-            js_string_to_mono_string_root(js_obj, <any>result);
+            stringToMonoStringRoot(js_obj, <any>result);
             return;
         case typeof js_obj === "symbol":
-            js_string_to_mono_string_interned_root(js_obj, <any>result);
+            stringToInternedMonoStringRoot(js_obj, <any>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, <any>ptr, numBytes);
+    const heapU8 = localHeapViewU8();
+    const heapBytes = new Uint8Array(heapU8.buffer, <any>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;
 }
 
index d75ca3c..bd1ded5 100644 (file)
@@ -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<MonoString>, 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;
index 6138864..e69883b 100644 (file)
@@ -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<MonoString>(method_name),
         resultRoot = mono_wasm_new_external_root<MonoObject>(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<MonoString>(property_name),
         resultRoot = mono_wasm_new_external_root<MonoObject>(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<MonoObject>(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<MonoString>(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<MonoString>();
-        js_string_to_mono_string_root(exceptionJsString, exceptionRoot);
+        stringToMonoStringRoot(exceptionJsString, exceptionRoot);
         exceptionRoot.copy_to_address(<any>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 (file)
index 0000000..a6c0790
--- /dev/null
@@ -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<MonoString>();
+    try {
+        stringToMonoStringRoot(string, temp);
+        return temp.value;
+    } finally {
+        temp.release();
+    }
+}
index 021273c..da627b5 100644 (file)
@@ -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);
index 30d0bdc..21f9f18 100644 (file)
@@ -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 <any>Module.HEAPU32[offset];
+        return <any>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<T extends MonoObject> implements WasmRoot<T> {
         // .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<T extends MonoObject> implements WasmRoot<T> {
     }
 
     get(): T {
-        const result = Module.HEAPU32[this.__external_address_32];
+        const result = localHeapViewU32()[this.__external_address_32];
         return <any>result;
     }
 
@@ -425,7 +425,7 @@ class WasmExternalRoot<T extends MonoObject> implements WasmRoot<T> {
     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[<any>this.__external_address >>> 2] = 0;
+        localHeapViewU32()[<any>this.__external_address >>> 2] = 0;
     }
 
     release(): void {
index bce9062..abe4a8d 100644 (file)
@@ -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, {
index f0bf309..bbb7816 100644 (file)
@@ -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);
     }
 
index 3fd4e01..ed488d2 100644 (file)
 // 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<string, MonoString>();
+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<MonoString, string>();
+let _empty_string_ptr: MonoString = <any>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<MonoString>): string | null {
-        this.init_fields();
-        if (root.value === MonoStringNull)
-            return null;
-
-        const ppChars = <any>this.mono_wasm_string_decoder_buffer + 0,
-            pLengthBytes = <any>this.mono_wasm_string_decoder_buffer + 4,
-            pIsInterned = <any>this.mono_wasm_string_decoder_buffer + 8;
-
-        cwraps.mono_wasm_string_get_data_ref(root.address, <any>ppChars, <any>pLengthBytes, <any>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(<any>pChars, <any>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(<any>start, <any>end)
-                : Module.HEAPU8.subarray(<any>start, <any>end);
-
-            str = this.mono_text_decoder.decode(subArray);
-        } else {
-            for (let i = 0; i < <any>end - <any>start; i += 2) {
-                const char = Module.getValue(<any>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<MonoString, string>();
-export const interned_js_string_table = new Map<string, MonoString>();
-let _empty_string_ptr: MonoString = <any>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<MonoString>): 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<MonoString>, internIt: boolean): void {
-    if (!root.value)
-        throw new Error("null pointer passed to _store_string_in_intern_table");
+export function monoStringToString(root: WasmRoot<MonoString>): string | null {
+    if (root.value === MonoStringNull)
+        return null;
 
-    const internBufferSize = 8192;
+    const ppChars = <any>mono_wasm_string_decoder_buffer + 0,
+        pLengthBytes = <any>mono_wasm_string_decoder_buffer + 4,
+        pIsInterned = <any>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, <any>ppChars, <any>pLengthBytes, <any>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(<any>pChars, <any>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<MonoString>): 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<MonoString>): void {
+export function stringToInternedMonoStringRoot(string: string | symbol, result: WasmRoot<MonoString>): 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<MonoString>): void {
-    result.clear();
+function _store_string_in_intern_table(string: string, root: WasmRoot<MonoString>, internIt: boolean): void {
+    if (!root.value)
+        throw new Error("null pointer passed to _store_string_in_intern_table");
 
-    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<MonoString>): void {
-    const buffer = Module._malloc((string.length + 1) * 2);
-    const buffer16 = (<any>buffer >>> 1) | 0;
-    for (let i = 0; i < string.length; i++)
-        Module.HEAP16[buffer16 + i] = string.charCodeAt(i);
-    Module.HEAP16[buffer16 + string.length] = 0;
-    cwraps.mono_wasm_string_from_utf16_ref(<any>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<MonoString>();
-    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<MonoString>();
-    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<MonoString>();
-    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<MonoString>): 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(<any>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(<any>start, <any>end)
+        : view.subarray(<any>start, <any>end);
+}
\ No newline at end of file
index bcefbbb..46a2449 100644 (file)
@@ -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;
index 7dfca1e..12815fa 100644 (file)
@@ -174,6 +174,8 @@ export type APIType = {
     getAssemblyExports(assemblyName: string): Promise<any>,
     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 = {
index a0ec828..946d504 100644 (file)
@@ -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<WebSocketExtension
 export function ws_wasm_send(ws: WebSocketExtension, buffer_ptr: VoidPtr, buffer_length: number, message_type: number, end_of_message: boolean): Promise<void> | null {
     mono_assert(!!ws, "ERR17: expected ws instance");
 
-    const buffer_view = new Uint8Array(Module.HEAPU8.buffer, <any>buffer_ptr, buffer_length);
+    const buffer_view = new Uint8Array(localHeapViewU8().buffer, <any>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, <any>buffer_ptr, buffer_length);
+        const bufferView = new Uint8Array(localHeapViewU8().buffer, <any>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
-                ? (<any>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);
index 5ecdc88..771208e 100644 (file)
       <EmccExportedRuntimeMethod Include="getValue" />
       <EmccExportedRuntimeMethod Include="UTF8ToString" />
       <EmccExportedRuntimeMethod Include="UTF8ArrayToString" />
+      <EmccExportedRuntimeMethod Include="stringToUTF8Array" />
       <EmccExportedRuntimeMethod Include="FS_createPath" />
       <EmccExportedRuntimeMethod Include="FS_createDataFile" />
       <EmccExportedRuntimeMethod Include="removeRunDependency" />
 
     <ItemGroup Condition="'$(MonoWasmThreads)' == 'true'">
       <WasmOptConfigurationFlags Include="--enable-threads;--enable-bulk-memory;--enable-sign-ext" />
+
+      <!-- workaround for https://github.com/emscripten-core/emscripten/issues/18034 -->
+      <_EmccLinkFlags Include="-s TEXTDECODER=0"/>
     </ItemGroup>
 
     <ItemGroup Condition="'$(OS)' != 'Windows_NT'">