Skip to content

Commit ac1f956

Browse files
committed
Split predictable mem opts into two different passes, one that runs before diagnostics and one that runs after diagnostics.
I discovered while updating PMO for ownership that for ~5 years there has been a bug where we were treating copy_addr of trivial values like an "Assign" (in PMO terminology) of a non-trivial value and thus stopping allocation elimination. When I fixed this I discovered that this caused us to no longer emit diagnostics in a predictable way. Specifically, consider the following swift snippet: var _: UInt = (-1) >> 0 Today, we emit a diagnostic that -1 can not be put into a UInt. This occurs since even though the underlying allocation is only stored into, the copy_addr assign keeps it alive, causing the diagnostics pass to see the conversion. With my fix though, we see that we are only storing into the allocation, causing the allocation to be eliminated before the constant propagation diagnostic pass runs, causing the diagnostic to no longer be emitted. We should truly not be performing this type of DCE before we emit such diagnostics. So in this commit, I split the pass into two parts: 1. A load promotion pass that performs the SSA formation needed for SSA based diagnostics to actually work. 2. An allocation elimination passes that run /after/ SSA based diagnostics. This should be NFC since the constant propagation SSA based diagnostics do not create memory operations so the output should be the same.
1 parent 611b0c9 commit ac1f956

File tree

4 files changed

+126
-50
lines changed

4 files changed

+126
-50
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,10 @@ PASS(PerfInliner, "inline",
232232
"Performance Function Inlining")
233233
PASS(PerformanceConstantPropagation, "performance-constant-propagation",
234234
"Constant Propagation for Performance without Diagnostics")
235-
PASS(PredictableMemoryOptimizations, "predictable-memopt",
236-
"Predictable Memory Optimization for Diagnostics")
235+
PASS(PredictableMemoryAccessOptimizations, "predictable-memaccess-opts",
236+
"Predictable Memory Access Optimizations for Diagnostics")
237+
PASS(PredictableDeadAllocationElimination, "predictable-deadalloc-elim",
238+
"Eliminate dead temporary allocations after diagnostics")
237239
PASS(ReleaseDevirtualizer, "release-devirtualizer",
238240
"SIL release Devirtualization")
239241
PASS(RetainSinking, "retain-sinking",

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 112 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,16 +1011,15 @@ class AllocOptimize {
10111011
AllocOptimize(AllocationInst *TheMemory, SmallVectorImpl<PMOMemoryUse> &Uses,
10121012
SmallVectorImpl<SILInstruction *> &Releases);
10131013

1014-
bool doIt();
1014+
bool optimizeMemoryAccesses();
1015+
bool tryToRemoveDeadAllocation();
10151016

10161017
private:
10171018
bool promoteLoad(SILInstruction *Inst);
10181019
void promoteDestroyAddr(DestroyAddrInst *DAI,
10191020
MutableArrayRef<AvailableValue> Values);
10201021
bool canPromoteDestroyAddr(DestroyAddrInst *DAI,
10211022
SmallVectorImpl<AvailableValue> &AvailableValues);
1022-
1023-
bool tryToRemoveDeadAllocation();
10241023
};
10251024

10261025
} // end anonymous namespace
@@ -1376,79 +1375,135 @@ bool AllocOptimize::tryToRemoveDeadAllocation() {
13761375
return true;
13771376
}
13781377

