Skip to content

Commit e6faa30

Browse files
committed
[move-function] Allow for move to be applied to inout function arguments.
These are verified like a var except that the parameter will have an implicit read use on all function exit terminators. This ensures that if a programmer moves a value out of the inout parameter, a new value must be assigned before function exit. This is important since otherwise the convention would be invalidated. Example: ``` func f(_ x: inout T) { // Move a value out of x. x does not have a value within it anymore. let value = _move(x) } // So we emit an error saying there is a use here since an inout must have // a valid object within it upon function exit due to convention guarantees. ``` As an added side-effect of this, one can now use move as on self in mutating contexts. Thus one can move the self value out of the self inout binding using _move and if one does not replace self with a new value by end of function, you will get a compile time error, e.x.: ``` struct S { var buffer: Klass mutating func doSomething() { let b = move(self).buffer // ... do some stuff, maybe get a different buffer ... let maybeDifferentBuffer = maybeNewBuffer(b) // If we do not re-initialize S with a new value by uncommenting // the following line, we will get a compile time error. // self = S(differentBuffer) } } ```
1 parent 7b0b78d commit e6faa30

File tree

4 files changed

+244
-17
lines changed

4 files changed

+244
-17
lines changed

lib/SILOptimizer/Mandatory/MoveFunctionCanonicalization.cpp

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,18 @@ tryHandlingLoadableVarMovePattern(MarkUnresolvedMoveAddrInst *markMoveAddr,
112112

113113
LLVM_DEBUG(llvm::dbgs() << "Found LI: " << *li);
114114
SILValue operand = stripAccessMarkers(li->getOperand());
115-
auto *originalASI = dyn_cast<AllocStackInst>(operand);
116-
// Make sure that we have a lexical alloc_stack marked as a var or a let. We
117-
// don't want properties.
118-
if (!originalASI || !originalASI->isLexical() ||
119-
!(originalASI->isVar() || originalASI->isLet()))
120-
return false;
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+
}
121126

122-
LLVM_DEBUG(llvm::dbgs() << "Found OriginalASI: " << *originalASI);
123127
// Make sure that there aren't any side-effect having instructions in
124128
// between our load/store.
125129
LLVM_DEBUG(llvm::dbgs() << "Checking for uses in between LI and SI.\n");
@@ -132,7 +136,7 @@ tryHandlingLoadableVarMovePattern(MarkUnresolvedMoveAddrInst *markMoveAddr,
132136
}
133137

