Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/coreclr/jit/fgopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,70 @@ bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block)

return true;
}
else if (block->GetSwitchTargets()->GetSuccCount() == 2 && block->GetSwitchTargets()->HasDefaultCase() &&
!block->IsLIR() && fgNodeThreading == NodeThreading::AllTrees)
{
// If all non-default cases jump to the same target and the default jumps to a different target,
// replace the switch with an unsigned comparison against the max case index:
// GT_SWITCH(switchVal) -> GT_JTRUE(GT_LT(switchVal, caseCount))

BBswtDesc* switchDesc = block->GetSwitchTargets();

FlowEdge* defaultEdge = switchDesc->GetDefaultCase();
BasicBlock* defaultDest = defaultEdge->getDestinationBlock();
FlowEdge* firstCaseEdge = switchDesc->GetCase(0);
BasicBlock* caseDest = firstCaseEdge->getDestinationBlock();

// Optimize only when all non-default cases share the same target, distinct from the default target.
// Only the default case targets defaultDest.
if (defaultEdge->getDupCount() != 1)
{
return modified;
}

JITDUMP("\nConverting a switch (" FMT_BB ") where all non-default cases target the same block to a "
"conditional branch. Before:\n",
block->bbNum);
DISPNODE(switchTree);

// Use GT_LT (e.g., switchVal < caseCount), so true (in range) goes to the shared case target and false (out of
// range) goes to the default case.
switchTree->ChangeOper(GT_JTRUE);
GenTree* switchVal = switchTree->AsOp()->gtOp1;
noway_assert(genActualTypeIsIntOrI(switchVal->TypeGet()));
const unsigned caseCount = firstCaseEdge->getDupCount();
GenTree* iconNode = gtNewIconNode(caseCount, genActualType(switchVal->TypeGet()));
GenTree* condNode = gtNewOperNode(GT_LT, TYP_INT, switchVal, iconNode);
condNode->SetUnsigned();
switchTree->AsOp()->gtOp1 = condNode;
switchTree->AsOp()->gtOp1->gtFlags |= (GTF_RELOP_JMP_USED | GTF_DONT_CSE);

gtSetStmtInfo(switchStmt);
fgSetStmtSeq(switchStmt);

// Fix up dup counts: multiple switch cases originally pointed to the same
// successor, but the conditional branch has exactly one edge per target.
const unsigned caseDupCount = firstCaseEdge->getDupCount();
if (caseDupCount > 1)
{
firstCaseEdge->decrementDupCount(caseDupCount - 1);
caseDest->bbRefs -= (caseDupCount - 1);
}

block->SetCond(firstCaseEdge, defaultEdge);

JITDUMP("After:\n");
DISPNODE(switchTree);

if (fgFoldCondToReturnBlock(block))
{
JITDUMP("Folded conditional return into branchless return. After:\n");
DISPNODE(switchTree);
}

return true;
}

return modified;
}

Expand Down
48 changes: 48 additions & 0 deletions src/tests/JIT/opt/OptSwitchRecognition/optSwitchRecognition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,52 @@ private static int RecSwitchSkipBitTest(int arch)
[InlineData(6, 4)]
[InlineData(10, 1)]
public static void TestRecSwitchSkipBitTest(int arg1, int expected) => Assert.Equal(expected, RecSwitchSkipBitTest(arg1));

// Test that consecutive equality comparisons (comparison chain) produce the same result
// as pattern matching. The switch recognition should convert the chain to a switch, and
// then fgOptimizeSwitchBranches should simplify it to an unsigned LE comparison.
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool IsLetterCategoryCompare(int uc)
{
return uc == 0
|| uc == 1
|| uc == 2
|| uc == 3
|| uc == 4;
}

[Theory]
[InlineData(-1, false)]
[InlineData(0, true)]
[InlineData(1, true)]
[InlineData(2, true)]
[InlineData(3, true)]
[InlineData(4, true)]
[InlineData(5, false)]
[InlineData(100, false)]
[InlineData(int.MinValue, false)]
[InlineData(int.MaxValue, false)]
public static void TestSwitchToRangeCheck(int arg1, bool expected) => Assert.Equal(expected, IsLetterCategoryCompare(arg1));

// Test with non-zero-based consecutive values
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool IsInRange10To14(int val)
{
return val == 10
|| val == 11
|| val == 12
|| val == 13
|| val == 14;
}

[Theory]
[InlineData(9, false)]
[InlineData(10, true)]
[InlineData(11, true)]
[InlineData(12, true)]
[InlineData(13, true)]
[InlineData(14, true)]
[InlineData(15, false)]
[InlineData(-1, false)]
public static void TestSwitchToRangeCheckNonZeroBased(int arg1, bool expected) => Assert.Equal(expected, IsInRange10To14(arg1));
}
Loading