Fix tailcall regression with compiled F# (#41206)
authorJan Kotas <jkotas@microsoft.com>
Tue, 25 Aug 2020 14:17:41 +0000 (07:17 -0700)
committerGitHub <noreply@github.com>
Tue, 25 Aug 2020 14:17:41 +0000 (07:17 -0700)
* Fix tailcall regression with compiled F#

This change skips instantiating stubs for direct tailcalls and instead passes the inst argument directly to the target method.

Fixes #40864

* Add regression test

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
13 files changed:
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/morph.cpp
src/coreclr/src/vm/corelib.h
src/coreclr/src/vm/ecalllist.h
src/coreclr/src/vm/gcenv.ee.cpp
src/coreclr/src/vm/jitinterface.cpp
src/coreclr/src/vm/tailcallhelp.cpp
src/coreclr/src/vm/tailcallhelp.h
src/coreclr/src/vm/threads.cpp
src/coreclr/src/vm/threads.h
src/tests/JIT/Directed/tailcall/more_tailcalls.cs
src/tests/JIT/Directed/tailcall/more_tailcalls.il

index 5c9a38e..6b6e463 100644 (file)
@@ -287,9 +287,6 @@ namespace System.Runtime.CompilerServices
         private static extern IntPtr AllocTailCallArgBuffer(int size, IntPtr gcDesc);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void FreeTailCallArgBuffer();
-
-        [MethodImpl(MethodImplOptions.InternalCall)]
         private static unsafe extern TailCallTls* GetTailCallInfo(IntPtr retAddrSlot, IntPtr* retAddr);
 
         private static unsafe void DispatchTailCalls(
@@ -323,6 +320,12 @@ namespace System.Runtime.CompilerServices
             finally
             {
                 tls->Frame = prevFrame;
+
+                // If the arg buffer is reporting inst argument, it is safe to abandon it now
+                if (tls->ArgBuffer != IntPtr.Zero && *(int*)tls->ArgBuffer == 1 /* TAILCALLARGBUFFER_INSTARG_ONLY */)
+                {
+                    *(int*)tls->ArgBuffer = 2 /* TAILCALLARGBUFFER_ABANDONED */;
+                }
             }
         }
 
@@ -481,9 +484,6 @@ namespace System.Runtime.CompilerServices
     {
         public PortableTailCallFrame* Frame;
         public IntPtr ArgBuffer;
-        private IntPtr _argBufferSize;
-        private IntPtr _argBufferGCDesc;
-        private fixed byte _argBufferInline[64];
     }
 
 }
index e6788dd..e94dc8a 100644 (file)
@@ -5579,7 +5579,6 @@ private:
     GenTree* fgCreateCallDispatcherAndGetResult(GenTreeCall*          origCall,
                                                 CORINFO_METHOD_HANDLE callTargetStubHnd,
                                                 CORINFO_METHOD_HANDLE dispatcherHnd);
