Skip to content

Call into PerfMap logic exclusively in preemptive mode#129019

Draft
davidwrighton wants to merge 3 commits into
dotnet:mainfrom
davidwrighton:CallPerfMapFromPREEMPTIVE
Draft

Call into PerfMap logic exclusively in preemptive mode#129019
davidwrighton wants to merge 3 commits into
dotnet:mainfrom
davidwrighton:CallPerfMapFromPREEMPTIVE

Conversation

@davidwrighton
Copy link
Copy Markdown
Member

This PR makes the PerfMap apis available on Windows, where our contracts work, and provides stub implementations which have the appropriate contracts. Then, I went through the codebase and found the places where we would need to transition to preemptive to make it all safe. As I found a number of the codepaths were actually GC_NOTRIGGER code paths, I pushed the work upstream until the logic primarily converted was all calls to entrypoint apis, and all non-allow creation calls to FindOrCreateAssociatedMethodDesc

This probably isn't suitable for backport to fix the customer reported issue, but is a reason to avoid implementing the deferred PerfMap writing logic, and simply implement the UNSAFE_ANYMODE lock that @hoyosjs first investigated as a fix, since its significant downsides can be mitigated with this work in future releases.

NOTE: this is draft as I'm expecting that I missed a few places where we need transition, and I need to investigate if there is a perf impact on reflection.

…e various techniques for getting an entrypoint, and FindAssociatedMethodDesc are called in preemptive code. This has the effect that the PerfMap::LogStubs logic is always called in preemptive mode, and thus taking the critical section in it won't trigger a GC transition.
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates CoreCLR VM call paths and contracts so PerfMap-related entrypoints (and a set of closely-related MethodDesc/slot resolution APIs) are only invoked in preemptive GC mode, and it removes many FEATURE_PERFMAP include/usage guards by providing always-available PerfMap APIs (with stubs when the feature is off).

Changes:

  • Introduces/uses conditional GCX_PREEMP / GCX_MAYBE_PREEMP transitions around stub generation and MethodDesc entrypoint resolution to satisfy PerfMap and related contract requirements.
  • Removes #ifdef FEATURE_PERFMAP wrappers at many call sites and centralizes “no-feature” behavior via stubbed PerfMap APIs.
  • Adjusts multiple VM contracts (e.g., MODE_ANYMODE_PREEMPTIVE) and refactors some call helpers / delegate QCall plumbing accordingly.

Reviewed changes

