Skip to content

Commit 9684bd2

Browse files
committed
Revert "Enable inlining P/Invokes into try blocks with no catch or filter clauses (dotnet#73032)"
This reverts commit 4c07f3d. We believe it is causing recent CI failures. See dotnet#73247
1 parent 0c5ca95 commit 9684bd2

File tree

6 files changed

+46
-126
lines changed

6 files changed

+46
-126
lines changed

src/coreclr/inc/corinfo.h

-5
Original file line numberDiff line numberDiff line change
@@ -3244,9 +3244,4 @@ class ICorDynamicInfo : public ICorStaticInfo
32443244
//
32453245
#define IMAGE_REL_BASED_REL_THUMB_MOV32_PCREL 0x14
32463246

3247-
/**********************************************************************************/
3248-
#ifdef TARGET_64BIT
3249-
#define USE_PER_FRAME_PINVOKE_INIT
3250-
#endif
3251-
32523247
#endif // _COR_INFO_H_

src/coreclr/jit/importer.cpp

+24-26
Original file line numberDiff line numberDiff line change
@@ -8428,43 +8428,41 @@ bool Compiler::impCanPInvokeInlineCallSite(BasicBlock* block)
84288428
return true;
84298429
}
84308430

8431-
#ifdef USE_PER_FRAME_PINVOKE_INIT
8432-
// For platforms that use per-P/Invoke InlinedCallFrame initialization,
8433-
// we can't inline P/Invokes inside of try blocks where we can resume execution in the same function.
8434-
// The runtime can correctly unwind out of an InlinedCallFrame and out of managed code. However,
8435-
// it cannot correctly unwind out of an InlinedCallFrame and stop at that frame without also unwinding
8436-
// at least one managed frame. In particular, the runtime struggles to restore non-volatile registers
8437-
// from the top-most unmanaged call before the InlinedCallFrame. As a result, the runtime does not support
8438-
// re-entering the same method frame as the InlinedCallFrame after an exception in unmanaged code.
8431+
#ifdef TARGET_64BIT
8432+
// On 64-bit platforms, we disable pinvoke inlining inside of try regions.
8433+
// Note that this could be needed on other architectures too, but we
8434+
// haven't done enough investigation to know for sure at this point.
8435+
//
8436+
// Here is the comment from JIT64 explaining why:
8437+
// [VSWhidbey: 611015] - because the jitted code links in the
8438+
// Frame (instead of the stub) we rely on the Frame not being
8439+
// 'active' until inside the stub. This normally happens by the
8440+
// stub setting the return address pointer in the Frame object
8441+
// inside the stub. On a normal return, the return address
8442+
// pointer is zeroed out so the Frame can be safely re-used, but
8443+
// if an exception occurs, nobody zeros out the return address
8444+
// pointer. Thus if we re-used the Frame object, it would go
8445+
// 'active' as soon as we link it into the Frame chain.
8446+
//
8447+
// Technically we only need to disable PInvoke inlining if we're
8448+
// in a handler or if we're in a try body with a catch or
8449+
// filter/except where other non-handler code in this method
8450+
// might run and try to re-use the dirty Frame object.
8451+
//
8452+
// A desktop test case where this seems to matter is
8453+
// jit\jit64\ebvts\mcpp\sources2\ijw\__clrcall\vector_ctor_dtor.02\deldtor_clr.exe
84398454
if (block->hasTryIndex())
84408455
{
84418456
// This does not apply to the raw pinvoke call that is inside the pinvoke
84428457
// ILStub. In this case, we have to inline the raw pinvoke call into the stub,
84438458
// otherwise we would end up with a stub that recursively calls itself, and end
84448459
// up with a stack overflow.
8445-
// This works correctly because the runtime never emits a catch block in a managed-to-native
8446-
// IL stub. If the runtime ever emits a catch block into a managed-to-native stub when using
8447-
// P/Invoke helpers, this condition will need to be revisited.
84488460
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && opts.ShouldUsePInvokeHelpers())
84498461
{
84508462
return true;
84518463
}
84528464

