Skip to content

Commit 1147897

Browse files
committed
[moveOnly] Implement a semi-generic _move function that can be used on non-generic, non-existential values.
This patch introduces a new stdlib function called _move: ```Swift @_alwaysEmitIntoClient @_transparent @_semantics("lifetimemanagement.move") public func _move<T>(_ value: __owned T) -> T { #if $ExperimentalMoveOnly Builtin.move(value) #else value #endif } ``` It is a first attempt at creating a "move" function for Swift, albeit a skleton one since we do not yet perform the "no use after move" analysis. But this at leasts gets the skeleton into place so we can built the analysis on top of it and churn tree in a manageable way. Thus in its current incarnation, all it does is take in an __owned +1 parameter and returns it after moving it through Builtin.move. Given that we want to use an OSSA based analysis for our "no use after move" analysis and we do not have opaque values yet, we can not supporting moving generic values since they are address only. This has stymied us in the past from creating this function. With the implementation in this PR via a bit of cleverness, we are now able to support this as a generic function over all concrete types by being a little clever. The trick is that when we transparent inline _move (to get the builtin), we perform one level of specialization causing the inlined Builtin.move to be of a loadable type. If after transparent inlining, we inline builtin "move" into a context where it is still address only, we emit a diagnostic telling the user that they applied move to a generic or existential and that this is not yet supported. The reason why we are taking this approach is that we wish to use this to implement a new (as yet unwritten) diagnostic pass that verifies that _move (even for non-trivial copyable values) ends the lifetime of the value. This will ensure that one can write the following code to reliably end the lifetime of a let binding in Swift: ```Swift let x = Klass() let _ = _move(x) // hypotheticalUse(x) ``` Without the diagnostic pass, if one were to write another hypothetical use of x after the _move, the compiler would copy x to at least hypotheticalUse(x) meaning the lifetime of x would not end at the _move, =><=. So to implement this diagnostic pass, we want to use the OSSA infrastructure and that only works on objects! So how do we square this circle: by taking advantage of the mandatory SIL optimzier pipeline! Specifically we take advantage of the following: 1. Mandatory Inlining and Predictable Dead Allocation Elimination run before any of the move only diagnostic passes that we run. 2. Mandatory Inlining is able to specialize a callee a single level when it inlines code. One can take advantage of this to even at -Onone to monomorphosize code. and then note that _move is such a simple function that predictable dead allocation elimination is able to without issue eliminate the extra alloc_stack that appear in the caller after inlining without issue. So we (as the tests show) get SIL that for concrete types looks exactly like we just had run a move_value for that specific type as an object since we promote away the stores/loads in favor of object operations when we eliminate the allocation. In order to prevent any issue with this being used in a context where multiple specializations may occur, I made the inliner emit a diagnostic if it inlines _move into a function that applies it to an address only value. The diagnostic is emitted at the source location where the function call occurs so it is easy to find, e.x.: ``` func addressOnlyMove<T>(t: T) -> T { _move(t) // expected-error {{move() used on a generic or existential value}} } moveonly_builtin_generic_failure.swift:12:5: error: move() used on a generic or existential value _move(t) ^ ``` To eliminate any potential ABI impact, if someone calls _move in a way that causes it to be used in a context where the transparent inliner will not inline it, I taught IRGen that Builtin.move is equivalent to a take from src -> dst and marked _move as always emit into client (AEIC). I also took advantage of the feature flag I added in the previous commit in order to prevent any cond_fails from exposing Builtin.move in the stdlib. If one does not pass in the flag -enable-experimental-move-only then the function just returns the value without calling Builtin.move, so we are safe. rdar://83957028
1 parent 44bd180 commit 1147897

17 files changed

+195
-31
lines changed

