Skip to content

Commit ce688fd

Browse files
authored
JIT: refactor to allow OSR to switch to optimized (#61851)
When OSR is enabled, the jit may still need to switch to optimized codegen if there is something in the method that OSR cannot handle. Examples include: * localloc * loops in handlers * reverse pinvoke (currently bypasses Tiering so somewhat moot) * tail. prefixes (currently tolerated as Tiering should suffice) When OSR is enabled, rework the "switch to optimize logic" in the jit to check for these cases up front before we start importing code. When both QuickJitForLoops and OnStackReplacement are enabled, this should ensure that if we can't transition out of long-running Tier0 code (via OSR) then we will instead optimize the method. Very few methods overall should opt-out of Tier0. Note some of these unhandled constructs can eventually be handled by OSR, with additional work. For explicit tail calls: this should replicate the current logic where we always optimize methods with explicit tail calls unless we're instrumenting them. To make this work with OSR we simply won't put patchpoints in methods with explicit tail calls, instead trusting that tiering can get us to optimized versions.
1 parent 07143d4 commit ce688fd

File tree

9 files changed

+215
-45
lines changed

9 files changed

+215
-45
lines changed

src/coreclr/jit/compiler.cpp

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1878,6 +1878,7 @@ void Compiler::compInit(ArenaAllocator* pAlloc,
18781878
compJmpOpUsed = false;
18791879
compLongUsed = false;
18801880
compTailCallUsed = false;
1881+
compTailPrefixSeen = false;
18811882
compLocallocSeen = false;
18821883
compLocallocUsed = false;
18831884
compLocallocOptimized = false;
@@ -6277,7 +6278,8 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
62776278
info.compTotalColdCodeSize = 0;
62786279
info.compClassProbeCount = 0;
62796280

6280-
compHasBackwardJump = false;
6281+
compHasBackwardJump = false;
6282+
compHasBackwardJumpInHandler = false;
62816283

62826284
#ifdef DEBUG
62836285
compCurBB = nullptr;
@@ -6431,10 +6433,51 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
64316433
goto _Next;
64326434
}
64336435

6434-
if (compHasBackwardJump && (info.compFlags & CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS) != 0 && fgCanSwitchToOptimized())
6436+
// We may decide to optimize this method,
6437+
// to avoid spending a long time stuck in Tier0 code.
6438+
//
6439+
if (fgCanSwitchToOptimized())
64356440
{
6436-
// Method likely has a loop, switch to the OptimizedTier to avoid spending too much time running slower code
6437-
fgSwitchToOptimized();
6441+
// We only expect to be able to do this at Tier0.
6442+
//
6443+
assert(opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0));
6444+
6445+
// Normal tiering should bail us out of Tier0 tail call induced loops.
6446+
// So keep these methods in Tier0 if we're gathering PGO data.
6447+
// If we're not gathering PGO, then switch these to optimized to
6448+
// minimize the number of tail call helper stubs we might need.
6449+
// Reconsider this if/when we're able to share those stubs.
6450+
//
6451+
// Honor the config setting that tells the jit to
6452+
// always optimize methods with loops.
6453+
//
6454+
// If that's not set, and OSR is enabled, the jit may still
6455+
// decide to optimize, if there's something in the method that
6456+
// OSR currently cannot handle.
6457+
//
6458+
const char* reason = nullptr;
6459+
6460+
if (compTailPrefixSeen && !opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR))
6461+
{
6462+
reason = "tail.call and not BBINSTR";
6463+
}
6464+
else if ((info.compFlags & CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS) != 0)
6465+
{
6466+
if (compHasBackwardJump)
6467+
{
6468+
reason = "loop";
6469+
}
6470+
}
6471+
else if (JitConfig.TC_OnStackReplacement() > 0)
6472+
{
6473+
const bool patchpointsOK = compCanHavePatchpoints(&reason);
6474+
assert(patchpointsOK || (reason != nullptr));
6475+
}
6476+
6477+
if (reason != nullptr)
6478+
{
6479+
fgSwitchToOptimized(reason);
6480+
}
64386481
}
64396482

64406483
compSetOptimizationLevel();

src/coreclr/jit/compiler.h

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,8 @@ class Compiler
29832983
bool fgNormalizeEHCase2();
29842984
bool fgNormalizeEHCase3();
29852985

2986+
void fgCheckForLoopsInHandlers();
2987+
29862988
#ifdef DEBUG
29872989
void dispIncomingEHClause(unsigned num, const CORINFO_EH_CLAUSE& clause);
29882990
void dispOutgoingEHClause(unsigned num, const CORINFO_EH_CLAUSE& clause);
@@ -6068,7 +6070,7 @@ class Compiler
60686070
BasicBlock* fgLookupBB(unsigned addr);
60696071