8453-
// Check if this block's try block or any containing try blocks have catch handlers.
8454-
// If any of the containing try blocks have catch handlers,
8455-
// we cannot inline a P/Invoke for reasons above. If the handler is a fault or finally handler,
8456-
// we can inline a P/Invoke into this block in the try since the code will not resume execution
8457-
// in the same method after throwing an exception if only fault or finally handlers are executed.
8458-
for (unsigned int ehIndex = block->getTryIndex(); ehIndex != EHblkDsc::NO_ENCLOSING_INDEX;
8459-
ehIndex = ehGetEnclosingTryIndex(ehIndex))
8460-
{
8461-
if (ehGetDsc(ehIndex)->HasCatchHandler())
8462-
{
8463-
return false;
8464-
}
8465-
}
8466-
8467-
return true;
8465+
return false;
84688466
}
84698467
#endif // TARGET_64BIT
84708468

src/coreclr/jit/lower.cpp

+8-13
Original file line numberDiff line numberDiff line change
@@ -4281,7 +4281,6 @@ GenTree* Lowering::CreateFrameLinkUpdate(FrameLinkAction action)
42814281
// Return Value:
42824282
// none
42834283
//
4284-
// See the usages for USE_PER_FRAME_PINVOKE_INIT for more information.
42854284
void Lowering::InsertPInvokeMethodProlog()
42864285
{
42874286
noway_assert(comp->info.compUnmanagedCallCountWithGCTransition);
@@ -4378,16 +4377,13 @@ void Lowering::InsertPInvokeMethodProlog()
43784377
// --------------------------------------------------------
43794378
// On 32-bit targets, CORINFO_HELP_INIT_PINVOKE_FRAME initializes the PInvoke frame and then pushes it onto
43804379
// the current thread's Frame stack. On 64-bit targets, it only initializes the PInvoke frame.
4381-
// As a result, don't push the frame onto the frame stack here for any 64-bit targets
43824380
CLANG_FORMAT_COMMENT_ANCHOR;
43834381

43844382
#ifdef TARGET_64BIT
4385-
#ifdef USE_PER_FRAME_PINVOKE_INIT
4386-
// For IL stubs, we push the frame once even when we're doing per-pinvoke init.
43874383
if (comp->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB))
4388-
#endif // USE_PER_FRAME_PINVOKE_INIT
43894384
{
4390-
// Push a frame. The init routine sets InlinedCallFrame's m_pNext, so we just set the thread's top-of-stack
4385+
// Push a frame - if we are NOT in an IL stub, this is done right before the call
4386+
// The init routine sets InlinedCallFrame's m_pNext, so we just set the thead's top-of-stack
43914387
GenTree* frameUpd = CreateFrameLinkUpdate(PushFrame);
43924388
firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, frameUpd));
43934389
ContainCheckStoreIndir(frameUpd->AsStoreInd());
@@ -4447,10 +4443,9 @@ void Lowering::InsertPInvokeMethodEpilog(BasicBlock* returnBB DEBUGARG(GenTree*
44474443
// this in the epilog for IL stubs; for non-IL stubs the frame is popped after every PInvoke call.
44484444
CLANG_FORMAT_COMMENT_ANCHOR;
44494445

4450-
#ifdef USE_PER_FRAME_PINVOKE_INIT
4451-
// For IL stubs, we push the frame once even when we're doing per-pinvoke init
4446+
#ifdef TARGET_64BIT
44524447
if (comp->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB))
4453-
#endif // USE_PER_FRAME_PINVOKE_INIT
4448+
#endif // TARGET_64BIT
44544449
{
44554450
GenTree* frameUpd = CreateFrameLinkUpdate(PopFrame);
44564451
returnBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, frameUpd));
@@ -4606,7 +4601,7 @@ void Lowering::InsertPInvokeCallProlog(GenTreeCall* call)
46064601
// contains PInvokes; on 64-bit targets this is necessary in non-stubs.
46074602
CLANG_FORMAT_COMMENT_ANCHOR;
46084603

