Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 45a3a48

Browse files
Conditionally remove the GC transition from a P/Invoke (#26458)
* Provide mechanism to remove the GC transition from a P/Invoke. New Attribute: 'System.Runtime.InteropServices.SuppressGCTransitionAttribute'
1 parent 2d636cd commit 45a3a48

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+692
-144
lines changed

Documentation/botr/clr-abi.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ ARM64-only: When a method returns a structure that is larger than 16 bytes the c
8585

8686
# PInvokes
8787

88-
The convention is that any method with an InlinedCallFrame (either an IL stub or a normal method with an inlined pinvoke) saves/restores all non-volatile integer registers in its prolog/epilog respectively. This is done so that the InlinedCallFrame can just contain a return address, a stack pointer and a frame pointer. Then using just those three it can start a full stack walk using the normal RtlVirtualUnwind.
88+
The convention is that any method with an InlinedCallFrame (either an IL stub or a normal method with an inlined PInvoke) saves/restores all non-volatile integer registers in its prolog/epilog respectively. This is done so that the InlinedCallFrame can just contain a return address, a stack pointer and a frame pointer. Then using just those three it can start a full stack walk using the normal RtlVirtualUnwind.
89+
90+
When encountering a PInvoke, the JIT will query the VM if the GC transition should be suppressed. Suppression of the GC transition is indicated by the addition of an attribute on the PInvoke definition. If the VM indicates the GC transition is to be suppressed, the PInvoke frame will be omitted in either the IL stub or inlined scenario and a GC Poll will be inserted near the unmanaged call site. If an enclosing function contains more than one inlined PInvoke but not all have requested a suppression of the GC transition a PInvoke frame will still be constructed for the other inlined PInvokes.
8991

9092
For AMD64, a method with an InlinedCallFrame must use RBP as the frame register.
9193

@@ -113,6 +115,8 @@ For IL stubs only, the per-frame initialization includes setting `Thread->m_pFra
113115

114116
## Per-call-site PInvoke work
115117

118+
The below is performed when the GC transition is not suppressed.
119+
116120
1. For direct calls, the JITed code sets `InlinedCallFrame->m_pDatum` to the MethodDesc of the call target.
117121
* For JIT64, indirect calls within IL stubs sets it to the secret parameter (this seems redundant, but it might have changed since the per-frame initialization?).
118122
* For JIT32 (ARM) indirect calls, it sets this member to the size of the pushed arguments, according to the comments. The implementation however always passed 0.

Documentation/botr/readytorun-format.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ fixup kind, the rest of the signature varies based on the fixup kind.
205205
| READYTORUN_FIXUP_Check_FieldOffset | 0x2B | Verification of field offset, followed by field signature and expected field layout descriptor
206206
| READYTORUN_FIXUP_DelegateCtor | 0x2C | Delegate constructor, followed by method signature
207207
| READYTORUN_FIXUP_DeclaringTypeHandle | 0x2D | Dictionary lookup for method declaring type. Followed by the type signature.
208-
| READYTORUN_FIXUP_IndirectPInvokeTarget | 0x2E | Target of an inlined PInvoke. Followed by method signature.
208+
| READYTORUN_FIXUP_IndirectPInvokeTarget | 0x2E | Target (indirect) of an inlined PInvoke. Followed by method signature.
209+
| READYTORUN_FIXUP_PInvokeTarget | 0x2F | Target of an inlined PInvoke. Followed by method signature.
209210
| READYTORUN_FIXUP_ModuleOverride | 0x80 | When or-ed to the fixup ID, the fixup byte in the signature is followed by an encoded uint with assemblyref index, either within the MSIL metadata of the master context module for the signature or within the manifest metadata R2R header table (used in cases inlining brings in references to assemblies not seen in the input MSIL).
210211

211212
#### Method Signatures

src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@
697697
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SafeHandle.cs" />
698698
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SEHException.cs" />
699699
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\StructLayoutAttribute.cs" />
700+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SuppressGCTransitionAttribute.cs" />
700701
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeIdentifierAttribute.cs" />
701702
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnknownWrapper.cs" />
702703
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedFunctionPointerAttribute.cs" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace System.Runtime.InteropServices
6+
{
7+
/// <summary>
8+
/// An attribute used to indicate a GC transition should be skipped when making an unmanaged function call.
9+
/// </summary>
10+
/// <remarks>
11+
/// The eventual public attribute should replace this one: https://github.com/dotnet/corefx/issues/40740
12+
/// </remarks>
13+
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
14+
public sealed class SuppressGCTransitionAttribute : Attribute
15+
{
16+
public SuppressGCTransitionAttribute()
17+
{
18+
}
19+
}
20+
}

src/debug/daccess/nidump.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,10 @@ void NativeImageDumper::FixupBlobToString(RVA rva, SString& buf)
21942194
buf.Append( W("Indirect P/Invoke target for ") );
21952195
break;
21962196

2197+
case ENCODE_PINVOKE_TARGET:
2198+
buf.Append( W("P/Invoke target for ") );
2199+
break;
2200+
21972201
case ENCODE_PROFILING_HANDLE:
21982202
buf.Append( W("Profiling handle for ") );
21992203
goto EncodeMethod;

src/inc/corcompile.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,8 @@ enum CORCOMPILE_FIXUP_BLOB_KIND
688688

689689
ENCODE_DECLARINGTYPE_HANDLE,
690690

691-
ENCODE_INDIRECT_PINVOKE_TARGET, /* For calling a pinvoke method ptr */
691+
ENCODE_INDIRECT_PINVOKE_TARGET, /* For calling a pinvoke method ptr indirectly */
692+
ENCODE_PINVOKE_TARGET, /* For calling a pinvoke method ptr */
692693

693694
ENCODE_MODULE_HANDLE = 0x50, /* Module token */
694695
ENCODE_STATIC_FIELD_ADDRESS, /* For accessing a static field */
@@ -1180,6 +1181,8 @@ class ICorCompilePreloader
11801181

11811182
virtual BOOL IsUncompiledMethod(CORINFO_METHOD_HANDLE handle) = 0;
11821183

1184+
virtual BOOL ShouldSuppressGCTransition(CORINFO_METHOD_HANDLE handle) = 0;
1185+
11831186
// Return a method handle that was previously registered and
11841187
// hasn't been compiled already, and remove it from the set
11851188
// of uncompiled methods.

src/inc/corinfo.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ enum CorInfoFlag
836836
CORINFO_FLG_AGGRESSIVE_OPT = 0x01000000, // The method may contain hot code and should be aggressively optimized if possible
837837
CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS = 0x02000000, // Indicates that tier 0 JIT should not be used for a method that contains a loop
838838
CORINFO_FLG_NOSECURITYWRAP = 0x04000000, // The method requires no security checks
839+
// CORINFO_FLG_UNUSED = 0x08000000,
839840
CORINFO_FLG_DONT_INLINE = 0x10000000, // The method should not be inlined
840841
CORINFO_FLG_DONT_INLINE_CALLER = 0x20000000, // The method should not be inlined, nor should its callers. It cannot be tail called.
841842
CORINFO_FLG_JIT_INTRINSIC = 0x40000000, // Method is a potential jit intrinsic; verify identity by name check
@@ -1188,8 +1189,9 @@ enum CorInfoContextFlags
11881189

11891190
enum CorInfoSigInfoFlags
11901191
{
1191-
CORINFO_SIGFLAG_IS_LOCAL_SIG = 0x01,
1192-
CORINFO_SIGFLAG_IL_STUB = 0x02,
1192+
CORINFO_SIGFLAG_IS_LOCAL_SIG = 0x01,
1193+
CORINFO_SIGFLAG_IL_STUB = 0x02,
1194+
CORINFO_SIGFLAG_SUPPRESS_GC_TRANSITION = 0x04,
11931195
};
11941196

11951197
struct CORINFO_SIG_INST

src/inc/readytorun.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ enum ReadyToRunFixupKind
185185
READYTORUN_FIXUP_DelegateCtor = 0x2C, /* optimized delegate ctor */
186186
READYTORUN_FIXUP_DeclaringTypeHandle = 0x2D,
187187

188-
READYTORUN_FIXUP_IndirectPInvokeTarget = 0x2E, /* Target of an inlined pinvoke */
188+
READYTORUN_FIXUP_IndirectPInvokeTarget = 0x2E, /* Target (indirect) of an inlined pinvoke */
189+
READYTORUN_FIXUP_PInvokeTarget = 0x2F, /* Target of an inlined pinvoke */
189190
};
190191

191192
//
@@ -239,6 +240,7 @@ enum ReadyToRunHelper
239240
// PInvoke helpers
240241
READYTORUN_HELPER_PInvokeBegin = 0x42,
241242
READYTORUN_HELPER_PInvokeEnd = 0x43,
243+
READYTORUN_HELPER_GCPoll = 0x44,
242244

243245
// Get string handle lazily
244246
READYTORUN_HELPER_GetString = 0x50,

src/inc/readytorunhelpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ HELPER(READYTORUN_HELPER_EndCatch, CORINFO_HELP_ENDCATCH,
113113

114114
HELPER(READYTORUN_HELPER_PInvokeBegin, CORINFO_HELP_JIT_PINVOKE_BEGIN, )
115115
HELPER(READYTORUN_HELPER_PInvokeEnd, CORINFO_HELP_JIT_PINVOKE_END, )
116+
HELPER(READYTORUN_HELPER_GCPoll, CORINFO_HELP_POLL_GC, )
116117

117118
HELPER(READYTORUN_HELPER_MonitorEnter, CORINFO_HELP_MON_ENTER, )
118119
HELPER(READYTORUN_HELPER_MonitorExit, CORINFO_HELP_MON_EXIT, )

src/jit/codegencommon.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6862,7 +6862,7 @@ void CodeGen::genFinalizeFrame()
68626862
}
68636863

68646864
/* If we have any pinvoke calls, we might potentially trash everything */
6865-
if (compiler->info.compCallUnmanaged)
6865+
if (compiler->compMethodRequiresPInvokeFrame())
68666866
{
68676867
noway_assert(isFramePointerUsed()); // Setup of Pinvoke frame currently requires an EBP style frame
68686868
regSet.rsSetRegsModified(RBM_INT_CALLEE_SAVED & ~RBM_FPBASE);
@@ -7339,7 +7339,7 @@ void CodeGen::genFnProlog()
73397339

73407340
// We should not use the special PINVOKE registers as the initReg
73417341
// since they are trashed by the jithelper call to setup the PINVOKE frame
7342-
if (compiler->info.compCallUnmanaged)
7342+
if (compiler->compMethodRequiresPInvokeFrame())
73437343
{
73447344
excludeMask |= RBM_PINVOKE_FRAME;
73457345

@@ -7402,7 +7402,7 @@ void CodeGen::genFnProlog()
74027402
}
74037403
}
74047404

7405-
noway_assert(!compiler->info.compCallUnmanaged || (initReg != REG_PINVOKE_FRAME));
7405+
noway_assert(!compiler->compMethodRequiresPInvokeFrame() || (initReg != REG_PINVOKE_FRAME));
74067406

74077407
#if defined(_TARGET_AMD64_)
74087408
// If we are a varargs call, in order to set up the arguments correctly this

src/jit/compiler.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5914,8 +5914,8 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
59145914
}
59155915
info.compRetNativeType = info.compRetType = JITtype2varType(methodInfo->args.retType);
59165916

5917-
info.compCallUnmanaged = 0;
5918-
info.compLvFrameListRoot = BAD_VAR_NUM;
5917+
info.compUnmanagedCallCountWithGCTransition = 0;
5918+
info.compLvFrameListRoot = BAD_VAR_NUM;
59195919

59205920
info.compInitMem = ((methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0);
59215921

src/jit/compiler.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8790,12 +8790,13 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
87908790
UNATIVE_OFFSET compTotalHotCodeSize; // Total number of bytes of Hot Code in the method
87918791
UNATIVE_OFFSET compTotalColdCodeSize; // Total number of bytes of Cold Code in the method
87928792

8793-
unsigned compCallUnmanaged; // count of unmanaged calls
8793+
unsigned compUnmanagedCallCountWithGCTransition; // count of unmanaged calls with GC transition.
8794+
87948795
unsigned compLvFrameListRoot; // lclNum for the Frame root
87958796
unsigned compXcptnsCount; // Number of exception-handling clauses read in the method's IL.
87968797
// You should generally use compHndBBtabCount instead: it is the
87978798
// current number of EH clauses (after additions like synchronized
8798-
// methods and funclets, and removals like unreachable code deletion).
8799+
// methods and funclets, and removals like unreachable code deletion).
87998800

88008801
bool compMatchedVM; // true if the VM is "matched": either the JIT is a cross-compiler
88018802
// and the VM expects that, or the JIT is a "self-host" compiler
@@ -8896,6 +8897,12 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
88968897
compMethodReturnsMultiRegRetType();
88978898
}
88988899

8900+
// Returns true if the method requires a PInvoke prolog and epilog
8901+
bool compMethodRequiresPInvokeFrame()
8902+
{
8903+
return (info.compUnmanagedCallCountWithGCTransition > 0);
8904+
}
8905+
88998906
#if defined(DEBUG)
89008907

89018908
void compDispLocalVars();

src/jit/compiler.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3681,7 +3681,15 @@ inline bool Compiler::IsGcSafePoint(GenTree* tree)
36813681
GenTreeCall* call = tree->AsCall();
36823682
if (!call->IsFastTailCall())
36833683
{
3684-
if (call->gtCallType == CT_INDIRECT)
3684+
if (call->IsUnmanaged() && call->IsSuppressGCTransition())
3685+
{
3686+
// Both an indirect and user calls can be unmanaged
3687+
// and have a request to suppress the GC transition so
3688+
// the check is done prior to the separate handling of
3689+
// indirect and user calls.
3690+
return false;
3691+
}
3692+
else if (call->gtCallType == CT_INDIRECT)
36853693
{
36863694
return true;
36873695
}

src/jit/flowgraph.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3860,7 +3860,6 @@ void Compiler::fgCreateGCPolls()
38603860

38613861
bool Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block)
38623862
{
3863-
assert(!(block->bbFlags & BBF_GC_SAFE_POINT));
38643863
bool createdPollBlocks;
38653864

38663865
void* addrTrap;
@@ -3878,19 +3877,26 @@ bool Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block)
38783877
}
38793878
#endif // ENABLE_FAST_GCPOLL_HELPER
38803879

3880+
// If the trap and address of thread global are null, make the call.
3881+
if (addrTrap == nullptr && pAddrOfCaptureThreadGlobal == nullptr)
3882+
{
3883+
pollType = GCPOLL_CALL;
3884+
}
3885+
38813886
if (GCPOLL_CALL == pollType)
38823887
{
38833888
createdPollBlocks = false;
38843889
GenTreeCall* call = gtNewHelperCallNode(CORINFO_HELP_POLL_GC, TYP_VOID);
3890+
GenTree* temp = fgMorphCall(call);
38853891

38863892
// for BBJ_ALWAYS I don't need to insert it before the condition. Just append it.
38873893
if (block->bbJumpKind == BBJ_ALWAYS)
38883894
{
3889-
fgNewStmtAtEnd(block, call);
3895+
fgNewStmtAtEnd(block, temp);
38903896
}
38913897
else
38923898
{
3893-
Statement* newStmt = fgNewStmtNearEnd(block, call);
3899+
Statement* newStmt = fgNewStmtNearEnd(block, temp);
38943900
// For DDB156656, we need to associate the GC Poll with the IL offset (and therefore sequence
38953901
// point) of the tree before which we inserted the poll. One example of when this is a
38963902
// problem:
@@ -4026,6 +4032,10 @@ bool Compiler::fgCreateGCPoll(GCPollType pollType, BasicBlock* block)
40264032
// jumps, 2 for conditional branches, N for switches).
40274033
switch (oldJumpKind)
40284034
{
4035+
case BBJ_NONE:
4036+
// nothing to update. This can happen when inserting a GC Poll
4037+
// when suppressing a GC transition during an unmanaged call.
4038+
break;
40294039
case BBJ_RETURN:
40304040
// no successors
40314041
break;
@@ -8901,7 +8911,7 @@ void Compiler::fgAddInternal()
89018911

89028912
// The backend requires a scratch BB into which it can safely insert a P/Invoke method prolog if one is
89038913
// required. Create it here.
8904-
if (info.compCallUnmanaged != 0)
8914+
if (compMethodRequiresPInvokeFrame())
89058915
{
89068916
fgEnsureFirstBBisScratch();
89078917
fgFirstBB->bbFlags |= BBF_DONT_REMOVE;
@@ -9000,7 +9010,7 @@ void Compiler::fgAddInternal()
90009010
// or for synchronized methods.
90019011
//
90029012
BasicBlock* lastBlockBeforeGenReturns = fgLastBB;
9003-
if (compIsProfilerHookNeeded() || (info.compCallUnmanaged != 0) || opts.IsReversePInvoke() ||
9013+
if (compIsProfilerHookNeeded() || compMethodRequiresPInvokeFrame() || opts.IsReversePInvoke() ||
90049014
((info.compFlags & CORINFO_FLG_SYNCH) != 0))
90059015
{
90069016
// We will generate only one return block
@@ -9053,7 +9063,7 @@ void Compiler::fgAddInternal()
90539063

90549064
merger.PlaceReturns();
90559065

9056-
if (info.compCallUnmanaged != 0)
9066+
if (compMethodRequiresPInvokeFrame())
90579067
{
90589068
// The P/Invoke helpers only require a frame variable, so only allocate the
90599069
// TCB variable if we're not using them.
@@ -23188,8 +23198,8 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2318823198
}
2318923199
#endif // FEATURE_SIMD
2319023200

23191-
// Update unmanaged call count
23192-
info.compCallUnmanaged += InlineeCompiler->info.compCallUnmanaged;
23201+
// Update unmanaged call details
23202+
info.compUnmanagedCallCountWithGCTransition += InlineeCompiler->info.compUnmanagedCallCountWithGCTransition;
2319323203

2319423204
// Update optMethodFlags
2319523205

0 commit comments

Comments
 (0)