Skip to content

Commit 332d23e

Browse files
authored
Merge pull request #74115 from Azoy/60-show-me-those-moves
[6.0] [IRGen] Add option for raw layout to move as its like type
2 parents b15adc5 + f6a85b7 commit 332d23e

File tree

16 files changed

+244
-14
lines changed

16 files changed

+244
-14
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -955,12 +955,16 @@ the memory of the annotated type:
955955
threads, writes don't overlap with reads or writes coming from the same
956956
thread, and that the pointer is not used after the value is moved or
957957
consumed.
958-
- When the value is moved, a bitwise copy of its memory is performed to the new
959-
address of the value in its new owner. As currently implemented, raw storage
960-
types are not suitable for storing values which are not bitwise-movable, such
961-
as nontrivial C++ types, Objective-C weak references, and data structures
962-
such as `pthread_mutex_t` which are implemented in C as always requiring a
963-
fixed address.
958+
- By default, when the value is moved a bitwise copy of its memory is performed
959+
to the new address of the value in its new owner. This makes it unsuitable to
960+
store not bitwise-movable types such as nontrivial C++ types, Objective-C weak
961+
references, and data structures such as `pthread_mutex_t` which are
962+
implemented in C as always requiring a fixed address. However, you can provide
963+
`movesAsLike` to the `like:` version of this attribute to enforce that moving
964+
the value will defer its move semantics to the type it's like. This makes it
965+
suitable for storing such values that are not bitwise-movable. Note that the
966+
raw storage for this variant must always be properly initialized after
967+
initialization because foreign moves will assume an initialized state.
964968

965969
Using the `@_rawLayout` attribute will suppress the annotated type from
966970
being implicitly `Sendable`. If the type is safe to access across threads, it
@@ -987,6 +991,11 @@ forms are currently accepted:
987991
- `@_rawLayout(likeArrayOf: T, count: N)` specifies the type's size should be
988992
`MemoryLayout<T>.stride * N` and alignment should match `T`'s, like an
989993
array of N contiguous elements of `T` in memory.
994+
- `@_rawLayout(like: T, movesAsLike)` specifies the type's size and alignment
995+
should be equal to the type `T`'s. It also guarantees that moving a value of
996+
this raw layout type will have the same move semantics as the type it's like.
997+
This is important for things like ObjC weak references and non-trivial move
998+
constructors in C++.
990999

9911000
A notable difference between `@_rawLayout(like: T)` and
9921001
`@_rawLayout(likeArrayOf: T, count: 1)` is that the latter will pad out the