4609-
#ifdef USE_PER_FRAME_PINVOKE_INIT
4604+
#ifdef TARGET_64BIT
46104605
if (!comp->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB))
46114606
{
46124607
// Set the TCB's frame to be the one we just created.
@@ -4618,7 +4613,7 @@ void Lowering::InsertPInvokeCallProlog(GenTreeCall* call)
46184613
BlockRange().InsertBefore(insertBefore, LIR::SeqTree(comp, frameUpd));
46194614
ContainCheckStoreIndir(frameUpd->AsStoreInd());
46204615
}
4621-
#endif // USE_PER_FRAME_PINVOKE_INIT
4616+
#endif // TARGET_64BIT
46224617

46234618
// IMPORTANT **** This instruction must be the last real instruction ****
46244619
// It changes the thread's state to Preemptive mode
@@ -4684,7 +4679,7 @@ void Lowering::InsertPInvokeCallEpilog(GenTreeCall* call)
46844679
// this happens after every PInvoke call in non-stubs. 32-bit targets instead mark the frame as inactive.
46854680
CLANG_FORMAT_COMMENT_ANCHOR;
46864681

4687-
#ifdef USE_PER_FRAME_PINVOKE_INIT
4682+
#ifdef TARGET_64BIT
46884683
if (!comp->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB))
46894684
{
46904685
tree = CreateFrameLinkUpdate(PopFrame);
@@ -4708,7 +4703,7 @@ void Lowering::InsertPInvokeCallEpilog(GenTreeCall* call)
47084703

47094704
BlockRange().InsertBefore(insertionPoint, constantZero, storeCallSiteTracker);
47104705
ContainCheckStoreLoc(storeCallSiteTracker);
4711-
#endif // USE_PER_FRAME_PINVOKE_INIT
4706+
#endif // TARGET_64BIT
47124707
}
47134708

47144709
//------------------------------------------------------------------------

src/coreclr/vm/exceptionhandling.cpp

+11-30
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include "virtualcallstub.h"
1717
#include "utilcode.h"
1818
#include "interoplibinterface.h"
19-
#include "corinfo.h"
2019

2120
#if defined(TARGET_X86)
2221
#define USE_CURRENT_CONTEXT_IN_FILTER
@@ -1777,10 +1776,8 @@ CLRUnwindStatus ExceptionTracker::ProcessOSExceptionNotification(
17771776
// InlinedCallFrames (ICF) are allocated, initialized and linked to the Frame chain
17781777
// by the code generated by the JIT for a method containing a PInvoke.
17791778
//
1780-
// On platforms where USE_PER_FRAME_PINVOKE_INIT is not defined,
1781-
// the JIT generates code that links in the ICF
1782-
// at the start of the method and unlinks it towards the method end.
1783-
// Thus, ICF is present on the Frame chain at any given point so long as the
1779+
// JIT generates code that links in the ICF at the start of the method and unlinks it towards
1780+
// the method end. Thus, ICF is present on the Frame chain at any given point so long as the
17841781
// method containing the PInvoke is on the stack.
17851782
//
17861783
// Now, if the method containing ICF catches an exception, we will reset the Frame chain
@@ -1818,16 +1815,13 @@ CLRUnwindStatus ExceptionTracker::ProcessOSExceptionNotification(
18181815
// below the callerSP for which we will invoke ExceptionUnwind.
18191816
//
18201817
// Thus, ICF::ExceptionUnwind should not do anything significant. If any of these assumptions
1821-
// break, then the next best thing will be to make the JIT link/unlink the frame dynamically
1818+
// break, then the next best thing will be to make the JIT link/unlink the frame dynamically.
18221819
//
1823-
// If the current method executing is from precompiled ReadyToRun code, each PInvoke is wrapped
1824-
// by calls to the JIT_PInvokeBegin and JIT_PInvokeEnd helpers,
1825-
// which push and pop the ICF to the current thread. The ICF is not
1826-
// linked during the method prolog, and unlinked at the epilog.
1820+
// If the current method executing is from precompiled ReadyToRun code, then the above is no longer
1821+
// applicable because each PInvoke is wrapped by calls to the JIT_PInvokeBegin and JIT_PInvokeEnd
1822+
// helpers, which push and pop the ICF to the current thread. Unlike jitted code, the ICF is not
1823+
// linked during the method prolog, and unlinked at the epilog (it looks more like the X64 case).
18271824
// In that case, we need to unlink the ICF during unwinding here.
1828-
// On platforms where USE_PER_FRAME_PINVOKE_INIT is defined, the JIT generates code that links in
1829-
// the ICF immediately before and after a PInvoke in non-IL-stubs, like ReadyToRun.
1830-
// See the usages for USE_PER_FRAME_PINVOKE_INIT for more information.
18311825

18321826
if (fTargetUnwind && (pFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr()))
18331827
{
@@ -1836,12 +1830,8 @@ CLRUnwindStatus ExceptionTracker::ProcessOSExceptionNotification(
18361830
//
18371831
// 1) ICF address is higher than the current frame's SP (which we get from DispatcherContext), AND
18381832
// 2) ICF address is below callerSP.
1839-
// 3) ICF is active.
1840-
// - IL stubs link the frame in for the whole stub, so if an exception is thrown during marshalling,
1841-
// the ICF will be on the frame chain and inactive.
1842-
if ((GetSP(pDispatcherContext->ContextRecord) < (TADDR)pICF)
1843-
&& ((UINT_PTR)pICF < uCallerSP)
1844-
&& InlinedCallFrame::FrameHasActiveCall(pICF))
1833+
if ((GetSP(pDispatcherContext->ContextRecord) < (TADDR)pICF) &&
1834+
((UINT_PTR)pICF < uCallerSP))
18451835
{
18461836
pICFForUnwindTarget = pFrame;
18471837

@@ -1850,18 +1840,9 @@ CLRUnwindStatus ExceptionTracker::ProcessOSExceptionNotification(
18501840
// to the JIT_PInvokeBegin and JIT_PInvokeEnd helpers, which push and pop the ICF on the thread. The
18511841
// ICF is not linked at the method prolog and unlined at the epilog when running R2R code. Since the
18521842
// JIT_PInvokeEnd helper will be skipped, we need to unlink the ICF here. If the executing method
1853-
// has another pinvoke, it will re-link the ICF again when the JIT_PInvokeBegin helper is called.
1843+
// has another pinovoke, it will re-link the ICF again when the JIT_PInvokeBegin helper is called
18541844

1855-
TADDR returnAddress = ((InlinedCallFrame*)pFrame)->m_pCallerReturnAddress;
1856-
#ifdef USE_PER_FRAME_PINVOKE_INIT
1857-
// If we're setting up the frame for each P/Invoke for the given platform,
1858-
// then we do this for all P/Invokes except ones in IL stubs.
1859-
if (!ExecutionManager::GetCodeMethodDesc(returnAddress)->IsILStub())
1860-
#else
1861-
// If we aren't setting up the frame for each P/Invoke (instead setting up once per method),
1862-
// then ReadyToRun code is the only code using the per-P/Invoke logic.
1863-
if (ExecutionManager::IsReadyToRunCode(returnAddress))
1864-
#endif
1845+
if (ExecutionManager::IsReadyToRunCode(((InlinedCallFrame*)pFrame)->m_pCallerReturnAddress))
18651846
{
18661847
pICFForUnwindTarget = pICFForUnwindTarget->Next();
18671848
}

src/coreclr/vm/i386/excepx86.cpp

+3-18
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include "eeconfig.h"
2929
#include "vars.hpp"
3030
#include "generics.h"
31-
#include "corinfo.h"
3231

3332
#include "asmconstants.h"
3433
#include "virtualcallstub.h"
@@ -2971,8 +2970,6 @@ void ResumeAtJitEH(CrawlFrame* pCf,
29712970
// Check that the InlinedCallFrame is in the method with the exception handler. There can be other
29722971
// InlinedCallFrame somewhere up the call chain that is not related to the current exception
29732972
// handling.
2974-
2975-
// See the usages for USE_PER_FRAME_PINVOKE_INIT for more information.
29762973

29772974
#ifdef DEBUG
29782975
TADDR handlerFrameSP = pCf->GetRegisterSet()->SP;
@@ -2985,22 +2982,10 @@ void ResumeAtJitEH(CrawlFrame* pCf,
29852982
NULL /* StackwalkCacheUnwindInfo* */);
29862983
_ASSERTE(unwindSuccess);
29872984

2988-
if (((TADDR)pThread->m_pFrame < pCf->GetRegisterSet()->SP))
2985+
if (((TADDR)pThread->m_pFrame < pCf->GetRegisterSet()->SP) && ExecutionManager::IsReadyToRunCode(((InlinedCallFrame*)pThread->m_pFrame)->m_pCallerReturnAddress))
29892986
{
2990-
TADDR returnAddress = ((InlinedCallFrame*)pThread->m_pFrame)->m_pCallerReturnAddress;
2991-
#ifdef USE_PER_FRAME_PINVOKE_INIT
2992-
// If we're setting up the frame for each P/Invoke for the given platform,
2993-
// then we do this for all P/Invokes except ones in IL stubs.
2994-
if (!ExecutionManager::GetCodeMethodDesc(returnAddress)->IsILStub())
2995-
#else
2996-
// If we aren't setting up the frame for each P/Invoke (instead setting up once per method),
2997-
// then ReadyToRun code is the only code using the per-P/Invoke logic.
2998-
if (ExecutionManager::IsReadyToRunCode(returnAddress))
2999-
#endif
3000-
{
3001-
_ASSERTE((TADDR)pThread->m_pFrame >= handlerFrameSP);
3002-
pThread->m_pFrame->Pop(pThread);
3003-
}
2987+
_ASSERTE((TADDR)pThread->m_pFrame >= handlerFrameSP);
2988+
pThread->m_pFrame->Pop(pThread);
30042989
}
30052990
}
30062991

src/tests/baseservices/exceptions/exceptioninterop/ExceptionInterop.cs

-34
Original file line numberDiff line numberDiff line change
@@ -122,38 +122,4 @@ public static void ThrowNativeExceptionAndCatchInFrameWithFinally()
122122

123123
Assert.True(caughtException);
124124
}
125-
126-
[Fact]
127-
[PlatformSpecific(TestPlatforms.Windows)]
128-
[SkipOnMono("Exception interop not supported on Mono.")]
129-
public static void ThrowNativeExceptionInFrameWithFinallyCatchInOuterFrame()
130-
{
131-
bool caughtException = false;
132-
try
133-
{
134-
ThrowInFrameWithFinally();
135-
}
136-
catch
137-
{
138-
caughtException = true;
139-
}
140-
141-
Assert.True(caughtException);
142-
143-
[MethodImpl(MethodImplOptions.NoInlining)]
144-
static void ThrowInFrameWithFinally()
145-
{
146-
try
147-
{
148-
ThrowException();
149-
}
150-
finally
151-
{
152-
// Try calling another P/Invoke in the finally block before the catch
153-
// to make sure we have everything set up
154-
// to recover from the exceptional control flow.
155-
NativeFunction();
156-
}
157-
}
158-
}
159125
}

0 commit comments

Comments
 (0)