Skip to content

Commit

Permalink
AST: Struct initializers that assign to self can now be resilient
Browse files Browse the repository at this point in the history
Initializers for non-fixed-layout structs that are inlinable or
are defined in a different module are treated as delegating
initializers.

Previously, only initializers containing a 'self.init' call were
delegating; initializers that assigned to 'self' were not, which
resulted in DI treating them as a root initializer where the
stored 'self' value was exploded into a series of stores to each
stored property member.

They were not resilient as a result.

Fixes <https://bugs.swift.org/browse/SR-5649>,
<rdar://problem/33767516>.
  • Loading branch information
slavapestov committed Oct 14, 2017
1 parent b5eeae7 commit 065cbd8
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 17 deletions.
20 changes: 18 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5306,10 +5306,26 @@ ConstructorDecl::getDelegatingOrChainedInitKind(DiagnosticEngine *diags,
// get the kind out of the finder.
auto Kind = finder.Kind;

auto *NTD = getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext();

// Protocol extension and enum initializers are always delegating.
if (Kind == BodyInitKind::None) {
if (getDeclContext()->getAsProtocolExtensionContext() ||
getDeclContext()->getAsEnumOrEnumExtensionContext()) {
if (isa<ProtocolDecl>(NTD) || isa<EnumDecl>(NTD)) {
Kind = BodyInitKind::Delegating;
}
}

// Struct initializers that cannot see the layout of the struct type are
// always delegating. This occurs if the struct type is not fixed layout,
// and the constructor is either inlinable or defined in another module.
//
// FIXME: Figure out the right condition to use here that does not depend
// on the -enable-resilience flag, and make it conditional on
// -swift-version 5 instead, once the "disallow memberwise cross-module
// initializer" proposal lands.
if (Kind == BodyInitKind::None) {
if (isa<StructDecl>(NTD) &&
!NTD->hasFixedLayout(getParentModule(), getResilienceExpansion())) {
Kind = BodyInitKind::Delegating;
}
}
Expand Down
32 changes: 32 additions & 0 deletions test/SILGen/struct_resilience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,26 @@ public func functionWithMyResilientTypes(_ s: MySize, f: (MySize) -> MySize) ->
self.x = x
self.y = y
}

// Non-inlineable initializer, assigns to self -- treated as a root initializer

// CHECK-LABEL: sil @_T017struct_resilience24VersionedResilientStructVA2C5other_tcfC : $@convention(method) (@in VersionedResilientStruct, @thin VersionedResilientStruct.Type) -> @out VersionedResilientStruct
// CHECK: [[SELF_BOX:%.*]] = alloc_box ${ var VersionedResilientStruct }
// CHECK-NEXT: [[SELF_UNINIT:%.*]] = mark_uninitialized [rootself] [[SELF_BOX]]
// CHECK: return
@_versioned init(other: VersionedResilientStruct) {
self = other
}

// Inlineable initializer, assigns to self -- treated as a delegating initializer

// CHECK-LABEL: sil [serialized] @_T017struct_resilience24VersionedResilientStructVA2C6other2_tcfC : $@convention(method) (@in VersionedResilientStruct, @thin VersionedResilientStruct.Type) -> @out VersionedResilientStruct
// CHECK: [[SELF_BOX:%.*]] = alloc_box ${ var VersionedResilientStruct }
// CHECK-NEXT: [[SELF_UNINIT:%.*]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
// CHECK: return
@_versioned @_inlineable init(other2: VersionedResilientStruct) {
self = other2
}
}

// CHECK-LABEL: sil [transparent] [serialized] @_T017struct_resilience27useVersionedResilientStructAA0deF0VADF : $@convention(thin) (@in VersionedResilientStruct) -> @out VersionedResilientStruct
Expand All @@ -241,3 +261,15 @@ public func functionWithMyResilientTypes(_ s: MySize, f: (MySize) -> MySize) ->

return VersionedResilientStruct(x: s.y, y: s.x)
}

// Initializers for resilient structs
extension Size {

// CHECK-LABEL: sil hidden @_T016resilient_struct4SizeV0B11_resilienceEA2C5other_tcfC : $@convention(method) (@in Size, @thin Size.Type) -> @out Size
// CHECK: [[SELF_BOX:%.*]] = alloc_box ${ var Size }
// CHECK-NEXT: [[SELF_UNINIT:%.*]] = mark_uninitialized [delegatingself] [[SELF_BOX]] : ${ var Size }
// CHECK: return
init(other: Size) {
self = other
}
}
25 changes: 10 additions & 15 deletions test/decl/init/resilience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,49 @@ extension Point {

// Size is not @_fixed_layout, so we cannot define a new designated initializer
extension Size {
// FIXME: Produce a decent diagnostic here
init(ww: Int, hh: Int) {
// expected-error@-1 {{initializer declared in an extension of non-'@_fixed_layout' type 'Size' must delegate to another initializer}}
self.w = ww
self.h = hh
self.h = hh // expected-error {{cannot assign to property: 'h' is a 'let' constant}}
}

// This is OK
init(www: Int, hhh: Int) {
self.init(w: www, h: hhh)
}

// FIXME: This should be allowed, but Sema doesn't distinguish this
// case from memberwise initialization, and DI explodes the value type
// This is OK
init(other: Size) {
// expected-error@-1 {{initializer declared in an extension of non-'@_fixed_layout' type 'Size' must delegate to another initializer}}
self = other
}
}

// Animal is not @_fixed_layout, so we cannot define an @_inlineable
// designated initializer
//
// FIXME: Crap diagnostics
public struct Animal {
public let name: String
public let name: String // expected-note 3{{change 'let' to 'var' to make it mutable}}

@_inlineable public init(name: String) {
// expected-error@-1 {{initializer for non-'@_fixed_layout' type 'Animal' is '@_inlineable' and must delegate to another initializer}}
self.name = name
self.name = name // expected-error {{cannot assign to property: 'name' is a 'let' constant}}
}

@inline(__always) public init(dog: String) {
// expected-error@-1 {{initializer for non-'@_fixed_layout' type 'Animal' is '@inline(__always)' and must delegate to another initializer}}
self.name = dog
self.name = dog // expected-error {{cannot assign to property: 'name' is a 'let' constant}}
}

@_transparent public init(cat: String) {
// expected-error@-1 {{initializer for non-'@_fixed_layout' type 'Animal' is '@_transparent' and must delegate to another initializer}}
self.name = cat
self.name = cat // expected-error {{cannot assign to property: 'name' is a 'let' constant}}
}

// This is OK
@_inlineable public init(cow: String) {
self.init(name: cow)
}

// FIXME: This should be allowed, but Sema doesn't distinguish this
// case from memberwise initialization, and DI explodes the value type
// This is OK
@_inlineable public init(other: Animal) {
// expected-error@-1 {{initializer for non-'@_fixed_layout' type 'Animal' is '@_inlineable' and must delegate to another initializer}}
self = other
}
}
Expand Down

0 comments on commit 065cbd8

Please sign in to comment.