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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ Wasm.Build.Tests.MemoryTests
Wasm.Build.Tests.AppSettingsTests
Wasm.Build.Tests.Blazor.AppsettingsTests
Wasm.Build.Tests.DownloadThenInitTests
Wasm.Build.Tests.Blazor.EventPipeDiagnosticsTests
41 changes: 41 additions & 0 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ static const char *g_stackTypeString[] = { "I4", "I8", "R4", "R8", "O ", "VT", "

const char* CorInfoHelperToName(CorInfoHelpFunc helper);

#ifdef PERFTRACING_DISABLE_THREADS
bool InterpCompiler::s_samplingProfilerEnabled = false;
#ifdef TARGET_BROWSER
bool InterpCompiler::s_browserProfilerEnabled = false;
#endif
#endif // PERFTRACING_DISABLE_THREADS

#if MEASURE_MEM_ALLOC
#include <minipal/mutex.h>

Expand Down Expand Up @@ -2203,6 +2210,16 @@ InterpCompiler::InterpCompiler(COMP_HANDLE compHnd,
DWORD jitFlagsSize = m_compHnd->getJitFlags(&m_corJitFlags, sizeof(m_corJitFlags));
assert(jitFlagsSize == sizeof(m_corJitFlags));

#ifdef PERFTRACING_DISABLE_THREADS
m_emitSamplingProfiler = s_samplingProfilerEnabled
&& InterpConfig.WasmPerformanceInstrumentation().contains(compHnd, m_methodHnd, m_classHnd, &m_methodInfo->args);

#ifdef TARGET_BROWSER
m_emitBrowserProfiler = s_browserProfilerEnabled
&& InterpConfig.WasmPerformanceInstrumentation().contains(compHnd, m_methodHnd, m_classHnd, &m_methodInfo->args);
#endif
#endif // PERFTRACING_DISABLE_THREADS

#ifdef DEBUG
m_methodName = ::PrintMethodName(compHnd, m_classHnd, m_methodHnd, &m_methodInfo->args,
/* includeAssembly */ false,
Expand Down Expand Up @@ -2912,7 +2929,13 @@ void InterpCompiler::EmitBranch(InterpOpcode opcode, int32_t ilOffset)

// Backwards branch, emit safepoint
if (ilOffset < 0)
{
AddIns(INTOP_SAFEPOINT);
#ifdef PERFTRACING_DISABLE_THREADS
if (m_emitSamplingProfiler)
AddIns(INTOP_PROF_SAMPLEPOINT);
#endif // PERFTRACING_DISABLE_THREADS
}

InterpBasicBlock *pTargetBB = m_ppOffsetToBB[target];
if (pTargetBB == NULL)
Expand Down Expand Up @@ -5734,6 +5757,13 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo)
return;
}

#ifdef PERFTRACING_DISABLE_THREADS
#ifdef TARGET_BROWSER
if (m_emitBrowserProfiler)
AddIns(INTOP_PROF_LEAVE);
#endif // TARGET_BROWSER
#endif // PERFTRACING_DISABLE_THREADS

if (m_methodInfo->args.isAsyncCall())
{
// We're doing a standard return. Set the continuation return to NULL.
Expand Down Expand Up @@ -8257,6 +8287,17 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo)
// Safepoint at each method entry. This could be done as part of a call, rather than
// adding an opcode.
AddIns(INTOP_SAFEPOINT);
#ifdef PERFTRACING_DISABLE_THREADS
if (m_emitSamplingProfiler)
AddIns(INTOP_PROF_SAMPLEPOINT);
#ifdef TARGET_BROWSER
if (m_emitBrowserProfiler)
{
AddIns(INTOP_PROF_ENTER);
m_pLastNewIns->data[0] = GetMethodDataItemIndex(m_methodHnd);
}
#endif // TARGET_BROWSER
#endif // PERFTRACING_DISABLE_THREADS

if (m_continuationArgIndex != -1)
{
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/interpreter/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ class InterpCompiler
COMP_HANDLE m_compHnd;
CORINFO_METHOD_INFO* m_methodInfo;
CORJIT_FLAGS m_corJitFlags;
#ifdef PERFTRACING_DISABLE_THREADS
bool m_emitSamplingProfiler;
#ifdef TARGET_BROWSER
bool m_emitBrowserProfiler;
#endif
#endif // PERFTRACING_DISABLE_THREADS

void DeclarePointerIsClass(CORINFO_CLASS_HANDLE clsHnd)
{
Expand Down Expand Up @@ -1112,6 +1118,13 @@ class InterpCompiler

int32_t* GetCode(int32_t *pCodeSize);

#ifdef PERFTRACING_DISABLE_THREADS
static bool s_samplingProfilerEnabled;
#ifdef TARGET_BROWSER
static bool s_browserProfilerEnabled;
#endif
#endif // PERFTRACING_DISABLE_THREADS

#if MEASURE_MEM_ALLOC
// Memory statistics for profiling.
using InterpMemStats = MemStats<InterpMemKindTraits>;
Expand Down
12 changes: 12 additions & 0 deletions src/coreclr/interpreter/eeinterp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ extern "C" INTERP_API void jitStartup(ICorJitHost* jitHost)
InterpCompiler::initMemStats();
#endif

// Enable profiling instrumentation if DOTNET_WasmPerformanceInstrumentation is set.
// This must happen before any managed code is compiled so all methods get samplepoints.
if (!InterpConfig.WasmPerformanceInstrumentation().isEmpty())
{
#ifdef PERFTRACING_DISABLE_THREADS
InterpCompiler::s_samplingProfilerEnabled = true;
#ifdef TARGET_BROWSER
InterpCompiler::s_browserProfilerEnabled = true;
#endif
#endif // PERFTRACING_DISABLE_THREADS
}

g_interpInitialized = true;
}
/*****************************************************************************/
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/interpreter/inc/intops.def
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ OPDEF(INTOP_LDLOCA, "ldloca", 3, 1, 0, InterpOpInt)
OPDEF(INTOP_SWITCH, "switch", 0, 0, 1, InterpOpSwitch)

OPDEF(INTOP_SAFEPOINT, "safepoint", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_PROF_SAMPLEPOINT, "prof.samplepoint", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_PROF_ENTER, "prof.enter", 2, 0, 0, InterpOpMethodHandle)
OPDEF(INTOP_PROF_LEAVE, "prof.leave", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_BR, "br", 2, 0, 0, InterpOpBranch)

OPDEF(INTOP_BRFALSE_I4, "brfalse.i4", 3, 0, 1, InterpOpBranch)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/interpreter/interpconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ RELEASE_CONFIG_INTEGER(InterpMode, "InterpMode", 0); // Interpreter mode, one of
// 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0

RELEASE_CONFIG_INTEGER(DisplayMemStats, "JitMemStats", 0); // Display interpreter memory usage statistics (0=off, 1=summary, 2=detailed per-method)
RELEASE_CONFIG_METHODSET(WasmPerformanceInstrumentation, "WasmPerformanceInstrumentation") // Method filter for WASM performance instrumentation profiler. Uses standard MethodSet pattern format.

#undef CONFIG_STRING
#undef RELEASE_CONFIG_STRING
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,11 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM)
exceptionhandling.cpp
gcinfodecoder.cpp
)
if (CLR_CMAKE_TARGET_BROWSER)
list(APPEND VM_SOURCES_WKS_ARCH
${ARCH_SOURCES_DIR}/browserprofiler.cpp
)
endif(CLR_CMAKE_TARGET_BROWSER)
set(VM_SOURCES_WKS_GEN
${ARCH_SOURCES_DIR}/callhelpers-interp-to-managed.cpp
${ARCH_SOURCES_DIR}/callhelpers-reverse.cpp
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_custom_command(OUTPUT ${GEN_EVENTPIPE_SOURCES}

list(APPEND CORECLR_EVENTPIPE_SHIM_SOURCES
ep-rt-coreclr.cpp
ep-rt-coreclr-wasm-sampling.cpp
)

list(APPEND CORECLR_EVENTPIPE_SHIM_HEADERS
Expand Down
186 changes: 186 additions & 0 deletions src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-wasm-sampling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include <eventpipe/ep-rt-config.h>

#ifdef ENABLE_PERFTRACING

#include <eventpipe/ep-types.h>
#include <eventpipe/ep.h>
#include <eventpipe/ep-stack-contents.h>
#include <eventpipe/ep-sample-profiler.h>
#include <eventpipe/ep-rt.h>
#include "threadsuspend.h"

#ifdef TARGET_BROWSER
#include <emscripten.h>
#endif


// State for single-threaded EP sampling profiler.
// On single-threaded WASM, sampling is cooperative: the interpreter calls
// SamplingProfiler_OnSamplepoint() at backward branches (loop iterations)
// and method entry. A skip counter provides a fast path, and when the
// counter expires we check if enough wall-clock time has elapsed to
// justify taking a real sample.

static EventPipeEvent *s_currentSamplingEvent = nullptr;
static Thread *s_currentSamplingThread = nullptr;

// Adaptive sampling state.
// s_skipsPerPeriod is the number of samplepoints to skip between actual
// samples. It is adaptively adjusted so that samples occur approximately
// once per s_desiredSampleIntervalMs.
static double s_desiredSampleIntervalMs = 10.0;
static double s_lastSampleTimeMs = 0.0;
static int32_t s_prevSkipsPerPeriod = 1;
static int32_t s_skipsPerPeriod = 1;
static int32_t s_sampleSkipCounter = 0;

// Returns the current time in milliseconds using the same high-resolution
// timer as EventPipe timestamps (performance.now() on browser WASM).
static double GetCurrentTimeMs()
{
#ifdef TARGET_BROWSER
return emscripten_get_now();
#else
return (double)minipal_hires_ticks() * 1000.0 / (double)minipal_hires_tick_frequency();
#endif
}

// Recalculates s_skipsPerPeriod based on how long the last period actually
// took relative to the desired interval. This is the same exponential
// moving average approach used by Mono's ep-rt-mono-runtime-provider.c.
static void UpdateSampleFrequency()
{
double now = GetCurrentTimeMs();

if (s_lastSampleTimeMs > 0.0)
{
double elapsed = now - s_lastSampleTimeMs;
if (elapsed > 0.0)
{
double ratio = s_desiredSampleIntervalMs / elapsed;
int32_t newSkips = (int32_t)((double)s_prevSkipsPerPeriod * ratio);
if (newSkips < 1)
newSkips = 1;
if (newSkips > 10000)
newSkips = 10000;

s_prevSkipsPerPeriod = s_skipsPerPeriod;
s_skipsPerPeriod = newSkips;
}
}

s_lastSampleTimeMs = now;
}

#ifndef PERFTRACING_DISABLE_THREADS

// On multi-threaded builds the sample profiler runs on a dedicated
// thread, so these callbacks are no-ops.

void ep_rt_coreclr_sample_profiler_enabled(EventPipeEvent *samplingEvent)
{
}

void ep_rt_coreclr_sample_profiler_session_enabled(void)
{
}

void ep_rt_coreclr_sample_profiler_disabled(void)
{
}

#else // PERFTRACING_DISABLE_THREADS

// The following functions are EP runtime callbacks invoked only on
// single-threaded builds where the regular threaded sample profiler
// cannot run.

void ep_rt_coreclr_sample_profiler_enabled(EventPipeEvent *samplingEvent)
{
s_currentSamplingEvent = samplingEvent;
s_currentSamplingThread = GetThread();

s_desiredSampleIntervalMs = (double)ep_sample_profiler_get_sampling_rate() / 1000000.0;

s_lastSampleTimeMs = 0.0;
s_prevSkipsPerPeriod = 1;
s_skipsPerPeriod = 1;
s_sampleSkipCounter = 0;
}

void ep_rt_coreclr_sample_profiler_session_enabled(void)
{
if (s_currentSamplingEvent == nullptr || s_currentSamplingThread == nullptr)
return;

EventPipeStackContents stackContents;
EventPipeStackContents *pStackContents = ep_stack_contents_init(&stackContents);

uint32_t payloadData = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;

ep_write_sample_profile_event(
s_currentSamplingThread,
s_currentSamplingEvent,
s_currentSamplingThread,
pStackContents,
(uint8_t *)&payloadData,
sizeof(payloadData));

ep_stack_contents_fini(pStackContents);
}

void ep_rt_coreclr_sample_profiler_disabled(void)
{
s_currentSamplingEvent = nullptr;
s_currentSamplingThread = nullptr;
s_sampleSkipCounter = 0;
s_skipsPerPeriod = 1;
}

// Called from the interpreter's INTOP_PROF_SAMPLEPOINT handler.
// On single-threaded WASM this is the cooperative sampling entry point.
// On multi-threaded platforms the opcode is never emitted.
extern "C" void SamplingProfiler_OnSamplepoint()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;

if (++s_sampleSkipCounter < s_skipsPerPeriod)
return;

s_sampleSkipCounter = 0;

if (s_currentSamplingEvent == nullptr || s_currentSamplingThread == nullptr)
return;

UpdateSampleFrequency();

EventPipeStackContents stackContents;
EventPipeStackContents *pStackContents = ep_stack_contents_init(&stackContents);

if (ep_rt_coreclr_walk_managed_stack_for_thread(s_currentSamplingThread, pStackContents)
Comment thread
pavelsavara marked this conversation as resolved.
&& !ep_stack_contents_is_empty(pStackContents))
{
uint32_t payloadData = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;

ep_write_sample_profile_event(
s_currentSamplingThread,
s_currentSamplingEvent,
s_currentSamplingThread,
pStackContents,
(uint8_t *)&payloadData,
sizeof(payloadData));
}

ep_stack_contents_fini(pStackContents);
}

#endif // PERFTRACING_DISABLE_THREADS

#endif // ENABLE_PERFTRACING
10 changes: 7 additions & 3 deletions src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
#undef EP_ALIGN_UP
#define EP_ALIGN_UP(val,align) ALIGN_UP(val,align)

extern void ep_rt_coreclr_sample_profiler_enabled (EventPipeEvent *sampling_event);
extern void ep_rt_coreclr_sample_profiler_session_enabled (void);
extern void ep_rt_coreclr_sample_profiler_disabled (void);

static
inline
ep_rt_lock_handle_t *
Expand Down Expand Up @@ -610,7 +614,7 @@ void
ep_rt_sample_profiler_enabled (EventPipeEvent *sampling_event)
{
STATIC_CONTRACT_NOTHROW;
// no-op
ep_rt_coreclr_sample_profiler_enabled (sampling_event);
}

static
Expand All @@ -619,7 +623,7 @@ void
ep_rt_sample_profiler_session_enabled (void)
{
STATIC_CONTRACT_NOTHROW;
// no-op
ep_rt_coreclr_sample_profiler_session_enabled ();
}

static
Expand All @@ -628,7 +632,7 @@ void
ep_rt_sample_profiler_disabled (void)
{
STATIC_CONTRACT_NOTHROW;
// no-op
ep_rt_coreclr_sample_profiler_disabled ();
}

static
Expand Down
Loading
Loading