Copilot reviewed 44 out of 44 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/coreclr/vm/virtualcallstub.cpp Wraps stub generation with conditional preemptive transitions; unguards PerfMap logging.
src/coreclr/vm/threads.cpp Unguards PerfMap logging for write barrier copy stubs.
src/coreclr/vm/stublink.cpp Unguards PerfMap logging for emitted stubs.
src/coreclr/vm/runtimehandles.cpp Narrows cooperative transitions in QCalls; adds preemptive transition for wrapper-stub unwrap.
src/coreclr/vm/riscv64/stubs.cpp Unguards dynamic helper PerfMap logging macro.
src/coreclr/vm/reflectioninvocation.cpp Moves call-target retrieval into a preemptive region.
src/coreclr/vm/readytoruninfo.cpp Unguards PerfMap logging for dynamic helper precodes.
src/coreclr/vm/qcall.h Adds ObjectHandleOnStack::IsNull() helper.
src/coreclr/vm/prestub.cpp Unguards PerfMap logging; adds preemptive transition in dynamic helper worker path.
src/coreclr/vm/precode.cpp Unguards PerfMap logging for various precode allocations.
src/coreclr/vm/perfmap.h Adds stub PerfMap class under !FEATURE_PERFMAP; keeps real implementation under feature.
src/coreclr/vm/methodtable.inl Changes GetMethodDescForSlot contract to MODE_PREEMPTIVE.
src/coreclr/vm/methodtable.cpp Shifts several contracts to MODE_PREEMPTIVE; adds local coop regions for managed ref access.
src/coreclr/vm/method.hpp Removes declaration of FindOrCreateExactClassMethod.
src/coreclr/vm/method.cpp Broad contract/mode changes and added coop subregions for OBJECTREF access.
src/coreclr/vm/loongarch64/stubs.cpp Unguards dynamic helper PerfMap logging macro.
src/coreclr/vm/jitinterface.cpp Unguards PerfMap header include.
src/coreclr/vm/jithelpers.cpp Adds preemptive transition before virtualized call target resolution.
src/coreclr/vm/interpexec.cpp Adds preemptive transitions around virtualized MethodDesc resolution in interpreter.
src/coreclr/vm/i386/cgenx86.cpp Unguards dynamic helper PerfMap logging macro.
src/coreclr/vm/genmeth.cpp Removes helper API; adjusts contracts for instantiation/associated MethodDesc creation.
src/coreclr/vm/fptrstubs.cpp Adds preemptive transition earlier in FuncPtr stub creation path.
src/coreclr/vm/eventing/eventpipe/ds-rt-coreclr.h Updates contracts around perfmap enable path.
src/coreclr/vm/dllimportcallback.cpp Unguards PerfMap logging and updates mode contracts for UM entry thunk paths.
src/coreclr/vm/dispatchinfo.cpp Reorders GC mode transitions; shifts GetMemberInfoMap contract to preemptive.
src/coreclr/vm/customattribute.cpp Precomputes ctor call target in preemptive mode and uses new callsite ctor.
src/coreclr/vm/corhost.cpp Wraps GetSingleCallableAddrOfCode in preemptive region.
src/coreclr/vm/comutilnative.cpp Avoids holding OBJECTREF across regions; uses MethodTable* in preemptive mode.
src/coreclr/vm/comdelegate.h Changes QCall signature for Delegate_AdjustTarget.
src/coreclr/vm/comdelegate.cpp Adds preemptive transitions around method desc association/entrypoint resolution; updates AdjustTarget QCall.
src/coreclr/vm/comconnectionpoints.cpp Caches entrypoints in preemptive mode before invoking managed entrypoints.
src/coreclr/vm/comcallablewrapper.cpp Changes a contract to MODE_PREEMPTIVE.
src/coreclr/vm/codeman.h Adds a stub ReportStubBlock with preemptive-mode contract when perfmap is off.
src/coreclr/vm/codeman.cpp Unguards PerfMap logging for jump stub emission.
src/coreclr/vm/class.cpp Updates boxing/unboxing entrypoint helpers to MODE_PREEMPTIVE.
src/coreclr/vm/ceemain.cpp Unguards PerfMap header include.
src/coreclr/vm/callhelpers.h Refactors MethodDescCallSite to take an explicit PCODE call target.
src/coreclr/vm/callhelpers.cpp Adjusts default ctor invocation to precompute callable entrypoint in preemptive region.
src/coreclr/vm/assembly.cpp Wraps GetSingleCallableAddrOfCode in preemptive region for main entry.
src/coreclr/vm/arm64/stubs.cpp Unguards dynamic helper PerfMap logging macro.
src/coreclr/vm/arm/stubs.cpp Unguards dynamic helper PerfMap logging macro.
src/coreclr/vm/amd64/cgenamd64.cpp Unguards dynamic helper PerfMap logging macro; modifies END macro behavior.
src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs Updates AdjustTarget interop to pass MethodTable* and keep target alive.
src/coreclr/debug/ee/funceval.cpp Adds preemptive transitions around call target resolution for func-eval.

Comment on lines 608 to 614
#define BEGIN_DYNAMIC_HELPER_EMIT(size) \
BEGIN_DYNAMIC_HELPER_EMIT_WORKER(size) \
PerfMap::LogStubs(__FUNCTION__, "DynamicHelper", (PCODE)p, size, PerfMapStubType::Individual);
#else
#define BEGIN_DYNAMIC_HELPER_EMIT(size) BEGIN_DYNAMIC_HELPER_EMIT_WORKER(size)
#endif

#define END_DYNAMIC_HELPER_EMIT() \
_ASSERTE(pStart + cb == p); \
while (p < pStart + cbAligned) *p++ = X86_INSTR_INT3; \
ClrFlushInstructionCache(pStartRX, cbAligned); \
return (PCODE)pStartRX
Comment on lines 543 to 545
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_AdjustTarget")]
private static partial IntPtr AdjustTarget(ObjectHandleOnStack target, IntPtr methodPtr);
private static partial IntPtr AdjustTarget(MethodTable* targetMT, IntPtr methodPtr);

Comment on lines 719 to 725
CONTRACT(MethodDesc*)
{
MODE_PREEMPTIVE;
THROWS;
if (allowCreate) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
if (allowCreate) { MODE_PREEMPTIVE; } else { MODE_ANY; }
if (!allowCreate) { SUPPORTS_DAC; }
INJECT_FAULT(COMPlusThrowOM(););
@jkotas
Copy link
Copy Markdown
Member

jkotas commented Jun 5, 2026

Switching more C/C++ loader code that does heavy lifting to preemptive mode is general goodness.

The test failures are related and needs fixing:

Fatal error.
Internal CLR error. (0x80131506 (COR_E_EXECUTIONENGINE))
   at System.Delegate.BindToMethodInfo(System.Object, System.IRuntimeMethodInfo, System.RuntimeType, System.DelegateBindingFlags)
   at System.Delegate.CreateDelegateInternal(System.RuntimeType, System.Reflection.RuntimeMethodInfo, System.Object, System.DelegateBindingFlags)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants