diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index b90143b89dad0d..b2c6617278259f 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -63,7 +63,6 @@ const fn_signatures: SigLine[] = [ [() => !runtimeHelpers.emscriptenBuildOptions.enableAotProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]], [() => !runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "mono_wasm_profiler_init_browser", "void", ["string"]], [() => !runtimeHelpers.emscriptenBuildOptions.enableLogProfiler, "mono_wasm_profiler_init_log", "void", ["string"]], - [true, "mono_wasm_profiler_init_browser", "void", ["number"]], [false, "mono_wasm_exec_regression", "number", ["number", "string"]], [false, "mono_wasm_invoke_jsexport", "void", ["number", "number"]], [true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]], @@ -73,6 +72,7 @@ const fn_signatures: SigLine[] = [ [true, "mono_wasm_f64_to_i52", "number", ["number", "number"]], [true, "mono_wasm_f64_to_u52", "number", ["number", "number"]], [true, "mono_wasm_method_get_name", "number", ["number"]], + [true, "mono_wasm_method_get_name_ex", "number", ["number"]], [true, "mono_wasm_method_get_full_name", "number", ["number"]], [true, "mono_wasm_gc_lock", "void", []], [true, "mono_wasm_gc_unlock", "void", []], @@ -131,6 +131,9 @@ const fn_signatures: SigLine[] = [ [true, "mono_jiterp_end_catch", "void", []], [true, "mono_interp_pgo_load_table", "number", ["number", "number"]], [true, "mono_interp_pgo_save_table", "number", ["number", "number"]], + [() => !runtimeHelpers.emscriptenBuildOptions.enablePerfTracing, "mono_jiterp_prof_enter", "void", ["number", "number"]], + [() => !runtimeHelpers.emscriptenBuildOptions.enablePerfTracing, "mono_jiterp_prof_samplepoint", "void", ["number", "number"]], + [() => !runtimeHelpers.emscriptenBuildOptions.enablePerfTracing, "mono_jiterp_prof_leave", "void", ["number", "number"]], ...threading_cwraps, ]; @@ -193,6 +196,7 @@ export interface t_Cwraps { mono_wasm_f64_to_i52(destination: VoidPtr, value: number): I52Error; mono_wasm_f64_to_u52(destination: VoidPtr, value: number): I52Error; mono_wasm_method_get_name(method: MonoMethod): CharPtr; + mono_wasm_method_get_name_ex(method: MonoMethod): CharPtr; mono_wasm_method_get_full_name(method: MonoMethod): CharPtr; mono_wasm_gc_lock(): void; mono_wasm_gc_unlock(): void; diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index 94269d04b1ce1e..62a9c4eb3b2c7d 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -534,6 +534,22 @@ EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name (MonoMethod *method) return res; } +EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) { + const char *res; + MONO_ENTER_GC_UNSAFE; + res = mono_method_get_name (method); + // starts with .ctor or .cctor + if (mono_method_get_flags (method, NULL) & 0x0800 /* METHOD_ATTRIBUTE_SPECIAL_NAME */ && strlen (res) < 7) { + char *res_ex = (char *) malloc (128); + snprintf (res_ex, 128,"%s.%s", mono_class_get_name (mono_method_get_class (method)), res); + res = res_ex; + } else { + res = strdup (res); + } + MONO_EXIT_GC_UNSAFE; + return res; +} + EMSCRIPTEN_KEEPALIVE float mono_wasm_get_f32_unaligned (const float *src) { return *src; } diff --git a/src/mono/browser/runtime/es6/dotnet.es6.lib.js b/src/mono/browser/runtime/es6/dotnet.es6.lib.js index bf5ceabaf44afd..2ff1c066d6cd48 100644 --- a/src/mono/browser/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/browser/runtime/es6/dotnet.es6.lib.js @@ -90,6 +90,7 @@ function injectDependencies() { `enablePerfTracing: ${FEATURE_PERFTRACING ? "true" : "false"}, ` + `enableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` + `enableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` + + `enablePerfTracing: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` + `enableLogProfiler: ${ENABLE_LOG_PROFILER ? "true" : "false"}, ` + `runAOTCompilation: ${RUN_AOT_COMPILATION ? "true" : "false"}, ` + `wasmEnableThreads: ${USE_PTHREADS ? "true" : "false"}, ` + diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index 8e819c48f93c7f..29d5eead5f5958 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -6,14 +6,14 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint, mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause } from "./debug"; import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_bind_js_import_ST, mono_wasm_invoke_js_function, mono_wasm_invoke_jsimport_MT, mono_wasm_invoke_jsimport_ST } from "./invoke-js"; -import { mono_interp_tier_prepare_jiterpreter, mono_jiterp_free_method_data_js } from "./jiterpreter"; +import { mono_interp_tier_prepare_jiterpreter, mono_wasm_free_method_data } from "./jiterpreter"; import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry"; import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue } from "./jiterpreter-jit-call"; import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js"; import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling"; import { mono_wasm_asm_loaded } from "./startup"; import { mono_log_warn, mono_wasm_console_clear, mono_wasm_trace_logger } from "./logging"; -import { mono_wasm_profiler_leave, mono_wasm_profiler_enter, ds_rt_websocket_close, ds_rt_websocket_create, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_send } from "./profiler"; +import { mono_wasm_profiler_record, mono_wasm_profiler_now, ds_rt_websocket_close, ds_rt_websocket_create, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_send } from "./profiler"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -68,10 +68,10 @@ export const mono_wasm_imports = [ mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue, - mono_jiterp_free_method_data_js, + mono_wasm_free_method_data, - mono_wasm_profiler_enter, - mono_wasm_profiler_leave, + mono_wasm_profiler_now, + mono_wasm_profiler_record, // driver.c mono_wasm_trace_logger, diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index e914669dbb444e..5bd39ede34563b 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -1600,6 +1600,27 @@ export const _now = (globalThis.performance && globalThis.performance.now) let scratchBuffer: NativePointer = 0; +export function append_profiler_event (builder: WasmBuilder, ip: MintOpcodePtr, opcode: MintOpcode) { + let event_name:string; + switch (opcode) { + case MintOpcode.MINT_PROF_ENTER: + event_name = "prof_enter"; + break; + case MintOpcode.MINT_PROF_SAMPLEPOINT: + event_name = "prof_samplepoint"; + break; + case MintOpcode.MINT_PROF_EXIT: + case MintOpcode.MINT_PROF_EXIT_VOID: + event_name = "prof_leave"; + break; + default: + throw new Error(`Unimplemented profiler event ${opcode}`); + } + builder.local("frame"); + builder.i32_const(ip); + builder.callImport(event_name); +} + export function append_safepoint (builder: WasmBuilder, ip: MintOpcodePtr) { // safepoints are never triggered in a single-threaded build if (!WasmEnableThreads) diff --git a/src/mono/browser/runtime/jiterpreter-trace-generator.ts b/src/mono/browser/runtime/jiterpreter-trace-generator.ts index fb36b03e2cdfe6..b777f01e285ba9 100644 --- a/src/mono/browser/runtime/jiterpreter-trace-generator.ts +++ b/src/mono/browser/runtime/jiterpreter-trace-generator.ts @@ -28,6 +28,7 @@ import { try_append_memmove_fast, getOpcodeTableValue, getMemberOffset, isZeroPageReserved, CfgBranchType, append_safepoint, modifyCounter, simdFallbackCounters, + append_profiler_event, } from "./jiterpreter-support"; import { sizeOfDataItem, sizeOfV128, sizeOfStackval, @@ -1415,9 +1416,15 @@ export function generateWasmBody ( } case MintOpcode.MINT_RETHROW: + ip = abort; + break; + + // call C + case MintOpcode.MINT_PROF_ENTER: + case MintOpcode.MINT_PROF_SAMPLEPOINT: case MintOpcode.MINT_PROF_EXIT: case MintOpcode.MINT_PROF_EXIT_VOID: - ip = abort; + append_profiler_event(builder, ip, opcode); break; // Generating code for these is kind of complex due to the intersection of JS and int64, diff --git a/src/mono/browser/runtime/jiterpreter.ts b/src/mono/browser/runtime/jiterpreter.ts index abaa7d63cd8e56..80da42e655f6ad 100644 --- a/src/mono/browser/runtime/jiterpreter.ts +++ b/src/mono/browser/runtime/jiterpreter.ts @@ -26,6 +26,7 @@ import { mono_jiterp_free_method_data_interp_entry } from "./jiterpreter-interp- import { mono_jiterp_free_method_data_jit_call } from "./jiterpreter-jit-call"; import { mono_log_error, mono_log_info, mono_log_warn } from "./logging"; import { utf8ToString } from "./strings"; +import { mono_wasm_profiler_free_method } from "./profiler"; // Controls miscellaneous diagnostic output. export const trace = 0; @@ -298,6 +299,12 @@ function getTraceImports () { if (nullCheckValidation) traceImports.push(importDef("notnull", assert_not_null)); + if (runtimeHelpers.emscriptenBuildOptions.enablePerfTracing) { + traceImports.push(importDef("prof_enter", getRawCwrap("mono_jiterp_prof_enter"))); + traceImports.push(importDef("prof_samplepoint", getRawCwrap("mono_jiterp_prof_samplepoint"))); + traceImports.push(importDef("prof_leave", getRawCwrap("mono_jiterp_prof_leave"))); + } + const pushMathOps = (list: string[], type: string) => { for (let i = 0; i < list.length; i++) { const mop = list[i]; @@ -579,6 +586,30 @@ function initialize_builder (builder: WasmBuilder) { }, WasmValtype.void, true ); + builder.defineType( + "prof_enter", + { + "frame": WasmValtype.i32, + "ip": WasmValtype.i32, + }, + WasmValtype.void, true + ); + builder.defineType( + "prof_samplepoint", + { + "frame": WasmValtype.i32, + "ip": WasmValtype.i32, + }, + WasmValtype.void, true + ); + builder.defineType( + "prof_leave", + { + "frame": WasmValtype.i32, + "ip": WasmValtype.i32, + }, + WasmValtype.void, true + ); builder.defineType( "hashcode", { @@ -1050,9 +1081,13 @@ export function mono_interp_tier_prepare_jiterpreter ( // NOTE: This will potentially be called once for every trace entry point // in a given method, not just once per method -export function mono_jiterp_free_method_data_js ( +export function mono_wasm_free_method_data ( method: MonoMethod, imethod: number, traceIndex: number ) { + if (runtimeHelpers.emscriptenBuildOptions.enablePerfTracing) { + mono_wasm_profiler_free_method(method); + } + // TODO: Uninstall the trace function pointer from the function pointer table, // so that the compiled trace module can be freed by the browser eventually // Release the trace info object, if present diff --git a/src/mono/browser/runtime/profiler.ts b/src/mono/browser/runtime/profiler.ts index 0b6c93af31e2ff..223fea908c77ff 100644 --- a/src/mono/browser/runtime/profiler.ts +++ b/src/mono/browser/runtime/profiler.ts @@ -7,6 +7,7 @@ import { ENVIRONMENT_IS_WEB, mono_assert, runtimeHelpers } from "./globals"; import { MonoMethod, AOTProfilerOptions, BrowserProfilerOptions, LogProfilerOptions } from "./types/internal"; import { profiler_c_functions as cwraps } from "./cwraps"; import { utf8ToString } from "./strings"; +import { free } from "./memory"; // Initialize the AOT profiler with OPTIONS. // Requires the AOT profiler to be linked into the app. @@ -32,7 +33,13 @@ export function mono_wasm_init_browser_profiler (options: BrowserProfilerOptions mono_assert(runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "Browser profiler is not enabled, please use browser; in your project file."); if (options == null) options = {}; - const arg = "browser:"; + let arg = "browser:"; + if (typeof options.callSpec === "string") { + arg += `callspec=${options.callSpec},`; + } + if (typeof options.sampleIntervalMs === "number") { + arg += `interval=${options.sampleIntervalMs},`; + } cwraps.mono_wasm_profiler_init_browser(arg); } @@ -76,6 +83,7 @@ export function startMeasure (): TimeStamp { export function endMeasure (start: TimeStamp, block: string, id?: string) { if (runtimeHelpers.enablePerfMeasure && start) { + // API is slightly different between web and Nodejs const options = ENVIRONMENT_IS_WEB ? { start: start as any } : { startTime: start as any }; @@ -84,28 +92,27 @@ export function endMeasure (start: TimeStamp, block: string, id?: string) { } } -const stackFrames: number[] = []; -export function mono_wasm_profiler_enter (): void { - if (runtimeHelpers.enablePerfMeasure) { - stackFrames.push(globalThis.performance.now()); - } +export function mono_wasm_profiler_now (): number { + return globalThis.performance.now(); +} + +export function mono_wasm_profiler_free_method (method: MonoMethod): void { + methodNames.delete(method as any); } const methodNames: Map = new Map(); -export function mono_wasm_profiler_leave (method: MonoMethod): void { - if (runtimeHelpers.enablePerfMeasure) { - const start = stackFrames.pop(); - const options = ENVIRONMENT_IS_WEB - ? { start: start } - : { startTime: start }; - let methodName = methodNames.get(method as any); - if (!methodName) { - const chars = cwraps.mono_wasm_method_get_name(method); - methodName = utf8ToString(chars); - methodNames.set(method as any, methodName); - } - globalThis.performance.measure(methodName, options); +export function mono_wasm_profiler_record (method: MonoMethod, start: number): void { + const options = ENVIRONMENT_IS_WEB + ? { start: start } + : { startTime: start }; + let methodName = methodNames.get(method as any); + if (!methodName) { + const chars = cwraps.mono_wasm_method_get_name_ex(method); + methodName = utf8ToString(chars); + methodNames.set(method as any, methodName); + free(chars as any); } + globalThis.performance.measure(methodName, options); } /* eslint-disable @typescript-eslint/no-unused-vars */ diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 2af269d1b226df..8dd0e98d08e0a8 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -259,6 +259,12 @@ export type AOTProfilerOptions = { } export type BrowserProfilerOptions = { + sampleIntervalMs?: number, // default: 1000 + /** + * See callspec in https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup + * When used together with Mono AOT, the callspec needs to match one in browser:callspec=N:Sample; in your project file. + */ + callSpec?: string, } export type LogProfilerOptions = { diff --git a/src/mono/mono/metadata/jit-icall-reg.h b/src/mono/mono/metadata/jit-icall-reg.h index e412fdd5448dca..c844391d31cea9 100644 --- a/src/mono/mono/metadata/jit-icall-reg.h +++ b/src/mono/mono/metadata/jit-icall-reg.h @@ -250,6 +250,7 @@ MONO_JIT_ICALL (mono_ppc_throw_exception) \ MONO_JIT_ICALL (mono_profiler_raise_exception_clause) \ MONO_JIT_ICALL (mono_profiler_raise_gc_allocation) \ MONO_JIT_ICALL (mono_profiler_raise_method_enter) \ +MONO_JIT_ICALL (mono_profiler_raise_method_samplepoint) \ MONO_JIT_ICALL (mono_profiler_raise_method_leave) \ MONO_JIT_ICALL (mono_profiler_raise_method_tail_call) \ MONO_JIT_ICALL (mono_resolve_generic_virtual_call) \ @@ -298,6 +299,7 @@ MONO_JIT_ICALL (mono_throw_platform_not_supported) \ MONO_JIT_ICALL (mono_throw_invalid_program) \ MONO_JIT_ICALL (mono_throw_type_load) \ MONO_JIT_ICALL (mono_trace_enter_method) \ +MONO_JIT_ICALL (mono_trace_samplepoint_method) \ MONO_JIT_ICALL (mono_trace_leave_method) \ MONO_JIT_ICALL (mono_trace_tail_method) \ MONO_JIT_ICALL (mono_upgrade_remote_class_wrapper) \ diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 2bc048ca8d5fe7..79d0b6d6295966 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -3891,6 +3891,24 @@ interp_ldvirtftn_delegate (gpointer arg, MonoDelegate *del) return imethod_to_ftnptr (imethod, need_unbox); } +#define INTERP_PROFILER_RAISE(name_lower, name_upper) \ + if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \ + (frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \ + MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\ + prof_ctx->interp_frame = frame;\ + prof_ctx->method = frame->imethod->method; \ + if (!is_void) \ + prof_ctx->return_value = frame->retval; \ + if (flag & TRACING_FLAG) \ + mono_trace_ ## name_lower ## _method (frame->imethod->method, frame->imethod->jinfo, prof_ctx); \ + if (flag & PROFILING_FLAG) \ + MONO_PROFILER_RAISE (method_ ## name_lower, (frame->imethod->method, prof_ctx)); \ + g_free (prof_ctx); \ + } else if ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower)) { \ + MONO_PROFILER_RAISE (method_ ## name_lower, (frame->imethod->method, NULL)); \ + } + + /* * If CLAUSE_ARGS is non-null, start executing from it. * The ERROR argument is used to avoid declaring an error object for every interp frame, its not used @@ -7610,24 +7628,19 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; MINT_IN_CASE(MINT_PROF_ENTER) { guint16 flag = ip [1]; ip += 2; - - if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_enter) && - (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_ENTER_CONTEXT))) { - MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1); - prof_ctx->interp_frame = frame; - prof_ctx->method = frame->imethod->method; - // FIXME push/pop LMF - if (flag & TRACING_FLAG) - mono_trace_enter_method (frame->imethod->method, frame->imethod->jinfo, prof_ctx); - if (flag & PROFILING_FLAG) - MONO_PROFILER_RAISE (method_enter, (frame->imethod->method, prof_ctx)); - g_free (prof_ctx); - } else if ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_enter)) { - MONO_PROFILER_RAISE (method_enter, (frame->imethod->method, NULL)); - } + gboolean is_void = TRUE; + // FIXME push/pop LMF + INTERP_PROFILER_RAISE(enter, ENTER); + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_PROF_SAMPLEPOINT) { + guint16 flag = ip [1]; + ip += 2; + gboolean is_void = TRUE; + // FIXME push/pop LMF + INTERP_PROFILER_RAISE(samplepoint, SAMPLEPOINT); MINT_IN_BREAK; } - MINT_IN_CASE(MINT_PROF_EXIT) MINT_IN_CASE(MINT_PROF_EXIT_VOID) { gboolean is_void = ip [0] == MINT_PROF_EXIT_VOID; @@ -7640,24 +7653,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; else frame->retval [0] = LOCAL_VAR (ip [1], stackval); } - - if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_leave) && - (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE_CONTEXT))) { - MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1); - prof_ctx->interp_frame = frame; - prof_ctx->method = frame->imethod->method; - if (!is_void) - prof_ctx->return_value = frame->retval; - // FIXME push/pop LMF - if (flag & TRACING_FLAG) - mono_trace_leave_method (frame->imethod->method, frame->imethod->jinfo, prof_ctx); - if (flag & PROFILING_FLAG) - MONO_PROFILER_RAISE (method_leave, (frame->imethod->method, prof_ctx)); - g_free (prof_ctx); - } else if ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_enter)) { - MONO_PROFILER_RAISE (method_leave, (frame->imethod->method, NULL)); - } - + INTERP_PROFILER_RAISE(leave, LEAVE); frame_data_allocator_pop (&context->data_stack, frame); goto exit_frame; } @@ -9126,7 +9122,7 @@ mono_jiterp_interp_entry (JiterpEntryData *_data, void *res) int params_size = get_arg_offset_fast (header.rmethod, NULL, header.params_count); // g_printf ("jiterp_interp_entry: rmethod=%d, params_count=%d, params_size=%d\n", header.rmethod, header.params_count, params_size); header.context->stack_pointer = (guchar*)ALIGN_TO ((guchar*)sp + params_size, MINT_STACK_ALIGNMENT); -; + g_assert (header.context->stack_pointer < header.context->stack_end); MONO_ENTER_GC_UNSAFE; @@ -9162,6 +9158,30 @@ mono_jiterp_get_polling_required_address () return &mono_polling_required; } +EMSCRIPTEN_KEEPALIVE void +mono_jiterp_prof_enter (InterpFrame *frame, guint16 *ip) +{ + gboolean is_void = TRUE; + guint16 flag = ip [1]; + INTERP_PROFILER_RAISE(enter, ENTER); +} + +EMSCRIPTEN_KEEPALIVE void +mono_jiterp_prof_samplepoint (InterpFrame *frame, guint16 *ip) +{ + guint16 flag = ip [1]; + gboolean is_void = TRUE; + INTERP_PROFILER_RAISE(samplepoint, SAMPLEPOINT); +} + +EMSCRIPTEN_KEEPALIVE void +mono_jiterp_prof_leave (InterpFrame *frame, guint16 *ip) +{ + gboolean is_void = ip [0] == MINT_PROF_EXIT_VOID; + guint16 flag = is_void ? ip [1] : ip [2]; + INTERP_PROFILER_RAISE(leave, LEAVE); +} + EMSCRIPTEN_KEEPALIVE void mono_jiterp_do_safepoint (InterpFrame *frame, guint16 *ip) { diff --git a/src/mono/mono/mini/interp/jiterpreter.c b/src/mono/mono/mini/interp/jiterpreter.c index cacc9041cab189..887038ca47fa64 100644 --- a/src/mono/mono/mini/interp/jiterpreter.c +++ b/src/mono/mono/mini/interp/jiterpreter.c @@ -977,7 +977,7 @@ mono_jiterp_free_method_data (MonoMethod *method, InterpMethod *imethod) JiterpreterOpcode *opcode = (JiterpreterOpcode *)p; guint32 trace_index = opcode->trace_index; need_extra_free = FALSE; - mono_jiterp_free_method_data_js (method, imethod, trace_index); + mono_wasm_free_method_data (method, imethod, trace_index); break; } } @@ -988,7 +988,7 @@ mono_jiterp_free_method_data (MonoMethod *method, InterpMethod *imethod) if (need_extra_free) { // HACK: Perform a single free operation to clear out any stuff from the jit queues // This will happen if we didn't encounter any jiterpreter traces in the method - mono_jiterp_free_method_data_js (method, imethod, 0); + mono_wasm_free_method_data (method, imethod, 0); } } diff --git a/src/mono/mono/mini/interp/jiterpreter.h b/src/mono/mono/mini/interp/jiterpreter.h index e9b3c67cb29374..078e10d77fa85c 100644 --- a/src/mono/mono/mini/interp/jiterpreter.h +++ b/src/mono/mono/mini/interp/jiterpreter.h @@ -163,7 +163,7 @@ mono_interp_invoke_wasm_jit_call_trampoline ( #ifdef __MONO_MINI_INTERPRETER_INTERNALS_H__ extern void -mono_jiterp_free_method_data_js ( +mono_wasm_free_method_data ( MonoMethod *method, InterpMethod *imethod, int trace_index ); @@ -202,6 +202,15 @@ mono_jiterp_get_polling_required_address (void); void mono_jiterp_do_safepoint (InterpFrame *frame, guint16 *ip); +void +mono_jiterp_prof_enter (InterpFrame *frame, guint16 *ip); + +void +mono_jiterp_prof_samplepoint (InterpFrame *frame, guint16 *ip); + +void +mono_jiterp_prof_leave (InterpFrame *frame, guint16 *ip); + void mono_jiterp_interp_entry (JiterpEntryData *_data, void *res); diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def index 43424be305ede3..d8742cf4ecb7be 100644 --- a/src/mono/mono/mini/interp/mintops.def +++ b/src/mono/mono/mini/interp/mintops.def @@ -794,6 +794,7 @@ OPDEF(MINT_MINF, "min_f", 4, 1, 2, MintOpNoArgs) OPDEF(MINT_MAXF, "max_f", 4, 1, 2, MintOpNoArgs) OPDEF(MINT_PROF_ENTER, "prof_enter", 2, 0, 0, MintOpShortInt) +OPDEF(MINT_PROF_SAMPLEPOINT, "prof_samplepoint", 2, 0, 0, MintOpShortInt) OPDEF(MINT_PROF_EXIT, "prof_exit", 5, 0, 1, MintOpShortAndInt) OPDEF(MINT_PROF_EXIT_VOID, "prof_exit_void", 2, 0, 0, MintOpNoArgs) OPDEF(MINT_PROF_COVERAGE_STORE, "prof_coverage_store", 5, 0, 0, MintOpLongInt) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index a948639a4bd3b8..573408c1e85e05 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -734,6 +734,18 @@ handle_branch (TransformData *td, int long_op, int offset) g_assert_not_reached (); /* Add exception checkpoint or safepoint for backward branches */ if (offset < 0) { + + InterpMethod *rtm = td->rtm; + guint16 samplepoint_profiling = 0; + if (mono_jit_trace_calls != NULL && mono_trace_eval (rtm->method)) + samplepoint_profiling |= TRACING_FLAG; + if (rtm->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT | MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT_CONTEXT )) + samplepoint_profiling |= PROFILING_FLAG; + if (samplepoint_profiling) { + interp_add_ins (td, MINT_PROF_SAMPLEPOINT); + td->last_ins->data [0] = samplepoint_profiling; + } + if (mono_threads_are_safepoints_enabled ()) interp_add_ins (td, MINT_SAFEPOINT); } @@ -5367,7 +5379,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, guint16 enter_profiling = 0; if (mono_jit_trace_calls != NULL && mono_trace_eval (method)) enter_profiling |= TRACING_FLAG; - if (rtm->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_ENTER) + if (rtm->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | MONO_PROFILER_CALL_INSTRUMENTATION_ENTER_CONTEXT)) enter_profiling |= PROFILING_FLAG; if (enter_profiling) { interp_add_ins (td, MINT_PROF_ENTER); @@ -5936,7 +5948,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, guint16 exit_profiling = 0; if (mono_jit_trace_calls != NULL && mono_trace_eval (method)) exit_profiling |= TRACING_FLAG; - if (rtm->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE) + if (rtm->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE_CONTEXT)) exit_profiling |= PROFILING_FLAG; if (exit_profiling) { /* This does the return as well */ diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index 85f566553fa55e..4fce6945132167 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -2068,6 +2068,22 @@ mono_get_exception_runtime_wrapped_checked (MonoObject *wrapped_exception_raw, M HANDLE_FUNCTION_RETURN_OBJ (ret); } +#define MONO_PROFILER_RAISE_EXCEPTION_CLAUSE(clause_flags) \ + if (G_UNLIKELY (mono_profiler_clauses_enabled ())) { \ + jit_tls->orig_ex_ctx_set = TRUE; \ + MONO_PROFILER_RAISE (exception_clause, (method, i, clause_flags , ex_obj)); \ + jit_tls->orig_ex_ctx_set = FALSE; \ + } + +#define MONO_PROFILER_RAISE_EXCEPTION_LEAVE \ + if (MONO_PROFILER_ENABLED (method_exception_leave) && \ + mono_profiler_get_call_instrumentation_flags (method) & MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE) { \ + jit_tls->orig_ex_ctx_set = TRUE; \ + MONO_PROFILER_RAISE (method_exception_leave, (method, ex_obj)); \ + jit_tls->orig_ex_ctx_set = FALSE; \ + } + + /** * mono_handle_exception_internal: * \param ctx saved processor state @@ -2437,11 +2453,9 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu * catch portion of this EH clause, pass MONO_EXCEPTION_CLAUSE_NONE explicitly * instead of ei->flags. */ - if (G_UNLIKELY (mono_profiler_clauses_enabled ())) { - jit_tls->orig_ex_ctx_set = TRUE; - MONO_PROFILER_RAISE (exception_clause, (method, i, MONO_EXCEPTION_CLAUSE_NONE, ex_obj)); - jit_tls->orig_ex_ctx_set = FALSE; - } + MONO_PROFILER_RAISE_EXCEPTION_CLAUSE ( MONO_EXCEPTION_CLAUSE_NONE ); + + MONO_PROFILER_RAISE_EXCEPTION_LEAVE; mini_set_abort_threshold (&frame); @@ -2493,22 +2507,17 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu if (mono_trace_is_enabled () && mono_trace_eval (method)) g_print ("EXCEPTION: fault clause %d of %s\n", i, mono_method_full_name (method, TRUE)); - if (G_UNLIKELY (mono_profiler_clauses_enabled ())) { - jit_tls->orig_ex_ctx_set = TRUE; - MONO_PROFILER_RAISE (exception_clause, (method, i, (MonoExceptionEnum)ei->flags, ex_obj)); - jit_tls->orig_ex_ctx_set = FALSE; - } + MONO_PROFILER_RAISE_EXCEPTION_CLAUSE ((MonoExceptionEnum)ei->flags); + + MONO_PROFILER_RAISE_EXCEPTION_LEAVE; } if (ei->flags == MONO_EXCEPTION_CLAUSE_FINALLY) { if (mono_trace_is_enabled () && mono_trace_eval (method)) g_print ("EXCEPTION: finally clause %d of %s\n", i, mono_method_full_name (method, TRUE)); - if (G_UNLIKELY (mono_profiler_clauses_enabled ())) { - jit_tls->orig_ex_ctx_set = TRUE; - MONO_PROFILER_RAISE (exception_clause, (method, i, (MonoExceptionEnum)ei->flags, ex_obj)); - jit_tls->orig_ex_ctx_set = FALSE; - } + MONO_PROFILER_RAISE_EXCEPTION_CLAUSE ((MonoExceptionEnum)ei->flags); + MONO_PROFILER_RAISE_EXCEPTION_LEAVE; } if (ei->flags == MONO_EXCEPTION_CLAUSE_FAULT || ei->flags == MONO_EXCEPTION_CLAUSE_FINALLY) { mono_set_lmf (lmf); @@ -2558,13 +2567,6 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu } } - if (MONO_PROFILER_ENABLED (method_exception_leave) && - mono_profiler_get_call_instrumentation_flags (method) & MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE) { - jit_tls->orig_ex_ctx_set = TRUE; - MONO_PROFILER_RAISE (method_exception_leave, (method, ex_obj)); - jit_tls->orig_ex_ctx_set = FALSE; - } - *ctx = new_ctx; } diff --git a/src/mono/mono/mini/mini-profiler.c b/src/mono/mono/mini/mini-profiler.c index 74f997f5bf5dee..58b83a0d798456 100644 --- a/src/mono/mono/mini/mini-profiler.c +++ b/src/mono/mono/mini/mini-profiler.c @@ -76,12 +76,12 @@ can_encode_method_ref (MonoMethod *method) void mini_profiler_emit_enter (MonoCompile *cfg) { - gboolean trace = mono_jit_trace_calls != NULL && mono_trace_eval (cfg->method); - - if ((!MONO_CFG_PROFILE (cfg, ENTER) || cfg->current_method != cfg->method || (cfg->compile_aot && !can_encode_method_ref (cfg->method))) && !trace) + if (cfg->current_method != cfg->method) return; - if (cfg->current_method != cfg->method) + gboolean trace = mono_jit_trace_calls != NULL && mono_trace_eval (cfg->method); + + if (!trace && (!(MONO_CFG_PROFILE (cfg, ENTER) || MONO_CFG_PROFILE (cfg, ENTER_CONTEXT)) || (cfg->compile_aot && !can_encode_method_ref (cfg->method)))) return; MonoInst *iargs [3]; @@ -101,12 +101,39 @@ mini_profiler_emit_enter (MonoCompile *cfg) mono_emit_jit_icall (cfg, mono_profiler_raise_method_enter, iargs); } +void +mini_profiler_emit_samplepoint (MonoCompile *cfg) +{ + if (cfg->current_method != cfg->method) + return; + + gboolean trace = mono_jit_trace_calls != NULL && mono_trace_eval (cfg->method); + + if (!trace && (!(MONO_CFG_PROFILE (cfg, SAMPLEPOINT) || MONO_CFG_PROFILE (cfg, SAMPLEPOINT_CONTEXT)) || (cfg->compile_aot && !can_encode_method_ref (cfg->method)))) + return; + + MonoInst *iargs [3]; + + EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method); + EMIT_NEW_PCONST (cfg, iargs [1], NULL); + iargs [2] = emit_fill_call_ctx (cfg, iargs [0], NULL); + + /* void mono_profiler_raise_method_samplepoint (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) */ + if (trace) + mono_emit_jit_icall (cfg, mono_trace_samplepoint_method, iargs); + else + mono_emit_jit_icall (cfg, mono_profiler_raise_method_samplepoint, iargs); +} + void mini_profiler_emit_leave (MonoCompile *cfg, MonoInst *ret) { + if (cfg->current_method != cfg->method) + return; + gboolean trace = mono_jit_trace_calls != NULL && mono_trace_eval (cfg->method); - if (!MONO_CFG_PROFILE (cfg, LEAVE) || cfg->current_method != cfg->method || (cfg->compile_aot && !can_encode_method_ref (cfg->method))) + if (!trace && (!(MONO_CFG_PROFILE (cfg, LEAVE) || MONO_CFG_PROFILE (cfg, LEAVE_CONTEXT)) || (cfg->compile_aot && !can_encode_method_ref (cfg->method)))) return; MonoInst *iargs [3]; @@ -129,9 +156,12 @@ mini_profiler_emit_leave (MonoCompile *cfg, MonoInst *ret) void mini_profiler_emit_tail_call (MonoCompile *cfg, MonoMethod *target) { + if (cfg->current_method != cfg->method) + return; + gboolean trace = mono_jit_trace_calls != NULL && mono_trace_eval (cfg->method); - if ((!MONO_CFG_PROFILE (cfg, TAIL_CALL) || cfg->current_method != cfg->method) && !trace) + if (!trace && (!MONO_CFG_PROFILE (cfg, TAIL_CALL) || (cfg->compile_aot && !can_encode_method_ref (cfg->method)))) return; g_assert (cfg->current_method == cfg->method); diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index bcd7754da660a6..eb2ff7d4df81ae 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -4940,11 +4940,13 @@ register_icalls (void) * so on. */ register_icall (mono_profiler_raise_method_enter, mono_icall_sig_void_ptr_ptr, TRUE); + register_icall (mono_profiler_raise_method_samplepoint, mono_icall_sig_void_ptr_ptr, TRUE); register_icall (mono_profiler_raise_method_leave, mono_icall_sig_void_ptr_ptr, TRUE); 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_ptr, TRUE); + register_icall (mono_trace_samplepoint_method, mono_icall_sig_void_ptr_ptr_ptr, TRUE); register_icall (mono_trace_leave_method, mono_icall_sig_void_ptr_ptr_ptr, TRUE); register_icall (mono_trace_tail_method, mono_icall_sig_void_ptr_ptr_ptr, TRUE); g_assert (mono_get_lmf_addr == mono_tls_get_lmf_addr); diff --git a/src/mono/mono/mini/mini.c b/src/mono/mono/mini/mini.c index 58667ee20c5666..203f84349eddce 100644 --- a/src/mono/mono/mini/mini.c +++ b/src/mono/mono/mini/mini.c @@ -2753,31 +2753,9 @@ insert_safepoint (MonoCompile *cfg, MonoBasicBlock *bblock) } } -/* -This code inserts safepoints into managed code at important code paths. -Those are: - --the first basic block --landing BB for exception handlers --loop body starts. - -*/ -static void -insert_safepoints (MonoCompile *cfg) +static bool +skip_insert_safepoint (MonoCompile *cfg) { - MonoBasicBlock *bb; - - g_assert (mini_safepoints_enabled ()); - - if (COMPILE_LLVM (cfg)) { - if (!cfg->llvm_only) { - /* We rely on LLVM's safepoints insertion capabilities. */ - if (cfg->verbose_level > 1) - printf ("SKIPPING SAFEPOINTS for code compiled with LLVM\n"); - return; - } - } - if (cfg->method->wrapper_type == MONO_WRAPPER_MANAGED_TO_NATIVE) { WrapperInfo *info = mono_marshal_get_wrapper_info (cfg->method); /* These wrappers are called from the wrapper for the polling function, leading to potential stack overflow */ @@ -2787,14 +2765,14 @@ insert_safepoints (MonoCompile *cfg) info->d.icall.jit_icall_id == MONO_JIT_ICALL_mono_threads_exit_gc_safe_region_unbalanced)) { if (cfg->verbose_level > 1) printf ("SKIPPING SAFEPOINTS for the polling function icall\n"); - return; + return TRUE; } } if (cfg->method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED) { if (cfg->verbose_level > 1) printf ("SKIPPING SAFEPOINTS for native-to-managed wrappers.\n"); - return; + return TRUE; } if (cfg->method->wrapper_type == MONO_WRAPPER_OTHER) { @@ -2804,13 +2782,44 @@ insert_safepoints (MonoCompile *cfg) /* These wrappers shouldn't do any icalls */ if (cfg->verbose_level > 1) printf ("SKIPPING SAFEPOINTS for interp-in wrappers.\n"); - return; + return TRUE; } } if (cfg->method->wrapper_type == MONO_WRAPPER_WRITE_BARRIER) { if (cfg->verbose_level > 1) printf ("SKIPPING SAFEPOINTS for write barrier wrappers.\n"); + return TRUE; + } + return FALSE; +} + +/* +This code inserts safepoints into managed code at important code paths. +Those are: + +-the first basic block +-landing BB for exception handlers +-loop body starts. + +*/ +static void +insert_safepoints (MonoCompile *cfg) +{ + MonoBasicBlock *bb; + + g_assert (mini_safepoints_enabled ()); + + if (COMPILE_LLVM (cfg)) { + if (!cfg->llvm_only) { + /* We rely on LLVM's safepoints insertion capabilities. */ + if (cfg->verbose_level > 1) + printf ("SKIPPING SAFEPOINTS for code compiled with LLVM\n"); + return; + } + } + + if (skip_insert_safepoint (cfg)) { return; } @@ -2840,6 +2849,62 @@ insert_safepoints (MonoCompile *cfg) } +static void +insert_samplepoint (MonoCompile *cfg, MonoBasicBlock *bblock) +{ + if (cfg->verbose_level > 1) + printf ("ADDING SAMPLE POINT TO BB%d\n", bblock->block_num); + + // store the previous instruction list and make the bb empty + MonoInst *begin = bblock->code; + MonoInst *end = bblock->last_ins; + bblock->code = bblock->last_ins = NULL; + + // insert the samplepoint at the start of the bb + MonoBasicBlock *prev_cbb = cfg->cbb; + cfg->cbb = bblock; + mini_profiler_emit_samplepoint (cfg); + cfg->cbb = prev_cbb; + + // append the previous instruction list to the end of the bb + if (begin) { + if(bblock->code) { + begin->prev = bblock->last_ins; + bblock->last_ins->next = begin; + bblock->last_ins = end; + } else { + bblock->code = begin; + bblock->last_ins = end; + } + } +} + +static void +insert_samplepoints (MonoCompile *cfg) +{ + MonoBasicBlock *bb; + + if (skip_insert_safepoint (cfg)) { + return; + } + + if (cfg->verbose_level > 1) + printf ("INSERTING SAMPLEPOINTS\n"); + if (cfg->verbose_level > 2) + mono_print_code (cfg, "BEFORE SAMPLEPOINTS"); + + for (bb = cfg->bb_entry->next_bb; bb; bb = bb->next_bb) { + if (bb->loop_body_start || (bb->flags & BB_EXCEPTION_HANDLER)) { + insert_samplepoint (cfg, bb); + } + } + + // we don't need samplepoint event on method entry, there is already a method entry event + + if (cfg->verbose_level > 2) + mono_print_code (cfg, "AFTER SAMPLEPOINTS"); + +} static void mono_insert_branches_between_bblocks (MonoCompile *cfg) @@ -3391,7 +3456,8 @@ mini_method_compile (MonoMethod *method, guint32 opts, JitFlags flags, int parts if (trace) cfg->prof_flags = (MonoProfilerCallInstrumentationFlags)( MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | MONO_PROFILER_CALL_INSTRUMENTATION_ENTER_CONTEXT | - MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE_CONTEXT); + MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE_CONTEXT | + MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT | MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT_CONTEXT); /* The debugger has no liveness information, so avoid sharing registers/stack slots */ if (mini_debug_options.mdb_optimizations || MONO_CFG_PROFILE_CALL_CONTEXT (cfg)) { @@ -3684,6 +3750,11 @@ mini_method_compile (MonoMethod *method, guint32 opts, JitFlags flags, int parts mono_cfg_dump_ir (cfg, "insert_safepoints"); } + if (MONO_CFG_PROFILE (cfg, SAMPLEPOINT) || MONO_CFG_PROFILE (cfg, SAMPLEPOINT_CONTEXT)) { + MONO_TIME_TRACK (mono_jit_stats.jit_insert_samplepoints, insert_samplepoints (cfg)); + mono_cfg_dump_ir (cfg, "insert_samplepoints"); + } + /* after method_to_ir */ if (parts == 1) { if (MONO_METHOD_COMPILE_END_ENABLED ()) @@ -4379,6 +4450,7 @@ mini_jit_init (void) mono_counters_register ("JIT/compile_dominator_info", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_compile_dominator_info); mono_counters_register ("JIT/compute_natural_loops", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_compute_natural_loops); mono_counters_register ("JIT/insert_safepoints", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_insert_safepoints); + mono_counters_register ("JIT/insert_samplepoints", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_insert_samplepoints); mono_counters_register ("JIT/ssa_compute", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_ssa_compute); mono_counters_register ("JIT/ssa_cprop", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_ssa_cprop); mono_counters_register ("JIT/ssa_deadce", MONO_COUNTER_JIT | MONO_COUNTER_LONG | MONO_COUNTER_TIME, &mono_jit_stats.jit_ssa_deadce); diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 97196e7e125cb4..9d4fb4fe2b109e 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -1679,7 +1679,7 @@ typedef struct { G_UNLIKELY ((cfg)->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_ ## flag) #define MONO_CFG_PROFILE_CALL_CONTEXT(cfg) \ - (MONO_CFG_PROFILE (cfg, ENTER_CONTEXT) || MONO_CFG_PROFILE (cfg, LEAVE_CONTEXT)) + (MONO_CFG_PROFILE (cfg, ENTER_CONTEXT) || MONO_CFG_PROFILE (cfg, SAMPLEPOINT_CONTEXT) || MONO_CFG_PROFILE (cfg, LEAVE_CONTEXT)) typedef enum { MONO_CFG_HAS_ALLOCA = 1 << 0, @@ -1740,6 +1740,7 @@ typedef struct { gint64 jit_compile_dominator_info; gint64 jit_compute_natural_loops; gint64 jit_insert_safepoints; + gint64 jit_insert_samplepoints; gint64 jit_ssa_compute; gint64 jit_ssa_cprop; gint64 jit_ssa_deadce; @@ -2125,6 +2126,7 @@ mono_bb_last_inst (MonoBasicBlock *bb, int filter) /* profiler support */ void mini_add_profiler_argument (const char *desc); void mini_profiler_emit_enter (MonoCompile *cfg); +void mini_profiler_emit_samplepoint (MonoCompile *cfg); void mini_profiler_emit_leave (MonoCompile *cfg, MonoInst *ret); void mini_profiler_emit_tail_call (MonoCompile *cfg, MonoMethod *target); void mini_profiler_emit_call_finally (MonoCompile *cfg, MonoMethodHeader *header, unsigned char *ip, guint32 index, MonoExceptionClause *clause); diff --git a/src/mono/mono/mini/trace.c b/src/mono/mono/mini/trace.c index c0b175fa7055ce..fd82208184be9d 100644 --- a/src/mono/mono/mini/trace.c +++ b/src/mono/mono/mini/trace.c @@ -139,8 +139,22 @@ frame_kind (MonoJitInfo *ji) return 'c'; } +static void mono_trace_enter_method_impl (const char *prefix, MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx); + void mono_trace_enter_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) +{ + mono_trace_enter_method_impl ("ENTER:%c %s(", method, ji, ctx); +} + +void +mono_trace_samplepoint_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) +{ + mono_trace_enter_method_impl ("SAMPLEPOINT:%c %s(", method, ji, ctx); +} + +static void +mono_trace_enter_method_impl (const char *prefix, MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx) { int i; MonoClass *klass; @@ -162,7 +176,7 @@ mono_trace_enter_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallCo if (!ji) ji = mini_jit_info_table_find ((char *)MONO_RETURN_ADDRESS ()); - printf ("ENTER:%c %s(", frame_kind (ji), fname); + printf (prefix, frame_kind (ji), fname); g_free (fname); sig = mono_method_signature_internal (method); diff --git a/src/mono/mono/mini/trace.h b/src/mono/mono/mini/trace.h index a286e574e1f4b4..5b07a74a087bb5 100644 --- a/src/mono/mono/mini/trace.h +++ b/src/mono/mono/mini/trace.h @@ -12,6 +12,10 @@ ICALL_EXPORT void mono_trace_enter_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx); +ICALL_EXPORT +void +mono_trace_samplepoint_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx); + ICALL_EXPORT void mono_trace_leave_method (MonoMethod *method, MonoJitInfo *ji, MonoProfilerCallContext *ctx); diff --git a/src/mono/mono/profiler/browser.c b/src/mono/mono/profiler/browser.c index 2882f0188ba59b..9fd0eebdd164c1 100644 --- a/src/mono/mono/profiler/browser.c +++ b/src/mono/mono/profiler/browser.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,61 +24,298 @@ #include #include #include +#include struct _MonoProfiler { gboolean verbose; }; static MonoProfiler browser_profiler; +static double desired_sample_interval_ms; +static MonoCallSpec callspec; -#ifdef HOST_WASM +#ifdef HOST_BROWSER -void -mono_wasm_profiler_enter (); +typedef struct _ProfilerStackFrame ProfilerStackFrame; -void -mono_wasm_profiler_leave (MonoMethod *method); +struct _ProfilerStackFrame { + MonoMethod *method; + gpointer interp_frame; + double start; + bool should_record; +}; + +enum { MAX_STACK_DEPTH = 600 }; +static ProfilerStackFrame profiler_stack_frames[MAX_STACK_DEPTH]; +// -1 means empty stack, we keep counting even after MAX_STACK_DEPTH is reached +static int top_stack_frame_index; + +static double last_sample_time; +static int prev_skips_per_period; +static int skips_per_period; +static int sample_skip_counter; + +double mono_wasm_profiler_now (); +void mono_wasm_profiler_record (MonoMethod *method, double start); + +static bool should_record_frame (double now) +{ + if (sample_skip_counter < skips_per_period) { + return FALSE; + } + + // timer resolution in non-isolated contexts: 100 microseconds (decimal number) + double ms_since_last_sample = now - last_sample_time; + + if (desired_sample_interval_ms > 0 && last_sample_time != 0) { + // recalculate ideal number of skips per period + double skips_per_ms = ((double)sample_skip_counter) / ms_since_last_sample; + double newskips_per_period = (skips_per_ms * ((double)desired_sample_interval_ms)); + skips_per_period = ((newskips_per_period + ((double)sample_skip_counter) + ((double)prev_skips_per_period)) / 3); + prev_skips_per_period = sample_skip_counter; + } else { + skips_per_period = 0; + } + last_sample_time = now; + sample_skip_counter = 0; + + return TRUE; +} static void method_enter (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) { - mono_wasm_profiler_enter (); + sample_skip_counter++; + + top_stack_frame_index++; + if (top_stack_frame_index < MAX_STACK_DEPTH) { + ProfilerStackFrame *newframe = &profiler_stack_frames[top_stack_frame_index]; + double now = newframe->start = mono_wasm_profiler_now (); + newframe->should_record = should_record_frame (now); + newframe->method = method; + newframe->interp_frame = ctx ? ctx->interp_frame : NULL; + } } static void -method_leave (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) +method_samplepoint (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) { - mono_wasm_profiler_leave (method); + // enter/leave are not balanced, perhaps due to different callspecs between AOT and interpreter + g_assert(top_stack_frame_index >= 0); + + sample_skip_counter++; + + bool is_over = top_stack_frame_index >= MAX_STACK_DEPTH; + int top_index = is_over ? MAX_STACK_DEPTH - 1 : top_stack_frame_index; + ProfilerStackFrame *top_frame = &profiler_stack_frames[top_index]; + + if (!is_over) { + g_assertf(top_frame->method == method, "method_exc_leave: %d method mismatch top_frame %s != leave %s\n", top_stack_frame_index, mono_method_get_full_name (top_frame->method), mono_method_get_full_name (method)); + g_assertf(!ctx || !top_frame->interp_frame || top_frame->interp_frame == ctx->interp_frame, "method_exc_leave: %d interp_frame mismatch top_frame %p != leave %p\n", top_stack_frame_index, top_frame->interp_frame, ctx->interp_frame); + } + + if (!top_frame->should_record) + { + top_frame->should_record = should_record_frame (mono_wasm_profiler_now ()); + } } static void -tail_call (MonoProfiler *prof, MonoMethod *method, MonoMethod *target) +method_leave (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) { - method_leave (prof, method, NULL); + // enter/leave are not balanced, perhaps due to different callspecs between AOT and interpreter + g_assert(top_stack_frame_index >= 0); + + sample_skip_counter++; + + bool is_over = top_stack_frame_index >= MAX_STACK_DEPTH; + int top_index = is_over ? MAX_STACK_DEPTH - 1 : top_stack_frame_index; + ProfilerStackFrame *top_frame = &profiler_stack_frames[top_index]; + + if (!is_over) { + g_assertf(top_frame->method == method, "method_exc_leave: %d method mismatch top_frame %s != leave %s\n", top_stack_frame_index, mono_method_get_full_name (top_frame->method), mono_method_get_full_name (method)); + g_assertf(!ctx || !top_frame->interp_frame || top_frame->interp_frame == ctx->interp_frame, "method_exc_leave: %d interp_frame mismatch top_frame %p != leave %p\n", top_stack_frame_index, top_frame->interp_frame, ctx->interp_frame); + } + + // pop top frame + top_stack_frame_index--; + + if (top_frame->should_record || should_record_frame (mono_wasm_profiler_now ())) + { + // propagate should_record to parent, if any + if(top_index > 0) + { + profiler_stack_frames[top_index - 1].should_record = TRUE; + } + + mono_wasm_profiler_record (method, top_frame->start); + } } static void method_exc_leave (MonoProfiler *prof, MonoMethod *method, MonoObject *exc) +{ + // enter/leave are not balanced, perhaps due to different callspecs between AOT and interpreter + g_assert(top_stack_frame_index >= 0); + + sample_skip_counter++; + + bool is_over = top_stack_frame_index >= MAX_STACK_DEPTH; + int top_index = is_over ? MAX_STACK_DEPTH - 1 : top_stack_frame_index; + ProfilerStackFrame *top_frame = &profiler_stack_frames[top_index]; + + if (top_frame->should_record || should_record_frame (mono_wasm_profiler_now ())) + { + // propagate should_record to parent, if any + if(top_index > 0) + { + profiler_stack_frames[top_index - 1].should_record = TRUE; + } + + mono_wasm_profiler_record (method, top_frame->start); + } + + // pop top frame + top_stack_frame_index--; + + is_over = top_stack_frame_index >= MAX_STACK_DEPTH; + if (!is_over) { + top_index = is_over ? MAX_STACK_DEPTH - 1 : top_stack_frame_index; + top_frame = &profiler_stack_frames[top_index]; + g_assertf(top_frame->method == method, "method_exc_leave: %d method mismatch top_frame %s != leave %s\n", top_stack_frame_index, mono_method_get_full_name (top_frame->method), mono_method_get_full_name (method)); + } +} + +static void +tail_call (MonoProfiler *prof, MonoMethod *method, MonoMethod *target) { method_leave (prof, method, NULL); } -#endif /* HOST_WASM */ +#endif /* HOST_BROWSER */ static MonoProfilerCallInstrumentationFlags method_filter (MonoProfiler *prof, MonoMethod *method) { - // TODO filter by namespace ? - return MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | - MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | - MONO_PROFILER_CALL_INSTRUMENTATION_TAIL_CALL | - MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE; + if (callspec.len > 0 && + !mono_callspec_eval (method, &callspec)) + return MONO_PROFILER_CALL_INSTRUMENTATION_NONE; + + return MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT | + MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | + MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | + MONO_PROFILER_CALL_INSTRUMENTATION_TAIL_CALL | + MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE; } MONO_API void mono_profiler_init_browser (const char *desc); +static gboolean +match_option (const char *arg, const char *opt_name, const char **rval) +{ + if (rval) { + const char *end = strchr (arg, '='); + + *rval = NULL; + if (!end) + return !strcmp (arg, opt_name); + + if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > (ptrdiff_t)strlen (opt_name) + 1) + return FALSE; + *rval = end + 1; + return TRUE; + } else { + //FIXME how should we handle passing a value to an arg that doesn't expect it? + return !strcmp (arg, opt_name); + } +} + +static void +parse_arg (const char *arg) +{ + const char *val; + + if (match_option (arg, "callspec", &val)) { + if (!val) + val = ""; + if (val[0] == '\"') + ++val; + char *spec = g_strdup (val); + size_t speclen = strlen (val); + if (speclen > 0 && spec[speclen - 1] == '\"') + spec[speclen - 1] = '\0'; + char *errstr; + if (!mono_callspec_parse (spec, &callspec, &errstr)) { + mono_profiler_printf_err ("Could not parse callspec '%s': %s", spec, errstr); + g_free (errstr); + mono_callspec_cleanup (&callspec); + } + g_free (spec); + } + else if (match_option (arg, "interval", &val)) { + char *end; + desired_sample_interval_ms = strtod (val, &end); + } +} + +static void +parse_args (const char *desc) +{ + const char *p; + gboolean in_quotes = FALSE; + char quote_char = '\0'; + char *buffer = g_malloc (strlen (desc) + 1); + int buffer_pos = 0; + + for (p = desc; *p; p++){ + switch (*p){ + case ',': + if (!in_quotes) { + if (buffer_pos != 0){ + buffer [buffer_pos] = 0; + parse_arg (buffer); + buffer_pos = 0; + } + } else { + buffer [buffer_pos++] = *p; + } + break; + + case '\\': + if (p [1]) { + buffer [buffer_pos++] = p[1]; + p++; + } + break; + case '\'': + case '"': + if (in_quotes) { + if (quote_char == *p) + in_quotes = FALSE; + else + buffer [buffer_pos++] = *p; + } else { + in_quotes = TRUE; + quote_char = *p; + } + break; + default: + buffer [buffer_pos++] = *p; + break; + } + } + + if (buffer_pos != 0) { + buffer [buffer_pos] = 0; + parse_arg (buffer); + } + + g_free (buffer); +} + + /** * mono_profiler_init_browser: * the entry point @@ -85,6 +323,14 @@ mono_profiler_init_browser (const char *desc); void mono_profiler_init_browser (const char *desc) { + desired_sample_interval_ms = 10;// ms + memset (&callspec, 0, sizeof (MonoCallSpec)); + + // browser: + if (desc && desc [7] == ':') { + parse_args (desc + 8); + } + MonoProfilerHandle handle = mono_profiler_create (&browser_profiler); mono_profiler_set_call_instrumentation_filter_callback (handle, method_filter); @@ -93,11 +339,18 @@ mono_profiler_init_browser (const char *desc) return; } -#ifdef HOST_WASM +#ifdef HOST_BROWSER + top_stack_frame_index = -1; + last_sample_time = 0; + prev_skips_per_period = 1; + skips_per_period = 1; + sample_skip_counter = 1; + // install this only in production run, not in AOT run + mono_profiler_set_method_samplepoint_callback (handle, method_samplepoint); mono_profiler_set_method_enter_callback (handle, method_enter); mono_profiler_set_method_leave_callback (handle, method_leave); mono_profiler_set_method_tail_call_callback (handle, tail_call); mono_profiler_set_method_exception_leave_callback (handle, method_exc_leave); -#endif /* HOST_WASM */ +#endif /* HOST_BROWSER */ } diff --git a/src/mono/sample/wasm/browser-advanced/Program.cs b/src/mono/sample/wasm/browser-advanced/Program.cs index 093d8c40404194..a46f23c267953f 100644 --- a/src/mono/sample/wasm/browser-advanced/Program.cs +++ b/src/mono/sample/wasm/browser-advanced/Program.cs @@ -66,6 +66,36 @@ internal static int TestMeaning() return Add(half, half); } + [JSExport] + internal static void SillyLoop() + { + // this silly method will generate few sample points for the profiler + for (int i = 1; i <= 60; i ++) + { + try + { + for (int s = 0; s <= 60; s ++) + { + try + { + if (DateTime.UtcNow.Millisecond == s) + { + Console.WriteLine("Time is " + s); + } + } + catch(Exception e) + { + Console.WriteLine(e.Message); + } + } + } + catch(Exception e) + { + Console.WriteLine(e.Message); + } + } + } + [JSExport] internal static bool IsPrime(int number) { diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index fee308d1230f9d..88824cf2b9ec81 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -11,12 +11,19 @@ -s USE_CLOSURE_COMPILER=1 -s LEGACY_GL_EMULATION=1 -lGL -lSDL -lidbfs.js - <_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'" + <_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'" -h "Timing-Allow-Origin: *" -h "Cross-Origin-Opener-Policy: same-origin" -h "Cross-Origin-Embedder-Policy: require-corp" - browser; + + browser:callspec=N:Sample; ./ + + diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js index fe0331693d6de6..94598646652fc7 100644 --- a/src/mono/sample/wasm/browser-advanced/main.js +++ b/src/mono/sample/wasm/browser-advanced/main.js @@ -15,6 +15,8 @@ let testAbort = true; let testError = true; try { + console.log(`crossOriginIsolated: ${globalThis.crossOriginIsolated}`); + const originalFetch = globalThis.fetch; globalThis.fetch = (url, fetchArgs) => { console.log("fetching " + url); @@ -50,7 +52,10 @@ try { console.log('user code Module.onConfigLoaded'); // config is loaded and could be tweaked before the rest of the runtime startup sequence config.environmentVariables["MONO_LOG_LEVEL"] = "debug"; - config.browserProfilerOptions = {}; + config.browserProfilerOptions = { + sampleIntervalMs: 5.15, + callSpec: "N:Sample" // needs to match AOT profile + }; }, preInit: () => { console.log('user code Module.preInit'); }, preRun: () => { console.log('user code Module.preRun'); }, @@ -103,6 +108,8 @@ try { const deepMeaning = new Promise(resolve => setTimeout(() => resolve(meaning), 100)); exports.Sample.Test.PrintMeaning(deepMeaning); + exports.Sample.Test.SillyLoop(); + let exit_code = await runMain(config.mainAssemblyName, []); exit(exit_code); } diff --git a/src/mono/sample/wasm/simple-server/Program.cs b/src/mono/sample/wasm/simple-server/Program.cs index 00d5908311e4a4..692eaddd017725 100644 --- a/src/mono/sample/wasm/simple-server/Program.cs +++ b/src/mono/sample/wasm/simple-server/Program.cs @@ -380,6 +380,7 @@ private async void ServeAsync(HttpListenerContext context) // context.Response.AppendHeader("cache-control", "public, max-age=31536000"); context.Response.AppendHeader("Cross-Origin-Embedder-Policy", "require-corp"); context.Response.AppendHeader("Cross-Origin-Opener-Policy", "same-origin"); + context.Response.AppendHeader("Timing-Allow-Origin", "*"); context.Response.AppendHeader("ETag", fc.hash); // test download re-try diff --git a/src/native/public/mono/metadata/details/profiler-types.h b/src/native/public/mono/metadata/details/profiler-types.h index 966459e58e3337..8371766743550a 100644 --- a/src/native/public/mono/metadata/details/profiler-types.h +++ b/src/native/public/mono/metadata/details/profiler-types.h @@ -77,6 +77,14 @@ typedef enum { * Instrument exceptional method exits. */ MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE = 1 << 6, + /** + * Instrument method samplepoints + */ + MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT = 1 << 7, + /** + * Instrument method samplepoints with context. + */ + MONO_PROFILER_CALL_INSTRUMENTATION_SAMPLEPOINT_CONTEXT = 1 << 8, } MonoProfilerCallInstrumentationFlags; typedef MonoProfilerCallInstrumentationFlags (*MonoProfilerCallInstrumentationFilterCallback) (MonoProfiler *prof, MonoMethod *method); diff --git a/src/native/public/mono/metadata/profiler-events.h b/src/native/public/mono/metadata/profiler-events.h index 20300bff65f1c2..15363a23c6332d 100644 --- a/src/native/public/mono/metadata/profiler-events.h +++ b/src/native/public/mono/metadata/profiler-events.h @@ -71,6 +71,7 @@ MONO_PROFILER_EVENT_1(assembly_unloading, AssemblyLUnloading, MonoAssembly *, as MONO_PROFILER_EVENT_1(assembly_unloaded, AssemblyLUnloaded, MonoAssembly *, assembly) MONO_PROFILER_EVENT_2(method_enter, MethodEnter, MonoMethod *, method, MonoProfilerCallContext *, context) +MONO_PROFILER_EVENT_2(method_samplepoint, MethodSamplepoint, MonoMethod *, method, MonoProfilerCallContext *, context) MONO_PROFILER_EVENT_2(method_leave, MethodLeave, MonoMethod *, method, MonoProfilerCallContext *, context) MONO_PROFILER_EVENT_2(method_tail_call, MethodTailCall, MonoMethod *, method, MonoMethod *, target) MONO_PROFILER_EVENT_2(method_exception_leave, MethodExceptionLeave, MonoMethod *, method, MonoObject *, exception) diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 23f51f761ae630..9f9224fb4e721f 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -1165,14 +1165,15 @@ void register_aot_modules (void) }} } - {{profilers.Join(writer.NewLine, profiler => - $$$"""" + {{profilers.Join(writer.NewLine, p => { + var profiler = p.Split(':')[0]; + return $$$"""" void mono_profiler_init_{{{profiler}}} (const char *desc); EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_{{{profiler}}} (const char *desc) { mono_profiler_init_{{{profiler}}} (desc); } - """") + """";}) }} {{parsedAotMode switch