Skip to content

Commit a44bf30

Browse files
authored
Merge pull request #84476 from kavon/manual-ownership/usability-fixes-1
Usability improvements for ManualOwnership (aka "explicit copies mode")
2 parents d806ee1 + c20ed75 commit a44bf30

File tree

6 files changed

+188
-33
lines changed

6 files changed

+188
-33
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ NOTE(performance_called_from,none,
435435
"called from here", ())
436436
ERROR(manualownership_copy,none,
437437
"explicit 'copy' required here", ())
438+
ERROR(manualownership_copy_happened,none,
439+
"accessing %0 produces a copy of it; write 'copy' to acknowledge", (Identifier))
440+
ERROR(manualownership_copy_demanded,none,
441+
"ownership of %0 is demanded and cannot not be consumed; write 'copy' to satisfy", (Identifier))
442+
ERROR(manualownership_copy_captured,none,
443+
"ownership of %0 is demanded by a closure; write 'copy' in its capture list to satisfy", (Identifier))
438444

439445
// 'transparent' diagnostics
440446
ERROR(circular_transparent,none,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,6 +2949,17 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
29492949
A->getAsString(Args));
29502950
}
29512951
}
2952+
// Have ManualOwnership imply MandatoryCopyPropagation.
2953+
// Once that pass becomes enabled by default, we don't need this.
2954+
if (LangOpts.hasFeature(ManualOwnership)) {
2955+
specifiedCopyPropagationOption = CopyPropagationOption::Always;
2956+
2957+
if (auto *Flag = Args.getLastArg(OPT_copy_propagation_state_EQ)) {
2958+
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_combination,
2959+
Flag->getAsString(Args),
2960+
"-enable-experimental-feature ManualOwnership");
2961+
}
2962+
}
29522963
if (Args.hasArg(OPT_enable_copy_propagation)) {
29532964
specifiedCopyPropagationOption = CopyPropagationOption::Always;
29542965
}

lib/SILGen/SILGenExpr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7315,7 +7315,7 @@ RValue RValueEmitter::visitCopyExpr(CopyExpr *E, SGFContext C) {
73157315
return RValue(SGF, {optTemp->getManagedAddress()}, subType.getASTType());
73167316
}
73177317