-    GenTree* getMethodPointerTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo);
     GenTree* getLookupTree(CORINFO_RESOLVED_TOKEN* pResolvedToken,
                            CORINFO_LOOKUP*         pLookup,
                            unsigned                handleFlags,
index 844692c..5430a2b 100644 (file)
@@ -7700,6 +7700,8 @@ GenTree* Compiler::fgMorphTailCallViaHelpers(GenTreeCall* call, CORINFO_TAILCALL
     assert(!call->IsImplicitTailCall());
     assert(!fgCanFastTailCall(call, nullptr));
 
+    bool virtualCall = call->IsVirtual();
+
     // If VSD then get rid of arg to VSD since we turn this into a direct call.
     // The extra arg will be the first arg so this needs to be done before we
     // handle the retbuf below.
@@ -7734,41 +7736,39 @@ GenTree* Compiler::fgMorphTailCallViaHelpers(GenTreeCall* call, CORINFO_TAILCALL
     // where we pass instantiating stub.
     if ((help.flags & CORINFO_TAILCALL_STORE_TARGET) != 0)
     {
-        // If asked to store target and we have a type arg we will store
-        // instantiating stub, so in that case we should not pass the type arg.
-        if (call->tailCallInfo->GetSig()->hasTypeArg())
+        JITDUMP("Adding target since VM requested it\n");
+        GenTree* target;
+        if (!virtualCall)
         {
-            JITDUMP("Removing type arg");
-
-            assert(call->gtCallArgs != nullptr);
-            if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L)
+            if (call->gtCallType == CT_INDIRECT)
             {
-                // Generic context is first arg
-                call->gtCallArgs = call->gtCallArgs->GetNext();
+                noway_assert(call->gtCallAddr != nullptr);
+                target = call->gtCallAddr;
             }
             else
             {
-                // Generic context is last arg
-                GenTreeCall::Use** lastArgSlot = &call->gtCallArgs;
-                while ((*lastArgSlot)->GetNext() != nullptr)
+                CORINFO_CONST_LOOKUP addrInfo;
+                info.compCompHnd->getFunctionEntryPoint(call->gtCallMethHnd, &addrInfo);
+
+                CORINFO_GENERIC_HANDLE handle       = nullptr;
+                void*                  pIndirection = nullptr;
+                assert(addrInfo.accessType != IAT_PPVALUE && addrInfo.accessType != IAT_RELPVALUE);
+
+                if (addrInfo.accessType == IAT_VALUE)
                 {
-                    lastArgSlot = &(*lastArgSlot)->NextRef();
+                    handle = addrInfo.handle;
                 }
-
-                *lastArgSlot = nullptr;
+                else if (addrInfo.accessType == IAT_PVALUE)
+                {
+                    pIndirection = addrInfo.addr;
+                }
+                target = gtNewIconEmbHndNode(handle, pIndirection, GTF_ICON_FTN_ADDR, call->gtCallMethHnd);
             }
-            call->fgArgInfo = nullptr;
-        }
-
-        JITDUMP("Adding target since VM requested it\n");
-        GenTree* target;
-        if (call->tailCallInfo->IsCalli())
-        {
-            noway_assert(call->gtCallType == CT_INDIRECT && call->gtCallAddr != nullptr);
-            target = call->gtCallAddr;
         }
         else
         {
+            assert(!call->tailCallInfo->GetSig()->hasTypeArg());
+
             CORINFO_CALL_INFO callInfo;
             unsigned          flags = CORINFO_CALLINFO_LDFTN;
             if (call->tailCallInfo->IsCallvirt())
@@ -7778,19 +7778,10 @@ GenTree* Compiler::fgMorphTailCallViaHelpers(GenTreeCall* call, CORINFO_TAILCALL
 
             eeGetCallInfo(call->tailCallInfo->GetToken(), nullptr, (CORINFO_CALLINFO_FLAGS)flags, &callInfo);
 
-            if (!call->tailCallInfo->IsCallvirt() ||
-                ((callInfo.methodFlags & (CORINFO_FLG_FINAL | CORINFO_FLG_STATIC)) != 0) ||
-                ((callInfo.methodFlags & CORINFO_FLG_VIRTUAL) == 0))
-            {
-                target = getMethodPointerTree(call->tailCallInfo->GetToken(), &callInfo);
-            }
-            else
-            {
-                assert(call->gtCallThisArg != nullptr);
-                // TODO: Proper cloning of the this pointer.
-                target = getVirtMethodPointerTree(gtCloneExpr(call->gtCallThisArg->GetNode()),
-                                                  call->tailCallInfo->GetToken(), &callInfo);
-            }
+            assert(call->gtCallThisArg != nullptr);
+            // TODO: Proper cloning of the this pointer.
+            target = getVirtMethodPointerTree(gtCloneExpr(call->gtCallThisArg->GetNode()),
+                                              call->tailCallInfo->GetToken(), &callInfo);
         }
 
         // Insert target as last arg
@@ -8045,30 +8036,6 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall*          orig
 }
 
 //------------------------------------------------------------------------
-// getMethodPointerTree: get a method pointer tree
-//
-// Arguments:
-//    pResolvedToken - resolved token of the call
-//    pCallInfo - the call info of the call
-//
-// Return Value:
-//    A node representing the method pointer
-//
-GenTree* Compiler::getMethodPointerTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo)
-{
-    switch (pCallInfo->kind)
-    {
-        case CORINFO_CALL:
-            return new (this, GT_FTN_ADDR) GenTreeFptrVal(TYP_I_IMPL, pCallInfo->hMethod);
-        case CORINFO_CALL_CODE_POINTER:
-            return getLookupTree(pResolvedToken, &pCallInfo->codePointerLookup, GTF_ICON_FTN_ADDR, pCallInfo->hMethod);
-        default:
-            noway_assert(!"unknown call kind");
-            return nullptr;
-    }
-}
-
-//------------------------------------------------------------------------
 // getLookupTree: get a lookup tree
 //
 // Arguments:
index 5b08cc4..044147c 100644 (file)
@@ -708,7 +708,6 @@ DEFINE_METHOD(RUNTIME_HELPERS,      ENUM_EQUALS,            EnumEquals, NoSig)
 DEFINE_METHOD(RUNTIME_HELPERS,      ENUM_COMPARE_TO,        EnumCompareTo, NoSig)
 DEFINE_METHOD(RUNTIME_HELPERS,      ALLOC_TAILCALL_ARG_BUFFER, AllocTailCallArgBuffer,  SM_Int_IntPtr_RetIntPtr)
 DEFINE_METHOD(RUNTIME_HELPERS,      GET_TAILCALL_INFO,      GetTailCallInfo, NoSig)
-DEFINE_METHOD(RUNTIME_HELPERS,      FREE_TAILCALL_ARG_BUFFER, FreeTailCallArgBuffer,    SM_RetVoid)
 DEFINE_METHOD(RUNTIME_HELPERS,      DISPATCH_TAILCALLS,     DispatchTailCalls,          NoSig)
 
 DEFINE_CLASS(UNSAFE,                InternalCompilerServices,       Unsafe)
@@ -760,9 +759,6 @@ DEFINE_FIELD(PORTABLE_TAIL_CALL_FRAME, NEXT_CALL,                     NextCall)
 DEFINE_CLASS(TAIL_CALL_TLS,            CompilerServices,              TailCallTls)
 DEFINE_FIELD(TAIL_CALL_TLS,            FRAME,                         Frame)
 DEFINE_FIELD(TAIL_CALL_TLS,            ARG_BUFFER,                    ArgBuffer)
-DEFINE_FIELD(TAIL_CALL_TLS,            ARG_BUFFER_SIZE,               _argBufferSize)
-DEFINE_FIELD(TAIL_CALL_TLS,            ARG_BUFFER_GC_DESC,            _argBufferGCDesc)
-DEFINE_FIELD(TAIL_CALL_TLS,            ARG_BUFFER_INLINE,             _argBufferInline)
 
 DEFINE_CLASS_U(CompilerServices,           PortableTailCallFrame, PortableTailCallFrame)
 DEFINE_FIELD_U(Prev,                       PortableTailCallFrame, Prev)
@@ -772,10 +768,6 @@ DEFINE_FIELD_U(NextCall,                   PortableTailCallFrame, NextCall)
 DEFINE_CLASS_U(CompilerServices,           TailCallTls,           TailCallTls)
 DEFINE_FIELD_U(Frame,                      TailCallTls,           m_frame)
 DEFINE_FIELD_U(ArgBuffer,                  TailCallTls,           m_argBuffer)
-DEFINE_FIELD_U(_argBufferSize,             TailCallTls,           m_argBufferSize)
-DEFINE_FIELD_U(_argBufferGCDesc,           TailCallTls,           m_argBufferGCDesc)
-DEFINE_FIELD_U(_argBufferInline,           TailCallTls,           m_argBufferInline)
-
 
 DEFINE_CLASS(RUNTIME_WRAPPED_EXCEPTION, CompilerServices,   RuntimeWrappedException)
 DEFINE_METHOD(RUNTIME_WRAPPED_EXCEPTION, OBJ_CTOR,          .ctor,                      IM_Obj_RetVoid)
index f1827fa..2c2dd51 100644 (file)
@@ -881,7 +881,6 @@ FCFuncStart(gRuntimeHelpers)
     FCFuncElement("GetUninitializedObjectInternal", ReflectionSerialization::GetUninitializedObject)
     QCFuncElement("AllocateTypeAssociatedMemoryInternal", RuntimeTypeHandle::AllocateTypeAssociatedMemory)
     FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer)
-    FCFuncElement("FreeTailCallArgBuffer", TailCallHelp::FreeTailCallArgBuffer)
     FCFuncElement("GetTailCallInfo", TailCallHelp::GetTailCallInfo)
     FCFuncElement("GetILBytesJitted", GetJittedBytes)
     FCFuncElement("GetMethodsJittedCount", GetJittedMethodsCount)
index 54bde52..6eaf176 100644 (file)
@@ -157,27 +157,33 @@ static void ScanStackRoots(Thread * pThread, promote_func* fn, ScanContext* sc)
 
 static void ScanTailCallArgBufferRoots(Thread* pThread, promote_func* fn, ScanContext* sc)
 {
-    void* gcDesc;
-    char* argBuffer = pThread->GetTailCallTls()->GetArgBuffer(&gcDesc);
-    if (gcDesc == NULL)
+    TailCallArgBuffer* argBuffer = pThread->GetTailCallTls()->GetArgBuffer();
+    if (argBuffer == NULL || argBuffer->GCDesc == NULL)
         return;
 
-    GCRefMapDecoder decoder(static_cast<PTR_BYTE>(gcDesc));
+    if (argBuffer->State == TAILCALLARGBUFFER_ABANDONED)
+        return;
+
+    bool instArgOnly = argBuffer->State == TAILCALLARGBUFFER_INSTARG_ONLY;
+
+    GCRefMapDecoder decoder(static_cast<PTR_BYTE>(argBuffer->GCDesc));
     while (!decoder.AtEnd())
     {
         int pos = decoder.CurrentPos();
         int token = decoder.ReadToken();
 
-        PTR_TADDR ppObj = dac_cast<PTR_TADDR>(argBuffer + pos * sizeof(TADDR));
+        PTR_TADDR ppObj = dac_cast<PTR_TADDR>(((BYTE*)argBuffer->Args) + pos * sizeof(TADDR));
         switch (token)
         {
         case GCREFMAP_SKIP:
             break;
         case GCREFMAP_REF:
-            fn(dac_cast<PTR_PTR_Object>(ppObj), sc, CHECK_APP_DOMAIN);
+            if (!instArgOnly)
+                fn(dac_cast<PTR_PTR_Object>(ppObj), sc, CHECK_APP_DOMAIN);
             break;
         case GCREFMAP_INTERIOR:
-            PromoteCarefully(fn, dac_cast<PTR_PTR_Object>(ppObj), sc, GC_CALL_INTERIOR);
+            if (!instArgOnly)
+                PromoteCarefully(fn, dac_cast<PTR_PTR_Object>(ppObj), sc, GC_CALL_INTERIOR);
             break;
         case GCREFMAP_METHOD_PARAM:
             if (sc->promotion)
index dbf4ec7..37bede3 100644 (file)
@@ -13952,7 +13952,7 @@ bool CEEInfo::getTailCallHelpersInternal(CORINFO_RESOLVED_TOKEN* callToken,
 
     TailCallHelp::CreateTailCallHelperStubs(
         m_pMethodBeingCompiled, pTargetMD,
-        msig, isCallvirt, isThisArgByRef,
+        msig, isCallvirt, isThisArgByRef, sig->hasTypeArg(),
         &pStoreArgsMD, &needsTarget,
         &pCallTargetMD);
 
index 0db1330..10a9c50 100644 (file)
@@ -23,7 +23,7 @@ FCIMPL2(void*, TailCallHelp::AllocTailCallArgBuffer, INT32 size, void* gcDesc)
 
     _ASSERTE(size >= 0);
 
-    void* result = GetThread()->GetTailCallTls()->AllocArgBuffer(static_cast<size_t>(size), gcDesc);
+    void* result = GetThread()->GetTailCallTls()->AllocArgBuffer(size, gcDesc);
     
     if (result == NULL)
         FCThrow(kOutOfMemoryException);
@@ -32,14 +32,6 @@ FCIMPL2(void*, TailCallHelp::AllocTailCallArgBuffer, INT32 size, void* gcDesc)
 }
 FCIMPLEND
 
-FCIMPL0(void, TailCallHelp::FreeTailCallArgBuffer)
-{
-    FCALL_CONTRACT;
-    
-    GetThread()->GetTailCallTls()->FreeArgBuffer();
-}
-FCIMPLEND
-
 FCIMPL2(void*, TailCallHelp::GetTailCallInfo, void** retAddrSlot, void** retAddr)
 {
     FCALL_CONTRACT;
@@ -67,12 +59,14 @@ struct ArgBufferValue
 struct ArgBufferLayout
 {
     bool HasTargetAddress;
+    bool HasInstArg;
     unsigned int TargetAddressOffset;
     InlineSArray<ArgBufferValue, 8> Values;
     unsigned int Size;
 
     ArgBufferLayout()
         : HasTargetAddress(false)
+        , HasInstArg(false)
         , TargetAddressOffset(0)
         , Size(0)
     {
@@ -133,7 +127,7 @@ MethodDesc* TailCallHelp::GetTailCallDispatcherMD()
 
 void TailCallHelp::CreateTailCallHelperStubs(
     MethodDesc* pCallerMD, MethodDesc* pCalleeMD,
-    MetaSig& callSiteSig, bool virt, bool thisArgByRef,
+    MetaSig& callSiteSig, bool virt, bool thisArgByRef, bool hasInstArg,
     MethodDesc** storeArgsStub, bool* storeArgsNeedsTarget,
     MethodDesc** callTargetStub)
 {
@@ -154,7 +148,7 @@ void TailCallHelp::CreateTailCallHelperStubs(
     TypeHandle retTyHnd = NormalizeSigType(callSiteSig.GetRetTypeHandleThrowing());
     TailCallInfo info(pCallerMD, pCalleeMD, pLoaderAllocator, &callSiteSig, virt, retTyHnd);
 
-    LayOutArgBuffer(callSiteSig, pCalleeMD, *storeArgsNeedsTarget, thisArgByRef, &info.ArgBufLayout);
+    LayOutArgBuffer(callSiteSig, pCalleeMD, *storeArgsNeedsTarget, thisArgByRef, hasInstArg, &info.ArgBufLayout);
     info.HasGCDescriptor = GenerateGCDescriptor(pCalleeMD, info.ArgBufLayout, &info.GCRefMapBuilder);
 
     *storeArgsStub = CreateStoreArgsStub(info);
@@ -163,9 +157,9 @@ void TailCallHelp::CreateTailCallHelperStubs(
 
 void TailCallHelp::LayOutArgBuffer(
     MetaSig& callSiteSig, MethodDesc* calleeMD,
-    bool storeTarget, bool thisArgByRef, ArgBufferLayout* layout)
+    bool storeTarget, bool thisArgByRef, bool hasInstArg, ArgBufferLayout* layout)
 {
-    unsigned int offs = 0;
+    unsigned int offs = offsetof(TailCallArgBuffer, Args);
 
     auto addValue = [&](TypeHandle th)
     {
@@ -194,6 +188,15 @@ void TailCallHelp::LayOutArgBuffer(
         addValue(thisHnd);
     }
 
+    layout->HasInstArg = hasInstArg;
+
+#ifndef TARGET_X86
+    if (hasInstArg)
+    {
+        addValue(TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_I)));
+    }
+#endif
+
     callSiteSig.Reset();
     CorElementType ty;
     while ((ty = callSiteSig.NextArg()) != ELEMENT_TYPE_END)
@@ -203,6 +206,13 @@ void TailCallHelp::LayOutArgBuffer(
         addValue(tyHnd);
     }
 
+#ifdef TARGET_X86
+    if (hasInstArg)
+    {
+        addValue(TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_I)));
+    }
+#endif
+
     if (storeTarget)
     {
         offs = AlignUp(offs, TARGET_POINTER_SIZE);
@@ -247,35 +257,49 @@ TypeHandle TailCallHelp::NormalizeSigType(TypeHandle tyHnd)
 bool TailCallHelp::GenerateGCDescriptor(
     MethodDesc* pTargetMD, const ArgBufferLayout& layout, GCRefMapBuilder* builder)
 {
-    auto writeGCType = [&](unsigned int offset, CorInfoGCType type)
+    auto writeGCType = [&](unsigned int argPos, CorInfoGCType type)
     {
-        _ASSERTE(offset % TARGET_POINTER_SIZE == 0);
         switch (type)
         {
-            case TYPE_GC_REF: builder->WriteToken(offset / TARGET_POINTER_SIZE, GCREFMAP_REF); break;
-            case TYPE_GC_BYREF: builder->WriteToken(offset / TARGET_POINTER_SIZE, GCREFMAP_INTERIOR); break;
+            case TYPE_GC_REF: builder->WriteToken(argPos, GCREFMAP_REF); break;
+            case TYPE_GC_BYREF: builder->WriteToken(argPos, GCREFMAP_INTERIOR); break;
             case TYPE_GC_NONE: break;
             default: UNREACHABLE_MSG("Invalid type"); break;
         }
     };
 
+    bool reportInstArg = layout.HasInstArg;
+
     CQuickBytes gcPtrs;
     for (COUNT_T i = 0; i < layout.Values.GetCount(); i++)
     {
         const ArgBufferValue& val = layout.Values[i];
 
+        unsigned int argPos = (val.Offset - offsetof(TailCallArgBuffer, Args)) / TARGET_POINTER_SIZE;
+
         TypeHandle tyHnd = val.TyHnd;
         if (tyHnd.IsValueType())
         {
             if (!tyHnd.GetMethodTable()->ContainsPointers())
+            {
+#ifndef TARGET_X86
+                // The generic instantiation arg is right after this pointer
+                if (reportInstArg)
+                {
+                    _ASSERTE(i == 0 || i == 1);
+                    builder->WriteToken(argPos, pTargetMD->RequiresInstMethodDescArg() ? GCREFMAP_METHOD_PARAM : GCREFMAP_TYPE_PARAM);
+                    reportInstArg = false;
+                }
+#endif
                 continue;
+            }
 
             unsigned int numSlots = (tyHnd.GetSize() + TARGET_POINTER_SIZE - 1) / TARGET_POINTER_SIZE;
             BYTE* ptr = static_cast<BYTE*>(gcPtrs.AllocThrows(numSlots));
             CEEInfo::getClassGClayoutStatic(tyHnd, ptr);
             for (unsigned int i = 0; i < numSlots; i++)
             {
-                writeGCType(val.Offset + i * TARGET_POINTER_SIZE, (CorInfoGCType)ptr[i]);
+                writeGCType(argPos + i , (CorInfoGCType)ptr[i]);
             }
 
             continue;
@@ -284,9 +308,19 @@ bool TailCallHelp::GenerateGCDescriptor(
         CorElementType ety = tyHnd.GetSignatureCorElementType();
         CorInfoGCType gc = CorTypeInfo::GetGCType(ety);
 
-        writeGCType(val.Offset, gc);
+        writeGCType(argPos, gc);
     }
 
+#ifdef TARGET_X86
+    // The generic instantiation arg is last
+    if (reportInstArg)
+    {
+        const ArgBufferValue& val = layout.Values[layout.Values.GetCount() - 1];
+        unsigned int argPos = (val.Offset - offsetof(TailCallArgBuffer, Args)) / TARGET_POINTER_SIZE;
+        builder->WriteToken(argPos, pTargetMD->RequiresInstMethodDescArg() ? GCREFMAP_METHOD_PARAM : GCREFMAP_TYPE_PARAM);
+    }
+#endif
+
     builder->Flush();
 
     return builder->GetBlobLength() > 0;
@@ -331,29 +365,24 @@ MethodDesc* TailCallHelp::CreateStoreArgsStub(TailCallInfo& info)
     auto emitOffs = [&](UINT offs)
     {
         pCode->EmitLDLOC(bufferLcl);
-        if (offs != 0)
-        {
-            pCode->EmitLDC(offs);
-            pCode->EmitADD();
-        }
+        pCode->EmitLDC(offs);
+        pCode->EmitADD();
     };
 
-    unsigned int argIndex = 0;
-
     for (COUNT_T i = 0; i < info.ArgBufLayout.Values.GetCount(); i++)
     {
         const ArgBufferValue& arg = info.ArgBufLayout.Values[i];
         CorElementType ty = arg.TyHnd.GetSignatureCorElementType();
 
         emitOffs(arg.Offset);
-        pCode->EmitLDARG(argIndex++);
+        pCode->EmitLDARG(i);
         EmitStoreTyHnd(pCode, arg.TyHnd);
     }
 
     if (info.ArgBufLayout.HasTargetAddress)
     {
         emitOffs(info.ArgBufLayout.TargetAddressOffset);
-        pCode->EmitLDARG(argIndex++);
+        pCode->EmitLDARG(info.ArgBufLayout.Values.GetCount());
         pCode->EmitSTIND_I();
     }
 
@@ -447,42 +476,31 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
     auto emitOffs = [&](UINT offs)
     {
         pCode->EmitLDARG(ARG_ARG_BUFFER);
-        if (offs != 0)
-        {
-            pCode->EmitLDC(offs);
-            pCode->EmitADD();
-        }
+        pCode->EmitLDC(offs);
+        pCode->EmitADD();
     };
 
-    StackSArray<DWORD> argLocals;
+    // *pTailCallAwareRetAddr = NextCallReturnAddress();
+    pCode->EmitLDARG(ARG_PTR_TAILCALL_AWARE_RET_ADDR);
+    pCode->EmitCALL(METHOD__STUBHELPERS__NEXT_CALL_RETURN_ADDRESS, 0, 1);
+    pCode->EmitSTIND_I();
+
     for (COUNT_T i = 0; i < info.ArgBufLayout.Values.GetCount(); i++)
     {
         const ArgBufferValue& arg = info.ArgBufLayout.Values[i];
-        DWORD argLcl = pCode->NewLocal(LocalDesc(arg.TyHnd));
-        argLocals.Append(argLcl);
 
         // arg = args->Arg_i
         emitOffs(arg.Offset);
         EmitLoadTyHnd(pCode, arg.TyHnd);
-        pCode->EmitSTLOC(argLcl);
-    }
-
-    DWORD targetAddrLcl;
-    if (info.ArgBufLayout.HasTargetAddress)
-    {
-        targetAddrLcl = pCode->NewLocal(ELEMENT_TYPE_I);
-
-        emitOffs(info.ArgBufLayout.TargetAddressOffset);
-        pCode->EmitLDIND_I();
-        pCode->EmitSTLOC(targetAddrLcl);
     }
 
-    // RuntimeHelpers.FreeTailCallArgBuffer();
-    pCode->EmitCALL(METHOD__RUNTIME_HELPERS__FREE_TAILCALL_ARG_BUFFER, 0, 0);
-
-    // *pTailCallAwareRetAddr = NextCallReturnAddress();
-    pCode->EmitLDARG(ARG_PTR_TAILCALL_AWARE_RET_ADDR);
-    pCode->EmitCALL(METHOD__STUBHELPERS__NEXT_CALL_RETURN_ADDRESS, 0, 1);
+    // All arguments are loaded on the stack, it is safe to disable the GC reporting of ArgBuffer now.
+    // This is optimization to avoid extending argument lifetime unnecessarily.
+    // We still need to report the inst argument of shared generic code to prevent it from being unloaded. The inst
+    // argument is just a regular IntPtr on the stack. It is safe to stop reporting it only after the target method
+    // takes over.
+    pCode->EmitLDARG(ARG_ARG_BUFFER);
+    pCode->EmitLDC(info.ArgBufLayout.HasInstArg ? TAILCALLARGBUFFER_INSTARG_ONLY : TAILCALLARGBUFFER_ABANDONED);
     pCode->EmitSTIND_I();
 
     int numRetVals = info.CallSiteSig->IsReturnTypeVoid() ? 0 : 1;
@@ -495,23 +513,18 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
         // the proper MethodRef.
         _ASSERTE(!info.CallSiteSig->IsVarArg());
 
-        for (COUNT_T i = 0; i < argLocals.GetCount(); i++)
-        {
-            pCode->EmitLDLOC(argLocals[i]);
-        }
-
         if (info.CallSiteIsVirtual)
         {
             pCode->EmitCALLVIRT(
                 pCode->GetToken(info.Callee),
-                static_cast<int>(argLocals.GetCount()),
+                static_cast<int>(info.ArgBufLayout.Values.GetCount()),
                 numRetVals);
         }
         else
         {
             pCode->EmitCALL(
                 pCode->GetToken(info.Callee),
-                static_cast<int>(argLocals.GetCount()),
+                static_cast<int>(info.ArgBufLayout.Values.GetCount()),
                 numRetVals);
         }
     }
@@ -538,7 +551,7 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
 
         COUNT_T firstSigArg = info.CallSiteSig->HasThis() ? 1 : 0;
 
-        for (COUNT_T i = firstSigArg; i < argLocals.GetCount(); i++)
+        for (COUNT_T i = firstSigArg; i < info.ArgBufLayout.Values.GetCount(); i++)
         {
             const ArgBufferValue& val = info.ArgBufLayout.Values[i];
             AppendTypeHandle(calliSig, val.TyHnd);
@@ -547,16 +560,12 @@ MethodDesc* TailCallHelp::CreateCallTargetStub(const TailCallInfo& info)
         DWORD cbCalliSig;
         PCCOR_SIGNATURE pCalliSig = (PCCOR_SIGNATURE)calliSig.GetSignature(&cbCalliSig);
 
-        for (COUNT_T i = 0; i < argLocals.GetCount(); i++)
-        {
-            pCode->EmitLDLOC(argLocals[i]);
-        }
-
-        pCode->EmitLDLOC(targetAddrLcl);
+        emitOffs(info.ArgBufLayout.TargetAddressOffset);
+        pCode->EmitLDIND_I();
 
         pCode->EmitCALLI(
             pCode->GetSigToken(pCalliSig, cbCalliSig),
-            static_cast<int>(argLocals.GetCount()),
+            static_cast<int>(info.ArgBufLayout.Values.GetCount()),
             numRetVals);
     }
 
index 327dc3d..3288307 100644 (file)
@@ -14,12 +14,11 @@ class TailCallHelp
 {
 public:
     static FCDECL2(void*, AllocTailCallArgBuffer, INT32, void*);
-    static FCDECL0(void,  FreeTailCallArgBuffer);
     static FCDECL2(void*, GetTailCallInfo, void**, void**);
 
     static void CreateTailCallHelperStubs(
         MethodDesc* pCallerMD, MethodDesc* pCalleeMD,
-        MetaSig& callSiteSig, bool virt, bool thisArgByRef,
+        MetaSig& callSiteSig, bool virt, bool thisArgByRef, bool hasInstArg,
         MethodDesc** storeArgsStub, bool* storeArgsNeedsTarget,
         MethodDesc** callTargetStub);
 
@@ -29,7 +28,7 @@ private:
 
     static void LayOutArgBuffer(
         MetaSig& callSiteSig, MethodDesc* calleeMD,
-        bool storeTarget, bool thisArgByRef, ArgBufferLayout* layout);
+        bool storeTarget, bool thisArgByRef, bool hasInstArg, ArgBufferLayout* layout);
     static TypeHandle NormalizeSigType(TypeHandle tyHnd);
     static bool GenerateGCDescriptor(MethodDesc* pTargetMD, const ArgBufferLayout& values, GCRefMapBuilder* builder);
 
index 2a1be66..588e230 100644 (file)
@@ -61,12 +61,10 @@ TailCallTls::TailCallTls()
     // so casting away const is ok here.
     : m_frame(const_cast<PortableTailCallFrame*>(&g_sentinelTailCallFrame))
     , m_argBuffer(NULL)
-    , m_argBufferSize(0)
-    , m_argBufferGCDesc(NULL)
 {
 }
 
-void* TailCallTls::AllocArgBuffer(size_t size, void* gcDesc)
+TailCallArgBuffer* TailCallTls::AllocArgBuffer(int size, void* gcDesc)
 {
     CONTRACTL
     {
@@ -75,42 +73,30 @@ void* TailCallTls::AllocArgBuffer(size_t size, void* gcDesc)
     }
     CONTRACTL_END
 
-    _ASSERTE(m_argBuffer == NULL);
+    _ASSERTE(size >= (int)offsetof(TailCallArgBuffer, Args));
 
-    if (size > sizeof(m_argBufferInline))
+    if (m_argBuffer != NULL && m_argBuffer->Size < size)
     {
-        m_argBuffer = new (nothrow) char[size];
-        if (m_argBuffer == NULL)
-            return NULL;
+        FreeArgBuffer();
     }
-    else
-        m_argBuffer = m_argBufferInline;
 
-    if (gcDesc != NULL)
+    if (m_argBuffer == NULL)
     {
-        memset(m_argBuffer, 0, size);
-        m_argBufferGCDesc = gcDesc;
+        m_argBuffer = (TailCallArgBuffer*)new (nothrow) BYTE[size];
+        if (m_argBuffer == NULL)
+            return NULL;
+        m_argBuffer->Size = size;
     }
 
-    m_argBufferSize = size;
-
-    return m_argBuffer;
-}
+    m_argBuffer->State = TAILCALLARGBUFFER_ACTIVE;
 
-void TailCallTls::FreeArgBuffer()
-{
-    CONTRACTL
+    m_argBuffer->GCDesc = gcDesc;
+    if (gcDesc != NULL)
     {
-        NOTHROW;
-        GC_NOTRIGGER;
+        memset(m_argBuffer->Args, 0, size - offsetof(TailCallArgBuffer, Args));
     }
-    CONTRACTL_END
-
-    if (m_argBufferSize > sizeof(m_argBufferInline))
-        delete[] m_argBuffer;
 
-    m_argBufferGCDesc = NULL;
-    m_argBuffer = NULL;
+    return m_argBuffer;
 }
 
 #if defined (_DEBUG_IMPL) || defined(_PREFAST_)
@@ -2646,6 +2632,8 @@ Thread::~Thread()
         delete m_pIBCInfo;
     }
 
+    m_tailCallTls.FreeArgBuffer();
+
 #ifdef FEATURE_EVENT_TRACE
     // Destruct the thread local type cache for allocation sampling
     if(m_pAllLoggedTypes) {
index c8acec7..64192c2 100644 (file)
@@ -239,6 +239,19 @@ public:
 #endif
 };
 
+// TailCallArgBuffer states
+#define TAILCALLARGBUFFER_ACTIVE       0
+#define TAILCALLARGBUFFER_INSTARG_ONLY 1
+#define TAILCALLARGBUFFER_ABANDONED    2
+
+struct TailCallArgBuffer
+{
+    int State;
+    int Size;
+    void* GCDesc;
+    BYTE Args[1];
+};
+
 #ifdef CROSSGEN_COMPILE
 
 #include "asmconstants.h"
@@ -968,18 +981,14 @@ class TailCallTls
     friend class CoreLibBinder;
 
     PortableTailCallFrame* m_frame;
-    char* m_argBuffer;
-    size_t m_argBufferSize;
-    void* m_argBufferGCDesc;
-    char m_argBufferInline[64];
+    TailCallArgBuffer* m_argBuffer;
 
 public:
     TailCallTls();
-    void* AllocArgBuffer(size_t size, void* gcDesc);
-    void FreeArgBuffer();
-    char* GetArgBuffer(void** gcDesc)
+    TailCallArgBuffer* AllocArgBuffer(int size, void* gcDesc);
+    void FreeArgBuffer() { delete[] (BYTE*)m_argBuffer; m_argBuffer = NULL; }
+    TailCallArgBuffer* GetArgBuffer()
     {
-        *gcDesc = m_argBufferGCDesc;
         return m_argBuffer;
     }
     const PortableTailCallFrame* GetFrame() { return m_frame; }
index 2a77824..141933d 100644 (file)
@@ -61,22 +61,26 @@ internal class Program
     {
         IL.Emit.Ldftn(new MethodRef(typeof(Program), nameof(CalcStaticCalli)));
         IL.Pop(out IntPtr calcStaticCalli);
+        s_calcStaticCalli = calcStaticCalli;
+
         IL.Emit.Ldftn(new MethodRef(typeof(Program), nameof(CalcStaticCalliOther)));
         IL.Pop(out IntPtr calcStaticCalliOther);
+        s_calcStaticCalliOther = calcStaticCalliOther;
+
         IL.Emit.Ldftn(new MethodRef(typeof(Program), nameof(CalcStaticCalliRetbuf)));
         IL.Pop(out IntPtr calcStaticCalliRetbuf);
+        s_calcStaticCalliRetbuf = calcStaticCalliRetbuf;
+
         IL.Emit.Ldftn(new MethodRef(typeof(Program), nameof(CalcStaticCalliRetbufOther)));
         IL.Pop(out IntPtr calcStaticCalliRetbufOther);
+        s_calcStaticCalliRetbufOther = calcStaticCalliRetbufOther;
+
         IL.Emit.Ldftn(new MethodRef(typeof(Program), nameof(EmptyCalliOther)));
         IL.Pop(out IntPtr emptyCalliOther);
+        s_emptyCalliOther = emptyCalliOther;
+
         IL.Emit.Ldftn(new MethodRef(typeof(S16), nameof(S16.InstanceMethod)));
         IL.Pop(out IntPtr instanceMethodOnValueType);
-
-        s_calcStaticCalli = calcStaticCalli;
-        s_calcStaticCalliOther = calcStaticCalliOther;
-        s_calcStaticCalliRetbuf = calcStaticCalliRetbuf;
-        s_calcStaticCalliRetbufOther = calcStaticCalliRetbufOther;
-        s_emptyCalliOther = emptyCalliOther;
         s_instanceMethodOnValueType = instanceMethodOnValueType;
     }
 
@@ -183,6 +187,10 @@ internal class Program
         Test(() => GenAbstractGString(ga1), "System.String", "Abstract generic without generic on method 1");
         Test(() => GenAbstractGInt(ga2), "System.Int32", "Abstract generic without generic on method 2");
 
+        int[] a = new int[1_000_000];
+        a[99] = 1;
+        Test(() => InstantiatingStub1(0, 0, "string", a), a.Length + 1, "Instantiating stub direct");
+
         if (result)
             Console.WriteLine("All tailcall-via-help succeeded");
         else
@@ -686,6 +694,42 @@ internal class Program
         IL.Emit.Callvirt(new MethodRef(typeof(GenAbstract<int>), nameof(GenAbstract<int>.G)));
         return IL.Return<string>();
     }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static int InstantiatingStub1<T>(int a, int r, T c, Span<int> d)
+    {
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(c);
+        IL.Push(a);
+        IL.Push(r);
+        IL.Emit.Ldarg(nameof(d));
+        IL.Push(r + d[99]);
+        IL.Emit.Tail();
+        IL.Emit.Call(new MethodRef(typeof(Program), nameof(InstantiatingStub1Other)).MakeGenericMethod(typeof(T)));
+        return IL.Return<int>();
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static int InstantiatingStub1Other<T>(T c0, T c1, T c2, T c3, T c4, T c5, T c6, T c7, int a, int r, Span<int> d, int result)
+    {
+        if (a == d.Length) return result;
+        else
+        {
+            IL.Push(a + 1);
+            IL.Push(result);
+            IL.Push(c0);
+            IL.Emit.Ldarg(nameof(d));
+            IL.Emit.Tail();
+            IL.Emit.Call(new MethodRef(typeof(Program), nameof(InstantiatingStub1)).MakeGenericMethod(typeof(T)));
+            return IL.Return<int>();
+        }
+    }
 }
 
 class Instance
index 6b88e67..f46fb3d 100644 (file)
@@ -8,19 +8,14 @@
 .assembly extern System.Runtime
 {
   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
-  .ver 4:2:2:0
-}
-.assembly extern System.Runtime.Extensions
-{
-  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
-  .ver 4:2:2:0
+  .ver 5:0:0:0
 }
 .assembly extern System.Console
 {
   .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )                         // .?_....:
-  .ver 4:1:2:0
+  .ver 5:0:0:0
 }
-.assembly example1
+.assembly more_tailcalls
 {
   .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
   .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
   //  .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 ) 
 
   .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56   // ....NETCoreApp,V
-                                                                                                              65 72 73 69 6F 6E 3D 76 33 2E 31 01 00 54 0E 14   // ersion=v3.1..T..
+                                                                                                              65 72 73 69 6F 6E 3D 76 35 2E 30 01 00 54 0E 14   // ersion=v5.0..T..
                                                                                                               46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79   // FrameworkDisplay
                                                                                                               4E 61 6D 65 00 )                                  // Name.
-  .custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 08 65 78 61 6D 70 6C 65 31 00 00 )          // ...example1..
+  .custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 0E 6D 6F 72 65 5F 74 61 69 6C 63 61 6C 6C   // ...more_tailcall
+                                                                                                      73 00 00 )                                        // s..
   .custom instance void [System.Runtime]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 07 52 65 6C 65 61 73 65 00 00 )             // ...Release..
   .custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 )             // ...1.0.0.0..
   .custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 05 31 2E 30 2E 30 00 00 )                   // ...1.0.0..
-  .custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 08 65 78 61 6D 70 6C 65 31 00 00 )          // ...example1..
-  .custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 08 65 78 61 6D 70 6C 65 31 00 00 )          // ...example1..
+  .custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0E 6D 6F 72 65 5F 74 61 69 6C 63 61 6C 6C   // ...more_tailcall
+                                                                                                      73 00 00 )                                        // s..
+  .custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0E 6D 6F 72 65 5F 74 61 69 6C 63 61 6C 6C   // ...more_tailcall
+                                                                                                    73 00 00 )                                        // s..
   .permissionset reqmin
