Skip to content

[6.0] [IRGen] Add option for raw layout to move as its like type #74115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -955,12 +955,16 @@ the memory of the annotated type:
threads, writes don't overlap with reads or writes coming from the same
thread, and that the pointer is not used after the value is moved or
consumed.
- When the value is moved, a bitwise copy of its memory is performed to the new
address of the value in its new owner. As currently implemented, raw storage
types are not suitable for storing values which are not bitwise-movable, such
as nontrivial C++ types, Objective-C weak references, and data structures
such as `pthread_mutex_t` which are implemented in C as always requiring a
fixed address.
- By default, when the value is moved a bitwise copy of its memory is performed
to the new address of the value in its new owner. This makes it unsuitable to
store not bitwise-movable types such as nontrivial C++ types, Objective-C weak
references, and data structures such as `pthread_mutex_t` which are
implemented in C as always requiring a fixed address. However, you can provide
`movesAsLike` to the `like:` version of this attribute to enforce that moving
the value will defer its move semantics to the type it's like. This makes it
suitable for storing such values that are not bitwise-movable. Note that the
raw storage for this variant must always be properly initialized after
initialization because foreign moves will assume an initialized state.

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

A notable difference between `@_rawLayout(like: T)` and
`@_rawLayout(likeArrayOf: T, count: 1)` is that the latter will pad out the
Expand Down
13 changes: 11 additions & 2 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2533,17 +2533,21 @@ class RawLayoutAttr final : public DeclAttribute {
unsigned SizeOrCount;
/// If `LikeType` is null, the alignment in bytes to use for the raw storage.
unsigned Alignment;
/// If a value of this raw layout type should move like its `LikeType`.
bool MovesAsLike = false;
/// The resolved like type.
mutable Type CachedResolvedLikeType = Type();

friend class ResolveRawLayoutLikeTypeRequest;

public:
/// Construct a `@_rawLayout(like: T)` attribute.
RawLayoutAttr(TypeRepr *LikeType, SourceLoc AtLoc, SourceRange Range)
RawLayoutAttr(TypeRepr *LikeType, bool movesAsLike, SourceLoc AtLoc,
SourceRange Range)
: DeclAttribute(DeclAttrKind::RawLayout, AtLoc, Range,
/*implicit*/ false),
LikeType(LikeType), SizeOrCount(0), Alignment(~0u) {}
LikeType(LikeType), SizeOrCount(0), Alignment(~0u),
MovesAsLike(movesAsLike) {}

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

/// Whether a value of this raw layout should move like its `LikeType`.
bool shouldMoveAsLikeType() const {
return MovesAsLike;
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::RawLayout;
}
Expand Down
11 changes: 11 additions & 0 deletions include/swift/SIL/SILType.h
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,17 @@ class SILType {
/// Returns true if this SILType is a differentiable type.
bool isDifferentiable(SILModule &M) const;

/// Returns the @_rawLayout attribute on this type if it has one.
RawLayoutAttr *getRawLayout() const {
auto sd = getStructOrBoundGenericStruct();

if (!sd) {
return nullptr;
}

return sd->getAttrs().getAttribute<RawLayoutAttr>();
}

/// If this is a SILBoxType, return getSILBoxFieldType(). Otherwise, return
/// SILType().
///
Expand Down
37 changes: 37 additions & 0 deletions lib/IRGen/GenRecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,25 @@ class RecordTypeInfoImpl : public Base,
return emitAssignWithTakeCall(IGF, T, dest, src);
}

if (auto rawLayout = T.getRawLayout()) {
// Because we have a rawlayout attribute, we know this has to be a struct.
auto structDecl = T.getStructOrBoundGenericStruct();

if (auto likeType = rawLayout->getResolvedScalarLikeType(structDecl)) {
if (rawLayout->shouldMoveAsLikeType()) {
auto astT = T.getASTType();
auto subs = astT->getContextSubstitutionMap(IGF.IGM.getSwiftModule(),
structDecl);
auto loweredLikeType = IGF.IGM.getLoweredType(likeType->subst(subs));
auto &likeTypeInfo = IGF.IGM.getTypeInfo(loweredLikeType);

likeTypeInfo.assignWithTake(IGF, dest, src, loweredLikeType,
isOutlined);
return;
}
}
}

if (isOutlined || T.hasParameterizedExistential()) {
auto offsets = asImpl().getNonFixedOffsets(IGF, T);
for (auto &field : getFields()) {
Expand Down Expand Up @@ -257,6 +276,24 @@ class RecordTypeInfoImpl : public Base,
return emitInitializeWithTakeCall(IGF, T, dest, src);
}

if (auto rawLayout = T.getRawLayout()) {
// Because we have a rawlayout attribute, we know this has to be a struct.
auto structDecl = T.getStructOrBoundGenericStruct();

if (auto likeType = rawLayout->getResolvedScalarLikeType(structDecl)) {
if (rawLayout->shouldMoveAsLikeType()) {
auto astT = T.getASTType();
auto subs = astT->getContextSubstitutionMap(IGF.IGM.getSwiftModule(),
structDecl);
auto loweredLikeType = IGF.IGM.getLoweredType(likeType->subst(subs));
auto &likeTypeInfo = IGF.IGM.getTypeInfo(loweredLikeType);

likeTypeInfo.initializeWithTake(IGF, dest, src, loweredLikeType,
isOutlined);
}
}
}

if (isOutlined || T.hasParameterizedExistential()) {
auto offsets = asImpl().getNonFixedOffsets(IGF, T);
for (auto &field : getFields()) {
Expand Down
15 changes: 15 additions & 0 deletions lib/IRGen/StructLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,26 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional<CanType> type,
MinimumAlign = likeFixedType->getFixedAlignment();
IsFixedLayout = true;
IsKnownAlwaysFixedSize = IsFixedSize;

// @_rawLayout(like: T) has an optional `movesAsLike` which enforces that
// a value of this raw layout type should have the same move semantics
// as the like its like.
if (rawLayout->shouldMoveAsLikeType()) {
IsKnownTriviallyDestroyable = likeFixedType->isTriviallyDestroyable(ResilienceExpansion::Maximal);
IsKnownBitwiseTakable = likeFixedType->isBitwiseTakable(ResilienceExpansion::Maximal);
}
} else {
MinimumSize = Size(0);
MinimumAlign = Alignment(1);
IsFixedLayout = false;
IsKnownAlwaysFixedSize = IsNotFixedSize;

// We don't know our like type, so assume we're not known to be bitwise
// takable.
if (rawLayout->shouldMoveAsLikeType()) {
IsKnownTriviallyDestroyable = IsNotTriviallyDestroyable;
IsKnownBitwiseTakable = IsNotBitwiseTakable;
}
}
} else if (auto likeArray = rawLayout->getResolvedArrayLikeTypeAndCount(sd)) {
auto elementType = likeArray->first;
Expand Down
12 changes: 11 additions & 1 deletion lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3989,14 +3989,24 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
if (likeType.isNull()) {
return makeParserSuccess();
}

bool movesAsLike = false;

// @_rawLayout(like: T, movesAsLike)
if (consumeIf(tok::comma) && Tok.isAny(tok::identifier) &&
!parseSpecificIdentifier("movesAsLike",
diag::attr_rawlayout_expected_label, "movesAsLike")) {
movesAsLike = true;
}

SourceLoc rParenLoc;
if (!consumeIf(tok::r_paren, rParenLoc)) {
diagnose(Tok.getLoc(), diag::attr_expected_rparen,
AttrName, /*isModifier*/false);
return makeParserSuccess();
}

attr = new (Context) RawLayoutAttr(likeType.get(),
attr = new (Context) RawLayoutAttr(likeType.get(), movesAsLike,
AtLoc, SourceRange(Loc, rParenLoc));
} else if (firstLabel.is("likeArrayOf")) {
// @_rawLayout(likeArrayOf: T, count: N)
Expand Down
4 changes: 3 additions & 1 deletion lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6291,8 +6291,9 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
TypeID typeID;
uint32_t rawSize;
uint8_t rawAlign;
bool movesAsLike;
serialization::decls_block::RawLayoutDeclAttrLayout::
readRecord(scratch, isImplicit, typeID, rawSize, rawAlign);
readRecord(scratch, isImplicit, typeID, rawSize, rawAlign, movesAsLike);

if (typeID) {
auto type = MF.getTypeChecked(typeID);
Expand All @@ -6302,6 +6303,7 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
auto typeRepr = new (ctx) FixedTypeRepr(type.get(), SourceLoc());
if (rawAlign == 0) {
Attr = new (ctx) RawLayoutAttr(typeRepr,
movesAsLike,
SourceLoc(),
SourceRange());
break;
Expand Down
3 changes: 2 additions & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -2154,7 +2154,8 @@ namespace decls_block {
BCFixed<1>, // implicit
TypeIDField, // like type
BCVBR<32>, // size
BCVBR<8> // alignment
BCVBR<8>, // alignment
BCFixed<1> // movesAsLike
>;

using SwiftNativeObjCRuntimeBaseDeclAttrLayout = BCRecordLayout<
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3305,7 +3305,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {

RawLayoutDeclAttrLayout::emitRecord(
S.Out, S.ScratchRecord, abbrCode, attr->isImplicit(),
typeID, rawSize, rawAlign);
typeID, rawSize, rawAlign, attr->shouldMoveAsLikeType());
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions test/IRGen/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ module PointerAuth {
module CFBridgedType {
header "CFBridgedType.h"
}

module RawLayoutCXX {
header "raw_layout_cxx.h"
}
16 changes: 16 additions & 0 deletions test/IRGen/Inputs/raw_layout_cxx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class NonBitwiseTakableCXXType {
public:
bool state = false;

NonBitwiseTakableCXXType() {}

NonBitwiseTakableCXXType(const NonBitwiseTakableCXXType &other) {
state = false;
}

NonBitwiseTakableCXXType(NonBitwiseTakableCXXType &&other) {
state = !other.state;
}

~NonBitwiseTakableCXXType() {}
};
79 changes: 78 additions & 1 deletion test/IRGen/raw_layout.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: %{python} %utils/chex.py < %s > %t/raw_layout.sil
// 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
// 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

import Builtin
import Swift
import RawLayoutCXX

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

// Raw Layout types that move like their like type

// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}19CellThatMovesAsLikeVWV" = {{.*}} %swift.vwtable
// initializeWithTake
// CHECK-SAME: , ptr @"$s10raw_layout19CellThatMovesAsLikeVwtk
// assignWithTake
// CHECK-SAME: , ptr @"$s10raw_layout19CellThatMovesAsLikeVwta
// size
// CHECK-SAME: , {{i64|i32}} 0
// stride
// CHECK-SAME: , {{i64|i32}} 0
// flags: alignment 0, incomplete
// CHECK-SAME: , <i32 0x400000>
@_rawLayout(like: T, movesAsLike)
struct CellThatMovesAsLike<T>: ~Copyable {}

// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}18ConcreteMoveAsLikeVWV" = {{.*}} %swift.vwtable
// initializeWithTake
// CHECK-SAME: , ptr @"$s10raw_layout18ConcreteMoveAsLikeVwtk
// assignWithTake
// CHECK-SAME: , ptr @"$s10raw_layout18ConcreteMoveAsLikeVwta
// size
// CHECK-SAME: , {{i64|i32}} 1
// stride
// CHECK-SAME: , {{i64|i32}} 1
// flags: not copyable, not bitwise takable, not pod, not inline
// CHECK-SAME: , <i32 0x930000>
struct ConcreteMoveAsLike: ~Copyable {
let cell: CellThatMovesAsLike<NonBitwiseTakableCXXType>
}

// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}21ConcreteIntMoveAsLikeVWV" = {{.*}} %swift.vwtable
// initializeWithTake
// CHECK-SAME: , ptr @__swift_memcpy4_4
// assignWithTake
// CHECK-SAME: , ptr @__swift_memcpy4_4
// size
// CHECK-SAME: , {{i64|i32}} 4
// stride
// CHECK-SAME: , {{i64|i32}} 4
// flags: alignment 3, not copyable
// CHECK-SAME: , <i32 0x800003>
struct ConcreteIntMoveAsLike: ~Copyable {
let cell: CellThatMovesAsLike<Int32>
}

sil @use_lock : $@convention(thin) (@in_guaranteed Lock) -> () {
entry(%L: $*Lock):
return undef : $()
Expand Down Expand Up @@ -193,3 +240,33 @@ entry(%0 : $*Cell<T>):

// CHECK-LABEL: define {{.*}} swiftcc %swift.metadata_response @"$s{{[A-Za-z0-9_]*}}14SmallVectorBufVMr"(ptr %"SmallVectorBuf<T>", ptr {{.*}}, ptr {{.*}})
// CHECK: call void @swift_initRawStructMetadata(ptr %"SmallVectorBuf<T>", {{i64|i32}} 0, ptr {{%.*}}, {{i64|i32}} 8)

// Ensure that 'movesAsLike' is correctly calling the underlying type's move constructor

// CellThatMovesAsLike<T> initializeWithTake

// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout19CellThatMovesAsLikeVwtk"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %"CellThatMovesAsLike<T>")
// CHECK: [[T_ADDR:%.*]] = getelementptr inbounds ptr, ptr %"CellThatMovesAsLike<T>", {{i64|i32}} 2
// CHECK-NEXT: [[T:%.*]] = load ptr, ptr [[T_ADDR]]
// CHECK: {{%.*}} = call ptr %InitializeWithTake(ptr {{.*}} %dest, ptr {{.*}} %src, ptr [[T]])

// CellThatMovesAsLike<T> assignWithTake

// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout19CellThatMovesAsLikeVwta"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %"CellThatMovesAsLike<T>")
// CHECK: [[T_ADDR:%.*]] = getelementptr inbounds ptr, ptr %"CellThatMovesAsLike<T>", {{i64|i32}} 2
// CHECK-NEXT: [[T:%.*]] = load ptr, ptr [[T_ADDR]]
// CHECK: {{%.*}} = call ptr %AssignWithTake(ptr {{.*}} %dest, ptr {{.*}} %src, ptr [[T]])

// ConcreteMoveAsLike initializeWithTake

// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout18ConcreteMoveAsLikeVwtk"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %ConcreteMoveAsLike)
// CHECK: [[DEST_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %dest, i32 0, i32 0
// CHECK: [[SRC_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %src, i32 0, i32 0
// CHECK: {{invoke void|invoke ptr|call ptr}} @{{.*}}(ptr [[DEST_CELL]], ptr [[SRC_CELL]])

// ConcreteMoveAsLike assignWithTake

// CHECK-LABEL: define {{.*}} ptr @"$s10raw_layout18ConcreteMoveAsLikeVwta"(ptr {{.*}} %dest, ptr {{.*}} %src, ptr %ConcreteMoveAsLike)
// CHECK: [[DEST_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %dest, i32 0, i32 0
// CHECK: [[SRC_CELL:%.*]] = getelementptr inbounds %T10raw_layout18ConcreteMoveAsLikeV, ptr %src, i32 0, i32 0
// CHECK: {{invoke void|invoke ptr|call ptr}} @{{.*}}(ptr [[DEST_CELL]], ptr [[SRC_CELL]])
4 changes: 4 additions & 0 deletions test/Serialization/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
module CLibrary {
header "CLibrary.h"
}

module RawLayoutCXX {
header "raw_layout_cxx.h"
}
3 changes: 3 additions & 0 deletions test/Serialization/Inputs/raw_layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ extension Bool: Foo {}
public struct Fred<T: Foo>: ~Copyable {
public init() {}
}

@_rawLayout(like: T, movesAsLike)
public struct CellThatMovesLike<T>: ~Copyable {}
16 changes: 16 additions & 0 deletions test/Serialization/Inputs/raw_layout_cxx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class NonBitwiseTakableCXXType {
public:
bool state = false;

NonBitwiseTakableCXXType() {}

NonBitwiseTakableCXXType(const NonBitwiseTakableCXXType &other) {
state = false;
}

NonBitwiseTakableCXXType(NonBitwiseTakableCXXType &&other) {
state = !other.state;
}

~NonBitwiseTakableCXXType() {}
};
Loading