Skip to content

Commit 26277c8

Browse files
committed
Add support for bounds check optimization of Span and InlineArray
1 parent 4edaaa4 commit 26277c8

File tree

3 files changed

+429
-93
lines changed

3 files changed

+429
-93
lines changed

lib/SILOptimizer/LoopTransforms/BoundsCheckOpts.cpp

Lines changed: 252 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
9821025
public:
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

13431388
bool 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+
13801450
bool 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

14931728
SILTransform *swift::createBoundsCheckOpts() { return new BoundsCheckOpts(); }

0 commit comments

Comments
 (0)