1379-
/// doIt - returns true on error.
1380-
bool AllocOptimize::doIt() {
1381-
bool Changed = false;
1382-
1383-
// Don't try to optimize incomplete aggregates.
1384-
if (MemoryType.aggregateHasUnreferenceableStorage())
1385-
return false;
1378+
bool AllocOptimize::optimizeMemoryAccesses() {
1379+
bool changed = false;
13861380

13871381
// If we've successfully checked all of the definitive initialization
13881382
// requirements, try to promote loads. This can explode copy_addrs, so the
13891383
// use list may change size.
13901384
for (unsigned i = 0; i != Uses.size(); ++i) {
1391-
auto &Use = Uses[i];
1385+
auto &use = Uses[i];
13921386
// Ignore entries for instructions that got expanded along the way.
1393-
if (Use.Inst && Use.Kind == PMOUseKind::Load) {
1394-
if (promoteLoad(Use.Inst)) {
1387+
if (use.Inst && use.Kind == PMOUseKind::Load) {
1388+
if (promoteLoad(use.Inst)) {
13951389
Uses[i].Inst = nullptr; // remove entry if load got deleted.
1396-
Changed = true;
1390+
changed = true;
13971391
}
13981392
}
13991393
}
1400-
1401-
// If this is an allocation, try to remove it completely.
1402-
Changed |= tryToRemoveDeadAllocation();
14031394

1404-
return Changed;
1395+
return changed;
1396+
}
1397+
1398+
//===----------------------------------------------------------------------===//
1399+
// Top Level Entrypoints
1400+
//===----------------------------------------------------------------------===//
1401+
1402+
static AllocationInst *getOptimizableAllocation(SILInstruction *i) {
1403+
if (!isa<AllocBoxInst>(i) && !isa<AllocStackInst>(i)) {
1404+
return nullptr;
1405+
}
1406+
1407+
auto *alloc = cast<AllocationInst>(i);
1408+
1409+
// If our aggregate has unreferencable storage, we can't optimize. Return
1410+
// nullptr.
1411+
if (getMemoryType(alloc).aggregateHasUnreferenceableStorage())
1412+
return nullptr;
1413+
1414+
// Otherwise we are good to go. Lets try to optimize this memory!
1415+
return alloc;
14051416
}
14061417

1407-
static bool optimizeMemoryAllocations(SILFunction &Fn) {
1408-
bool Changed = false;
1409-
for (auto &BB : Fn) {
1410-
auto I = BB.begin(), E = BB.end();
1411-
while (I != E) {
1412-
SILInstruction *Inst = &*I;
1413-
if (!isa<AllocBoxInst>(Inst) && !isa<AllocStackInst>(Inst)) {
1414-
++I;
1418+
static bool optimizeMemoryAccesses(SILFunction &fn) {
1419+
bool changed = false;
1420+
for (auto &bb : fn) {
1421+
auto i = bb.begin(), e = bb.end();
1422+
while (i != e) {
1423+
// First see if i is an allocation that we can optimize. If not, skip it.
1424+
AllocationInst *alloc = getOptimizableAllocation(&*i);
1425+
if (!alloc) {
1426+
++i;
14151427
continue;
14161428
}
1417-
auto Alloc = cast<AllocationInst>(Inst);
14181429

1419-
LLVM_DEBUG(llvm::dbgs() << "*** PMO Optimize looking at: " << *Alloc
1420-
<< "\n");
1421-
PMOMemoryObjectInfo MemInfo(Alloc);
1430+
LLVM_DEBUG(llvm::dbgs() << "*** PMO Optimize Memory Accesses looking at: "
1431+
<< *alloc << "\n");
1432+
PMOMemoryObjectInfo memInfo(alloc);
14221433

14231434
// Set up the datastructure used to collect the uses of the allocation.
1424-
SmallVector<PMOMemoryUse, 16> Uses;
1425-
SmallVector<SILInstruction*, 4> Releases;
1435+
SmallVector<PMOMemoryUse, 16> uses;
1436+
SmallVector<SILInstruction *, 4> destroys;
14261437

14271438
// Walk the use list of the pointer, collecting them. If we are not able
14281439
// to optimize, skip this value. *NOTE* We may still scalarize values
14291440
// inside the value.
1430-
if (!collectPMOElementUsesFrom(MemInfo, Uses, Releases)) {
1431-
++I;
1441+
if (!collectPMOElementUsesFrom(memInfo, uses, destroys)) {
1442+
++i;
14321443
continue;
14331444
}
14341445

1435-
Changed |= AllocOptimize(Alloc, Uses, Releases).doIt();
1436-
1437-
// Carefully move iterator to avoid invalidation problems.
1438-
++I;
1439-
if (Alloc->use_empty()) {
1440-
Alloc->eraseFromParent();
1446+
AllocOptimize allocOptimize(alloc, uses, destroys);
1447+
changed |= allocOptimize.optimizeMemoryAccesses();
1448+
1449+
// Move onto the next instruction. We know this is safe since we do not
1450+
// eliminate allocations here.
1451+
++i;
1452+
}
1453+
}
1454+
1455+
return changed;
1456+
}
1457+
1458+
static bool eliminateDeadAllocations(SILFunction &fn) {
1459+
bool changed = false;
1460+
for (auto &bb : fn) {
1461+
auto i = bb.begin(), e = bb.end();
1462+
while (i != e) {
1463+
// First see if i is an allocation that we can optimize. If not, skip it.
1464+
AllocationInst *alloc = getOptimizableAllocation(&*i);
1465+
if (!alloc) {
1466+
++i;
1467+
continue;
1468+
}
1469+
1470+
LLVM_DEBUG(llvm::dbgs()
1471+
<< "*** PMO Dead Allocation Elimination looking at: " << *alloc
1472+
<< "\n");
1473+
PMOMemoryObjectInfo memInfo(alloc);
1474+
1475+
// Set up the datastructure used to collect the uses of the allocation.
1476+
SmallVector<PMOMemoryUse, 16> uses;
1477+
SmallVector<SILInstruction *, 4> destroys;
1478+
1479+
// Walk the use list of the pointer, collecting them. If we are not able
1480+
// to optimize, skip this value. *NOTE* We may still scalarize values
1481+
// inside the value.
1482+
if (!collectPMOElementUsesFrom(memInfo, uses, destroys)) {
1483+
++i;
1484+
continue;
1485+
}
1486+
1487+
AllocOptimize allocOptimize(alloc, uses, destroys);
1488+
changed |= allocOptimize.tryToRemoveDeadAllocation();
1489+
1490+
// Move onto the next instruction. We know this is safe since we do not
1491+
// eliminate allocations here.
1492+
++i;
1493+
if (alloc->use_empty()) {
1494+
alloc->eraseFromParent();
14411495
++NumAllocRemoved;
1442-
Changed = true;
1496+
changed = true;
14431497
}
14441498
}
14451499
}
1446-
return Changed;
1500+
1501+
return changed;
14471502
}
14481503

14491504
namespace {
14501505

1451-
class PredictableMemoryOptimizations : public SILFunctionTransform {
1506+
class PredictableMemoryAccessOptimizations : public SILFunctionTransform {
14521507
/// The entry point to the transformation.
14531508
///
14541509
/// FIXME: This pass should not need to rerun on deserialized
@@ -1457,14 +1512,25 @@ class PredictableMemoryOptimizations : public SILFunctionTransform {
14571512
/// either indicates that this pass missing some opportunities the first time,
14581513
/// or has a pass order dependency on other early passes.
14591514
void run() override {
1460-
if (optimizeMemoryAllocations(*getFunction()))
1515+
// TODO: Can we invalidate here just instructions?
1516+
if (optimizeMemoryAccesses(*getFunction()))
1517+
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
1518+
}
1519+
};
1520+
1521+
class PredictableDeadAllocationElimination : public SILFunctionTransform {
1522+
void run() override {
1523+
if (eliminateDeadAllocations(*getFunction()))
14611524
invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody);
14621525
}
14631526
};
14641527

14651528
} // end anonymous namespace
14661529

1530+
SILTransform *swift::createPredictableMemoryAccessOptimizations() {
1531+
return new PredictableMemoryAccessOptimizations();
1532+
}
14671533

1468-
SILTransform *swift::createPredictableMemoryOptimizations() {
1469-
return new PredictableMemoryOptimizations();
1534+
SILTransform *swift::createPredictableDeadAllocationElimination() {
1535+
return new PredictableDeadAllocationElimination();
14701536
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,20 @@ static void addMandatoryOptPipeline(SILPassPipelinePlan &P,
106106
P.addOwnershipModelEliminator();
107107
P.addMandatoryInlining();
108108
P.addMandatorySILLinker();
109-
P.addPredictableMemoryOptimizations();
109+
110+
// Promote loads as necessary to ensure we have enough SSA formation to emit
111+
// SSA based diagnostics.
112+
P.addPredictableMemoryAccessOptimizations();
110113

111114
// Diagnostic ConstantPropagation must be rerun on deserialized functions
112115
// because it is sensitive to the assert configuration.
113116
// Consequently, certain optimization passes beyond this point will also rerun.
114117
P.addDiagnosticConstantPropagation();
118+
119+
// Now that we have emitted constant propagation diagnostics, try to eliminate
120+
// dead allocations.
121+
P.addPredictableDeadAllocationElimination();
122+
115123
P.addGuaranteedARCOpts();
116124
P.addDiagnoseUnreachable();
117125
P.addDiagnoseInfiniteRecursion();

test/SILOptimizer/predictable_memopt.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-sil-opt -enable-sil-verify-all %s -predictable-memopt | %FileCheck %s
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -predictable-memaccess-opts -predictable-deadalloc-elim | %FileCheck %s
22

33
import Builtin
44
import Swift

0 commit comments

Comments
 (0)