Skip to content

Commit

Permalink
JIT: add OSR patchpoint strategy, inhibit tail duplication (dotnet#66208
Browse files Browse the repository at this point in the history
)

Two changes for OSR:
* add new strategies for placing patchpoints -- either at
  backedge sources (instead of targets) or adaptive. depending
  on number of backedges. Change default to adaptive, since this
  works better with the flow we see from C# `for` loops.
* inhibit tail duplication for OSR as it may end up interfering
  with loop recognition.

We may not be able to place patchpoints at sources, for various reasons;
if so we fall back to placing them at targets.

We also can't place patchpoints unless block entries are also stack empty
ponts. This means forgoing patchpoints in some IL cases..
  • Loading branch information
AndyAyersMS authored Mar 6, 2022
1 parent 6fce82c commit f9da3db
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/coreclr/jit/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ void BasicBlock::dspFlags()
{
printf("bwd-target ");
}
if (bbFlags & BBF_BACKWARD_JUMP_SOURCE)
{
printf("bwd-src ");
}
if (bbFlags & BBF_PATCHPOINT)
{
printf("ppoint ");
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,8 @@ enum BasicBlockFlags : unsigned __int64
BBF_HAS_ALIGN = MAKE_BBFLAG(39), // BB ends with 'align' instruction
BBF_TAILCALL_SUCCESSOR = MAKE_BBFLAG(40), // BB has pred that has potential tail call

BBF_BACKWARD_JUMP_SOURCE = MAKE_BBFLAG(41), // Block is a source of a backward jump

// The following are sets of flags.

// Flags that relate blocks to loop structure.
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/fgbasic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,7 @@ void Compiler::fgMarkBackwardJump(BasicBlock* targetBlock, BasicBlock* sourceBlo
}
}

sourceBlock->bbFlags |= BBF_BACKWARD_JUMP_SOURCE;
targetBlock->bbFlags |= BBF_BACKWARD_JUMP_TARGET;
}

Expand Down
25 changes: 25 additions & 0 deletions src/coreclr/jit/fgopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3477,6 +3477,31 @@ bool Compiler::fgOptimizeUncondBranchToSimpleCond(BasicBlock* block, BasicBlock*
return false;
}

// At this point we know target is BBJ_COND.
//
// Bail out if OSR, as we can have unusual flow into loops. If one
// of target's successors is also a backedge target, this optimization
// may mess up loop recognition by creating too many non-loop preds.
//
if (opts.IsOSR())
{
assert(target->bbJumpKind == BBJ_COND);

if ((target->bbNext->bbFlags & BBF_BACKWARD_JUMP_TARGET) != 0)
{
JITDUMP("Deferring: " FMT_BB " --> " FMT_BB "; latter looks like loop top\n", target->bbNum,
target->bbNext->bbNum);
return false;
}

if ((target->bbJumpDest->bbFlags & BBF_BACKWARD_JUMP_TARGET) != 0)
{
JITDUMP("Deferring: " FMT_BB " --> " FMT_BB "; latter looks like loop top\n", target->bbNum,
target->bbJumpDest->bbNum);
return false;
}
}

// See if this block assigns constant or other interesting tree to that same local.
//
if (!fgBlockEndFavorsTailDuplication(block, lclNum))
Expand Down
138 changes: 127 additions & 11 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11873,35 +11873,151 @@ void Compiler::impImportBlockCode(BasicBlock* block)

