@@ -451,21 +451,32 @@ static BuiltinValueKind invertCmpID(BuiltinValueKind ID) {
451451 }
452452}
453453
454- // / Checks if Start to End is the range of 0 to the count of an array.
455- // / Returns the array if this is the case.
456- static SILValue getZeroToCountArray (SILValue Start , SILValue End ) {
457- auto *IL = dyn_cast<IntegerLiteralInst>(Start );
458- if (!IL || IL ->getValue () != 0 )
454+ // / Checks if Start to End is the range of 0 to the count of an array or a fixed
455+ // / storage type. Returns the self value if this is the case.
456+ static SILValue getZeroToCountOfSelf (SILValue start , SILValue end ) {
457+ auto *intLiteral = dyn_cast<IntegerLiteralInst>(start );
458+ if (!intLiteral || intLiteral ->getValue () != 0 ) {
459459 return SILValue ();
460-
461- auto *SEI = dyn_cast<StructExtractInst>(End );
462- if (!SEI)
460+ }
461+ auto *sei = dyn_cast<StructExtractInst>(end );
462+ if (!sei) {
463463 return SILValue ();
464-
465- ArraySemanticsCall SemCall (SEI ->getOperand ());
466- if (SemCall. getKind () != ArrayCallKind:: kGetCount )
464+ }
465+ auto *applyInst = dyn_cast<ApplyInst>(sei ->getOperand ());
466+ if (!applyInst) {
467467 return SILValue ();
468- return SemCall.getSelf ();
468+ }
469+ auto *callee = applyInst->getReferencedFunctionOrNull ();
470+ if (!callee) {
471+ return SILValue ();
472+ }
473+ for (auto attr : callee->getSemanticsAttrs ()) {
474+ if (attr == " array.get_count" || attr == " fixed_storage.get_count" ) {
475+ return applyInst->hasSelfArgument () ? applyInst->getSelfArgument ()
476+ : SILValue ();
477+ }
478+ }
479+ return SILValue ();
469480}
470481
471482// / Checks whether the cond_br in the preheader's predecessor ensures that the
@@ -503,7 +514,7 @@ static bool isLessThanCheck(SILValue Start, SILValue End,
503514 // Special case: if it is a 0-to-count loop, we know that the count cannot
504515 // be negative. In this case the 'Start < End' check can also be done with
505516 // 'count != 0'.
506- return getZeroToCountArray (Start, End);
517+ return getZeroToCountOfSelf (Start, End);
507518 default :
508519 return false ;
509520 }
@@ -815,6 +826,24 @@ class AccessFunction {
815826 return getZeroToCountOfSelf (Ind->Start , Ind->End ) == selfValue;
816827 }
817828
829+ SILValue getFirstValue (SILInstruction *insertPt) {
830+ SILBuilderWithScope builder (insertPt);
831+ auto firstValue =
832+ Ind->getFirstValue (insertPt->getLoc (), builder, preIncrement ? 1 : 0 );
833+ auto intType = SILType::getPrimitiveObjectType (
834+ builder.getASTContext ().getIntType ()->getCanonicalType ());
835+ return builder.createStruct (insertPt->getLoc (), intType, {firstValue});
836+ }
837+
838+ SILValue getLastValue (SILInstruction *insertPt) {
839+ SILBuilderWithScope builder (insertPt);
840+ auto lastValue =
841+ Ind->getLastValue (insertPt->getLoc (), builder, preIncrement ? 0 : 1 );
842+ auto intType = SILType::getPrimitiveObjectType (
843+ builder.getASTContext ().getIntType ()->getCanonicalType ());
844+ return builder.createStruct (insertPt->getLoc (), intType, {lastValue});
845+ }
846+
818847 // / Hoists the necessary check for beginning and end of the induction
819848 // / encapsulated by this access function to the header.
820849 void hoistCheckToPreheader (ArraySemanticsCall CheckToHoist,
@@ -958,8 +987,12 @@ class BoundsCheckOpts : public SILFunctionTransform {
958987 std::pair<bool , std::optional<InductionAnalysis>>
959988 findAndOptimizeInductionVariables (SILLoop *loop);
960989
961- bool optimizeArrayBoundsCheckInLoop (SILLoop *loop,
962- std::optional<InductionAnalysis> indVars);
990+ bool
991+ optimizeArrayBoundsCheckInLoop (SILLoop *loop,
992+ std::optional<InductionAnalysis> &indVars);
993+
994+ bool optimizeFixedStorageBoundsCheckInLoop (
995+ SILLoop *loop, std::optional<InductionAnalysis> &indVars);
963996
964997 // / Remove redundant checks in a basic block. This function will reset the
965998 // / state after an instruction that may modify any array allowing removal of
@@ -979,6 +1012,16 @@ class BoundsCheckOpts : public SILFunctionTransform {
9791012 InductionAnalysis &indVars,
9801013 int recursionDepth);
9811014
1015+ bool hoistFixedStorageBoundsChecksInLoop (SILLoop *loop,
1016+ DominanceInfoNode *currentNode,
1017+ InductionAnalysis &indVars,
1018+ int recursionDepth);
1019+
1020+ bool removeRedundantFixedStorageBoundsChecksInLoop (
1021+ SILLoop *loop, DominanceInfoNode *currentNode,
1022+ llvm::DenseSet<std::pair<SILValue, SILValue>> &dominatingSafeChecks,
1023+ int recursionDepth);
1024+
9821025public:
9831026 void run () override {
9841027 bool changed = false ;
@@ -1232,7 +1275,9 @@ bool BoundsCheckOpts::optimizeArrayBoundsCheckInLoop(SILLoop *loop) {
12321275 bool changed = false ;
12331276 auto result = findAndOptimizeInductionVariables (loop);
12341277 changed |= result.first ;
1235- changed |= optimizeArrayBoundsCheckInLoop (loop, std::move (result.second ));
1278+ auto indVars = std::move (result.second );
1279+ changed |= optimizeArrayBoundsCheckInLoop (loop, indVars);
1280+ changed |= optimizeFixedStorageBoundsCheckInLoop (loop, indVars);
12361281
12371282 if (changed) {
12381283 preheader->getParent ()->verify (
@@ -1341,7 +1386,7 @@ BoundsCheckOpts::findAndOptimizeInductionVariables(SILLoop *loop) {
13411386}
13421387
13431388bool BoundsCheckOpts::optimizeArrayBoundsCheckInLoop (
1344- SILLoop *loop, std::optional<InductionAnalysis> indVars) {
1389+ SILLoop *loop, std::optional<InductionAnalysis> & indVars) {
13451390
13461391 // Collect safe arrays. Arrays are safe if there is no function call that
13471392 // could mutate their size in the loop.
@@ -1377,6 +1422,31 @@ bool BoundsCheckOpts::optimizeArrayBoundsCheckInLoop(
13771422 return changed;
13781423}
13791424
1425+ bool BoundsCheckOpts::optimizeFixedStorageBoundsCheckInLoop (
1426+ SILLoop *loop, std::optional<InductionAnalysis> &indVars) {
1427+ // Try removing redundant bounds checks in the loop.
1428+ LLVM_DEBUG (llvm::dbgs () << " Attempting to eliminate redundant bounds checks "
1429+ " for Span and InlineArray in "
1430+ << *loop);
1431+ llvm::DenseSet<std::pair<SILValue, SILValue>>
1432+ dominatingSafeFixedStorageChecks;
1433+ bool changed = removeRedundantFixedStorageBoundsChecksInLoop (
1434+ loop, DT->getNode (loop->getHeader ()), dominatingSafeFixedStorageChecks,
1435+ /* recursionDepth*/ 0 );
1436+
1437+ // Try hoisting bounds checks from the loop.
1438+ LLVM_DEBUG (llvm::dbgs ()
1439+ << " Attempting to hoist bounds checks for Span and InlineArray in "
1440+ << *loop);
1441+ if (!indVars) {
1442+ LLVM_DEBUG (llvm::dbgs () << " No induction variables found\n " );
1443+ return changed;
1444+ }
1445+ changed |= hoistFixedStorageBoundsChecksInLoop (
1446+ loop, DT->getNode (loop->getHeader ()), *indVars, /* recursionDepth*/ 0 );
1447+ return changed;
1448+ }
1449+
13801450bool BoundsCheckOpts::hoistArrayBoundsChecksInLoop (
13811451 SILLoop *loop, DominanceInfoNode *currentNode, ABCAnalysis &abcAnalysis,
13821452 InductionAnalysis &indVars, int recursionDepth) {
@@ -1488,6 +1558,171 @@ bool BoundsCheckOpts::hoistArrayBoundsChecksInLoop(
14881558 return changed;
14891559}
14901560
1561+ bool BoundsCheckOpts::hoistFixedStorageBoundsChecksInLoop (
1562+ SILLoop *loop, DominanceInfoNode *currentNode, InductionAnalysis &indVars,
1563+ int recursionDepth) {
1564+ auto preheader = loop->getLoopPreheader ();
1565+ auto singleExitingBlock = loop->getExitingBlock ();
1566+ // Avoid a stack overflow for very deep dominator trees.
1567+ if (recursionDepth >= maxRecursionDepth)
1568+ return false ;
1569+
1570+ bool changed = false ;
1571+ auto *curBlock = currentNode->getBlock ();
1572+ bool blockAlwaysExecutes =
1573+ isGuaranteedToBeExecuted (DT, curBlock, singleExitingBlock);
1574+
1575+ for (auto instIt = curBlock->begin (); instIt != curBlock->end ();) {
1576+ auto inst = &*instIt;
1577+ ++instIt;
1578+
1579+ FixedStorageSemanticsCall fixedStorageSemantics (inst);
1580+ if (!fixedStorageSemantics ||
1581+ fixedStorageSemantics.getKind () !=
1582+ FixedStorageSemanticsCallKind::CheckIndex) {
1583+ continue ;
1584+ }
1585+
1586+ if (!fixedStorageSemantics->hasSelfArgument ()) {
1587+ continue ;
1588+ }
1589+
1590+ auto selfValue = fixedStorageSemantics->getSelfArgument ();
1591+
1592+ if (!DT->dominates (selfValue->getParentBlock (), preheader)) {
1593+ LLVM_DEBUG (llvm::dbgs ()
1594+ << " " << *selfValue << " does not dominate preheader\n " );
1595+ continue ;
1596+ }
1597+
1598+ auto indexValue = fixedStorageSemantics->getArgument (0 );
1599+
1600+ // If the bounds check is loop invariant, hoist it.
1601+ if (blockAlwaysExecutes && dominates (DT, indexValue, preheader)) {
1602+ LLVM_DEBUG (llvm::dbgs () << " Invariant bounds check removed\n " );
1603+ changed = true ;
1604+ fixedStorageSemantics->moveBefore (preheader->getTerminator ());
1605+ continue ;
1606+ }
1607+
1608+ auto accessFunction =
1609+ AccessFunction::getLinearFunction (indexValue, indVars);
1610+ if (!accessFunction) {
1611+ LLVM_DEBUG (llvm::dbgs () << " not a linear function " << *inst);
1612+ continue ;
1613+ }
1614+
1615+ // If the loop iterates 0 through count, remove the bounds check.
1616+ if (accessFunction.isZeroToCount (selfValue)) {
1617+ LLVM_DEBUG (llvm::dbgs ()
1618+ << " Redundant Span/InlineArray bounds check removed\n " );
1619+ changed = true ;
1620+ fixedStorageSemantics->eraseFromParent ();
1621+ continue ;
1622+ }
1623+
1624+ // If the bounds check does not execute always, we cannot hoist it.
1625+ if (!blockAlwaysExecutes) {
1626+ LLVM_DEBUG (llvm::dbgs () << " Bounds check does not execute always\n " );
1627+ continue ;
1628+ }
1629+
1630+ LLVM_DEBUG (llvm::dbgs () << " Span/InlineArray bounds check hoisted\n " );
1631+ changed = true ;
1632+ auto firstValue = accessFunction.getFirstValue (preheader->getTerminator ());
1633+ auto newLowerBoundCheck =
1634+ fixedStorageSemantics->clone (preheader->getTerminator ());
1635+ newLowerBoundCheck->setOperand (1 , firstValue);
1636+
1637+ auto lastValue = accessFunction.getLastValue (preheader->getTerminator ());
1638+ auto newUpperBoundCheck =
1639+ fixedStorageSemantics->clone (preheader->getTerminator ());
1640+ newUpperBoundCheck->setOperand (1 , lastValue);
1641+ fixedStorageSemantics->eraseFromParent ();
1642+ }
1643+
1644+ // Traverse the children in the dominator tree.
1645+ for (auto child : *currentNode) {
1646+ changed |= hoistFixedStorageBoundsChecksInLoop (loop, child, indVars,
1647+ recursionDepth + 1 );
1648+ }
1649+
1650+ return changed;
1651+ }
1652+
1653+ bool BoundsCheckOpts::removeRedundantFixedStorageBoundsChecksInLoop (
1654+ SILLoop *loop, DominanceInfoNode *currentNode,
1655+ llvm::DenseSet<std::pair<SILValue, SILValue>> &dominatingSafeChecks,
1656+ int recursionDepth) {
1657+ auto *currentBlock = currentNode->getBlock ();
1658+ if (!loop->contains (currentBlock)) {
1659+ return false ;
1660+ }
1661+
1662+ if (recursionDepth >= maxRecursionDepth) {
1663+ return false ;
1664+ }
1665+
1666+ bool changed = false ;
1667+
1668+ // When we come back from the dominator tree recursion we need to remove
1669+ // checks that we have seen for the first time.
1670+ SmallVector<std::pair<SILValue, SILValue>, 8 > safeChecksToPop;
1671+
1672+ for (auto iter = currentBlock->begin (); iter != currentBlock->end ();) {
1673+ auto inst = &*iter;
1674+ ++iter;
1675+
1676+ FixedStorageSemanticsCall fixedStorageSemantics (inst);
1677+ if (!fixedStorageSemantics ||
1678+ fixedStorageSemantics.getKind () !=
1679+ FixedStorageSemanticsCallKind::CheckIndex) {
1680+ continue ;
1681+ }
1682+
1683+ if (!fixedStorageSemantics->hasSelfArgument ()) {
1684+ continue ;
1685+ }
1686+
1687+ auto selfValue = fixedStorageSemantics->getSelfArgument ();
1688+
1689+ if (!DT->dominates (selfValue->getParentBlock (), loop->getLoopPreheader ())) {
1690+ LLVM_DEBUG (llvm::dbgs ()
1691+ << " " << *selfValue << " does not dominate preheader\n " );
1692+ continue ;
1693+ }
1694+
1695+ auto indexValue = fixedStorageSemantics->getArgument (0 );
1696+ auto selfAndIndex = std::make_pair (selfValue, indexValue);
1697+ if (!dominatingSafeChecks.count (selfAndIndex)) {
1698+ LLVM_DEBUG (llvm::dbgs ()
1699+ << " first time: " << *inst << " with self: " << *selfValue);
1700+ dominatingSafeChecks.insert (selfAndIndex);
1701+ safeChecksToPop.push_back (selfAndIndex);
1702+ continue ;
1703+ }
1704+
1705+ LLVM_DEBUG (llvm::dbgs ()
1706+ << " Eliminated redundant Span/InlineArray bounds check" );
1707+ changed = true ;
1708+ fixedStorageSemantics->eraseFromParent ();
1709+ }
1710+
1711+ // Traverse the children in the dominator tree inside the loop.
1712+ for (auto child : *currentNode) {
1713+ changed |= removeRedundantFixedStorageBoundsChecksInLoop (
1714+ loop, child, dominatingSafeChecks, recursionDepth + 1 );
1715+ }
1716+
1717+ // Remove checks we have seen for the first time.
1718+ std::for_each (safeChecksToPop.begin (), safeChecksToPop.end (),
1719+ [&](std::pair<SILValue, SILValue> &value) {
1720+ dominatingSafeChecks.erase (value);
1721+ });
1722+
1723+ return changed;
1724+ }
1725+
14911726} // end anonymous namespace
14921727
14931728SILTransform *swift::createBoundsCheckOpts () { return new BoundsCheckOpts (); }
0 commit comments