Skip to content

Commit 7a43d24

Browse files
authored
Merge pull request #60047 from gottesmm/plus_zero_fix_noimplicitcopy
[no-implicit-copy] Use default param convention instead of forcing +1
2 parents 9890088 + f1182a7 commit 7a43d24

33 files changed

+3333
-287
lines changed

docs/SIL.rst

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,198 @@ Consider the following legal SIL where we leak ``%0`` in blocks prefixed with
25702570
return %1 : $Klass
25712571
}
25722572

2573+
Move Only Wrapped Types
2574+
-----------------------
2575+
2576+
Semantics
2577+
~~~~~~~~~
2578+
2579+
Today SIL supports values of move only wrapped type. Given a copyable type
2580+
``$T``, the type ``$@moveOnly T`` is the move only wrapped variant of the
2581+
type. This type is always non-trivial even if ``$T`` itself is trivial (see
2582+
discussion below). All move only wrapped types obey the invariant that they can
2583+
be copied in Raw SIL but cannot be copied in Canonical SIL and later SIL
2584+
stages. This ensures that once we are in Canonical SIL such values are "move
2585+
only values" that are guaranteed to never be copied. This is enforced by:
2586+
2587+
* Having SILGen emit copies as it normally does.
2588+
2589+
* Use OSSA canonicalization to eliminate copies that aren't needed semantically
2590+
due to consuming uses of the value. This is implemented by the pass guaranteed
2591+
pass "MoveOnlyChecker". Emit errors on any of the consuming uses that we found
2592+
in said pass.
2593+
2594+
Assuming that no errors are emitted, we can then conclude before we reach
2595+
canonical SIL that the value was never copied and thus is a "move only value"
2596+
even though the actual underlying wrapped type is copyable. As an example of
2597+
this, consider the following Swift::
2598+
2599+
func doSomething(@_noImplicitCopy _ x: Klass) -> () { // expected-error {{'x' has guaranteed ownership but was consumed}}
2600+
x.doSomething()
2601+
let x2 = x // expected-note {{consuming use}}
2602+
x2.doSomething()
2603+
}
2604+
2605+
which codegens to the following SIL::
2606+
2607+
sil hidden [ossa] @doSomething : $@convention(thin) (@guaranteed Klass) -> () {
2608+
bb0(%0 : @noImplicitCopy $Klass):
2609+
%1 = copyable_to_moveonlywrapper [guaranteed] %0 : $@moveOnly Klass
2610+
%2 = copy_value %1 : $@moveOnly Klass
2611+
%3 = mark_must_check [no_copy] %2 : $@moveOnly Klass
2612+
debug_value %3 : $@moveOnly Klass, let, name "x", argno 1
2613+
%4 = begin_borrow %3 : $@moveOnly Klass
2614+
%5 = function_ref @$s4test5KlassC11doSomethingyyF : $@convention(method) (@guaranteed Klass) -> ()
2615+
%6 = moveonlywrapper_to_copyable [guaranteed] %4 : $@moveOnly Klass
2616+
%7 = apply %5(%6) : $@convention(method) (@guaranteed Klass) -> ()
2617+
end_borrow %4 : $@moveOnly Klass
2618+
%9 = begin_borrow %3 : $@moveOnly Klass
2619+
%10 = copy_value %9 : $@moveOnly Klass
2620+
%11 = moveonlywrapper_to_copyable [owned] %10 : $@moveOnly Klass
2621+
%12 = begin_borrow [lexical] %11 : $Klass
2622+
debug_value %12 : $Klass, let, name "x2"
2623+
end_borrow %9 : $@moveOnly Klass
2624+
%15 = function_ref @$s4test5KlassC11doSomethingyyF : $@convention(method) (@guaranteed Klass) -> ()
2625+
%16 = apply %15(%12) : $@convention(method) (@guaranteed Klass) -> ()
2626+
end_borrow %12 : $Klass
2627+
destroy_value %11 : $Klass
2628+
destroy_value %3 : $@moveOnly Klass
2629+
%20 = tuple ()
2630+
return %20 : $()
2631+
} // end sil function '$s4test11doSomethingyyAA5KlassCF'
2632+
2633+
When the move only checker runs upon this SIL, it will see that the
2634+
``moveonlywrapped_to_copyable [owned]`` is a transitive consuming use of ``%2``
2635+
(ignoring copies) and that we have a non-consuming use later. So it will emit an
2636+
error that we have a guaranteed parameter that is being consumed. If we remove
2637+
the assignment to ``x2``, then after the move checker runs successfully we would
2638+
get the following SIL::
2639+
2640+
sil hidden [ossa] @doSomething : $@convention(thin) (@guaranteed Klass) -> () {
2641+
bb0(%0 : @noImplicitCopy $Klass):
2642+
%1 = copyable_to_moveonlywrapper [guaranteed] %0 : $Klass
2643+
debug_value %1 : $@moveOnly Klass, let, name "x", argno 1
2644+
%4 = begin_borrow %1 : $@moveOnly Klass
2645+
%5 = function_ref @$s4test5KlassC11doSomethingyyF : $@convention(method) (@guaranteed Klass) -> ()
2646+
%6 = moveonlywrapper_to_copyable [guaranteed] %4 : $@moveOnly Klass
2647+
%7 = apply %5(%6) : $@convention(method) (@guaranteed Klass) -> ()
2648+
end_borrow %4 : $@moveOnly Klass
2649+
%20 = tuple ()
2650+
return %20 : $()
2651+
} // end sil function '$s4test11doSomethingyyAA5KlassCF'
2652+
2653+
yielding SIL without any copies just as we wanted.
2654+
2655+
At the ABI level, ``@moveOnly`` does not exist and thus never shows up in a
2656+
SILFunctionType's parameters. Instead, we represent it only in the body of
2657+
functions and on a function's internal SILArgument representation. This is since
2658+
move only wrapped is intended to be a local power tool for controlling lifetimes
2659+
rather than a viral type level annotation that would constrain the type system.
2660+
2661+
As mentioned above trivial move only wrapped types are actually
2662+
non-trivial. This is because in SIL ownership is tied directly to
2663+
non-trivialness so unless we did that we could not track ownership
2664+
accurately. This is loss of triviality is not an issue for most of the pipeline
2665+
since we eliminate all move only wrapper types for trivial types during the
2666+
guaranteed optimizations after we have run various ownership checkers but before
2667+
we have run diagnostics for trivial types (e.x.: DiagnosticConstantPropagation).
2668+
2669+
As an example in practice, consider the following Swift::
2670+
2671+
func doSomethingWithInt(@_noImplicitCopy _ x: Int) -> Int {
2672+
x + x
2673+
}
2674+
2675+
Today this codegens to the following Swift::
2676+
2677+
sil hidden [ossa] @doSomethingWithInt : $@convention(thin) (Int) -> Int {
2678+
bb0(%0 : @noImplicitCopy $Int):
2679+
%1 = copyable_to_moveonlywrapper [owned] %0 : $Int
2680+
%2 = move_value [lexical] %1 : $@moveOnly Int
2681+
%3 = mark_must_check [no_implicit_copy] %2 : $@moveOnly Int
2682+
%5 = begin_borrow %3 : $@moveOnly Int
2683+
%6 = begin_borrow %3 : $@moveOnly Int
2684+
%7 = function_ref @addIntegers : $@convention(method) (Int, Int Int.Type) -> Int
2685+
%8 = moveonlywrapper_to_copyable [guaranteed] %5 : $@moveOnly Int
2686+
%9 = moveonlywrapper_to_copyable [guaranteed] %6 : $@moveOnly Int
2687+
%10 = apply %7(%8, %9) : $@convention(method) (Int, Int) -> Int
2688+
end_borrow %6 : $@moveOnly Int
2689+
end_borrow %5 : $@moveOnly Int
2690+
destroy_value %3 : $@moveOnly Int
2691+
return %10 : $Int
2692+
}
2693+
2694+
once the checker has run, this becomes::
2695+
2696+
sil hidden [ossa] @doSomethingWithInt : $@convention(thin) (Int) -> Int {
2697+
bb0(%0 : @noImplicitCopy $Int):
2698+
%1 = copyable_to_moveonlywrapper [owned] %0 : $Int
2699+
%2 = move_value [lexical] %1 : $@moveOnly Int
2700+
%5 = begin_borrow %2 : $@moveOnly Int
2701+
%6 = begin_borrow %2 : $@moveOnly Int
2702+
%7 = function_ref @addIntegers : $@convention(method) (Int, Int Int.Type) -> Int
2703+
%8 = moveonlywrapper_to_copyable [guaranteed] %5 : $@moveOnly Int
2704+
%9 = moveonlywrapper_to_copyable [guaranteed] %6 : $@moveOnly Int
2705+
%10 = apply %7(%8, %9) : $@convention(method) (Int, Int) -> Int
2706+
end_borrow %6 : $@moveOnly Int
2707+
end_borrow %5 : $@moveOnly Int
2708+
return %10 : $Int
2709+
}
2710+
2711+
and once we have run the move only wrapped type lowerer, we get the following
2712+
SIL::
2713+
2714+
sil hidden [ossa] @doSomethingWithInt : $@convention(thin) (Int) -> Int {
2715+
bb0(%0 : @noImplicitCopy $Int):
2716+
%1 = function_ref @addIntegers : $@convention(method) (Int, Int) -> Int
2717+
%2 = apply %1(%0, %0) : $@convention(method) (Int, Int) -> Int
2718+
return %2 : $Int
2719+
}
2720+
2721+
exactly what we wanted in the end.
2722+
2723+
If we are given an owned argument or a let binding, we use a similar
2724+
approach. Consider the following Swift::
2725+
2726+
func doSomethingWithKlass(_ x: Klass) -> Klass {
2727+
@_noImplicitCopy let value = x
2728+
let value2 = value
2729+
return value
2730+
}
2731+
2732+
A hypothetical SILGen for this code is as follows::
2733+
2734+
sil hidden [ossa] @$s4test20doSomethingWithKlassyAA0E0CADF : $@convention(thin) (@guaranteed Klass) -> @owned Klass {
2735+
bb0(%0 : @guaranteed $Klass):
2736+
debug_value %0 : $Klass, let, name "x", argno 1
2737+
%3 = begin_borrow [lexical] %0 : $Klass
2738+
%4 = copy_value %3 : $Klass
2739+
%5 = copyable_to_moveonlywrapper [owned] %4 : $Klass
2740+
%6 = mark_must_check [no_implicit_copy] %5 : $@moveOnly Klass
2741+
debug_value %6 : $@moveOnly Klass, let, name "value"
2742+
%8 = begin_borrow %6 : $@moveOnly Klass
2743+
%9 = copy_value %8 : $@moveOnly Klass
2744+
%10 = moveonlywrapper_to_copyable [owned] %9 : $@moveOnly Klass
2745+
%11 = begin_borrow [lexical] %10 : $Klass
2746+
debug_value %11 : $Klass, let, name "value2"
2747+
end_borrow %8 : $@moveOnly Klass
2748+
%14 = begin_borrow %6 : $@moveOnly Klass
2749+
%15 = copy_value %14 : $@moveOnly Klass
2750+
end_borrow %14 : $@moveOnly Klass
2751+
end_borrow %11 : $Klass
2752+
destroy_value %10 : $Klass
2753+
destroy_value %6 : $@moveOnly Klass
2754+
end_borrow %3 : $Klass
2755+
%22 = moveonlywrapper_to_copyable [owned] %15 : $@moveOnly Klass
2756+
return %22 : $Klass
2757+
} // end sil function '$s4test20doSomethingWithKlassyAA0E0CADF'
2758+
2759+
Notice above how the ``moveonlywrapper_to_copyable [owned]`` is used to escape
2760+
the no implicit copy value. In fact, if one runs the following SILGen through
2761+
sil-opt, one will see that we actually have an ownership violation due to the
2762+
two uses of "value", one for initializing value2 and the other for the return
2763+
value.
2764+
25732765
Runtime Failure
25742766
---------------
25752767

