Repository: dotnet/runtime
Pull request #115089: win-x86: Replace "ret n" thunks with assembly code
Author: login: filipnavara
Labels: name: area-VM-coreclr
name: community-contribution
description: Indicates that the PR has been added by a community member
Created: 2025-04-27T12:33:44Z | Merged: 2025-04-28T22:55:36Z

Replace x86 COM "ret n" thunks with assembly code that emulates "ret [n]" for an "[n]" specified using a variable value stored in register.


Diff:
diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def
index 2b60d49026b597..4bdfd7409f8447 100644
--- a/src/coreclr/inc/CrstTypes.def
+++ b/src/coreclr/inc/CrstTypes.def
@@ -208,10 +208,6 @@ Crst MethodTableExposedObject
     Unordered
 End
 
-Crst RetThunkCache
-    AcquiredBefore LoaderHeap
-End
-
 Crst FuncPtrStubs
     AcquiredBefore IbcProfile LoaderHeap UniqueStack CodeFragmentHeap JumpStubCache
 End
diff --git a/src/coreclr/inc/crsttypes_generated.h b/src/coreclr/inc/crsttypes_generated.h
index f5bd7a83bb89b5..6a5ef2352d24d0 100644
--- a/src/coreclr/inc/crsttypes_generated.h
+++ b/src/coreclr/inc/crsttypes_generated.h
@@ -96,30 +96,29 @@ enum CrstType
     CrstReadyToRunEntryPointToMethodDescMap = 78,
     CrstReflection = 79,
     CrstReJITGlobalRequest = 80,
-    CrstRetThunkCache = 81,
-    CrstSigConvert = 82,
-    CrstSingleUseLock = 83,
-    CrstStressLog = 84,
-    CrstStubCache = 85,
-    CrstStubDispatchCache = 86,
-    CrstSyncBlockCache = 87,
-    CrstSyncHashLock = 88,
-    CrstSystemDomain = 89,
-    CrstSystemDomainDelayedUnloadList = 90,
-    CrstThreadIdDispenser = 91,
-    CrstThreadLocalStorageLock = 92,
-    CrstThreadStore = 93,
-    CrstTieredCompilation = 94,
-    CrstTypeEquivalenceMap = 95,
-    CrstTypeIDMap = 96,
-    CrstUMEntryThunkCache = 97,
-    CrstUMEntryThunkFreeListLock = 98,
-    CrstUniqueStack = 99,
-    CrstUnresolvedClassLock = 100,
-    CrstUnwindInfoTableLock = 101,
-    CrstVSDIndirectionCellLock = 102,
-    CrstWrapperTemplate = 103,
-    kNumberOfCrstTypes = 104
+    CrstSigConvert = 81,
+    CrstSingleUseLock = 82,
+    CrstStressLog = 83,
+    CrstStubCache = 84,
+    CrstStubDispatchCache = 85,
+    CrstSyncBlockCache = 86,
+    CrstSyncHashLock = 87,
+    CrstSystemDomain = 88,
+    CrstSystemDomainDelayedUnloadList = 89,
+    CrstThreadIdDispenser = 90,
+    CrstThreadLocalStorageLock = 91,
+    CrstThreadStore = 92,
+    CrstTieredCompilation = 93,
+    CrstTypeEquivalenceMap = 94,
+    CrstTypeIDMap = 95,
+    CrstUMEntryThunkCache = 96,
+    CrstUMEntryThunkFreeListLock = 97,
+    CrstUniqueStack = 98,
+    CrstUnresolvedClassLock = 99,
+    CrstUnwindInfoTableLock = 100,
+    CrstVSDIndirectionCellLock = 101,
+    CrstWrapperTemplate = 102,
+    kNumberOfCrstTypes = 103
 };
 
 #endif // __CRST_TYPES_INCLUDED
@@ -211,7 +210,6 @@ int g_rgCrstLevelMap[] =
     9,          // CrstReadyToRunEntryPointToMethodDescMap
     7,          // CrstReflection
     15,         // CrstReJITGlobalRequest
-    3,          // CrstRetThunkCache
     3,          // CrstSigConvert
     4,          // CrstSingleUseLock
     -1,         // CrstStressLog
