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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public abstract partial class Delegate : ICloneable, ISerializable
// 1. Multicast delegates - Wrapper[]
// 2. Method cache - MethodInfo
// 3. Collectible delegates - LoaderAllocator and such
private object? _helperObject;
internal object? _helperObject;

// _target is the object we will invoke on
// Keep _target and _methodPtr next to each other for optimal delegate invoke performance
Expand Down
6 changes: 4 additions & 2 deletions src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2532,9 +2532,11 @@ internal static void ValidateObject(object obj, IntPtr pMD)
internal static IntPtr GetStubContext() => throw new UnreachableException(); // Unconditionally expanded intrinsic

[MethodImpl(MethodImplOptions.NoInlining)]
internal static void MulticastDebuggerTraceHelper(object o, int count)
internal static unsafe void MulticastDebuggerTraceHelper(MulticastDelegate d, ref Delegate.Wrapper position)
{
MulticastDebuggerTraceHelperQCall(ObjectHandleOnStack.Create(ref o), count);
nuint byteOffset = (nuint)Unsafe.ByteOffset(ref (((Delegate.Wrapper[])d._helperObject!))[0], ref position);
nuint count = byteOffset / (uint)sizeof(Delegate.Wrapper);
MulticastDebuggerTraceHelperQCall(ObjectHandleOnStack.Create(ref d), (int)count);
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint="StubHelpers_MulticastDebuggerTraceHelper")]
Expand Down
130 changes: 47 additions & 83 deletions src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,109 +310,73 @@ public override MethodIL EmitIL()
ILEmitter emitter = new ILEmitter();
ILCodeStream codeStream = emitter.NewCodeStream();

TypeDesc delegateWrapperType = ((MetadataType)SystemDelegateType).GetKnownNestedType("Wrapper"u8);
ArrayType invocationListArrayType = delegateWrapperType.MakeArrayType();
TypeDesc wrapper = ((MetadataType)SystemDelegateType).GetKnownNestedType("Wrapper"u8);
ByRefType wrapperRef = wrapper.MakeByRefType();
ILLocalVariable currentRef = emitter.NewLocal(wrapperRef);
ILLocalVariable endRef = emitter.NewLocal(wrapperRef);

ILLocalVariable delegateArrayLocal = emitter.NewLocal(invocationListArrayType);
ILLocalVariable invocationCountLocal = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
ILLocalVariable iteratorLocal = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
bool returns = !Signature.ReturnType.IsVoid;
ILLocalVariable returnLocal = returns ? emitter.NewLocal(Signature.ReturnType) : 0;

ILLocalVariable returnValueLocal = 0;
if (!Signature.ReturnType.IsVoid)
{
returnValueLocal = emitter.NewLocal(Signature.ReturnType);
}

// Fill in delegateArrayLocal
// Delegate.Wrapper[] delegateArrayLocal = (Delegate.Wrapper[])this._helperObject

// ldarg.0 (this pointer)
// ldfld Delegate._helperObject
// castclass Delegate.Wrapper[] (omitted - generate unsafe cast assuming the delegate is well-formed)
// stloc delegateArrayLocal
// Load start and end refs
codeStream.EmitLdArg(0);
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(HelperObjectField));
// codeStream.Emit(ILOpcode.castclass, emitter.NewToken(invocationListArrayType));
codeStream.EmitStLoc(delegateArrayLocal);

// Fill in invocationCountLocal
// int invocationCountLocal = this._extraFunctionPointerOrData
// We need GADR to get the start
TypeDesc signatureVariable = Context.GetSignatureVariable(0, method: true);
MethodSignature signature = new(MethodSignatureFlags.Static, 1, signatureVariable.MakeByRefType(), [signatureVariable.MakeArrayType()]);
MethodDesc gadr = Context.GetCoreLibEntryPoint("System.Runtime.InteropServices"u8, "MemoryMarshal"u8, "GetArrayDataReference"u8, signature);

// Get start ref
codeStream.Emit(ILOpcode.call, emitter.NewToken(gadr.MakeInstantiatedMethod(wrapper)));
codeStream.EmitStLoc(currentRef);

// ldarg.0 (this pointer)
// ldfld Delegate._extraFunctionPointerOrData
// stloc invocationCountLocal
// Get end ref from adding count
codeStream.EmitLdLoc(currentRef);
codeStream.EmitLdArg(0);
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(ExtraFunctionPointerOrDataField));
codeStream.EmitStLoc(invocationCountLocal);