7318-
if (subType.isLoadable(SGF.F)) {
7318+
if (subType.isLoadable(SGF.F) || !SGF.silConv.useLoweredAddresses()) {
73197319
ManagedValue mv =
73207320
SGF.emitRValue(subExpr, SGFContext::AllowImmediatePlusZero)
73217321
.getAsSingleValue(SGF, subExpr);

lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h"
2121
#include "swift/SILOptimizer/PassManager/Transforms.h"
2222
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
23+
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
2324
#include "llvm/Support/Debug.h"
2425

2526
using namespace swift;
@@ -376,12 +377,16 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
376377
LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc);
377378

378379
if (perfConstr == PerformanceConstraints::ManualOwnership) {
379-
if (impact == RuntimeEffect::RefCounting) {
380+
if (impact & RuntimeEffect::RefCounting) {
380381
bool shouldDiagnose = false;
381382
switch (inst->getKind()) {
383+
case SILInstructionKind::PartialApplyInst:
384+
case SILInstructionKind::DestroyAddrInst:
385+
case SILInstructionKind::DestroyValueInst:
386+
break; // These modify reference counts, but aren't copies.
382387
case SILInstructionKind::ExplicitCopyAddrInst:
383388
case SILInstructionKind::ExplicitCopyValueInst:
384-
break;
389+
break; // Explicitly acknowledged copies are OK.
385390
case SILInstructionKind::LoadInst: {
386391
// FIXME: we don't have an `explicit_load` and transparent functions can
387392
// end up bringing in a `load [copy]`. A better approach is needed to
@@ -418,19 +423,40 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
418423
break;
419424
}
420425
if (shouldDiagnose) {
426+
LLVM_DEBUG(llvm::dbgs()
427+
<< "function " << inst->getFunction()->getName()
428+
<< "\n has unexpected copying instruction: " << *inst);
429+
430+
// Try to come up with a useful diagnostic.
431+
if (auto svi = dyn_cast<SingleValueInstruction>(inst)) {
432+
if (auto name = VariableNameInferrer::inferName(svi)) {
433+
// Simplistic check for whether this is a closure capture.
434+
for (auto user : svi->getUsers()) {
435+
if (isa<PartialApplyInst>(user)) {
436+
LLVM_DEBUG(llvm::dbgs() << "captured by "<< *user);
437+
diagnose(loc, diag::manualownership_copy_captured, *name);
438+
return false;
439+
}
440+
}
441+
442+
// There's no hope of borrowing access if there's a consuming use.
443+
for (auto op : svi->getUses()) {
444+
if (op->getOperandOwnership() == OperandOwnership::ForwardingConsume) {
445+
LLVM_DEBUG(llvm::dbgs() << "demanded by "<< *(op->getUser()));
446+
diagnose(loc, diag::manualownership_copy_demanded, *name);
447+
return false;
448+
}
449+
}
450+
451+
diagnose(loc, diag::manualownership_copy_happened, *name);
452+
return false;
453+
}
454+
}
455+
456+
// Back-up diagnostic, when all-else fails.
421457
diagnose(loc, diag::manualownership_copy);
422-
llvm::dbgs() << "function " << inst->getFunction()->getName();
423-
llvm::dbgs() << "\n has unexpected copying instruction: ";
424-
inst->dump();
425458
return false; // Don't bail-out early; diagnose more issues in the func.
426459
}
427-
} else if (impact == RuntimeEffect::ExclusivityChecking) {
428-
// TODO: diagnose only the nested exclusivity; perhaps as a warning
429-
// since we don't yet have reference bindings?
430-
431-
// diagnose(loc, diag::performance_arc);
432-
// inst->dump();
433-
// return true;
434460
}
435461
return false;
436462
}

test/SIL/manual_ownership.swift

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// RUN: %target-swift-frontend %s -emit-sil -verify \
2-
// RUN: -enable-experimental-feature ManualOwnership \
3-
// RUN: -enable-copy-propagation=always
2+
// RUN: -enable-experimental-feature ManualOwnership
43

54
// REQUIRES: swift_feature_ManualOwnership
65

@@ -34,6 +33,14 @@ public class Triangle {
3433
borrowing func borrowing() {}
3534
}
3635

36+
// MARK: utilities
37+
38+
func eat(_ t: consuming Triangle) {}
39+
func use(_ t: borrowing Triangle) {}
40+
41+
func consume_generic<T>(_ t: consuming T) {}
42+
func borrow_generic<T>(_ t: borrowing T) {}
43+
3744
/// MARK: return statements
3845

3946
@_manualOwnership
@@ -44,7 +51,7 @@ public func basic_return1() -> Triangle {
4451

4552
@_manualOwnership
4653
public func basic_return2(t: Triangle) -> Triangle {
47-
return t // expected-error {{explicit 'copy' required here}}
54+
return t // expected-error {{ownership of 't' is demanded}}
4855
}
4956
@_manualOwnership
5057
public func basic_return2_fixed(t: Triangle) -> Triangle {
@@ -68,8 +75,9 @@ func reassign_with_lets() -> Triangle {
6875
func renamed_return(_ cond: Bool, _ a: Triangle) -> Triangle {
6976
let b = a
7077
let c = b
71-
if cond { return b } // expected-error {{explicit 'copy' required here}}
72-
return c // expected-error {{explicit 'copy' required here}}
78+
// FIXME: we say 'c' instead of 'b', because of the propagation.
79+
if cond { return b } // expected-error {{ownership of 'c' is demanded}}
80+
return c // expected-error {{ownership of 'c' is demanded}}
7381
}
7482

7583
@_manualOwnership
@@ -101,26 +109,27 @@ func basic_methods_borrowing(_ t1: Triangle) {
101109
@_manualOwnership
102110
func basic_methods_consuming(_ t1: Triangle) {
103111
let t2 = Triangle()
104-
t1.consuming() // expected-error {{explicit 'copy' required here}}
112+
t1.consuming() // expected-error {{ownership of 't1' is demanded}}
105113
t2.consuming()
106114
}
107115
@_manualOwnership
108116
func basic_methods_consuming_fixed(_ t1: Triangle) {
109117
let t2 = Triangle()
110-
var t3 = Triangle()
111-
t3 = Triangle()
112118

113119
(copy t1).consuming()
114120
(copy t2).consuming() // FIXME: why is this not propagated?
115-
(copy t3).consuming()
116121
}
117122

118-
func consumingFunc(_ t0: consuming Triangle) {}
123+
@_manualOwnership
124+
@discardableResult
125+
func consumingFunc(_ t0: consuming Triangle) -> Bool { return false }
126+
127+
@_manualOwnership
119128
func plainFunc(_ t0: Triangle) {}
120129

121130
@_manualOwnership
122131
func basic_function_call(_ t1: Triangle) {
123-
consumingFunc(t1) // expected-error {{explicit 'copy' required here}}
132+
consumingFunc(t1) // expected-error {{ownership of 't1' is demanded}}
124133
consumingFunc(copy t1)
125134
plainFunc(t1)
126135
}
@@ -146,7 +155,7 @@ func basic_function_call(_ t1: Triangle) {
146155
@_manualOwnership
147156
public func basic_loop_trivial_values(_ t: Triangle, _ xs: [Triangle]) {
148157
var p: Pair = t.a
149-
for x in xs { // expected-error {{explicit 'copy' required here}}
158+
for x in xs { // expected-error {{ownership of 'xs' is demanded}}
150159
p = p.midpoint(x.a)
151160
}
152161
t.a = p
@@ -169,33 +178,125 @@ public func basic_loop_trivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) {
169178

170179
@_manualOwnership
171180
public func basic_loop_nontrivial_values(_ t: Triangle, _ xs: [Triangle]) {
172-
var p: Pair = t.nontrivial.a // expected-error {{explicit 'copy' required here}}
173-
for x in xs { // expected-error {{explicit 'copy' required here}}
174-
p = p.midpoint(x.nontrivial.a) // expected-error {{explicit 'copy' required here}}
181+
var p: Pair = t.nontrivial.a // expected-error {{accessing 't.nontrivial' produces a copy of it}}
182+
for x in xs { // expected-error {{ownership of 'xs' is demanded}}
183+
p = p.midpoint(x.nontrivial.a) // expected-error {{accessing 'x.nontrivial' produces a copy of it}}
175184
}
176-
t.nontrivial.a = p // expected-error {{explicit 'copy' required here}}
185+
t.nontrivial.a = p // expected-error {{accessing 't.nontrivial' produces a copy of it}}
177186
}
178187

179188
// FIXME: there should be no copies required in the below, other than what's already written.
180189
@_manualOwnership
181190
public func basic_loop_nontrivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) {
182-
var p: Pair = (copy t.nontrivial).a // expected-error {{explicit 'copy' required here}}
191+
var p: Pair = (copy t.nontrivial).a // expected-error {{accessing 't.nontrivial' produces a copy of it}}
183192
for x in copy xs {
184-
p = p.midpoint((copy x.nontrivial).a) // expected-error {{explicit 'copy' required here}}
193+
p = p.midpoint((copy x.nontrivial).a) // expected-error {{accessing 'x.nontrivial' produces a copy of it}}
185194
}
186-
(copy t.nontrivial).a = p // expected-error {{explicit 'copy' required here}}
195+
(copy t.nontrivial).a = p // expected-error {{accessing 't.nontrivial' produces a copy of it}}
187196
}
188197

189198

190199
/// MARK: Globals
191200
let ref_result = [5, 13, 29]
192201

202+
// FIXME: if we had a borrow operator, we could allow people to elide these simple copies that
203+
// are present to avoid exclusivity issues. We'd need to start generating read coroutines.
193204
@_manualOwnership
194205
func access_global_1() -> Int {
195-
return ref_result[2] // expected-error {{explicit 'copy' required here}}
206+
return ref_result[2] // expected-error {{accessing 'ref_result' produces a copy of it}}
196207
}
197208
@_manualOwnership
198209
func access_global_1_fixed() -> Int {
199210
return (copy ref_result)[2]
200211
}
201212

213+
/// MARK: closures
214+
215+
// FIXME: (1) Closure capture lists need to support the short-hand [copy t] and produce explicit copies.
216+
// We also need a better error message for when this is missed for closure captures.
217+
// (2) Escaping closures need to be recursively checked by the PerformanceDiagnostics.
218+
// We might just need to widen the propagation of [manual_ownership]?
219+
// (3) Autoclosures have no ability to annotate captures. Is that OK?
220+
221+
@_manualOwnership
222+
func closure_basic(_ t: Triangle) -> () -> Triangle {
223+
return { return t } // expected-error {{ownership of 't' is demanded by a closure}}
224+
}
225+
@_manualOwnership
226+
func closure_basic_fixed(_ t: Triangle) -> () -> Triangle {
227+
return { [t = copy t] in return t }
228+
}
229+
230+
@_manualOwnership
231+
func closure_copies_in_body(_ t: Triangle) -> () -> Triangle {
232+
return { [t = copy t] in
233+
eat(t) // FIXME: missing required copies
234+
eat(t)
235+
return t }
236+
}
237+
238+
@_manualOwnership
239+
func closure_copies_in_body_noescape(_ t: Triangle) -> Triangle {
240+
let f = { [t = copy t] in
241+
eat(t) // FIXME: missing required copies
242+
eat(t)
243+
return t
244+
}
245+
return f()
246+
}
247+
248+
@_manualOwnership
249+
func simple_assert(_ f: @autoclosure () -> Bool) {
250+
guard f() else { fatalError() }
251+
}
252+
@_manualOwnership
253+
func try_to_assert(_ n: Int, _ names: [String]) {
254+
simple_assert(names.count == n)
255+
}
256+
257+
@_manualOwnership
258+
func copy_in_autoclosure(_ t: Triangle) {
259+
simple_assert(consumingFunc(t)) // FIXME: missing required copies
260+
}
261+
262+
/// MARK: generics
263+
264+
@_manualOwnership
265+
func return_generic<T>(_ t: T) -> T {
266+
return t // expected-error {{explicit 'copy' required here}}
267+
}
268+
@_manualOwnership
269+
func return_generic_fixed<T>(_ t: T) -> T {
270+
return copy t
271+
}
272+
273+
@_manualOwnership
274+
func reassign_with_lets<T>(_ t: T) -> T {
275+
let x = t // expected-error {{explicit 'copy' required here}}
276+
let y = x // expected-error {{explicit 'copy' required here}}
277+
let z = y // expected-error {{explicit 'copy' required here}}
278+
return copy z
279+
}
280+
281+
// FIXME: there's copy propagation has no effect on address-only types.
282+
@_manualOwnership
283+
func reassign_with_lets_fixed<T>(_ t: T) -> T {
284+
let x = copy t
285+
let y = copy x
286+
let z = copy y
287+
return copy z
288+
}
289+
290+
@_manualOwnership
291+
func copy_generic<T>(_ t: T) {
292+
consume_generic(t) // expected-error {{explicit 'copy' required here}}
293+
borrow_generic(t)
294+
consume_generic(t) // expected-error {{explicit 'copy' required here}}
295+
}
296+
297+
@_manualOwnership
298+
func copy_generic_fixed<T>(_ t: T) {
299+
consume_generic(copy t)
300+
borrow_generic(t)
301+
consume_generic(copy t)
302+
}

test/SILGen/opaque_values_silgen.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,3 +885,14 @@ struct Twople<T> {
885885
func throwTypedValue(_ e: Err) throws(Err) { throw e }
886886

887887
struct Err : Error {}
888+
889+
// CHECK-LABEL: sil{{.*}} [ossa] @copy_expr_generic : {{.*}} {
890+
// CHECK: bb0([[E:%[^,]+]] : @guaranteed $T
891+
// CHECK: [[E_COPY:%[^,]+]] = explicit_copy_value [[E]]
892+
// CHECK: apply {{.*}}<T>([[E_COPY]])
893+
// CHECK-LABEL: } // end sil function 'copy_expr_generic'
894+
@_silgen_name("copy_expr_generic")
895+
func copy_expr_generic<T>(_ t: T) {
896+
eat_generic(copy t)
897+
}
898+
func eat_generic<T>(_ t: consuming T) {}

0 commit comments

Comments
 (0)