include/swift/AST/Builtins.def

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,6 @@ BUILTIN_SIL_OPERATION(WithUnsafeThrowingContinuation, "withUnsafeThrowingContinu
515515
/// Force the current task to be rescheduled on the specified actor.
516516
BUILTIN_SIL_OPERATION(HopToActor, "hopToActor", None)
517517

518-
/// Generate a move_value instruction to convert a T to a @_moveOnly T.
519-
BUILTIN_SIL_OPERATION(Move, "move", Special)
520-
521518
#undef BUILTIN_SIL_OPERATION
522519

523520
// BUILTIN_RUNTIME_CALL - A call into a runtime function.
@@ -780,6 +777,19 @@ BUILTIN_MISC_OPERATION(CreateTaskGroup,
780777
BUILTIN_MISC_OPERATION(DestroyTaskGroup,
781778
"destroyTaskGroup", "", Special)
782779

780+
781+
/// A builtin that can only be called from a transparent generic function. Takes
782+
/// two operands, the first operand the result address, the second operand the
783+
/// input address. Transforms into load [take] + move_value + store [init] when
784+
/// transparently inlined into a caller that has the generic of the callee
785+
/// specialized into a loadable type. If the transparent inlining does not
786+
/// specialize the type (due to being inlined into a non-generic context, the
787+
/// SILVerifier will abort).
788+
///
789+
/// Illegal to call except for in Swift._move in the stdlib. This is enforced by
790+
/// the SILVerifier.
791+
BUILTIN_MISC_OPERATION(Move, "move", "", Special)
792+
783793
// BUILTIN_MISC_OPERATION_WITH_SILGEN - Miscellaneous operations that are
784794
// specially emitted during SIL generation.
785795
//

include/swift/AST/DiagnosticsSIL.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,5 +686,9 @@ NOTE(capturepromotion_concurrentcapture_capturinguse_here, none,
686686
NOTE(capturepromotion_variable_defined_here,none,
687687
"variable defined here", ())
688688

689+
// move operator used on generic or evalue
690+
ERROR(move_operator_used_on_generic_or_existential_value, none,
691+
"move() used on a generic or existential value", ())
692+
689693
#define UNDEFINE_DIAGNOSTIC_MACROS
690694
#include "DefineDiagnosticMacros.h"

include/swift/AST/SemanticAttrs.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,7 @@ SEMANTICS_ATTR(FORCE_EMIT_OPT_REMARK_PREFIX, "optremark")
113113
/// for testing purposes.
114114
SEMANTICS_ATTR(OBJC_FORBID_ASSOCIATED_OBJECTS, "objc.forbidAssociatedObjects")
115115

116+
SEMANTICS_ATTR(LIFETIMEMANAGEMENT_MOVE, "lifetimemanagement.move")
117+
116118
#undef SEMANTICS_ATTR
117119

include/swift/SIL/SILType.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,9 @@ class SILType {
620620
/// Get the SIL token type.
621621
static SILType getSILTokenType(const ASTContext &C);
622622

623+
/// Return '()'
624+
static SILType getEmptyTupleType(const ASTContext &C);
625+
623626
//
624627
// Utilities for treating SILType as a pointer-like type.
625628
//

lib/AST/Builtins.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,7 @@ static ValueDecl *getDestroyArrayOperation(ASTContext &ctx, Identifier id) {
856856

857857
static ValueDecl *getMoveOperation(ASTContext &ctx, Identifier id) {
858858
return getBuiltinFunction(ctx, id, _thin, _generics(_unrestricted),
859-
_parameters(_owned(_typeparam(0))),
860-
_moveOnly(_typeparam(0)));
859+
_parameters(_owned(_typeparam(0))), _typeparam(0));
861860
}
862861

863862
static ValueDecl *getTransferArrayOperation(ASTContext &ctx, Identifier id) {

lib/IRGen/GenBuiltin.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,5 +1308,16 @@ if (Builtin.ID == BuiltinValueKind::id) { \
13081308
return;
13091309
}
13101310

1311+
if (Builtin.ID == BuiltinValueKind::Move) {
1312+
auto input = args.claimNext();
1313+
auto result = args.claimNext();
1314+
SILType addrTy = argTypes[0];
1315+
const TypeInfo &addrTI = IGF.getTypeInfo(addrTy);
1316+
Address inputAttr = addrTI.getAddressForPointer(input);
1317+
Address resultAttr = addrTI.getAddressForPointer(result);
1318+
addrTI.initializeWithTake(IGF, resultAttr, inputAttr, addrTy, false);
1319+
return;
1320+
}
1321+
13111322
llvm_unreachable("IRGen unimplemented for this builtin!");
13121323
}

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, PoundAssert)
775775
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GlobalStringTablePointer)
776776
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TypePtrAuthDiscriminator)
777777
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, IntInstrprofIncrement)
778+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, Move)
778779
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, StartAsyncLet)
779780
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, EndAsyncLet)
780781
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, StartAsyncLetWithLocalBuffer)

lib/SIL/IR/SILType.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ SILType SILType::getOptionalType(SILType type) {
9494
return getPrimitiveType(CanType(optType), type.getCategory());
9595
}
9696

97+
SILType SILType::getEmptyTupleType(const ASTContext &C) {
98+
return getPrimitiveObjectType(C.TheEmptyTupleType);
99+
}
100+
97101
SILType SILType::getSILTokenType(const ASTContext &C) {
98102
return getPrimitiveObjectType(C.TheSILTokenType);
99103
}

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, StartAsyncLetWithLocalBuffer)
556556
CONSTANT_OWNERSHIP_BUILTIN(None, EndAsyncLetLifetime)
557557
CONSTANT_OWNERSHIP_BUILTIN(None, CreateTaskGroup)
558558
CONSTANT_OWNERSHIP_BUILTIN(None, DestroyTaskGroup)
559+
CONSTANT_OWNERSHIP_BUILTIN(None, Move)
559560

560561
#undef CONSTANT_OWNERSHIP_BUILTIN
561562

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,12 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
21352135
visitor(&builtin->getAllOperands()[2]);
21362136
return;
21372137

2138+
// This consumes its second parameter (the arg) and takes/places that value
2139+
// into the first parameter (the result).
2140+
case BuiltinValueKind::Move:
2141+
visitor(&builtin->getAllOperands()[1]);
2142+
return;
2143+
21382144
// These consume values out of their second operand.
21392145
case BuiltinValueKind::ResumeNonThrowingContinuationReturning:
21402146
case BuiltinValueKind::ResumeThrowingContinuationReturning:

0 commit comments

Comments
 (0)