Skip to content

Commit 6738ecb

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. It does this for both trivial and non-trivial types. For trivial types, this will remain the behavior forever, but in the future, we will propagate the moveOnly bit for non-trivial types so that we can catch the optimizer adding extra copies later in the pipeline. 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. The failing test was: noimplicitcopy_attr.swift.
1 parent 0d11e8e commit 6738ecb

File tree

7 files changed

+1172
-0
lines changed

7 files changed

+1172
-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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ 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 trivial types")
441443
PASS(LexicalLifetimeEliminator, "sil-lexical-lifetime-eliminator",
442444
"Pass that removes lexical lifetime markers from borrows and alloc stack")
443445
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: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
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+
199+
SILMoveOnlyTypeEliminator(SILFunction *fn) : fn(fn) {}
200+
201+
bool process();
202+
};
203+
204+
} // namespace
205+
206+
bool SILMoveOnlyTypeEliminator::process() {
207+
bool madeChange = true;
208+
209+
SmallSetVector<SILArgument *, 8> touchedArgs;
210+
SmallSetVector<SILInstruction *, 8> touchedInsts;
211+
212+
for (auto &bb : *fn) {
213+
// We should (today) never have move only function arguments. Instead we
214+
// convert them in the prologue block.
215+
if (&bb != &fn->front()) {
216+
for (auto *arg : bb.getArguments()) {
217+
if (arg->getType().isMoveOnlyWrapped()) {
218+
if (arg->unsafelyEliminateMoveOnlyWrapper()) {
219+
// If our new type is trivial, convert the arguments ownership to
220+
// None. Otherwise, preserve the ownership kind of the argument.
221+
if (arg->getType().isTrivial(*fn))
222+
arg->setOwnershipKind(OwnershipKind::None);
223+
touchedArgs.insert(arg);
224+
for (auto *use : arg->getNonTypeDependentUses())
225+
touchedInsts.insert(use->getUser());
226+
}
227+
}
228+
}
229+
}
230+
231+
for (auto &ii : bb) {
232+
for (SILValue v : ii.getResults()) {
233+
if (v->getType().isMoveOnlyWrapped()) {
234+
v->unsafelyEliminateMoveOnlyWrapper();
235+
touchedInsts.insert(&ii);
236+
237+
// Add all users as well. This ensures we visit things like
238+
// destroy_value and end_borrow.
239+
for (auto *use : v->getNonTypeDependentUses())
240+
touchedInsts.insert(use->getUser());
241+
madeChange = true;
242+
}
243+
}
244+
}
245+
}
246+
247+
SILMoveOnlyTypeEliminatorVisitor visitor(touchedArgs);
248+
while (!touchedInsts.empty()) {
249+
visitor.visit(touchedInsts.pop_back_val());
250+
}
251+
252+
return madeChange;
253+
}
254+
255+
//===----------------------------------------------------------------------===//
256+
// Top Level Entrypoint
257+
//===----------------------------------------------------------------------===//
258+
259+
namespace {
260+
261+
class SILMoveOnlyTypeEliminatorPass : public SILFunctionTransform {
262+
void run() override {
263+
auto *fn = getFunction();
264+
265+
// Don't rerun on deserialized functions. We lower trivial things earlier
266+
// during Raw SIL.
267+
if (getFunction()->wasDeserializedCanonical())
268+
return;
269+
270+
assert(fn->getModule().getStage() == SILStage::Raw &&
271+
"Should only run on Raw SIL");
272+
273+
if (SILMoveOnlyTypeEliminator(getFunction()).process()) {
274+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
275+
}
276+
}
277+
};
278+
279+
} // anonymous namespace
280+
281+
SILTransform *swift::createTrivialMoveOnlyTypeEliminator() {
282+
return new SILMoveOnlyTypeEliminatorPass();
283+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ 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+
176181
// This phase performs optimizations necessary for correct interoperation of
177182
// Swift os log APIs with C os_log ABIs.
178183
// Pass dependencies: this pass depends on MandatoryInlining and Mandatory

0 commit comments

Comments
 (0)