-             = {class 'System.Security.Permissions.SecurityPermissionAttribute, System.Runtime.Extensions, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' = {property bool 'SkipVerification' = bool(true)}}
+             = {class 'System.Security.Permissions.SecurityPermissionAttribute, System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' = {property bool 'SkipVerification' = bool(true)}}
   .hash algorithm 0x00008004
   .ver 1:0:0:0
 }
-.module example1.dll
-// MVID: {E5213016-898B-454A-941D-D8C885B9972D}
+.module more_tailcalls.dll
+// MVID: {2945C9C4-EED0-49EA-8A87-27475136401B}
 .custom instance void [System.Runtime]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) 
 .imagebase 0x00400000
 .file alignment 0x00000200
 .stackreserve 0x00100000
 .subsystem 0x0003       // WINDOWS_CUI
 .corflags 0x00000001    //  ILONLY
-// Image base: 0x052D0000
+// Image base: 0x00000273609B0000
 
 
 // =============== CLASS MEMBERS DECLARATION ===================
     .field public class IGenInterface`2<string,object> ig2
     .field public class GenAbstractImpl`1<string> ga1
     .field public class GenAbstractImpl`1<int32> ga2
+    .field public int32[] a
     .method public hidebysig specialname rtspecialname 
             instance void  .ctor() cil managed
     {
     {
       // Code size       105 (0x69)
       .maxstack  4
-      .locals init (class [System.Runtime.Extensions]System.Diagnostics.Stopwatch V_0,
+      .locals init (class [System.Runtime]System.Diagnostics.Stopwatch V_0,
                !!T V_1)
       IL_0000:  ldstr      "{0}: "
       IL_0005:  ldarg.3
       IL_0006:  call       void [System.Console]System.Console::Write(string,
                                                                       object)
-      IL_000b:  call       class [System.Runtime.Extensions]System.Diagnostics.Stopwatch [System.Runtime.Extensions]System.Diagnostics.Stopwatch::StartNew()
+      IL_000b:  call       class [System.Runtime]System.Diagnostics.Stopwatch [System.Runtime]System.Diagnostics.Stopwatch::StartNew()
       IL_0010:  stloc.0
       IL_0011:  ldarg.1
       IL_0012:  callvirt   instance !0 class [System.Runtime]System.Func`1<!!T>::Invoke()
       IL_0017:  stloc.1
       IL_0018:  ldloc.0
