dnl ***************************
dnl When adding experiments, also add to mono/utils/mono-experiments.def
-AC_ARG_ENABLE(experiment, [ --enable-experiment=LIST Enable experimental fatures from the comma-separate LIST. Available experiments: null],[
+AC_ARG_ENABLE(experiment, [ --enable-experiment=LIST Enable experimental fatures from the comma-separate LIST. Available experiments: null,tiered],[
if test x$enable_experiment != x ; then
AC_DEFINE(ENABLE_EXPERIMENTS,1,[Enable feature experiments])
if test "x$mono_experiment_test_enable_all" = "xyes"; then
eval "mono_experiment_test_enable_null='yes'"
+ eval "mono_experiment_test_enable_tiered='yes'"
true
fi
if test "x$mono_experiment_test_enable_null" = "xyes"; then
AC_DEFINE(ENABLE_EXPERIMENT_null, 1, [Enable experiment 'null'])
fi
+ if test "x$mono_experiment_test_enable_tiered" = "xyes"; then
+ AC_DEFINE(ENABLE_EXPERIMENT_TIERED, 1, [Enable experiment 'Tiered Compilation'])
+ fi
],[])
+AM_CONDITIONAL(ENABLE_EXPERIMENT_TIERED, test x$mono_experiment_test_enable_tiered = xyes)
+
dnl **********************
dnl *** checked builds ***
dnl **********************
llvmonly-runtime.c \
aot-runtime.h \
ee.h \
+ tiered.h \
+ tiered.c \
mini-runtime.h
endif # !ENABLE_MSVC_ONLY
MonoJitInfo *jinfo;
MonoDomain *domain;
MonoProfilerCallInstrumentationFlags prof_flags;
+#ifdef ENABLE_EXPERIMENT_TIERED
+ MiniTieredCounter tiered_counter;
+#endif
} InterpMethod;
struct _InterpFrame {
typedef struct {
gint64 transform_time;
+ gint64 methods_transformed;
gint64 cprop_time;
gint64 super_instructions_time;
gint32 stloc_nps;
* Call JITted code through a gsharedvt_out wrapper. These wrappers receive every argument
* by ref and return a return value using an explicit return value argument.
*/
- if (!rmethod->jit_wrapper) {
+ if (G_UNLIKELY (!rmethod->jit_wrapper)) {
MonoMethod *method = rmethod->method;
sig = mono_method_signature_internal (method);
sp++;
}
+#ifdef ENABLE_EXPERIMENT_TIERED
+ mini_tiered_inc (frame->imethod->domain, frame->imethod->method, &frame->imethod->tiered_counter, 0);
+#endif
//g_print ("(%p) Call %s\n", mono_thread_internal_current (), mono_method_get_full_name (frame->imethod->method));
/*
goto common_vcall;
}
MINT_IN_CASE(MINT_CALL)
- sp = mono_interp_call (frame, context, &child_frame, (ip += 2) - 2, sp, vt_sp, FALSE);
+ sp = mono_interp_call (frame, context, &child_frame, ip, sp, vt_sp, FALSE);
+#ifdef ENABLE_EXPERIMENT_TIERED
+ ip += 5;
+#else
+ ip += 2;
+#endif
common_call:
child_frame.stack_args = sp;
interp_exec_method (&child_frame, context, error);
MINT_IN_BREAK;
MINT_IN_CASE(MINT_VCALL)
- sp = mono_interp_call (frame, context, &child_frame, (ip += 2) - 2, sp, vt_sp, FALSE);
+ sp = mono_interp_call (frame, context, &child_frame, ip, sp, vt_sp, FALSE);
+#ifdef ENABLE_EXPERIMENT_TIERED
+ ip += 5;
+#else
+ ip += 2;
+#endif
common_vcall:
child_frame.stack_args = sp;
interp_exec_method (&child_frame, context, error);
MINT_IN_BREAK;
}
+ MINT_IN_CASE(MINT_JIT_CALL2) {
+#ifdef ENABLE_EXPERIMENT_TIERED
+ InterpMethod *rmethod = (InterpMethod *) READ64 (ip + 1);
+
+ MONO_API_ERROR_INIT (error);
+ frame->ip = ip;
+
+ sp = do_jit_call (sp, vt_sp, context, frame, rmethod, error);
+ if (!is_ok (error)) {
+ MonoException *ex = mono_error_convert_to_exception (error);
+ THROW_EX (ex, ip);
+ }
+ ip += 5;
+
+ CHECK_RESUME_STATE (context);
+
+ if (rmethod->rtype->type != MONO_TYPE_VOID)
+ sp++;
+#else
+ g_error ("MINT_JIT_ICALL2 shouldn't be used");
+#endif
+ MINT_IN_BREAK;
+ }
MINT_IN_CASE(MINT_CALLRUN) {
#ifndef ENABLE_NETCORE
MonoMethod *target_method = (MonoMethod*) frame->imethod->data_items [ip [1]];
g_warning_d ("ret.vt: more values on stack: %d", sp - frame->stack);
goto exit_frame;
}
- MINT_IN_CASE(MINT_BR_S)
- ip += (short) *(ip + 1);
+
+#ifdef ENABLE_EXPERIMENT_TIERED
+#define BACK_BRANCH_PROFILE(offset) do { \
+ if (offset < 0) \
+ mini_tiered_inc (frame->imethod->domain, frame->imethod->method, &frame->imethod->tiered_counter, 0); \
+ } while (0);
+#else
+#define BACK_BRANCH_PROFILE(offset)
+#endif
+
+ MINT_IN_CASE(MINT_BR_S) {
+ short br_offset = (short) *(ip + 1);
+ BACK_BRANCH_PROFILE (br_offset);
+ ip += br_offset;
MINT_IN_BREAK;
- MINT_IN_CASE(MINT_BR)
- ip += (gint32) READ32(ip + 1);
+ }
+ MINT_IN_CASE(MINT_BR) {
+ gint32 br_offset = (gint32) READ32(ip + 1);
+ BACK_BRANCH_PROFILE (br_offset);
+ ip += br_offset;
MINT_IN_BREAK;
+ }
+
#define ZEROP_S(datamem, op) \
--sp; \
- if (sp->data.datamem op 0) \
- ip += (gint16)ip [1]; \
- else \
+ if (sp->data.datamem op 0) { \
+ gint16 br_offset = (gint16) ip [1]; \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 2;
#define ZEROP(datamem, op) \
--sp; \
- if (sp->data.datamem op 0) \
- ip += (gint32)READ32(ip + 1); \
- else \
+ if (sp->data.datamem op 0) { \
+ gint32 br_offset = (gint32)READ32(ip + 1); \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 3;
MINT_IN_CASE(MINT_BRFALSE_I4_S)
MINT_IN_BREAK;
#define CONDBR_S(cond) \
sp -= 2; \
- if (cond) \
- ip += (gint16)ip [1]; \
- else \
+ if (cond) { \
+ gint16 br_offset = (gint16) ip [1]; \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 2;
#define BRELOP_S(datamem, op) \
CONDBR_S(sp[0].data.datamem op sp[1].data.datamem)
#define CONDBR(cond) \
sp -= 2; \
- if (cond) \
- ip += (gint32)READ32(ip + 1); \
- else \
+ if (cond) { \
+ gint32 br_offset = (gint32) READ32 (ip + 1); \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 3;
#define BRELOP(datamem, op) \
#define BRELOP_S_CAST(datamem, op, type) \
sp -= 2; \
- if ((type) sp[0].data.datamem op (type) sp[1].data.datamem) \
- ip += (gint16)ip [1]; \
- else \
+ if ((type) sp[0].data.datamem op (type) sp[1].data.datamem) { \
+ gint16 br_offset = (gint16) ip [1]; \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 2;
#define BRELOP_CAST(datamem, op, type) \
sp -= 2; \
- if ((type) sp[0].data.datamem op (type) sp[1].data.datamem) \
- ip += (gint32)READ32(ip + 1); \
- else \
+ if ((type) sp[0].data.datamem op (type) sp[1].data.datamem) { \
+ gint32 br_offset = (gint32) ip [1]; \
+ BACK_BRANCH_PROFILE (br_offset); \
+ ip += br_offset; \
+ } else \
ip += 3;
MINT_IN_CASE(MINT_BGE_UN_I4_S)
prof_ctx->interp_frame = frame;
prof_ctx->method = frame->imethod->method;
- mono_trace_enter_method (frame->imethod->method, prof_ctx);
+ mono_trace_enter_method (frame->imethod->method, frame->imethod->jinfo, prof_ctx);
MINT_IN_BREAK;
}
prof_ctx->interp_frame = frame;
prof_ctx->method = frame->imethod->method;
- mono_trace_leave_method (frame->imethod->method, prof_ctx);
+ mono_trace_leave_method (frame->imethod->method, frame->imethod->jinfo, prof_ctx);
ip += 3;
goto exit_frame;
}
{
mono_counters_init ();
mono_counters_register ("Total transform time", MONO_COUNTER_INTERP | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_interp_stats.transform_time);
+ mono_counters_register ("Methods transformed", MONO_COUNTER_INTERP | MONO_COUNTER_LONG, &mono_interp_stats.methods_transformed);
mono_counters_register ("Total cprop time", MONO_COUNTER_INTERP | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_interp_stats.cprop_time);
mono_counters_register ("Total super instructions time", MONO_COUNTER_INTERP | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_interp_stats.super_instructions_time);
mono_counters_register ("STLOC_NP count", MONO_COUNTER_INTERP | MONO_COUNTER_INT, &mono_interp_stats.stloc_nps);
OPDEF(MINT_ICALL_PPPPPP_P, "mono_icall_pppppp_p", 2, Pop6, Push1, MintOpClassToken)
// FIXME: MintOp
OPDEF(MINT_JIT_CALL, "mono_jit_call", 2, VarPop, VarPush, MintOpNoArgs)
+OPDEF(MINT_JIT_CALL2, "mono_jit_call2", 5, VarPop, VarPush, MintOpNoArgs)
OPDEF(MINT_MONO_LDPTR, "mono_ldptr", 2, Pop0, Push1, MintOpClassToken)
OPDEF(MINT_MONO_SGEN_THREAD_INFO, "mono_sgen_thread_info", 1, Pop0, Push1, MintOpNoArgs)
#define MINT_IS_STLOC_NP(op) ((op) >= MINT_STLOC_NP_I4 && (op) <= MINT_STLOC_NP_O)
#define MINT_IS_CONDITIONAL_BRANCH(op) ((op) >= MINT_BRFALSE_I4 && (op) <= MINT_BLT_UN_R8_S)
#define MINT_IS_CALL(op) ((op) >= MINT_CALL && (op) <= MINT_JIT_CALL)
+#define MINT_IS_PATCHABLE_CALL(op) ((op) >= MINT_CALL && (op) <= MINT_VCALL)
#define MINT_IS_NEWOBJ(op) ((op) >= MINT_NEWOBJ && (op) <= MINT_NEWOBJ_MAGIC)
#define MINT_IS_LDC_I4(op) ((op) >= MINT_LDC_I4_M1 && (op) <= MINT_LDC_I4)
#define MINT_IS_UNOP(op) ((op) >= MINT_ADD1_I4 && (op) <= MINT_CEQ0_I4)
#define INTERP_INST_FLAG_SEQ_POINT_METHOD_ENTRY 2
#define INTERP_INST_FLAG_SEQ_POINT_METHOD_EXIT 4
#define INTERP_INST_FLAG_SEQ_POINT_NESTED_CALL 8
+#define INTERP_INST_FLAG_RECORD_CALL_PATCH 16
#define INTERP_LOCAL_FLAG_INDIRECT 1
#define INTERP_LOCAL_FLAG_DEAD 2
int max_data_items;
void **data_items;
GHashTable *data_hash;
+#ifdef ENABLE_EXPERIMENT_TIERED
+ GHashTable *patchsite_hash;
+#endif
int *clause_indexes;
gboolean gen_sdb_seq_points;
GPtrArray *seq_points;
(ins)->data [index + 1] = * ((guint16 *)(v) + 1); \
} while (0)
+#define WRITE64(ins, v) \
+ do { \
+ *((ins) + 0) = * ((guint16 *)(v) + 0); \
+ *((ins) + 1) = * ((guint16 *)(v) + 1); \
+ *((ins) + 2) = * ((guint16 *)(v) + 2); \
+ *((ins) + 3) = * ((guint16 *)(v) + 3); \
+ } while (0)
+
#define WRITE64_INS(ins, index, v) \
do { \
(ins)->data [index] = * (guint16 *)(v); \
* (guint32 *)(&(ins)->data [index]) = * (guint32 *)(v); \
} while (0)
+#define WRITE64(ip, v) \
+ do { \
+ * (guint64*)(ip) = * (guint64 *)(v); \
+ (ip) += 4; \
+ } while (0)
#define WRITE64_INS(ins, index, v) \
do { \
* (guint64 *)(&(ins)->data [index]) = * (guint64 *)(v); \
return FALSE;
}
+#ifdef ENABLE_EXPERIMENT_TIERED
+static gboolean
+jit_call2_supported (MonoMethod *method, MonoMethodSignature *sig)
+{
+ if (sig->param_count > 6)
+ return FALSE;
+ if (sig->pinvoke)
+ return FALSE;
+ if (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL)
+ return FALSE;
+ if (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL)
+ return FALSE;
+ if (method->is_inflated)
+ return FALSE;
+ if (method->string_ctor)
+ return FALSE;
+
+ return TRUE;
+}
+#endif
+
static int mono_class_get_magic_index (MonoClass *k)
{
if (mono_class_is_magic_int (k))
}
} else {
td->last_ins->data [0] = get_data_item_index (td, (void *)mono_interp_get_imethod (domain, target_method, error));
+#ifdef ENABLE_EXPERIMENT_TIERED
+ if (MINT_IS_PATCHABLE_CALL (td->last_ins->opcode)) {
+ g_assert (!calli && !is_virtual);
+ td->last_ins->flags |= INTERP_INST_FLAG_RECORD_CALL_PATCH;
+ g_hash_table_insert (td->patchsite_hash, td->last_ins, target_method);
+ }
+#endif
return_val_if_nok (error, FALSE);
if (csignature->call_convention == MONO_CALL_VARARG)
td->last_ins->data [1] = get_data_item_index (td, (void *)csignature);
if (sym_seq_points) {
last_seq_point = interp_add_ins (td, MINT_SDB_SEQ_POINT);
- last_seq_point->flags = INTERP_INST_FLAG_SEQ_POINT_METHOD_ENTRY;
+ last_seq_point->flags |= INTERP_INST_FLAG_SEQ_POINT_METHOD_ENTRY;
}
if (mono_debugger_method_has_breakpoint (method))
if (sym_seq_points) {
last_seq_point = interp_add_ins (td, MINT_SDB_SEQ_POINT);
- td->last_ins->flags = INTERP_INST_FLAG_SEQ_POINT_METHOD_EXIT;
+ td->last_ins->flags |= INTERP_INST_FLAG_SEQ_POINT_METHOD_EXIT;
}
if (mono_jit_trace_calls != NULL && mono_trace_eval (method)) {
{
if (ins->opcode == MINT_SWITCH)
return MINT_SWITCH_LEN (READ32 (&ins->data [0]));
+#ifdef ENABLE_EXPERIMENT_TIERED
+ else if (MINT_IS_PATCHABLE_CALL (ins->opcode))
+ return MAX (mono_interp_oplen [MINT_JIT_CALL2], mono_interp_oplen [ins->opcode]);
+#endif
else
return mono_interp_oplen [ins->opcode];
}
cbb->seq_points = g_slist_prepend_mempool (td->mempool, cbb->seq_points, seqp);
cbb->last_seq_point = seqp;
+#ifdef ENABLE_EXPERIMENT_TIERED
+ } else if (ins->flags & INTERP_INST_FLAG_RECORD_CALL_PATCH) {
+ g_assert (MINT_IS_PATCHABLE_CALL (opcode));
+
+ /* TODO: could `ins` be removed by any interp optimization? */
+ MonoMethod *target_method = (MonoMethod *) g_hash_table_lookup (td->patchsite_hash, ins);
+ g_assert (target_method);
+ g_hash_table_remove (td->patchsite_hash, ins);
+
+ mini_tiered_record_callsite (start_ip, target_method, TIERED_PATCH_KIND_INTERP);
+
+ int size = mono_interp_oplen [ins->opcode];
+ int jit_call2_size = mono_interp_oplen [MINT_JIT_CALL2];
+
+ g_assert (size < jit_call2_size);
+
+ // Emit the rest of the data
+ for (int i = 0; i < size - 1; i++)
+ *ip++ = ins->data [i];
+
+ /* intentional padding so we can patch a MINT_JIT_CALL2 here */
+ for (int i = size - 1; i < (jit_call2_size - 1); i++)
+ *ip++ = MINT_NIY;
+#endif
} else {
if (MINT_IS_LDLOC (opcode) || MINT_IS_STLOC (opcode) || MINT_IS_STLOC_NP (opcode) || opcode == MINT_LDLOCA_S ||
MINT_IS_LDLOCFLD (opcode) || MINT_IS_LOCUNOP (opcode) || MINT_IS_STLOCFLD (opcode)) {
td->max_data_items = 0;
td->data_items = NULL;
td->data_hash = g_hash_table_new (NULL, NULL);
+#ifdef ENABLE_EXPERIMENT_TIERED
+ td->patchsite_hash = g_hash_table_new (NULL, NULL);
+#endif
td->gen_sdb_seq_points = mini_debug_options.gen_sdb_seq_points;
td->seq_points = g_ptr_array_new ();
td->verbose_level = mono_interp_traceopt;
}
save_seq_points (td, jinfo);
+#ifdef ENABLE_EXPERIMENT_TIERED
+ /* debugging aid, it makes `mono_pmip` work. */
+ mono_jit_info_table_add (domain, jinfo);
+#endif
exit:
g_free (td->in_offsets);
g_free (td->is_bb_start);
g_free (td->locals);
g_hash_table_destroy (td->data_hash);
+#ifdef ENABLE_EXPERIMENT_TIERED
+ g_hash_table_destroy (td->patchsite_hash);
+#endif
g_ptr_array_free (td->seq_points, TRUE);
g_array_free (td->line_numbers, TRUE);
mono_mempool_destroy (td->mempool);
static mono_mutex_t calc_section;
+#ifdef ENABLE_EXPERIMENT_TIERED
+static gboolean
+tiered_patcher (MiniTieredPatchPointContext *ctx, gpointer patchsite)
+{
+ ERROR_DECL (error);
+ MonoMethod *m = ctx->target_method;
+
+ if (!jit_call2_supported (m, mono_method_signature_internal (m)))
+ return FALSE;
+
+ /* TODO: Force compilation here. Currently the JIT will be invoked upon
+ * first execution of `MINT_JIT_CALL2`. */
+ InterpMethod *rmethod = mono_interp_get_imethod (ctx->domain, m, error);
+ mono_error_assert_ok (error);
+
+ guint16 *ip = ((guint16 *) patchsite);
+ *ip++ = MINT_JIT_CALL2;
+ /* FIXME: this only works on 64bit */
+ WRITE64 (ip, &rmethod);
+ mono_memory_barrier ();
+
+ return TRUE;
+}
+#endif
+
+
void
mono_interp_transform_init (void)
{
mono_os_mutex_init_recursive(&calc_section);
+
+#ifdef ENABLE_EXPERIMENT_TIERED
+ mini_tiered_register_callsite_patcher (tiered_patcher, TIERED_PATCH_KIND_INTERP);
+#endif
}
void
imethod->alloca_size = imethod->stack_size;
mono_memory_barrier ();
imethod->transformed = TRUE;
+ mono_interp_stats.methods_transformed++;
mono_os_mutex_unlock (&calc_section);
MONO_PROFILER_RAISE (jit_done, (method, NULL));
return;
memcpy ((char*)imethod + start_offset, (char*)&tmp_imethod + start_offset, sizeof (InterpMethod) - start_offset);
mono_memory_barrier ();
imethod->transformed = TRUE;
+ mono_interp_stats.methods_transformed++;
mono_atomic_fetch_add_i32 (&mono_jit_stats.methods_with_interp, 1);
}
if (cfg->current_method != cfg->method)
return;
- MonoInst *iargs [2];
+ MonoInst *iargs [3];
EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method);
+ EMIT_NEW_PCONST (cfg, iargs [1], NULL);
if (MONO_CFG_PROFILE (cfg, ENTER_CONTEXT))
- iargs [1] = emit_fill_call_ctx (cfg, iargs [0], NULL);
+ iargs [2] = emit_fill_call_ctx (cfg, iargs [0], NULL);
else
- EMIT_NEW_PCONST (cfg, iargs [1], NULL);
+ EMIT_NEW_PCONST (cfg, iargs [2], NULL);
- /* void mono_profiler_raise_method_enter (MonoMethod *method, MonoProfilerCallContext *ctx) */
+ /* void mono_profiler_raise_method_enter (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) */
if (trace)
mono_emit_jit_icall (cfg, mono_trace_enter_method, iargs);
else
if (!MONO_CFG_PROFILE (cfg, LEAVE) || cfg->current_method != cfg->method || (cfg->compile_aot && !can_encode_method_ref (cfg->method)))
return;
- MonoInst *iargs [2];
+ MonoInst *iargs [3];
EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method);
+ EMIT_NEW_PCONST (cfg, iargs [1], NULL);
if (MONO_CFG_PROFILE (cfg, LEAVE_CONTEXT))
- iargs [1] = emit_fill_call_ctx (cfg, iargs [0], ret);
+ iargs [2] = emit_fill_call_ctx (cfg, iargs [0], ret);
else
- EMIT_NEW_PCONST (cfg, iargs [1], NULL);
+ EMIT_NEW_PCONST (cfg, iargs [2], NULL);
- /* void mono_profiler_raise_method_leave (MonoMethod *method, MonoProfilerCallContext *ctx) */
+ /* void mono_profiler_raise_method_leave (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) */
if (trace)
mono_emit_jit_icall (cfg, mono_trace_leave_method, iargs);
else
g_assert (cfg->current_method == cfg->method);
- MonoInst *iargs [2];
+ MonoInst *iargs [3];
EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method);
+ EMIT_NEW_PCONST (cfg, iargs [1], NULL);
if (target)
- EMIT_NEW_METHODCONST (cfg, iargs [1], target);
+ EMIT_NEW_METHODCONST (cfg, iargs [2], target);
else
- EMIT_NEW_PCONST (cfg, iargs [1], NULL);
+ EMIT_NEW_PCONST (cfg, iargs [2], NULL);
/* void mono_profiler_raise_method_tail_call (MonoMethod *method, MonoMethod *target) */
if (trace)
if (location)
file_loc = g_strdup_printf ("[%s :: %du]", location->source_file, location->row);
- res = g_strdup_printf (" %s [{%p} + 0x%x] %s (%p %p) [%p - %s]", method_name, method, (int)((char*)ip - (char*)ji->code_start), file_loc ? file_loc : "", ji->code_start, (char*)ji->code_start + ji->code_size, domain, domain->friendly_name);
+ const char *in_interp = ji->is_interp ? " interp" : "";
+
+ res = g_strdup_printf (" %s [{%p} + 0x%x%s] %s (%p %p) [%p - %s]", method_name, method, (int)((char*)ip - (char*)ji->code_start), in_interp, file_loc ? file_loc : "", ji->code_start, (char*)ji->code_start + ji->code_size, domain, domain->friendly_name);
mono_debug_free_source_location (location);
g_free (method_name);
MONO_PROFILER_RAISE (thread_name, (MONO_NATIVE_THREAD_ID_TO_UINT (mono_native_thread_id_get ()), "Main"));
#endif
+#ifdef ENABLE_EXPERIMENT_TIERED
+ if (!mono_compile_aot) {
+ /* create compilation thread in background */
+ mini_tiered_init ();
+ }
+#endif
+
if (mono_profiler_sampling_enabled ())
mono_runtime_setup_stat_profiler ();
register_icall (mono_profiler_raise_method_tail_call, mono_icall_sig_void_ptr_ptr, TRUE);
register_icall (mono_profiler_raise_exception_clause, mono_icall_sig_void_ptr_int_int_object, TRUE);
- register_icall (mono_trace_enter_method, mono_icall_sig_void_ptr_ptr, TRUE);
- register_icall (mono_trace_leave_method, mono_icall_sig_void_ptr_ptr, TRUE);
+ register_icall (mono_trace_enter_method, mono_icall_sig_void_ptr_ptr_ptr, TRUE);
+ register_icall (mono_trace_leave_method, mono_icall_sig_void_ptr_ptr_ptr, TRUE);
g_assert (mono_get_lmf_addr == mono_tls_get_lmf_addr);
register_icall (mono_jit_set_domain, mono_icall_sig_void_ptr, TRUE);
register_icall (mono_domain_get, mono_icall_sig_ptr, TRUE);
#include "mini-unwind.h"
#include "jit.h"
#include "cfgdump.h"
+#include "tiered.h"
#include "mono/metadata/tabledefs.h"
#include "mono/metadata/marshal.h"
--- /dev/null
+#include <config.h>
+#include <mono/utils/mono-compiler.h>
+
+#include "mini.h"
+#include "mini-runtime.h"
+#include <mono/utils/mono-counters.h>
+#include <mono/utils/mono-logger-internals.h>
+
+#ifdef ENABLE_EXPERIMENT_TIERED
+
+MiniTieredStats mini_tiered_stats;
+
+static MonoCoopCond compilation_wait;
+static MonoCoopMutex compilation_mutex;
+
+#define NUM_TIERS 2
+
+static GSList *compilation_queue [NUM_TIERS];
+static CallsitePatcher patchers [NUM_TIERS] = { NULL };
+
+static const char* const patch_kind_str[] = {
+ "INTERP",
+ "JIT",
+};
+
+static GHashTable *callsites_hash [TIERED_PATCH_KIND_NUM] = { NULL };
+
+/* TODO: use scientific methods (TM) to determine values */
+static const int threshold [NUM_TIERS] = {
+ 1000, /* tier 0 */
+ 3000, /* tier 1 */
+};
+
+static void
+compiler_thread (void)
+{
+ MonoInternalThread *internal = mono_thread_internal_current ();
+ internal->state |= ThreadState_Background;
+ internal->flags |= MONO_THREAD_FLAG_DONT_MANAGE;
+
+ mono_native_thread_set_name (mono_native_thread_id_get (), "Tiered Compilation Thread");
+
+ while (TRUE) {
+ mono_coop_cond_wait (&compilation_wait, &compilation_mutex);
+
+ for (int tier_level = 0; tier_level < NUM_TIERS; tier_level++) {
+ GSList *ppcs = compilation_queue [tier_level];
+ compilation_queue [tier_level] = NULL;
+
+ for (GSList *ppc_= ppcs; ppc_ != NULL; ppc_ = ppc_->next) {
+ MiniTieredPatchPointContext *ppc = (MiniTieredPatchPointContext *) ppc_->data;
+
+ for (int patch_kind = 0; patch_kind < TIERED_PATCH_KIND_NUM; patch_kind++) {
+ if (!callsites_hash [patch_kind])
+ continue;
+
+ GSList *patchsites = g_hash_table_lookup (callsites_hash [patch_kind], ppc->target_method);
+
+ for (; patchsites != NULL; patchsites = patchsites->next) {
+ gpointer patchsite = (gpointer) patchsites->data;
+
+ mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_TIERED, "tiered: patching %p with patch_kind=%s @ tier_level=%d", patchsite, patch_kind_str [patch_kind], tier_level);
+ mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_TIERED, "\t-> caller=%s", mono_pmip (patchsite));
+ mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_TIERED, "\t-> callee=%s", mono_method_full_name (ppc->target_method, TRUE));
+
+ gboolean success = patchers [patch_kind] (ppc, patchsite);
+
+ if (!success)
+ mono_trace (G_LOG_LEVEL_WARNING, MONO_TRACE_TIERED, "tiered: couldn't patch %p with target %s, dropping it.", patchsite, mono_method_full_name (ppc->target_method, TRUE));
+ }
+ g_hash_table_remove (callsites_hash [patch_kind], ppc->target_method);
+ g_slist_free (patchsites);
+ }
+ g_free (ppc);
+ }
+ g_slist_free (ppcs);
+ }
+ mono_coop_mutex_unlock (&compilation_mutex);
+ }
+}
+
+void
+mini_tiered_init (void)
+{
+ ERROR_DECL (error);
+
+ mono_counters_init ();
+ mono_counters_register ("Methods promoted", MONO_COUNTER_TIERED | MONO_COUNTER_LONG, &mini_tiered_stats.methods_promoted);
+
+ mono_coop_cond_init (&compilation_wait);
+ mono_coop_mutex_init (&compilation_mutex);
+
+ mono_thread_create_internal (mono_domain_get (), compiler_thread, NULL, MONO_THREAD_CREATE_FLAGS_THREADPOOL, error);
+ mono_error_assert_ok (error);
+}
+
+void
+mini_tiered_register_callsite_patcher (CallsitePatcher func, int level)
+{
+ g_assert (level < NUM_TIERS);
+
+ patchers [level] = func;
+}
+
+void
+mini_tiered_record_callsite (gpointer ip, MonoMethod *target_method, int patch_kind)
+{
+ if (!callsites_hash [patch_kind])
+ callsites_hash [patch_kind] = g_hash_table_new (NULL, NULL);
+
+ GSList *patchsites = g_hash_table_lookup (callsites_hash [patch_kind], target_method);
+ patchsites = g_slist_prepend (patchsites, ip);
+ g_hash_table_insert (callsites_hash [patch_kind], target_method, patchsites);
+}
+
+void
+mini_tiered_inc (MonoDomain *domain, MonoMethod *method, MiniTieredCounter *tcnt, int tier_level)
+{
+ if (G_UNLIKELY (tcnt->hotness == threshold [tier_level] && !tcnt->promoted)) {
+ tcnt->promoted = TRUE;
+ mini_tiered_stats.methods_promoted++;
+
+ mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_TIERED, "tiered: queued %s", mono_method_full_name (method, TRUE));
+
+ MiniTieredPatchPointContext *ppc = g_new0 (MiniTieredPatchPointContext, 1);
+ ppc->domain = domain;
+ ppc->target_method = method;
+ ppc->tier_level = tier_level;
+
+ mono_coop_mutex_lock (&compilation_mutex);
+ compilation_queue [tier_level] = g_slist_append (compilation_queue [tier_level], ppc);
+ mono_coop_mutex_unlock (&compilation_mutex);
+ mono_coop_cond_signal (&compilation_wait);
+ } else if (!tcnt->promoted) {
+ /* FIXME: inline that into caller */
+ tcnt->hotness++;
+ }
+}
+#else
+MONO_EMPTY_SOURCE_FILE (tiered);
+#endif
--- /dev/null
+#ifdef ENABLE_EXPERIMENT_TIERED
+
+#ifndef __MONO_MINI_TIERED_H__
+#define __MONO_MINI_TIERED_H__
+
+#define TIERED_PATCH_KIND_INTERP 0
+#define TIERED_PATCH_KIND_JIT 1
+#define TIERED_PATCH_KIND_NUM 2
+
+typedef struct {
+ int hotness;
+ gboolean promoted;
+} MiniTieredCounter;
+
+typedef struct {
+ gint64 methods_promoted;
+} MiniTieredStats;
+
+typedef struct {
+ MonoDomain *domain;
+ MonoMethod *target_method;
+ int tier_level;
+} MiniTieredPatchPointContext;
+
+typedef gboolean (*CallsitePatcher)(MiniTieredPatchPointContext *context, gpointer patchsite);
+
+void
+mini_tiered_init (void);
+
+void
+mini_tiered_inc (MonoDomain *domain, MonoMethod *method, MiniTieredCounter *tcnt, int level);
+
+void
+mini_tiered_record_callsite (gpointer callsite, MonoMethod *target_method, int level);
+
+void
+mini_tiered_register_callsite_patcher (CallsitePatcher func, int level);
+
+#endif /* __MONO_MINI_TIERED_H__ */
+#endif /* ENABLE_EXPERIMENT_TIERED */
*/
#define arg_in_stack_slot(cpos, type) ((type *)(cpos))
+static gboolean
+is_gshared_vt_wrapper (MonoMethod *m)
+{
+ if (m->wrapper_type != MONO_WRAPPER_OTHER)
+ return FALSE;
+ return !strcmp (m->name, "interp_in") || !strcmp (m->name, "gsharedvt_out_sig");
+}
+
void
-mono_trace_enter_method (MonoMethod *method, MonoProfilerCallContext *ctx)
+mono_trace_enter_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx)
{
int i;
MonoClass *klass;
while (output_lock != 0 || mono_atomic_cas_i32 (&output_lock, 1, 0) != 0)
mono_thread_info_yield ();
- printf ("ENTER: %s(", fname);
+
+ /* FIXME: Might be better to pass the ji itself */
+ if (!ji)
+ ji = mini_jit_info_table_find (mono_domain_get (), (char *)MONO_RETURN_ADDRESS (), NULL);
+
+ /* ENTER:i <- interp
+ * ENTER:c <- compiled (JIT or AOT)
+ */
+ printf ("ENTER:%c %s(", ji->is_interp ? 'i' : 'c' , fname);
g_free (fname);
sig = mono_method_signature_internal (method);
if (method->is_inflated) {
- /* FIXME: Might be better to pass the ji itself */
- MonoJitInfo *ji = mini_jit_info_table_find (mono_domain_get (), (char *)MONO_RETURN_ADDRESS (), NULL);
+
if (ji) {
gsctx = mono_jit_info_get_generic_sharing_context (ji);
if (gsctx && gsctx->is_gsharedvt) {
if (sig->hasthis) {
void *this_buf = mini_profiler_context_get_this (ctx);
- if (m_class_is_valuetype (method->klass)) {
+ if (m_class_is_valuetype (method->klass) || is_gshared_vt_wrapper (method)) {
printf ("value:%p", this_buf);
} else {
MonoObject *o = *(MonoObject**)this_buf;
}
void
-mono_trace_leave_method (MonoMethod *method, MonoProfilerCallContext *ctx)
+mono_trace_leave_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx)
{
MonoType *type;
char *fname;
while (output_lock != 0 || mono_atomic_cas_i32 (&output_lock, 1, 0) != 0)
mono_thread_info_yield ();
- printf ("LEAVE: %s", fname);
+ /* FIXME: Might be better to pass the ji itself from the JIT */
+ if (!ji)
+ ji = mini_jit_info_table_find (mono_domain_get (), (char *)MONO_RETURN_ADDRESS (), NULL);
+
+ /* LEAVE:i <- interp
+ * LEAVE:c <- compiled (JIT or AOT)
+ */
+ printf ("LEAVE:%c %s(", ji->is_interp ? 'i' : 'c' , fname);
g_free (fname);
if (method->is_inflated) {
- /* FIXME: Might be better to pass the ji itself */
- MonoJitInfo *ji = mini_jit_info_table_find (mono_domain_get (), (char *)MONO_RETURN_ADDRESS (), NULL);
if (ji) {
gsctx = mono_jit_info_get_generic_sharing_context (ji);
if (gsctx && gsctx->is_gsharedvt) {
ICALL_EXTERN_C
void
-mono_trace_enter_method (MonoMethod *method, MonoProfilerCallContext *ctx);
+mono_trace_enter_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx);
ICALL_EXTERN_C
void
-mono_trace_leave_method (MonoMethod *method, MonoProfilerCallContext *ctx);
+mono_trace_leave_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx);
void mono_trace_enable (gboolean enable);
gboolean mono_trace_is_enabled (void);
"", // MONO_COUNTER_PERFCOUNTERS - not used.
"Profiler",
"Interp",
+ "Tiered",
};
static void
MONO_COUNTER_PERFCOUNTERS = 1 << 15,
MONO_COUNTER_PROFILER = 1 << 16,
MONO_COUNTER_INTERP = 1 << 17,
+ MONO_COUNTER_TIERED = 1 << 18,
MONO_COUNTER_LAST_SECTION,
/* Unit, bits 24-27 (4 bits) */
MONO_TRACE_IO_LAYER_HANDLE = 1 << 15,
MONO_TRACE_TAILCALL = 1 << 16,
MONO_TRACE_PROFILER = 1 << 17,
+ MONO_TRACE_TIERED = 1 << 18,
} MonoTraceMask;
MONO_API_DATA GLogLevelFlags mono_internal_current_level;
{ "w32handle", MONO_TRACE_IO_LAYER_HANDLE },
{ "tailcall", MONO_TRACE_TAILCALL },
{ "profiler", MONO_TRACE_PROFILER },
+ { "tiered", MONO_TRACE_TIERED },
{ "all", (MonoTraceMask)~0 }, // FIXMEcxx there is a better way -- operator overloads of enums
{ NULL, (MonoTraceMask)0 },
};