Skip to content
Draft
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
2 changes: 2 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ enum CorInfoHelpFunc
CORINFO_HELP_ALLOC_CONTINUATION_METHOD,
CORINFO_HELP_ALLOC_CONTINUATION_CLASS,

CORINFO_HELP_WASM_PROF_SAMPLEPOINT, // WASM only: cooperative EventPipe CPU-sampling samplepoint

CORINFO_HELP_COUNT,
};

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 0470bb0a-8e19-446c-a494-04f35ca0404c */
0x0470bb0a,
0x8e19,
0x446c,
{0xa4, 0x94, 0x04, 0xf3, 0x5c, 0xa0, 0x40, 0x4c}
constexpr GUID JITEEVersionIdentifier = { /* 1f960272-4271-4a63-8613-309367a41370 */
0x1f960272,
0x4271,
0x4a63,
{0x86, 0x13, 0x30, 0x93, 0x67, 0xa4, 0x13, 0x70}
};

#endif // JIT_EE_VERSIONING_GUID_H
8 changes: 8 additions & 0 deletions src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,14 @@
DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_METHOD)
DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_CLASS)

// WASM EventPipe CPU-sampling samplepoint. Native helper (no managed fallback), auto-wrapped in a
// PortableEntryPoint by getHelperFtnAddr. Never emitted on other targets; NULL keeps enum/table aligned.
#ifdef TARGET_WASM
JITHELPER(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, JIT_WasmProfSamplepoint, METHOD__NIL)
#else
JITHELPER(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, NULL, METHOD__NIL)
#endif

#undef JITHELPER
#undef DYNAMICJITHELPER
#undef JITHELPER
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/inc/readytorun.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// If you update this, ensure you run `git grep MINIMUM_READYTORUN_MAJOR_VERSION`
// and handle pending work.
#define READYTORUN_MAJOR_VERSION 27
#define READYTORUN_MINOR_VERSION 0x0000
#define READYTORUN_MINOR_VERSION 0x0001

#define MINIMUM_READYTORUN_MAJOR_VERSION 26

Expand Down Expand Up @@ -68,6 +68,7 @@
// R2R Version 26 changes ARM64 NativeVarInfo register encoding to include V0-V31
// R2R Version 26.1 adds READYTORUN_FIXUP_StoreMultiCallableAddrOfCode for storing a method's MultiCallableAddrOfCode into a location in the R2R image (used on WebAssembly)
// R2R Version 27 redefines READYTORUN_FIXUP_DeclaringTypeHandle to be encoded as a method signature instead of a pair of type signatures
// R2R Version 27.1 adds READYTORUN_HELPER_WasmProfSamplepoint (used on WebAssembly)

struct READYTORUN_CORE_HEADER
{
Expand Down Expand Up @@ -507,6 +508,7 @@ enum ReadyToRunHelper
READYTORUN_HELPER_InitClass = 0x116,
READYTORUN_HELPER_InitInstClass = 0x117,
READYTORUN_HELPER_R2RToInterpreter = 0x118,
READYTORUN_HELPER_WasmProfSamplepoint = 0x119,
};

#include "readytoruninstructionset.h"
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/readytorunhelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ HELPER(READYTORUN_HELPER_CheckedWriteBarrier_EBP, CORINFO_HELP_CHECKED_ASSIGN_
HELPER(READYTORUN_HELPER_PInvokeBegin, CORINFO_HELP_JIT_PINVOKE_BEGIN, )
HELPER(READYTORUN_HELPER_PInvokeEnd, CORINFO_HELP_JIT_PINVOKE_END, )
HELPER(READYTORUN_HELPER_GCPoll, CORINFO_HELP_POLL_GC, )
HELPER(READYTORUN_HELPER_WasmProfSamplepoint, CORINFO_HELP_WASM_PROF_SAMPLEPOINT, )
HELPER(READYTORUN_HELPER_ReversePInvokeEnter, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, )
HELPER(READYTORUN_HELPER_ReversePInvokeExit, CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, )

Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
// no codegen needed here.
break;

case GT_WASM_PROF_SAMPLEPOINT:
// Managed helper ABI: push the shadow stack pointer, then call the samplepoint helper.
GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetStackPointerRegIndex());
genEmitHelperCall(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, 0, EA_UNKNOWN);
break;