// Fill in iteratorLocal
// int iteratorLocal = 0;

// ldc.0
// stloc iteratorLocal
codeStream.EmitLdc(0);
codeStream.EmitStLoc(iteratorLocal);

// Loop across every element of the array.
ILCodeLabel startOfLoopLabel = emitter.NewCodeLabel();
codeStream.EmitLabel(startOfLoopLabel);

// Implement as do/while loop. We only have this stub in play if we're in the multicast situation
// Find the delegate to call
// delegateArrayLocal[iteratorLocal].Value
codeStream.EmitLdc(wrapper.GetElementSize().AsInt);
codeStream.Emit(ILOpcode.mul);
codeStream.Emit(ILOpcode.add);
codeStream.EmitStLoc(endRef);

// ldloc delegateArrayLocal
// ldloc iteratorLocal
// ldelema Delegate.Wrapper
// ldfld Delegate.Wrapper.Value
codeStream.EmitLdLoc(delegateArrayLocal);
codeStream.EmitLdLoc(iteratorLocal);
codeStream.Emit(ILOpcode.ldelema, emitter.NewToken(delegateWrapperType));
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(delegateWrapperType.GetKnownField("Value"u8)));
ILCodeLabel nextDelegate = emitter.NewCodeLabel();
// Label_nextDelegate:
codeStream.EmitLabel(nextDelegate);

// Call the delegate
// delegateArrayLocal[iteratorLocal].Value(...)
// Load the delegate
codeStream.EmitLdLoc(currentRef);
codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(wrapper.GetKnownField("Value"u8)));

// ldarg 1, n
// callvirt DelegateType.Invoke(...)
// IF there is a return value
// stloc returnValueLocal

for (int i = 0; i < Signature.Length; i++)
// Load the arguments
for (int param = 0; param < Signature.Length; param++)
{
codeStream.EmitLdArg(i + 1);
codeStream.EmitLdArg(param + 1);
}

codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(_delegateInfo.InvokeMethod.InstantiateAsOpen()));

if (returnValueLocal != 0)
codeStream.EmitStLoc(returnValueLocal);
// Call the delegate
codeStream.Emit(ILOpcode.call, emitter.NewToken(_delegateInfo.InvokeMethod.InstantiateAsOpen()));

// Increment iteratorLocal
// ++iteratorLocal;
// Save return value.
if (returnLocal != 0)
codeStream.EmitStLoc(returnLocal);

// ldloc iteratorLocal
// ldc.i4.1
// add
// stloc iteratorLocal
codeStream.EmitLdLoc(iteratorLocal);
codeStream.EmitLdc(1);
// Increment the ref
codeStream.EmitLdLoc(currentRef);
codeStream.EmitLdc(wrapper.GetElementSize().AsInt);
codeStream.Emit(ILOpcode.add);
codeStream.EmitStLoc(iteratorLocal);

// Check to see if the loop is done
codeStream.EmitLdLoc(invocationCountLocal);
codeStream.EmitLdLoc(iteratorLocal);
codeStream.Emit(ILOpcode.bne_un, startOfLoopLabel);
codeStream.EmitStLoc(currentRef);

// Return to caller. If the delegate has a return value, be certain to return that.
// return returnValueLocal;
// Compare start ref with end. If less than branch to nextDelegate
codeStream.EmitLdLoc(currentRef);
codeStream.EmitLdLoc(endRef);
codeStream.Emit(ILOpcode.blt_un, nextDelegate);

// ldloc returnValueLocal
// ret
if (returnValueLocal != 0)
codeStream.EmitLdLoc(returnValueLocal);
// Load the return value, return value from the last delegate call is returned
if (returns)
codeStream.EmitLdLoc(returnLocal);

// Return
codeStream.Emit(ILOpcode.ret);

return emitter.Link(this);
Expand Down
129 changes: 72 additions & 57 deletions src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2034,92 +2034,107 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat

MetaSig sig(pMD);

BOOL fReturnVal = !sig.IsReturnTypeVoid();

