Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
99f405d
Make incorrect PerfMap usage trigger contract failures on Windows (wh…
davidwrighton Jun 3, 2026
1759f5d
Adjust preemptive/cooperative state throughout the runtime so that th…
davidwrighton Jun 4, 2026
e1f7ef1
Update genmeth.cpp
davidwrighton Jun 4, 2026
029f87d
Adjust based on test failures
davidwrighton Jun 5, 2026
d94d6a5
Fix wasm build break
davidwrighton Jun 5, 2026
e05223a
Merge branch 'CallPerfMapFromPREEMPTIVE' of github.com:davidwrighton/…
davidwrighton Jun 5, 2026
01bf23e
Code review updates
davidwrighton Jun 11, 2026
8dd3e3f
Merge branch 'main' of https://github.com/dotnet/runtime into CallPer…
davidwrighton Jun 11, 2026
be0225a
PerfMap::LogStubs is now MODE_PREEMPTIVE only
davidwrighton Jun 11, 2026
9d6d2e9
Make apply metadata logic happen on in preemptive mode, and fix a few…
davidwrighton Jun 15, 2026
1863d42
Adjust BuildMethodTable
davidwrighton Jun 15, 2026
16a5f31
Make error handling and EEClassHashTable construction GC_NOTRIGGER
davidwrighton Jun 16, 2026
6d21f26
Make new MethodDescCallSite for the custom attribute call scenario
davidwrighton Jun 18, 2026
65990ef
Merge branch 'main' of https://github.com/dotnet/runtime into CallPer…
davidwrighton Jun 18, 2026
5d786fd
Merge branch 'main' of https://github.com/dotnet/runtime into CallPer…
davidwrighton Jun 19, 2026
df2fb2a
Tweaks on delegate construction
davidwrighton Jun 19, 2026
b467065
Handle BindToMethodDetails correctly
davidwrighton Jun 19, 2026
0fe2486
Merge branch 'main' of https://github.com/dotnet/runtime into CallPer…
davidwrighton Jun 19, 2026
92f86c4
Make the standard delegate slow construction path avoid GC transitions
davidwrighton Jun 19, 2026
efa3adb
Safety comment about cast
davidwrighton Jun 19, 2026
827e26d
Fix global.json oops
davidwrighton Jun 19, 2026
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
87 changes: 73 additions & 14 deletions src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,27 +410,70 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target
Justification = "The parameter 'methodType' is passed by ref to QCallTypeHandle")]
private bool BindToMethodName(object? target, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] RuntimeType methodType, string method, DelegateBindingFlags flags)
{
Delegate d = this;
return BindToMethodName(ObjectHandleOnStack.Create(ref d), ObjectHandleOnStack.Create(ref target),
new QCallTypeHandle(ref methodType), method, flags);
bool ret = BindToMethodName(RuntimeHelpers.GetMethodTable(this), (target != null) ? RuntimeHelpers.GetMethodTable(target) : null,
new QCallTypeHandle(ref methodType), method, flags, ObjectHandleOnStack.Create(ref target), out BindToMethodDetails bindToMethodDetails);

if (ret)
{
// Apply the results of the QCall to the delegate instance.
_methodPtr = bindToMethodDetails.methodPtr;
_methodPtrAux = bindToMethodDetails.methodPtrAux;
Unsafe.As<MulticastDelegate>(this)._invocationCount = bindToMethodDetails.invocationCount;
if (bindToMethodDetails.loaderAllocatorGCHandle.IsAllocated)
{
_methodBase = bindToMethodDetails.loaderAllocatorGCHandle.Target;
GC.KeepAlive(method);
}

if (bindToMethodDetails.selfReferentialTarget != 0)
_target = this;
else
_target = target;
}
return ret;
}