60706072
bool fgCanSwitchToOptimized();
6071-
void fgSwitchToOptimized();
6073+
void fgSwitchToOptimized(const char* reason);
60726074

60736075
bool fgMayExplicitTailCall();
60746076

@@ -9385,21 +9387,23 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
93859387

93869388
InlineResult* compInlineResult; // The result of importing the inlinee method.
93879389

9388-
bool compDoAggressiveInlining; // If true, mark every method as CORINFO_FLG_FORCEINLINE
9389-
bool compJmpOpUsed; // Does the method do a JMP
9390-
bool compLongUsed; // Does the method use TYP_LONG
9391-
bool compFloatingPointUsed; // Does the method use TYP_FLOAT or TYP_DOUBLE
9392-
bool compTailCallUsed; // Does the method do a tailcall
9393-
bool compLocallocSeen; // Does the method IL have localloc opcode
9394-
bool compLocallocUsed; // Does the method use localloc.
9395-
bool compLocallocOptimized; // Does the method have an optimized localloc
9396-
bool compQmarkUsed; // Does the method use GT_QMARK/GT_COLON
9397-
bool compQmarkRationalized; // Is it allowed to use a GT_QMARK/GT_COLON node.
9398-
bool compUnsafeCastUsed; // Does the method use LDIND/STIND to cast between scalar/refernce types
9399-
bool compHasBackwardJump; // Does the method (or some inlinee) have a lexically backwards jump?
9400-
bool compSwitchedToOptimized; // Codegen initially was Tier0 but jit switched to FullOpts
9401-
bool compSwitchedToMinOpts; // Codegen initially was Tier1/FullOpts but jit switched to MinOpts
9402-
bool compSuppressedZeroInit; // There are vars with lvSuppressedZeroInit set
9390+
bool compDoAggressiveInlining; // If true, mark every method as CORINFO_FLG_FORCEINLINE
9391+
bool compJmpOpUsed; // Does the method do a JMP
9392+
bool compLongUsed; // Does the method use TYP_LONG
9393+
bool compFloatingPointUsed; // Does the method use TYP_FLOAT or TYP_DOUBLE
9394+
bool compTailCallUsed; // Does the method do a tailcall
9395+
bool compTailPrefixSeen; // Does the method IL have tail. prefix
9396+
bool compLocallocSeen; // Does the method IL have localloc opcode
9397+
bool compLocallocUsed; // Does the method use localloc.
9398+
bool compLocallocOptimized; // Does the method have an optimized localloc
9399+
bool compQmarkUsed; // Does the method use GT_QMARK/GT_COLON
9400+
bool compQmarkRationalized; // Is it allowed to use a GT_QMARK/GT_COLON node.
9401+
bool compUnsafeCastUsed; // Does the method use LDIND/STIND to cast between scalar/refernce types
9402+
bool compHasBackwardJump; // Does the method (or some inlinee) have a lexically backwards jump?
9403+
bool compHasBackwardJumpInHandler; // Does the method have a lexically backwards jump in a handler?
9404+
bool compSwitchedToOptimized; // Codegen initially was Tier0 but jit switched to FullOpts
9405+
bool compSwitchedToMinOpts; // Codegen initially was Tier1/FullOpts but jit switched to MinOpts
9406+
bool compSuppressedZeroInit; // There are vars with lvSuppressedZeroInit set
94039407

94049408
// NOTE: These values are only reliable after
94059409
// the importing is completely finished.

src/coreclr/jit/compiler.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4716,6 +4716,10 @@ inline bool Compiler::compCanHavePatchpoints(const char** reason)
47164716
{
47174717
whyNot = "localloc";
47184718
}
4719+
else if (compHasBackwardJumpInHandler)
4720+
{
4721+
whyNot = "loop in handler";
4722+
}
47194723
else if (opts.IsReversePInvoke())
47204724
{
47214725
whyNot = "reverse pinvoke";

src/coreclr/jit/fgbasic.cpp

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,15 +2738,9 @@ unsigned Compiler::fgMakeBasicBlocks(const BYTE* codeAddr, IL_OFFSET codeSize, F
27382738
BADCODE3("tail call not followed by ret", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
27392739
}
27402740

2741-
if (fgCanSwitchToOptimized() && fgMayExplicitTailCall())
2741+
if (fgMayExplicitTailCall())
27422742
{
2743-
// Method has an explicit tail call that may run like a loop or may not be generated as a tail
2744-
// call in tier 0, switch to optimized to avoid spending too much time running slower code
2745-
if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) ||
2746-
((info.compFlags & CORINFO_FLG_DISABLE_TIER0_FOR_LOOPS) != 0))
2747-
{
2748-
fgSwitchToOptimized();
2749-
}
2743+
compTailPrefixSeen = true;
27502744
}
27512745
}
27522746
else
@@ -3534,6 +3528,57 @@ void Compiler::fgFindBasicBlocks()
35343528
#endif
35353529