include/swift/AST/Attr.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,17 +2533,21 @@ class RawLayoutAttr final : public DeclAttribute {
25332533
unsigned SizeOrCount;
25342534
/// If `LikeType` is null, the alignment in bytes to use for the raw storage.
25352535
unsigned Alignment;
2536+
/// If a value of this raw layout type should move like its `LikeType`.
2537+
bool MovesAsLike = false;
25362538
/// The resolved like type.
25372539
mutable Type CachedResolvedLikeType = Type();
25382540

25392541
friend class ResolveRawLayoutLikeTypeRequest;
25402542

25412543
public:
25422544
/// Construct a `@_rawLayout(like: T)` attribute.
2543-
RawLayoutAttr(TypeRepr *LikeType, SourceLoc AtLoc, SourceRange Range)
2545+
RawLayoutAttr(TypeRepr *LikeType, bool movesAsLike, SourceLoc AtLoc,
2546+
SourceRange Range)
25442547
: DeclAttribute(DeclAttrKind::RawLayout, AtLoc, Range,
25452548
/*implicit*/ false),
2546-
LikeType(LikeType), SizeOrCount(0), Alignment(~0u) {}
2549+
LikeType(LikeType), SizeOrCount(0), Alignment(~0u),
2550+
MovesAsLike(movesAsLike) {}
25472551

25482552
/// Construct a `@_rawLayout(likeArrayOf: T, count: N)` attribute.
25492553
RawLayoutAttr(TypeRepr *LikeType, unsigned Count, SourceLoc AtLoc,
@@ -2615,6 +2619,11 @@ class RawLayoutAttr final : public DeclAttribute {
26152619
return std::make_pair(getResolvedLikeType(sd), SizeOrCount);
26162620
}
26172621

2622+
/// Whether a value of this raw layout should move like its `LikeType`.
2623+
bool shouldMoveAsLikeType() const {
2624+
return MovesAsLike;
2625+
}
2626+
26182627
static bool classof(const DeclAttribute *DA) {
26192628
return DA->getKind() == DeclAttrKind::RawLayout;
26202629
}

include/swift/SIL/SILType.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,17 @@ class SILType {
889889
/// Returns true if this SILType is a differentiable type.
890890
bool isDifferentiable(SILModule &M) const;
891891

892+
/// Returns the @_rawLayout attribute on this type if it has one.
893+
RawLayoutAttr *getRawLayout() const {
894+
auto sd = getStructOrBoundGenericStruct();
895+
896+
if (!sd) {
897+
return nullptr;
898+
}
899+
900+
return sd->getAttrs().getAttribute<RawLayoutAttr>();
901+
}
902+
892903
/// If this is a SILBoxType, return getSILBoxFieldType(). Otherwise, return
893904
/// SILType().
894905
///

lib/IRGen/GenRecord.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,25 @@ class RecordTypeInfoImpl : public Base,
195195
return emitAssignWithTakeCall(IGF, T, dest, src);
196196
}
197197

198+
if (auto rawLayout = T.getRawLayout()) {
199+
// Because we have a rawlayout attribute, we know this has to be a struct.
200+
auto structDecl = T.getStructOrBoundGenericStruct();
201+
202+
if (auto likeType = rawLayout->getResolvedScalarLikeType(structDecl)) {
203+
if (rawLayout->shouldMoveAsLikeType()) {
204+
auto astT = T.getASTType();
205+
auto subs = astT->getContextSubstitutionMap(IGF.IGM.getSwiftModule(),
206+
structDecl);
207+
auto loweredLikeType = IGF.IGM.getLoweredType(likeType->subst(subs));
208+
auto &likeTypeInfo = IGF.IGM.getTypeInfo(loweredLikeType);
209+
210+
likeTypeInfo.assignWithTake(IGF, dest, src, loweredLikeType,
211+
isOutlined);
212+
return;
213+
}
214+
}
215+
}
216+
198217
if (isOutlined || T.hasParameterizedExistential()) {
199218
auto offsets = asImpl().getNonFixedOffsets(IGF, T);
200219
for (auto &field : getFields()) {
@@ -257,6 +276,24 @@ class RecordTypeInfoImpl : public Base,
257276
return emitInitializeWithTakeCall(IGF, T, dest, src);
258277
}
259278

279+
if (auto rawLayout = T.getRawLayout()) {
280+
// Because we have a rawlayout attribute, we know this has to be a struct.
281+
auto structDecl = T.getStructOrBoundGenericStruct();
282+
283+
if (auto likeType = rawLayout->getResolvedScalarLikeType(structDecl)) {
284+
if (rawLayout->shouldMoveAsLikeType()) {
285+
auto astT = T.getASTType();
286+
auto subs = astT->getContextSubstitutionMap(IGF.IGM.getSwiftModule(),
287+
structDecl);
288+
auto loweredLikeType = IGF.IGM.getLoweredType(likeType->subst(subs));
289+
auto &likeTypeInfo = IGF.IGM.getTypeInfo(loweredLikeType);
290+
291+
likeTypeInfo.initializeWithTake(IGF, dest, src, loweredLikeType,
292+
isOutlined);
293+
}
294+
}
295+
}
296+
260297
if (isOutlined || T.hasParameterizedExistential()) {
261298
auto offsets = asImpl().getNonFixedOffsets(IGF, T);
262299
for (auto &field : getFields()) {

lib/IRGen/StructLayout.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,26 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional<CanType> type,
119119
MinimumAlign = likeFixedType->getFixedAlignment();
120120
IsFixedLayout = true;
121121
IsKnownAlwaysFixedSize = IsFixedSize;
122+
123+
// @_rawLayout(like: T) has an optional `movesAsLike` which enforces that
124+
// a value of this raw layout type should have the same move semantics
125+
// as the like its like.
126+
if (rawLayout->shouldMoveAsLikeType()) {
127+
IsKnownTriviallyDestroyable = likeFixedType->isTriviallyDestroyable(ResilienceExpansion::Maximal);
128+
IsKnownBitwiseTakable = likeFixedType->isBitwiseTakable(ResilienceExpansion::Maximal);
129+
}
122130
} else {
123131
MinimumSize = Size(0);
124132
MinimumAlign = Alignment(1);
125133
IsFixedLayout = false;
126134
IsKnownAlwaysFixedSize = IsNotFixedSize;
135+
136+
// We don't know our like type, so assume we're not known to be bitwise
137+
// takable.
138+
if (rawLayout->shouldMoveAsLikeType()) {
139+
IsKnownTriviallyDestroyable = IsNotTriviallyDestroyable;
140+
IsKnownBitwiseTakable = IsNotBitwiseTakable;
141+
}
127142
}
128143
} else if (auto likeArray = rawLayout->getResolvedArrayLikeTypeAndCount(sd)) {
129144
auto elementType = likeArray->first;

lib/Parse/ParseDecl.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3989,14 +3989,24 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
39893989
if (likeType.isNull()) {
39903990
return makeParserSuccess();
39913991
}
3992+
3993+
bool movesAsLike = false;
3994+
3995+
// @_rawLayout(like: T, movesAsLike)
3996+
if (consumeIf(tok::comma) && Tok.isAny(tok::identifier) &&
3997+
!parseSpecificIdentifier("movesAsLike",
3998+
diag::attr_rawlayout_expected_label, "movesAsLike")) {
3999+
movesAsLike = true;
4000+
}
4001+
39924002
SourceLoc rParenLoc;
39934003
if (!consumeIf(tok::r_paren, rParenLoc)) {
39944004
diagnose(Tok.getLoc(), diag::attr_expected_rparen,
39954005
AttrName, /*isModifier*/false);
39964006
return makeParserSuccess();
39974007
}
39984008

3999-
attr = new (Context) RawLayoutAttr(likeType.get(),
4009+
attr = new (Context) RawLayoutAttr(likeType.get(), movesAsLike,
40004010
AtLoc, SourceRange(Loc, rParenLoc));
40014011
} else if (firstLabel.is("likeArrayOf")) {
40024012
// @_rawLayout(likeArrayOf: T, count: N)

lib/Serialization/Deserialization.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6291,8 +6291,9 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
62916291
TypeID typeID;
62926292
uint32_t rawSize;
62936293
uint8_t rawAlign;
6294+
bool movesAsLike;
62946295
serialization::decls_block::RawLayoutDeclAttrLayout::
6295-
readRecord(scratch, isImplicit, typeID, rawSize, rawAlign);
6296+
readRecord(scratch, isImplicit, typeID, rawSize, rawAlign, movesAsLike);
62966297

62976298
if (typeID) {
62986299
auto type = MF.getTypeChecked(typeID);
@@ -6302,6 +6303,7 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
63026303
auto typeRepr = new (ctx) FixedTypeRepr(type.get(), SourceLoc());
63036304
if (rawAlign == 0) {
63046305
Attr = new (ctx) RawLayoutAttr(typeRepr,
6306+
movesAsLike,
63056307
SourceLoc(),
63066308
SourceRange());
63076309
break;

lib/Serialization/ModuleFormat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2154,7 +2154,8 @@ namespace decls_block {
21542154
BCFixed<1>, // implicit
21552155
TypeIDField, // like type
21562156
BCVBR<32>, // size
2157-
BCVBR<8> // alignment
2157+
BCVBR<8>, // alignment
2158+
BCFixed<1> // movesAsLike
21582159
>;
21592160

21602161
using SwiftNativeObjCRuntimeBaseDeclAttrLayout = BCRecordLayout<

lib/Serialization/Serialization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3305,7 +3305,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
33053305

33063306
RawLayoutDeclAttrLayout::emitRecord(
33073307
S.Out, S.ScratchRecord, abbrCode, attr->isImplicit(),
3308-
typeID, rawSize, rawAlign);
3308+
typeID, rawSize, rawAlign, attr->shouldMoveAsLikeType());
33093309
}
33103310
}
33113311
}

test/IRGen/Inputs/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@ module PointerAuth {
3939
module CFBridgedType {
4040
header "CFBridgedType.h"
4141
}
42+
43+
module RawLayoutCXX {
44+
header "raw_layout_cxx.h"
45+
}

test/IRGen/Inputs/raw_layout_cxx.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class NonBitwiseTakableCXXType {
2+
public:
3+
bool state = false;
4+
5+
NonBitwiseTakableCXXType() {}
6+
7+
NonBitwiseTakableCXXType(const NonBitwiseTakableCXXType &other) {
8+
state = false;
9+
}
10+
11+
NonBitwiseTakableCXXType(NonBitwiseTakableCXXType &&other) {
12+
state = !other.state;
13+
}
14+
15+
~NonBitwiseTakableCXXType() {}
16+
};

test/IRGen/raw_layout.swift

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %{python} %utils/chex.py < %s > %t/raw_layout.sil
3-
// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -emit-ir -disable-availability-checking %t/raw_layout.sil | %FileCheck %t/raw_layout.sil --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize
3+
// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -emit-ir -disable-availability-checking -I %S/Inputs -cxx-interoperability-mode=upcoming-swift %t/raw_layout.sil | %FileCheck %t/raw_layout.sil --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize
44

55
import Builtin
66
import Swift
7+
import RawLayoutCXX
78

89
// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}4LockVWV" = {{.*}} %swift.vwtable
910
// size
@@ -143,6 +144,52 @@ struct BadBuffer: ~Copyable {
143144
let buffer: SmallVectorOf3<Int64?>
144145
}
145146

147+
// Raw Layout types that move like their like type
148+
149+
// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}19CellThatMovesAsLikeVWV" = {{.*}} %swift.vwtable
150+
// initializeWithTake
151+
// CHECK-SAME: , ptr @"$s10raw_layout19CellThatMovesAsLikeVwtk
152+
// assignWithTake
153+
// CHECK-SAME: , ptr @"$s10raw_layout19CellThatMovesAsLikeVwta
154+
// size
155+
// CHECK-SAME: , {{i64|i32}} 0
156+
// stride
157+
// CHECK-SAME: , {{i64|i32}} 0
158+
// flags: alignment 0, incomplete
159+
// CHECK-SAME: , <i32 0x400000>
160+
@_rawLayout(like: T, movesAsLike)
161+
struct CellThatMovesAsLike<T>: ~Copyable {}
162+
163+
// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}18ConcreteMoveAsLikeVWV" = {{.*}} %swift.vwtable
164+
// initializeWithTake
165+
// CHECK-SAME: , ptr @"$s10raw_layout18ConcreteMoveAsLikeVwtk
166+
// assignWithTake
167+
// CHECK-SAME: , ptr @"$s10raw_layout18ConcreteMoveAsLikeVwta
168+
// size
169+
// CHECK-SAME: , {{i64|i32}} 1
170+
// stride
171+
// CHECK-SAME: , {{i64|i32}} 1
172+
// flags: not copyable, not bitwise takable, not pod, not inline
173+
// CHECK-SAME: , <i32 0x930000>
174+
struct ConcreteMoveAsLike: ~Copyable {
175+
let cell: CellThatMovesAsLike<NonBitwiseTakableCXXType>
176+
}
177+
178+
// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}21ConcreteIntMoveAsLikeVWV" = {{.*}} %swift.vwtable
179+
// initializeWithTake
180+
// CHECK-SAME: , ptr @__swift_memcpy4_4
181+
// assignWithTake
182+
// CHECK-SAME: , ptr @__swift_memcpy4_4
183+
// size
184+
// CHECK-SAME: , {{i64|i32}} 4
185+
// stride
186+
// CHECK-SAME: , {{i64|i32}} 4
187+
// flags: alignment 3, not copyable
188+
// CHECK-SAME: , <i32 0x800003>
189+
struct ConcreteIntMoveAsLike: ~Copyable {
190+
let cell: CellThatMovesAsLike<Int32>
191+
}
192+
146193
sil @use_lock : $@convention(thin) (@in_guaranteed Lock) -> () {
147194
entry(%L: $*Lock):
148195
return undef : $()
@@ -193,3 +240,33 @@ entry(%0 : $*Cell<T>):
193240

194241
// CHECK-LABEL: define {{.*}} swiftcc %swift.metadata_response @"$s{{[A-Za-z0-9_]*}}14SmallVectorBufVMr"(ptr %"SmallVectorBuf<T>", ptr {{.*}}, ptr {{.*}})
195242
// CHECK: call void @swift_initRawStructMetadata(ptr %"SmallVectorBuf<T>", {{i64|i32}} 0, ptr {{%.*}}, {{i64|i32}} 8)
243+
244+
// Ensure that 'movesAsLike' is correctly calling the underlying type's move constructor
245+
246+
// CellThatMovesAsLike<T> initializeWithTake
247+
248+
// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout19CellThatMovesAsLikeVwtk"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %"CellThatMovesAsLike<T>")
249+
// CHECK: [[T_ADDR:%.*]] = getelementptr inbounds ptr, ptr %"CellThatMovesAsLike<T>", {{i64|i32}} 2
250+
// CHECK-NEXT: [[T:%.*]] = load ptr, ptr [[T_ADDR]]
251+
// CHECK: {{%.*}} = call ptr %InitializeWithTake(ptr {{.*}} %dest, ptr {{.*}} %src, ptr [[T]])
252+
253+
// CellThatMovesAsLike<T> assignWithTake
254+
255+
// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout19CellThatMovesAsLikeVwta"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %"CellThatMovesAsLike<T>")
256+
// CHECK: [[T_ADDR:%.*]] = getelementptr inbounds ptr, ptr %"CellThatMovesAsLike<T>", {{i64|i32}} 2
257+
// CHECK-NEXT: [[T:%.*]] = load ptr, ptr [[T_ADDR]]
258+
// CHECK: {{%.*}} = call ptr %AssignWithTake(ptr {{.*}} %dest, ptr {{.*}} %src, ptr [[T]])
259+
260+
// ConcreteMoveAsLike initializeWithTake
261+
262+
// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout18ConcreteMoveAsLikeVwtk"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %ConcreteMoveAsLike)
263+
// CHECK: [[DEST_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %dest, i32 0, i32 0
264+
// CHECK: [[SRC_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %src, i32 0, i32 0
265+
// CHECK: {{invoke void|invoke ptr|call ptr}} @{{.*}}(ptr [[DEST_CELL]], ptr [[SRC_CELL]])
266+
267+
// ConcreteMoveAsLike assignWithTake
268+
269+
// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout18ConcreteMoveAsLikeVwta"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %ConcreteMoveAsLike)
270+
// CHECK: [[DEST_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %dest, i32 0, i32 0
271+
// CHECK: [[SRC_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %src, i32 0, i32 0
272+
// CHECK: {{invoke void|invoke ptr|call ptr}} @{{.*}}(ptr [[DEST_CELL]], ptr [[SRC_CELL]])

test/Serialization/Inputs/module.modulemap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
module CLibrary {
33
header "CLibrary.h"
44
}
5+
6+
module RawLayoutCXX {
7+
header "raw_layout_cxx.h"
8+
}

test/Serialization/Inputs/raw_layout.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ extension Bool: Foo {}
55
public struct Fred<T: Foo>: ~Copyable {
66
public init() {}
77
}
8+
9+
@_rawLayout(like: T, movesAsLike)
10+
public struct CellThatMovesLike<T>: ~Copyable {}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class NonBitwiseTakableCXXType {
2+
public:
3+
bool state = false;
4+
5+
NonBitwiseTakableCXXType() {}
6+
7+
NonBitwiseTakableCXXType(const NonBitwiseTakableCXXType &other) {
8+
state = false;
9+
}
10+
11+
NonBitwiseTakableCXXType(NonBitwiseTakableCXXType &&other) {
12+
state = !other.state;
13+
}
14+
15+
~NonBitwiseTakableCXXType() {}
16+
};

0 commit comments

Comments
 (0)