-      IL_0019:  callvirt   instance void [System.Runtime.Extensions]System.Diagnostics.Stopwatch::Stop()
+      IL_0019:  callvirt   instance void [System.Runtime]System.Diagnostics.Stopwatch::Stop()
       IL_001e:  ldloca.s   V_1
       IL_0020:  ldarg.2
       IL_0021:  box        !!T
       IL_0033:  ldstr      "OK in {1} ms"
       IL_0038:  ldarg.3
       IL_0039:  ldloc.0
-      IL_003a:  callvirt   instance int64 [System.Runtime.Extensions]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
+      IL_003a:  callvirt   instance int64 [System.Runtime]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
       IL_003f:  box        [System.Runtime]System.Int64
       IL_0044:  call       void [System.Console]System.Console::WriteLine(string,
                                                                           object,
       IL_0008:  stfld      class [System.Runtime]System.Func`3<int32,int32,!0> class Program/'<>c__DisplayClass7_1`1'<!!T>::f
       IL_000d:  ldarg.0
       IL_000e:  ldloc.0
-      IL_000f:  ldftn      instance !0 class Program/'<>c__DisplayClass7_1`1'<!!T>::'<Main>b__29'()
+      IL_000f:  ldftn      instance !0 class Program/'<>c__DisplayClass7_1`1'<!!T>::'<Main>b__30'()
       IL_0015:  newobj     instance void class [System.Runtime]System.Func`1<!!T>::.ctor(object,
                                                                                          native int)
       IL_001a:  ldarg.2
       IL_000b:  ret
     } // end of method '<>c__DisplayClass7_0'::'<Main>b__28'
 
+    .method assembly hidebysig instance int32 
+            '<Main>b__29'() cil managed
+    {
+      // Code size       24 (0x18)
+      .maxstack  8
+      IL_0000:  ldc.i4.0
+      IL_0001:  ldc.i4.0
+      IL_0002:  ldstr      "string"
+      IL_0007:  ldarg.0
+      IL_0008:  ldfld      int32[] Program/'<>c__DisplayClass7_0'::a
+      IL_000d:  call       valuetype [System.Runtime]System.Span`1<!0> valuetype [System.Runtime]System.Span`1<int32>::op_Implicit(!0[])
+      IL_0012:  call       int32 Program::InstantiatingStub1<string>(int32,
+                                                                     int32,
+                                                                     !!0,
+                                                                     valuetype [System.Runtime]System.Span`1<int32>)
+      IL_0017:  ret
+    } // end of method '<>c__DisplayClass7_0'::'<Main>b__29'
+
   } // end of class '<>c__DisplayClass7_0'
 
   .class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass7_1`1'<T>
     } // end of method '<>c__DisplayClass7_1`1'::.ctor
 
     .method assembly hidebysig instance !T 