case GT_WASM_THROW_REF:
// Reload and rethrow the exnref stashed at the catch_ref landing.
{
Expand Down Expand Up @@ -3344,6 +3350,9 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize,
// RhBulkMoveWithWriteBarrier
HELPER_SIG(CORINFO_HELP_BULK_WRITEBARRIER, UNMANAGED, CORINFO_WASM_TYPE_VOID /* retval */, CORINFO_WASM_TYPE_I,
CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I);
// EventPipe CPU-sampling samplepoint (native helper wrapped in a PortableEntryPoint; managed ABI)
HELPER_SIG(CORINFO_HELP_WASM_PROF_SAMPLEPOINT, MANAGED, CORINFO_WASM_TYPE_VOID /* retval */,
CORINFO_WASM_TYPE_I /* sp */, CORINFO_WASM_TYPE_I /* pep */);
default:
JITDUMP("Helper '%s' has no hard-coded signature\n", m_compiler->eeGetMethodFullName(params.methHnd));
unreached();
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4983,6 +4983,11 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
m_pLowering->FinalizeOutgoingArgSpace();

#ifdef TARGET_WASM
// Insert EventPipe CPU-sampling samplepoints before the Virtual IP phase, so the
// per-block Virtual IP stores land ahead of each samplepoint.
//
DoPhase(this, PHASE_WASM_PROF_INSTRUMENT, &Compiler::fgWasmProfInstrument);

// Determine if a Virtual IP is needed and add code as needed to
// keep the Virtual IP updated.
//
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6971,6 +6971,7 @@ class Compiler
PhaseStatus fgWasmRepairTryEntries();
PhaseStatus fgWasmVirtualIP();
PhaseStatus fgWasmSpillRefs();
PhaseStatus fgWasmProfInstrument();
#ifdef DEBUG
void fgDumpWasmControlFlow();
void fgDumpWasmControlFlowDot();
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compphases.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ CompPhaseNameMacro(PHASE_WASM_TRANSFORM_SCCS, "Wasm transform sccs",
CompPhaseNameMacro(PHASE_WASM_CONTROL_FLOW, "Wasm control flow", false, -1, false)
CompPhaseNameMacro(PHASE_WASM_SPILL_REFS, "Wasm spill refs", false, -1, false)
CompPhaseNameMacro(PHASE_WASM_VIRTUAL_IP, "Wasm virtual IP", false, -1, false)
CompPhaseNameMacro(PHASE_WASM_PROF_INSTRUMENT, "Wasm profiler instrumentation", false, -1, false)

CompPhaseNameMacro(PHASE_ASYNC, "Transform async", false, -1, true)
CompPhaseNameMacro(PHASE_LCLVARLIVENESS, "Local var liveness", true, -1, false)
Expand Down
117 changes: 117 additions & 0 deletions src/coreclr/jit/fgwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3350,6 +3350,123 @@ void Compiler::fgWasmEhTransformTry(ArrayStack<BasicBlock*>* catchRetBlocks,
}
}

//-----------------------------------------------------------------------------
// fgWasmProfInstrument: insert EventPipe CPU-sampling samplepoints
//
// Returns:
// suitable phase status
//
// Notes:
// Mirrors the interpreter's INTOP_PROF_SAMPLEPOINT placement (see
// src/coreclr/interpreter/compiler.cpp): one samplepoint at method entry and
// one on every loop back-edge. Emitted only for methods matching the
// WasmPerformanceInstrumentation MethodSet filter (same key the interpreter
// uses, so both engines share the filter and the runtime skip counter).
//
// Each samplepoint is a GT_WASM_PROF_SAMPLEPOINT leaf that codegen lowers to a
// call to CORINFO_HELP_WASM_PROF_SAMPLEPOINT. We run just before fgWasmVirtualIP
// so the per-block Virtual IP store is inserted ahead of the samplepoint, giving
// the cooperative stack walk a correct Virtual IP for the sampled frame.
//
PhaseStatus Compiler::fgWasmProfInstrument()
{
// Codegen only supports single-threaded wasm today; the shared runtime skip
// counter and cooperative sampling both assume PERFTRACING_DISABLE_THREADS.
assert(!WASM_THREAD_SUPPORT);

if (!JitConfig.WasmPerformanceInstrumentation().contains(info.compMethodHnd, info.compClassHnd,
&info.compMethodInfo->args))
{
return PhaseStatus::MODIFIED_NOTHING;
}

// Insert a samplepoint at the beginning of a block. Placed after any Virtual IP
// store that fgWasmVirtualIP will later insert at the block's beginning.
auto insertSamplepoint = [this](BasicBlock* block) {
GenTree* const samplepoint = new (this, GT_WASM_PROF_SAMPLEPOINT) GenTree(GT_WASM_PROF_SAMPLEPOINT, TYP_VOID);
LIR::AsRange(block).InsertAtBeginning(samplepoint);
};

unsigned samplepointsAdded = 0;

// Method entry.
insertSamplepoint(fgFirstBB);
samplepointsAdded++;

// Loop back-edges: a DFS back edge (source -> target where target is an
// ancestor still on the DFS stack) identifies target as a loop header.
// We sample at each unique loop header, which executes on every iteration.
// Unlike fgHasCycleWithoutGCSafePoint we do NOT skip BBF_GC_SAFE_POINT blocks:
// the framework is not instrumented, so a loop calling only BCL methods would
// otherwise never sample.
BitVecTraits traits(fgBBNumMax + 1, this);
BitVec visited(BitVecOps::MakeEmpty(&traits));
BitVec onStack(BitVecOps::MakeEmpty(&traits));
BitVec headers(BitVecOps::MakeEmpty(&traits));

struct DfsFrame
{
BasicBlock* block;
unsigned nextSucc;
};

ArrayStack<DfsFrame> stack(getAllocator(CMK_ArrayStack));

for (BasicBlock* const start : Blocks())
{
if (BitVecOps::IsMember(&traits, visited, start->bbNum))
{
continue;
}

BitVecOps::AddElemD(&traits, visited, start->bbNum);
BitVecOps::AddElemD(&traits, onStack, start->bbNum);
stack.Push(DfsFrame{start, 0});

while (stack.Height() > 0)
{
DfsFrame& top = stack.TopRef();
BasicBlock* const block = top.block;
const unsigned numSuccs = block->NumSucc();

if (top.nextSucc < numSuccs)
{
BasicBlock* const succ = block->GetSucc(top.nextSucc++);

if (BitVecOps::IsMember(&traits, onStack, succ->bbNum))
{
// Back edge: succ is a loop header.
BitVecOps::AddElemD(&traits, headers, succ->bbNum);
}
else if (!BitVecOps::IsMember(&traits, visited, succ->bbNum))
{
BitVecOps::AddElemD(&traits, visited, succ->bbNum);
BitVecOps::AddElemD(&traits, onStack, succ->bbNum);
stack.Push(DfsFrame{succ, 0});
}
}
else
{
BitVecOps::RemoveElemD(&traits, onStack, block->bbNum);
stack.Pop();
}
}
}

for (BasicBlock* const block : Blocks())
{
// fgFirstBB already got an entry samplepoint above.
if ((block != fgFirstBB) && BitVecOps::IsMember(&traits, headers, block->bbNum))
{
insertSamplepoint(block);
samplepointsAdded++;
}
}

JITDUMP("Added %u Wasm profiler samplepoint(s)\n", samplepointsAdded);
return (samplepointsAdded > 0) ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
}

//-----------------------------------------------------------------------------
// fgWasmVirtualIP: set up virtual IP mapping for EH and calls
//
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12062,6 +12062,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node)
case GT_GCPOLL:
case GT_WASM_THROW_REF:
case GT_WASM_JEXCEPT:
case GT_WASM_PROF_SAMPLEPOINT:
m_state = -1;
return;