@@ -7456,14 +7648,20 @@ mark_must_check
74567648
'[' sil-optimizer-analysis-marker ']'
74577649

74587650
sil-optimizer-analysis-marker ::= 'no_implicit_copy'
7651+
::= 'no_copy'
74597652

74607653
A canary value inserted by a SIL generating frontend to signal to the move
74617654
checker to check a specific value. Valid only in Raw SIL. The relevant checkers
74627655
should remove the `mark_must_check`_ instruction after successfully running the
74637656
relevant diagnostic. The idea here is that instead of needing to introduce
7464-
multiple "flaging" instructions for the optimizer, we can just reuse this one
7657+
multiple "flagging" instructions for the optimizer, we can just reuse this one
74657658
instruction by varying the kind.
74667659

7660+
If the sil optimizer analysis marker is ``no_implicit_copy`` then the move
7661+
checker is told to check that the result of this instruction is consumed at most
7662+
once. If the marker is ``no_copy``, then the move checker will validate that the
7663+
result of this instruction is never consumed.
7664+
74677665
No Implicit Copy and No Escape Value Instructions
74687666
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74697667

include/swift/AST/DiagnosticsSIL.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,8 +725,10 @@ ERROR(noimplicitcopy_used_on_generic_or_existential, none,
725725
"binding or a nominal type containing such typed things", ())
726726

