Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/mono/mono/mini/interp/interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
187 changes: 187 additions & 0 deletions src/mono/mono/mini/interp/jiterpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ void jiterp_preserve_module (void);
#include <stdlib.h>
#include <math.h>

#ifndef DISABLE_THREADS
#include <pthread.h>
#endif

#include <mono/metadata/mono-config.h>
#include <mono/utils/mono-threads.h>
#include <mono/metadata/gc-internals.h>
Expand All @@ -48,6 +52,7 @@ void jiterp_preserve_module (void);
#include <mono/mini/llvmonly-runtime.h>
#include <mono/utils/options.h>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-tls.h>

#include "jiterpreter.h"

Expand Down Expand Up @@ -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]++;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/mono/mono/mini/interp/jiterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = <any>{};
Expand Down
5 changes: 3 additions & 2 deletions src/mono/wasm/runtime/exports-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -163,4 +164,4 @@ export function replace_linker_placeholders(imports: WebAssembly.Imports) {
}
}

}
}
5 changes: 5 additions & 0 deletions src/mono/wasm/runtime/jiterpreter-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,8 @@ export const BailoutReasonNames = [
"UnexpectedRetIp",
"LeaveCheck",
];

export const enum JitQueue {
JitCall = 0,
InterpEntry = 1
}
Loading