From aee071bf4eab420e8d63148f6cbadad03329994b Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Fri, 7 Jul 2023 15:04:06 -0700 Subject: [PATCH] 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. --- docs/ReferenceGuides/UnderscoredAttributes.md | 100 ++++++++++++ include/swift/AST/Attr.h | 101 ++++++++++++ include/swift/AST/DiagnosticsIRGen.def | 3 + include/swift/AST/DiagnosticsParse.def | 11 ++ include/swift/AST/DiagnosticsSema.def | 11 ++ include/swift/Basic/Features.def | 3 + lib/AST/ASTPrinter.cpp | 4 + lib/AST/Attr.cpp | 24 +++ lib/IRGen/DebugTypeInfo.cpp | 5 +- lib/IRGen/GenReflection.cpp | 8 +- lib/IRGen/GenType.cpp | 5 +- lib/IRGen/StructLayout.cpp | 150 ++++++++++++++---- lib/Parse/ParseDecl.cpp | 136 ++++++++++++++++ lib/SIL/IR/TypeLowering.cpp | 8 + lib/SILGen/SILGenConstructor.cpp | 5 +- .../Mandatory/MoveOnlyAddressCheckerUtils.cpp | 10 +- lib/Sema/ConstraintSystem.cpp | 16 +- lib/Sema/TypeCheckAttr.cpp | 71 +++++++++ lib/Sema/TypeCheckConcurrency.cpp | 22 +++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + lib/Serialization/Deserialization.cpp | 32 ++++ lib/Serialization/ModuleFormat.h | 8 + lib/Serialization/Serialization.cpp | 29 ++++ test/DebugInfo/raw_layout.swift | 9 ++ test/IRGen/raw_layout.swift | 94 +++++++++++ test/Prototypes/UnfairLock.swift | 120 ++++++++++++++ test/SILGen/raw_layout.swift | 26 +++ test/Sema/raw_layout.swift | 48 ++++++ test/Sema/raw_layout_correct.swift | 10 ++ test/Sema/raw_layout_parse.swift | 29 ++++ test/Sema/raw_layout_sendable.swift | 34 ++++ test/Serialization/attr-rawlayout.swift | 18 +++ utils/gyb_syntax_support/AttributeKinds.py | 6 + 33 files changed, 1112 insertions(+), 45 deletions(-) create mode 100644 test/DebugInfo/raw_layout.swift create mode 100644 test/IRGen/raw_layout.swift create mode 100644 test/Prototypes/UnfairLock.swift create mode 100644 test/SILGen/raw_layout.swift create mode 100644 test/Sema/raw_layout.swift create mode 100644 test/Sema/raw_layout_correct.swift create mode 100644 test/Sema/raw_layout_parse.swift create mode 100644 test/Sema/raw_layout_sendable.swift create mode 100644 test/Serialization/attr-rawlayout.swift diff --git a/docs/ReferenceGuides/UnderscoredAttributes.md b/docs/ReferenceGuides/UnderscoredAttributes.md index dc69dd63a3d8c..680b81436ab4a 100644 --- a/docs/ReferenceGuides/UnderscoredAttributes.md +++ b/docs/ReferenceGuides/UnderscoredAttributes.md @@ -832,6 +832,106 @@ Fully bypasses access control, allowing access to private declarations in the imported module. The imported module needs to be compiled with `-Xfrontend -enable-private-imports` for this to work. +## `@_rawLayout(...)` + +Specifies the declared type consists of raw storage. The type must be +noncopyable, and declare no stored properties. +Raw storage is left almost entirely unmanaged by the language, and so +can be used as storage for data structures with nonstandard access patterns +including atomics and many kinds of locks such as `os_unfair_lock` on Darwin +or `futex` on Linux, to replicate the behavior of things +like C++'s `mutable` fields or Rust's `Cell` type which allow for mutation +in typically immutable contexts, and/or to provide inline storage for data +structures that may be conditionally initialized, such as a "small vector" +which stores up to N elements in inline storage but spills into heap allocation +past a threshold. + +Programmers can safely make the following assumptions about +the memory of the annotated type: + +- A value has a **stable address** until it is either consumed or moved. + No value of any type in Swift can ever be moved while it is being borrowed or + mutated, so the address of `self` within a `borrowing` or `mutating` method, + or more generally the address of a `borrowing` or `inout` parameter to any + function, cannot change within the function body. Values that appear in a + global variable or class stored property can never be moved, and can only be + consumed by the deallocation of the containing object instance, so + effectively has a stable address for their entire lifetime. +- A value's memory **may be read and mutated at any time** independent of + formal accesses. In particular, pointers into the storage may be "escaped" + outside of scopes where the address is statically guaranteed to be stable, and + those pointers may be used freely for as long as the storage dynamically + isn't consumed or moved. It becomes the programmer's responsibility in this + case to ensure that reads and writes to the storage do not race across + 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. + +Using the `@_rawLayout` attribute will suppress the annotated type from +being implicitly `Sendable`. If the type is safe to access across threads, it +may be declared to conform to `@unchecked Sendable`, with the usual level +of programmer-assumed responsibility that involves. This generally means that +any mutations must be done atomically or with a lock guard, and if the storage +is ever mutated, then any reads of potentially-mutated state within the storage +must also be atomic or lock-guarded, because the storage may be accessed +simultaneously by multiple threads. + +A non-Sendable type's memory will be confined to accesses from a single thread +or task; however, since most mutating operations in Swift still expect +exclusivity while executing, a programmer must ensure that overlapping +mutations cannot occur from aliasing, recursion, reentrancy, signal handlers, or +other potential sources of overlapping access within the same thread. + +The parameters to the attribute specify the layout of the type. The following +forms are currently accepted: + +- `@_rawLayout(size: N, alignment: M)` specifies the type's size and alignment + in bytes. +- `@_rawLayout(like: T)` specifies the type's size and alignment should be + equal to the type `T`'s. +- `@_rawLayout(likeArrayOf: T, count: N)` specifies the type's size should be + `MemoryLayout.stride * N` and alignment should match `T`'s, like an + array of N contiguous elements of `T` in memory. + +A notable difference between `@_rawLayout(like: T)` and +`@_rawLayout(likeArrayOf: T, count: 1)` is that the latter will pad out the +size of the raw storage to include the full stride of the single element. +This ensures that the buffer can be safely used with bulk array operations +despite containing only a single element. `@_rawLayout(like: T)` by contrast +will exactly match the size and stride of the original type `T`, allowing for +other values to be stored in the tail padding when the raw layout type appears +in a larger aggregate. + +``` +// struct Weird has size 5, stride 8, alignment 4 +struct Weird { + var x: Int32 + var y: Int8 +} + +// struct LikeWeird has size 5, stride 8, alignment 4 +@_rawLayout(like: Weird) +struct LikeWeird { } + +// struct LikeWeirdSingleArray has **size 8**, stride 8, alignment 4 +@_rawLayout(likeArrayOf: Weird, count: 1) +struct LikeWeirdSingleArray { } +``` + +Although the `like:` and `likeArrayOf:count:` forms will produce raw storage +with the size and alignment of another type, the memory is **not** implicitly +*bound* to that type, as *bound* is defined by `UnsafePointer` and +`UnsafeMutablePointer`. The memory can be accessed as raw memory +if it is never explicitly bound using a typed pointer method like +`withMemoryRebound(to:)` or `bindMemory(to:)`. However, if the raw memory is +bound, it must only be used with compatible typed memory accesses for as long +as the binding is active. + ## `@_section("section_name")` Places a global variable or a top-level function into a section of the object diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 9129cd666c9d3..80738070420e5 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -2485,6 +2485,107 @@ class MacroRoleAttr final } }; +/// Specifies the raw storage used by a type. +class RawLayoutAttr final : public DeclAttribute { + /// The element type to share size and alignment with, if any. + TypeRepr *LikeType; + /// The number of elements in an array to share stride and alignment with, + /// or zero if no such size was specified. If `LikeType` is null, this is + /// the size in bytes of the raw storage. + unsigned SizeOrCount; + /// If `LikeType` is null, the alignment in bytes to use for the raw storage. + unsigned Alignment; + /// The resolved like type. + Type ResolvedLikeType = Type(); + +public: + /// Construct a `@_rawLayout(like: T)` attribute. + RawLayoutAttr(TypeRepr *LikeType, + SourceLoc AtLoc, + SourceRange Range) + : DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false), + LikeType(LikeType), SizeOrCount(0), Alignment(~0u) + {} + + /// Construct a `@_rawLayout(likeArrayOf: T, count: N)` attribute. + RawLayoutAttr(TypeRepr *LikeType, unsigned Count, + SourceLoc AtLoc, + SourceRange Range) + : DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false), + LikeType(LikeType), SizeOrCount(Count), Alignment(0) + {} + + /// Construct a `@_rawLayout(size: N, alignment: M)` attribute. + RawLayoutAttr(unsigned Size, unsigned Alignment, + SourceLoc AtLoc, + SourceRange Range) + : DeclAttribute(DAK_RawLayout, AtLoc, Range, /*implicit*/false), + LikeType(nullptr), SizeOrCount(Size), Alignment(Alignment) + {} + + /// Return the type whose single-element layout the attribute type should get + /// its layout from. Returns null if the attribute specifies an array or manual + /// layout. + TypeRepr *getScalarLikeType() const { + if (!LikeType) + return nullptr; + if (Alignment != ~0u) + return nullptr; + return LikeType; + } + + /// Return the type whose array layout the attribute type should get its + /// layout from, along with the size of that array. Returns None if the + /// attribute specifies scalar or manual layout. + llvm::Optional> getArrayLikeTypeAndCount() const { + if (!LikeType) + return llvm::None; + if (Alignment == ~0u) + return llvm::None; + return std::make_pair(LikeType, SizeOrCount); + } + + /// Return the size and alignment of the attributed type. Returns + /// None if the attribute specifies layout like some other type. + llvm::Optional> getSizeAndAlignment() const { + if (LikeType) + return llvm::None; + return std::make_pair(SizeOrCount, Alignment); + } + + /// Set the resolved type. + void setResolvedLikeType(Type ty) { + assert(LikeType && "doesn't have a like type"); + ResolvedLikeType = ty; + } + + /// Return the type whose single-element layout the attribute type should get + /// its layout from. Returns None if the attribute specifies an array or manual + /// layout. + llvm::Optional getResolvedScalarLikeType() const { + if (!LikeType) + return llvm::None; + if (Alignment != ~0u) + return llvm::None; + return ResolvedLikeType; + } + + /// Return the type whose array layout the attribute type should get its + /// layout from, along with the size of that array. Returns None if the + /// attribute specifies scalar or manual layout. + llvm::Optional> getResolvedArrayLikeTypeAndCount() const { + if (!LikeType) + return llvm::None; + if (Alignment == ~0u) + return llvm::None; + return std::make_pair(ResolvedLikeType, SizeOrCount); + } + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_RawLayout; + } +}; + /// Predicate used to filter MatchingAttributeRange. template struct ToAttributeKind { ToAttributeKind() {} diff --git a/include/swift/AST/DiagnosticsIRGen.def b/include/swift/AST/DiagnosticsIRGen.def index 3c088f73913f4..6886e2b09a9b3 100644 --- a/include/swift/AST/DiagnosticsIRGen.def +++ b/include/swift/AST/DiagnosticsIRGen.def @@ -49,6 +49,9 @@ ERROR(alignment_less_than_natural,none, ERROR(alignment_more_than_maximum,none, "@_alignment cannot increase alignment above maximum alignment of %0", (unsigned)) + +ERROR(raw_layout_dynamic_type_layout_unsupported,none, + "@_rawLayout is not yet supported for layouts dependent on generic types", ()) ERROR(temporary_allocation_size_negative,none, "allocation capacity must be greater than or equal to zero", ()) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index cf1edcb4d2889..b2587a6a89344 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1846,6 +1846,17 @@ ERROR(documentation_attr_metadata_expected_text,none, ERROR(documentation_attr_duplicate_metadata,none, "cannot give more than one metadata argument to the same item", ()) +ERROR(attr_rawlayout_expected_label,none, + "expected %0 argument to @_rawLayout attribute", (StringRef)) +ERROR(attr_rawlayout_expected_integer_size,none, + "expected integer literal size in @_rawLayout attribute", ()) +ERROR(attr_rawlayout_expected_integer_alignment,none, + "expected integer literal alignment in @_rawLayout attribute", ()) +ERROR(attr_rawlayout_expected_integer_count,none, + "expected integer literal count in @_rawLayout attribute", ()) +ERROR(attr_rawlayout_expected_params,none, + "expected %1 argument after %0 argument in @_rawLayout attribute", (StringRef, StringRef)) + //------------------------------------------------------------------------------ // MARK: Generics parsing diagnostics //------------------------------------------------------------------------------ diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c9058fa0632cf..84bacf2085bf9 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3811,6 +3811,15 @@ ERROR(attr_MainType_without_main,none, #undef SELECT_APPLICATION_MAIN #undef SELECT_APPLICATION_DELEGATE +ERROR(attr_rawlayout_experimental,none, + "the @_rawLayout attribute is experimental", ()) +ERROR(attr_rawlayout_cannot_be_copyable,none, + "type with @_rawLayout cannot be copied and must be declared ~Copyable", ()) +ERROR(attr_rawlayout_cannot_have_stored_properties,none, + "type with @_rawLayout cannot have stored properties", ()) +ERROR(attr_rawlayout_cannot_have_alignment_attr,none, + "type with @_rawLayout cannot also have an @_alignment attribute", ()) + // lazy ERROR(lazy_not_on_let,none, "'lazy' cannot be used on a let", ()) @@ -5342,6 +5351,8 @@ ERROR(concurrent_value_inherit,none, (bool, DeclName)) ERROR(non_sendable_type,none, "type %0 does not conform to the 'Sendable' protocol", (Type)) +ERROR(sendable_raw_storage,none, + "struct %0 with @_rawLayout does not conform to the 'Sendable' protocol", (DeclName)) WARNING(unchecked_conformance_not_special,none, "@unchecked conformance to %0 has no meaning", (Type)) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 8302b98b47792..4ecd18bdbda68 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -224,6 +224,9 @@ EXPERIMENTAL_FEATURE(DeferredSendableChecking, false) /// "playground transform" is enabled. EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true) +/// Enable the `@_rawLayout` attribute. +EXPERIMENTAL_FEATURE(RawLayout, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 102e4a546684b..819dbb3c1b678 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3439,6 +3439,10 @@ static bool usesFeatureBuiltinModule(Decl *decl) { return false; } +static bool usesFeatureRawLayout(Decl *decl) { + return decl->getAttrs().hasAttribute(); +} + static bool hasParameterPacks(Decl *decl) { if (auto genericContext = decl->getAsGenericContext()) { auto sig = genericContext->getGenericSignature(); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index d48ade2bfe4e4..3935fde27b9cc 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1462,6 +1462,28 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, Printer << ")"; break; } + + case DAK_RawLayout: { + auto *attr = cast(this); + Printer.printAttrName("@_rawLayout"); + Printer << "("; + + if (auto sizeAndAlign = attr->getSizeAndAlignment()) { + Printer << "size: " << sizeAndAlign->first + << ", alignment: " << sizeAndAlign->second; + } else if (auto type = attr->getScalarLikeType()) { + Printer << "like: "; + type->print(Printer, Options); + } else if (auto array = attr->getArrayLikeTypeAndCount()) { + Printer << "likeArrayOf: "; + array->first->print(Printer, Options); + Printer << ", count: " << array->second; + } else { + llvm_unreachable("unhandled @_rawLayout form"); + } + Printer << ")"; + break; + } case DAK_Count: llvm_unreachable("exceed declaration attribute kinds"); @@ -1653,6 +1675,8 @@ StringRef DeclAttribute::getAttrName() const { case MacroSyntax::Attached: return "attached"; } + case DAK_RawLayout: + return "_rawLayout"; } llvm_unreachable("bad DeclAttrKind"); } diff --git a/lib/IRGen/DebugTypeInfo.cpp b/lib/IRGen/DebugTypeInfo.cpp index 7f1fde92232d3..3965cd0effcbc 100644 --- a/lib/IRGen/DebugTypeInfo.cpp +++ b/lib/IRGen/DebugTypeInfo.cpp @@ -37,11 +37,12 @@ DebugTypeInfo::DebugTypeInfo(swift::Type Ty, llvm::Type *FragmentStorageTy, assert(Align.getValue() != 0); } -/// Determine whether this type has a custom @_alignment attribute. +/// Determine whether this type has an attribute specifying a custom alignment. static bool hasDefaultAlignment(swift::Type Ty) { if (auto CanTy = Ty->getCanonicalType()) if (auto *TyDecl = CanTy.getNominalOrBoundGenericNominal()) - if (TyDecl->getAttrs().getAttribute()) + if (TyDecl->getAttrs().getAttribute() + || TyDecl->getAttrs().getAttribute()) return false; return true; } diff --git a/lib/IRGen/GenReflection.cpp b/lib/IRGen/GenReflection.cpp index b240925e44da0..39cbdd1fb0870 100644 --- a/lib/IRGen/GenReflection.cpp +++ b/lib/IRGen/GenReflection.cpp @@ -1674,13 +1674,15 @@ void IRGenModule::emitFieldDescriptor(const NominalTypeDecl *D) { needsFieldDescriptor = false; } - // If the type has custom @_alignment, emit a fixed record with the - // alignment since remote mirrors will need to treat the type as opaque. + // If the type has custom @_alignment, @_rawLayout, or other manual layout + // attributes, emit a fixed record with the size and alignment since the + // remote mirrors will need to treat the type as opaque. // // Note that we go on to also emit a field descriptor in this case, // since in-process reflection only cares about the types of the fields // and does not independently re-derive the layout. - if (D->getAttrs().hasAttribute()) { + if (D->getAttrs().hasAttribute() + || D->getAttrs().hasAttribute()) { auto &TI = getTypeInfoForUnlowered(T); if (isa(TI)) { needsOpaqueDescriptor = true; diff --git a/lib/IRGen/GenType.cpp b/lib/IRGen/GenType.cpp index a4e121c9b0451..ab1c37a002298 100644 --- a/lib/IRGen/GenType.cpp +++ b/lib/IRGen/GenType.cpp @@ -2831,9 +2831,10 @@ SILType irgen::getSingletonAggregateFieldType(IRGenModule &IGM, SILType t, || structDecl->hasClangNode()) return SILType(); - // A single-field struct with custom alignment has different layout from its + // A single-field struct with custom layout has different layout from its // field. - if (structDecl->getAttrs().hasAttribute()) + if (structDecl->getAttrs().hasAttribute() + || structDecl->getAttrs().hasAttribute()) return SILType(); // If there's only one stored property, we have the layout of its field. diff --git a/lib/IRGen/StructLayout.cpp b/lib/IRGen/StructLayout.cpp index eb2230fb022c9..b0b19f22b2929 100644 --- a/lib/IRGen/StructLayout.cpp +++ b/lib/IRGen/StructLayout.cpp @@ -63,43 +63,137 @@ StructLayout::StructLayout(IRGenModule &IGM, builder.addHeapHeader(); } - bool nonEmpty = builder.addFields(Elements, strategy); - auto deinit = (decl && decl->getValueTypeDestructor()) ? IsNotTriviallyDestroyable : IsTriviallyDestroyable; auto copyable = (decl && decl->isMoveOnly()) ? IsNotCopyable : IsCopyable; - // Special-case: there's nothing to store. - // In this case, produce an opaque type; this tends to cause lovely - // assertions. - if (!nonEmpty) { - assert(!builder.empty() == requiresHeapHeader(layoutKind)); - MinimumAlign = Alignment(1); - MinimumSize = Size(0); - headerSize = builder.getHeaderSize(); - SpareBits.clear(); - IsFixedLayout = true; + // Handle a raw layout specification on a struct. + RawLayoutAttr *rawLayout = nullptr; + if (decl) { + rawLayout = decl->getAttrs().getAttribute(); + } + if (rawLayout) { IsKnownTriviallyDestroyable = deinit; IsKnownBitwiseTakable = IsBitwiseTakable; - IsKnownAlwaysFixedSize = IsFixedSize; + SpareBits.clear(); + assert(!copyable); IsKnownCopyable = copyable; - Ty = (typeToFill ? typeToFill : IGM.OpaqueTy); + assert(builder.getHeaderSize() == Size(0)); + headerSize = Size(0); + + auto &Diags = IGM.Context.Diags; + // Fixed size and alignment specified. + if (auto sizeAndAlign = rawLayout->getSizeAndAlignment()) { + auto size = Size(sizeAndAlign->first); + auto requestedAlignment = Alignment(sizeAndAlign->second); + MinimumAlign = IGM.getCappedAlignment(requestedAlignment); + if (requestedAlignment > MinimumAlign) { + Diags.diagnose(rawLayout->getLocation(), + diag::alignment_more_than_maximum, + MinimumAlign.getValue()); + } + + MinimumSize = size; + SpareBits.extendWithClearBits(MinimumSize.getValueInBits()); + IsFixedLayout = true; + IsKnownAlwaysFixedSize = IsFixedSize; + } else if (auto likeType = rawLayout->getResolvedScalarLikeType()) { + const TypeInfo &likeTypeInfo + = IGM.getTypeInfoForUnlowered(AbstractionPattern::getOpaque(), + *likeType); + + // Take layout attributes from the like type. + if (const FixedTypeInfo *likeFixedType = dyn_cast(&likeTypeInfo)) { + MinimumSize = likeFixedType->getFixedSize(); + SpareBits.extendWithClearBits(MinimumSize.getValueInBits()); + MinimumAlign = likeFixedType->getFixedAlignment(); + IsFixedLayout = true; + IsKnownAlwaysFixedSize = IsFixedSize; + } else { + MinimumSize = Size(0); + MinimumAlign = Alignment(1); + IsFixedLayout = false; + IsKnownAlwaysFixedSize = IsNotFixedSize; + } + } else if (auto likeArray = rawLayout->getResolvedArrayLikeTypeAndCount()) { + auto elementType = likeArray->first; + unsigned count = likeArray->second; + + const TypeInfo &likeTypeInfo + = IGM.getTypeInfoForUnlowered(AbstractionPattern::getOpaque(), + elementType); + + // Take layout attributes from the like type. + if (const FixedTypeInfo *likeFixedType = dyn_cast(&likeTypeInfo)) { + MinimumSize = likeFixedType->getFixedStride() * count; + SpareBits.extendWithClearBits(MinimumSize.getValueInBits()); + MinimumAlign = likeFixedType->getFixedAlignment(); + IsFixedLayout = true; + IsKnownAlwaysFixedSize = IsFixedSize; + } else { + MinimumSize = Size(0); + MinimumAlign = Alignment(1); + IsFixedLayout = false; + IsKnownAlwaysFixedSize = IsNotFixedSize; + } + } else { + llvm_unreachable("unhandled raw layout variant?"); + } + + // Set the LLVM struct type for a fixed layout according to the stride and + // alignment we determined. + if (IsKnownAlwaysFixedSize) { + auto eltTy = llvm::IntegerType::get(IGM.getLLVMContext(), 8); + auto bodyTy = llvm::ArrayType::get(eltTy, MinimumSize.getValue()); + if (typeToFill) { + typeToFill->setBody(bodyTy, /*packed*/ true); + Ty = typeToFill; + } else { + Ty = llvm::StructType::get(IGM.getLLVMContext(), bodyTy, /*packed*/ true); + } + } else { + Ty = (typeToFill ? typeToFill : IGM.OpaqueTy); + + // TODO: For types with dependent layout, the metadata initialization also + // has to be updated to account for the raw layout description. + Diags.diagnose(rawLayout->getLocation(), + diag::raw_layout_dynamic_type_layout_unsupported); + } } else { - MinimumAlign = builder.getAlignment(); - MinimumSize = builder.getSize(); - headerSize = builder.getHeaderSize(); - SpareBits = builder.getSpareBits(); - IsFixedLayout = builder.isFixedLayout(); - IsKnownTriviallyDestroyable = deinit & builder.isTriviallyDestroyable(); - IsKnownBitwiseTakable = builder.isBitwiseTakable(); - IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize(); - IsKnownCopyable = copyable & builder.isCopyable(); - if (typeToFill) { - builder.setAsBodyOfStruct(typeToFill); - Ty = typeToFill; + bool nonEmpty = builder.addFields(Elements, strategy); + + // Special-case: there's nothing to store. + // In this case, produce an opaque type; this tends to cause lovely + // assertions. + if (!nonEmpty) { + assert(!builder.empty() == requiresHeapHeader(layoutKind)); + MinimumAlign = Alignment(1); + MinimumSize = Size(0); + headerSize = builder.getHeaderSize(); + SpareBits.clear(); + IsFixedLayout = true; + IsKnownTriviallyDestroyable = deinit; + IsKnownBitwiseTakable = IsBitwiseTakable; + IsKnownAlwaysFixedSize = IsFixedSize; + IsKnownCopyable = copyable; + Ty = (typeToFill ? typeToFill : IGM.OpaqueTy); } else { - Ty = builder.getAsAnonStruct(); + MinimumAlign = builder.getAlignment(); + MinimumSize = builder.getSize(); + headerSize = builder.getHeaderSize(); + SpareBits = builder.getSpareBits(); + IsFixedLayout = builder.isFixedLayout(); + IsKnownTriviallyDestroyable = deinit & builder.isTriviallyDestroyable(); + IsKnownBitwiseTakable = builder.isBitwiseTakable(); + IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize(); + IsKnownCopyable = copyable & builder.isCopyable(); + if (typeToFill) { + builder.setAsBodyOfStruct(typeToFill); + Ty = typeToFill; + } else { + Ty = builder.getAsAnonStruct(); + } } } @@ -122,6 +216,8 @@ void irgen::applyLayoutAttributes(IRGenModule &IGM, auto &Diags = IGM.Context.Diags; if (auto alignment = decl->getAttrs().getAttribute()) { + assert(!decl->getAttrs().hasAttribute() + && "_alignment and _rawLayout not supported together"); auto value = alignment->getValue(); assert(value != 0 && ((value - 1) & value) == 0 && "alignment not a power of two!"); diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 0e2e77fdc9333..82e198d2da712 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -3656,6 +3656,142 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, return Attr; break; } + case DAK_RawLayout: { + if (Tok.isNot(tok::l_paren)) { + diagnose(Loc, diag::attr_expected_lparen, AttrName, + DeclAttribute::isDeclModifier(DK)); + return makeParserSuccess(); + } + + SourceLoc LParenLoc = consumeToken(tok::l_paren); + + if (!Tok.canBeArgumentLabel()) { + diagnose(Loc, diag::attr_rawlayout_expected_label, "'size', 'like', or 'likeArrayOf'"); + consumeIf(tok::r_paren); + return makeParserSuccess(); + } + + Identifier firstLabel; + SourceLoc firstLabelLoc = consumeArgumentLabel(firstLabel, true); + if (!consumeIf(tok::colon)) { + diagnose(Loc, diag::attr_expected_colon_after_label, firstLabel.str()); + return makeParserSuccess(); + } + + RawLayoutAttr *attr; + if (firstLabel.is("size")) { + // @_rawLayout(size: N, alignment: N) + unsigned size; + SourceLoc sizeLoc; + if (parseUnsignedInteger(size, sizeLoc, + diag::attr_rawlayout_expected_integer_size)) { + return makeParserSuccess(); + } + + if (!consumeIf(tok::comma)) { + diagnose(Loc, diag::attr_rawlayout_expected_params, "size", "alignment"); + consumeIf(tok::r_paren); + return makeParserSuccess(); + } + + if (!Tok.canBeArgumentLabel()) { + diagnose(Loc, diag::attr_rawlayout_expected_label, "'alignment'"); + return makeParserSuccess(); + } + + Identifier alignLabel; + SourceLoc alignLabelLoc = consumeArgumentLabel(alignLabel, true); + if (!consumeIf(tok::colon)) { + diagnose(Loc, diag::attr_expected_colon_after_label, "alignment"); + return makeParserSuccess(); + } + if (!alignLabel.is("alignment")) { + diagnose(Loc, diag::attr_rawlayout_expected_label, "'alignment'"); + return makeParserSuccess(); + } + + unsigned align; + SourceLoc alignLoc; + if (parseUnsignedInteger(align, alignLoc, + diag::attr_rawlayout_expected_integer_alignment)) { + return makeParserSuccess(); + } + + if (!consumeIf(tok::r_paren)) { + diagnose(Tok.getLoc(), diag::attr_expected_rparen, + AttrName, /*isModifier*/false); + return makeParserSuccess(); + } + + attr = new (Context) RawLayoutAttr(size, align, + AtLoc, SourceRange(Loc, Tok.getLoc())); + } else if (firstLabel.is("like")) { + // @_rawLayout(like: T) + auto likeType = parseType(diag::expected_type); + if (likeType.isNull()) { + return makeParserSuccess(); + } + if (!consumeIf(tok::r_paren)) { + diagnose(Tok.getLoc(), diag::attr_expected_rparen, + AttrName, /*isModifier*/false); + return makeParserSuccess(); + } + + attr = new (Context) RawLayoutAttr(likeType.get(), + AtLoc, SourceRange(Loc, Tok.getLoc())); + } else if (firstLabel.is("likeArrayOf")) { + // @_rawLayout(likeArrayOf: T, count: N) + auto likeType = parseType(diag::expected_type); + if (likeType.isNull()) { + return makeParserSuccess(); + } + + if (!consumeIf(tok::comma)) { + diagnose(Loc, diag::attr_rawlayout_expected_params, "likeArrayOf", "count"); + consumeIf(tok::r_paren); + return makeParserSuccess(); + } + + if (!Tok.canBeArgumentLabel()) { + diagnose(Loc, diag::attr_rawlayout_expected_label, "'count'"); + consumeIf(tok::r_paren); + return makeParserSuccess(); + } + + Identifier countLabel; + SourceLoc countLabelLoc = consumeArgumentLabel(countLabel, true); + if (!consumeIf(tok::colon)) { + diagnose(Loc, diag::attr_expected_colon_after_label, "count"); + return makeParserSuccess(); + } + if (!countLabel.is("count")) { + diagnose(Loc, diag::attr_rawlayout_expected_label, "'count'"); + return makeParserSuccess(); + } + + unsigned count; + SourceLoc countLoc; + if (parseUnsignedInteger(count, countLoc, + diag::attr_rawlayout_expected_integer_count)) { + return makeParserSuccess(); + } + + if (!consumeIf(tok::r_paren)) { + diagnose(Tok.getLoc(), diag::attr_expected_rparen, + AttrName, /*isModifier*/false); + return makeParserSuccess(); + } + + attr = new (Context) RawLayoutAttr(likeType.get(), count, + AtLoc, SourceRange(Loc, Tok.getLoc())); + } else { + diagnose(Loc, diag::attr_rawlayout_expected_label, + "'size', 'like', or 'likeArrayOf'"); + return makeParserSuccess(); + } + Attributes.add(attr); + break; + } } if (DuplicateAttribute) { diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index e457cdf318fe6..7a6d31c6ffb87 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2360,6 +2360,14 @@ namespace { properties.setNonTrivial(); properties.setLexical(IsLexical); } + + // If the type has raw storage, it is move-only and address-only. + if (D->getAttrs().hasAttribute()) { + properties.setAddressOnly(); + properties.setNonTrivial(); + properties.setLexical(IsLexical); + return handleMoveOnlyAddressOnly(structType, properties); + } auto subMap = structType->getContextSubstitutionMap(&TC.M, D); diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index ad4d75f2c5052..d56984ccc0a07 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -722,8 +722,9 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) { // DISCUSSION: This only happens with noncopyable types since the memory // lifetime checker doesn't seem to process trivial locations. But empty // move only structs are non-trivial, so we need to handle this here. - if (isa(nominal) && nominal->isMoveOnly() && - nominal->getStoredProperties().empty()) { + if (isa(nominal) && nominal->isMoveOnly() + && !nominal->getAttrs().hasAttribute() + && nominal->getStoredProperties().empty()) { auto *si = B.createStruct(ctor, lowering.getLoweredType(), {}); B.emitStoreValueOperation(ctor, si, selfLV.getLValueAddress(), StoreOwnershipQualifier::Init); diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index d2e793d6fa621..ef8878db0be25 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -944,7 +944,7 @@ void UseState::initializeLiveness( reinitInstAndValue.second); } } - + // Then check if our markedValue is from an argument that is in, // in_guaranteed, inout, or inout_aliasable, consider the marked address to be // the initialization point. @@ -952,7 +952,13 @@ void UseState::initializeLiveness( SILValue operand = address->getOperand(); if (auto *c = dyn_cast(operand)) operand = c->getOperand(); - if (auto *fArg = dyn_cast(operand)) { + // If the type uses raw layout, its initialization is unmanaged, so consider + // it always initialized. + auto s = operand->getType().getStructOrBoundGenericStruct(); + if (s && s->getAttrs().hasAttribute()) { + recordInitUse(address, address, liveness.getTopLevelSpan()); + liveness.initializeDef(address, liveness.getTopLevelSpan()); + } else if (auto *fArg = dyn_cast(operand)) { switch (fArg->getArgumentConvention()) { case swift::SILArgumentConvention::Indirect_In: case swift::SILArgumentConvention::Indirect_In_Guaranteed: diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 00b722163ff8b..99ffab99b0081 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -1932,14 +1932,16 @@ TypeVariableType *ConstraintSystem::openGenericParameter( // This lookup only can fail if the stdlib (i.e. the Swift module) has not // been loaded because you've passed `-parse-stdlib` and are not building the // stdlib itself (which would have `-module-name Swift` too). - if (auto *copyable = TypeChecker::getProtocol(getASTContext(), SourceLoc(), - KnownProtocolKind::Copyable)) { - addConstraint( - ConstraintKind::ConformsTo, typeVar, - copyable->getDeclaredInterfaceType(), - locator.withPathElement(LocatorPathElt::GenericParameter(parameter))); + if (!outerDC->getParentModule()->isBuiltinModule()) { + if (auto *copyable = TypeChecker::getProtocol(getASTContext(), SourceLoc(), + KnownProtocolKind::Copyable)) { + addConstraint( + ConstraintKind::ConformsTo, typeVar, + copyable->getDeclaredInterfaceType(), + locator.withPathElement(LocatorPathElt::GenericParameter(parameter))); + } } - + return typeVar; } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 66cf9de332d95..f8dfe447c4b32 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -348,6 +348,8 @@ class AttributeChecker : public AttributeVisitor { void visitRuntimeMetadataAttr(RuntimeMetadataAttr *attr); void visitMacroRoleAttr(MacroRoleAttr *attr); + + void visitRawLayoutAttr(RawLayoutAttr *attr); }; } // end anonymous namespace @@ -7173,6 +7175,75 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) { {}); } +void AttributeChecker::visitRawLayoutAttr(RawLayoutAttr *attr) { + if (!Ctx.LangOpts.hasFeature(Feature::RawLayout)) { + diagnoseAndRemoveAttr(attr, diag::attr_rawlayout_experimental); + return; + } + + // Can only apply to structs. + auto sd = dyn_cast(D); + if (!sd) { + diagnoseAndRemoveAttr(attr, diag::attr_only_one_decl_kind, + attr, "struct"); + return; + } + + if (!sd->isMoveOnly()) { + diagnoseAndRemoveAttr(attr, diag::attr_rawlayout_cannot_be_copyable); + } + + if (!sd->getStoredProperties().empty()) { + diagnoseAndRemoveAttr(attr, diag::attr_rawlayout_cannot_have_stored_properties); + } + + if (auto sizeAndAlign = attr->getSizeAndAlignment()) { + // Alignment must be a power of two. + auto align = sizeAndAlign->second; + if (align == 0 || (align & (align - 1)) != 0) { + diagnoseAndRemoveAttr(attr, diag::alignment_not_power_of_two); + return; + } + } else if (auto likeType = attr->getScalarLikeType()) { + // Resolve the like type in the struct's context. + auto resolvedType = TypeResolution::resolveContextualType( + likeType, sd, llvm::None, + // Unbound generics and placeholders + // are not allowed within this + // attribute. + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr); + + attr->setResolvedLikeType(resolvedType); + } else if (auto arrayType = attr->getArrayLikeTypeAndCount()) { + // Resolve the like type in the struct's context. + auto resolvedType = TypeResolution::resolveContextualType( + arrayType->first, sd, llvm::None, + // Unbound generics and placeholders + // are not allowed within this + // attribute. + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr); + + attr->setResolvedLikeType(resolvedType); + } else { + llvm_unreachable("new unhandled rawLayout attribute form?"); + } + + // If the type also specifies an `@_alignment`, that's an error. + // Maybe this is interesting to support to have a layout like another + // type but with different alignment in the future. + if (D->getAttrs().hasAttribute()) { + diagnoseAndRemoveAttr(attr, diag::attr_rawlayout_cannot_have_alignment_attr); + return; + } + + // The storage is not directly referenceable by stored properties. + sd->setHasUnreferenceableStorage(true); +} + namespace { class ClosureAttributeChecker diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 3e9cbbf4d5852..fb280cbeca5ef 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -1111,6 +1111,13 @@ namespace { llvm::Optional inferSendableFromInstanceStorage(NominalTypeDecl *nominal, SmallVectorImpl &requirements) { + // Raw storage is assumed not to be sendable. + if (auto sd = dyn_cast(nominal)) { + if (sd->getAttrs().hasAttribute()) { + return true; + } + } + struct Visitor { NominalTypeDecl *nominal; SmallVectorImpl &requirements; @@ -4609,6 +4616,21 @@ namespace { /// it is comprised only of Sendable instance storage. static bool checkSendableInstanceStorage( NominalTypeDecl *nominal, DeclContext *dc, SendableCheck check) { + // Raw storage is assumed not to be sendable. + if (auto sd = dyn_cast(nominal)) { + if (auto rawLayout = sd->getAttrs().getAttribute()) { + auto behavior = SendableCheckContext( + dc, check).defaultDiagnosticBehavior(); + if (!isImplicitSendableCheck(check) + && SendableCheckContext(dc, check) + .defaultDiagnosticBehavior() != DiagnosticBehavior::Ignore) { + sd->diagnose(diag::sendable_raw_storage, sd->getName()) + .limitBehavior(behavior); + } + return true; + } + } + // Stored properties of structs and classes must have // Sendable-conforming types. struct Visitor { diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 2a30e7ed02253..e895b38d4287e 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1534,6 +1534,7 @@ namespace { UNINTERESTING_ATTR(Optional) UNINTERESTING_ATTR(Override) UNINTERESTING_ATTR(RawDocComment) + UNINTERESTING_ATTR(RawLayout) UNINTERESTING_ATTR(Required) UNINTERESTING_ATTR(Convenience) UNINTERESTING_ATTR(Semantics) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index bc097481f6589..a9c33ed5d319b 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -5934,6 +5934,38 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { break; } + case decls_block::RawLayout_DECL_ATTR: { + bool isImplicit; + TypeID typeID; + uint32_t rawSize; + uint8_t rawAlign; + serialization::decls_block::RawLayoutDeclAttrLayout:: + readRecord(scratch, isImplicit, typeID, rawSize, rawAlign); + + if (typeID) { + auto type = MF.getTypeChecked(typeID); + if (!type) { + return type.takeError(); + } + auto typeRepr = new (ctx) FixedTypeRepr(type.get(), SourceLoc()); + if (rawAlign == 0) { + Attr = new (ctx) RawLayoutAttr(typeRepr, + SourceLoc(), + SourceRange()); + break; + } else { + Attr = new (ctx) RawLayoutAttr(typeRepr, rawSize, + SourceLoc(), + SourceRange()); + break; + } + } + + Attr = new (ctx) RawLayoutAttr(rawSize, rawAlign, + SourceLoc(), SourceRange()); + break; + } + #define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \ case decls_block::CLASS##_DECL_ATTR: { \ bool isImplicit; \ diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 3ef06e34c2dd0..e45ad841d8074 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -2061,6 +2061,14 @@ namespace decls_block { BCVBR<8> // alignment >; + using RawLayoutDeclAttrLayout = BCRecordLayout< + RawLayout_DECL_ATTR, + BCFixed<1>, // implicit + TypeIDField, // like type + BCVBR<32>, // size + BCVBR<8> // alignment + >; + using SwiftNativeObjCRuntimeBaseDeclAttrLayout = BCRecordLayout< SwiftNativeObjCRuntimeBase_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index ad3f1d6a9d23c..02d7941404b0d 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -3190,6 +3190,35 @@ class Serializer::DeclSerializer : public DeclVisitor { introducedDeclNames); return; } + + case DAK_RawLayout: { + auto *attr = cast(DA); + auto abbrCode = S.DeclTypeAbbrCodes[RawLayoutDeclAttrLayout::Code]; + + uint32_t rawSize; + uint8_t rawAlign; + TypeID typeID; + + if (auto sizeAndAlign = attr->getSizeAndAlignment()) { + typeID = 0; + rawSize = sizeAndAlign->first; + rawAlign = sizeAndAlign->second; + } else if (auto likeType = attr->getResolvedScalarLikeType()) { + typeID = S.addTypeRef(*likeType); + rawSize = 0; + rawAlign = 0; + } else if (auto likeArrayTypeAndCount = attr->getResolvedArrayLikeTypeAndCount()) { + typeID = S.addTypeRef(likeArrayTypeAndCount->first); + rawSize = likeArrayTypeAndCount->second; + rawAlign = ~0u; + } else { + llvm_unreachable("unhandled raw layout attribute, or trying to serialize unresolved attr!"); + } + + RawLayoutDeclAttrLayout::emitRecord( + S.Out, S.ScratchRecord, abbrCode, attr->isImplicit(), + typeID, rawSize, rawAlign); + } } } diff --git a/test/DebugInfo/raw_layout.swift b/test/DebugInfo/raw_layout.swift new file mode 100644 index 0000000000000..37f1138884833 --- /dev/null +++ b/test/DebugInfo/raw_layout.swift @@ -0,0 +1,9 @@ +// RUN: %target-swift-frontend %s -enable-experimental-feature RawLayout -emit-ir -g -o - | %FileCheck %s + +@_rawLayout(size: 12, alignment: 4) +// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "S8_4" +// CHECK-SAME: size: 96, +// CHECK-SAME: align: 32, +struct S8_4: ~Copyable {} + +var s = S8_4() diff --git a/test/IRGen/raw_layout.swift b/test/IRGen/raw_layout.swift new file mode 100644 index 0000000000000..692d678261d1d --- /dev/null +++ b/test/IRGen/raw_layout.swift @@ -0,0 +1,94 @@ +// RUN: %empty-directory(%t) +// RUN: %{python} %utils/chex.py < %s > %t/raw_layout.sil +// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -emit-ir %t/raw_layout.sil | %FileCheck %t/raw_layout.sil + +import Swift + +// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}4LockVWV" = {{.*}} %swift.vwtable +// size +// CHECK-SAME: , {{i64|i32}} 4 +// stride +// CHECK-SAME: , {{i64|i32}} 4 +// flags: alignment 3, noncopyable +// CHECK-SAME: , + +@_rawLayout(size: 4, alignment: 4) +struct Lock: ~Copyable { } + +struct PaddedStride { + var x: Int32 + var y: Int8 +} + +// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}16LikePaddedStrideVWV" = {{.*}} %swift.vwtable +// size +// CHECK-SAME: , {{i64|i32}} 5 +// stride +// CHECK-SAME: , {{i64|i32}} 8 +// flags: alignment 3, noncopyable +// CHECK-SAME: , +@_rawLayout(like: PaddedStride) +struct LikePaddedStride: ~Copyable {} + +// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}22LikePaddedStrideArray1VWV" = {{.*}} %swift.vwtable +// size +// CHECK-SAME: , {{i64|i32}} 8 +// stride +// CHECK-SAME: , {{i64|i32}} 8 +// flags: alignment 3, noncopyable +// CHECK-SAME: , +@_rawLayout(likeArrayOf: PaddedStride, count: 1) +struct LikePaddedStrideArray1: ~Copyable {} + +// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}22LikePaddedStrideArray2VWV" = {{.*}} %swift.vwtable +// size +// CHECK-SAME: , {{i64|i32}} 16 +// stride +// CHECK-SAME: , {{i64|i32}} 16 +// flags: alignment 3, noncopyable +// CHECK-SAME: , +@_rawLayout(likeArrayOf: PaddedStride, count: 2) +struct LikePaddedStrideArray2: ~Copyable {} + +// CHECK-LABEL: @"$s{{[A-Za-z0-9_]*}}9KeymasterVWV" = {{.*}} %swift.vwtable +// size +// CHECK-SAME: , {{i64|i32}} 12 +// stride +// CHECK-SAME: , {{i64|i32}} 12 +// flags: alignment 3, noncopyable +// CHECK-SAME: , +struct Keymaster: ~Copyable { + let lock1: Lock + let lock2: Lock + let lock3: Lock +} + +/* +TODO: Dependent layout not yet implemented + +@_rawLayout(like: T) +struct Cell: ~Copyable {} + +@_rawLayout(likeArrayOf: T, count: 1) +struct PaddedCell: ~Copyable {} + +@_rawLayout(likeArrayOf: T, count: 8) +struct SmallVectorBuf: ~Copyable {} +*/ + +sil @use_lock : $@convention(thin) (@in_guaranteed Lock) -> () { +entry(%L: $*Lock): + return undef : $() +} + +sil @use_keymaster_locks : $@convention(thin) (@in_guaranteed Keymaster) -> () { +entry(%K: $*Keymaster): + %f = function_ref @use_lock : $@convention(thin) (@in_guaranteed Lock) -> () + %a = struct_element_addr %K : $*Keymaster, #Keymaster.lock1 + apply %f(%a) : $@convention(thin) (@in_guaranteed Lock) -> () + %b = struct_element_addr %K : $*Keymaster, #Keymaster.lock2 + apply %f(%b) : $@convention(thin) (@in_guaranteed Lock) -> () + %c = struct_element_addr %K : $*Keymaster, #Keymaster.lock2 + apply %f(%c) : $@convention(thin) (@in_guaranteed Lock) -> () + return undef : $() +} diff --git a/test/Prototypes/UnfairLock.swift b/test/Prototypes/UnfairLock.swift new file mode 100644 index 0000000000000..4c9baa8d60321 --- /dev/null +++ b/test/Prototypes/UnfairLock.swift @@ -0,0 +1,120 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -enable-experimental-feature RawLayout -enable-experimental-feature BuiltinModule %s -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s + +// REQUIRES: OS=macosx +// REQUIRES: executable_test + +import Builtin +import Darwin + +@_rawLayout(like: os_unfair_lock_s) +struct UnfairLock: ~Copyable, @unchecked Sendable { + // TODO: Clang importer can't handle the OS_UNFAIR_LOCK_INIT macro definition + private static let OS_UNFAIR_LOCK_INIT = os_unfair_lock_s() + + // The lock is at a stable address as long as someone is borrowing it. + // If the address is gotten, it should only be used within the scope + // of a borrowing method. + @inline(__always) + private var _address: os_unfair_lock_t { + os_unfair_lock_t(Builtin.addressOfBorrow(self)) + } + + @inline(__always) + init() { + _address.initialize(to: UnfairLock.OS_UNFAIR_LOCK_INIT) + } + + @inline(__always) + borrowing func withLock(_ body: () throws -> R) rethrows -> R { + let address = _address + os_unfair_lock_lock(address) + defer { os_unfair_lock_unlock(address) } + + return try body() + } +} + +final class Locked: @unchecked Sendable { + private let lock = UnfairLock() + + // Don't need exclusivity checking since accesses always go through the + // lock + @exclusivity(unchecked) + private var value: T + + init(initialValue: T) { + self.value = consume initialValue + } + + func withLock(_ body: (inout T) throws -> R) rethrows -> R { + return try lock.withLock { try body(&value) } + } +} + +let myString = Locked(initialValue: "") + +@Sendable +func thread1(_: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + usleep(1) + myString.withLock { $0 += "apple\n" } + usleep(1) + myString.withLock { $0 += "banana\n" } + usleep(1) + myString.withLock { $0 += "grapefruit\n" } + return nil +} + +@Sendable +func thread2(_: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + usleep(1) + myString.withLock { $0 += "BRUSSELS SPROUTS\n" } + usleep(1) + myString.withLock { $0 += "CAULIFLOWER\n" } + usleep(1) + myString.withLock { $0 += "BROCCOLI\n" } + return nil +} + +@Sendable +func thread3(_: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + usleep(1) + myString.withLock { $0 += "Croissant\n" } + usleep(1) + myString.withLock { $0 += "Boule\n" } + usleep(1) + myString.withLock { $0 += "Batard\n" } + return nil +} + +func createPthread( + _ body: @Sendable @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? +) -> pthread_t? { + var thread: pthread_t? = nil + let r = pthread_create(&thread, nil, body, nil) + if r != 0 { + return nil + } + return thread +} + +var t1 = createPthread(thread1)! +var t2 = createPthread(thread2)! +var t3 = createPthread(thread3)! + +pthread_join(t1, nil) +pthread_join(t2, nil) +pthread_join(t3, nil) + +// CHECK-DAG: apple +// CHECK-DAG: banana +// CHECK-DAG: grapefruit +// CHECK-DAG: BRUSSELS SPROUTS +// CHECK-DAG: CAULIFLOWER +// CHECK-DAG: BROCCOLI +// CHECK-DAG: Croissant +// CHECK-DAG: Boule +// CHECK-DAG: Batard +myString.withLock { print($0) } diff --git a/test/SILGen/raw_layout.swift b/test/SILGen/raw_layout.swift new file mode 100644 index 0000000000000..8543f5786552c --- /dev/null +++ b/test/SILGen/raw_layout.swift @@ -0,0 +1,26 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature RawLayout %s | %FileCheck %s + +// CHECK: @_rawLayout(size: 4, alignment: 4) @_moveOnly struct Lock +// CHECK: @_rawLayout(like: T) @_moveOnly struct Cell +// CHECK: @_rawLayout(likeArrayOf: T, count: 8) @_moveOnly struct SmallVectorBuf + +@_rawLayout(size: 4, alignment: 4) +struct Lock: ~Copyable { + // Raw layout type should be lowered as address only + // CHECK-LABEL: sil {{.*}} @{{.*}}4Lock{{.*}}3foo{{.*}} : $@convention(method) (@in_guaranteed Lock) -> () + borrowing func foo() {} + + // CHECK-LABEL: sil {{.*}} @{{.*}}4Lock{{.*}}3bar{{.*}} : $@convention(method) (@inout Lock) -> () + mutating func bar() {} + + // CHECK-LABEL: sil {{.*}} @{{.*}}4Lock{{.*}}3bas{{.*}} : $@convention(method) (@in Lock) -> () + consuming func bas() {} + + deinit {} +} + +@_rawLayout(like: T) +struct Cell: ~Copyable {} + +@_rawLayout(likeArrayOf: T, count: 8) +struct SmallVectorBuf: ~Copyable {} diff --git a/test/Sema/raw_layout.swift b/test/Sema/raw_layout.swift new file mode 100644 index 0000000000000..7cd4f463626a0 --- /dev/null +++ b/test/Sema/raw_layout.swift @@ -0,0 +1,48 @@ +// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -typecheck -verify %s + +@_rawLayout(size: 4, alignment: 4) // expected-error{{type with @_rawLayout cannot be copied and must be declared ~Copyable}} +struct ImproperlyCopyable {} + +@_rawLayout(size: 4, alignment: 3) // expected-error{{alignment value must be a power of two}} +struct InvalidAlignment: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) // expected-error{{type with @_rawLayout cannot have stored properties}} +struct HasStoredProperty: ~Copyable { var x: Int } + +@_rawLayout(size: 4, alignment: 4) // expected-error{{type with @_rawLayout cannot have stored properties}} +struct HasLazyStoredProperty: ~Copyable { lazy var x: Int = 42 } + +@_rawLayout(size: 4, alignment: 4) // expected-error{{type with @_rawLayout cannot have stored properties}} +struct HasObservedStoredProperty: ~Copyable { var x: Int { didSet { } } } + +@propertyWrapper +struct Wrapper { + var wrappedValue: T { fatalError() } +} + +@_rawLayout(size: 4, alignment: 4) // expected-error{{type with @_rawLayout cannot have stored properties}} +struct HasWrappedStoredProperty: ~Copyable { @Wrapper var x: Int } + +@_rawLayout(like: T) // expected-error{{cannot find type 'T' in scope}} +struct TypeDoesntExist: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) // expected-error{{@_rawLayout may only be used on 'struct'}} +enum EnumWithRawLayout: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) // expected-error{{@_rawLayout may only be used on 'struct'}} +class ClassWithRawLayout {} + +@_rawLayout(size: 4, alignment: 4) +struct Lock: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) // expected-error{{@_rawLayout may only be used on 'struct'}} +typealias TypealiasWithRawLayout = Lock + +struct Butt { + @_rawLayout(size: 4, alignment: 4) // expected-error{{@_rawLayout may only be used on 'struct'}} + var propertyWithStoredLayout: () +} + +@_rawLayout(size: 4, alignment: 4) @_alignment(16) // expected-error{{type with @_rawLayout cannot also have an @_alignment attribute}} +struct RawLayoutAndAlignment: ~Copyable {} + diff --git a/test/Sema/raw_layout_correct.swift b/test/Sema/raw_layout_correct.swift new file mode 100644 index 0000000000000..c4512819fc4ee --- /dev/null +++ b/test/Sema/raw_layout_correct.swift @@ -0,0 +1,10 @@ +// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -typecheck -verify %s + +@_rawLayout(like: T) +struct RawStorage: ~Copyable {} + +@_rawLayout(likeArrayOf: T, count: 4) +struct RawSmallArray: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) +struct Lock: ~Copyable {} diff --git a/test/Sema/raw_layout_parse.swift b/test/Sema/raw_layout_parse.swift new file mode 100644 index 0000000000000..bbe974fb9f6b3 --- /dev/null +++ b/test/Sema/raw_layout_parse.swift @@ -0,0 +1,29 @@ +// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -parse -verify %s + +@_rawLayout(size: 4, alignment: 4) +struct Lock: ~Copyable {} + +@_rawLayout(like: Int) +struct Lock2: ~Copyable {} + +@_rawLayout(like: Optional) +struct Lock3: ~Copyable {} + +@_rawLayout(like: T) +struct MyUnmanaged: ~Copyable {} + +@_rawLayout(likeArrayOf: T, count: 8) +struct SmallVectorBuf: ~Copyable {} + +@_rawLayout // expected-error{{expected '('}} +struct NoLayoutSpecified: ~Copyable {} + +@_rawLayout() // expected-error{{expected 'size', 'like', or 'likeArrayOf' argument to @_rawLayout attribute}} +struct NoParamsSpecified: ~Copyable {} + +@_rawLayout(size: 4) // expected-error{{expected alignment argument after size argument in @_rawLayout attribute}} +struct SizeWithoutAlignment: ~Copyable {} + +@_rawLayout(likeArrayOf: Optional) // expected-error{{expected count argument after likeArrayOf argument in @_rawLayout attribute}} +struct ArrayWithoutSize: ~Copyable {} + diff --git a/test/Sema/raw_layout_sendable.swift b/test/Sema/raw_layout_sendable.swift new file mode 100644 index 0000000000000..67a4872cf50e8 --- /dev/null +++ b/test/Sema/raw_layout_sendable.swift @@ -0,0 +1,34 @@ +// RUN: %target-swift-frontend -enable-experimental-feature StrictConcurrency -enable-experimental-feature RawLayout -typecheck -verify %s + +func checkSendable(_: @Sendable () -> ()) {} + +@_rawLayout(size: 4, alignment: 4) +struct NotAutomaticallySendableAndNotUsedAsSendable: ~Copyable {} + +@_rawLayout(size: 4, alignment: 4) +struct NotAutomaticallySendable: ~Copyable {} // expected-note{{}} + +func testNotAutomaticallySendable() { + let s = NotAutomaticallySendable() + + checkSendable { _ = s } // expected-warning{{capture of 's' with non-sendable type 'NotAutomaticallySendable'}} +} + +@_rawLayout(size: 4, alignment: 4) +struct UnuncheckedSendable: ~Copyable, Sendable {} // expected-warning{{@_rawLayout does not conform to the 'Sendable' protocol}} + +func testUnuncheckedSendable() { + let s = UnuncheckedSendable() + + checkSendable { _ = s } +} + +@_rawLayout(size: 4, alignment: 4) +struct UncheckedSendable: ~Copyable, @unchecked Sendable {} + +func testUncheckedSendable() { + let s = UncheckedSendable() + + checkSendable { _ = s } +} + diff --git a/test/Serialization/attr-rawlayout.swift b/test/Serialization/attr-rawlayout.swift new file mode 100644 index 0000000000000..e1bb66e1ea676 --- /dev/null +++ b/test/Serialization/attr-rawlayout.swift @@ -0,0 +1,18 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -enable-experimental-feature RawLayout -emit-module-path %t/a.swiftmodule -module-name a %s +// RUN: llvm-bcanalyzer -dump %t/a.swiftmodule | %FileCheck --check-prefix BC-CHECK --implicit-check-not UnknownCode %s +// RUN: %target-swift-ide-test -print-module -module-to-print a -source-filename x -I %t | %FileCheck --check-prefix MODULE-CHECK %s + +// BC-CHECK: : ~Copyable {} + +// MODULE-CHECK: @_rawLayout(likeArrayOf: T, count: 8) struct C_SmallVector +@_rawLayout(likeArrayOf: T, count: 8) +struct C_SmallVector: ~Copyable {} diff --git a/utils/gyb_syntax_support/AttributeKinds.py b/utils/gyb_syntax_support/AttributeKinds.py index 1d9aa24201d07..33d25cb17c00a 100644 --- a/utils/gyb_syntax_support/AttributeKinds.py +++ b/utils/gyb_syntax_support/AttributeKinds.py @@ -741,6 +741,12 @@ def __init__(self, name, swift_name=None): APIBreakingToAdd, APIBreakingToRemove, code=144), + DeclAttribute('_rawLayout', 'RawLayout', + OnStruct, + UserInaccessible, + ABIBreakingToAdd, ABIBreakingToRemove, + APIStableToAdd, APIStableToRemove, + code=146) ] # Schema for declaration modifiers: