Skip to content

Commit aeceaf7

Browse files
authored
Merge pull request #40478 from gottesmm/pr-cfbd8a7087ef5ca3ec578da8993a686ad7a50077
[move-function] Add support for loadable vars
2 parents 6a75261 + deb4541 commit aeceaf7

11 files changed

+453
-168
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4028,6 +4028,9 @@ class StoreInst
40284028
SILValue getSrc() const { return Operands[Src].get(); }
40294029
SILValue getDest() const { return Operands[Dest].get(); }
40304030

4031+
void setSrc(SILValue V) { Operands[Src].set(V); }
4032+
void setDest(SILValue V) { Operands[Dest].set(V); }
4033+
40314034
ArrayRef<Operand> getAllOperands() const { return Operands.asArray(); }
40324035
MutableArrayRef<Operand> getAllOperands() { return Operands.asArray(); }
40334036

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ PASS(LexicalLifetimeEliminator, "sil-lexical-lifetime-eliminator",
430430
PASS(MoveKillsCopyableAddressesChecker, "sil-move-kills-copyable-addresses-checker",
431431
"Pass that checks that any copyable (non-move only) address that is passed "
432432
"to _move do not have any uses later than the _move")
433+
PASS(MoveFunctionCanonicalization, "sil-move-function-canon",
434+
"Pass that canonicalizes certain parts of the IR before we perform move "
435+
"function checking.")
433436
PASS(PruneVTables, "prune-vtables",
434437
"Mark class methods that do not require vtable dispatch")
435438
PASS_RANGE(AllPasses, AADumper, PruneVTables)

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ target_sources(swiftSILOptimizer PRIVATE
1818
LexicalLifetimeEliminator.cpp
1919
LowerHopToActor.cpp
2020
MandatoryInlining.cpp
21-
MoveOnlyChecker.cpp
22-
MoveKillsCopyableValuesChecker.cpp
21+
MoveFunctionCanonicalization.cpp
2322
MoveKillsCopyableAddressesChecker.cpp
23+
MoveKillsCopyableValuesChecker.cpp
24+
MoveOnlyChecker.cpp
2425
NestedSemanticFunctionCheck.cpp
2526
OptimizeHopToExecutor.cpp
2627
PerformanceDiagnostics.cpp
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
//===--- MoveFunctionCanonicalization.cpp ---------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#define DEBUG_TYPE "sil-move-function-canonicalization"
14+
15+
#include "swift/AST/DiagnosticsSIL.h"
16+
#include "swift/Basic/Defer.h"
17+
#include "swift/Basic/FrozenMultiMap.h"
18+
#include "swift/SIL/BasicBlockBits.h"
19+
#include "swift/SIL/BasicBlockDatastructures.h"
20+
#include "swift/SIL/Consumption.h"
21+
#include "swift/SIL/DebugUtils.h"
22+
#include "swift/SIL/InstructionUtils.h"
23+
#include "swift/SIL/MemAccessUtils.h"
24+
#include "swift/SIL/OwnershipUtils.h"
25+
#include "swift/SIL/SILArgument.h"
26+
#include "swift/SIL/SILBuilder.h"
27+
#include "swift/SIL/SILFunction.h"
28+
#include "swift/SIL/SILInstruction.h"
29+
#include "swift/SIL/SILUndef.h"
30+
#include "swift/SILOptimizer/Analysis/ClosureScope.h"
31+
#include "swift/SILOptimizer/PassManager/Transforms.h"
32+
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
33+
#include "llvm/ADT/PointerEmbeddedInt.h"
34+
#include "llvm/ADT/PointerSumType.h"
35+
36+
using namespace swift;
37+
38+
//===----------------------------------------------------------------------===//
39+
// Utility
40+
//===----------------------------------------------------------------------===//
41+
42+
static bool findInitAndDestroyForAllocation(
43+
AllocStackInst *asi, MarkUnresolvedMoveAddrInst *markMoveAddr,
44+
CopyAddrInst *&cai, DestroyAddrInst *&dai, StoreInst *&si) {
45+
for (auto *use : asi->getUses()) {
46+
auto *user = use->getUser();
47+
LLVM_DEBUG(llvm::dbgs() << " Visiting User: " << *user);
48+
49+
// If we find our own instruction or a dealloc stack, just skip.
50+
if (user == markMoveAddr || isa<DeallocStackInst>(user)) {
51+
LLVM_DEBUG(
52+
llvm::dbgs()
53+
<< " Found our original inst or a dealloc stack... Ok!\n");
54+
continue;
55+
}
56+
57+
if (auto *destroyAddrInst = dyn_cast<DestroyAddrInst>(user)) {
58+
if (dai)
59+
return false;
60+
dai = destroyAddrInst;
61+
continue;
62+
}
63+
64+
if (auto *newCAI = dyn_cast<CopyAddrInst>(user)) {
65+
LLVM_DEBUG(llvm::dbgs()
66+
<< " Found copy_addr... checking if legal...\n");
67+
// We require that our copy_addr be an init into our temp and in the same
68+
// block as markMoveAddr.
69+
if (newCAI->getDest() == asi && bool(newCAI->isInitializationOfDest()) &&
70+
!bool(newCAI->isTakeOfSrc()) &&
71+
newCAI->getParent() == markMoveAddr->getParent()) {
72+
if (cai || si)
73+
return false;
74+
cai = newCAI;
75+
continue;
76+
}
77+
}
78+
79+
if (auto *newSI = dyn_cast<StoreInst>(user)) {
80+
LLVM_DEBUG(llvm::dbgs()
81+
<< " Found store... checking if legal...\n");
82+
// We require that our copy_addr be an init into our temp and in the same
83+
// block as markMoveAddr.
84+
if (newSI->getDest() == asi &&
85+
newSI->getOwnershipQualifier() == StoreOwnershipQualifier::Init &&
86+
newSI->getParent() == markMoveAddr->getParent()) {
87+
if (cai || si)
88+
return false;
89+
si = newSI;
90+
continue;
91+
}
92+
}
93+
94+
// If we do not find an instruction that we know about, return we can't
95+
// optimize.
96+
LLVM_DEBUG(
97+
llvm::dbgs()
98+
<< " Found instruction we did not understand! Bailing!\n");
99+
return false;
100+
}
101+
102+
return true;
103+
}
104+
105+
static bool
106+
tryHandlingLoadableVarMovePattern(MarkUnresolvedMoveAddrInst *markMoveAddr,
107+
StoreInst *si, AliasAnalysis *aa) {
108+
auto *li = dyn_cast<LoadInst>(si->getSrc());
109+
if (!li || li->getOwnershipQualifier() != LoadOwnershipQualifier::Copy ||
110+
li->getParent() != si->getParent())
111+
return false;
112+
113+
LLVM_DEBUG(llvm::dbgs() << "Found LI: " << *li);
114+
SILValue operand = stripAccessMarkers(li->getOperand());
115+
auto *originalASI = dyn_cast<AllocStackInst>(operand);
116+
if (!originalASI || !originalASI->isLexical() || !originalASI->isVar())
117+
return false;
118+
119+
LLVM_DEBUG(llvm::dbgs() << "Found OriginalASI: " << *originalASI);
120+
// Make sure that there aren't any side-effect having instructions in
121+
// between our load/store.
122+
LLVM_DEBUG(llvm::dbgs() << "Checking for uses in between LI and SI.\n");
123+
auto range =
124+
llvm::make_range(std::next(li->getIterator()), si->getIterator());
125+
if (!llvm::none_of(range, [&](SILInstruction &iter) {
126+
if (!iter.mayHaveSideEffects()) {
127+
LLVM_DEBUG(llvm::dbgs() << "Found no side effect inst: " << iter);
128+
return false;
129+
}
130+
131+
if (auto *dvi = dyn_cast<DestroyAddrInst>(&iter)) {
132+
if (aa->isNoAlias(dvi->getOperand(), originalASI)) {
133+
// We are going to be extending the lifetime of our
134+
// underlying value, not shrinking it so we can ignore
135+
// destroy_addr on other non-aliasing values.
136+
LLVM_DEBUG(llvm::dbgs() << "Found no alias destroy_addr: " << iter);
137+
return false;
138+
}
139+
}
140+
141+
// Ignore end of scope markers with side-effects.
142+
if (isEndOfScopeMarker(&iter)) {
143+
LLVM_DEBUG(llvm::dbgs() << "Found end of scope marker: " << iter);
144+
return false;
145+
}
146+
147+
LLVM_DEBUG(llvm::dbgs()
148+
<< " Found side-effect inst... Bailing!: " << iter);
149+
return true;
150+
})) {
151+
return false;
152+
}
153+
154+
// Ok, we know our original lexical alloc_stack is not written to in between
155+
// the load/store. Move the mark_move_addr onto the lexical alloc_stack.
156+
LLVM_DEBUG(llvm::dbgs() << " Doing loadable var!\n");
157+
markMoveAddr->setSrc(originalASI);
158+
return true;
159+
}
160+
161+
/// Attempts to perform several small optimizations to setup both the address
162+
/// and object checkers. Returns true if we made a change to the IR.
163+
static bool tryConvertSimpleMoveFromAllocStackTemporary(
164+
MarkUnresolvedMoveAddrInst *markMoveAddr, AliasAnalysis *aa,
165+
InstructionDeleter &deleter) {
166+
LLVM_DEBUG(llvm::dbgs() << "Trying to fix up: " << *markMoveAddr);
167+
168+
// We need a non-lexical alloc_stack as our source.
169+
auto *asi = dyn_cast<AllocStackInst>(markMoveAddr->getSrc());
170+
if (!asi || asi->isLexical()) {
171+
LLVM_DEBUG(llvm::dbgs()
172+
<< " Source isnt an alloc_stack or is lexical... Bailing!\n");
173+
return false;
174+
}
175+
176+
DestroyAddrInst *dai = nullptr;
177+
CopyAddrInst *cai = nullptr;
178+
StoreInst *si = nullptr;
179+
if (!findInitAndDestroyForAllocation(asi, markMoveAddr, cai, dai, si))
180+
return false;
181+
182+
// If we did not find an (init | store) or destroy_addr, just bail.
183+
if (!(cai || si) || !dai) {
184+
LLVM_DEBUG(llvm::dbgs()
185+
<< " Did not find a single init! Bailing!\n");
186+
return false;
187+
}
188+
189+
assert(bool(cai) != bool(si));
190+
191+
// Otherwise, lets walk from cai/si to markMoveAddr and make sure there aren't
192+
// any side-effect having instructions in between them.
193+
//
194+
// NOTE: We know that cai must be before the markMoveAddr in the block since
195+
// otherwise we would be moving from uninitialized memory.
196+
SILInstruction *init = nullptr;
197+
if (cai)
198+
init = cai;
199+
else
200+
init = si;
201+
202+
auto range = llvm::make_range(std::next(init->getIterator()),
203+
markMoveAddr->getIterator());
204+
if (llvm::any_of(range, [&](SILInstruction &iter) {
205+
if (!iter.mayHaveSideEffects()) {
206+
return false;
207+
}
208+
209+
if (auto *dvi = dyn_cast<DestroyAddrInst>(&iter)) {
210+
if (aa->isNoAlias(dvi->getOperand(), asi)) {
211+
// We are going to be extending the lifetime of our
212+
// underlying value, not shrinking it so we can ignore
213+
// destroy_addr on other non-aliasing values.
214+
return false;
215+
}
216+
}
217+
218+
// Ignore end of scope markers with side-effects.
219+
if (isEndOfScopeMarker(&iter)) {
220+
return false;
221+
}
222+
223+
LLVM_DEBUG(llvm::dbgs()
224+
<< " Found side-effect inst... Bailing!: " << iter);
225+
return true;
226+
}))
227+
return false;
228+
229+
// Ok, we can perform our optimization! Change move_addr's source to be the
230+
// original copy_addr's src and add add uses of the stack location to an
231+
// instruction deleter. We will eliminate them later.
232+
if (cai) {
233+
LLVM_DEBUG(llvm::dbgs() << " Success! Performing optimization!\n");
234+
markMoveAddr->setSrc(cai->getSrc());
235+
return true;
236+
}
237+
238+
// If we have a store [init], see if our src is a load [copy] from an
239+
// alloc_stack that is lexical var. In this case, we want to move our
240+
// mark_unresolved_move_addr onto that lexical var. This pattern occurs due to
241+
// SILGen always loading loadable values from memory when retrieving an
242+
// RValue. Calling _move then since _move is generic forces the value to be
243+
// re-materialized into an alloc_stack. In this example remembering that
244+
// mark_unresolved_move_addr is a copy_addr [init], we try to move the MUMA
245+
// onto the original lexical alloc_stack.
246+
if (tryHandlingLoadableVarMovePattern(markMoveAddr, si, aa))
247+
return true;
248+
249+
// If we do not have a load [copy], transform this mark_resolved_move_addr
250+
// into a move_value [diagnostic] + store [init]. Predictable mem opts is
251+
// smart enough to handle this case and promote away loads from the
252+
// allocation. This runs before the value move checker runs.
253+
SILBuilderWithScope builder(si);
254+
auto *newValue = builder.createMoveValue(si->getLoc(), si->getSrc());
255+
newValue->setAllowsDiagnostics(true);
256+
si->setSrc(newValue);
257+
si->setDest(markMoveAddr->getDest());
258+
deleter.forceTrackAsDead(markMoveAddr);
259+
deleter.forceTrackAsDead(dai);
260+
261+
LLVM_DEBUG(llvm::dbgs() << " Success! Performing optimization!\n");
262+
return true;
263+
}
264+
265+
//===----------------------------------------------------------------------===//
266+
// Top Level Entrypoint
267+
//===----------------------------------------------------------------------===//
268+
269+
namespace {
270+
271+
class MoveFunctionCanonicalization : public SILFunctionTransform {
272+
void run() override {
273+
auto *fn = getFunction();
274+
275+
// Don't rerun diagnostics on deserialized functions.
276+
if (getFunction()->wasDeserializedCanonical())
277+
return;
278+
279+
bool madeChange = false;
280+
281+
assert(fn->getModule().getStage() == SILStage::Raw &&
282+
"Should only run on Raw SIL");
283+
284+
auto *aa = getAnalysis<AliasAnalysis>(fn);
285+
InstructionDeleter deleter;
286+
287+
for (auto &block : *fn) {
288+
for (auto ii = block.begin(), ie = block.end(); ii != ie;) {
289+
auto *inst = &*ii;
290+
++ii;
291+
292+
// See if we see a mark_unresolved_move_addr inst from a simple
293+
// temporary and move it onto the temporary's source. This ensures that
294+
// the mark_unresolved_move_addr is always on the operand regardless if
295+
// in the caller we materalized the address into a temporary.
296+
if (auto *markMoveAddr = dyn_cast<MarkUnresolvedMoveAddrInst>(inst)) {
297+
madeChange |= tryConvertSimpleMoveFromAllocStackTemporary(
298+
markMoveAddr, aa, deleter);
299+
continue;
300+
}
301+
}
302+
}
303+
304+
deleter.cleanupDeadInstructions();
305+
306+
if (madeChange) {
307+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
308+
}
309+
}
310+
};
311+
312+
} // anonymous namespace
313+
314+
SILTransform *swift::createMoveFunctionCanonicalization() {
315+
return new MoveFunctionCanonicalization();
316+
}

0 commit comments

Comments
 (0)