diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 1b832de431db0c..6e11ef4f35708b 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -3459,6 +3459,11 @@ interp_free_method (MonoMethod *method) jit_mm_lock (jit_mm); imethod = (InterpMethod*)mono_internal_hash_table_lookup (&jit_mm->interp_code_hash, method); + +#if HOST_BROWSER + mono_jiterp_free_method_data (method, imethod); +#endif + mono_internal_hash_table_remove (&jit_mm->interp_code_hash, method); if (imethod && jit_mm->interp_method_pointer_hash) { if (imethod->jit_entry) diff --git a/src/mono/mono/mini/interp/jiterpreter.c b/src/mono/mono/mini/interp/jiterpreter.c index da0b79b2533916..0dd5ea27f9af7a 100644 --- a/src/mono/mono/mini/interp/jiterpreter.c +++ b/src/mono/mono/mini/interp/jiterpreter.c @@ -28,6 +28,10 @@ void jiterp_preserve_module (void); #include #include +#ifndef DISABLE_THREADS +#include +#endif + #include #include #include @@ -48,6 +52,7 @@ void jiterp_preserve_module (void); #include #include #include +#include #include "jiterpreter.h" @@ -478,6 +483,7 @@ mono_jiterp_type_get_raw_value_size (MonoType *type) { EMSCRIPTEN_KEEPALIVE void mono_jiterp_trace_bailout (int reason) { + // FIXME: Harmless race condition if threads are in use if (reason < 256) jiterp_trace_bailout_counts[reason]++; } @@ -670,6 +676,7 @@ should_generate_trace_here (InterpBasicBlock *bb) { for (InterpInst *ins = bb->first_ins; ins != NULL; ins = ins->next) { int value = jiterp_get_opcode_value(ins, &inside_branch_block); if (value < 0) { + // FIXME: Harmless race condition if threads are in use jiterpreter_abort_counts[ins->opcode]++; return current_trace_value >= mono_opt_jiterpreter_minimum_trace_value; } else if (value >= VALUE_SIMD) { @@ -743,7 +750,11 @@ trace_info_get (gint32 index) { static gint32 trace_info_alloc () { +#ifdef DISABLE_THREADS gint32 index = trace_count++, +#else + gint32 index = atomic_fetch_add ((atomic_int *)&trace_count, 1), +#endif limit = (MAX_TRACE_SEGMENTS * TRACE_SEGMENT_SIZE); // Make sure we're not out of space in the trace info table. if (index == limit) @@ -931,6 +942,52 @@ mono_interp_tier_prepare_jiterpreter_fast ( } } +void +mono_jiterp_free_method_data (MonoMethod *method, InterpMethod *imethod) +{ + MonoJitInfo *jinfo = imethod->jinfo; + const guint8 *start; + gboolean need_extra_free = TRUE; + + // Erase the method and interpmethod from all of the thread local JIT queues + mono_jiterp_tlqueue_purge_all (method); + mono_jiterp_tlqueue_purge_all (imethod); + + // FIXME: Enumerate all active threads and ensure we perform the free_method_data_js + // call on every thread. + + // Scan through the interp opcodes for the method and ensure that any jiterp traces + // owned by it are cleaned up. This will automatically clean up any AOT related data for + // the method in the process + if (jinfo) { + start = (const guint8*) jinfo->code_start; + const guint16 *p = (const guint16 *)start, + *end = (const guint16 *)(start + jinfo->code_size); + + while (p < end) { + switch (*p) { + case MINT_TIER_PREPARE_JITERPRETER: + case MINT_TIER_NOP_JITERPRETER: + case MINT_TIER_ENTER_JITERPRETER: + case MINT_TIER_MONITOR_JITERPRETER: { + JiterpreterOpcode *opcode = (JiterpreterOpcode *)p; + guint32 trace_index = opcode->trace_index; + need_extra_free = FALSE; + mono_jiterp_free_method_data_js (method, imethod, trace_index); + break; + } + } + p = mono_interp_dis_mintop_len (p); + } + } + + if (need_extra_free) { + // HACK: Perform a single free operation to clear out any stuff from the jit queues + // This will happen if we didn't encounter any jiterpreter traces in the method + mono_jiterp_free_method_data_js (method, imethod, 0); + } +} + // Used to parse runtime options that control the jiterpreter. This is *also* used at runtime // by the jiterpreter typescript to reconfigure the jiterpreter, for example if WASM EH is not // actually available even though it was enabled (to turn it off). @@ -1339,6 +1396,7 @@ mono_jiterp_monitor_trace (const guint16 *ip, void *_frame, void *locals) if (mono_opt_jiterpreter_trace_monitoring_log > 1) g_print ("trace #%d @%d '%s' accepted; average_penalty %f <= %f\n", opcode->trace_index, ip, frame->imethod->method->name, average_penalty, threshold); } else { + // FIXME: Harmless race condition if threads are in use traces_rejected++; if (mono_opt_jiterpreter_trace_monitoring_log > 0) { char * full_name = mono_method_get_full_name (frame->imethod->method); @@ -1491,6 +1549,135 @@ mono_jiterp_patch_opcode (volatile JiterpreterOpcode *ip, guint16 old_opcode, gu #endif } +/* + * Unordered thread-local pointer queue (used for do_jit_call and interp_entry wrappers) + * The queues are all tracked in a global list so that it is possible to perform a global + * 'purge item with this value from all queues' operation, which means queue operations + * are protected by a mutex + */ + +#define NUM_QUEUES 2 +static MonoNativeTlsKey queue_keys[NUM_QUEUES] = { 0 }; +#ifdef DISABLE_THREADS +gboolean queue_keys_initialized = FALSE; +#else +pthread_once_t queue_keys_initialized = PTHREAD_ONCE_INIT; +#endif +// NOTE: We're using OS mutexes here not coop mutexes, because we need to be able to run +// during a GC and at any point (before runtime startup or after shutdown) +// This means if we aren't careful we could deadlock, so we have to be cautious about +// what operations we perform while holding this mutex. +static mono_mutex_t queue_mutex; +static GPtrArray *shared_queues = NULL; + +static void +free_queue (void *ptr) { + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + g_ptr_array_remove_fast (shared_queues, ptr); + g_ptr_array_free ((GPtrArray *)ptr, TRUE); + mono_os_mutex_unlock (&queue_mutex); +} + +static void +initialize_queue_keys () { + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + shared_queues = g_ptr_array_new (); + + for (int i = 0; i < NUM_QUEUES; i++) + g_assert (mono_native_tls_alloc (&queue_keys[i], free_queue)); + +#ifdef DISABLE_THREADS + queue_keys_initialized = TRUE; +#endif + + mono_os_mutex_unlock (&queue_mutex); +} + +static MonoNativeTlsKey +get_queue_key (int queue) { + g_assert ((queue >= 0) && (queue < NUM_QUEUES)); + +#ifdef DISABLE_THREADS + if (!queue_keys_initialized) + initialize_queue_keys (); +#else + pthread_once (&queue_keys_initialized, initialize_queue_keys); +#endif + + return queue_keys[queue]; +} + +static GPtrArray * +get_queue (int queue) { + MonoNativeTlsKey key = get_queue_key (queue); + GPtrArray *result = NULL; + if ((result = (GPtrArray *)mono_native_tls_get_value (key)) == NULL) { + g_assert (mono_native_tls_set_value (key, result = g_ptr_array_new ())); + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + g_ptr_array_add (shared_queues, result); + mono_os_mutex_unlock (&queue_mutex); + } + return result; +} + +// Purges this item from all queues +void +mono_jiterp_tlqueue_purge_all (gpointer item) { + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + for (int i = 0; i < shared_queues->len; i++) { + GPtrArray *queue = (GPtrArray *)g_ptr_array_index (shared_queues, i); + gboolean ok = g_ptr_array_remove_fast (queue, item); + if (ok) { + // g_printf ("Purged %x from queue %x\n", (unsigned int)item, (unsigned int)queue); + } + } + mono_os_mutex_unlock (&queue_mutex); +} + +// Removes the next item from the queue, if any, and returns it (NULL if empty) +EMSCRIPTEN_KEEPALIVE gpointer +mono_jiterp_tlqueue_next (int queue) { + // This lock-per-call is unpleasant, but the queue is usually only going to contain + // a handful of items and will only be enumerated a few hundred times total during + // execution, so performance is not especially important compared to safety/simplicity + GPtrArray *items = get_queue (queue); + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + gpointer result = NULL; + if (items->len) { + result = g_ptr_array_index (items, 0); + g_ptr_array_remove_index_fast (items, 0); + } + mono_os_mutex_unlock (&queue_mutex); + return result; +} + +// Adds a new item to the end of the queue and returns the new size of the queue +EMSCRIPTEN_KEEPALIVE int +mono_jiterp_tlqueue_add (int queue, gpointer item) { + int result; + GPtrArray *items = get_queue (queue); + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + g_ptr_array_add (items, item); + result = items->len; + mono_os_mutex_unlock (&queue_mutex); + return result; +} + +EMSCRIPTEN_KEEPALIVE void +mono_jiterp_tlqueue_clear (int queue) { + GPtrArray *items = get_queue (queue); + mono_os_mutex_lock (&queue_mutex); + // WARNING: Ensure we do not call into the runtime or JS while holding this mutex! + g_ptr_array_set_size (items, 0); + mono_os_mutex_unlock (&queue_mutex); +} + // HACK: fix C4206 EMSCRIPTEN_KEEPALIVE #endif // HOST_BROWSER diff --git a/src/mono/mono/mini/interp/jiterpreter.h b/src/mono/mono/mini/interp/jiterpreter.h index 41001c53027dfe..643f2c05478289 100644 --- a/src/mono/mono/mini/interp/jiterpreter.h +++ b/src/mono/mono/mini/interp/jiterpreter.h @@ -172,6 +172,11 @@ mono_jiterp_do_jit_call_indirect ( #ifdef __MONO_MINI_INTERPRETER_INTERNALS_H__ +extern void +mono_jiterp_free_method_data_js ( + MonoMethod *method, InterpMethod *imethod, int trace_index +); + typedef struct { InterpMethod *rmethod; ThreadContext *context; @@ -231,9 +236,15 @@ mono_jiterp_placeholder_trace (void *frame, void *pLocals, JiterpreterCallInfo * void mono_jiterp_placeholder_jit_call (void *ret_sp, void *sp, void *ftndesc, gboolean *thrown); +void +mono_jiterp_free_method_data (MonoMethod *method, InterpMethod *imethod); + void * mono_jiterp_get_interp_entry_func (int table); +void +mono_jiterp_tlqueue_purge_all (gpointer item); + #endif // __MONO_MINI_INTERPRETER_INTERNALS_H__ extern WasmDoJitCall jiterpreter_do_jit_call; diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index f905f452cc5060..f8c283eae52cf1 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -144,6 +144,9 @@ const fn_signatures: SigLine[] = [ [true, "mono_jiterp_get_interp_entry_func", "number", ["number"]], [true, "mono_jiterp_get_counter", "number", ["number"]], [true, "mono_jiterp_modify_counter", "number", ["number", "number"]], + [true, "mono_jiterp_tlqueue_next", "number", ["number"]], + [true, "mono_jiterp_tlqueue_add", "number", ["number", "number"]], + [true, "mono_jiterp_tlqueue_clear", "void", ["number"]], ...diagnostics_cwraps, ...legacy_interop_cwraps @@ -283,6 +286,11 @@ export interface t_Cwraps { mono_jiterp_get_interp_entry_func(type: number): number; mono_jiterp_get_counter(counter: number): number; mono_jiterp_modify_counter(counter: number, delta: number): number; + // returns value or, if queue is empty, VoidPtrNull + mono_jiterp_tlqueue_next(queue: number): VoidPtr; + // returns new size of queue after add + mono_jiterp_tlqueue_add(queue: number, value: VoidPtr): number; + mono_jiterp_tlqueue_clear(queue: number): void; } const wrapped_c_functions: t_Cwraps = {}; diff --git a/src/mono/wasm/runtime/exports-binding.ts b/src/mono/wasm/runtime/exports-binding.ts index 834c9b4588b8de..e3143c04f973be 100644 --- a/src/mono/wasm/runtime/exports-binding.ts +++ b/src/mono/wasm/runtime/exports-binding.ts @@ -8,7 +8,7 @@ import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_s import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_invoke_import } from "./invoke-js"; -import { mono_interp_tier_prepare_jiterpreter } from "./jiterpreter"; +import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } from "./jiterpreter"; import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry"; import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue, mono_jiterp_do_jit_call_indirect } from "./jiterpreter-jit-call"; import { mono_wasm_marshal_promise } from "./marshal-to-js"; @@ -89,6 +89,7 @@ export const mono_wasm_imports = [ mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue, mono_jiterp_do_jit_call_indirect, + mono_jiterp_free_method_data_js, mono_wasm_profiler_enter, mono_wasm_profiler_leave, @@ -163,4 +164,4 @@ export function replace_linker_placeholders(imports: WebAssembly.Imports) { } } -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/jiterpreter-enums.ts b/src/mono/wasm/runtime/jiterpreter-enums.ts index ee219f635cfdbd..1c98615cc57d27 100644 --- a/src/mono/wasm/runtime/jiterpreter-enums.ts +++ b/src/mono/wasm/runtime/jiterpreter-enums.ts @@ -157,3 +157,8 @@ export const BailoutReasonNames = [ "UnexpectedRetIp", "LeaveCheck", ]; + +export const enum JitQueue { + JitCall = 0, + InterpEntry = 1 +} diff --git a/src/mono/wasm/runtime/jiterpreter-interp-entry.ts b/src/mono/wasm/runtime/jiterpreter-interp-entry.ts index 29ea23aa65eb8f..cedf54cdde9306 100644 --- a/src/mono/wasm/runtime/jiterpreter-interp-entry.ts +++ b/src/mono/wasm/runtime/jiterpreter-interp-entry.ts @@ -14,13 +14,13 @@ import { _now, getRawCwrap, importDef, getWasmFunctionTable, recordFailure, getOptions, JiterpreterOptions, getMemberOffset, - getCounter, modifyCounter, + getCounter, modifyCounter } from "./jiterpreter-support"; import { WasmValtype } from "./jiterpreter-opcodes"; import { mono_log_error, mono_log_info } from "./logging"; import { utf8ToString } from "./strings"; import { - JiterpreterTable, JiterpCounter, JiterpMember + JiterpreterTable, JiterpCounter, JiterpMember, JitQueue } from "./jiterpreter-enums"; // Controls miscellaneous diagnostic output. @@ -59,7 +59,6 @@ let trampBuilder: WasmBuilder; let trampImports: Array<[string, string, Function]> | undefined; let fnTable: WebAssembly.Table; let jitQueueTimeout = 0; -const jitQueue: TrampolineInfo[] = []; const infoTable: { [ptr: number]: TrampolineInfo } = {}; /* @@ -135,6 +134,13 @@ class TrampolineInfo { let mostRecentOptions: JiterpreterOptions | undefined = undefined; +// If a method is freed we need to remove its info (just in case another one gets +// allocated at that exact memory offset later) and more importantly, ensure it is +// not waiting in the jit queue +export function mono_jiterp_free_method_data_interp_entry(imethod: number) { + delete infoTable[imethod]; +} + // FIXME: move this counter into C and make it thread safe export function mono_interp_record_interp_entry(imethod: number) { // clear the unbox bit @@ -154,8 +160,8 @@ export function mono_interp_record_interp_entry(imethod: number) { else if (info.hitCount !== mostRecentOptions!.interpEntryHitCount) return; - jitQueue.push(info); - if (jitQueue.length >= maxJitQueueLength) + const jitQueueLength = cwraps.mono_jiterp_tlqueue_add(JitQueue.InterpEntry, imethod); + if (jitQueueLength >= maxJitQueueLength) flush_wasm_entry_trampoline_jit_queue(); else ensure_jit_is_scheduled(); @@ -222,7 +228,18 @@ function ensure_jit_is_scheduled() { } function flush_wasm_entry_trampoline_jit_queue() { - if (jitQueue.length <= 0) + const jitQueue : TrampolineInfo[] = []; + let methodPtr = 0; + while ((methodPtr = cwraps.mono_jiterp_tlqueue_next(JitQueue.InterpEntry)) != 0) { + const info = infoTable[methodPtr]; + if (!info) { + mono_log_info(`Failed to find corresponding info for method ptr ${methodPtr} from jit queue!`); + continue; + } + jitQueue.push(info); + } + + if (!jitQueue.length) return; // If the function signature contains types that need stackval_from_data, that'll use @@ -268,7 +285,6 @@ function flush_wasm_entry_trampoline_jit_queue() { builder.clear(constantSlots); if (builder.options.wasmBytesLimit <= getCounter(JiterpCounter.BytesGenerated)) { - jitQueue.length = 0; return; } @@ -428,8 +444,6 @@ function flush_wasm_entry_trampoline_jit_queue() { } else if (rejected && !threw) { mono_log_error("failed to generate trampoline for unknown reason"); } - - jitQueue.length = 0; } } diff --git a/src/mono/wasm/runtime/jiterpreter-jit-call.ts b/src/mono/wasm/runtime/jiterpreter-jit-call.ts index 2f24301b590a7d..1af011764e7d8e 100644 --- a/src/mono/wasm/runtime/jiterpreter-jit-call.ts +++ b/src/mono/wasm/runtime/jiterpreter-jit-call.ts @@ -16,7 +16,7 @@ import { getCounter, modifyCounter, jiterpreter_allocate_tables } from "./jiterpreter-support"; -import { JiterpreterTable, JiterpCounter } from "./jiterpreter-enums"; +import { JiterpreterTable, JiterpCounter, JitQueue } from "./jiterpreter-enums"; import { compileDoJitCall } from "./jiterpreter-feature-detect"; @@ -69,7 +69,7 @@ let wasmEhSupported: boolean | undefined = undefined; let nextDisambiguateIndex = 0; const fnCache: Array = []; const targetCache: { [target: number]: TrampolineInfo } = {}; -const jitQueue: TrampolineInfo[] = []; +const infosByMethod: { [method: number]: TrampolineInfo[] } = {}; class TrampolineInfo { method: MonoMethod; @@ -195,6 +195,21 @@ export function mono_interp_invoke_wasm_jit_call_trampoline( } } +// If a method is freed we need to remove its info (just in case another one gets +// allocated at that exact memory offset later) and more importantly, ensure it is +// not waiting in the jit queue +export function mono_jiterp_free_method_data_jit_call(method: MonoMethod) { + // FIXME + const infoArray = infosByMethod[method]; + if (!infoArray) + return; + + for (let i = 0; i < infoArray.length; i++) + delete targetCache[infoArray[i].addr]; + + delete infosByMethod[method]; +} + export function mono_interp_jit_wasm_jit_call_trampoline( method: MonoMethod, rmethod: VoidPtr, cinfo: VoidPtr, arg_offsets: VoidPtr, catch_exceptions: number @@ -227,12 +242,17 @@ export function mono_interp_jit_wasm_jit_call_trampoline( arg_offsets, catch_exceptions !== 0 ); targetCache[cacheKey] = info; - jitQueue.push(info); + const jitQueueLength = cwraps.mono_jiterp_tlqueue_add(JitQueue.JitCall, method); + + let ibm = infosByMethod[method]; + if (!ibm) + ibm = infosByMethod[method] = []; + ibm.push(info); // we don't want the queue to get too long, both because jitting too many trampolines // at once can hit the 4kb limit and because it makes it more likely that we will // fail to jit them early enough - if (jitQueue.length >= maxJitQueueLength) + if (jitQueueLength >= maxJitQueueLength) mono_interp_flush_jitcall_queue(); } @@ -328,7 +348,21 @@ export function mono_jiterp_do_jit_call_indirect( } export function mono_interp_flush_jitcall_queue(): void { - if (jitQueue.length === 0) + const jitQueue : TrampolineInfo[] = []; + let methodPtr = 0; + while ((methodPtr = cwraps.mono_jiterp_tlqueue_next(JitQueue.JitCall)) != 0) { + const infos = infosByMethod[methodPtr]; + if (!infos) { + mono_log_info(`Failed to find corresponding info list for method ptr ${methodPtr} from jit queue!`); + continue; + } + + for (let i = 0; i < infos.length; i++) + if (infos[i].result === 0) + jitQueue.push(infos[i]); + } + + if (!jitQueue.length) return; let builder = trampBuilder; @@ -348,7 +382,7 @@ export function mono_interp_flush_jitcall_queue(): void { builder.clear(0); if (builder.options.wasmBytesLimit <= getCounter(JiterpCounter.BytesGenerated)) { - jitQueue.length = 0; + cwraps.mono_jiterp_tlqueue_clear(JitQueue.JitCall); return; } @@ -377,7 +411,6 @@ export function mono_interp_flush_jitcall_queue(): void { for (let i = 0; i < jitQueue.length; i++) { const info = jitQueue[i]; - const sig: any = {}; if (info.enableDirect) { @@ -552,8 +585,6 @@ export function mono_interp_flush_jitcall_queue(): void { } else if (rejected && !threw) { mono_log_error("failed to generate trampoline for unknown reason"); } - - jitQueue.length = 0; } } diff --git a/src/mono/wasm/runtime/jiterpreter-support.ts b/src/mono/wasm/runtime/jiterpreter-support.ts index 6115f6558858b3..fb62d3f8bdc2bf 100644 --- a/src/mono/wasm/runtime/jiterpreter-support.ts +++ b/src/mono/wasm/runtime/jiterpreter-support.ts @@ -95,6 +95,7 @@ export class WasmBuilder { argumentCount!: number; activeBlocks!: number; base!: MintOpcodePtr; + traceIndex!: number; frame: NativePointer = 0; traceBuf: Array = []; branchTargets = new Set(); @@ -1462,7 +1463,7 @@ export function append_safepoint(builder: WasmBuilder, ip: MintOpcodePtr) { export function append_bailout(builder: WasmBuilder, ip: MintOpcodePtr, reason: BailoutReason) { builder.ip_const(ip); if (builder.options.countBailouts) { - builder.i32_const(builder.base); + builder.i32_const(builder.traceIndex); builder.i32_const(reason); builder.callImport("bailout"); } @@ -1487,7 +1488,7 @@ export function append_exit(builder: WasmBuilder, ip: MintOpcodePtr, opcodeCount builder.ip_const(ip); if (builder.options.countBailouts) { - builder.i32_const(builder.base); + builder.i32_const(builder.traceIndex); builder.i32_const(reason); builder.callImport("bailout"); } diff --git a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts index e2c9acf2948b23..2c7bb17ef9e82c 100644 --- a/src/mono/wasm/runtime/jiterpreter-trace-generator.ts +++ b/src/mono/wasm/runtime/jiterpreter-trace-generator.ts @@ -142,8 +142,6 @@ export function generateWasmBody( let result = 0, prologueOpcodeCounter = 0, conditionalOpcodeCounter = 0; - const traceIp = ip; - eraseInferredState(); // Skip over the enter opcode @@ -161,7 +159,7 @@ export function generateWasmBody( builder.cfg.ip = ip; if (ip >= endOfBody) { - record_abort(traceIp, ip, traceName, "end-of-body"); + record_abort(builder.traceIndex, ip, traceName, "end-of-body"); if (instrumentedTraceId) mono_log_info(`instrumented trace ${traceName} exited at end of body @${(ip).toString(16)}`); break; @@ -174,7 +172,7 @@ export function generateWasmBody( spaceLeft = maxBytesGenerated - builder.bytesGeneratedSoFar - builder.cfg.overheadBytes; if (builder.size >= spaceLeft) { // mono_log_info(`trace too big, estimated size is ${builder.size + builder.bytesGeneratedSoFar}`); - record_abort(traceIp, ip, traceName, "trace-too-big"); + record_abort(builder.traceIndex, ip, traceName, "trace-too-big"); if (instrumentedTraceId) mono_log_info(`instrumented trace ${traceName} exited because of size limit at @${(ip).toString(16)} (spaceLeft=${spaceLeft}b)`); break; @@ -795,7 +793,7 @@ export function generateWasmBody( bailoutOnFailure = (opcode === MintOpcode.MINT_CASTCLASS_INTERFACE), destOffset = getArgU16(ip, 1); if (!klass) { - record_abort(traceIp, ip, traceName, "null-klass"); + record_abort(builder.traceIndex, ip, traceName, "null-klass"); ip = abort; continue; } @@ -880,7 +878,7 @@ export function generateWasmBody( (opcode === MintOpcode.MINT_CASTCLASS_COMMON), destOffset = getArgU16(ip, 1); if (!klass) { - record_abort(traceIp, ip, traceName, "null-klass"); + record_abort(builder.traceIndex, ip, traceName, "null-klass"); ip = abort; continue; } @@ -1014,7 +1012,7 @@ export function generateWasmBody( elementClass = getU32_unaligned(klass + elementClassOffset); if (!klass || !elementClass) { - record_abort(traceIp, ip, traceName, "null-klass"); + record_abort(builder.traceIndex, ip, traceName, "null-klass"); ip = abort; continue; } @@ -1603,7 +1601,7 @@ export function generateWasmBody( } else { if (instrumentedTraceId) mono_log_info(`instrumented trace ${traceName} aborted for opcode ${opname} @${(_ip).toString(16)}`); - record_abort(traceIp, _ip, traceName, opcode); + record_abort(builder.traceIndex, _ip, traceName, opcode); } } @@ -1813,7 +1811,7 @@ function append_ldloc_cknull(builder: WasmBuilder, localOffset: number, ip: Mint if (nullCheckValidation) { builder.local("cknull_ptr"); append_ldloc(builder, localOffset, WasmOpcode.i32_load); - builder.i32_const(builder.base); + builder.i32_const(builder.traceIndex); builder.i32_const(ip); builder.callImport("notnull"); } @@ -2103,7 +2101,7 @@ function emit_fieldop( // cknull_ptr was not used here so all we can do is verify that the target object is not null append_ldloc(builder, objectOffset, WasmOpcode.i32_load); append_ldloc(builder, objectOffset, WasmOpcode.i32_load); - builder.i32_const(builder.base); + builder.i32_const(builder.traceIndex); builder.i32_const(ip); builder.callImport("notnull"); } diff --git a/src/mono/wasm/runtime/jiterpreter.ts b/src/mono/wasm/runtime/jiterpreter.ts index 3ec26451eb3e26..f47149dda1eb15 100644 --- a/src/mono/wasm/runtime/jiterpreter.ts +++ b/src/mono/wasm/runtime/jiterpreter.ts @@ -22,6 +22,8 @@ import { import { generateWasmBody } from "./jiterpreter-trace-generator"; +import { mono_jiterp_free_method_data_interp_entry } from "./jiterpreter-interp-entry"; +import { mono_jiterp_free_method_data_jit_call } from "./jiterpreter-jit-call"; import { mono_log_error, mono_log_info, mono_log_warn } from "./logging"; import { utf8ToString } from "./strings"; @@ -232,15 +234,15 @@ const mathOps1d = "powf", ]; -function recordBailout(ip: number, base: MintOpcodePtr, reason: BailoutReason) { +function recordBailout(ip: number, traceIndex: number, reason: BailoutReason) { cwraps.mono_jiterp_trace_bailout(reason); // Counting these is not meaningful and messes up the end of run statistics if (reason === BailoutReason.Return) return ip; - const info = traceInfo[base]; + const info = traceInfo[traceIndex]; if (!info) { - mono_log_error(`trace info not found for ${base}`); + mono_log_error(`trace info not found for ${traceIndex}`); return; } let table = info.bailoutCounts; @@ -702,11 +704,11 @@ function initialize_builder(builder: WasmBuilder) { } function assert_not_null( - value: number, expectedValue: number, traceIp: MintOpcodePtr, ip: MintOpcodePtr + value: number, expectedValue: number, traceIndex: number, ip: MintOpcodePtr ) { if (value && (value === expectedValue)) return; - const info = traceInfo[traceIp]; + const info = traceInfo[traceIndex]; throw new Error(`expected non-null value ${expectedValue} but found ${value} in trace ${info.name} @ 0x${(ip).toString(16)}`); } @@ -714,8 +716,8 @@ function assert_not_null( function generate_wasm( frame: NativePointer, methodName: string, ip: MintOpcodePtr, startOfBody: MintOpcodePtr, sizeOfBody: MintOpcodePtr, - methodFullName: string | undefined, backwardBranchTable: Uint16Array | null, - presetFunctionPointer: number + traceIndex: number, methodFullName: string | undefined, + backwardBranchTable: Uint16Array | null, presetFunctionPointer: number ): number { // Pre-allocate a decent number of constant slots - this adds fixed size bloat // to the trace but will make the actual pointer constants in the trace smaller @@ -750,7 +752,7 @@ function generate_wasm( let compileStarted = 0; let rejected = true, threw = false; - const ti = traceInfo[ip]; + const ti = traceInfo[traceIndex]; const instrument = ti.isVerbose || (methodFullName && ( instrumentedMethodNames.findIndex( (filter) => methodFullName.indexOf(filter) >= 0 @@ -809,6 +811,7 @@ function generate_wasm( } builder.base = ip; + builder.traceIndex = traceIndex; builder.frame = frame; switch (getU16(ip)) { case MintOpcode.MINT_TIER_PREPARE_JITERPRETER: @@ -971,7 +974,7 @@ export function trace_operands(a: number, b: number) { mostRecentTrace.operand2 = b >>> 0; } -export function record_abort(traceIp: MintOpcodePtr, ip: MintOpcodePtr, traceName: string, reason: string | MintOpcode) { +export function record_abort(traceIndex: number, ip: MintOpcodePtr, traceName: string, reason: string | MintOpcode) { if (typeof (reason) === "number") { cwraps.mono_jiterp_adjust_abort_count(reason, 1); reason = getOpcodeName(reason); @@ -986,9 +989,9 @@ export function record_abort(traceIp: MintOpcodePtr, ip: MintOpcodePtr, traceNam } if ((traceAbortLocations && (reason !== "end-of-body")) || (trace >= 2)) - mono_log_info(`abort ${traceIp} ${traceName}@${ip} ${reason}`); + mono_log_info(`abort #${traceIndex} ${traceName}@${ip} ${reason}`); - traceInfo[traceIp].abortReason = reason; + traceInfo[traceIndex].abortReason = reason; } const JITERPRETER_TRAINING = 0; @@ -1009,10 +1012,10 @@ export function mono_interp_tier_prepare_jiterpreter( else if (mostRecentOptions.wasmBytesLimit <= getCounter(JiterpCounter.BytesGenerated)) return JITERPRETER_NOT_JITTED; - let info = traceInfo[ip]; + let info = traceInfo[index]; if (!info) - traceInfo[ip] = info = new TraceInfo(ip, index, isVerbose); + traceInfo[index] = info = new TraceInfo(ip, index, isVerbose); modifyCounter(JiterpCounter.TraceCandidates, 1); let methodFullName: string | undefined; @@ -1055,8 +1058,8 @@ export function mono_interp_tier_prepare_jiterpreter( const fnPtr = generate_wasm( frame, methodName, ip, startOfBody, - sizeOfBody, methodFullName, backwardBranchTable, - presetFunctionPointer + sizeOfBody, index, methodFullName, + backwardBranchTable, presetFunctionPointer ); if (fnPtr) { @@ -1070,6 +1073,20 @@ export function mono_interp_tier_prepare_jiterpreter( } } +// NOTE: This will potentially be called once for every trace entry point +// in a given method, not just once per method +export function mono_jiterp_free_method_data_js( + method: MonoMethod, imethod: number, traceIndex: number +) { + // TODO: Uninstall the trace function pointer from the function pointer table, + // so that the compiled trace module can be freed by the browser eventually + // Release the trace info object, if present + delete traceInfo[traceIndex]; + // Remove any AOT data and queue entries associated with the method + mono_jiterp_free_method_data_interp_entry(imethod); + mono_jiterp_free_method_data_jit_call(method); +} + export function jiterpreter_dump_stats(b?: boolean, concise?: boolean) { if (!runtimeHelpers.runtimeReady) { return;