Skip to content

Commit c4fcb4d

Browse files
committed
Introduce an experimental @_rawLayout attribute.
This attribute can be attached to a noncopyable struct to specify that its storage is raw, meaning the type definition is (with some limitations) able to do as it pleases with the storage. This provides a basis for implementing types for things like atomics, locks, and data structures that use inline storage to store conditionally-initialized values. The example in `test/Prototypes/UnfairLock.swift` demonstrates the use of a raw layout type to wrap Darwin's `os_unfair_lock` APIs, allowing a lock value to be stored inside of classes or other types without needing a separate allocation, and using the borrow model to enforce safe access to lock-guarded storage.
1 parent c27e2c7 commit c4fcb4d

33 files changed

+1116
-45
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,106 @@ Fully bypasses access control, allowing access to private declarations
832832
in the imported module. The imported module needs to be compiled with
833833
`-Xfrontend -enable-private-imports` for this to work.
834834

835+
## `@_rawLayout(...)`
836+
837+
Specifies the declared type consists of raw storage. The type must be
838+
noncopyable, and declare no stored properties.
839+
Raw storage is left almost entirely unmanaged by the language, and so
840+
can be used as storage for data structures with nonstandard access patterns
841+
including atomics and many kinds of locks such as `os_unfair_lock` on Darwin
842+
or `futex` on Linux, to replicate the behavior of things
843+
like C++'s `mutable` fields or Rust's `Cell<T>` type which allow for mutation
844+
in typically immutable contexts, and/or to provide inline storage for data
845+
structures that may be conditionally initialized, such as a "small vector"
846+
which stores up to N elements in inline storage but spills into heap allocation
847+
past a threshold.
848+
849+
Programmers can safely make the following assumptions about
850+
the memory of the annotated type:
851+
852+
- A value has a **stable address** until it is either consumed or moved.
853+
No value of any type in Swift can ever be moved while it is being borrowed or
854+
mutated, so the address of `self` within a `borrowing` or `mutating` method,
855+
or more generally the address of a `borrowing` or `inout` parameter to any
856+
function, cannot change within the function body. Values that appear in a
857+
global variable or class stored property can never be moved, and can only be
858+
consumed by the deallocation of the containing object instance, so
859+
effectively has a stable address for their entire lifetime.
860+
- A value's memory **may be read and mutated at any time** independent of
861+
formal accesses. In particular, pointers into the storage may be "escaped"
862+
outside of scopes where the address is statically guaranteed to be stable, and
863+
those pointers may be used freely for as long as the storage dynamically
864+
isn't consumed or moved. It becomes the programmer's responsibility in this
865+
case to ensure that reads and writes to the storage do not race across
866+
threads, writes don't overlap with reads or writes coming from the same
867+
thread, and that the pointer is not used after the value is moved or consumed.
868+
- When the value is moved, a bitwise copy of its memory is performed to the new
869+
address of the value in its new owner. As currently implemented, raw storage
870+
types are not suitable for storing values which are not bitwise-movable, such
871+
as nontrivial C++ types, Objective-C weak references, and data structures
872+
such as `pthread_mutex_t` which are implemented in C as always requiring a
873+
fixed address.
874+
875+
Using the `@_rawLayout` attribute will suppress the annotated type from
876+
being implicitly `Sendable`. If the type is safe to access across threads, it
877+
may be declared to conform to `@unchecked Sendable`, with the usual level
878+
of programmer-assumed responsibility that involves. This generally means that
879+
any mutations must be done atomically or with a lock guard, and if the storage
880+
is ever mutated, then any reads of potentially-mutated state within the storage
881+
must also be atomic or lock-guarded, because the storage may be accessed
882+
simultaneously by multiple threads.
883+
884+
A non-Sendable type's memory will be confined to accesses from a single thread
885+
or task; however, since most mutating operations in Swift still expect
886+
exclusivity while executing, a programmer must ensure that overlapping
887+
mutations cannot occur from aliasing, recursion, reentrancy, signal handlers, or
888+
other potential sources of overlapping access within the same thread.
889+
890+
The parameters to the attribute specify the layout of the type. The following
891+
forms are currently accepted:
892+
893+
- `@_rawLayout(size: N, alignment: M)` specifies the type's size and alignment
894+
in bytes.
895+
- `@_rawLayout(like: T)` specifies the type's size and alignment should be
896+
equal to the type `T`'s.
897+
- `@_rawLayout(likeArrayOf: T, count: N)` specifies the type's size should be
898+
`MemoryLayout<T>.stride * N` and alignment should match `T`'s, like an
899+
array of N contiguous elements of `T` in memory.
900+
901+
A notable difference between `@_rawLayout(like: T)` and
902+
`@_rawLayout(likeArrayOf: T, count: 1)` is that the latter will pad out the
903+
size of the raw storage to include the full stride of the single element.
904+
This ensures that the buffer can be safely used with bulk array operations
905+
despite containing only a single element. `@_rawLayout(like: T)` by contrast
906+
will exactly match the size and stride of the original type `T`, allowing for
907+
other values to be stored in the tail padding when the raw layout type appears
908+
in a larger aggregate.
909+
910+
```
911+
// struct Weird has size 5, stride 8, alignment 4
912+
struct Weird {
913+
var x: Int32
914+
var y: Int8
915+
}
916+
917+
// struct LikeWeird has size 5, stride 8, alignment 4
918+
@_rawLayout(like: Weird)
919+
struct LikeWeird { }
920+
921+
// struct LikeWeirdSingleArray has **size 8**, stride 8, alignment 4
922+
@_rawLayout(likeArrayOf: Weird, count: 1)
923+
struct LikeWeirdSingleArray { }
924+
```
925+
926+
Although the `like:` and `likeArrayOf:count:` forms will produce raw storage
927+
with the size and alignment of another type, the memory is **not** implicitly
928+
*bound* to that type, as *bound* is defined by `UnsafePointer` and
929+
`UnsafeMutablePointer`. The memory can be accessed as raw memory
930+
if it is never explicitly bound using a typed pointer method like
931+
`withMemoryRebound(to:)` or `bindMemory(to:)`. However, if the raw memory is
932+
bound, it must only be used with compatible typed memory accesses for as long
933+
as the binding is active.
934+
835935
## `@_section("section_name")`
836936