-            '<Main>b__29'() cil managed
+            '<Main>b__30'() cil managed
     {
       // Code size       18 (0x12)
       .maxstack  8
       IL_000c:  callvirt   instance !2 class [System.Runtime]System.Func`3<int32,int32,!T>::Invoke(!0,
                                                                                                    !1)
       IL_0011:  ret
-    } // end of method '<>c__DisplayClass7_1`1'::'<Main>b__29'
+    } // end of method '<>c__DisplayClass7_1`1'::'<Main>b__30'
 
   } // end of class '<>c__DisplayClass7_1`1'
 
     IL_0000:  ldftn      int32 Program::CalcStaticCalli(int32,
                                                         int32)
     IL_0006:  stloc.0
-    IL_0007:  ldftn      int32 Program::CalcStaticCalliOther(int32,
+    IL_0007:  ldloc.0
+    IL_0008:  stsfld     native int Program::s_calcStaticCalli
+    IL_000d:  ldftn      int32 Program::CalcStaticCalliOther(int32,
                                                              valuetype S32,
                                                              int32)
-    IL_000d:  stloc.1
-    IL_000e:  ldftn      valuetype S32 Program::CalcStaticCalliRetbuf(int32,
+    IL_0013:  stloc.1
+    IL_0014:  ldloc.1
+    IL_0015:  stsfld     native int Program::s_calcStaticCalliOther
+    IL_001a:  ldftn      valuetype S32 Program::CalcStaticCalliRetbuf(int32,
                                                                       int32)
-    IL_0014:  stloc.2
-    IL_0015:  ldftn      valuetype S32 Program::CalcStaticCalliRetbufOther(int32,
+    IL_0020:  stloc.2
+    IL_0021:  ldloc.2
+    IL_0022:  stsfld     native int Program::s_calcStaticCalliRetbuf
+    IL_0027:  ldftn      valuetype S32 Program::CalcStaticCalliRetbufOther(int32,
                                                                            valuetype S32,
                                                                            int32)
-    IL_001b:  stloc.3
-    IL_001c:  ldftn      string Program::EmptyCalliOther()
-    IL_0022:  stloc.s    V_4
-    IL_0024:  ldftn      instance string S16::InstanceMethod()
-    IL_002a:  stloc.s    V_5
-    IL_002c:  ldloc.0
-    IL_002d:  stsfld     native int Program::s_calcStaticCalli
-    IL_0032:  ldloc.1
-    IL_0033:  stsfld     native int Program::s_calcStaticCalliOther
-    IL_0038:  ldloc.2
-    IL_0039:  stsfld     native int Program::s_calcStaticCalliRetbuf
-    IL_003e:  ldloc.3
-    IL_003f:  stsfld     native int Program::s_calcStaticCalliRetbufOther
-    IL_0044:  ldloc.s    V_4
-    IL_0046:  stsfld     native int Program::s_emptyCalliOther
+    IL_002d:  stloc.3
+    IL_002e:  ldloc.3
+    IL_002f:  stsfld     native int Program::s_calcStaticCalliRetbufOther
+    IL_0034:  ldftn      string Program::EmptyCalliOther()
+    IL_003a:  stloc.s    V_4
+    IL_003c:  ldloc.s    V_4
+    IL_003e:  stsfld     native int Program::s_emptyCalliOther
+    IL_0043:  ldftn      instance string S16::InstanceMethod()
+    IL_0049:  stloc.s    V_5
     IL_004b:  ldloc.s    V_5
     IL_004d:  stsfld     native int Program::s_instanceMethodOnValueType
     IL_0052:  ret
           Main() cil managed
   {
     .entrypoint
-    // Code size       1733 (0x6c5)
+    // Code size       1792 (0x700)
     .maxstack  4
     .locals init (class Program/'<>c__DisplayClass7_0' V_0,
              int32 V_1,
                                                                                                  !!0,
                                                                                                  string)
     IL_069a:  ldloc.0
-    IL_069b:  ldfld      bool Program/'<>c__DisplayClass7_0'::result
-    IL_06a0:  brfalse.s  IL_06ae
+    IL_069b:  ldc.i4     0xf4240
+    IL_06a0:  newarr     [System.Runtime]System.Int32
+    IL_06a5:  stfld      int32[] Program/'<>c__DisplayClass7_0'::a
+    IL_06aa:  ldloc.0
+    IL_06ab:  ldfld      int32[] Program/'<>c__DisplayClass7_0'::a
+    IL_06b0:  ldc.i4.s   99
+    IL_06b2:  ldc.i4.1
+    IL_06b3:  stelem.i4
+    IL_06b4:  ldloc.0
+    IL_06b5:  ldloc.0
+    IL_06b6:  ldftn      instance int32 Program/'<>c__DisplayClass7_0'::'<Main>b__29'()
+    IL_06bc:  newobj     instance void class [System.Runtime]System.Func`1<int32>::.ctor(object,
+                                                                                         native int)
+    IL_06c1:  ldloc.0
+    IL_06c2:  ldfld      int32[] Program/'<>c__DisplayClass7_0'::a
+    IL_06c7:  ldlen
+    IL_06c8:  conv.i4
+    IL_06c9:  ldc.i4.1
+    IL_06ca:  add
+    IL_06cb:  ldstr      "Instantiating stub direct"
+    IL_06d0:  callvirt   instance void Program/'<>c__DisplayClass7_0'::'<Main>g__Test|0'<int32>(class [System.Runtime]System.Func`1<!!0>,
+                                                                                                !!0,
+                                                                                                string)
+    IL_06d5:  ldloc.0
+    IL_06d6:  ldfld      bool Program/'<>c__DisplayClass7_0'::result
+    IL_06db:  brfalse.s  IL_06e9
 
-    IL_06a2:  ldstr      "All tailcall-via-help succeeded"
-    IL_06a7:  call       void [System.Console]System.Console::WriteLine(string)
-    IL_06ac:  br.s       IL_06b8
+    IL_06dd:  ldstr      "All tailcall-via-help succeeded"
+    IL_06e2:  call       void [System.Console]System.Console::WriteLine(string)
+    IL_06e7:  br.s       IL_06f3
 
-    IL_06ae:  ldstr      "One or more failures in tailcall-via-help test"
-    IL_06b3:  call       void [System.Console]System.Console::WriteLine(string)
-    IL_06b8:  ldloc.0
-    IL_06b9:  ldfld      bool Program/'<>c__DisplayClass7_0'::result
-    IL_06be:  brtrue.s   IL_06c2
+    IL_06e9:  ldstr      "One or more failures in tailcall-via-help test"
+    IL_06ee:  call       void [System.Console]System.Console::WriteLine(string)
+    IL_06f3:  ldloc.0
+    IL_06f4:  ldfld      bool Program/'<>c__DisplayClass7_0'::result
+    IL_06f9:  brtrue.s   IL_06fd
 
-    IL_06c0:  ldc.i4.1
-    IL_06c1:  ret
+    IL_06fb:  ldc.i4.1
+    IL_06fc:  ret
 
-    IL_06c2:  ldc.i4.s   100
-    IL_06c4:  ret
+    IL_06fd:  ldc.i4.s   100
+    IL_06ff:  ret
   } // end of method Program::Main
 
   .method public hidebysig static void  Calc(int32& x,
     // Code size       41 (0x29)
     .maxstack  2
     .locals init (int32 V_0)
-    IL_0000:  call       int32 [System.Runtime.Extensions]System.Environment::get_TickCount()
+    IL_0000:  call       int32 [System.Runtime]System.Environment::get_TickCount()
     IL_0005:  ldc.i4.0
     IL_0006:  blt.s      IL_000c
 
     IL_0028:  ret
   } // end of method Program::EmptyCalli
 
-    .method private hidebysig static string 
+  .method private hidebysig static string 
           ValueTypeInstanceMethodCalli() cil managed noinlining
   {
     // Code size       51 (0x33)
     .maxstack  2
     .locals init (valuetype S16 V_0,
              int32 V_1)
-    IL_0000:  call       int32 [System.Runtime.Extensions]System.Environment::get_TickCount()
+    IL_0000:  call       int32 [System.Runtime]System.Environment::get_TickCount()
     IL_0005:  ldc.i4.0
     IL_0006:  blt.s      IL_000c
 
     .maxstack  2
     .locals init (valuetype S16 V_0,
              int32 V_1)
-    IL_0000:  call       int32 [System.Runtime.Extensions]System.Environment::get_TickCount()
+    IL_0000:  call       int32 [System.Runtime]System.Environment::get_TickCount()
     IL_0005:  ldc.i4.0
     IL_0006:  blt.s      IL_000c
 
     IL_0008:  ret
   } // end of method Program::GenAbstractGInt
 
+  .method private hidebysig static int32 
+          InstantiatingStub1<T>(int32 a,
+                                int32 r,
+                                !!T c,
+                                valuetype [System.Runtime]System.Span`1<int32> d) cil managed noinlining
+  {
+    // Code size       31 (0x1f)
+    .maxstack  14
+    IL_0000:  ldarg.2
+    IL_0001:  ldarg.2
+    IL_0002:  ldarg.2
+    IL_0003:  ldarg.2
+    IL_0004:  ldarg.2
+    IL_0005:  ldarg.2
+    IL_0006:  ldarg.2
+    IL_0007:  ldarg.2
+    IL_0008:  ldarg.0
+    IL_0009:  ldarg.1
+    IL_000a:  ldarg.3
+    IL_000b:  ldarg.1
+    IL_000c:  ldarga.s   d
+    IL_000e:  ldc.i4.s   99
+    IL_0010:  call       instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
+    IL_0015:  ldind.i4
+    IL_0016:  add
+    IL_0017:  tail.
+    IL_0019:  call       int32 Program::InstantiatingStub1Other<!!0>(!!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     !!0,
+                                                                     int32,
+                                                                     int32,
+                                                                     valuetype [System.Runtime]System.Span`1<int32>,
+                                                                     int32)
+    IL_001e:  ret
+  } // end of method Program::InstantiatingStub1
+
+  .method private hidebysig static int32 
+          InstantiatingStub1Other<T>(!!T c0,
+                                     !!T c1,
+                                     !!T c2,
+                                     !!T c3,
+                                     !!T c4,
+                                     !!T c5,
+                                     !!T c6,
+                                     !!T c7,
+                                     int32 a,
+                                     int32 r,
+                                     valuetype [System.Runtime]System.Span`1<int32> d,
+                                     int32 result) cil managed noinlining
+  {
+    // Code size       31 (0x1f)
+    .maxstack  4
+    IL_0000:  ldarg.s    a
+    IL_0002:  ldarga.s   d
+    IL_0004:  call       instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
+    IL_0009:  bne.un.s   IL_000e
+
+    IL_000b:  ldarg.s    result
+    IL_000d:  ret
+
+    IL_000e:  ldarg.s    a
+    IL_0010:  ldc.i4.1
+    IL_0011:  add
+    IL_0012:  ldarg.s    result
+    IL_0014:  ldarg.0
+    IL_0015:  ldarg.s    d
+    IL_0017:  tail.
+    IL_0019:  call       int32 Program::InstantiatingStub1<!!0>(int32,
+                                                                int32,
+                                                                !!0,
+                                                                valuetype [System.Runtime]System.Span`1<int32>)
+    IL_001e:  ret
+  } // end of method Program::InstantiatingStub1Other
+
   .method public hidebysig specialname rtspecialname 
           instance void  .ctor() cil managed
   {
 
 } // end of class GenAbstractImpl`1
 
-.class private auto ansi example1_Fody.ProcessedByFody
+.class private auto ansi more_tailcalls_ProcessedByFody
        extends [System.Runtime]System.Object
 {
-  .field static assembly literal string FodyVersion = "6.1.1.0"
-  .field static assembly literal string InlineIL = "1.4.0.0"
-} // end of class example1_Fody.ProcessedByFody
+  .field static assembly literal string FodyVersion = "6.2.4.0"
+  .field static assembly literal string InlineIL = "1.5.0.0"
+} // end of class more_tailcalls_ProcessedByFody
 
 
 // =============================================================
 
 // *********** DISASSEMBLY COMPLETE ***********************
+// WARNING: Created Win32 resource file more_tailcalls.res