727727
// move only checker diagnostics
728-
ERROR(sil_moveonlychecker_value_consumed_more_than_once, none,
728+
ERROR(sil_moveonlychecker_owned_value_consumed_more_than_once, none,
729729
"'%0' consumed more than once", (StringRef))
730+
ERROR(sil_moveonlychecker_guaranteed_value_consumed, none,
731+
"'%0' has guaranteed ownership but was consumed", (StringRef))
730732
NOTE(sil_moveonlychecker_consuming_use_here, none,
731733
"consuming use", ())
732734
ERROR(sil_moveonlychecker_not_understand_mark_move, none,

include/swift/SIL/SILBuilder.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,9 +1291,20 @@ class SILBuilder {
12911291
}
12921292

12931293
CopyableToMoveOnlyWrapperValueInst *
1294-
createCopyableToMoveOnlyWrapperValue(SILLocation loc, SILValue src) {
1294+
createOwnedCopyableToMoveOnlyWrapperValue(SILLocation loc, SILValue src) {
12951295
return insert(new (getModule()) CopyableToMoveOnlyWrapperValueInst(
1296-
getSILDebugLocation(loc), src));
1296+
getSILDebugLocation(loc), src,
1297+
CopyableToMoveOnlyWrapperValueInst::Owned));
1298+
}
1299+
1300+
CopyableToMoveOnlyWrapperValueInst *
1301+
createGuaranteedCopyableToMoveOnlyWrapperValue(SILLocation loc,
1302+
SILValue src) {
1303+
assert(!src->getType().isTrivial(*F) &&
1304+
"trivial types can only use the owned version of this API");
1305+
return insert(new (getModule()) CopyableToMoveOnlyWrapperValueInst(
1306+
getSILDebugLocation(loc), src,
1307+
CopyableToMoveOnlyWrapperValueInst::Guaranteed));
12971308
}
12981309

12991310
MoveOnlyWrapperToCopyableValueInst *

include/swift/SIL/SILCloner.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,11 +1788,18 @@ void SILCloner<ImplClass>::visitMoveOnlyWrapperToCopyableValueInst(
17881788

17891789
template <typename ImplClass>
17901790
void SILCloner<ImplClass>::visitCopyableToMoveOnlyWrapperValueInst(
1791-
CopyableToMoveOnlyWrapperValueInst *Inst) {
1792-
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
1793-
auto *MVI = getBuilder().createCopyableToMoveOnlyWrapperValue(
1794-
getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()));
1795-
recordClonedInstruction(Inst, MVI);
1791+
CopyableToMoveOnlyWrapperValueInst *inst) {
1792+
getBuilder().setCurrentDebugScope(getOpScope(inst->getDebugScope()));
1793+
CopyableToMoveOnlyWrapperValueInst *cvt;
1794+
if (inst->getOwnershipKind() == OwnershipKind::Owned) {
1795+
cvt = getBuilder().createOwnedCopyableToMoveOnlyWrapperValue(
1796+
getOpLocation(inst->getLoc()), getOpValue(inst->getOperand()));
1797+
} else {
1798+
assert(inst->getOwnershipKind() == OwnershipKind::Guaranteed);
1799+
cvt = getBuilder().createGuaranteedCopyableToMoveOnlyWrapperValue(
1800+
getOpLocation(inst->getLoc()), getOpValue(inst->getOperand()));
1801+
}
1802+
recordClonedInstruction(inst, cvt);
17961803
}
17971804

17981805
template <typename ImplClass>

include/swift/SIL/SILInstruction.h

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,7 @@ FirstArgOwnershipForwardingSingleValueInst::classof(SILInstructionKind kind) {
13151315
case SILInstructionKind::InitExistentialRefInst:
13161316
case SILInstructionKind::MarkDependenceInst:
13171317
case SILInstructionKind::MoveOnlyWrapperToCopyableValueInst:
1318+
case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
13181319
return true;
13191320
default:
13201321
return false;
@@ -7565,7 +7566,16 @@ class MarkMustCheckInst
75657566
public:
75667567
enum class CheckKind : unsigned {
75677568
Invalid = 0,
7569+
7570+
// A signal to the move only checker to perform no implicit copy checking on
7571+
// the result of this instruction. This implies that the result can be
7572+
// consumed at most once.
75687573
NoImplicitCopy,
7574+
7575+
// A signal to the move only checker ot perform no copy checking. This
7576+
// forces the result of this instruction owned value to never be consumed
7577+
// (still allowing for non-consuming uses of course).
7578+
NoCopy,
75697579
};
75707580

75717581
private:
@@ -7584,19 +7594,61 @@ class MarkMustCheckInst
75847594
public:
75857595
CheckKind getCheckKind() const { return kind; }
75867596

7587-
bool isNoImplicitCopy() const { return kind == CheckKind::NoImplicitCopy; }
7597+
bool hasMoveCheckerKind() const {
7598+
switch (kind) {
7599+
case CheckKind::Invalid:
7600+
return false;
7601+
case CheckKind::NoImplicitCopy:
7602+
case CheckKind::NoCopy:
7603+
return true;
7604+
}
7605+
}
75887606
};
75897607

7608+
/// Convert from a non-trivial copyable type to an `@moveOnly` wrapper type.
7609+
///
7610+
/// IMPORTANT: Unlike other forwarding instructions, the ownership of
7611+
/// copyable_to_moveonly is not decided by the operand passed in on
7612+
/// construction. Instead in SILBuilder one must select the specific type of
7613+
/// ownership one wishes by using the following APIs:
7614+
///
7615+
/// * SILBuilder::createOwnedCopyableToMoveOnlyWrapperValueInst
7616+
/// * SILBuilder::createGuaranteedCopyableToMoveOnlyWrapperInst
7617+
///
7618+
/// The reason why this instruction was designed in this manner is that a
7619+
/// frontend chooses the ownership form of this instruction based off of the
7620+
/// semantic place that the value is used. Specifically:
7621+
///
7622+
/// 1. When creating a moveOnly wrapped value for an owned argument or a value,
7623+
/// we use the owned variant.
7624+
///
7625+
/// 2. When creating a moveOnly wrapped value from a guaranteed argument, we use
7626+
/// the guaranteed variant.
75907627
class CopyableToMoveOnlyWrapperValueInst
75917628
: public UnaryInstructionBase<
75927629
SILInstructionKind::CopyableToMoveOnlyWrapperValueInst,
7593-
SingleValueInstruction> {
7630+
FirstArgOwnershipForwardingSingleValueInst> {
7631+
public:
7632+
enum InitialKind {
7633+
Guaranteed,
7634+
Owned,
7635+
};
7636+
7637+
private:
75947638
friend class SILBuilder;
75957639

7640+
InitialKind initialKind;
7641+
75967642
CopyableToMoveOnlyWrapperValueInst(SILDebugLocation DebugLoc,
7597-
SILValue operand)
7598-
: UnaryInstructionBase(DebugLoc, operand,
7599-
operand->getType().addingMoveOnlyWrapper()) {}
7643+
SILValue operand, InitialKind kind)
7644+
: UnaryInstructionBase(
7645+
DebugLoc, operand, operand->getType().addingMoveOnlyWrapper(),
7646+
kind == InitialKind::Guaranteed ? OwnershipKind::Guaranteed
7647+
: OwnershipKind::Owned),
7648+
initialKind(kind) {}
7649+
7650+
public:
7651+
InitialKind getInitialKind() const { return initialKind; }
76007652
};
76017653

76027654
/// Convert from an @moveOnly wrapper type to the underlying copyable type. Can
@@ -9824,6 +9876,8 @@ OwnershipForwardingMixin::get(SILInstruction *inst) {
98249876
return result;
98259877
if (auto *result = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(inst))
98269878
return result;
9879+
if (auto *result = dyn_cast<CopyableToMoveOnlyWrapperValueInst>(inst))
9880+
return result;
98279881
return nullptr;
98289882
}
98299883

include/swift/SIL/SILNodes.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,9 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction)
474474
SINGLE_VALUE_INST(MarkMustCheckInst, mark_must_check,
475475
SingleValueInstruction, None, DoesNotRelease)
476476
// Convert a $T to $@moveOnly T. This is the method that one uses to convert a
477-
// trivial value to a non-trivial move only value.
477+
// trivial value to a non-trivial move only value. Ownership is fixed at construction by
478+
// frontend to express specific semantics: guaranteed for guaranteed function arguments
479+
// and owned for assignment/owned function arguments.
478480
SINGLE_VALUE_INST(CopyableToMoveOnlyWrapperValueInst, copyable_to_moveonlywrapper,
479481
SingleValueInstruction, None, DoesNotRelease)
480482
// Convert a $@moveOnly T to $T. Ownership is fixed at construction by

0 commit comments

Comments
 (0)