134138
if (auto *dvi = dyn_cast<DestroyAddrInst>(&iter)) {
135-
if (aa->isNoAlias(dvi->getOperand(), originalASI)) {
139+
if (aa->isNoAlias(dvi->getOperand(), operand)) {
136140
// We are going to be extending the lifetime of our
137141
// underlying value, not shrinking it so we can ignore
138142
// destroy_addr on other non-aliasing values.
@@ -157,7 +161,7 @@ tryHandlingLoadableVarMovePattern(MarkUnresolvedMoveAddrInst *markMoveAddr,
157161
// Ok, we know our original lexical alloc_stack is not written to in between
158162
// the load/store. Move the mark_move_addr onto the lexical alloc_stack.
159163
LLVM_DEBUG(llvm::dbgs() << " Doing loadable var!\n");
160-
markMoveAddr->setSrc(originalASI);
164+
markMoveAddr->setSrc(operand);
161165
return true;
162166
}
163167

@@ -239,13 +243,13 @@ static bool tryConvertSimpleMoveFromAllocStackTemporary(
239243
}
240244

241245
// If we have a store [init], see if our src is a load [copy] from an
242-
// alloc_stack that is lexical var. In this case, we want to move our
243-
// mark_unresolved_move_addr onto that lexical var. This pattern occurs due to
244-
// SILGen always loading loadable values from memory when retrieving an
245-
// RValue. Calling _move then since _move is generic forces the value to be
246-
// re-materialized into an alloc_stack. In this example remembering that
247-
// mark_unresolved_move_addr is a copy_addr [init], we try to move the MUMA
248-
// onto the original lexical alloc_stack.
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.
249253
if (tryHandlingLoadableVarMovePattern(markMoveAddr, si, aa))
250254
return true;
251255

lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,20 @@ bool MoveKillsCopyableAddressesObjectChecker::check() {
885885
if (!visitAccessPathUses(visitor, accessPath, fn))
886886
continue;
887887

888+
// See if our base address is an inout. If we found any moves, add as a
889+
// liveness use all function terminators.
890+
if (auto *fArg = dyn_cast<SILFunctionArgument>(address)) {
891+
if (fArg->hasConvention(SILArgumentConvention::Indirect_Inout)) {
892+
if (visitor.useState.markMoves.size()) {
893+
SmallVector<SILBasicBlock *, 2> exitingBlocks;
894+
fn->findExitingBlocks(exitingBlocks);
895+
for (auto *block : exitingBlocks) {
896+
visitor.useState.livenessUses.insert(block->getTerminator());
897+
}
898+
}
899+
}
900+
}
901+
888902
// Now initialize our data structures.
889903
SWIFT_DEFER {
890904
useBlocks.clear();
@@ -987,7 +1001,7 @@ class MoveKillsCopyableAddressesCheckerPass : public SILFunctionTransform {
9871001
for (auto *arg : fn->front().getSILFunctionArguments()) {
9881002
if (arg->getType().isAddress() &&
9891003
(arg->hasConvention(SILArgumentConvention::Indirect_In) ||
990-
arg->hasConvention(SILArgumentConvention::Indirect_In)))
1004+
arg->hasConvention(SILArgumentConvention::Indirect_Inout)))
9911005
checker.addressesToCheck.insert(arg);
9921006
}
9931007

test/SILOptimizer/move_function_kills_copyable_addressonly_vars.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class Klass {}
1313
func consumingUse<T>(_ k: __owned T) {}
1414
var booleanValue: Bool { false }
1515
func nonConsumingUse<T>(_ k: T) {}
16+
func exchangeUse<T>(_ k: __owned T) -> T { k }
1617

1718
///////////
1819
// Tests //
@@ -93,3 +94,106 @@ public func performMoveOnVarMultiBlockError2<T>(_ p: T) {
9394
x = p
9495
nonConsumingUse(x)
9596
}
97+
98+
public func performMoveOnInOut<T>(_ p: inout T) { // expected-error {{'p' used after being moved}}
99+
let buf = _move(p) // expected-note {{move here}}
100+
let _ = buf
101+
} // expected-note {{use here}}
102+
103+
public func performMoveOnInOut2<T>(_ p: inout T, _ p2: T) {
104+
let buf = _move(p)
105+
let _ = buf
106+
p = p2
107+
}
108+
109+
struct S<T> {
110+
var buffer: T?
111+
112+
mutating func appendNoError() {
113+
let b = _move(self).buffer
114+
let maybeNewB = exchangeUse(b)
115+
self = .init(buffer: maybeNewB)
116+
}
117+
118+
mutating func appendError() { // expected-error {{'self' used after being moved}}
119+
let b = _move(self).buffer // expected-note {{move here}}
120+
let _ = b
121+
} // expected-note {{use here}}
122+
123+
mutating func appendThrowingNoError1(_ f: () throws -> ()) throws {
124+
let b = _move(self).buffer
125+
let maybeNewB = exchangeUse(b)
126+
// We have to initialize self before we call try since otherwise we will
127+
// not initialize self along the throws path.
128+
self = .init(buffer: maybeNewB)
129+
try f()
130+
}
131+
132+
mutating func appendThrowingNoError2(_ f: () throws -> ()) {
133+
do {
134+
let b = _move(self).buffer
135+
try f()
136+
let maybeNewB = exchangeUse(b)
137+
self = .init(buffer: maybeNewB)
138+
} catch {
139+
self = .init(buffer: nil)
140+
}
141+
}
142+
143+
// In this case, since we initialize self before the try point, we will have
144+
// re-initialized self before hitting either the code after the try that is
145+
// inline or the catch block.
146+
mutating func appendThrowingNoError3(_ f: () throws -> ()) {
147+
do {
148+
let b = _move(self).buffer
149+
let maybeNewB = exchangeUse(b)
150+
self = .init(buffer: maybeNewB)
151+
try f()
152+
} catch {
153+
}
154+
}
155+
156+
mutating func appendThrowingError0(_ f: () throws -> ()) throws { // expected-error {{'self' used after being moved}}
157+
let b = _move(self).buffer // expected-note {{move here}}
158+
let maybeNewB = exchangeUse(b)
159+
try f() // expected-note {{use here}}
160+
self = .init(buffer: maybeNewB)
161+
}
162+
163+
164+
mutating func appendThrowingError1(_ f: () throws -> ()) throws { // expected-error {{'self' used after being moved}}
165+
let b = _move(self).buffer // expected-note {{move here}}
166+
let maybeNewB = exchangeUse(b)
167+
let _ = maybeNewB
168+
try f() // expected-note {{use here}}
169+
}
170+
171+
mutating func appendThrowingError2(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
172+
do {
173+
let b = _move(self).buffer // expected-note {{move here}}
174+
let _ = b
175+
try f()
176+
} catch {
177+
self = .init(buffer: nil)
178+
}
179+
} // expected-note {{use here}}
180+
181+
mutating func appendThrowingError3(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
182+
do {
183+
let b = _move(self).buffer // expected-note {{move here}}
184+
try f()
185+
let maybeNewB = exchangeUse(b)
186+
self = .init(buffer: maybeNewB)
187+
} catch {
188+
}
189+
} // expected-note {{use here}}
190+
191+
mutating func appendThrowingError4(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
192+
do {
193+
let b = _move(self).buffer // expected-note {{move here}}
194+
let _ = b
195+
try f()
196+
} catch {
197+
}
198+
} // expected-note {{use here}}
199+
}

test/SILOptimizer/move_function_kills_copyable_loadable_vars.swift

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func consumingUse(_ k: __owned Klass) {}
1414
var booleanValue: Bool { false }
1515
var booleanValue2: Bool { false }
1616
func nonConsumingUse(_ k: Klass) {}
17+
func exchangeUse(_ k: Klass) -> Klass { k }
1718

1819
///////////
1920
// Klassests //
@@ -165,3 +166,107 @@ public func performMoveOnLaterDefinedInit2(_ p: Klass) {
165166
nonConsumingUse(x)
166167
let _ = _move(x)
167168
}
169+
170+
public func performMoveOnInOut(_ p: inout Klass) { // expected-error {{'p' used after being moved}}
171+
let buf = _move(p) // expected-note {{move here}}
172+
let _ = buf
173+
} // expected-note {{use here}}
174+
175+
public func performMoveOnInOut2(_ p: inout Klass, _ p2: Klass) {
176+
let buf = _move(p)
177+
p = p2
178+
let _ = buf
179+
}
180+
181+
// Mutating self is an inout argument.
182+
struct S {
183+
var buffer: Klass?
184+
185+
mutating func appendNoError() {
186+
let b = _move(self).buffer!
187+
let maybeNewB = exchangeUse(b)
188+
self = .init(buffer: maybeNewB)
189+
}
190+
191+
mutating func appendError() { // expected-error {{'self' used after being moved}}
192+
let b = _move(self).buffer // expected-note {{move here}}
193+
let _ = b
194+
} // expected-note {{use here}}
195+
196+
mutating func appendThrowingNoError1(_ f: () throws -> ()) throws {
197+
let b = _move(self).buffer!
198+
let maybeNewB = exchangeUse(b)
199+
// We have to initialize self before we call try since otherwise we will
200+
// not initialize self along the throws path.
201+
self = .init(buffer: maybeNewB)
202+
try f()
203+
}
204+
205+
mutating func appendThrowingNoError2(_ f: () throws -> ()) {
206+
do {
207+
let b = _move(self).buffer!
208+
try f()
209+
let maybeNewB = exchangeUse(b)
210+
self = .init(buffer: maybeNewB)
211+
} catch {
212+
self = .init(buffer: nil)
213+
}
214+
}
215+
216+
// In this case, since we initialize self before the try point, we will have
217+
// re-initialized self before hitting either the code after the try that is
218+
// inline or the catch block.
219+
mutating func appendThrowingNoError3(_ f: () throws -> ()) {
220+
do {
221+
let b = _move(self).buffer!
222+
let maybeNewB = exchangeUse(b)
223+
self = .init(buffer: maybeNewB)
224+
try f()
225+
} catch {
226+
}
227+
}
228+
229+
mutating func appendThrowingError0(_ f: () throws -> ()) throws { // expected-error {{'self' used after being moved}}
230+
let b = _move(self).buffer! // expected-note {{move here}}
231+
let maybeNewB = exchangeUse(b)
232+
try f() // expected-note {{use here}}
233+
self = .init(buffer: maybeNewB)
234+
}
235+
236+
237+
mutating func appendThrowingError1(_ f: () throws -> ()) throws { // expected-error {{'self' used after being moved}}
238+
let b = _move(self).buffer! // expected-note {{move here}}
239+
let maybeNewB = exchangeUse(b)
240+
let _ = maybeNewB
241+
try f() // expected-note {{use here}}
242+
}
243+
244+
mutating func appendThrowingError2(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
245+
do {
246+
let b = _move(self).buffer // expected-note {{move here}}
247+
let _ = b
248+
try f()
249+
} catch {
250+
self = .init(buffer: nil)
251+
}
252+
} // expected-note {{use here}}
253+
254+
mutating func appendThrowingError3(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
255+
do {
256+
let b = _move(self).buffer! // expected-note {{move here}}
257+
try f()
258+
let maybeNewB = exchangeUse(b)
259+
self = .init(buffer: maybeNewB)
260+
} catch {
261+
}
262+
} // expected-note {{use here}}
263+
264+
mutating func appendThrowingError4(_ f: () throws -> ()) { // expected-error {{'self' used after being moved}}
265+
do {
266+
let b = _move(self).buffer // expected-note {{move here}}
267+
let _ = b
268+
try f()
269+
} catch {
270+
}
271+
} // expected-note {{use here}}
272+
}

0 commit comments

Comments
 (0)