@@ -320,7 +318,6 @@ LPCSTR g_rgCrstNameMap[] =
     "CrstReadyToRunEntryPointToMethodDescMap",
     "CrstReflection",
     "CrstReJITGlobalRequest",
-    "CrstRetThunkCache",
     "CrstSigConvert",
     "CrstSingleUseLock",
     "CrstStressLog",
diff --git a/src/coreclr/vm/clrtocomcall.cpp b/src/coreclr/vm/clrtocomcall.cpp
index a9505696385123..73c839d2aa462c 100644
--- a/src/coreclr/vm/clrtocomcall.cpp
+++ b/src/coreclr/vm/clrtocomcall.cpp
@@ -937,76 +937,3 @@ BOOL CLRToCOMMethodFrame::TraceFrame_Impl(Thread *thread, BOOL fromPatch,
 
     return TRUE;
 }
-
-#ifdef TARGET_X86
-
-#ifndef DACCESS_COMPILE
-
-CrstStatic   CLRToCOMCall::s_RetThunkCacheCrst;
-SHash<CLRToCOMCall::RetThunkSHashTraits> *CLRToCOMCall::s_pRetThunkCache = NULL;
-
-// One time init.
-void CLRToCOMCall::Init()
-{
-    CONTRACTL
-    {
-        THROWS;
-        GC_NOTRIGGER;
-        MODE_ANY;
-    }
-    CONTRACTL_END;
-
-    s_RetThunkCacheCrst.Init(CrstRetThunkCache);
-}
-
-LPVOID CLRToCOMCall::GetRetThunk(UINT numStackBytes)
-{
-    STANDARD_VM_CONTRACT;
-
-    LPVOID pRetThunk = NULL;
-    CrstHolder crst(&s_RetThunkCacheCrst);
-
-    // Lazily allocate the ret thunk cache.
-    if (s_pRetThunkCache == NULL)
-        s_pRetThunkCache = new SHash<RetThunkSHashTraits>();
-
-    const RetThunkCacheElement *pElement = s_pRetThunkCache->LookupPtr(numStackBytes);
-    if (pElement != NULL)
-    {
-        pRetThunk = pElement->m_pRetThunk;
-    }
-    else
-    {
-        // cache miss -> create a new thunk
-        AllocMemTracker dummyAmTracker;
-        size_t thunkSize = (numStackBytes == 0) ? 1 : 3;
-        pRetThunk = (LPVOID)dummyAmTracker.Track(SystemDomain::GetGlobalLoaderAllocator()->GetExecutableHeap()->AllocMem(S_SIZE_T(thunkSize)));
-
-        ExecutableWriterHolder<BYTE> thunkWriterHolder((BYTE *)pRetThunk, thunkSize);
-        BYTE *pThunkRW = thunkWriterHolder.GetRW();
-
-        if (numStackBytes == 0)
-        {
-            pThunkRW[0] = 0xc3;
-        }
-        else
-        {
-            pThunkRW[0] = 0xc2;
-            *(USHORT *)&pThunkRW[1] = (USHORT)numStackBytes;
-        }
-
-        // add it to the cache
-        RetThunkCacheElement element;
-        element.m_cbStack = numStackBytes;
-        element.m_pRetThunk = pRetThunk;
-        s_pRetThunkCache->Add(element);
-
-        dummyAmTracker.SuppressRelease();
-    }
-
-    return pRetThunk;
-}
-
-#endif // !DACCESS_COMPILE
-
-#endif // TARGET_X86
diff --git a/src/coreclr/vm/clrtocomcall.h b/src/coreclr/vm/clrtocomcall.h
index 15a3df4b5cf345..4c3189fb5d2f98 100644
--- a/src/coreclr/vm/clrtocomcall.h
+++ b/src/coreclr/vm/clrtocomcall.h
@@ -30,42 +30,8 @@ class CLRToCOMCall
         static PCODE       GetStubForILStub(MethodDesc* pMD, MethodDesc** ppStubMD);
 
         static CLRToCOMCallInfo *PopulateCLRToCOMCallMethodDesc(MethodDesc* pMD, DWORD* pdwStubFlags);