// OSR is not yet supported for methods with explicit tail calls.
//
// But we also do not have to switch these methods to be optimized as we should be
// But we also do not have to switch these methods to be optimized, as we should be
// able to avoid getting trapped in Tier0 code by normal call counting.
// So instead, just suppress adding patchpoints.
//
if (!compTailPrefixSeen)
{
// The normaly policy is only to add patchpoints to the targets of lexically
// backwards branches.
// We only need to add patchpoints if the method can loop.
//
if (compHasBackwardJump)
{
assert(compCanHavePatchpoints());

// Is the start of this block a suitable patchpoint?
// By default we use the "adaptive" strategy.
//
if (((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) != 0) && (verCurrentState.esStackDepth == 0))
// This can create both source and target patchpoints within a given
// loop structure, which isn't ideal, but is not incorrect. We will
// just have some extra Tier0 overhead.
//
// Todo: implement support for mid-block patchpoints. If `block`
// is truly a backedge source (and not in a handler) then we should be
// able to find a stack empty point somewhere in the block.
//
const int patchpointStrategy = JitConfig.TC_PatchpointStrategy();
bool addPatchpoint = false;
bool mustUseTargetPatchpoint = false;

switch (patchpointStrategy)
{
// We should have noted this earlier and bailed out of OSR.
//
assert(!block->hasHndIndex());
default:
{
// Patchpoints at backedge sources, if possible, otherwise targets.
//
addPatchpoint = ((block->bbFlags & BBF_BACKWARD_JUMP_SOURCE) == BBF_BACKWARD_JUMP_SOURCE);
mustUseTargetPatchpoint = (verCurrentState.esStackDepth != 0) || block->hasHndIndex();
break;
}

case 1:
{
// Patchpoints at stackempty backedge targets.
// Note if we have loops where the IL stack is not empty on the backedge we can't patchpoint
// them.
//
// We should not have allowed OSR if there were backedges in handlers.
//
assert(!block->hasHndIndex());
addPatchpoint = ((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) == BBF_BACKWARD_JUMP_TARGET) &&
(verCurrentState.esStackDepth == 0);
break;
}

case 2:
{
// Adaptive strategy.
//
// Patchpoints at backedge targets if there are multiple backedges,
// otherwise at backedge sources, if possible. Note a block can be both; if so we
// just need one patchpoint.
//
if ((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) == BBF_BACKWARD_JUMP_TARGET)
{
// We don't know backedge count, so just use ref count.
//
addPatchpoint = (block->bbRefs > 1) && (verCurrentState.esStackDepth == 0);
}

if (!addPatchpoint && ((block->bbFlags & BBF_BACKWARD_JUMP_SOURCE) == BBF_BACKWARD_JUMP_SOURCE))
{
addPatchpoint = true;
mustUseTargetPatchpoint = (verCurrentState.esStackDepth != 0) || block->hasHndIndex();

// Also force target patchpoint if target block has multiple (backedge) preds.
//
if (!mustUseTargetPatchpoint)
{
for (BasicBlock* const succBlock : block->Succs(this))
{
if ((succBlock->bbNum <= block->bbNum) && (succBlock->bbRefs > 1))
{
mustUseTargetPatchpoint = true;
break;
}
}
}
}
break;
}
}

if (addPatchpoint)
{
if (mustUseTargetPatchpoint)
{
// We wanted a source patchpoint, but could not have one.
// So, add patchpoints to the backedge targets.
//
for (BasicBlock* const succBlock : block->Succs(this))
{
if (succBlock->bbNum <= block->bbNum)
{
// The succBlock had better agree it's a target.
//
assert((succBlock->bbFlags & BBF_BACKWARD_JUMP_TARGET) == BBF_BACKWARD_JUMP_TARGET);

// We may already have decided to put a patchpoint in succBlock. If not, add one.
//
if ((succBlock->bbFlags & BBF_PATCHPOINT) != 0)
{
// In some cases the target may not be stack-empty at entry.
// If so, we will bypass patchpoints for this backedge.
//
if (succBlock->bbStackDepthOnEntry() > 0)
{
JITDUMP("\nCan't set source patchpoint at " FMT_BB ", can't use target " FMT_BB
" as it has non-empty stack on entry.\n",
block->bbNum, succBlock->bbNum);
}
else
{
JITDUMP("\nCan't set source patchpoint at " FMT_BB ", using target " FMT_BB
" instead\n",
block->bbNum, succBlock->bbNum);

assert(!succBlock->hasHndIndex());
succBlock->bbFlags |= BBF_PATCHPOINT;
}
}
}
}
}
else
{
assert(!block->hasHndIndex());
block->bbFlags |= BBF_PATCHPOINT;
}

block->bbFlags |= BBF_PATCHPOINT;
setMethodHasPatchpoint();
}
}
else
{
// Should not see backward branch targets w/o backwards branches
assert((block->bbFlags & BBF_BACKWARD_JUMP_TARGET) == 0);
// Should not see backward branch targets w/o backwards branches.
// So if !compHasBackwardsBranch, these flags should never be set.
//
assert((block->bbFlags & (BBF_BACKWARD_JUMP_TARGET | BBF_BACKWARD_JUMP_SOURCE)) == 0);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,11 @@ CONFIG_INTEGER(TC_OnStackReplacement, W("TC_OnStackReplacement"), 0)
CONFIG_INTEGER(TC_OnStackReplacement_InitialCounter, W("TC_OnStackReplacement_InitialCounter"), 1000)
// Enable partial compilation for Tier0 methods
CONFIG_INTEGER(TC_PartialCompilation, W("TC_PartialCompilation"), 0)
// Patchpoint strategy:
// 0 - backedge sources
// 1 - backedge targets
// 2 - adaptive (default)
CONFIG_INTEGER(TC_PatchpointStrategy, W("TC_PatchpointStrategy"), 2)
#if defined(DEBUG)
// Randomly sprinkle patchpoints. Value is the likelyhood any given stack-empty point becomes a patchpoint.
CONFIG_INTEGER(JitRandomOnStackReplacement, W("JitRandomOnStackReplacement"), 0)
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/jiteh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2350,6 +2350,7 @@ bool Compiler::fgNormalizeEHCase2()

BasicBlock* newTryStart = bbNewBasicBlock(BBJ_NONE);
fgInsertBBbefore(insertBeforeBlk, newTryStart);
insertBeforeBlk->bbRefs++;

#ifdef DEBUG
if (verbose)
Expand All @@ -2376,6 +2377,11 @@ bool Compiler::fgNormalizeEHCase2()
// start.
newTryStart->bbFlags |= (BBF_TRY_BEG | BBF_DONT_REMOVE | BBF_INTERNAL);

if (insertBeforeBlk->bbFlags & BBF_BACKWARD_JUMP_TARGET)
{
newTryStart->bbFlags |= BBF_BACKWARD_JUMP_TARGET;
}

// Now we need to split any flow edges targetting the old try begin block between the old
// and new block. Note that if we are handling a multiply-nested 'try', we may have already
// split the inner set. So we need to split again, from the most enclosing block that we've
Expand Down

0 comments on commit f9da3db

Please sign in to comment.