mono/metadata/threads* @luhenry @kumpera
mono/metadata/threadpool* @luhenry
mono/metadata/w32* @luhenry
+mono/mini/profiler* @alexrp
mono/profiler @alexrp
mono/utils/atomic* @alexrp
mono/utils/mono-hwcap* @alexrp
MONO_PROFILER_EVENT_1(assembly_unloading, AssemblyLUnloading, MonoAssembly *, assembly)
MONO_PROFILER_EVENT_1(assembly_unloaded, AssemblyLUnloaded, MonoAssembly *, assembly)
-MONO_PROFILER_EVENT_1(method_enter, MethodEnter, MonoMethod *, method)
-MONO_PROFILER_EVENT_1(method_leave, MethodLeave, MonoMethod *, method)
+MONO_PROFILER_EVENT_2(method_enter, MethodEnter, MonoMethod *, method, MonoProfilerCallContext *, context)
+MONO_PROFILER_EVENT_2(method_leave, MethodLeave, MonoMethod *, method, MonoProfilerCallContext *, context)
MONO_PROFILER_EVENT_2(method_exception_leave, MethodExceptionLeave, MonoMethod *, method, MonoObject *, exception)
MONO_PROFILER_EVENT_1(method_free, MethodFree, MonoMethod *, method)
MONO_PROFILER_EVENT_1(method_begin_invoke, MethodBeginInvoke, MonoMethod *, method)
#define MONO_PROFILER_UNSTABLE_GC_ROOTS
#include <mono/metadata/profiler.h>
+#include <mono/utils/mono-context.h>
#include <mono/utils/mono-lazy-init.h>
#include <mono/utils/mono-os-mutex.h>
#include <mono/utils/mono-os-semaphore.h>
typedef struct {
gboolean startup_done;
+
MonoProfilerHandle profilers;
+
mono_lazy_init_t coverage_status;
mono_mutex_t coverage_mutex;
GHashTable *coverage_hash;
+
MonoProfilerHandle sampling_owner;
MonoSemType sampling_semaphore;
MonoProfilerSampleMode sample_mode;
guint32 sample_freq;
+
gboolean allocations;
+ gboolean call_contexts;
+ void (*context_enable) (void);
+ gpointer (*context_get_this) (MonoProfilerCallContext *);
+ gpointer (*context_get_argument) (MonoProfilerCallContext *, guint32);
+ gpointer (*context_get_local) (MonoProfilerCallContext *, guint32);
+ gpointer (*context_get_result) (MonoProfilerCallContext *);
+ gpointer (*context_free_buffer) (gpointer);
+
#define _MONO_PROFILER_EVENT(name) \
volatile gint32 name ## _count;
#define MONO_PROFILER_EVENT_0(name, type) \
MonoProfilerCoverageInfo *mono_profiler_coverage_alloc (MonoMethod *method, guint32 entries);
void mono_profiler_coverage_free (MonoMethod *method);
-gboolean mono_profiler_should_instrument_method (MonoMethod *method, gboolean entry);
+struct _MonoProfilerCallContext {
+ /*
+ * Must be the first field (the JIT relies on it). Only filled out if this
+ * is a JIT frame; otherwise, zeroed.
+ */
+ MonoContext context;
+ /*
+ * A non-NULL MonoInterpFrameHandle if this is an interpreter frame.
+ */
+ gpointer interp_frame;
+ MonoMethod *method;
+ /*
+ * Points to the return value for an epilogue context. For a prologue, this
+ * is set to NULL.
+ */
+ gpointer return_value;
+};
+
+MonoProfilerCallInstrumentationFlags mono_profiler_get_call_instrumentation_flags (MonoMethod *method);
gboolean mono_profiler_sampling_enabled (void);
void mono_profiler_sampling_thread_post (void);
if (mono_profiler_state.startup_done)
return FALSE;
- mono_profiler_state.allocations = TRUE;
-
- return TRUE;
+ return mono_profiler_state.allocations = TRUE;
}
void
InterlockedWritePointer (&handle->call_instrumentation_filter, (gpointer) cb);
}
-gboolean
-mono_profiler_should_instrument_method (MonoMethod *method, gboolean entry)
+mono_bool
+mono_profiler_enable_call_context_introspection (void)
+{
+ if (mono_profiler_state.startup_done)
+ return FALSE;
+
+ mono_profiler_state.context_enable ();
+
+ return mono_profiler_state.call_contexts = TRUE;
+}
+
+void *
+mono_profiler_call_context_get_this (MonoProfilerCallContext *context)
+{
+ if (!mono_profiler_state.call_contexts)
+ return NULL;
+
+ return mono_profiler_state.context_get_this (context);
+}
+
+void *
+mono_profiler_call_context_get_argument (MonoProfilerCallContext *context, uint32_t position)
+{
+ if (!mono_profiler_state.call_contexts)
+ return NULL;
+
+ return mono_profiler_state.context_get_argument (context, position);
+}
+
+void *
+mono_profiler_call_context_get_local (MonoProfilerCallContext *context, uint32_t position)
+{
+ if (!mono_profiler_state.call_contexts)
+ return NULL;
+
+ return mono_profiler_state.context_get_local (context, position);
+}
+
+void *
+mono_profiler_call_context_get_result (MonoProfilerCallContext *context)
+{
+ if (!mono_profiler_state.call_contexts)
+ return NULL;
+
+ return mono_profiler_state.context_get_result (context);
+}
+
+void
+mono_profiler_call_context_free_buffer (void *buffer)
+{
+ mono_profiler_state.context_free_buffer (buffer);
+}
+
+MonoProfilerCallInstrumentationFlags
+mono_profiler_get_call_instrumentation_flags (MonoMethod *method)
{
MonoProfilerCallInstrumentationFlags flags = MONO_PROFILER_CALL_INSTRUMENTATION_NONE;
flags |= cb (handle->prof, method);
}
- if (entry)
- return flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE;
- else
- return flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE;
+ return flags;
}
void
* Sets a code coverage filter function. The profiler API will invoke filter
* functions from all installed profilers. If any of them return TRUE, then the
* given method will be instrumented for coverage analysis. All filters are
- * guaranteed to be called exactly once per method, even if an earlier filter
+ * guaranteed to be called at least once per method, even if an earlier filter
* has already returned TRUE.
*
* Note that filter functions must be installed before a method is compiled in
MONO_PROFILER_CALL_INSTRUMENTATION_NONE = 1 << 0,
/* Instrument method prologues. */
MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE = 1 << 1,
+ /* Also capture a call context for prologues. */
+ MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT = 1 << 2,
/* Instrument method epilogues. */
- MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE = 1 << 2,
+ MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE = 1 << 3,
+ /* Also capture a call context for epilogues. */
+ MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT = 1 << 4,
} MonoProfilerCallInstrumentationFlags;
typedef MonoProfilerCallInstrumentationFlags (*MonoProfilerCallInstrumentationFilterCallback) (MonoProfiler *prof, MonoMethod *method);
* filter functions from all installed profilers. If any of them return flags
* other than MONO_PROFILER_CALL_INSTRUMENTATION_NONE, then the given method
* will be instrumented as requested. All filters are guaranteed to be called
- * at least once (possibly more) per method entry and exit, even if earlier
- * filters have already specified all flags.
+ * exactly once per method, even if earlier filters have already specified all
+ * flags.
*
* Note that filter functions must be installed before a method is compiled in
* order to have any effect, i.e. you should register your filter function in
- * your profiler's init function.
+ * your profiler's init function. Also, if you want to instrument a method
+ * that's going to be AOT-compiled, you must attach your profiler and install a
+ * call instrumentation filter function at AOT time. This can be done in
+ * exactly the same way as you would normally, i.e. by passing the --profile
+ * option on the command line, by calling mono_profiler_load, or simply by
+ * using the profiler API as an embedder.
*
- * Keep in mind that method instrumentation is extremely heavy and will slow
- * down most applications to a crawl. Consider using sampling instead if it
- * would work for your use case.
+ * Keep in mind that indiscriminate method instrumentation is extremely heavy
+ * and will slow down most applications to a crawl. Consider using sampling
+ * instead if it would work for your use case.
*
* This function is async safe.
*/
MONO_API void mono_profiler_set_call_instrumentation_filter_callback (MonoProfilerHandle handle, MonoProfilerCallInstrumentationFilterCallback cb);
+/*
+ * Enables support for retrieving stack frame data from a call context. At the
+ * moment, this means enabling the debug info subsystem. If you do not call
+ * this function, you will not be able to use the call context introspection
+ * functions (they will simply return NULL). Returns TRUE if call context
+ * introspection was enabled, or FALSE if the function was called too late for
+ * this to be possible.
+ *
+ * Please note: Mono's LLVM backend does not support this feature. This means
+ * that methods with call context instrumentation will be handled by Mono's
+ * JIT even in LLVM mode. There is also a special case when Mono is compiling
+ * in LLVM-only mode: Since LLVM does not provide a way to implement call
+ * contexts, a NULL context will always be passed to enter/leave events even
+ * though this method returns TRUE.
+ *
+ * This function may only be called from your profiler's init function.
+ *
+ * This function is not async safe.
+ */
+MONO_API mono_bool mono_profiler_enable_call_context_introspection (void);
+
+typedef struct _MonoProfilerCallContext MonoProfilerCallContext;
+
+/*
+ * Given a valid call context from an enter/leave event, retrieves a pointer to
+ * the this reference for the method. Returns NULL if none exists (i.e. it's a
+ * static method) or if call context introspection was not enabled.
+ *
+ * The buffer returned by this function must be freed with
+ * mono_profiler_call_context_free_buffer.
+ *
+ * Please note that a call context is only valid for the duration of the
+ * enter/leave callback it was passed to.
+ *
+ * This function is not async safe.
+ */
+MONO_API void *mono_profiler_call_context_get_this (MonoProfilerCallContext *context);
+
+/*
+ * Given a valid call context from an enter/leave event, retrieves a pointer to
+ * the method argument at the given position. Returns NULL if position is out
+ * of bounds or if call context introspection was not enabled.
+ *
+ * The buffer returned by this function must be freed with
+ * mono_profiler_call_context_free_buffer.
+ *
+ * Please note that a call context is only valid for the duration of the
+ * enter/leave callback it was passed to.
+ *
+ * This function is not async safe.
+ */
+MONO_API void *mono_profiler_call_context_get_argument (MonoProfilerCallContext *context, uint32_t position);
+
+/*
+ * Given a valid call context from an enter/leave event, retrieves a pointer to
+ * the local variable at the given position. Returns NULL if position is out of
+ * bounds or if call context introspection was not enabled.
+ *
+ * The buffer returned by this function must be freed with
+ * mono_profiler_call_context_free_buffer.
+ *
+ * Please note that a call context is only valid for the duration of the
+ * enter/leave callback it was passed to.
+ *
+ * This function is not async safe.
+ */
+MONO_API void *mono_profiler_call_context_get_local (MonoProfilerCallContext *context, uint32_t position);
+
+/*
+ * Given a valid call context from an enter/leave event, retrieves a pointer to
+ * return value of a method. Returns NULL if the method has no return value
+ * (i.e. it returns void), if the leave event was the result of a tail call, if
+ * the function is called on a context from an enter event, or if call context
+ * introspection was not enabled.
+ *
+ * The buffer returned by this function must be freed with
+ * mono_profiler_call_context_free_buffer.
+ *
+ * Please note that a call context is only valid for the duration of the
+ * enter/leave callback it was passed to.
+ *
+ * This function is not async safe.
+ */
+MONO_API void *mono_profiler_call_context_get_result (MonoProfilerCallContext *context);
+
+/*
+ * Frees a buffer returned by one of the call context introspection functions.
+ * Passing a NULL buffer is allowed, which makes this function a no-op.
+ *
+ * This function is not async safe.
+ */
+MONO_API void mono_profiler_call_context_free_buffer (void *buffer);
+
#ifdef MONO_PROFILER_UNSTABLE_GC_ROOTS
typedef enum {
/* Upper 2 bytes. */
type-checking.c \
lldb.h \
lldb.c \
- memory-access.c
+ memory-access.c \
+ mini-profiler.c
test_sources = \
basic-calls.cs \
generic_class_init: src1:A len:32 clob:c
get_last_error: dest:i len:32
+
+fill_prof_call_ctx: src1:i len:128
atomic_store_r8: dest:b src1:f len:32
generic_class_init: src1:a len:44 clob:c
+
+fill_prof_call_ctx: src1:i len:128
generic_class_init: src1:a len:44 clob:c
gc_safe_point: src1:i len:12 clob:c
+
+fill_prof_call_ctx: src1:i len:128
gc_param_slot_liveness_def: len:0
get_sp: dest:i len:6
set_sp: src1:i len:6
+
+fill_prof_call_ctx: src1:i len:128
jit->code_start = cfg->native_code;
jit->epilogue_begin = cfg->epilog_begin;
jit->code_size = cfg->code_len;
- jit->has_var_info = debug_options.mdb_optimizations != 0;
+ jit->has_var_info = debug_options.mdb_optimizations || MONO_CFG_PROFILE_CALL_CONTEXT (cfg);
if (jit->epilogue_begin)
record_line_number (info, jit->epilogue_begin, header->code_size);
MonoType **param_types;
MonoJitInfo *jinfo;
MonoDomain *domain;
+ MonoProfilerCallInstrumentationFlags prof_flags;
} InterpMethod;
struct _InterpFrame {
g_print ("%s)\n", args);
g_free (args);
}
- if (mono_profiler_should_instrument_method (frame->imethod->method, TRUE))
- MONO_PROFILER_RAISE (method_enter, (frame->imethod->method));
}
g_free (args); \
debug_indent_level--; \
if (tracing == 3) global_tracing = 0; \
- } \
- if (mono_profiler_should_instrument_method (frame->imethod->method, FALSE)) \
- MONO_PROFILER_RAISE (method_leave, (frame->imethod->method));
+ }
#else
mono_internal_hash_table_insert (&info->interp_code_hash, method, rtm);
mono_domain_jit_code_hash_unlock (domain);
+ rtm->prof_flags = mono_profiler_get_call_instrumentation_flags (rtm->method);
+
return rtm;
}
if (!rethrow) { \
FILL_IN_TRACE(frame->ex, frame); \
} \
+ MONO_PROFILER_RAISE (exception_throw, ((MonoObject *) exception)); \
goto handle_exception; \
} while (0)
MINT_IN_BREAK;
}
+ MINT_IN_CASE(MINT_PROF_ENTER) {
+ ip += 1;
+
+ if (MONO_PROFILER_ENABLED (method_enter)) {
+ MonoProfilerCallContext *prof_ctx = NULL;
+
+ if (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT) {
+ prof_ctx = g_new0 (MonoProfilerCallContext, 1);
+ prof_ctx->interp_frame = frame;
+ prof_ctx->method = frame->imethod->method;
+ }
+
+ MONO_PROFILER_RAISE (method_enter, (frame->imethod->method, prof_ctx));
+
+ g_free (prof_ctx);
+ }
+
+ MINT_IN_BREAK;
+ }
+
MINT_IN_CASE(MINT_LDARGA)
sp->data.p = frame->args + * (guint16 *)(ip + 1);
ip += 2;
goto exit_frame;
}
exit_frame:
+
+ if (!frame->ex) {
+ if (MONO_PROFILER_ENABLED (method_leave) && frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE) {
+ MonoProfilerCallContext *prof_ctx = NULL;
+
+ if (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT) {
+ prof_ctx = g_new0 (MonoProfilerCallContext, 1);
+ prof_ctx->interp_frame = frame;
+ prof_ctx->method = frame->imethod->method;
+
+ MonoType *rtype = mono_method_signature (frame->imethod->method)->ret;
+
+ switch (rtype->type) {
+ case MONO_TYPE_VOID:
+ break;
+ case MONO_TYPE_VALUETYPE:
+ prof_ctx->return_value = frame->retval->data.p;
+ break;
+ default:
+ prof_ctx->return_value = frame->retval;
+ break;
+ }
+ }
+
+ MONO_PROFILER_RAISE (method_leave, (frame->imethod->method, prof_ctx));
+
+ g_free (prof_ctx);
+ }
+ } else
+ MONO_PROFILER_RAISE (method_exception_leave, (frame->imethod->method, (MonoObject *) frame->ex));
+
DEBUG_LEAVE ();
}
OPDEF(MINT_SDB_INTR_LOC, "sdb_intr_loc", 1, MintOpNoArgs)
OPDEF(MINT_SDB_SEQ_POINT, "sdb_seq_point", 1, MintOpNoArgs)
OPDEF(MINT_SDB_BREAKPOINT, "sdb_breakpoint", 1, MintOpNoArgs)
+
+/*
+ * This needs to be an opcode because we need to trigger the enter event after
+ * the STINARG* opcodes have executed.
+ */
+OPDEF(MINT_PROF_ENTER, "prof_enter", 1, MintOpNoArgs)
}
}
+ if (rtm->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE)
+ ADD_CODE (td, MINT_PROF_ENTER);
+
if (sym_seq_points) {
InterpBasicBlock *cbb = td->offset_to_bb [0];
g_assert (cbb);
EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, lmf_addr_reg, 0, prev_lmf_reg);
}
-static void
-emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry)
-{
- MonoInst *iargs [1];
-
- /*
- * Avoid instrumenting inlined methods since it can
- * distort profiling results.
- */
- if (cfg->method != cfg->current_method)
- return;
-
- if (mono_profiler_should_instrument_method (cfg->method, entry)) {
- EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method);
- mono_emit_jit_icall (cfg, func, iargs);
- }
-}
-
static int
ret_type_to_call_opcode (MonoCompile *cfg, MonoType *type, int calli, int virt)
{
tail = FALSE;
if (tail) {
- emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE);
+ mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL);
MONO_INST_NEW_CALL (cfg, call, OP_TAILCALL);
} else
if (g_list_find (cfg->dont_inline, method))
return FALSE;
+ if (mono_profiler_get_call_instrumentation_flags (method))
+ return FALSE;
+
return TRUE;
}
if (cfg->gshared && mono_method_check_context_used (cmethod))
GENERIC_SHARING_FAILURE (CEE_JMP);
- emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE);
+ mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL);
fsig = mono_method_signature (cmethod);
n = fsig->param_count + fsig->hasthis;
/* Handle tail calls similarly to normal calls */
tail_call = TRUE;
} else {
- emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE);
+ mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL);
MONO_INST_NEW_CALL (cfg, call, OP_JMP);
call->tail_call = TRUE;
break;
}
case CEE_RET:
+ mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, sp - 1, sig->ret);
+
if (cfg->method != method) {
/* return from inlined method */
/*
cfg->ret_var_set = TRUE;
}
} else {
- emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE);
-
if (cfg->lmf_var && cfg->cbb->in_count && !cfg->llvm_only)
emit_pop_lmf (cfg);
}
cfg->cbb = init_localsbb;
- emit_instrumentation_call (cfg, mono_profiler_raise_method_enter, TRUE);
+ mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_enter, TRUE, NULL, NULL);
if (seq_points) {
MonoBasicBlock *bb;
case OP_GET_LAST_ERROR:
emit_get_last_error(code, ins->dreg);
break;
+ case OP_FILL_PROF_CALL_CTX:
+ for (int i = 0; i < AMD64_NREG; i++)
+ if (AMD64_IS_CALLEE_SAVED_REG (i) || i == AMD64_RSP)
+ amd64_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, gregs) + i * sizeof (mgreg_t), i, sizeof (mgreg_t));
+ break;
default:
g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__);
g_assert_not_reached ();
arm_patch (buf [0], code);
break;
}
-
+ case OP_FILL_PROF_CALL_CTX:
+ for (int i = 0; i < ARMREG_MAX; i++)
+ if ((MONO_ARCH_CALLEE_SAVED_REGS & (1 << i)) || i == ARMREG_SP || i == ARMREG_FP)
+ ARM_STR_IMM (code, i, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, regs) + i * sizeof (mgreg_t));
+ break;
default:
g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__);
g_assert_not_reached ();
#endif
break;
}
-
+ case OP_FILL_PROF_CALL_CTX:
+ for (int i = 0; i < MONO_MAX_IREGS; i++)
+ if ((MONO_ARCH_CALLEE_SAVED_REGS & (1 << i)) || i == ARMREG_SP || i == ARMREG_FP)
+ arm_strx (code, i, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, regs) + i * sizeof (mgreg_t));
+ break;
default:
g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__);
g_assert_not_reached ();
MINI_OP(OP_SET_SP, "set_sp", NONE, IREG, NONE)
MINI_OP(OP_GET_LAST_ERROR, "get_last_error", IREG, NONE, NONE)
+
+/*
+ * Fill out a MonoContext contained in a MonoProfilerCallContext. This only
+ * stores the stack pointer, frame pointer, and callee-saved registers. This
+ * should be enough to locate arguments and variables.
+ */
+MINI_OP(OP_FILL_PROF_CALL_CTX, "fill_prof_call_ctx", NONE, IREG, NONE)
--- /dev/null
+/*
+ * Licensed to the .NET Foundation under one or more agreements.
+ * The .NET Foundation licenses this file to you under the MIT license.
+ * See the LICENSE file in the project root for more information.
+ */
+
+#include <mono/metadata/abi-details.h>
+#include <mono/metadata/mono-debug.h>
+
+#include "interp/interp.h"
+#include "ir-emit.h"
+#include "mini.h"
+
+void
+mini_profiler_emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry, MonoInst **ret, MonoType *rtype)
+{
+ gboolean instrument, capture;
+
+ if (entry) {
+ instrument = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE;
+ capture = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT;
+ } else {
+ instrument = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE;
+ capture = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT;
+ }
+
+ if (!instrument)
+ return;
+
+ g_assert (cfg->current_method == cfg->method);
+
+ MonoInst *iargs [2];
+
+ EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method);
+
+ if (capture && !cfg->llvm_only) {
+ cfg->flags |= MONO_CFG_HAS_ALLOCA;
+
+ MonoInst *size, *fill_ctx;
+
+ EMIT_NEW_ICONST (cfg, size, sizeof (MonoProfilerCallContext));
+ MONO_INST_NEW (cfg, iargs [1], OP_LOCALLOC);
+ iargs [1]->dreg = alloc_preg (cfg);
+ iargs [1]->sreg1 = size->dreg;
+ iargs [1]->flags |= MONO_INST_INIT;
+ MONO_ADD_INS (cfg->cbb, iargs [1]);
+ MONO_INST_NEW (cfg, fill_ctx, OP_FILL_PROF_CALL_CTX);
+ fill_ctx->sreg1 = iargs [1]->dreg;
+ MONO_ADD_INS (cfg->cbb, fill_ctx);
+ MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STORE_MEMBASE_REG, iargs [1]->dreg, MONO_STRUCT_OFFSET (MonoProfilerCallContext, method), iargs [0]->dreg);
+
+ if (rtype && rtype->type != MONO_TYPE_VOID) {
+ MonoInst *var = mono_compile_create_var (cfg, rtype, OP_LOCAL);
+
+ MonoInst *store, *addr;
+
+ EMIT_NEW_TEMPSTORE (cfg, store, var->inst_c0, *ret);
+ EMIT_NEW_VARLOADA (cfg, addr, var, NULL);
+ MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STORE_MEMBASE_REG, iargs [1]->dreg, MONO_STRUCT_OFFSET (MonoProfilerCallContext, return_value), addr->dreg);
+ }
+ } else
+ EMIT_NEW_PCONST (cfg, iargs [1], NULL);
+
+ mono_emit_jit_icall (cfg, func, iargs);
+}
+
+void
+mini_profiler_context_enable (void)
+{
+ if (!mono_debug_enabled ())
+ mono_debug_init (MONO_DEBUG_FORMAT_MONO);
+}
+
+static gpointer
+memdup_with_type (gpointer data, MonoType *t)
+{
+ int dummy;
+
+ return g_memdup (data, mono_type_size (t, &dummy));
+}
+
+static guint8 *
+get_int_reg (MonoContext *ctx, guint32 reg)
+{
+ return (guint8 *) mono_arch_context_get_int_reg (ctx, reg);
+}
+
+static gpointer
+get_variable_buffer (MonoDebugMethodJitInfo *jit, MonoDebugVarInfo *var, MonoContext *ctx)
+{
+ guint32 flags = var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+ guint32 reg = var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+
+ switch (flags) {
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER: {
+ /*
+ * This is kind of a special case: All other address modes ultimately
+ * produce an address to where the actual value is located, but this
+ * address mode gets us the value itself as an mgreg_t value.
+ */
+ mgreg_t value = (mgreg_t) get_int_reg (ctx, reg);
+
+ return memdup_with_type (&value, var->type);
+ }
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET:
+ return memdup_with_type (get_int_reg (ctx, reg) + (gint32) var->offset, var->type);
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET_INDIR:
+ case MONO_DEBUG_VAR_ADDRESS_MODE_VTADDR:
+ return memdup_with_type (*(guint8 **) (get_int_reg (ctx, reg) + (gint32) var->offset), var->type);
+ case MONO_DEBUG_VAR_ADDRESS_MODE_GSHAREDVT_LOCAL: {
+ guint32 idx = reg;
+
+ MonoDebugVarInfo *info_var = jit->gsharedvt_info_var;
+
+ flags = info_var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+ reg = info_var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+
+ MonoGSharedVtMethodRuntimeInfo *info;
+
+ switch (flags) {
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER:
+ info = (MonoGSharedVtMethodRuntimeInfo *) get_int_reg (ctx, reg);
+ break;
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET:
+ info = *(MonoGSharedVtMethodRuntimeInfo **) (get_int_reg (ctx, reg) + (gint32) info_var->offset);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ MonoDebugVarInfo *locals_var = jit->gsharedvt_locals_var;
+
+ flags = locals_var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+ reg = locals_var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS;
+
+ guint8 *locals;
+
+ switch (flags) {
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER:
+ locals = get_int_reg (ctx, reg);
+ break;
+ case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET:
+ locals = *(guint8 **) (get_int_reg (ctx, reg) + (gint32) info_var->offset);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return memdup_with_type (locals + (gsize) info->entries [idx], var->type);
+ }
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
+gpointer
+mini_profiler_context_get_this (MonoProfilerCallContext *ctx)
+{
+ if (!mono_method_signature (ctx->method)->hasthis)
+ return NULL;
+
+ if (ctx->interp_frame)
+ return memdup_with_type (mono_interp_frame_get_this (ctx->interp_frame), &ctx->method->klass->this_arg);
+
+ MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ());
+
+ if (!info)
+ return NULL;
+
+ return get_variable_buffer (info, info->this_var, &ctx->context);
+}
+
+gpointer
+mini_profiler_context_get_argument (MonoProfilerCallContext *ctx, guint32 pos)
+{
+ MonoMethodSignature *sig = mono_method_signature (ctx->method);
+
+ if (pos >= sig->param_count)
+ return NULL;
+
+ if (ctx->interp_frame)
+ return memdup_with_type (mono_interp_frame_get_arg (ctx->interp_frame, pos), sig->params [pos]);
+
+ MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ());
+
+ if (!info)
+ return NULL;
+
+ return get_variable_buffer (info, &info->params [pos], &ctx->context);
+}
+
+gpointer
+mini_profiler_context_get_local (MonoProfilerCallContext *ctx, guint32 pos)
+{
+ MonoError error;
+ MonoMethodHeader *header = mono_method_get_header_checked (ctx->method, &error);
+ mono_error_assert_ok (&error); // Must be a valid method at this point.
+
+ if (pos >= header->num_locals) {
+ mono_metadata_free_mh (header);
+ return NULL;
+ }
+
+ MonoType *t = header->locals [pos];
+
+ mono_metadata_free_mh (header);
+
+ if (ctx->interp_frame)
+ return memdup_with_type (mono_interp_frame_get_local (ctx->interp_frame, pos), t);
+
+ MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ());
+
+ if (!info)
+ return NULL;
+
+ return get_variable_buffer (info, &info->locals [pos], &ctx->context);
+}
+
+gpointer
+mini_profiler_context_get_result (MonoProfilerCallContext *ctx)
+{
+ if (!ctx->return_value)
+ return NULL;
+
+ return memdup_with_type (ctx->return_value, mono_method_signature (ctx->method)->ret);
+}
+
+void
+mini_profiler_context_free_buffer (void *buffer)
+{
+ g_free (buffer);
+}
mono_install_get_class_from_name (mono_aot_get_class_from_name);
mono_install_jit_info_find_in_aot (mono_aot_find_jit_info);
+ mono_profiler_state.context_enable = mini_profiler_context_enable;
+ mono_profiler_state.context_get_this = mini_profiler_context_get_this;
+ mono_profiler_state.context_get_argument = mini_profiler_context_get_argument;
+ mono_profiler_state.context_get_local = mini_profiler_context_get_local;
+ mono_profiler_state.context_get_result = mini_profiler_context_get_result;
+ mono_profiler_state.context_free_buffer = mini_profiler_context_free_buffer;
+
if (profile_options)
for (guint i = 0; i < profile_options->len; i++)
mono_profiler_load ((const char *) g_ptr_array_index (profile_options, i));
* the wrapper would call the icall which would call the wrapper and
* so on.
*/
- register_icall (mono_profiler_raise_method_enter, "mono_profiler_raise_method_enter", "void ptr", TRUE);
- register_icall (mono_profiler_raise_method_leave, "mono_profiler_raise_method_leave", "void ptr", TRUE);
+ register_icall (mono_profiler_raise_method_enter, "mono_profiler_raise_method_enter", "void ptr ptr", TRUE);
+ register_icall (mono_profiler_raise_method_leave, "mono_profiler_raise_method_leave", "void ptr ptr", TRUE);
register_icall (mono_trace_enter_method, "mono_trace_enter_method", NULL, TRUE);
register_icall (mono_trace_leave_method, "mono_trace_leave_method", NULL, TRUE);
case OP_SET_SP:
x86_mov_reg_reg (code, X86_ESP, ins->sreg1, sizeof (mgreg_t));
break;
+ case OP_FILL_PROF_CALL_CTX:
+ x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, esp), X86_ESP, sizeof (mgreg_t));
+ x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, ebp), X86_EBP, sizeof (mgreg_t));
+ x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, ebx), X86_EBX, sizeof (mgreg_t));
+ x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, esi), X86_ESI, sizeof (mgreg_t));
+ x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, edi), X86_EDI, sizeof (mgreg_t));
+ break;
default:
g_warning ("unknown opcode %s\n", mono_inst_name (ins->opcode));
g_assert_not_reached ();
}
#endif
+ cfg->prof_flags = mono_profiler_get_call_instrumentation_flags (cfg->method);
+
/* The debugger has no liveness information, so avoid sharing registers/stack slots */
- if (debug_options.mdb_optimizations) {
+ if (debug_options.mdb_optimizations || MONO_CFG_PROFILE_CALL_CONTEXT (cfg)) {
cfg->disable_reuse_registers = TRUE;
cfg->disable_reuse_stack_slots = TRUE;
/*
int stat_inlineable_methods;
int stat_inlined_methods;
int stat_code_reallocs;
+
+ MonoProfilerCallInstrumentationFlags prof_flags;
} MonoCompile;
+#define MONO_CFG_PROFILE_CALL_CONTEXT(cfg) \
+ ((cfg)->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT))
+
typedef enum {
MONO_CFG_HAS_ALLOCA = 1 << 0,
MONO_CFG_HAS_CALLS = 1 << 1,
MONO_API MonoDebugOptions *mini_get_debug_options (void);
MONO_API gboolean mini_parse_debug_option (const char *option);
void mini_add_profiler_argument (const char *desc);
+void mini_profiler_emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry, MonoInst **ret, MonoType *rtype);
+void mini_profiler_context_enable (void);
+gpointer mini_profiler_context_get_this (MonoProfilerCallContext *ctx);
+gpointer mini_profiler_context_get_argument (MonoProfilerCallContext *ctx, guint32 pos);
+gpointer mini_profiler_context_get_local (MonoProfilerCallContext *ctx, guint32 pos);
+gpointer mini_profiler_context_get_result (MonoProfilerCallContext *ctx);
+void mini_profiler_context_free_buffer (gpointer buffer);
/* graph dumping */
void mono_cfg_dump_create_context (MonoCompile *cfg);
}
static void
-method_enter (MonoProfiler *prof, MonoMethod *method)
+method_enter (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx)
{
if (get_thread ()->call_depth++ <= log_config.max_call_depth) {
ENTER_LOG (&method_entries_ctr, logbuffer,
}
static void
-method_leave (MonoProfiler *prof, MonoMethod *method)
+method_leave (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx)
{
if (--get_thread ()->call_depth <= log_config.max_call_depth) {
ENTER_LOG (&method_exits_ctr, logbuffer,
<ClCompile Include="..\mono\mini\type-checking.c" />\r
<ClCompile Include="..\mono\mini\lldb.c" />\r
<ClCompile Include="..\mono\mini\interp\interp-stubs.c" />\r
+ <ClCompile Include="..\mono\mini\mini-profiler.c" />\r
</ItemGroup>\r
<PropertyGroup Label="Globals">\r
<ProjectGuid>{CB0D9E92-293C-439C-9AC7-C5F59B6E0772}</ProjectGuid>\r
<ClCompile Include="..\mono\mini\interp\interp-stubs.c">\r
<Filter>Source Files</Filter>\r
</ClCompile>\r
+ <ClCompile Include="..\mono\mini\mini-profiler.c">\r
+ <Filter>Source Files</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="..\mono\mini\abcremoval.h">\r
mono_print_thread_dump
mono_print_thread_dump_from_ctx
mono_print_unhandled_exception
+mono_profiler_call_context_free_buffer
+mono_profiler_call_context_get_argument
+mono_profiler_call_context_get_local
+mono_profiler_call_context_get_result
+mono_profiler_call_context_get_this
mono_profiler_create
mono_profiler_enable_allocations
+mono_profiler_enable_call_context_introspection
mono_profiler_enable_sampling
mono_profiler_get_coverage_data
mono_profiler_get_sample_mode
mono_print_thread_dump
mono_print_thread_dump_from_ctx
mono_print_unhandled_exception
+mono_profiler_call_context_free_buffer
+mono_profiler_call_context_get_argument
+mono_profiler_call_context_get_local
+mono_profiler_call_context_get_result
+mono_profiler_call_context_get_this
mono_profiler_create
mono_profiler_enable_allocations
+mono_profiler_enable_call_context_introspection
mono_profiler_enable_sampling
mono_profiler_get_coverage_data
mono_profiler_get_sample_mode