private struct BindToMethodDetails
{
public int selfReferentialTarget; // Whether the delegate's target object is the same as the first argument of the method to bind to. Only meaningful for open instance delegates.
public IntPtr methodPtr;
public IntPtr methodPtrAux;
public IntPtr invocationCount;
public GCHandle loaderAllocatorGCHandle; // The loader allocator needed if the delegate needs to keep it alive
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_BindToMethodName", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool BindToMethodName(ObjectHandleOnStack d, ObjectHandleOnStack target, QCallTypeHandle methodType, string method, DelegateBindingFlags flags);
private static partial bool BindToMethodName(MethodTable* pDelegateMT, MethodTable *pTargetMT, QCallTypeHandle methodType, string method, DelegateBindingFlags flags, ObjectHandleOnStack targetParameter, out BindToMethodDetails bindToMethodDetails);

private bool BindToMethodInfo(object? target, IRuntimeMethodInfo method, RuntimeType methodType, DelegateBindingFlags flags)
{
Delegate d = this;
bool ret = BindToMethodInfo(ObjectHandleOnStack.Create(ref d), ObjectHandleOnStack.Create(ref target),
method.Value, new QCallTypeHandle(ref methodType), flags);
GC.KeepAlive(method);
bool ret = BindToMethodInfo(RuntimeHelpers.GetMethodTable(this), (target != null) ? RuntimeHelpers.GetMethodTable(target) : null,
method.Value, new QCallTypeHandle(ref methodType), flags, ObjectHandleOnStack.Create(ref target), out BindToMethodDetails bindToMethodDetails);

if (ret)
{
// Apply the results of the QCall to the delegate instance.
_methodPtr = bindToMethodDetails.methodPtr;
_methodPtrAux = bindToMethodDetails.methodPtrAux;
Unsafe.As<MulticastDelegate>(this)._invocationCount = bindToMethodDetails.invocationCount;
if (bindToMethodDetails.loaderAllocatorGCHandle.IsAllocated)
{
_methodBase = bindToMethodDetails.loaderAllocatorGCHandle.Target;
GC.KeepAlive(method);
}

if (bindToMethodDetails.selfReferentialTarget != 0)
_target = this;
else
_target = target;
}
return ret;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_BindToMethodInfo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool BindToMethodInfo(ObjectHandleOnStack d, ObjectHandleOnStack target, RuntimeMethodHandleInternal method, QCallTypeHandle methodType, DelegateBindingFlags flags);
private static partial bool BindToMethodInfo(MethodTable* pDelegateMT, MethodTable *pTargetMT, RuntimeMethodHandleInternal method, QCallTypeHandle methodType, DelegateBindingFlags flags, ObjectHandleOnStack targetParameter, out BindToMethodDetails bindToMethodDetails);

private static MulticastDelegate InternalAlloc(RuntimeType type)
{
Expand Down Expand Up @@ -475,12 +518,26 @@ private void DelegateConstruct(object target, IntPtr method)
throw new ArgumentNullException(nameof(method));
}

Delegate _this = this;
Construct(ObjectHandleOnStack.Create(ref _this), ObjectHandleOnStack.Create(ref target), method);
Construct(RuntimeHelpers.GetMethodTable(this), (target != null) ? RuntimeHelpers.GetMethodTable(target) : null,
method, out BindToMethodDetails bindToMethodDetails);

// Apply the results of the QCall to the delegate instance.
_methodPtr = bindToMethodDetails.methodPtr;
_methodPtrAux = bindToMethodDetails.methodPtrAux;
Unsafe.As<MulticastDelegate>(this)._invocationCount = bindToMethodDetails.invocationCount;
if (bindToMethodDetails.loaderAllocatorGCHandle.IsAllocated)
{
_methodBase = bindToMethodDetails.loaderAllocatorGCHandle.Target;
}

if (bindToMethodDetails.selfReferentialTarget != 0)
_target = this;
else
_target = target;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_Construct")]
private static partial void Construct(ObjectHandleOnStack _this, ObjectHandleOnStack target, IntPtr method);
private static partial void Construct(MethodTable* pDelegateMT, MethodTable* pTargetMT, IntPtr method, out BindToMethodDetails bindToMethodDetails);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe void* GetMulticastInvoke(MethodTable* pMT);
Expand Down Expand Up @@ -535,11 +592,13 @@ private static bool InternalEqualMethodHandles(Delegate left, Delegate right)

internal static IntPtr AdjustTarget(object target, IntPtr methodPtr)
{
return AdjustTarget(ObjectHandleOnStack.Create(ref target), methodPtr);
IntPtr result = AdjustTarget(RuntimeHelpers.GetMethodTable(target), methodPtr);
GC.KeepAlive(target);
return result;
}

[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 thread
davidwrighton marked this conversation as resolved.
internal void InitializeVirtualCallStub(IntPtr methodPtr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public abstract partial class MulticastDelegate : Delegate
// 2. Unmanaged function pointer
// 3. Open virtual delegate
private object? _invocationList; // Initialized by VM as needed
private nint _invocationCount;
internal nint _invocationCount;

internal bool IsUnmanagedFunctionPtr()
{
Expand Down
9 changes: 0 additions & 9 deletions src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12471,15 +12471,6 @@ HRESULT Debugger::ApplyChangesAndSendResult(DebuggerModule * pDebuggerModule,
}
else
{
// Violation with the following call stack:
// CONTRACT in MethodTableBuilder::InitMethodDesc
// CONTRACT in EEClass::AddMethod
// CONTRACT in EditAndContinueModule::AddMethod
// CONTRACT in EditAndContinueModule::ApplyEditAndContinue
// CONTRACT in EEDbgInterfaceImpl::EnCApplyChanges
// VIOLATED--> CONTRACT in Debugger::ApplyChangesAndSendResult
CONTRACT_VIOLATION(GCViolation);

// Tell the VM to apply the edit
hr = g_pEEInterface->EnCApplyChanges(
(EditAndContinueModule*)pModule, cbMetadata, pMetadata, cbIL, pIL);
Expand Down
16 changes: 13 additions & 3 deletions src/coreclr/debug/ee/funceval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,7 @@ void GatherFuncEvalMethodInfo(DebuggerEval *pDE,
//
if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsStatic() && pDE->m_md->IsUnboxingStub())
{
GCX_PREEMP();
*ppUnboxedMD = pDE->m_md->GetMethodTable()->GetUnboxedEntryPointMD(pDE->m_md);
}

Expand Down Expand Up @@ -2213,13 +2214,19 @@ void GatherFuncEvalMethodInfo(DebuggerEval *pDE,
// Now, find the proper MethodDesc for this interface method based on the object we're invoking the
// method on.
//
pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(&objRef, pDE->m_ownerTypeHandle);
{
GCX_PREEMP();
pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(&objRef, pDE->m_ownerTypeHandle);
}

GCPROTECT_END();
}
else
{
pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(NULL, pDE->m_ownerTypeHandle);
{
GCX_PREEMP();
pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(NULL, pDE->m_ownerTypeHandle);
}
}

//
Expand Down Expand Up @@ -3195,7 +3202,10 @@ static void DoNormalFuncEval( DebuggerEval *pDE,
// Now that all the args are protected, we can go back and deal with generic args and resolving
// all their information.
//
ResolveFuncEvalGenericArgInfo(pDE);
{
GCX_PREEMP();
ResolveFuncEvalGenericArgInfo(pDE);
}

//
// Grab the signature of the method we're working on and do some error checking.
Expand Down
6 changes: 0 additions & 6 deletions src/coreclr/vm/amd64/cgenamd64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
#include "clrtocomcall.h"
#endif // FEATURE_COMINTEROP

#ifdef FEATURE_PERFMAP
#include "perfmap.h"
#endif

void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegisters * pRegs)
{
Expand Down Expand Up @@ -607,13 +605,9 @@ DWORD GetOffsetAtEndOfFunction(ULONGLONG uImageBase,
size_t rxOffset = pStartRX - pStart; \
BYTE * p = pStart;

#ifdef FEATURE_PERFMAP
#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); \
Expand Down
6 changes: 0 additions & 6 deletions src/coreclr/vm/arm/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
#include "ecall.h"
#include "threadsuspend.h"

#ifdef FEATURE_PERFMAP
#include "perfmap.h"
#endif

// target write barriers
EXTERN_C void JIT_WriteBarrier(Object **dst, Object *ref);
Expand Down Expand Up @@ -1494,13 +1492,9 @@ void MovRegImm(BYTE* p, int reg, TADDR imm)
size_t rxOffset = pStartRX - pStart; \
BYTE * p = pStart;

#ifdef FEATURE_PERFMAP
#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() \
Expand Down
6 changes: 0 additions & 6 deletions src/coreclr/vm/arm64/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
#include "ecall.h"
#include "writebarriermanager.h"

#ifdef FEATURE_PERFMAP
#include "perfmap.h"
#endif

#ifndef DACCESS_COMPILE
//-----------------------------------------------------------------------
Expand Down Expand Up @@ -951,13 +949,9 @@ void StubLinkerCPU::EmitCallManagedMethod(MethodDesc *pMD, BOOL fTailCall)
size_t rxOffset = pStartRX - pStart; \
BYTE * p = pStart;

#ifdef FEATURE_PERFMAP
#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() \
Expand Down
13 changes: 10 additions & 3 deletions src/coreclr/vm/assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,11 @@ static void RunMainInternal(Param* pParam)
StrArgArray = *pParam->stringArgs;

pParam->pFD->EnsureActive();
PCODE entryPoint = pParam->pFD->GetSingleCallableAddrOfCode();
PCODE entryPoint;
{
GCX_PREEMP();
entryPoint = pParam->pFD->GetSingleCallableAddrOfCode();
}

BOOL hasReturnValue = !pParam->pFD->IsVoid();
PTRARRAYREF* pArgument = (pParam->EntryType == EntryManagedMain) ? &StrArgArray : NULL;
Expand Down Expand Up @@ -1701,7 +1705,8 @@ void Assembly::AddType(
CONTRACTL
{
THROWS;
GC_TRIGGERS;
GC_NOTRIGGER;
MODE_PREEMPTIVE;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END
Expand All @@ -1727,7 +1732,8 @@ void Assembly::AddExportedType(mdExportedType cl)
CONTRACTL
{
THROWS;
GC_TRIGGERS;
GC_NOTRIGGER;
MODE_PREEMPTIVE;
INJECT_FAULT(COMPlusThrowOM(););
}
CONTRACTL_END
Expand Down Expand Up @@ -1991,6 +1997,7 @@ void Assembly::SetError(Exception *ex)
SetProfilerNotified();

#ifdef PROFILING_SUPPORTED
GCX_PREEMP();
// Only send errors for non-shared assemblies; other assemblies might be successfully completed
// in another app domain later.
m_pModule->NotifyProfilerLoadFinished(ex->GetHR());
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/vm/assemblynative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,6 @@ extern "C" void QCALLTYPE AssemblyNative_ApplyUpdate(
_ASSERTE(ilDeltaLength > 0);

#ifdef FEATURE_METADATA_UPDATER
GCX_COOP();
{
if (CORDebuggerAttached())
{
Expand Down
10 changes: 8 additions & 2 deletions src/coreclr/vm/callhelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,11 +559,17 @@ void CallDefaultConstructor(OBJECTREF ref)

GCPROTECT_BEGIN (ref);

MethodDesc *pMD = pMT->GetDefaultConstructor();

PCODE methodEntry;
{
GCX_PREEMP();
MethodDesc *pMD = pMT->GetDefaultConstructor();
methodEntry = pMD->GetSingleCallableAddrOfCode();
}

UnmanagedCallersOnlyCaller defaultCtorInvoker{METHOD__RUNTIME_HELPERS__CALL_DEFAULT_CONSTRUCTOR};

defaultCtorInvoker.InvokeThrowing(&ref, pMD->GetSingleCallableAddrOfCode());
defaultCtorInvoker.InvokeThrowing(&ref, methodEntry);

GCPROTECT_END ();
}
Loading
Loading