Skip to content

Commit 3fbb52c

Browse files
authored
Merge pull request #40538 from gottesmm/release/5.6-move-function-upstreaming
[5.6] move function upstreaming
2 parents 3b4ace1 + 3766892 commit 3fbb52c

14 files changed

+932
-204
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: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
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+
if (auto *originalASI = dyn_cast<AllocStackInst>(operand)) {
116+
if (!originalASI->isLexical() ||
117+
!(originalASI->isVar() || originalASI->isLet()))
118+
return false;
119+
LLVM_DEBUG(llvm::dbgs() << "Found OriginalASI: " << *originalASI);
120+
} else {
121+
auto *fArg = dyn_cast<SILFunctionArgument>(operand);
122+
if (!fArg || !fArg->hasConvention(SILArgumentConvention::Indirect_Inout))
123+
return false;
124+
LLVM_DEBUG(llvm::dbgs() << "Found fArg: " << *fArg);
125+
}
126+
127+
// Make sure that there aren't any side-effect having instructions in
128+
// between our load/store.
129+
LLVM_DEBUG(llvm::dbgs() << "Checking for uses in between LI and SI.\n");
130+
auto range =
131+
llvm::make_range(std::next(li->getIterator()), si->getIterator());
132+
if (!llvm::none_of(range, [&](SILInstruction &iter) {
133+
if (!iter.mayHaveSideEffects()) {
134+
LLVM_DEBUG(llvm::dbgs() << "Found no side effect inst: " << iter);
135+
return false;
136+
}
137+
138+
if (auto *dvi = dyn_cast<DestroyAddrInst>(&iter)) {
139+
if (aa->isNoAlias(dvi->getOperand(), operand)) {
140+
// We are going to be extending the lifetime of our
141+
// underlying value, not shrinking it so we can ignore
142+
// destroy_addr on other non-aliasing values.
143+
LLVM_DEBUG(llvm::dbgs() << "Found no alias destroy_addr: " << iter);
144+
return false;
145+
}
146+
}
147+
148+
// Ignore end of scope markers with side-effects.
149+
if (isEndOfScopeMarker(&iter)) {
150+
LLVM_DEBUG(llvm::dbgs() << "Found end of scope marker: " << iter);
151+
return false;
152+
}
153+
154+
LLVM_DEBUG(llvm::dbgs()
155+
<< " Found side-effect inst... Bailing!: " << iter);
156+
return true;
157+
})) {
158+
return false;
159+
}
160+
161+
// Ok, we know our original lexical alloc_stack is not written to in between
162+
// the load/store. Move the mark_move_addr onto the lexical alloc_stack.
163+
LLVM_DEBUG(llvm::dbgs() << " Doing loadable var!\n");
164+
markMoveAddr->setSrc(operand);
165+
return true;
166+
}
167+
168+
/// Attempts to perform several small optimizations to setup both the address
169+
/// and object checkers. Returns true if we made a change to the IR.
170+
static bool tryConvertSimpleMoveFromAllocStackTemporary(
171+
MarkUnresolvedMoveAddrInst *markMoveAddr, AliasAnalysis *aa,
172+
InstructionDeleter &deleter) {
173+
LLVM_DEBUG(llvm::dbgs() << "Trying to fix up: " << *markMoveAddr);
174+
175+
// We need a non-lexical alloc_stack as our source.
176+
auto *asi = dyn_cast<AllocStackInst>(markMoveAddr->getSrc());
177+
if (!asi || asi->isLexical()) {
178+
LLVM_DEBUG(llvm::dbgs()
179+
<< " Source isnt an alloc_stack or is lexical... Bailing!\n");
180+
return false;
181+
}
182+
183+
DestroyAddrInst *dai = nullptr;
184+
CopyAddrInst *cai = nullptr;
185+
StoreInst *si = nullptr;
186+
if (!findInitAndDestroyForAllocation(asi, markMoveAddr, cai, dai, si))
187+
return false;
188+
189+
// If we did not find an (init | store) or destroy_addr, just bail.
190+
if (!(cai || si) || !dai) {
191+
LLVM_DEBUG(llvm::dbgs()
192+
<< " Did not find a single init! Bailing!\n");
193+
return false;
194+
}
195+
196+
assert(bool(cai) != bool(si));
197+
198+
// Otherwise, lets walk from cai/si to markMoveAddr and make sure there aren't
199+
// any side-effect having instructions in between them.
200+
//
201+
// NOTE: We know that cai must be before the markMoveAddr in the block since
202+
// otherwise we would be moving from uninitialized memory.
203+
SILInstruction *init = nullptr;
204+
if (cai)
205+
init = cai;
206+
else
207+
init = si;
208+
209+
auto range = llvm::make_range(std::next(init->getIterator()),
210+
markMoveAddr->getIterator());
211+
if (llvm::any_of(range, [&](SILInstruction &iter) {
212+
if (!iter.mayHaveSideEffects()) {
213+
return false;
214+
}
215+
216+
if (auto *dvi = dyn_cast<DestroyAddrInst>(&iter)) {
217+
if (aa->isNoAlias(dvi->getOperand(), asi)) {
218+
// We are going to be extending the lifetime of our
219+
// underlying value, not shrinking it so we can ignore
220+
// destroy_addr on other non-aliasing values.
221+
return false;
222+
}
223+
}
224+
225+
// Ignore end of scope markers with side-effects.
226+
if (isEndOfScopeMarker(&iter)) {
227+
return false;
228+
}
229+
230+
LLVM_DEBUG(llvm::dbgs()
231+
<< " Found side-effect inst... Bailing!: " << iter);
232+
return true;
233+
}))
234+
return false;
235+
236+
// Ok, we can perform our optimization! Change move_addr's source to be the
237+
// original copy_addr's src and add add uses of the stack location to an
238+
// instruction deleter. We will eliminate them later.
239+
if (cai) {
240+
LLVM_DEBUG(llvm::dbgs() << " Success! Performing optimization!\n");
241+
markMoveAddr->setSrc(cai->getSrc());
242+
return true;
243+
}
244+
245+
// If we have a store [init], see if our src is a load [copy] from an
246+
// alloc_stack that is lexical var or an inout argument. In this case, we want
247+
// to move our mark_unresolved_move_addr onto that lexical var. This pattern
248+
// occurs due to SILGen always loading loadable values from memory when
249+
// retrieving an RValue. Calling _move then since _move is generic forces the
250+
// value to be re-materialized into an alloc_stack. In this example
251+
// remembering that mark_unresolved_move_addr is a copy_addr [init], we try to
252+
// move the MUMA onto the original lexical alloc_stack.
253+
if (tryHandlingLoadableVarMovePattern(markMoveAddr, si, aa))
254+
return true;
255+
256+
// If we do not have a load [copy], transform this mark_resolved_move_addr
257+
// into a move_value [diagnostic] + store [init]. Predictable mem opts is
258+
// smart enough to handle this case and promote away loads from the
259+
// allocation. This runs before the value move checker runs.
260+
SILBuilderWithScope builder(si);
261+
auto *newValue = builder.createMoveValue(si->getLoc(), si->getSrc());
262+
newValue->setAllowsDiagnostics(true);
263+
si->setSrc(newValue);
264+
si->setDest(markMoveAddr->getDest());
265+
deleter.forceTrackAsDead(markMoveAddr);
266+
deleter.forceTrackAsDead(dai);
267+
268+
LLVM_DEBUG(llvm::dbgs() << " Success! Performing optimization!\n");
269+
return true;
270+
}
271+
272+
//===----------------------------------------------------------------------===//
273+
// Top Level Entrypoint
274+
//===----------------------------------------------------------------------===//
275+
276+
namespace {
277+
278+
class MoveFunctionCanonicalization : public SILFunctionTransform {
279+
void run() override {
280+
auto *fn = getFunction();
281+
282+
// Don't rerun diagnostics on deserialized functions.
283+
if (getFunction()->wasDeserializedCanonical())
284+
return;
285+
286+
bool madeChange = false;
287+
288+
assert(fn->getModule().getStage() == SILStage::Raw &&
289+
"Should only run on Raw SIL");
290+
291+
auto *aa = getAnalysis<AliasAnalysis>(fn);
292+
InstructionDeleter deleter;
293+
294+
for (auto &block : *fn) {
295+
for (auto ii = block.begin(), ie = block.end(); ii != ie;) {
296+
auto *inst = &*ii;
297+
++ii;
298+
299+
// See if we see a mark_unresolved_move_addr inst from a simple
300+
// temporary and move it onto the temporary's source. This ensures that
301+
// the mark_unresolved_move_addr is always on the operand regardless if
302+
// in the caller we materalized the address into a temporary.
303+
if (auto *markMoveAddr = dyn_cast<MarkUnresolvedMoveAddrInst>(inst)) {
304+
madeChange |= tryConvertSimpleMoveFromAllocStackTemporary(
305+
markMoveAddr, aa, deleter);
306+
continue;
307+
}
308+
}
309+
}
310+
311+
deleter.cleanupDeadInstructions();
312+
313+
if (madeChange) {
314+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
315+
}
316+
}
317+
};
318+
319+
} // anonymous namespace
320+
321+
SILTransform *swift::createMoveFunctionCanonicalization() {
322+
return new MoveFunctionCanonicalization();
323+
}

0 commit comments

Comments
 (0)