From e38c66a58fc60eebef766ef493e741dd66c446bd Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 21 Oct 2024 19:12:14 +0200 Subject: [PATCH 1/9] wip --- src/mono/browser/runtime/debug.ts | 4 +-- src/mono/browser/runtime/interp-pgo.ts | 6 ++-- src/mono/browser/runtime/invoke-js.ts | 5 +++ .../runtime/jiterpreter-interp-entry.ts | 5 +-- .../browser/runtime/jiterpreter-support.ts | 11 +++--- src/mono/browser/runtime/managed-exports.ts | 4 +-- src/mono/browser/runtime/marshal-to-cs.ts | 12 +++---- src/mono/browser/runtime/marshal-to-js.ts | 7 ++-- src/mono/browser/runtime/marshal.ts | 4 +-- src/mono/browser/runtime/memory.ts | 9 +++-- src/mono/browser/runtime/roots.ts | 4 +-- src/mono/browser/runtime/startup.ts | 34 +++++++++---------- src/mono/browser/runtime/strings.ts | 10 +++--- .../WasmBasicTestApp/App/MemoryTest.cs | 10 +++--- .../WasmBasicTestApp/App/wwwroot/main.js | 13 +++++++ 15 files changed, 82 insertions(+), 56 deletions(-) diff --git a/src/mono/browser/runtime/debug.ts b/src/mono/browser/runtime/debug.ts index 99963d964f00cc..d273632f52b01e 100644 --- a/src/mono/browser/runtime/debug.ts +++ b/src/mono/browser/runtime/debug.ts @@ -6,7 +6,7 @@ import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; import { mono_log_warn } from "./logging"; -import { forceThreadMemoryViewRefresh, localHeapViewU8 } from "./memory"; +import { forceThreadMemoryViewRefresh, localHeapViewU8, malloc } from "./memory"; import { utf8ToString } from "./strings"; const commands_received: any = new Map(); commands_received.remove = function (key: number): CommandResponse { @@ -66,7 +66,7 @@ function mono_wasm_malloc_and_set_debug_buffer (command_parameters: string) { if (_debugger_buffer) Module._free(_debugger_buffer); _debugger_buffer_len = Math.max(command_parameters.length, _debugger_buffer_len, 256); - _debugger_buffer = Module._malloc(_debugger_buffer_len); + _debugger_buffer = malloc(_debugger_buffer_len); } const byteCharacters = atob(command_parameters); const heapU8 = localHeapViewU8(); diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index 90557b5f9e810f..7ab9e6d9a4c3aa 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { ENVIRONMENT_IS_WEB, Module, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_info, mono_log_error, mono_log_warn } from "./logging"; -import { localHeapViewU8 } from "./memory"; +import { localHeapViewU8, malloc } from "./memory"; import cwraps from "./cwraps"; import { MonoConfigInternal } from "./types/internal"; @@ -31,7 +31,7 @@ export async function interp_pgo_save_data () { return; } - const pData = Module._malloc(expectedSize); + const pData = malloc(expectedSize); const saved = cwraps.mono_interp_pgo_save_table(pData, expectedSize) === 0; if (!saved) { mono_log_error("Failed to save interp_pgo table (Unknown error)"); @@ -66,7 +66,7 @@ export async function interp_pgo_load_data () { return; } - const pData = Module._malloc(data.byteLength); + const pData = malloc(data.byteLength); const u8 = localHeapViewU8(); u8.set(new Uint8Array(data), pData); diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index 191bd8a0fc2934..aabe6d93df2333 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -24,6 +24,7 @@ export const js_import_wrapper_by_fn_handle: Function[] = [null];// 0th slo export function mono_wasm_bind_js_import_ST (signature: JSFunctionSignature): VoidPtr { if (WasmEnableThreads) return VoidPtrNull; assert_js_interop(); + signature = ((signature as any) >>> 0) as any; try { bind_js_import(signature); return VoidPtrNull; @@ -35,6 +36,8 @@ export function mono_wasm_bind_js_import_ST (signature: JSFunctionSignature): Vo export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, args: JSMarshalerArguments) { if (!WasmEnableThreads) return; assert_js_interop(); + signature = ((signature as any) >>> 0) as any; + args = ((args as any) >>> 0) as any; const function_handle = get_signature_handle(signature); @@ -73,6 +76,7 @@ export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, ar export function mono_wasm_invoke_jsimport_ST (function_handle: JSFnHandle, args: JSMarshalerArguments): void { if (WasmEnableThreads) return; loaderHelpers.assert_runtime_running(); + args = ((args as any) >>> 0) as any; const bound_fn = js_import_wrapper_by_fn_handle[function_handle]; mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`); bound_fn(args); @@ -364,6 +368,7 @@ export function mono_wasm_invoke_js_function_impl (bound_function_js_handle: JSH loaderHelpers.assert_runtime_running(); const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle); mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`); + args = ((args as any) >>> 0) as any; bound_fn(args); } diff --git a/src/mono/browser/runtime/jiterpreter-interp-entry.ts b/src/mono/browser/runtime/jiterpreter-interp-entry.ts index 84be352d9125ae..7c1e67e28b4c5c 100644 --- a/src/mono/browser/runtime/jiterpreter-interp-entry.ts +++ b/src/mono/browser/runtime/jiterpreter-interp-entry.ts @@ -5,7 +5,8 @@ import { MonoMethod, MonoType } from "./types/internal"; import { NativePointer } from "./types/emscripten"; import { Module, mono_assert } from "./globals"; import { - setI32, getU32_unaligned, _zero_region + setI32, getU32_unaligned, _zero_region, + malloc } from "./memory"; import { WasmOpcode } from "./jiterpreter-opcodes"; import cwraps from "./cwraps"; @@ -554,7 +555,7 @@ function generate_wasm_body ( // FIXME: Pre-allocate these buffers and their constant slots at the start before we // generate function bodies, so that even if we run out of constant slots for MonoType we // will always have put the buffers in a constant slot. This will be necessary for thread safety - const scratchBuffer = Module._malloc(sizeOfJiterpEntryData); + const scratchBuffer = malloc(sizeOfJiterpEntryData); _zero_region(scratchBuffer, sizeOfJiterpEntryData); // Initialize the parameter count in the data blob. This is used to calculate the new value of sp diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index 7f9c3586bcb907..e914669dbb444e 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -8,7 +8,7 @@ import { WasmOpcode, WasmSimdOpcode, WasmAtomicOpcode, WasmValtype } from "./jit import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; import { mono_log_error, mono_log_info } from "./logging"; -import { localHeapViewU8, localHeapViewU32 } from "./memory"; +import { localHeapViewU8, localHeapViewU32, malloc } from "./memory"; import { JiterpNumberMode, BailoutReason, JiterpreterTable, JiterpCounter, JiterpMember, OpcodeInfoType @@ -20,7 +20,8 @@ export const maxFailures = 2, shortNameBase = 36, // NOTE: This needs to be big enough to hold the maximum module size since there's no auto-growth // support yet. If that becomes a problem, we should just make it growable - blobBuilderCapacity = 24 * 1024; + blobBuilderCapacity = 24 * 1024, + INT32_MIN = -2147483648; // uint16 export declare interface MintOpcodePtr extends NativePointer { @@ -948,7 +949,7 @@ export class BlobBuilder { constructor () { this.capacity = blobBuilderCapacity; - this.buffer = Module._malloc(this.capacity); + this.buffer = malloc(this.capacity); mono_assert(this.buffer, () => `Failed to allocate ${blobBuilderCapacity}b buffer for BlobBuilder`); localHeapViewU8().fill(0, this.buffer, this.buffer + this.capacity); this.size = 0; @@ -1665,7 +1666,7 @@ export function append_exit (builder: WasmBuilder, ip: MintOpcodePtr, opcodeCoun export function copyIntoScratchBuffer (src: NativePointer, size: number): NativePointer { if (!scratchBuffer) - scratchBuffer = Module._malloc(64); + scratchBuffer = malloc(64); if (size > 64) throw new Error("Scratch buffer size is 64"); @@ -2106,7 +2107,7 @@ function updateOptions () { optionTable = {}; for (const k in optionNames) { const value = cwraps.mono_jiterp_get_option_as_int(optionNames[k]); - if (value > -2147483647) + if (value !== INT32_MIN) (optionTable)[k] = value; else mono_log_info(`Failed to retrieve value of option ${optionNames[k]}`); diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 79bbcbf2af964c..472c47708c4739 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -12,7 +12,7 @@ import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begi import { do_not_force_dispose, is_gcv_handle } from "./gc-handles"; import { assert_c_interop, assert_js_interop } from "./invoke-js"; import { monoThreadInfo, mono_wasm_main_thread_ptr } from "./pthreads"; -import { _zero_region, copyBytes } from "./memory"; +import { _zero_region, copyBytes, malloc } from "./memory"; import { stringToUTF8Ptr } from "./strings"; import { mono_log_error } from "./logging"; @@ -285,7 +285,7 @@ export function invoke_async_jsexport (managedTID: PThreadPtr, method: MonoMetho } else { set_receiver_should_free(args); const bytes = JavaScriptMarshalerArgSize * size; - const cpy = Module._malloc(bytes) as any; + const cpy = malloc(bytes) as any; copyBytes(args as any, cpy, bytes); twraps.mono_wasm_invoke_jsexport_async_post(managedTID, method, cpy); } diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index 58db72d21dd076..5926828bbe7016 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -8,7 +8,7 @@ import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue"; import { PromiseHolder, isThenable } from "./cancelable-promise"; import cwraps from "./cwraps"; import { alloc_gcv_handle, assert_not_disposed, cs_owned_js_handle_symbol, js_owned_gc_handle_symbol, mono_wasm_get_js_handle, setup_managed_proxy } from "./gc-handles"; -import { Module, mono_assert, runtimeHelpers } from "./globals"; +import { mono_assert, runtimeHelpers } from "./globals"; import { ManagedError, set_gc_handle, set_js_handle, set_arg_type, set_arg_i32, set_arg_f64, set_arg_i52, set_arg_f32, set_arg_i16, set_arg_u8, set_arg_bool, set_arg_date, @@ -18,7 +18,7 @@ import { set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol, get_arg_gc_handle, get_arg_type, set_arg_proxy_context, get_arg_intptr } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; -import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; +import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8, malloc } from "./memory"; import { stringToMonoStringRoot, stringToUTF16 } from "./strings"; import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; @@ -218,7 +218,7 @@ export function marshal_string_to_cs (arg: JSMarshalerArgument, value: string) { function _marshal_string_to_cs_impl (arg: JSMarshalerArgument, value: string) { if (WasmEnableJsInteropByValue) { const bufferLen = value.length * 2; - const buffer = Module._malloc(bufferLen);// together with Marshal.FreeHGlobal + const buffer = malloc(bufferLen);// together with Marshal.FreeHGlobal stringToUTF16(buffer as any, buffer as any + bufferLen, value); set_arg_intptr(arg, buffer); set_arg_length(arg, value.length); @@ -458,7 +458,7 @@ export function marshal_array_to_cs_impl (arg: JSMarshalerArgument, value: Array mono_assert(element_size != -1, () => `Element type ${element_type} not supported`); const length = value.length; const buffer_length = element_size * length; - const buffer_ptr = Module._malloc(buffer_length); + const buffer_ptr = malloc(buffer_length) as any; if (element_type == MarshalerType.String) { mono_check(Array.isArray(value), "Value is not an Array"); _zero_region(buffer_ptr, buffer_length); @@ -494,11 +494,11 @@ export function marshal_array_to_cs_impl (arg: JSMarshalerArgument, value: Array targetView.set(value); } else if (element_type == MarshalerType.Int32) { mono_check(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array"); - const targetView = localHeapViewI32().subarray(buffer_ptr >> 2, (buffer_ptr >> 2) + length); + const targetView = localHeapViewI32().subarray(buffer_ptr >>> 2, (buffer_ptr >>> 2) + length); targetView.set(value); } else if (element_type == MarshalerType.Double) { mono_check(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array"); - const targetView = localHeapViewF64().subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); + const targetView = localHeapViewF64().subarray(buffer_ptr >>> 3, (buffer_ptr >>> 3) + length); targetView.set(value); } else { throw new Error("not implemented"); diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index 38354b39d0fe75..6b54bec99bc02c 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -345,6 +345,7 @@ export function mono_wasm_resolve_or_reject_promise_impl (args: JSMarshalerArgum mono_log_debug("This promise resolution/rejection can't be propagated to managed code, mono runtime already exited."); return; } + args = ((args as any) >>> 0) as any; const exc = get_arg(args, 0); const receiver_should_free = WasmEnableThreads && is_receiver_should_free(args); try { @@ -524,13 +525,13 @@ function _marshal_array_to_js_impl (arg: JSMarshalerArgument, element_type: Mars result[index] = _marshal_js_object_to_js(element_arg); } } else if (element_type == MarshalerType.Byte) { - const sourceView = localHeapViewU8().subarray(buffer_ptr, buffer_ptr + length); + const sourceView = localHeapViewU8().subarray(buffer_ptr >>> 0, (buffer_ptr >>> 0) + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Int32) { - const sourceView = localHeapViewI32().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 = localHeapViewF64().subarray(buffer_ptr >> 3, (buffer_ptr >> 3) + length); + const sourceView = localHeapViewF64().subarray(buffer_ptr >>> 3, (buffer_ptr >>> 3) + length); result = sourceView.slice();//copy } else { throw new Error(`NotImplementedException ${element_type}. ${jsinteropDoc}`); diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index b26a94926f88e7..5d537670c4d9db 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -232,7 +232,7 @@ export function get_arg_i32 (arg: JSMarshalerArgument): number { export function get_arg_intptr (arg: JSMarshalerArgument): number { mono_assert(arg, "Null arg"); - return getI32(arg); + return getU32(arg); } export function get_arg_i52 (arg: JSMarshalerArgument): number { @@ -291,7 +291,7 @@ export function set_arg_i32 (arg: JSMarshalerArgument, value: number): void { export function set_arg_intptr (arg: JSMarshalerArgument, value: VoidPtr): void { mono_assert(arg, "Null arg"); - setI32(arg, value); + setU32(arg, value); } export function set_arg_i52 (arg: JSMarshalerArgument, value: number): void { diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 1834bcf3b88891..48c6df465d960c 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -17,7 +17,7 @@ let alloca_base: VoidPtr, alloca_offset: VoidPtr, alloca_limit: VoidPtr; function _ensure_allocated (): void { if (alloca_base) return; - alloca_base = Module._malloc(alloca_buffer_size); + alloca_base = malloc(alloca_buffer_size); alloca_offset = alloca_base; alloca_limit = (alloca_base + alloca_buffer_size); } @@ -37,6 +37,11 @@ export function temp_malloc (size: number): VoidPtr { return result; } +// returns always uint32 (not negative Number) +export function malloc (size: number): VoidPtr { + return (Module._malloc(size) as any >>> 0) as any; +} + export function _create_temp_frame (): void { _ensure_allocated(); alloca_stack.push(alloca_offset); @@ -326,7 +331,7 @@ export function withStackAlloc (bytesWanted: number, f: (pt // and it is copied to that location. returns the address of the allocation. export function mono_wasm_load_bytes_into_heap (bytes: Uint8Array): VoidPtr { // pad sizes by 16 bytes for simd - const memoryOffset = Module._malloc(bytes.length + 16); + const memoryOffset = malloc(bytes.length + 16); if (memoryOffset <= 0) { mono_log_error(`malloc failed to allocate ${(bytes.length + 16)} bytes.`); throw new Error("Out of memory"); diff --git a/src/mono/browser/runtime/roots.ts b/src/mono/browser/runtime/roots.ts index fdd4b2a5302f2a..03b91db4fd5b1a 100644 --- a/src/mono/browser/runtime/roots.ts +++ b/src/mono/browser/runtime/roots.ts @@ -7,7 +7,7 @@ import cwraps from "./cwraps"; import { Module, mono_assert, runtimeHelpers } from "./globals"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal"; -import { _zero_region, localHeapViewU32 } from "./memory"; +import { _zero_region, localHeapViewU32, malloc } from "./memory"; import { gc_locked } from "./gc-lock"; const maxScratchRoots = 8192; @@ -31,7 +31,7 @@ export function mono_wasm_new_root_buffer (capacity: number, name?: string): Was capacity = capacity | 0; const capacityBytes = capacity * 4; - const offset = Module._malloc(capacityBytes); + const offset = malloc(capacityBytes); if ((offset % 4) !== 0) throw new Error("Malloc returned an unaligned offset"); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 6bc0b69c118826..8b37d90e281810 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -16,7 +16,7 @@ import { init_polyfills_async } from "./polyfills"; 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"; +import { CharPtr, EmscriptenModule, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; import { wait_for_all_assets } from "./assets"; import { replace_linker_placeholders } from "./exports-binding"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; @@ -28,7 +28,7 @@ import { populateEmscriptenPool, mono_wasm_init_threads } from "./pthreads"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads"; import { mono_wasm_pthread_ptr, update_thread_info } from "./pthreads"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; -import { localHeapViewU8 } from "./memory"; +import { localHeapViewU8, malloc } from "./memory"; import { assertNoProxies } from "./gc-handles"; import { runtimeList } from "./exports"; import { nativeAbort, nativeExit } from "./run"; @@ -70,11 +70,11 @@ export function configureEmscriptenStartup (module: DotnetModuleInternal): void // these all could be overridden on DotnetModuleConfig, we are chaing them to async below, as opposed to emscripten // when user set configSrc or config, we are running our default startup sequence. const userInstantiateWasm: undefined | ((imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any) = module.instantiateWasm; - const userPreInit: (() => void)[] = !module.preInit ? [] : typeof module.preInit === "function" ? [module.preInit] : module.preInit; - const userPreRun: (() => void)[] = !module.preRun ? [] : typeof module.preRun === "function" ? [module.preRun] : module.preRun as any; - const userpostRun: (() => void)[] = !module.postRun ? [] : typeof module.postRun === "function" ? [module.postRun] : module.postRun as any; + const userPreInit: ((module:EmscriptenModule) => void)[] = !module.preInit ? [] : typeof module.preInit === "function" ? [module.preInit] : module.preInit; + const userPreRun: ((module:EmscriptenModule) => void)[] = !module.preRun ? [] : typeof module.preRun === "function" ? [module.preRun] : module.preRun as any; + const userpostRun: ((module:EmscriptenModule) => void)[] = !module.postRun ? [] : typeof module.postRun === "function" ? [module.postRun] : module.postRun as any; // eslint-disable-next-line @typescript-eslint/no-empty-function - const userOnRuntimeInitialized: () => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; + const userOnRuntimeInitialized: (module:EmscriptenModule) => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.native.wasm) @@ -144,7 +144,7 @@ async function instantiateWasmWorker ( Module.wasmModule = null; } -function preInit (userPreInit: (() => void)[]) { +function preInit (userPreInit: ((module:EmscriptenModule) => void)[]) { Module.addRunDependency("mono_pre_init"); const mark = startMeasure(); try { @@ -152,7 +152,7 @@ function preInit (userPreInit: (() => void)[]) { mono_log_debug("preInit"); runtimeHelpers.beforePreInit.promise_control.resolve(); // all user Module.preInit callbacks - userPreInit.forEach(fn => fn()); + userPreInit.forEach(fn => fn(Module)); } catch (err) { mono_log_error("user preInint() failed", err); loaderHelpers.mono_exit(1, err); @@ -219,7 +219,7 @@ export function preRunWorker () { } } -async function preRunAsync (userPreRun: (() => void)[]) { +async function preRunAsync (userPreRun: ((module:EmscriptenModule) => void)[]) { Module.addRunDependency("mono_pre_run_async"); // wait for previous stages try { @@ -228,7 +228,7 @@ async function preRunAsync (userPreRun: (() => void)[]) { mono_log_debug("preRunAsync"); const mark = startMeasure(); // all user Module.preRun callbacks - userPreRun.map(fn => fn()); + userPreRun.map(fn => fn(Module)); endMeasure(mark, MeasuredBlock.preRun); } catch (err) { mono_log_error("preRunAsync() failed", err); @@ -240,7 +240,7 @@ async function preRunAsync (userPreRun: (() => void)[]) { Module.removeRunDependency("mono_pre_run_async"); } -async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) { +async function onRuntimeInitializedAsync (userOnRuntimeInitialized: (module:EmscriptenModule) => void) { try { // wait for previous stage await runtimeHelpers.afterPreRun.promise; @@ -343,7 +343,7 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) // call user code try { - userOnRuntimeInitialized(); + userOnRuntimeInitialized(Module); } catch (err: any) { mono_log_error("user callback onRuntimeInitialized() failed", err); throw err; @@ -361,7 +361,7 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: () => void) runtimeHelpers.afterOnRuntimeInitialized.promise_control.resolve(); } -async function postRunAsync (userpostRun: (() => void)[]) { +async function postRunAsync (userpostRun: ((module:EmscriptenModule) => void)[]) { // wait for previous stage try { await runtimeHelpers.afterOnRuntimeInitialized.promise; @@ -373,7 +373,7 @@ async function postRunAsync (userpostRun: (() => void)[]) { Module["FS_createPath"]("/", "usr/share", true, true); // all user Module.postRun callbacks - userpostRun.map(fn => fn()); + userpostRun.map(fn => fn(Module)); endMeasure(mark, MeasuredBlock.postRun); } catch (err) { mono_log_error("postRunAsync() failed", err); @@ -471,7 +471,7 @@ export function mono_wasm_set_runtime_options (options: string[]): void { if (!Array.isArray(options)) throw new Error("Expected runtimeOptions to be an array of strings"); - const argv = Module._malloc(options.length * 4); + const argv = malloc(options.length * 4); let aindex = 0; for (let i = 0; i < options.length; ++i) { const option = options[i]; @@ -631,7 +631,7 @@ export function bindings_init (): void { init_managed_exports(); initialize_marshalers_to_js(); initialize_marshalers_to_cs(); - runtimeHelpers._i52_error_scratch_buffer = Module._malloc(4); + runtimeHelpers._i52_error_scratch_buffer = malloc(4); endMeasure(mark, MeasuredBlock.bindingsInit); } catch (err) { mono_log_error("Error in bindings_init", err); @@ -665,7 +665,7 @@ export function mono_wasm_asm_loaded (assembly_name: CharPtr, assembly_ptr: numb export function mono_wasm_set_main_args (name: string, allRuntimeArguments: string[]): void { const main_argc = allRuntimeArguments.length + 1; - const main_argv = Module._malloc(main_argc * 4); + const main_argv = malloc(main_argc * 4); let aindex = 0; Module.setValue(main_argv + (aindex * 4), cwraps.mono_wasm_strdup(name), "i32"); aindex += 1; diff --git a/src/mono/browser/runtime/strings.ts b/src/mono/browser/runtime/strings.ts index 0eda60ba850a00..a03225efe6f43c 100644 --- a/src/mono/browser/runtime/strings.ts +++ b/src/mono/browser/runtime/strings.ts @@ -7,7 +7,7 @@ import { mono_wasm_new_root, mono_wasm_new_root_buffer } from "./roots"; import { MonoString, MonoStringNull, WasmRoot, WasmRootBuffer } from "./types/internal"; import { Module } from "./globals"; import cwraps from "./cwraps"; -import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region } from "./memory"; +import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region, malloc } from "./memory"; import { NativePointer, CharPtr, VoidPtr } from "./types/emscripten"; export const interned_js_string_table = new Map(); @@ -31,7 +31,7 @@ export function strings_init (): void { _text_decoder_utf8_validating = new TextDecoder("utf-8"); _text_encoder_utf8 = new TextEncoder(); } - mono_wasm_string_decoder_buffer = Module._malloc(12); + mono_wasm_string_decoder_buffer = malloc(12); } if (!mono_wasm_string_root) mono_wasm_string_root = mono_wasm_new_root(); @@ -49,7 +49,7 @@ export function stringToUTF8 (str: string): Uint8Array { export function stringToUTF8Ptr (str: string): CharPtr { const size = Module.lengthBytesUTF8(str) + 1; - const ptr = Module._malloc(size) as any; + const ptr = malloc(size) as any; const buffer = localHeapViewU8().subarray(ptr, ptr + size); Module.stringToUTF8Array(str, buffer, 0, size); buffer[size - 1] = 0; @@ -113,7 +113,7 @@ export function stringToUTF16 (dstPtr: number, endPtr: number, text: string) { export function stringToUTF16Ptr (str: string): VoidPtr { const bytes = (str.length + 1) * 2; - const ptr = Module._malloc(bytes) as any; + const ptr = malloc(bytes) as any; _zero_region(ptr, str.length * 2); stringToUTF16(ptr, ptr + bytes, str); return ptr; @@ -264,7 +264,7 @@ function stringToMonoStringNewRoot (string: string, result: WasmRoot // TODO this could be stack allocated for small strings // or temp_malloc/alloca for large strings // or skip the scratch buffer entirely, and make a new MonoString of size string.length, pin it, and then call stringToUTF16 to write directly into the MonoString's chars - const buffer = Module._malloc(bufferLen); + const buffer = malloc(bufferLen); stringToUTF16(buffer as any, buffer as any + bufferLen, string); cwraps.mono_wasm_string_from_utf16_ref(buffer, string.length, result.address); Module._free(buffer); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs index aa9ee1819ae2ad..956ab0e33a0021 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs @@ -15,23 +15,23 @@ public partial class MemoryTest [JSExport] internal static void Run() { - // Allocate over 2GB space, 2 621 440 000 bytes - const int arrayCnt = 25; + // Allocate over 1GB space + const int arrayCnt = 10; int[][] arrayHolder = new int[arrayCnt][]; string errors = ""; - TestOutput.WriteLine("Starting over 2GB array allocation"); + TestOutput.WriteLine("Starting over 1GB array allocation"); for (int i = 0; i < arrayCnt; i++) { try { - arrayHolder[i] = new int[1024 * 1024 * 25]; + arrayHolder[i] = new int[1024 * 1024 * 100]; } catch (Exception ex) { errors += $"Exception {ex} was thrown on i={i}"; } } - TestOutput.WriteLine("Finished over 2GB array allocation"); + TestOutput.WriteLine("Finished over 1GB array allocation"); // call a method many times to trigger tier-up optimization string randomString = GenerateRandomString(1000); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index bc9cc730a364e9..6f315e6706b954 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -127,6 +127,19 @@ switch (testCase) { }; dotnet.withConfig({ maxParallelDownloads: maxParallelDownloads }); break; + case "AllocateLargeHeapThenInterop": + dotnet.withEnvironmentVariable("MONO_LOG_LEVEL", "debug") + dotnet.withEnvironmentVariable("MONO_LOG_MASK", "gc") + dotnet.withModuleConfig({ + preRun: (Module) => { + // wasting 2GB of memory + for (let i = 0; i < 210; i++) { + testOutput(`malloc test 1m ${Module._malloc(10 * 1024 * 1024)}`); + } + testOutput(`WASM ${Module.HEAP32.byteLength} bytes.`); + } + }) + break; case "ProfilerTest": dotnet.withConfig({ logProfilerOptions: { From 53ba4bcb05586a77a9abffacbc8554364b0dc99c Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Tue, 22 Oct 2024 09:27:19 +0200 Subject: [PATCH 2/9] more --- src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs index 956ab0e33a0021..7f838a9d34a94e 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs @@ -16,7 +16,7 @@ public partial class MemoryTest internal static void Run() { // Allocate over 1GB space - const int arrayCnt = 10; + const int arrayCnt = 25; int[][] arrayHolder = new int[arrayCnt][]; string errors = ""; TestOutput.WriteLine("Starting over 1GB array allocation"); @@ -24,7 +24,7 @@ internal static void Run() { try { - arrayHolder[i] = new int[1024 * 1024 * 100]; + arrayHolder[i] = new int[1024 * 1024 * 25]; } catch (Exception ex) { From 45e23a52ff13dac90434bb6083d6a70b2deff411 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 22 Oct 2024 12:25:41 +0200 Subject: [PATCH 3/9] more --- .../wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs | 8 ++++---- .../wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs index 7f838a9d34a94e..d46a85fc916af8 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs @@ -15,11 +15,11 @@ public partial class MemoryTest [JSExport] internal static void Run() { - // Allocate over 1GB space - const int arrayCnt = 25; + // Allocate 250MB managed space above 2GB already wasted before startup + const int arrayCnt = 10; int[][] arrayHolder = new int[arrayCnt][]; string errors = ""; - TestOutput.WriteLine("Starting over 1GB array allocation"); + TestOutput.WriteLine("Starting over managed array allocation"); for (int i = 0; i < arrayCnt; i++) { try @@ -31,7 +31,7 @@ internal static void Run() errors += $"Exception {ex} was thrown on i={i}"; } } - TestOutput.WriteLine("Finished over 1GB array allocation"); + TestOutput.WriteLine("Finished over managed array allocation"); // call a method many times to trigger tier-up optimization string randomString = GenerateRandomString(1000); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index 6f315e6706b954..ae00040024799a 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -134,7 +134,7 @@ switch (testCase) { preRun: (Module) => { // wasting 2GB of memory for (let i = 0; i < 210; i++) { - testOutput(`malloc test 1m ${Module._malloc(10 * 1024 * 1024)}`); + testOutput(`wasting 1m ${Module._malloc(10 * 1024 * 1024)}`); } testOutput(`WASM ${Module.HEAP32.byteLength} bytes.`); } From 9d09c93ff12c56d2928a8f0f213d3124228d8339 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Fri, 25 Oct 2024 11:17:17 +0200 Subject: [PATCH 4/9] Update src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs Co-authored-by: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> --- src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs index d46a85fc916af8..ef8e2721933bc1 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs @@ -19,7 +19,7 @@ internal static void Run() const int arrayCnt = 10; int[][] arrayHolder = new int[arrayCnt][]; string errors = ""; - TestOutput.WriteLine("Starting over managed array allocation"); + TestOutput.WriteLine("Starting managed array allocation"); for (int i = 0; i < arrayCnt; i++) { try From 2685179e920a16cb740c93630eb5f0c0b8a5efc8 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Fri, 25 Oct 2024 11:17:23 +0200 Subject: [PATCH 5/9] Update src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs Co-authored-by: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> --- src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs index ef8e2721933bc1..827c0a151c2c0e 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs @@ -31,7 +31,7 @@ internal static void Run() errors += $"Exception {ex} was thrown on i={i}"; } } - TestOutput.WriteLine("Finished over managed array allocation"); + TestOutput.WriteLine("Finished managed array allocation"); // call a method many times to trigger tier-up optimization string randomString = GenerateRandomString(1000); From b0b9d3eb473b739100bbeb055def836ae685196b Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 31 Oct 2024 10:10:35 +0100 Subject: [PATCH 6/9] Remove redundant condition. --- src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs index 239950d1fd7279..d0589c04f1ddd1 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs @@ -30,7 +30,7 @@ public async Task AllocateLargeHeapThenRepeatedlyInterop() { string config = "Release"; CopyTestAsset("WasmBasicTestApp", "MemoryTests", "App"); - string extraArgs = BuildTestBase.IsUsingWorkloads ? "-p:EmccMaximumHeapSize=4294901760" : "-p:EmccMaximumHeapSize=4294901760"; + string extraArgs = "-p:EmccMaximumHeapSize=4294901760"; BuildProject(config, assertAppBundle: false, extraArgs: extraArgs, expectSuccess: BuildTestBase.IsUsingWorkloads); if (BuildTestBase.IsUsingWorkloads) From 820b05ec58abe6daca3e99dada989e575e75ea84 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 31 Oct 2024 10:36:49 +0100 Subject: [PATCH 7/9] Feedback: helper function. --- src/mono/browser/runtime/invoke-js.ts | 12 ++++++------ src/mono/browser/runtime/marshal-to-cs.ts | 8 +++++--- src/mono/browser/runtime/marshal-to-js.ts | 13 ++++++++----- src/mono/browser/runtime/memory.ts | 4 ++++ 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index aabe6d93df2333..208e7be8c2e9a3 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import { marshal_exception_to_cs, bind_arg_marshal_to_cs, marshal_task_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, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid, get_sync_done_semaphore_ptr, get_arg } from "./marshal"; -import { forceThreadMemoryViewRefresh } from "./memory"; +import { fixupPointer, forceThreadMemoryViewRefresh } from "./memory"; import { JSFunctionSignature, JSMarshalerArguments, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType, VoidPtrNull } from "./types/internal"; import { VoidPtr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; @@ -24,7 +24,7 @@ export const js_import_wrapper_by_fn_handle: Function[] = [null];// 0th slo export function mono_wasm_bind_js_import_ST (signature: JSFunctionSignature): VoidPtr { if (WasmEnableThreads) return VoidPtrNull; assert_js_interop(); - signature = ((signature as any) >>> 0) as any; + signature = fixupPointer(signature, 0); try { bind_js_import(signature); return VoidPtrNull; @@ -36,8 +36,8 @@ export function mono_wasm_bind_js_import_ST (signature: JSFunctionSignature): Vo export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, args: JSMarshalerArguments) { if (!WasmEnableThreads) return; assert_js_interop(); - signature = ((signature as any) >>> 0) as any; - args = ((args as any) >>> 0) as any; + signature = fixupPointer(signature, 0); + args = fixupPointer(args, 0); const function_handle = get_signature_handle(signature); @@ -76,7 +76,7 @@ export function mono_wasm_invoke_jsimport_MT (signature: JSFunctionSignature, ar export function mono_wasm_invoke_jsimport_ST (function_handle: JSFnHandle, args: JSMarshalerArguments): void { if (WasmEnableThreads) return; loaderHelpers.assert_runtime_running(); - args = ((args as any) >>> 0) as any; + args = fixupPointer(args, 0); const bound_fn = js_import_wrapper_by_fn_handle[function_handle]; mono_assert(bound_fn, () => `Imported function handle expected ${function_handle}`); bound_fn(args); @@ -368,7 +368,7 @@ export function mono_wasm_invoke_js_function_impl (bound_function_js_handle: JSH loaderHelpers.assert_runtime_running(); const bound_fn = mono_wasm_get_jsobj_from_js_handle(bound_function_js_handle); mono_assert(bound_fn && typeof (bound_fn) === "function" && bound_fn[bound_js_function_symbol], () => `Bound function handle expected ${bound_function_js_handle}`); - args = ((args as any) >>> 0) as any; + args = fixupPointer(args, 0); bound_fn(args); } diff --git a/src/mono/browser/runtime/marshal-to-cs.ts b/src/mono/browser/runtime/marshal-to-cs.ts index 5926828bbe7016..326905214f9778 100644 --- a/src/mono/browser/runtime/marshal-to-cs.ts +++ b/src/mono/browser/runtime/marshal-to-cs.ts @@ -18,7 +18,7 @@ import { set_arg_element_type, ManagedObject, JavaScriptMarshalerArgSize, proxy_debug_symbol, get_arg_gc_handle, get_arg_type, set_arg_proxy_context, get_arg_intptr } from "./marshal"; import { get_marshaler_to_js_by_type } from "./marshal-to-js"; -import { _zero_region, localHeapViewF64, localHeapViewI32, localHeapViewU8, malloc } from "./memory"; +import { _zero_region, fixupPointer, localHeapViewF64, localHeapViewI32, localHeapViewU8, malloc } from "./memory"; import { stringToMonoStringRoot, stringToUTF16 } from "./strings"; import { JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToCs, MarshalerType } from "./types/internal"; import { TypedArray } from "./types/emscripten"; @@ -494,11 +494,13 @@ export function marshal_array_to_cs_impl (arg: JSMarshalerArgument, value: Array targetView.set(value); } else if (element_type == MarshalerType.Int32) { mono_check(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array"); - const targetView = localHeapViewI32().subarray(buffer_ptr >>> 2, (buffer_ptr >>> 2) + length); + const bufferOffset = fixupPointer(buffer_ptr, 2); + const targetView = localHeapViewI32().subarray(bufferOffset, bufferOffset + length); targetView.set(value); } else if (element_type == MarshalerType.Double) { mono_check(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array"); - const targetView = localHeapViewF64().subarray(buffer_ptr >>> 3, (buffer_ptr >>> 3) + length); + const bufferOffset = fixupPointer(buffer_ptr, 3); + const targetView = localHeapViewF64().subarray(bufferOffset, bufferOffset + length); targetView.set(value); } else { throw new Error("not implemented"); diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index 6b54bec99bc02c..cd94f1ae765b7b 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -20,7 +20,7 @@ import { monoStringToString, utf16ToString } from "./strings"; import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType, JSHandle } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs"; -import { localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; +import { fixupPointer, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { call_delegate } from "./managed-exports"; import { mono_log_debug } from "./logging"; import { invoke_later_when_on_ui_thread_async } from "./invoke-js"; @@ -345,7 +345,7 @@ export function mono_wasm_resolve_or_reject_promise_impl (args: JSMarshalerArgum mono_log_debug("This promise resolution/rejection can't be propagated to managed code, mono runtime already exited."); return; } - args = ((args as any) >>> 0) as any; + args = fixupPointer(args, 0); const exc = get_arg(args, 0); const receiver_should_free = WasmEnableThreads && is_receiver_should_free(args); try { @@ -525,13 +525,16 @@ function _marshal_array_to_js_impl (arg: JSMarshalerArgument, element_type: Mars result[index] = _marshal_js_object_to_js(element_arg); } } else if (element_type == MarshalerType.Byte) { - const sourceView = localHeapViewU8().subarray(buffer_ptr >>> 0, (buffer_ptr >>> 0) + length); + const bufferOffset = fixupPointer(buffer_ptr, 0); + const sourceView = localHeapViewU8().subarray(bufferOffset, bufferOffset + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Int32) { - const sourceView = localHeapViewI32().subarray(buffer_ptr >>> 2, (buffer_ptr >>> 2) + length); + const bufferOffset = fixupPointer(buffer_ptr, 2); + const sourceView = localHeapViewI32().subarray(bufferOffset, bufferOffset + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Double) { - const sourceView = localHeapViewF64().subarray(buffer_ptr >>> 3, (buffer_ptr >>> 3) + length); + const bufferOffset =fixupPointer(buffer_ptr, 3); + const sourceView = localHeapViewF64().subarray(bufferOffset, bufferOffset + length); result = sourceView.slice();//copy } else { throw new Error(`NotImplementedException ${element_type}. ${jsinteropDoc}`); diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 48c6df465d960c..14747832a7c19f 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -506,3 +506,7 @@ export function forceThreadMemoryViewRefresh () { runtimeHelpers.updateMemoryViews(); } } + +export function fixupPointer(signature: any, shiftAmount: number): any { + return ((signature as any) >>> shiftAmount) as any; +} From 1cd4b696a589f67c0a84a6f0b2958a17bb62ec8d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 31 Oct 2024 11:34:50 +0100 Subject: [PATCH 8/9] Feedback: free can we wrapped. --- ...SM Transition wrappers and trampolines.txt | 44 +++++++++++++++++++ src/mono/browser/runtime/debug.ts | 6 +-- src/mono/browser/runtime/interp-pgo.ts | 8 ++-- src/mono/browser/runtime/invoke-js.ts | 4 +- .../runtime/jiterpreter-interp-entry.ts | 7 +-- .../browser/runtime/jiterpreter-jit-call.ts | 5 ++- src/mono/browser/runtime/jiterpreter.ts | 8 ++-- src/mono/browser/runtime/marshal-to-js.ts | 12 ++--- src/mono/browser/runtime/memory.ts | 6 ++- src/mono/browser/runtime/roots.ts | 6 +-- src/mono/browser/runtime/strings.ts | 4 +- 11 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 src/mono/browser/runtime/WASM Transition wrappers and trampolines.txt diff --git a/src/mono/browser/runtime/WASM Transition wrappers and trampolines.txt b/src/mono/browser/runtime/WASM Transition wrappers and trampolines.txt new file mode 100644 index 00000000000000..b7968ba8ac164c --- /dev/null +++ b/src/mono/browser/runtime/WASM Transition wrappers and trampolines.txt @@ -0,0 +1,44 @@ +WASM Transition wrappers and trampolines +When running managed code in the browser there are multiple scenarios that call for JITcode or pre-compiled wrappers/trampolines. I'll attempt to enumerate them here and describe how we could address each scenario. The following does not apply to WASI since it has no facilities for JIT, but the same motivations apply due to similar platform constraints. + +Interpreter to native code +Unlike every other target, it's not possible to call a target function of an unknown signature in WASM. The call instruction encodes an exact signature (number of parameters, parameter types, and return type). The call instruction is also variadic, so it expects a set number of parameters on the stack. + +If you know there are a limited set of signatures you need to call, you can pre-generate a bunch of individual wrappers at compile time, or have a switch statement that dispatches to a call with the appropriate signature. Mono WASM currently uses a combination of both approaches to address these scenarios. The jiterpreter has support for optimizing a subset of these wrappers on-the-fly. + +With a JIT facility, you can generate a small WASM helper function on-the-fly that can load parameter values from the stack/heap and then pass them to a target function pointer with the appropriate signature. The jiterpreter already has support for doing this, which could be generalized or (ideally) reimplemented in a simpler form to support all scenarios for these interpreter->native transitions. + +Native code to interpreter +Similarly, if a native function expects a function pointer with a given signature, we can't hand it a generic dispatch helper (the signature will not match) or a trampoline (we don't know in advance every possible signature that the user might want to make a function pointer for). There are multiple solutions: + +Restrict the set of native->interp transitions at build time. This is what we do in Mono WASM, using attributes like UnmanagedCallersOnly. +JIT trampolines on demand with the appropriate signature. Each target managed function would need a dedicated trampoline, which is unfortunate. +Change the call signature on the native side to accept a userdata parameter which contains the managed function to call. In this case, we could reuse a generic trampoline for all call targets, and only need one trampoline per signature. This is currently how native-to-interp transition wrappers work in Mono WASM, and the jiterpreter has support for generating optimized trampolines with matching signatures on-the-fly. +Native code to arbitrary managed code directly +In Mono Wasm AOT we currently try to compile every managed method into a native wasm function. The calling convention for these methods does not match C, so any transition from native code directly into managed code requires a calling convention wrapper for each signature. These are generated at compile time, and it is feasible to know all the possible signatures since we know every signature in the managed binary, and the wasm type system's expressiveness is so limited that a significant % of managed signatures all map to the same wasm signature. + +These transition wrappers are somewhat expensive as-is and have similar constraints at present (you need to annotate the method(s) you expect to call so we can generate dedicated wrappers, because we don't have a way to generate dedicated trampolines.) The jiterpreter could address this if necessary, but currently doesn't. + +This means that at present converting a delegate to a function pointer does not work in WASM. As said above, this is fixable. + +Arbitrary managed code to arbitrary native code +This can be done seamlessly at AOT compile time as long as we know the target signature - we perform a wasm indirect call, specifying the signature and loading the right arguments onto the stack. + +Delegate invocations are more complex and typically bounce through a helper, with arguments temporarily stored on the stack or in the heap and flowing through a calling convention helper like mentioned above. More on this below. + +Delegates +A given delegate can point to various things: + +External native code (i.e. libc) +Internal native code (i.e. an icall) +AOT'd managed code +Interpreted managed code +JITted managed code +At present in Mono WASM we solve this by making all delegate invocations go through a helper which knows how to dispatch to the right kind of handler for each scenario, and we store the arguments on the stack/in the heap. At present for WASM we don't have the 'JITted managed code' scenario and some of the others may not work as expected, due to the ftnptr problem (explained below.) + +The ftnptr problem +On other targets, it's possible to create a unique function pointer value for any call target, managed or native, by jitting a little trampoline on demand. On WASM it is presently not straightforward to do this (we could do it with the jiterpreter), so we don't. With the interpreter in the picture it gets more complex. + +There are two types of function pointer; one is a 'real' native function pointer to i.e. a libc function, the other is a 'fake' function pointer which points to an interpreted method (which somewhat obviously has no dedicated trampoline or callable function pointer). As a result, any time a ftnptr is used, we need to know what 'kind' of pointer it is and invoke it appropriately. + +If a ftnptr 'leaks' from the managed world into the native world, or vice versa, we have to be careful to do something appropriate to convert one type to the other, or detect this unsafe operation and abort. At present we have some known deficiencies in this area. diff --git a/src/mono/browser/runtime/debug.ts b/src/mono/browser/runtime/debug.ts index d273632f52b01e..522c3c41158b28 100644 --- a/src/mono/browser/runtime/debug.ts +++ b/src/mono/browser/runtime/debug.ts @@ -1,12 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { INTERNAL, Module, loaderHelpers, runtimeHelpers } from "./globals"; +import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; import { toBase64StringImpl } from "./base64"; import cwraps from "./cwraps"; import { VoidPtr, CharPtr } from "./types/emscripten"; import { mono_log_warn } from "./logging"; -import { forceThreadMemoryViewRefresh, localHeapViewU8, malloc } from "./memory"; +import { forceThreadMemoryViewRefresh, free, localHeapViewU8, malloc } from "./memory"; import { utf8ToString } from "./strings"; const commands_received: any = new Map(); commands_received.remove = function (key: number): CommandResponse { @@ -64,7 +64,7 @@ export function mono_wasm_add_dbg_command_received (res_ok: boolean, id: number, function mono_wasm_malloc_and_set_debug_buffer (command_parameters: string) { if (command_parameters.length > _debugger_buffer_len) { if (_debugger_buffer) - Module._free(_debugger_buffer); + free(_debugger_buffer); _debugger_buffer_len = Math.max(command_parameters.length, _debugger_buffer_len, 256); _debugger_buffer = malloc(_debugger_buffer_len); } diff --git a/src/mono/browser/runtime/interp-pgo.ts b/src/mono/browser/runtime/interp-pgo.ts index 7ab9e6d9a4c3aa..3701deafd53d81 100644 --- a/src/mono/browser/runtime/interp-pgo.ts +++ b/src/mono/browser/runtime/interp-pgo.ts @@ -3,9 +3,9 @@ import ProductVersion from "consts:productVersion"; import WasmEnableThreads from "consts:wasmEnableThreads"; -import { ENVIRONMENT_IS_WEB, Module, loaderHelpers, runtimeHelpers } from "./globals"; +import { ENVIRONMENT_IS_WEB, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_log_info, mono_log_error, mono_log_warn } from "./logging"; -import { localHeapViewU8, malloc } from "./memory"; +import { free, localHeapViewU8, malloc } from "./memory"; import cwraps from "./cwraps"; import { MonoConfigInternal } from "./types/internal"; @@ -47,7 +47,7 @@ export async function interp_pgo_save_data () { cleanupCache(tablePrefix, cacheKey); // no await - Module._free(pData); + free(pData); } catch (exc) { mono_log_error(`Failed to save interp_pgo table: ${exc}`); } @@ -73,7 +73,7 @@ export async function interp_pgo_load_data () { if (cwraps.mono_interp_pgo_load_table(pData, data.byteLength)) mono_log_error("Failed to load interp_pgo table (Unknown error)"); - Module._free(pData); + free(pData); } async function openCache (): Promise { diff --git a/src/mono/browser/runtime/invoke-js.ts b/src/mono/browser/runtime/invoke-js.ts index 208e7be8c2e9a3..0440a29c27e81b 100644 --- a/src/mono/browser/runtime/invoke-js.ts +++ b/src/mono/browser/runtime/invoke-js.ts @@ -6,7 +6,7 @@ import BuildConfiguration from "consts:configuration"; import { marshal_exception_to_cs, bind_arg_marshal_to_cs, marshal_task_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, get_signature_handle, get_signature_function_name, get_signature_module_name, is_receiver_should_free, get_caller_native_tid, get_sync_done_semaphore_ptr, get_arg } from "./marshal"; -import { fixupPointer, forceThreadMemoryViewRefresh } from "./memory"; +import { fixupPointer, forceThreadMemoryViewRefresh, free } from "./memory"; import { JSFunctionSignature, JSMarshalerArguments, BoundMarshalerToJs, JSFnHandle, BoundMarshalerToCs, JSHandle, MarshalerType, VoidPtrNull } from "./types/internal"; import { VoidPtr } from "./types/emscripten"; import { INTERNAL, Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; @@ -338,7 +338,7 @@ function bind_fn (closure: BindingClosure) { marshal_exception_to_cs(args, ex); } finally { if (receiver_should_free) { - Module._free(args as any); + free(args as any); } endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } diff --git a/src/mono/browser/runtime/jiterpreter-interp-entry.ts b/src/mono/browser/runtime/jiterpreter-interp-entry.ts index 7c1e67e28b4c5c..f152a70da25706 100644 --- a/src/mono/browser/runtime/jiterpreter-interp-entry.ts +++ b/src/mono/browser/runtime/jiterpreter-interp-entry.ts @@ -3,10 +3,11 @@ import { MonoMethod, MonoType } from "./types/internal"; import { NativePointer } from "./types/emscripten"; -import { Module, mono_assert } from "./globals"; +import { mono_assert } from "./globals"; import { setI32, getU32_unaligned, _zero_region, - malloc + malloc, + free } from "./memory"; import { WasmOpcode } from "./jiterpreter-opcodes"; import cwraps from "./cwraps"; @@ -137,7 +138,7 @@ class TrampolineInfo { this.traceName = subName; } finally { if (namePtr) - Module._free(namePtr); + free(namePtr); } } diff --git a/src/mono/browser/runtime/jiterpreter-jit-call.ts b/src/mono/browser/runtime/jiterpreter-jit-call.ts index 48e9797cb29e8f..102e402f284e4c 100644 --- a/src/mono/browser/runtime/jiterpreter-jit-call.ts +++ b/src/mono/browser/runtime/jiterpreter-jit-call.ts @@ -5,7 +5,8 @@ import { MonoType, MonoMethod } from "./types/internal"; import { NativePointer, VoidPtr } from "./types/emscripten"; import { Module, mono_assert, runtimeHelpers } from "./globals"; import { - getU8, getI32_unaligned, getU32_unaligned, setU32_unchecked, receiveWorkerHeapViews + getU8, getI32_unaligned, getU32_unaligned, setU32_unchecked, receiveWorkerHeapViews, + free } from "./memory"; import { WasmOpcode, WasmValtype } from "./jiterpreter-opcodes"; import { @@ -152,7 +153,7 @@ class TrampolineInfo { suffix = utf8ToString(pMethodName); } finally { if (pMethodName) - Module._free(pMethodName); + free(pMethodName); } } diff --git a/src/mono/browser/runtime/jiterpreter.ts b/src/mono/browser/runtime/jiterpreter.ts index 996e7ed34f9d15..abaa7d63cd8e56 100644 --- a/src/mono/browser/runtime/jiterpreter.ts +++ b/src/mono/browser/runtime/jiterpreter.ts @@ -3,8 +3,8 @@ import { MonoMethod } from "./types/internal"; import { NativePointer } from "./types/emscripten"; -import { Module, mono_assert, runtimeHelpers } from "./globals"; -import { getU16 } from "./memory"; +import { mono_assert, runtimeHelpers } from "./globals"; +import { free, getU16 } from "./memory"; import { WasmValtype, WasmOpcode, getOpcodeName } from "./jiterpreter-opcodes"; import { MintOpcode } from "./mintops"; import cwraps from "./cwraps"; @@ -1004,7 +1004,7 @@ export function mono_interp_tier_prepare_jiterpreter ( ) { const pMethodName = cwraps.mono_wasm_method_get_full_name(method); methodFullName = utf8ToString(pMethodName); - Module._free(pMethodName); + free(pMethodName); } const methodName = utf8ToString(cwraps.mono_wasm_method_get_name(method)); info.name = methodFullName || methodName; @@ -1148,7 +1148,7 @@ export function jiterpreter_dump_stats (concise?: boolean): void { const pMethodName = cwraps.mono_wasm_method_get_full_name(targetMethod); const targetMethodName = utf8ToString(pMethodName); const hitCount = callTargetCounts[targetMethod]; - Module._free(pMethodName); + free(pMethodName); mono_log_info(`${targetMethodName} ${hitCount}`); } } diff --git a/src/mono/browser/runtime/marshal-to-js.ts b/src/mono/browser/runtime/marshal-to-js.ts index cd94f1ae765b7b..29d9f56ff6fb3a 100644 --- a/src/mono/browser/runtime/marshal-to-js.ts +++ b/src/mono/browser/runtime/marshal-to-js.ts @@ -7,7 +7,7 @@ import WasmEnableJsInteropByValue from "consts:wasmEnableJsInteropByValue"; import cwraps from "./cwraps"; import { _lookup_js_owned_object, mono_wasm_get_js_handle, mono_wasm_get_jsobj_from_js_handle, mono_wasm_release_cs_owned_object, register_with_jsv_handle, setup_managed_proxy, teardown_managed_proxy } from "./gc-handles"; -import { Module, loaderHelpers, mono_assert } from "./globals"; +import { loaderHelpers, mono_assert } from "./globals"; import { ManagedObject, ManagedError, get_arg_gc_handle, get_arg_js_handle, get_arg_type, get_arg_i32, get_arg_f64, get_arg_i52, get_arg_i16, get_arg_u8, get_arg_f32, @@ -20,7 +20,7 @@ import { monoStringToString, utf16ToString } from "./strings"; import { GCHandleNull, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, BoundMarshalerToJs, MarshalerType, JSHandle } from "./types/internal"; import { TypedArray } from "./types/emscripten"; import { get_marshaler_to_cs_by_type, jsinteropDoc, marshal_exception_to_cs } from "./marshal-to-cs"; -import { fixupPointer, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; +import { fixupPointer, free, localHeapViewF64, localHeapViewI32, localHeapViewU8 } from "./memory"; import { call_delegate } from "./managed-exports"; import { mono_log_debug } from "./logging"; import { invoke_later_when_on_ui_thread_async } from "./invoke-js"; @@ -364,7 +364,7 @@ export function mono_wasm_resolve_or_reject_promise_impl (args: JSMarshalerArgum holder.resolve_or_reject(type, js_handle, arg_value); if (receiver_should_free) { // this works together with AllocHGlobal in JSFunctionBinding.ResolveOrRejectPromise - Module._free(args as any); + free(args as any); } else { set_arg_type(res, MarshalerType.Void); set_arg_type(exc, MarshalerType.None); @@ -387,7 +387,7 @@ export function marshal_string_to_js (arg: JSMarshalerArgument): string | null { const buffer = get_arg_intptr(arg); const len = get_arg_length(arg) * 2; const value = utf16ToString(buffer, buffer + len); - Module._free(buffer as any); + free(buffer as any); return value; } else { mono_assert(!WasmEnableThreads, "Marshaling strings by reference is not supported in multithreaded mode"); @@ -533,13 +533,13 @@ function _marshal_array_to_js_impl (arg: JSMarshalerArgument, element_type: Mars const sourceView = localHeapViewI32().subarray(bufferOffset, bufferOffset + length); result = sourceView.slice();//copy } else if (element_type == MarshalerType.Double) { - const bufferOffset =fixupPointer(buffer_ptr, 3); + const bufferOffset = fixupPointer(buffer_ptr, 3); const sourceView = localHeapViewF64().subarray(bufferOffset, bufferOffset + length); result = sourceView.slice();//copy } else { throw new Error(`NotImplementedException ${element_type}. ${jsinteropDoc}`); } - Module._free(buffer_ptr); + free(buffer_ptr); return result; } diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 14747832a7c19f..0f9f751854de17 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -42,6 +42,10 @@ export function malloc (size: number): VoidPtr { return (Module._malloc(size) as any >>> 0) as any; } +export function free (ptr: VoidPtr) { + Module._free(ptr); +} + export function _create_temp_frame (): void { _ensure_allocated(); alloca_stack.push(alloca_offset); @@ -507,6 +511,6 @@ export function forceThreadMemoryViewRefresh () { } } -export function fixupPointer(signature: any, shiftAmount: number): any { +export function fixupPointer (signature: any, shiftAmount: number): any { return ((signature as any) >>> shiftAmount) as any; } diff --git a/src/mono/browser/runtime/roots.ts b/src/mono/browser/runtime/roots.ts index 03b91db4fd5b1a..5b81331c29ff25 100644 --- a/src/mono/browser/runtime/roots.ts +++ b/src/mono/browser/runtime/roots.ts @@ -4,10 +4,10 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import cwraps from "./cwraps"; -import { Module, mono_assert, runtimeHelpers } from "./globals"; +import { mono_assert, runtimeHelpers } from "./globals"; import { VoidPtr, ManagedPointer, NativePointer } from "./types/emscripten"; import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal"; -import { _zero_region, localHeapViewU32, malloc } from "./memory"; +import { _zero_region, free, localHeapViewU32, malloc } from "./memory"; import { gc_locked } from "./gc-lock"; const maxScratchRoots = 8192; @@ -238,7 +238,7 @@ export class WasmRootBufferImpl implements WasmRootBuffer { mono_assert(!WasmEnableThreads || !gc_locked, "GC must not be locked when disposing a GC root"); cwraps.mono_wasm_deregister_root(this.__offset); _zero_region(this.__offset, this.__count * 4); - Module._free(this.__offset); + free(this.__offset); } this.__handle = (this.__offset) = this.__count = this.__offset32 = 0; diff --git a/src/mono/browser/runtime/strings.ts b/src/mono/browser/runtime/strings.ts index a03225efe6f43c..2b52f82b0320b8 100644 --- a/src/mono/browser/runtime/strings.ts +++ b/src/mono/browser/runtime/strings.ts @@ -7,7 +7,7 @@ import { mono_wasm_new_root, mono_wasm_new_root_buffer } from "./roots"; import { MonoString, MonoStringNull, WasmRoot, WasmRootBuffer } from "./types/internal"; import { Module } from "./globals"; import cwraps from "./cwraps"; -import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region, malloc } from "./memory"; +import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region, malloc, free } from "./memory"; import { NativePointer, CharPtr, VoidPtr } from "./types/emscripten"; export const interned_js_string_table = new Map(); @@ -267,7 +267,7 @@ function stringToMonoStringNewRoot (string: string, result: WasmRoot const buffer = malloc(bufferLen); stringToUTF16(buffer as any, buffer as any + bufferLen, string); cwraps.mono_wasm_string_from_utf16_ref(buffer, string.length, result.address); - Module._free(buffer); + free(buffer); } // When threading is enabled, TextDecoder does not accept a view of a From 641106af9453ca9dc3e398d8b3caa94e7ac833c7 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:39:47 +0100 Subject: [PATCH 9/9] Update src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek FiĊĦera --- src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index ae00040024799a..b2354f0672c3f1 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -134,7 +134,7 @@ switch (testCase) { preRun: (Module) => { // wasting 2GB of memory for (let i = 0; i < 210; i++) { - testOutput(`wasting 1m ${Module._malloc(10 * 1024 * 1024)}`); + testOutput(`wasting 10m ${Module._malloc(10 * 1024 * 1024)}`); } testOutput(`WASM ${Module.HEAP32.byteLength} bytes.`); }