35363530
fgNormalizeEH();
3531+
3532+
fgCheckForLoopsInHandlers();
3533+
}
3534+
3535+
//------------------------------------------------------------------------
3536+
// fgCheckForLoopsInHandlers: scan blocks seeing if any handler block
3537+
// is a backedge target.
3538+
//
3539+
// Notes:
3540+
// Sets compHasBackwardJumpInHandler if so. This will disable
3541+
// setting patchpoints in this method and prompt the jit to
3542+
// optimize the method instead.
3543+
//
3544+
// We assume any late-added handler (say for synchronized methods) will
3545+
// not introduce any loops.
3546+
//
3547+
void Compiler::fgCheckForLoopsInHandlers()
3548+
{
3549+
// We only care about this if we are going to set OSR patchpoints
3550+
// and the method has exception handling.
3551+
//
3552+
if (!opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0))
3553+
{
3554+
return;
3555+
}
3556+
3557+
if (JitConfig.TC_OnStackReplacement() == 0)
3558+
{
3559+
return;
3560+
}
3561+
3562+
if (info.compXcptnsCount == 0)
3563+
{
3564+
return;
3565+
}
3566+
3567+
// Walk blocks in handlers and filters, looing for a backedge target.
3568+
//
3569+
assert(!compHasBackwardJumpInHandler);
3570+
for (BasicBlock* const blk : Blocks())
3571+
{
3572+
if (blk->hasHndIndex())
3573+
{
3574+
if (blk->bbFlags & BBF_BACKWARD_JUMP_TARGET)
3575+
{
3576+
JITDUMP("\nHander block " FMT_BB "is backward jump target; can't have patchpoints in this method\n");
3577+
compHasBackwardJumpInHandler = true;
3578+
break;
3579+
}
3580+
}
3581+
}
35373582
}
35383583

35393584
//------------------------------------------------------------------------

src/coreclr/jit/fgopt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,7 @@ void Compiler::fgPostImportationCleanup()
15231523

15241524
BasicBlock* const newBlock = fgSplitBlockAtBeginning(fromBlock);
15251525
fromBlock->bbFlags |= BBF_INTERNAL;
1526+
newBlock->bbFlags &= ~BBF_DONT_REMOVE;
15261527

15271528
GenTree* const entryStateLcl = gtNewLclvNode(entryStateVar, TYP_INT);
15281529
GenTree* const compareEntryStateToZero =

src/coreclr/jit/flowgraph.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,9 @@ bool Compiler::fgCanSwitchToOptimized()
524524
//------------------------------------------------------------------------
525525
// fgSwitchToOptimized: Switch the opt level from tier 0 to optimized
526526
//
527+
// Arguments:
528+
// reason - reason why opt level was switched
529+
//
527530
// Assumptions:
528531
// - fgCanSwitchToOptimized() is true
529532
// - compSetOptimizationLevel() has not been called
@@ -532,15 +535,16 @@ bool Compiler::fgCanSwitchToOptimized()
532535
// This method is to be called at some point before compSetOptimizationLevel() to switch the opt level to optimized
533536
// based on information gathered in early phases.
534537

535-
void Compiler::fgSwitchToOptimized()
538+
void Compiler::fgSwitchToOptimized(const char* reason)
536539
{
537540
assert(fgCanSwitchToOptimized());
538541

539542
// Switch to optimized and re-init options
540-
JITDUMP("****\n**** JIT Tier0 jit request switching to Tier1 because of loop\n****\n");
543+
JITDUMP("****\n**** JIT Tier0 jit request switching to Tier1 because: %s\n****\n", reason);
541544
assert(opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0));
542545
opts.jitFlags->Clear(JitFlags::JIT_FLAG_TIER0);
543546
opts.jitFlags->Clear(JitFlags::JIT_FLAG_BBINSTR);
547+
opts.jitFlags->Clear(JitFlags::JIT_FLAG_OSR);
544548

545549
// Leave a note for jit diagnostics
546550
compSwitchedToOptimized = true;