837937
Places a global variable or a top-level function into a section of the object

include/swift/AST/Attr.h

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2485,6 +2485,107 @@ class MacroRoleAttr final
24852485
}
24862486
};
24872487

2488+
/// Specifies the raw storage used by a type.
2489+
class RawLayoutAttr final : public DeclAttribute {
2490+
/// The element type to share size and alignment with, if any.
2491+
TypeRepr *LikeType;
2492+
/// The number of elements in an array to share stride and alignment with,
2493+
/// or zero if no such size was specified. If `LikeType` is null, this is
2494+
/// the size in bytes of the raw storage.
2495+
unsigned SizeOrCount;
2496+
/// If `LikeType` is null, the alignment in bytes to use for the raw storage.
2497+
unsigned Alignment;
2498+
/// The resolved like type.
2499+
Type ResolvedLikeType = Type();
2500+
2501+
public:
2502+
/// Construct a `@_rawLayout(like: T)` attribute.
2503+
RawLayoutAttr(TypeRepr *LikeType,
2504+
SourceLoc AtLoc,
2505+
SourceRange Range)
2506+
: DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false),
2507+
LikeType(LikeType), SizeOrCount(0), Alignment(~0u)
2508+
{}
2509+
2510+
/// Construct a `@_rawLayout(likeArrayOf: T, count: N)` attribute.
2511+
RawLayoutAttr(TypeRepr *LikeType, unsigned Count,
2512+
SourceLoc AtLoc,
2513+
SourceRange Range)
2514+
: DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false),
2515+
LikeType(LikeType), SizeOrCount(Count), Alignment(0)
2516+
{}
2517+
2518+
/// Construct a `@_rawLayout(size: N, alignment: M)` attribute.
2519+
RawLayoutAttr(unsigned Size, unsigned Alignment,
2520+
SourceLoc AtLoc,
2521+
SourceRange Range)
2522+
: DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false),
2523+
LikeType(nullptr), SizeOrCount(Size), Alignment(Alignment)
2524+
{}
2525+
2526+
/// Return the type whose single-element layout the attribute type should get
2527+
/// its layout from. Returns null if the attribute specifies an array or manual
2528+
/// layout.
2529+
TypeRepr *getScalarLikeType() const {
2530+
if (!LikeType)
2531+
return nullptr;
2532+
if (Alignment != ~0u)
2533+
return nullptr;
2534+
return LikeType;
2535+
}
2536+
2537+
/// Return the type whose array layout the attribute type should get its
2538+
/// layout from, along with the size of that array. Returns None if the
2539+
/// attribute specifies scalar or manual layout.
2540+
llvm::Optional<std::pair<TypeRepr *, unsigned>> getArrayLikeTypeAndCount() const {
2541+
if (!LikeType)
2542+
return llvm::None;
2543+
if (Alignment == ~0u)
2544+
return llvm::None;
2545+
return std::make_pair(LikeType, SizeOrCount);
2546+
}
2547+
2548+
/// Return the size and alignment of the attributed type. Returns
2549+
/// None if the attribute specifies layout like some other type.
2550+
llvm::Optional<std::pair<unsigned, unsigned>> getSizeAndAlignment() const {
2551+
if (LikeType)
2552+
return llvm::None;
2553+
return std::make_pair(SizeOrCount, Alignment);
2554+
}
2555+
2556+
/// Set the resolved type.
2557+
void setResolvedLikeType(Type ty) {
2558+
assert(LikeType && "doesn't have a like type");
2559+
ResolvedLikeType = ty;
2560+
}
2561+
2562+
/// Return the type whose single-element layout the attribute type should get
2563+
/// its layout from. Returns None if the attribute specifies an array or manual
2564+
/// layout.
2565+
llvm::Optional<Type> getResolvedScalarLikeType() const {
2566+
if (!LikeType)
2567+
return llvm::None;
2568+
if (Alignment != ~0u)
2569+
return llvm::None;
2570+
return ResolvedLikeType;
2571+
}
2572+
2573+
/// Return the type whose array layout the attribute type should get its
2574+
/// layout from, along with the size of that array. Returns None if the
2575+
/// attribute specifies scalar or manual layout.
2576+
llvm::Optional<std::pair<Type, unsigned>> getResolvedArrayLikeTypeAndCount() const {
2577+
if (!LikeType)
2578+
return llvm::None;
2579+
if (Alignment == ~0u)
2580+
return llvm::None;
2581+
return std::make_pair(ResolvedLikeType, SizeOrCount);
2582+
}
2583+
2584+
static bool classof(const DeclAttribute *DA) {
2585+
return DA->getKind() == DAK_RawLayout;
2586+
}
2587+
};
2588+
24882589
/// Predicate used to filter MatchingAttributeRange.
24892590
template <typename ATTR, bool AllowInvalid> struct ToAttributeKind {
24902591
ToAttributeKind() {}

include/swift/AST/DiagnosticsIRGen.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ ERROR(alignment_less_than_natural,none,
4949
ERROR(alignment_more_than_maximum,none,
5050
"@_alignment cannot increase alignment above maximum alignment of %0",
5151
(unsigned))
52+
53+
ERROR(raw_layout_dynamic_type_layout_unsupported,none,
54+
"@_rawLayout is not yet supported for layouts dependent on generic types", ())
5255

5356
ERROR(temporary_allocation_size_negative,none,
5457
"allocation capacity must be greater than or equal to zero", ())

include/swift/AST/DiagnosticsParse.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,6 +1846,17 @@ ERROR(documentation_attr_metadata_expected_text,none,
18461846
ERROR(documentation_attr_duplicate_metadata,none,
18471847
"cannot give more than one metadata argument to the same item", ())
18481848

1849+
ERROR(attr_rawlayout_expected_label,none,
1850+
"expected %0 argument to @_rawLayout attribute", (StringRef))
1851+
ERROR(attr_rawlayout_expected_integer_size,none,
1852+
"expected integer literal size in @_rawLayout attribute", ())
1853+
ERROR(attr_rawlayout_expected_integer_alignment,none,
1854+
"expected integer literal alignment in @_rawLayout attribute", ())
1855+
ERROR(attr_rawlayout_expected_integer_count,none,
1856+
"expected integer literal count in @_rawLayout attribute", ())
1857+
ERROR(attr_rawlayout_expected_params,none,
1858+
"expected %1 argument after %0 argument in @_rawLayout attribute", (StringRef, StringRef))
1859+
18491860
//------------------------------------------------------------------------------
18501861
// MARK: Generics parsing diagnostics
18511862
//------------------------------------------------------------------------------

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3811,6 +3811,15 @@ ERROR(attr_MainType_without_main,none,
38113811
#undef SELECT_APPLICATION_MAIN
38123812
#undef SELECT_APPLICATION_DELEGATE
38133813

3814+
ERROR(attr_rawlayout_experimental,none,
3815+
"the @_rawLayout attribute is experimental", ())
3816+
ERROR(attr_rawlayout_cannot_be_copyable,none,
3817+
"type with @_rawLayout cannot be copied and must be declared ~Copyable", ())
3818+
ERROR(attr_rawlayout_cannot_have_stored_properties,none,
3819+
"type with @_rawLayout cannot have stored properties", ())
3820+
ERROR(attr_rawlayout_cannot_have_alignment_attr,none,
3821+
"type with @_rawLayout cannot also have an @_alignment attribute", ())
3822+
38143823
// lazy
38153824
ERROR(lazy_not_on_let,none,
38163825
"'lazy' cannot be used on a let", ())
@@ -5342,6 +5351,8 @@ ERROR(concurrent_value_inherit,none,
53425351
(bool, DeclName))
53435352
ERROR(non_sendable_type,none,
53445353
"type %0 does not conform to the 'Sendable' protocol", (Type))
5354+
ERROR(sendable_raw_storage,none,
5355+
"struct %0 with @_rawLayout does not conform to the 'Sendable' protocol", (DeclName))
53455356

53465357
WARNING(unchecked_conformance_not_special,none,
53475358
"@unchecked conformance to %0 has no meaning", (Type))

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ EXPERIMENTAL_FEATURE(DeferredSendableChecking, false)
224224
/// "playground transform" is enabled.
225225
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)
226226

227+
/// Enable the `@_rawLayout` attribute.
228+
EXPERIMENTAL_FEATURE(RawLayout, true)
229+
227230
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
228231
#undef EXPERIMENTAL_FEATURE
229232
#undef UPCOMING_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3439,6 +3439,10 @@ static bool usesFeatureBuiltinModule(Decl *decl) {
34393439
return false;
34403440
}
34413441

3442+
static bool usesFeatureRawLayout(Decl *decl) {
3443+
return decl->getAttrs().hasAttribute<RawLayoutAttr>();
3444+
}
3445+
34423446
static bool hasParameterPacks(Decl *decl) {
34433447
if (auto genericContext = decl->getAsGenericContext()) {
34443448
auto sig = genericContext->getGenericSignature();

lib/AST/Attr.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,28 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
14621462
Printer << ")";
14631463
break;
14641464
}
1465+
1466+
case DAK_RawLayout: {
1467+
auto *attr = cast<RawLayoutAttr>(this);
1468+
Printer.printAttrName("@_rawLayout");
1469+
Printer << "(";
1470+
1471+
if (auto sizeAndAlign = attr->getSizeAndAlignment()) {
1472+
Printer << "size: " << sizeAndAlign->first
1473+
<< ", alignment: " << sizeAndAlign->second;
1474+
} else if (auto type = attr->getScalarLikeType()) {
1475+
Printer << "like: ";
1476+
type->print(Printer, Options);
1477+
} else if (auto array = attr->getArrayLikeTypeAndCount()) {
1478+
Printer << "likeArrayOf: ";
1479+
array->first->print(Printer, Options);
1480+
Printer << ", count: " << array->second;
1481+
} else {
1482+
llvm_unreachable("unhandled @_rawLayout form");
1483+
}
1484+
Printer << ")";
1485+
break;
1486+
}
14651487

14661488
case DAK_Count:
14671489
llvm_unreachable("exceed declaration attribute kinds");
@@ -1653,6 +1675,8 @@ StringRef DeclAttribute::getAttrName() const {
16531675
case MacroSyntax::Attached:
16541676
return "attached";
16551677
}
1678+
case DAK_RawLayout:
1679+
return "_rawLayout";
16561680
}
16571681
llvm_unreachable("bad DeclAttrKind");
16581682
}

lib/IRGen/DebugTypeInfo.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ DebugTypeInfo::DebugTypeInfo(swift::Type Ty, llvm::Type *FragmentStorageTy,
3737
assert(Align.getValue() != 0);
3838
}
3939

40-
/// Determine whether this type has a custom @_alignment attribute.
40+
/// Determine whether this type has an attribute specifying a custom alignment.
4141
static bool hasDefaultAlignment(swift::Type Ty) {
4242
if (auto CanTy = Ty->getCanonicalType())
4343
if (auto *TyDecl = CanTy.getNominalOrBoundGenericNominal())
44-
if (TyDecl->getAttrs().getAttribute<AlignmentAttr>())
44+
if (TyDecl->getAttrs().getAttribute<AlignmentAttr>()
45+
|| TyDecl->getAttrs().getAttribute<RawLayoutAttr>())
4546
return false;
4647
return true;
4748
}

lib/IRGen/GenReflection.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,13 +1674,15 @@ void IRGenModule::emitFieldDescriptor(const NominalTypeDecl *D) {
16741674
needsFieldDescriptor = false;
16751675
}
16761676

1677-
// If the type has custom @_alignment, emit a fixed record with the
1678-
// alignment since remote mirrors will need to treat the type as opaque.
1677+
// If the type has custom @_alignment, @_rawLayout, or other manual layout
1678+
// attributes, emit a fixed record with the size and alignment since the
1679+
// remote mirrors will need to treat the type as opaque.
16791680
//
16801681
// Note that we go on to also emit a field descriptor in this case,
16811682
// since in-process reflection only cares about the types of the fields
16821683
// and does not independently re-derive the layout.
1683-
if (D->getAttrs().hasAttribute<AlignmentAttr>()) {
1684+
if (D->getAttrs().hasAttribute<AlignmentAttr>()
1685+
|| D->getAttrs().hasAttribute<RawLayoutAttr>()) {
16841686
auto &TI = getTypeInfoForUnlowered(T);
16851687
if (isa<FixedTypeInfo>(TI)) {
16861688
needsOpaqueDescriptor = true;

lib/IRGen/GenType.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2831,9 +2831,10 @@ SILType irgen::getSingletonAggregateFieldType(IRGenModule &IGM, SILType t,
28312831
|| structDecl->hasClangNode())
28322832
return SILType();
28332833

2834-
// A single-field struct with custom alignment has different layout from its
2834+
// A single-field struct with custom layout has different layout from its
28352835
// field.
2836-
if (structDecl->getAttrs().hasAttribute<AlignmentAttr>())
2836+
if (structDecl->getAttrs().hasAttribute<AlignmentAttr>()
2837+
|| structDecl->getAttrs().hasAttribute<RawLayoutAttr>())
28372838
return SILType();
28382839

28392840
// If there's only one stored property, we have the layout of its field.

0 commit comments

Comments
 (0)