From 4ce5a761eb93e9526bfbfb868f88a25261d5c4ce Mon Sep 17 00:00:00 2001 From: Bernhard Urban-Forster Date: Tue, 29 Oct 2019 22:36:01 +0100 Subject: [PATCH] [mini] Initial tiered compilation work (mono/mono#17551) [mini] Initial tiered compilation work Enable it with `./autogen.sh --enable-experiment=tiered`. Let's consider `Simple.cs`: ```csharp using System.Runtime.CompilerServices; using System; public class Simple { public static void Main (string []args) { HotMethod (); Console.WriteLine ("cnt: " + cnt); HotMethod (); Console.WriteLine ("cnt: " + cnt); } static int cnt = 0; [MethodImplAttribute (MethodImplOptions.NoInlining)] public static void HotMethod () { for (int i = 0; i <= 1000; i++) cnt += i; } } ``` ```console $ csc Simple.cs $ MONO_LOG_LEVEL=debug MONO_LOG_MASK=tiered ./mono/mini/mono-sgen --trace=M:Simple:HotMethod --interp=-all Simple.exe [0x10ec275c0: 0.00000 0] ENTER:i Simple:HotMethod ()() Mono: tiered: queued Simple:HotMethod () [0x10ec275c0: 0.00010 0] LEAVE:i Simple:HotMethod ()( Mono: tiered: patching 0x7fe855803224 with patch_kind=INTERP @ tier_level=0 Mono: -> caller= Simple:Main (string[]) [{0x7fe85420df88} + 0x34 interp] (0x7fe8558031f0 0x7fe855803258) [0x7fe85420c880 - Simple.exe] Mono: -> callee=Simple:HotMethod () Mono: tiered: patching 0x7fe8558031f2 with patch_kind=INTERP @ tier_level=0 Mono: -> caller= Simple:Main (string[]) [{0x7fe85420df88} + 0x2 interp] (0x7fe8558031f0 0x7fe855803258) [0x7fe85420c880 - Simple.exe] Mono: -> callee=Simple:HotMethod () cnt: 500500 [0x10ec275c0: 0.04093 0] ENTER:c Simple:HotMethod ()() [0x10ec275c0: 0.04095 0] LEAVE:c Simple:HotMethod ()( cnt: 1001000 ``` Note the suffix after `ENTER:` * `i` indicates it's executed by the interpreter * `c` indicates it's a compiled method (JIT) Another example: ```console $ make -C mono/mini gshared.exe $ ./mono/mini/mono-sgen --interp=-all --stats ./mono/mini/gshared.exe Regression tests: 84 ran, 0 failed in Tests [...] Tiered statistics Methods promoted : 68 ``` It's a basic proof-of-concept for now. An incomplete list of future work items: * Right now it only works for direct calls, need to expand it to virtual/interface calls. * Calls of the JIT leading into the interpreter again can't be patched yet. * Kind of related, no concept of versioning compiled methods does exist yet. The interpreter maintains its own table of "transformed" methods, however, when doing the transition from JIT->interpreter, the wrapper+interpmethod will end up in the JIT table. We need a way to have multiple JIT compilation results for the same MonoMethod exist. * Investigate actual performance. We might have to optimize the interp<>JIT transition. * Patching is racy. Need to make that atomic. * All the FIXMEs in this PR Commit migrated from https://github.com/mono/mono/commit/d274cfbacdb2aeab0184662997882f3d78991e48 --- src/mono/configure.ac | 8 +- src/mono/mono/mini/Makefile.am.in | 2 + src/mono/mono/mini/interp/interp-internals.h | 4 + src/mono/mono/mini/interp/interp.c | 120 ++++++++++++++++++----- src/mono/mono/mini/interp/mintops.def | 1 + src/mono/mono/mini/interp/mintops.h | 1 + src/mono/mono/mini/interp/transform.c | 119 +++++++++++++++++++++- src/mono/mono/mini/mini-profiler.c | 25 ++--- src/mono/mono/mini/mini-runtime.c | 15 ++- src/mono/mono/mini/mini.h | 1 + src/mono/mono/mini/tiered.c | 141 +++++++++++++++++++++++++++ src/mono/mono/mini/tiered.h | 40 ++++++++ src/mono/mono/mini/trace.c | 38 ++++++-- src/mono/mono/mini/trace.h | 4 +- src/mono/mono/utils/mono-counters.c | 1 + src/mono/mono/utils/mono-counters.h | 1 + src/mono/mono/utils/mono-logger-internals.h | 1 + src/mono/mono/utils/mono-logger.c | 1 + 18 files changed, 468 insertions(+), 55 deletions(-) create mode 100644 src/mono/mono/mini/tiered.c create mode 100644 src/mono/mono/mini/tiered.h diff --git a/src/mono/configure.ac b/src/mono/configure.ac index b5384c2..7ab5aa4 100644 --- a/src/mono/configure.ac +++ b/src/mono/configure.ac @@ -5691,7 +5691,7 @@ dnl *** feature experiments *** 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]) @@ -5702,14 +5702,20 @@ AC_ARG_ENABLE(experiment, [ --enable-experiment=LIST Enable experimental f 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 ********************** diff --git a/src/mono/mono/mini/Makefile.am.in b/src/mono/mono/mini/Makefile.am.in index 0c66de7..ba93c8c 100755 --- a/src/mono/mono/mini/Makefile.am.in +++ b/src/mono/mono/mini/Makefile.am.in @@ -551,6 +551,8 @@ common_sources = \ llvmonly-runtime.c \ aot-runtime.h \ ee.h \ + tiered.h \ + tiered.c \ mini-runtime.h endif # !ENABLE_MSVC_ONLY diff --git a/src/mono/mono/mini/interp/interp-internals.h b/src/mono/mono/mini/interp/interp-internals.h index 26b16a6..060a610 100644 --- a/src/mono/mono/mini/interp/interp-internals.h +++ b/src/mono/mono/mini/interp/interp-internals.h @@ -169,6 +169,9 @@ typedef struct _InterpMethod MonoJitInfo *jinfo; MonoDomain *domain; MonoProfilerCallInstrumentationFlags prof_flags; +#ifdef ENABLE_EXPERIMENT_TIERED + MiniTieredCounter tiered_counter; +#endif } InterpMethod; struct _InterpFrame { @@ -198,6 +201,7 @@ typedef struct { typedef struct { gint64 transform_time; + gint64 methods_transformed; gint64 cprop_time; gint64 super_instructions_time; gint32 stloc_nps; diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 9a62539..0b2ae13 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -2105,7 +2105,7 @@ do_jit_call (stackval *sp, unsigned char *vt_sp, ThreadContext *context, InterpF * 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); @@ -3305,6 +3305,9 @@ interp_exec_method_full (InterpFrame *frame, ThreadContext *context, FrameClause 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)); /* @@ -3625,7 +3628,12 @@ main_loop: 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); @@ -3638,7 +3646,12 @@ vcall_return: 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); @@ -3670,6 +3683,29 @@ common_vcall: 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]]; @@ -3715,24 +3751,45 @@ common_vcall: 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) @@ -3785,18 +3842,22 @@ common_vcall: 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) \ @@ -3949,16 +4010,20 @@ common_vcall: #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) @@ -6357,7 +6422,7 @@ common_vcall: 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; } @@ -6378,7 +6443,7 @@ common_vcall: 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; } @@ -7058,6 +7123,7 @@ register_interp_stats (void) { 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); diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def index ef83487..51f8dc8 100644 --- a/src/mono/mono/mini/interp/mintops.def +++ b/src/mono/mono/mini/interp/mintops.def @@ -734,6 +734,7 @@ OPDEF(MINT_ICALL_PPPPPP_V, "mono_icall_pppppp_v", 2, Pop6, Push0, MintOpClassTok 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) diff --git a/src/mono/mono/mini/interp/mintops.h b/src/mono/mono/mini/interp/mintops.h index 69c4239..9e3c3b6 100644 --- a/src/mono/mono/mini/interp/mintops.h +++ b/src/mono/mono/mini/interp/mintops.h @@ -60,6 +60,7 @@ typedef enum { #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) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index de88b7b..81d9dd3 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -36,6 +36,7 @@ #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 @@ -157,6 +158,9 @@ typedef struct 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; @@ -437,6 +441,14 @@ interp_prev_ins (InterpInst *ins) (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); \ @@ -455,6 +467,11 @@ interp_prev_ins (InterpInst *ins) * (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); \ @@ -887,6 +904,27 @@ jit_call_supported (MonoMethod *method, MonoMethodSignature *sig) 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)) @@ -2451,6 +2489,13 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target } } 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); @@ -3274,7 +3319,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, 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)) @@ -3685,7 +3730,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, 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)) { @@ -6207,6 +6252,10 @@ get_inst_length (InterpInst *ins) { 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]; } @@ -6397,6 +6446,30 @@ emit_compacted_instruction (TransformData *td, guint16* start_ip, InterpInst *in 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)) { @@ -7245,6 +7318,9 @@ generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, MonoG 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; @@ -7353,6 +7429,10 @@ generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, MonoG } 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); @@ -7367,6 +7447,9 @@ exit: 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); @@ -7374,10 +7457,40 @@ exit: 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 @@ -7459,6 +7572,7 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon 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; @@ -7497,6 +7611,7 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon 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); } diff --git a/src/mono/mono/mini/mini-profiler.c b/src/mono/mono/mini/mini-profiler.c index e677dd6..8f70714 100644 --- a/src/mono/mono/mini/mini-profiler.c +++ b/src/mono/mono/mini/mini-profiler.c @@ -92,16 +92,17 @@ mini_profiler_emit_enter (MonoCompile *cfg) 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 @@ -116,16 +117,17 @@ mini_profiler_emit_leave (MonoCompile *cfg, MonoInst *ret) 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 @@ -142,14 +144,15 @@ mini_profiler_emit_tail_call (MonoCompile *cfg, MonoMethod *target) 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) diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 91e1d16..8e644d1 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -232,7 +232,9 @@ mono_get_method_from_ip (void *ip) 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); @@ -4382,6 +4384,13 @@ mini_init (const char *filename, const char *runtime_version) 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 (); @@ -4421,8 +4430,8 @@ register_icalls (void) 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); diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index db89b70..0a7a98b 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -52,6 +52,7 @@ typedef struct SeqPointInfo SeqPointInfo; #include "mini-unwind.h" #include "jit.h" #include "cfgdump.h" +#include "tiered.h" #include "mono/metadata/tabledefs.h" #include "mono/metadata/marshal.h" diff --git a/src/mono/mono/mini/tiered.c b/src/mono/mono/mini/tiered.c new file mode 100644 index 0000000..3de1541 --- /dev/null +++ b/src/mono/mono/mini/tiered.c @@ -0,0 +1,141 @@ +#include +#include + +#include "mini.h" +#include "mini-runtime.h" +#include +#include + +#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 diff --git a/src/mono/mono/mini/tiered.h b/src/mono/mono/mini/tiered.h new file mode 100644 index 0000000..91e91da --- /dev/null +++ b/src/mono/mono/mini/tiered.h @@ -0,0 +1,40 @@ +#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 */ diff --git a/src/mono/mono/mini/trace.c b/src/mono/mono/mini/trace.c index 3832620..5aada9f 100644 --- a/src/mono/mono/mini/trace.c +++ b/src/mono/mono/mini/trace.c @@ -111,8 +111,16 @@ string_to_utf8 (MonoString *s) */ #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; @@ -130,14 +138,21 @@ mono_trace_enter_method (MonoMethod *method, MonoProfilerCallContext *ctx) 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) { @@ -151,7 +166,7 @@ mono_trace_enter_method (MonoMethod *method, MonoProfilerCallContext *ctx) 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; @@ -289,7 +304,7 @@ mono_trace_enter_method (MonoMethod *method, MonoProfilerCallContext *ctx) } void -mono_trace_leave_method (MonoMethod *method, MonoProfilerCallContext *ctx) +mono_trace_leave_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) { MonoType *type; char *fname; @@ -304,12 +319,17 @@ mono_trace_leave_method (MonoMethod *method, MonoProfilerCallContext *ctx) 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) { diff --git a/src/mono/mono/mini/trace.h b/src/mono/mono/mini/trace.h index 86be5f9..8a53d08 100644 --- a/src/mono/mono/mini/trace.h +++ b/src/mono/mono/mini/trace.h @@ -10,11 +10,11 @@ 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); diff --git a/src/mono/mono/utils/mono-counters.c b/src/mono/mono/utils/mono-counters.c index 012aa16..7bf6ccb 100644 --- a/src/mono/mono/utils/mono-counters.c +++ b/src/mono/mono/utils/mono-counters.c @@ -572,6 +572,7 @@ section_names [][12] = { "", // MONO_COUNTER_PERFCOUNTERS - not used. "Profiler", "Interp", + "Tiered", }; static void diff --git a/src/mono/mono/utils/mono-counters.h b/src/mono/mono/utils/mono-counters.h index eb261d6..77175b3 100644 --- a/src/mono/mono/utils/mono-counters.h +++ b/src/mono/mono/utils/mono-counters.h @@ -32,6 +32,7 @@ enum { 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) */ diff --git a/src/mono/mono/utils/mono-logger-internals.h b/src/mono/mono/utils/mono-logger-internals.h index 86ce493..8e15efa 100644 --- a/src/mono/mono/utils/mono-logger-internals.h +++ b/src/mono/mono/utils/mono-logger-internals.h @@ -28,6 +28,7 @@ typedef enum { 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; diff --git a/src/mono/mono/utils/mono-logger.c b/src/mono/mono/utils/mono-logger.c index 89de7ed..174a227 100644 --- a/src/mono/mono/utils/mono-logger.c +++ b/src/mono/mono/utils/mono-logger.c @@ -314,6 +314,7 @@ mono_trace_set_mask_string (const char *value) { "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 }, }; -- 2.7.4