-
-#ifdef TARGET_X86
-        static void Init();
-        static LPVOID GetRetThunk(UINT numStackBytes);
-#endif // TARGET_X86
     private:
         CLRToCOMCall();     // prevent "new"'s on this class
-
-#ifdef TARGET_X86
-    struct RetThunkCacheElement
-    {
-        RetThunkCacheElement()
-        {
-            LIMITED_METHOD_CONTRACT;
-            m_cbStack = 0;
-            m_pRetThunk = NULL;
-        }
-
-        UINT m_cbStack;
-        LPVOID m_pRetThunk;
-    };
-
-    class RetThunkSHashTraits : public NoRemoveSHashTraits< DefaultSHashTraits<RetThunkCacheElement> >
-    {
-    public:
-        typedef UINT key_t;
-        static key_t GetKey(element_t e)       { LIMITED_METHOD_CONTRACT; return e.m_cbStack; }
-        static BOOL Equals(key_t k1, key_t k2) { LIMITED_METHOD_CONTRACT; return (k1 == k2); }
-        static count_t Hash(key_t k)           { LIMITED_METHOD_CONTRACT; return (count_t)(size_t)k; }
-        static const element_t Null()          { LIMITED_METHOD_CONTRACT; return RetThunkCacheElement(); }
-        static bool IsNull(const element_t &e) { LIMITED_METHOD_CONTRACT; return (e.m_pRetThunk == NULL); }
-    };
-
-    static SHash<RetThunkSHashTraits> *s_pRetThunkCache;
-    static CrstStatic s_RetThunkCacheCrst;
-#endif // TARGET_X86
 };
 
 #endif // __CLRTOCOMCALL_H__
diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp
index 858c42329c0720..15b4bb31fc669e 100644
--- a/src/coreclr/vm/dllimport.cpp
+++ b/src/coreclr/vm/dllimport.cpp
@@ -5763,9 +5763,11 @@ void CreateCLRToDispatchCOMStub(
                             iLCIDArg);
 
     _ASSERTE(pMD->IsCLRToCOMCall()); // no generic disp-calls
-    ((CLRToCOMCallMethodDesc *)pMD)->InitRetThunk();
-}
 
+#ifdef TARGET_X86
+    ((CLRToCOMCallMethodDesc *)pMD)->InitStackPop();
+#endif
+}
 
 #endif // FEATURE_COMINTEROP
 