SigTypeContext emptyContext;
ILStubLinker sl(pMD->GetModule(), pMD->GetSignature(), &emptyContext, pMD, (ILStubLinkerFlags)(ILSTUB_LINKER_FLAG_STUB_HAS_THIS | ILSTUB_LINKER_FLAG_TARGET_HAS_THIS));

ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch);

DWORD dwLoopCounterNum = pCode->NewLocal(ELEMENT_TYPE_I4);

DWORD dwReturnValNum = -1;
if (fReturnVal)
dwReturnValNum = pCode->NewLocal(sig.GetRetTypeHandleNT());

TypeHandle wrapper(CoreLibBinder::GetClass(CLASS__DELEGATEWRAPPER));
LocalDesc wrapperRef(wrapper.MakeByRef());
DWORD dwCurrentRef = pCode->NewLocal(wrapperRef);
DWORD dwEndRef = pCode->NewLocal(wrapperRef);

BOOL fReturns = !sig.IsReturnTypeVoid();
DWORD dwReturnNum = fReturns ? pCode->NewLocal(sig.GetRetTypeHandleNT()) : -1;

// Load start and end refs
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__HELPER_OBJECT)));

// We need GADR to get the start
MethodDesc* gadr = CoreLibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE);
gadr = MethodDesc::FindOrCreateAssociatedMethodDesc(gadr, gadr->GetMethodTable(), FALSE, Instantiation(&wrapper, 1), TRUE);

// Create signature for the MethodSpec. See ECMA-335 - II.23.2.15
SigBuilder sigBuilder;
sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST);
sigBuilder.AppendData(1);
sigBuilder.AppendElementType(ELEMENT_TYPE_MVAR);
sigBuilder.AppendData(0);
uint32_t sigLen;
PCCOR_SIGNATURE gadrSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen);
mdToken methodSpecSigToken = pCode->GetSigToken(gadrSig, sigLen);

// Get start ref
pCode->EmitCALL(pCode->GetToken(gadr, mdTokenNil, methodSpecSigToken), 1, 1);
pCode->EmitSTLOC(dwCurrentRef);

// Get end ref from adding count
pCode->EmitLDLOC(dwCurrentRef);
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__EXTRA_DATA)));
pCode->EmitLDC(wrapper.GetSize());
pCode->EmitMUL();
pCode->EmitADD();
pCode->EmitSTLOC(dwEndRef);

ILCodeLabel *nextDelegate = pCode->NewCodeLabel();

// initialize counter
pCode->EmitLDC(0);
pCode->EmitSTLOC(dwLoopCounterNum);

//Label_nextDelegate:
// Label_nextDelegate:
pCode->EmitLabel(nextDelegate);

#ifdef DEBUGGING_SUPPORTED
ILCodeLabel *invokeTraceHelper = pCode->NewCodeLabel();
ILCodeLabel *debuggerCheckEnd = pCode->NewCodeLabel();

// Call MulticastDebuggerTraceHelper only if we have a controller subscribing to the event
pCode->EmitLDC((DWORD_PTR)&g_multicastDelegateTraceActiveCount);
pCode->EmitCONV_I();
pCode->EmitLDIND_I4();
// g_multicastDelegateTraceActiveCount != 0
pCode->EmitCONV_U();
pCode->EmitLDIND_U4();
// g_multicastDelegateTraceActiveCount == 0
pCode->EmitLDC(0);
pCode->EmitCEQ();
pCode->EmitBRFALSE(invokeTraceHelper);

pCode->EmitLabel(debuggerCheckEnd);
#endif // DEBUGGING_SUPPORTED
ILCodeLabel *nextCall = pCode->NewCodeLabel();
pCode->EmitBEQ(nextCall);

// Load next delegate from array using LoopCounter as index
// Call debugger tracing
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__HELPER_OBJECT)));
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitLDELEMA(pCode->GetToken(CoreLibBinder::GetClass(CLASS__DELEGATEWRAPPER)));
pCode->EmitLDLOC(dwCurrentRef);
pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0);

// Label_nextCall:
pCode->EmitLabel(nextCall);
#endif // DEBUGGING_SUPPORTED

// Load the delegate
pCode->EmitLDLOC(dwCurrentRef);
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATEWRAPPER__VALUE)));

// Load the arguments
for (UINT paramCount = 0; paramCount < sig.NumFixedArgs(); paramCount++)
pCode->EmitLDARG(paramCount);
for (UINT param = 0; param < sig.NumFixedArgs(); param++)
pCode->EmitLDARG(param);