Expand Down Expand Up @@ -14252,6 +14253,7 @@ void Compiler::gtDispLeaf(GenTree* tree, IndentStack* indentStack)
case GT_GCPOLL:
case GT_WASM_THROW_REF:
case GT_WASM_JEXCEPT:
case GT_WASM_PROF_SAMPLEPOINT:
break;

case GT_CONTINUATION_MEMBER_OFFSET:
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/gtlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ GTNODE(SWIFT_ERROR_RET , GenTreeOp ,0,1,GTK_BINOP|GTK_NOVALUE) // Retu

GTNODE(WASM_JEXCEPT , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Special jump for Wasm exception handling
GTNODE(WASM_THROW_REF , GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Wasm rethrow host exception (exception is an implicit operand)
GTNODE(WASM_PROF_SAMPLEPOINT, GenTree ,0,0,GTK_LEAF|GTK_NOVALUE|DBK_NOTHIR) // Wasm EventPipe CPU-sampling samplepoint

//-----------------------------------------------------------------------------
// Nodes used by Lower to generate a closer CPU representation of other nodes
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,9 @@ RELEASE_CONFIG_INTEGER(JitWasmNyiToR2RUnsupported, "JitWasmNyiToR2RUnsupported",
CONFIG_STRING(JitR2RUnsupportedRange, "JitR2RUnsupportedRange")
// Enable processing methods with funclets. Set to 0 to bail to R2R unsupported before codegen.
RELEASE_CONFIG_INTEGER(JitWasmFunclets, "JitWasmFunclets", 1)
// Method filter for WASM performance instrumentation profiler. Uses standard MethodSet pattern format.
// Same key as the interpreter's InterpConfig.WasmPerformanceInstrumentation so both engines share it.
RELEASE_CONFIG_METHODSET(WasmPerformanceInstrumentation, "WasmPerformanceInstrumentation")
#endif // defined(TARGET_WASM)

// Allow to enregister locals with struct type.
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/liveness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2482,6 +2482,7 @@ void Liveness<TLiveness>::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR
case GT_SWIFT_ERROR_RET:
case GT_GCPOLL:
case GT_WASM_JEXCEPT:
case GT_WASM_PROF_SAMPLEPOINT:
// Never remove these nodes, as they are always side-effecting.
//
// NOTE: the only side-effect of some of these nodes (GT_CMP, GT_SUB_HI) is a write to the flags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ public enum ReadyToRunHelper
InitClass = 0x116,
InitInstClass = 0x117,
R2RToInterpreter = 0x118,
WasmProfSamplepoint = 0x119,

// **********************************************************************************************
//
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ which is the right helper to use to allocate an object of a given type. */
CORINFO_HELP_ALLOC_CONTINUATION_METHOD,
CORINFO_HELP_ALLOC_CONTINUATION_CLASS,

CORINFO_HELP_WASM_PROF_SAMPLEPOINT, // WASM only: cooperative EventPipe CPU-sampling samplepoint

CORINFO_HELP_COUNT,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,10 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum)
id = ReadyToRunHelper.GCPoll;
break;

case CorInfoHelpFunc.CORINFO_HELP_WASM_PROF_SAMPLEPOINT:
id = ReadyToRunHelper.WasmProfSamplepoint;
break;

case CorInfoHelpFunc.CORINFO_HELP_GETCURRENTMANAGEDTHREADID:
id = ReadyToRunHelper.GetCurrentManagedThreadId;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,10 @@ private void ParseHelper(StringBuilder builder)
builder.Append("GCPOLL");
break;

case ReadyToRunHelper.WasmProfSamplepoint:
builder.Append("WASM_PROF_SAMPLEPOINT");
break;

case ReadyToRunHelper.GetCurrentManagedThreadId:
builder.Append("GET_CURRENT_MANAGED_THREAD_ID");
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ void ep_rt_coreclr_sample_profiler_disabled(void)
s_skipsPerPeriod = 1;
}

// True only while an EventPipe CPU-sampling session is active. R2R samplepoints use this to
// skip the InlinedCallFrame anchor push on the overwhelmingly common no-session path.
extern "C" bool SamplingProfiler_IsActive()
{
LIMITED_METHOD_CONTRACT;
return s_currentSamplingEvent != nullptr && s_currentSamplingThread != nullptr;
}

// 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.
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/jitinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ EXTERN_C FCDECL1(PCODE, JIT_PatchpointForced, int ilOffset);

EXTERN_C FCDECL0(void, JIT_PollGC);

#ifdef TARGET_WASM
// WASM EventPipe CPU-sampling samplepoint helper (defined in vm/wasm/helpers.cpp).
EXTERN_C void JIT_WasmProfSamplepoint(uintptr_t callersStackPointer, PCODE portableEntryPointContext);
#endif // TARGET_WASM

#ifndef JIT_GetGCStaticBase
#define JIT_GetGCStaticBase NULL
#else
Expand Down
Loading
Loading