src/coreclr/jit/importer.cpp

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11591,26 +11591,38 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1159111591
#ifdef FEATURE_ON_STACK_REPLACEMENT
1159211592

1159311593
// Are there any places in the method where we might add a patchpoint?
11594+
//
1159411595
if (compHasBackwardJump)
1159511596
{
11596-
// Are patchpoints enabled and supported?
11597-
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0) && (JitConfig.TC_OnStackReplacement() > 0) &&
11598-
compCanHavePatchpoints())
11597+
// Is OSR enabled?
11598+
//
11599+
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0) && (JitConfig.TC_OnStackReplacement() > 0))
1159911600
{
11600-
// We don't inline at Tier0, if we do, we may need rethink our approach.
11601-
// Could probably support inlines that don't introduce flow.
11602-
assert(!compIsForInlining());
11603-
11604-
// Is the start of this block a suitable patchpoint?
11605-
// Current strategy is blocks that are stack-empty and backwards branch targets and not in a handler
11601+
// OSR is not yet supported for methods with explicit tail calls.
1160611602
//
11607-
// Todo (perhaps): bail out of OSR and jit this method with optimization.
11603+
// But we also may not switch methods to be optimized as we should be
11604+
// able to avoid getting trapped in Tier0 code by normal call counting.
11605+
// So instead, just suppress adding patchpoints.
1160811606
//
11609-
if (!block->hasHndIndex() && ((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) != 0) &&
11610-
(verCurrentState.esStackDepth == 0))
11607+
if (!compTailPrefixSeen)
1161111608
{
11612-
block->bbFlags |= BBF_PATCHPOINT;
11613-
setMethodHasPatchpoint();
11609+
assert(compCanHavePatchpoints());
11610+
11611+
// We don't inline at Tier0, if we do, we may need rethink our approach.
11612+
// Could probably support inlines that don't introduce flow.
11613+
assert(!compIsForInlining());
11614+
11615+
// Is the start of this block a suitable patchpoint?
11616+
//
11617+
if (((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) != 0) && (verCurrentState.esStackDepth == 0))
11618+
{
11619+
// We should have noted this earlier and bailed out of OSR.
11620+
//
11621+
assert(!block->hasHndIndex());
11622+
11623+
block->bbFlags |= BBF_PATCHPOINT;
11624+
setMethodHasPatchpoint();
11625+
}
1161411626
}
1161511627
}
1161611628
}
@@ -11630,10 +11642,12 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1163011642
// propagate rareness back through flow and place the partial compilation patchpoints "earlier"
1163111643
// so there are fewer overall.
1163211644
//
11645+
// Note unlike OSR, it's ok to forgo these.
11646+
//
1163311647
// Todo: stress mode...
1163411648
//
1163511649
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TIER0) && (JitConfig.TC_PartialCompilation() > 0) &&
11636-
compCanHavePatchpoints())
11650+
compCanHavePatchpoints() && !compTailPrefixSeen)
1163711651
{
1163811652
// Is this block a good place for partial compilation?
1163911653
//

src/tests/JIT/opt/OSR/handlerloop.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
4+
using System;
5+
6+
// OSR can't bail us out of a loop in a handler
7+
//
8+
class OSRHandlerLoop
9+
{
10+
public static int Main()
11+
{
12+
int result = 0;
13+
int expected = 0;
14+
try
15+
{
16+
result++;
17+
expected = 704982705;
18+
}
19+
finally
20+
{
21+
for (int i = 0; i < 100_000; i++)
22+
{
23+
result += i;
24+
}
25+
}
26+
27+
Console.WriteLine($"{result} expected {expected}");
28+
29+
return (result == expected) ? 100 : -1;
30+
}
31+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<DebugType />
5+
<Optimize>True</Optimize>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<Compile Include="$(MSBuildProjectName).cs" />
9+
</ItemGroup>
10+
<PropertyGroup>
11+
<CLRTestBatchPreCommands><![CDATA[
12+
$(CLRTestBatchPreCommands)
13+
set COMPlus_TieredCompilation=1
14+
set COMPlus_TC_QuickJitForLoops=1
15+
set COMPlus_TC_OnStackReplacement=1
16+
]]></CLRTestBatchPreCommands>
17+
<BashCLRTestPreCommands><![CDATA[
18+
$(BashCLRTestPreCommands)
19+
export COMPlus_TieredCompilation=1
20+
export COMPlus_TC_QuickJitForLoops=1
21+
export COMPlus_TC_OnStackReplacement=1
22+
]]></BashCLRTestPreCommands>
23+
</PropertyGroup>
24+
</Project>

0 commit comments

Comments
 (0)