Skip to content

Commit 2f9d67d

Browse files
committed
[move-only] Add a new pass called MoveOnlyTypeEliminator that runs after move only checking.
This pass lowers moveonly-ness from the IR after we have finished move only checking. The transform can be configured in two ways: such that it only handles trivial types and such that it does trivial and non-trivial types. For ease of use, I created two top level transforms (TrivialMoveOnlyTypeEliminator and MoveOnlyTypeElimintor) that invoke the two behaviors by configuring the underlying transform slightly differently. For now, I am running first the trivial-only and then the all of the above lowering. The trivial only pass will remain at this part of the pipeline forever, but with time we are going to move the lower everything pass later into the pipeline once I have audited the optimizer pipeline to just not perform any work on move only types. That being said, currently we do not have this guarantee and this patch at least improves the world and lets us codegen no implicit copy code again.
1 parent 0d11e8e commit 2f9d67d

File tree

7 files changed

+1302
-0
lines changed

7 files changed

+1302
-0
lines changed

include/swift/SIL/SILValue.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,19 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> {
558558

559559
bool isLexical() const;
560560

561+
/// Unsafely eliminate moveonly from this value's type. Returns true if the
562+
/// value's underlying type was move only and thus was changed. Returns false
563+
/// otherwise.
564+
///
565+
/// NOTE: Please do not use this directly! It is only meant to be used by the
566+
/// optimizer pass: SILMoveOnlyTypeEliminator.
567+
bool unsafelyEliminateMoveOnlyWrapper() {
568+
if (!Type.isMoveOnlyWrapped())
569+
return false;
570+
Type = Type.removingMoveOnlyWrapper();
571+
return true;
572+
}
573+
561574
static bool classof(SILNodePointer node) {
562575
return node->getKind() >= SILNodeKind::First_ValueBase &&
563576
node->getKind() <= SILNodeKind::Last_ValueBase;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ PASS(MoveOnlyChecker, "sil-move-only-checker",
438438
PASS(MoveKillsCopyableValuesChecker, "sil-move-kills-copyable-values-checker",
439439
"Pass that checks that any copyable (non-move only) value that is passed "
440440
"to _move do not have any uses later than the _move")
441+
PASS(TrivialMoveOnlyTypeEliminator, "sil-trivial-move-only-type-eliminator",
442+
"Pass that rewrites SIL to remove move only types from values of trivial type")
443+
PASS(MoveOnlyTypeEliminator, "sil-move-only-type-eliminator",
444+
"Pass that rewrites SIL to remove move only types from all values")
441445
PASS(LexicalLifetimeEliminator, "sil-lexical-lifetime-eliminator",
442446
"Pass that removes lexical lifetime markers from borrows and alloc stack")
443447
PASS(MoveKillsCopyableAddressesChecker, "sil-move-kills-copyable-addresses-checker",

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ target_sources(swiftSILOptimizer PRIVATE
3636
YieldOnceCheck.cpp
3737
MandatoryCombine.cpp
3838
OSLogOptimization.cpp
39+
MoveOnlyTypeEliminator.cpp
3940
OwnershipModelEliminator.cpp)
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
//===--- SILMoveOnlyTypeEliminator.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+
/// This file contains an optimizer pass that lowers away move only types from
14+
/// SIL. It can run on all types or just trivial types. It works by Walking all
15+
/// values in the IR and unsafely converting their type to be without move
16+
/// only. If a change is made, we add the defining instruction to a set list for
17+
/// post-processing. Once we have updated all types in the function, we revisit
18+
/// the instructions that we touched and update/delete them as appropriate.
19+
///
20+
//===----------------------------------------------------------------------===//
21+
22+
#define DEBUG_TYPE "sil-move-only-type-eliminator"
23+
24+
#include "swift/AST/DiagnosticsSIL.h"
25+
#include "swift/Basic/Defer.h"
26+
#include "swift/SIL/BasicBlockBits.h"
27+
#include "swift/SIL/DebugUtils.h"
28+
#include "swift/SIL/InstructionUtils.h"
29+
#include "swift/SIL/SILArgument.h"
30+
#include "swift/SIL/SILBuilder.h"
31+
#include "swift/SIL/SILFunction.h"
32+
#include "swift/SIL/SILInstruction.h"
33+
#include "swift/SIL/SILUndef.h"
34+
#include "swift/SIL/SILVisitor.h"
35+
#include "swift/SILOptimizer/Analysis/ClosureScope.h"
36+
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
37+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
38+
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
39+
#include "swift/SILOptimizer/PassManager/Transforms.h"
40+
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
41+
42+
using namespace swift;
43+
44+
//===----------------------------------------------------------------------===//
45+
// Visitor
46+
//===----------------------------------------------------------------------===//
47+
48+
namespace {
49+
50+
struct SILMoveOnlyTypeEliminatorVisitor
51+
: SILInstructionVisitor<SILMoveOnlyTypeEliminatorVisitor, bool> {
52+
const SmallSetVector<SILArgument *, 8> &touchedArgs;
53+
54+
SILMoveOnlyTypeEliminatorVisitor(
55+
const SmallSetVector<SILArgument *, 8> &touchedArgs)
56+
: touchedArgs(touchedArgs) {}
57+
58+
bool visitSILInstruction(SILInstruction *inst) {
59+
llvm::errs() << "Unhandled SIL Instruction: " << *inst;
60+
llvm_unreachable("error");
61+
}
62+
63+
bool eraseFromParent(SILInstruction *i) {
64+
LLVM_DEBUG(llvm::dbgs() << "Erasing Inst: " << *i);
65+
i->eraseFromParent();
66+
return true;
67+
}
68+
69+
bool visitLoadInst(LoadInst *li) {
70+
if (!li->getType().isTrivial(*li->getFunction()))
71+
return false;
72+
li->setOwnershipQualifier(LoadOwnershipQualifier::Trivial);
73+
return true;
74+
}
75+
76+
bool visitStoreInst(StoreInst *si) {
77+
if (!si->getSrc()->getType().isTrivial(*si->getFunction()))
78+
return false;
79+
si->setOwnershipQualifier(StoreOwnershipQualifier::Trivial);
80+
return true;
81+
}
82+
83+
bool visitStoreBorrowInst(StoreBorrowInst *si) {
84+
if (!si->getSrc()->getType().isTrivial(*si->getFunction()))
85+
return false;
86+
SILBuilderWithScope b(si);
87+
b.emitStoreValueOperation(si->getLoc(), si->getSrc(), si->getDest(),
88+
StoreOwnershipQualifier::Trivial);
89+
return eraseFromParent(si);
90+
}
91+
92+
bool visitLoadBorrowInst(LoadBorrowInst *li) {
93+
if (!li->getType().isTrivial(*li->getFunction()))
94+
return false;
95+
SILBuilderWithScope b(li);
96+
auto newVal = b.emitLoadValueOperation(li->getLoc(), li->getOperand(),
97+
LoadOwnershipQualifier::Trivial);
98+
li->replaceAllUsesWith(newVal);
99+
return eraseFromParent(li);
100+
}
101+
102+
#define RAUW_IF_TRIVIAL_RESULT(CLS) \
103+
bool visit##CLS##Inst(CLS##Inst *inst) { \
104+
if (!inst->getType().isTrivial(*inst->getFunction())) { \
105+
return false; \
106+
} \
107+
inst->replaceAllUsesWith(inst->getOperand()); \
108+
return eraseFromParent(inst); \
109+
}
110+
RAUW_IF_TRIVIAL_RESULT(CopyValue)
111+
RAUW_IF_TRIVIAL_RESULT(ExplicitCopyValue)
112+
RAUW_IF_TRIVIAL_RESULT(BeginBorrow)
113+
#undef RAUW_IF_TRIVIAL_RESULT
114+
115+
#define RAUW_ALWAYS(CLS) \
116+
bool visit##CLS##Inst(CLS##Inst *inst) { \
117+
inst->replaceAllUsesWith(inst->getOperand()); \
118+
return eraseFromParent(inst); \
119+
}
120+
RAUW_ALWAYS(MoveOnlyWrapperToCopyableValue)
121+
RAUW_ALWAYS(CopyableToMoveOnlyWrapperValue)
122+
#undef RAUW_ALWAYS
123+
124+
#define DELETE_IF_TRIVIAL_OP(CLS) \
125+
bool visit##CLS##Inst(CLS##Inst *inst) { \
126+
if (!inst->getOperand()->getType().isTrivial(*inst->getFunction())) { \
127+
return false; \
128+
} \
129+
return eraseFromParent(inst); \
130+
}
131+
DELETE_IF_TRIVIAL_OP(DestroyValue)
132+
DELETE_IF_TRIVIAL_OP(EndBorrow)
133+
#undef DELETE_IF_TRIVIAL_OP
134+
135+
#define NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP(CLS) \
136+
bool visit##CLS##Inst(CLS##Inst *inst) { \
137+
if (!inst->getOperand()->getType().isTrivial(*inst->getFunction())) \
138+
return false; \
139+
inst->setForwardingOwnershipKind(OwnershipKind::None); \
140+
return true; \
141+
}
142+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP(StructExtract)
143+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP(TupleExtract)
144+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP(UncheckedEnumData)
145+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP(SwitchEnum)
146+
#undef NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_OP
147+
148+
#define NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_RESULT(CLS) \
149+
bool visit##CLS##Inst(CLS##Inst *inst) { \
150+
if (!inst->getType().isTrivial(*inst->getFunction())) \
151+
return false; \
152+
inst->setForwardingOwnershipKind(OwnershipKind::None); \
153+
return true; \
154+
}
155+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_RESULT(Enum)
156+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_RESULT(Struct)
157+
NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_RESULT(Tuple)
158+
#undef NEED_TO_CONVERT_FORWARDING_TO_NONE_IF_TRIVIAL_RESULT
159+
160+
#define NO_UPDATE_NEEDED(CLS) \
161+
bool visit##CLS##Inst(CLS##Inst *inst) { return false; }
162+
NO_UPDATE_NEEDED(AllocStack)
163+
NO_UPDATE_NEEDED(DebugValue)
164+
NO_UPDATE_NEEDED(StructElementAddr)
165+
NO_UPDATE_NEEDED(TupleElementAddr)
166+
NO_UPDATE_NEEDED(UncheckedTakeEnumDataAddr)
167+
NO_UPDATE_NEEDED(DestructureTuple)
168+
NO_UPDATE_NEEDED(DestructureStruct)
169+
NO_UPDATE_NEEDED(SelectEnum)
170+
NO_UPDATE_NEEDED(SelectValue)
171+
NO_UPDATE_NEEDED(MarkDependence)
172+
NO_UPDATE_NEEDED(DestroyAddr)
173+
NO_UPDATE_NEEDED(DeallocStack)
174+
NO_UPDATE_NEEDED(Branch)
175+
NO_UPDATE_NEEDED(UncheckedAddrCast)
176+
NO_UPDATE_NEEDED(RefElementAddr)
177+
NO_UPDATE_NEEDED(Upcast)
178+
NO_UPDATE_NEEDED(CheckedCastBranch)
179+
NO_UPDATE_NEEDED(Object)
180+
NO_UPDATE_NEEDED(OpenExistentialRef)
181+
NO_UPDATE_NEEDED(ConvertFunction)
182+
NO_UPDATE_NEEDED(RefToBridgeObject)
183+
NO_UPDATE_NEEDED(BridgeObjectToRef)
184+
NO_UPDATE_NEEDED(UnconditionalCheckedCast)
185+
#undef NO_UPDATE_NEEDED
186+
};
187+
188+
} // namespace
189+
190+
//===----------------------------------------------------------------------===//
191+
// Top Levelish Code?
192+
//===----------------------------------------------------------------------===//
193+
194+
namespace {
195+
196+
struct SILMoveOnlyTypeEliminator {
197+
SILFunction *fn;
198+
bool trivialOnly;
199+
200+
SILMoveOnlyTypeEliminator(SILFunction *fn, bool trivialOnly)
201+
: fn(fn), trivialOnly(trivialOnly) {}
202+
203+
bool process();
204+
};
205+
206+
} // namespace
207+
208+
bool SILMoveOnlyTypeEliminator::process() {
209+
bool madeChange = true;
210+
211+
SmallSetVector<SILArgument *, 8> touchedArgs;
212+
SmallSetVector<SILInstruction *, 8> touchedInsts;
213+
214+
for (auto &bb : *fn) {
215+
// We should (today) never have move only function arguments. Instead we
216+
// convert them in the prologue block.
217+
if (&bb != &fn->front()) {
218+
for (auto *arg : bb.getArguments()) {
219+
if (!arg->getType().isMoveOnlyWrapped())
220+
continue;
221+
222+
// If we are looking at trivial only, skip non-trivial function args.
223+
if (trivialOnly &&
224+
!arg->getType().removingMoveOnlyWrapper().isTrivial(*fn))
225+
continue;
226+
227+
arg->unsafelyEliminateMoveOnlyWrapper();
228+
229+
// If our new type is trivial, convert the arguments ownership to
230+
// None. Otherwise, preserve the ownership kind of the argument.
231+
if (arg->getType().isTrivial(*fn))
232+
arg->setOwnershipKind(OwnershipKind::None);
233+
touchedArgs.insert(arg);
234+
for (auto *use : arg->getNonTypeDependentUses())
235+
touchedInsts.insert(use->getUser());
236+
}
237+
}
238+
239+
for (auto &ii : bb) {
240+
for (SILValue v : ii.getResults()) {
241+
if (!v->getType().isMoveOnlyWrapped())
242+
continue;
243+
244+
if (trivialOnly &&
245+
!v->getType().removingMoveOnlyWrapper().isTrivial(*fn))
246+
continue;
247+
248+
v->unsafelyEliminateMoveOnlyWrapper();
249+
touchedInsts.insert(&ii);
250+
251+
// Add all users as well. This ensures we visit things like
252+
// destroy_value and end_borrow.
253+
for (auto *use : v->getNonTypeDependentUses())
254+
touchedInsts.insert(use->getUser());
255+
madeChange = true;
256+
}
257+
}
258+
}
259+
260+
SILMoveOnlyTypeEliminatorVisitor visitor(touchedArgs);
261+
while (!touchedInsts.empty()) {
262+
visitor.visit(touchedInsts.pop_back_val());
263+
}
264+
265+
return madeChange;
266+
}
267+
268+
//===----------------------------------------------------------------------===//
269+
// Top Level Entrypoint
270+
//===----------------------------------------------------------------------===//
271+
272+
namespace {
273+
274+
struct SILMoveOnlyTypeEliminatorPass : SILFunctionTransform {
275+
bool trivialOnly;
276+
277+
SILMoveOnlyTypeEliminatorPass(bool trivialOnly)
278+
: SILFunctionTransform(), trivialOnly(trivialOnly) {}
279+
280+
void run() override {
281+
auto *fn = getFunction();
282+
283+
// Don't rerun on deserialized functions. We lower trivial things earlier
284+
// during Raw SIL.
285+
if (getFunction()->wasDeserializedCanonical())
286+
return;
287+
288+
assert(fn->getModule().getStage() == SILStage::Raw &&
289+
"Should only run on Raw SIL");
290+
291+
if (SILMoveOnlyTypeEliminator(getFunction(), trivialOnly).process()) {
292+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
293+
}
294+
}
295+
};
296+
297+
} // anonymous namespace
298+
299+
SILTransform *swift::createTrivialMoveOnlyTypeEliminator() {
300+
return new SILMoveOnlyTypeEliminatorPass(true /*trivial only*/);
301+
}
302+
303+
SILTransform *swift::createMoveOnlyTypeEliminator() {
304+
return new SILMoveOnlyTypeEliminatorPass(false /*trivial only*/);
305+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
173173
// value.
174174
P.addMoveOnlyChecker(); // Check noImplicitCopy isn't copied.
175175

176+
// Now that we have run move only checking, eliminate SILMoveOnly wrapped
177+
// trivial types from the IR. We cannot introduce extra "copies" of trivial
178+
// things so we can simplify our implementation by eliminating them here.
179+
P.addTrivialMoveOnlyTypeEliminator();
180+
181+
// As a temporary measure, we also eliminate move only for non-trivial types
182+
// until we can audit the later part of the pipeline. Eventually, this should
183+
// occur before IRGen.
184+
P.addMoveOnlyTypeEliminator();
185+
176186
// This phase performs optimizations necessary for correct interoperation of
177187
// Swift os log APIs with C os_log ABIs.
178188
// Pass dependencies: this pass depends on MandatoryInlining and Mandatory

0 commit comments

Comments
 (0)