From 364e7eb88005eb7064e98fa56730b7793ba05dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Sun, 5 Jul 2026 00:32:33 +0200 Subject: [PATCH 1/2] Optimize multicast stubs --- .../TypeSystem/IL/Stubs/DelegateThunks.cs | 104 ++++++------------ src/coreclr/vm/comdelegate.cpp | 76 ++++++++----- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/metasig.h | 1 + 4 files changed, 87 insertions(+), 95 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs index 40b267d9d2c19c..73de382f0602ae 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/DelegateThunks.cs @@ -310,12 +310,8 @@ public override MethodIL EmitIL() ILEmitter emitter = new ILEmitter(); ILCodeStream codeStream = emitter.NewCodeStream(); - TypeDesc delegateWrapperType = ((MetadataType)SystemDelegateType).GetKnownNestedType("Wrapper"u8); - ArrayType invocationListArrayType = delegateWrapperType.MakeArrayType(); - - ILLocalVariable delegateArrayLocal = emitter.NewLocal(invocationListArrayType); - ILLocalVariable invocationCountLocal = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32)); - ILLocalVariable iteratorLocal = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32)); + ILLocalVariable startLocal = emitter.NewLocal(_delegateInfo.Type.MakeByRefType()); + ILLocalVariable endLocal = emitter.NewLocal(_delegateInfo.Type.MakeByRefType()); ILLocalVariable returnValueLocal = 0; if (!Signature.ReturnType.IsVoid) @@ -323,96 +319,64 @@ public override MethodIL EmitIL() 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 + // Get start ref + TypeDesc signatureVariable = Context.GetSignatureVariable(0, method: true); + MethodSignature signature = new(MethodSignatureFlags.Static, 1, signatureVariable.MakeByRefType(), [signatureVariable.MakeArrayType()]); + TypeDesc wrapper = ((MetadataType)SystemDelegateType).GetKnownNestedType("Wrapper"u8); + MethodDesc gadr = Context.GetCoreLibEntryPoint("System.Runtime.InteropServices"u8, "MemoryMarshal"u8, "GetArrayDataReference"u8, signature).MakeInstantiatedMethod(wrapper); + codeStream.Emit(ILOpcode.call, emitter.NewToken(gadr)); + codeStream.EmitStLoc(startLocal); - // ldarg.0 (this pointer) - // ldfld Delegate._extraFunctionPointerOrData - // stloc invocationCountLocal + // Get end ref from adding count + codeStream.EmitLdLoc(startLocal); 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); + codeStream.EmitLdc(Context.Target.PointerSize); + codeStream.Emit(ILOpcode.mul); + codeStream.Emit(ILOpcode.add); + codeStream.EmitStLoc(endLocal); - // Loop across every element of the array. ILCodeLabel startOfLoopLabel = emitter.NewCodeLabel(); + // Label_nextDelegate: 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 - - // 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))); - - // Call the delegate - // delegateArrayLocal[iteratorLocal].Value(...) - - // ldarg 1, n - // callvirt DelegateType.Invoke(...) - // IF there is a return value - // stloc returnValueLocal + // Load the delegate + codeStream.EmitLdLoc(startLocal); + codeStream.Emit(ILOpcode.ldind_ref); + // Load the arguments for (int i = 0; i < Signature.Length; i++) { codeStream.EmitLdArg(i + 1); } - codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(_delegateInfo.InvokeMethod.InstantiateAsOpen())); + // Call the delegate + codeStream.Emit(ILOpcode.call, emitter.NewToken(_delegateInfo.InvokeMethod.InstantiateAsOpen())); + // Save return value. if (returnValueLocal != 0) codeStream.EmitStLoc(returnValueLocal); - // Increment iteratorLocal - // ++iteratorLocal; - - // ldloc iteratorLocal - // ldc.i4.1 - // add - // stloc iteratorLocal - codeStream.EmitLdLoc(iteratorLocal); - codeStream.EmitLdc(1); + // Increment the ref + codeStream.EmitLdLoc(startLocal); + codeStream.EmitLdc(Context.Target.PointerSize); 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(startLocal); - // 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(startLocal); + codeStream.EmitLdLoc(endLocal); + codeStream.Emit(ILOpcode.blt_un, startOfLoopLabel); - // ldloc returnValueLocal - // ret + // Load the return value, return value from the last delegate call is returned if (returnValueLocal != 0) codeStream.EmitLdLoc(returnValueLocal); + // Return codeStream.Emit(ILOpcode.ret); return emitter.Link(this); diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index ec6d9f4277c91e..d50d193f17e67f 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -2038,19 +2038,47 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - DWORD dwLoopCounterNum = pCode->NewLocal(ELEMENT_TYPE_I4); + LocalDesc refLocalDesc(TypeHandle(pDelegateMT).MakeByRef()); + DWORD dwStartNum = pCode->NewLocal(refLocalDesc); + DWORD dwEndNum = pCode->NewLocal(refLocalDesc); DWORD dwReturnValNum = -1; if (fReturnVal) dwReturnValNum = pCode->NewLocal(sig.GetRetTypeHandleNT()); - ILCodeLabel *nextDelegate = pCode->NewCodeLabel(); + // Load start and end refs + pCode->EmitLoadThis(); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_LIST))); - // initialize counter - pCode->EmitLDC(0); - pCode->EmitSTLOC(dwLoopCounterNum); + // Get start ref + MethodDesc* gadr = CoreLibBinder::GetMethod(METHOD__MEMORY_MARSHAL__GET_ARRAY_DATA_REFERENCE); + TypeHandle th(CoreLibBinder::GetClass(CLASS__OBJECT)); + gadr = MethodDesc::FindOrCreateAssociatedMethodDesc(gadr, gadr->GetMethodTable(), FALSE, Instantiation(&th, 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); + + pCode->EmitCALL(pCode->GetToken(gadr, mdTokenNil, methodSpecSigToken), 1, 1); + pCode->EmitSTLOC(dwStartNum); + + // Get end ref from adding count + pCode->EmitLDLOC(dwStartNum); + pCode->EmitLoadThis(); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_COUNT))); + pCode->EmitLDC(TARGET_POINTER_SIZE); + pCode->EmitMUL(); + pCode->EmitADD(); + pCode->EmitSTLOC(dwEndNum); - //Label_nextDelegate: + ILCodeLabel *nextDelegate = pCode->NewCodeLabel(); + // Label_nextDelegate: pCode->EmitLabel(nextDelegate); #ifdef DEBUGGING_SUPPORTED @@ -2063,46 +2091,42 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat pCode->EmitLDIND_I4(); // g_multicastDelegateTraceActiveCount != 0 pCode->EmitLDC(0); - pCode->EmitCEQ(); - pCode->EmitBRFALSE(invokeTraceHelper); + pCode->EmitBNE_UN(invokeTraceHelper); pCode->EmitLabel(debuggerCheckEnd); #endif // DEBUGGING_SUPPORTED - // Load next delegate from array using LoopCounter as index - pCode->EmitLoadThis(); - pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_LIST))); - pCode->EmitLDLOC(dwLoopCounterNum); - pCode->EmitLDELEM_REF(); + // Load the delegate + pCode->EmitLDLOC(dwStartNum); + pCode->EmitLDIND_REF(); // Load the arguments for (UINT paramCount = 0; paramCount < sig.NumFixedArgs(); paramCount++) pCode->EmitLDARG(paramCount); - // call the delegate + // Call the delegate pCode->EmitCALL(pCode->GetToken(pMD), sig.NumFixedArgs(), fReturnVal); // Save return value. if (fReturnVal) pCode->EmitSTLOC(dwReturnValNum); - // increment counter - pCode->EmitLDLOC(dwLoopCounterNum); - pCode->EmitLDC(1); + // Increment the ref + pCode->EmitLDLOC(dwStartNum); + pCode->EmitLDC(TARGET_POINTER_SIZE); pCode->EmitADD(); - pCode->EmitSTLOC(dwLoopCounterNum); + pCode->EmitSTLOC(dwStartNum); - // compare LoopCounter with InvocationCount. If less then branch to nextDelegate - pCode->EmitLDLOC(dwLoopCounterNum); - pCode->EmitLoadThis(); - pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_COUNT))); + // Compare start ref with end. If less than branch to nextDelegate + pCode->EmitLDLOC(dwStartNum); + pCode->EmitLDLOC(dwEndNum); pCode->EmitBLT(nextDelegate); - // load the return value. return value from the last delegate call is returned + // Load the return value, return value from the last delegate call is returned if (fReturnVal) pCode->EmitLDLOC(dwReturnValNum); - // return + // Return pCode->EmitRET(); #ifdef DEBUGGING_SUPPORTED @@ -2110,7 +2134,9 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat pCode->EmitLabel(invokeTraceHelper); pCode->EmitLoadThis(); - pCode->EmitLDLOC(dwLoopCounterNum); + pCode->EmitLDLOC(dwEndNum); + pCode->EmitLDLOC(dwStartNum); + pCode->EmitSUB(); pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0); pCode->EmitBR(debuggerCheckEnd); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index fa6f4000edaa7e..aed3b2fe924f72 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -821,6 +821,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) diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index fb3801964ee0cf..f6c1f136e3e8e8 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -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))) From 9290fe1a3cc927f391b79df9ef833d3ae1daac39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Sun, 5 Jul 2026 03:49:17 +0200 Subject: [PATCH 2/2] Avoid weird IL --- src/coreclr/vm/comdelegate.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index d50d193f17e67f..cc53bc05eb6a9e 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -2082,18 +2082,27 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat 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 + // g_multicastDelegateTraceActiveCount == 0 pCode->EmitLDC(0); - pCode->EmitBNE_UN(invokeTraceHelper); + + ILCodeLabel *realLoopStart = pCode->NewCodeLabel(); + pCode->EmitBEQ(realLoopStart); + + // Call debugger tracing + pCode->EmitLoadThis(); + pCode->EmitLDLOC(dwStartNum); + pCode->EmitLoadThis(); + pCode->EmitLDFLD(pCode->GetToken(CoreLibBinder::GetField(FIELD__MULTICAST_DELEGATE__INVOCATION_LIST))); + pCode->EmitCALL(pCode->GetToken(gadr, mdTokenNil, methodSpecSigToken), 1, 1); + pCode->EmitSUB(); + pCode->EmitCALL(METHOD__STUBHELPERS__MULTICAST_DEBUGGER_TRACE_HELPER, 2, 0); - pCode->EmitLabel(debuggerCheckEnd); + // Label_realLoopStart: + pCode->EmitLabel(realLoopStart); #endif // DEBUGGING_SUPPORTED // Load the delegate @@ -2129,19 +2138,6 @@ extern "C" PCODE QCALLTYPE Delegate_GetMulticastInvokeSlow(MethodTable* pDelegat // 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(dwEndNum); - pCode->EmitLDLOC(dwStartNum); - pCode->EmitSUB(); - 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);