diff --git a/src/coreclr/vm/i386/asmconstants.h b/src/coreclr/vm/i386/asmconstants.h
index b80b459c52e548..c594922a195224 100644
--- a/src/coreclr/vm/i386/asmconstants.h
+++ b/src/coreclr/vm/i386/asmconstants.h
@@ -227,8 +227,8 @@ ASMCONSTANTS_C_ASSERT(OFFSETOF__FrameHandlerExRecord__m_pEntryFrame == offsetof(
 #define CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo DBG_FRE(0x20, 0xC)
 ASMCONSTANTS_C_ASSERT(CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo == offsetof(CLRToCOMCallMethodDesc, m_pCLRToCOMCallInfo))
 
-#define CLRToCOMCallInfo__m_pRetThunk 0x10
-ASMCONSTANTS_C_ASSERT(CLRToCOMCallInfo__m_pRetThunk == offsetof(CLRToCOMCallInfo, m_pRetThunk))
+#define CLRToCOMCallInfo__m_cbStackPop 0x0e
+ASMCONSTANTS_C_ASSERT(CLRToCOMCallInfo__m_cbStackPop == offsetof(CLRToCOMCallInfo, m_cbStackPop))
 
 #define COMMETHOD_PREPAD_ASM  8
 ASMCONSTANTS_C_ASSERT(COMMETHOD_PREPAD_ASM == COMMETHOD_PREPAD)
diff --git a/src/coreclr/vm/i386/asmhelpers.asm b/src/coreclr/vm/i386/asmhelpers.asm
index e64931d6405882..21325c80c87966 100644
--- a/src/coreclr/vm/i386/asmhelpers.asm
+++ b/src/coreclr/vm/i386/asmhelpers.asm
@@ -1290,14 +1290,19 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
+    ; Get the return address, pushed registers on stack are 24 bytes big
+    mov         ebx, [esp + 24]
+    ; Set the return address on stack at the last stack slot
+    mov         [esp + ecx + 24], ebx
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Move esp to point to the last stack slot where we put the return
+    ; address earlier
+    lea         esp, [esp + ecx]
 
-    ; This will never be executed. It is just to help out stack-walking logic
-    ; which disassembles the epilog to unwind the stack.
     ret
 
 _GenericCLRToCOMCallStub@0 endp
diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp
index 83657cf3759b1c..87870f5e00eb5b 100644
--- a/src/coreclr/vm/interoputil.cpp
+++ b/src/coreclr/vm/interoputil.cpp
@@ -3673,9 +3673,6 @@ void InitializeComInterop()
 
     InitializeSListHead(&RCW::s_RCWStandbyList);
     ComCall::Init();
-#ifdef TARGET_X86
-    CLRToCOMCall::Init();
-#endif
     CtxEntryCache::Init();
     ComCallWrapperTemplate::Init();
 #ifdef _DEBUG
diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp
index 198881236a2c3a..32c6860c202483 100644
--- a/src/coreclr/vm/method.cpp
+++ b/src/coreclr/vm/method.cpp
@@ -3810,26 +3810,6 @@ PrecodeType MethodDesc::GetPrecodeType()
 
 #endif // !DACCESS_COMPILE
 
-#ifdef FEATURE_COMINTEROP
-#ifndef DACCESS_COMPILE
-void CLRToCOMCallMethodDesc::InitRetThunk()
-{
-    WRAPPER_NO_CONTRACT;
-
-#ifdef TARGET_X86
-    if (m_pCLRToCOMCallInfo->m_pRetThunk != NULL)
-        return;
-
-    UINT numStackBytes = CbStackPop();
-
-    LPVOID pRetThunk = CLRToCOMCall::GetRetThunk(numStackBytes);
-
-    InterlockedCompareExchangeT<void *>(&m_pCLRToCOMCallInfo->m_pRetThunk, pRetThunk, NULL);
-#endif // TARGET_X86
-}
-#endif //!DACCESS_COMPILE
-#endif // FEATURE_COMINTEROP
-
 #ifndef DACCESS_COMPILE
 void MethodDesc::PrepareForUseAsADependencyOfANativeImageWorker()
 {
diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp
index 8fe4987c5d305b..51b827c447b05b 100644
--- a/src/coreclr/vm/method.hpp
+++ b/src/coreclr/vm/method.hpp
@@ -3150,6 +3150,8 @@ struct CLRToCOMCallInfo
     // Size of outgoing arguments (on stack). This is currently used only
     // on x86 when we have an InlinedCallFrame representing a CLR->COM call.
     WORD        m_cbStackArgumentSize;
+    // Size of incoming arguments (on stack).
+    WORD        m_cbStackPop;
 
     void InitStackArgumentSize()
     {
@@ -3177,9 +3179,6 @@ struct CLRToCOMCallInfo
         _ASSERTE(m_cbStackArgumentSize != 0xFFFF);
         return m_cbStackArgumentSize;
     }
-
-    LPVOID      m_pRetThunk;
-
 #else // TARGET_X86
     void InitStackArgumentSize()
     {
@@ -3204,7 +3203,6 @@ class CLRToCOMCallMethodDesc : public MethodDesc
 public:
     CLRToCOMCallInfo *m_pCLRToCOMCallInfo; // initialized in code:CLRToCOMCall.PopulateCLRToCOMCallMethodDesc
 
-    void InitRetThunk();
     void InitComEventCallInfo();
 
     PCODE * GetAddrOfILStubField()
@@ -3254,6 +3252,12 @@ class CLRToCOMCallMethodDesc : public MethodDesc
         LIMITED_METHOD_CONTRACT;
         m_pCLRToCOMCallInfo->SetStackArgumentSize(cbDstBuffer);
     }
+
+    void InitStackPop()
+    {
+        LIMITED_METHOD_CONTRACT;
+        m_pCLRToCOMCallInfo->m_cbStackPop = (WORD)CbStackPop();
+    }
 #else // TARGET_X86
     void SetStackArgumentSize(WORD cbDstBuffer)
     {


Reviews:
user: login: huoyaoyuan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T14:21:01Z
user: login: filipnavara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T14:33:41Z
user: login: jkotas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T16:34:37Z
user: login: filipnavara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T16:39:15Z
user: login: jkotas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T17:06:41Z
user: login: filipnavara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T18:32:11Z
user: login: filipnavara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T19:43:23Z
user: login: jkotas
body: There is an existing pattern used for this in this .asm file. Would it make sense to do the same here?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T21:38:42Z
user: login: filipnavara
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T21:42:37Z
user: login: jkotas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T21:58:59Z
user: login: jkotas
body: Thanks
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-28T22:55:20Z

Review comments:
diff_hunk: @@ -937,76 +937,3 @@ BOOL CLRToCOMMethodFrame::TraceFrame_Impl(Thread *thread, BOOL fromPatch,
 
     return TRUE;
 }
-
-#ifdef TARGET_X86
-
-#ifndef DACCESS_COMPILE
-
-CrstStatic   CLRToCOMCall::s_RetThunkCacheCrst;
-SHash<CLRToCOMCall::RetThunkSHashTraits> *CLRToCOMCall::s_pRetThunkCache = NULL;
-
-// One time init.
-void CLRToCOMCall::Init()
-{
-    CONTRACTL
-    {
-        THROWS;
-        GC_NOTRIGGER;
-        MODE_ANY;
-    }
-    CONTRACTL_END;
-
-    s_RetThunkCacheCrst.Init(CrstRetThunkCache);
path: src/coreclr/vm/clrtocomcall.cpp
user: login: huoyaoyuan
body: The crst level can be removed from `inc\CrstTypes.def` and generated `crsttypes_generated.h`.
created_at: 2025-04-27T14:21:01Z
author_association: MEMBER
diff_hunk: @@ -937,76 +937,3 @@ BOOL CLRToCOMMethodFrame::TraceFrame_Impl(Thread *thread, BOOL fromPatch,
 
     return TRUE;
 }
-
-#ifdef TARGET_X86
-
-#ifndef DACCESS_COMPILE
-
-CrstStatic   CLRToCOMCall::s_RetThunkCacheCrst;
-SHash<CLRToCOMCall::RetThunkSHashTraits> *CLRToCOMCall::s_pRetThunkCache = NULL;
-
-// One time init.
-void CLRToCOMCall::Init()
-{
-    CONTRACTL
-    {
-        THROWS;
-        GC_NOTRIGGER;
-        MODE_ANY;
-    }
-    CONTRACTL_END;
-
-    s_RetThunkCacheCrst.Init(CrstRetThunkCache);
path: src/coreclr/vm/clrtocomcall.cpp
user: login: filipnavara
body: Good catch
created_at: 2025-04-27T14:33:41Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,17 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Pop the incoming arguments off the stack and push back the return
+    ; address on the last slot
+    lea         esp, [esp + ecx + 4]
+    xor         ecx, -1
+    push        dword ptr [esp + ecx - 3]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: jkotas
body: Can the stack at negative offset be overwritten between `lea         esp` and `push`, e.g. using sampling profiler? I do not think there is a concept of red zone on x86.
created_at: 2025-04-28T16:34:37Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,17 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Pop the incoming arguments off the stack and push back the return
+    ; address on the last slot
+    lea         esp, [esp + ecx + 4]
+    xor         ecx, -1
+    push        dword ptr [esp + ecx - 3]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: filipnavara
body: Hmm, fair point. I will have to think more about it and perhaps find a different assembly pattern (can always temporarily push one register, move the return address first, then pop the temp register, and finally move esp).
created_at: 2025-04-28T16:39:15Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,17 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Pop the incoming arguments off the stack and push back the return
+    ; address on the last slot
+    lea         esp, [esp + ecx + 4]
+    xor         ecx, -1
+    push        dword ptr [esp + ecx - 3]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: jkotas
body: You should be able to copy the return address to the right spot first and then move esp.
created_at: 2025-04-28T17:06:41Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,17 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Pop the incoming arguments off the stack and push back the return
+    ; address on the last slot
+    lea         esp, [esp + ecx + 4]
+    xor         ecx, -1
+    push        dword ptr [esp + ecx - 3]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: filipnavara
body: Yeah, I just need to reorder it a bit to have the extra temp register but it's definitely doable.
created_at: 2025-04-28T18:32:11Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,17 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Pop the incoming arguments off the stack and push back the return
+    ; address on the last slot
+    lea         esp, [esp + ecx + 4]
+    xor         ecx, -1
+    push        dword ptr [esp + ecx - 3]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: filipnavara
body: Hopefully fixed. It passed the tests on my machine.
created_at: 2025-04-28T19:43:22Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,19 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
+    ; Get the return address, pushed registers on stack are 24 bytes big
+    mov         ebx, [esp + 24]
+    ; Set the return address on stack at the last stack slot
+    mov         [esp + ecx + 24], ebx
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: jkotas
body: ```suggestion
    movzx       edx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
```
This does not return anything in edx, so we can just use it to pass the amount to pop
created_at: 2025-04-28T21:34:49Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,19 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
+    ; Get the return address, pushed registers on stack are 24 bytes big
+    mov         ebx, [esp + 24]
+    ; Set the return address on stack at the last stack slot
+    mov         [esp + ecx + 24], ebx
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Move esp to point to the last stack slot where we put the return
+    ; address earlier
+    lea         esp, [esp + ecx]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: jkotas
body: ```suggestion
    pop     ecx                 ; return address
    add     esp, edx            ; pop bytes of the stack
    push    ecx                 ; return address
```
And then use the exact same sequence we use in other place in this file https://github.com/dotnet/runtime/blob/29e3af6b30656a514acac92e7989835e2c025e6f/src/coreclr/vm/i386/asmhelpers.asm#L1422-L1424
created_at: 2025-04-28T21:35:57Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,19 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
+    ; Get the return address, pushed registers on stack are 24 bytes big
+    mov         ebx, [esp + 24]
+    ; Set the return address on stack at the last stack slot
+    mov         [esp + ecx + 24], ebx
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Move esp to point to the last stack slot where we put the return
+    ; address earlier
+    lea         esp, [esp + ecx]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: filipnavara
body: We don't have the luxury of extra register here. EAX:EDX is used as return value and needs to be preserved.

```asm
    push        eax
    call        _setFPReturn@12     ; pop & set the return value
    ; From here on, mustn't trash eax:edx
```
created_at: 2025-04-28T21:42:36Z
author_association: MEMBER
diff_hunk: @@ -1290,14 +1290,19 @@ _GenericCLRToCOMCallStub@0 proc public
 
     ; Get pCLRToCOMCallInfo for return thunk
     mov         ecx, [ebx + CLRToCOMCallMethodDesc__m_pCLRToCOMCallInfo]
+    ; Get size of arguments to pop
+    movzx       ecx, word ptr [ecx + CLRToCOMCallInfo__m_cbStackPop]
+    ; Get the return address, pushed registers on stack are 24 bytes big
+    mov         ebx, [esp + 24]
+    ; Set the return address on stack at the last stack slot
+    mov         [esp + ecx + 24], ebx
 
     STUB_EPILOG_RETURN
 
-    ; Tailcall return thunk
-    jmp [ecx + CLRToCOMCallInfo__m_pRetThunk]
+    ; Move esp to point to the last stack slot where we put the return
+    ; address earlier
+    lea         esp, [esp + ecx]
path: src/coreclr/vm/i386/asmhelpers.asm
user: login: jkotas
body: Ah ok, I have misread how this works.
created_at: 2025-04-28T21:58:59Z
author_association: MEMBER

PR comments:
user: login: filipnavara
created_at: 2025-04-27T20:32:15Z
body: The motivation is mainly to reduce the number of unnecessary usages of `ExecutableWriteHolder` for dynamic codegen. While the assembly code is not the prettiest due to restriction to use only single GP register it's still simple arithmetic instead of an indirect jump. Given the code around it the performance difference should be negligable. We still use `ret` so call-ret prediction for speculative execution should work just fine.
author_association: MEMBER
user: login: jkotas
created_at: 2025-04-28T22:54:56Z
body: /ba-g deadletter
author_association: MEMBER