// call the delegate
pCode->EmitCALL(pCode->GetToken(pMD), sig.NumFixedArgs(), fReturnVal);
// Call the delegate
pCode->EmitCALL(pCode->GetToken(pMD), sig.NumFixedArgs(), fReturns);

// Save return value.
if (fReturnVal)
pCode->EmitSTLOC(dwReturnValNum);
if (fReturns)
pCode->EmitSTLOC(dwReturnNum);

// increment counter
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitLDC(1);
// Increment the ref
pCode->EmitLDLOC(dwCurrentRef);
pCode->EmitLDC(wrapper.GetSize());
pCode->EmitADD();
pCode->EmitSTLOC(dwLoopCounterNum);
pCode->EmitSTLOC(dwCurrentRef);

// compare LoopCounter with _extraData. If less then branch to nextDelegate
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitLoadThis();
pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__DELEGATE__EXTRA_DATA)));
// Compare start ref with end. If less than branch to nextDelegate
pCode->EmitLDLOC(dwCurrentRef);
pCode->EmitLDLOC(dwEndRef);
pCode->EmitBLT(nextDelegate);

// load the return value. return value from the last delegate call is returned
if (fReturnVal)
pCode->EmitLDLOC(dwReturnValNum);
// Load the return value, return value from the last delegate call is returned
if (fReturns)
pCode->EmitLDLOC(dwReturnNum);

// return
// Return
pCode->EmitRET();

#ifdef DEBUGGING_SUPPORTED
// Emit debugging support at the end of the method for better perf
pCode->EmitLabel(invokeTraceHelper);

pCode->EmitLoadThis();
pCode->EmitLDLOC(dwLoopCounterNum);
pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0);

pCode->EmitBR(debuggerCheckEnd);
#endif // DEBUGGING_SUPPORTED

PCCOR_SIGNATURE pSig;
DWORD cbSig;
pMD->GetSig(&pSig, &cbSig);
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/vm/corelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,7 @@ DEFINE_METHOD(UNSAFE, UNBOX, Unbox, NoSig)
DEFINE_METHOD(UNSAFE, WRITE, Write, NoSig)

DEFINE_CLASS(MEMORY_MARSHAL, Interop, MemoryMarshal)
DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE, GetArrayDataReference, GM_ArrT_RetRefT)
DEFINE_METHOD(MEMORY_MARSHAL, GET_ARRAY_DATA_REFERENCE_MDARRAY, GetArrayDataReference, SM_Array_RetRefByte)

DEFINE_CLASS(INTERLOCKED, Threading, Interlocked)
Expand Down Expand Up @@ -1127,7 +1128,7 @@ DEFINE_METHOD(STUBHELPERS, PROFILER_BEGIN_TRANSITION_CALLBACK, Profiler
DEFINE_METHOD(STUBHELPERS, PROFILER_END_TRANSITION_CALLBACK, ProfilerEndTransitionCallback, SM_PtrVoid_RetVoid)
#endif

DEFINE_METHOD(STUBHELPERS, MULTICAST_DEBUGGER_TRACE_HELPER, MulticastDebuggerTraceHelper, SM_Obj_Int_RetVoid)
DEFINE_METHOD(STUBHELPERS, MULTICAST_DEBUGGER_TRACE_HELPER, MulticastDebuggerTraceHelper, NoSig)

DEFINE_CLASS(CLEANUP_WORK_LIST_ELEMENT, StubHelpers, CleanupWorkListElement)

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/metasig.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ DEFINE_METASIG(GM(RefByte_T_RetVoid, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(b) M(0)
DEFINE_METASIG(GM(PtrVoid_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, P(v), M(0)))
DEFINE_METASIG(GM(PtrVoid_T_RetVoid, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, P(v) M(0), v))

DEFINE_METASIG(GM(ArrT_RetRefT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, a(M(0)), r(M(0))))
DEFINE_METASIG(GM(RefT_RetRefT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)), r(M(0))))
DEFINE_METASIG(GM(RefTFrom_RetRefTTo, IMAGE_CEE_CS_CALLCONV_DEFAULT, 2, r(M(0)), r(M(1))))
DEFINE_METASIG(GM(Obj_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, j, M(0)))
Expand Down
Loading