Skip to content

Commit fc4f64c

Browse files
authored
Merge pull request #59741 from kavon/5.7-inconvenienced-actors
🍒[5.7] Remove the need for `convenience` keyword for delegating actor initializers
2 parents 388230e + 4dac108 commit fc4f64c

19 files changed

+515
-82
lines changed

docs/SILInitializerConventions.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# SIL Initializer Conventions
2+
3+
A nominal type can define a number of initializers, some of which may
4+
delegate initialization to another initializer. There are specific calling
5+
conventions for these initializers within SIL that make up a part of the ABI
6+
for a type. This document aims to summarize the key calling conventions for
7+
these initializers.
8+
9+
10+
# Structs and Enums
11+
12+
The delegation status for the initializer of a struct or enum is not encoded
13+
in the definitions of these initializers. Thus, all of these initializers
14+
have an implicit `metatype` argument for the instance to be passed in as the
15+
last argument to the initializer. Using `<...>` as a stand-in for other
16+
arguments that are part of the usual function calling convention, consider this
17+
example:
18+
19+
```swift
20+
// the non-delegating init MyStruct.init(final:)
21+
sil hidden [ossa] @$s4test8MyStructV5finalACSi_tcfC : $@convention(method) (<...>, @thin MyStruct.Type) -> MyStruct {
22+
bb0(<...>, %meta : $@thin MyStruct.Type):
23+
%a = alloc_box ${ var MyStruct }, var, name "self"
24+
%b = mark_uninitialized [rootself] %a : ${ var MyStruct }
25+
%c = begin_borrow [lexical] %b : ${ var MyStruct }
26+
%d = project_box %c : ${ var MyStruct }, 0
27+
28+
// ... initialize properties, etc ...
29+
30+
%end = load [trivial] %d : $*MyStruct
31+
end_borrow %c : ${ var MyStruct }
32+
destroy_value %b : ${ var MyStruct }
33+
return %end : $MyStruct
34+
}
35+
36+
37+
// the delegating init MyStruct.init(delegates:)
38+
sil hidden [ossa] @$s4test8MyStructV9delegatesACyt_tcfC : $@convention(method) (<...>, @thin MyStruct.Type) -> MyStruct {
39+
bb0(<...>, %meta : $@thin MyStruct.Type):
40+
// Same allocation as the non-delegating:
41+
%a = alloc_box ${ var MyStruct }, var, name "self"
42+
%b = mark_uninitialized [rootself] %a : ${ var MyStruct }
43+
%c = begin_borrow [lexical] %b : ${ var MyStruct }
44+
%d = project_box %c : ${ var MyStruct }, 0
45+
46+
// ... delegate to MyStruct.init(final:) ...
47+
48+
%ctor = function_ref @$s4test8MyStructV5finalACSi_tcfC : $@convention(method) (Int, @thin MyStruct.Type) -> MyStruct
49+
%ret = apply %ctor(<...>, %meta) : $@convention(method) (Int, @thin MyStruct.Type) -> MyStruct
50+
51+
assign %ret to %d : $*MyStruct
52+
%end = load [trivial] %d : $*MyStruct
53+
end_borrow %c : ${ var MyStruct }
54+
destroy_value %b : ${ var MyStruct }
55+
return %end : $MyStruct
56+
}
57+
```
58+
59+
It's important to note that all initializers take a metadata argument,
60+
regardless of whether it is a delegating initializer. There is also no
61+
separation between allocating and non-allocating initializer entrypoints.
62+
All initializers may perform allocation.
63+
64+
# Classes
65+
66+
Every designated initializer has two entry-points. One performs allocation
67+
(i.e., the "allocating" entry) before continuing at the second entrypoint
68+
which does the initialization (i.e., the "initializing" entrypoint).
69+
Here's an example of `MyClass.init(final:)`, which is a designated initializer,
70+
with its two entry-points:
71+
72+
```swift
73+
// MyClass.__allocating_init(final:)
74+
sil hidden [exact_self_class] [ossa] @$s4test7MyClassC5finalACSi_tcfC : $@convention(method) (<...>, @thick MyClass.Type) -> @owned MyClass {
75+
bb0(%0 : $Int, %1 : $@thick MyClass.Type):
76+
%2 = alloc_ref $MyClass
77+
// function_ref MyClass.init(final:)
78+
%3 = function_ref @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass
79+
%4 = apply %3(%0, %2) : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %5
80+
return %4 : $MyClass
81+
}
82+
83+
// MyClass.init(final:)
84+
sil hidden [ossa] @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass {
85+
bb0(<...>, %1 : @owned $MyClass):
86+
%4 = mark_uninitialized [rootself] %1 : $MyClass
87+
88+
// ... initialize MyClass ...
89+
90+
%11 = copy_value %4 : $MyClass
91+
destroy_value %4 : $MyClass
92+
return %11 : $MyClass
93+
}
94+
```
95+
96+
In the mangling of these entrypoint labels, the uppercase `C` suffix indicates
97+
that it's the allocating entrypoint, whereas the lowercase `c` is the
98+
initializing entrypoint. Only the allocating entrypoint is published in the
99+
type's vtable:
100+
101+
```swift
102+
sil_vtable MyClass {
103+
// ...
104+
#MyClass.init!allocator: (MyClass.Type) -> (<...>) -> MyClass : @$s4test7MyClassC5finalACSi_tcfC // MyClass.__allocating_init(final:)
105+
}
106+
```
107+
108+
The initializing entrypoint is only referenced by either it's corresponding
109+
allocating entrypoint, or by a sub-class that is delegating up in a `super.init`
110+
call. For example, if we had:
111+
112+
```swift
113+
class MyClass {
114+
var x: Int
115+
init(final x: Int) {
116+
self.x = x
117+
}
118+
}
119+
120+
class MyDerivedClass: MyClass {
121+
var y: Int
122+
init(subFinal y: Int) {
123+
self.y = y
124+
super.init(final: y)
125+
}
126+
}
127+
```
128+
129+
Then the `super.init(final: y)` call directly invokes `MyClass.init(final:)`'s
130+
initializing entrypoint, bypassing its allocating init. Here's what that looks
131+
like in SIL:
132+
133+
```
134+
// MyDerivedClass.__allocating_init(final:)
135+
sil hidden [exact_self_class] [ossa] @$s4test14MyDerivedClassC5finalACSi_tcfC : $@convention(method) (Int, @thick MyDerivedClass.Type) -> @owned MyDerivedClass {
136+
// ... calls $s4test14MyDerivedClassC5finalACSi_tcfc in the usual way ...
137+
}
138+
139+
// MyDerivedClass.init(final:)
140+
sil hidden [ossa] @$s4test14MyDerivedClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyDerivedClass) -> @owned MyDerivedClass {
141+
bb0(%0 : $Int, %1 : @owned $MyDerivedClass):
142+
%2 = alloc_box ${ var MyDerivedClass }, let, name "self"
143+
%3 = mark_uninitialized [derivedself] %2 : ${ var MyDerivedClass }
144+
%4 = begin_borrow [lexical] %3 : ${ var MyDerivedClass }
145+
%5 = project_box %4 : ${ var MyDerivedClass }, 0
146+
debug_value %0 : $Int, let, name "y", argno 1
147+
store %1 to [init] %5 : $*MyDerivedClass
148+
149+
// ... initialize self.y ...
150+
151+
// perform the super call. notice the ownership transfer to the super.init.
152+
%14 = load [take] %5 : $*MyDerivedClass
153+
%15 = upcast %14 : $MyDerivedClass to $MyClass
154+
// function_ref MyClass.init(final:)
155+
%16 = function_ref @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %17
156+
%17 = apply %16(%0, %15) : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %18
157+
%18 = unchecked_ref_cast %17 : $MyClass to $MyDerivedClass
158+
store %18 to [init] %5 : $*MyDerivedClass // id: %19
159+
160+
// return as usual
161+
%20 = load [copy] %5 : $*MyDerivedClass
162+
end_borrow %4 : ${ var MyDerivedClass }
163+
destroy_value %3 : ${ var MyDerivedClass }
164+
return %20 : $MyDerivedClass
165+
}
166+
```
167+
168+
# Actors
169+
170+
There does not exist a sub-actor that inherits from some other actor in the type
171+
system. As a result, the `convenience` keyword is not required for actor
172+
initializers in the source code. Without inheritance, only the allocating
173+
entry-points can ever be used by an actor.
174+
175+
Nevertheless, internally the compiler will still differentiate between
176+
convenience and designated initializers. So everything discussed
177+
earlier for classes also apply to actors. The body of the initializer determines
178+
whether the compiler internally treats it as `convenience` or not. For example,
179+
an internally designated initializer for an actor still emits two entry-points,
180+
but the initializing entrypoint is exclusively used by its corresponding
181+
allocating entrypoint.
182+
183+
184+

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3603,10 +3603,10 @@ ERROR(designated_init_in_extension,none,
36033603
"designated initializer cannot be declared in an extension of %0; "
36043604
"did you mean this to be a convenience initializer?",
36053605
(DeclName))
3606-
ERROR(cfclass_designated_init_in_extension,none,
3606+
ERROR(designated_init_in_extension_no_convenience_tip,none,
36073607
"designated initializer cannot be declared in an extension of %0",
36083608
(DeclName))
3609-
ERROR(enumstruct_convenience_init,none,
3609+
ERROR(no_convenience_keyword_init,none,
36103610
"initializers in %0 are not marked with 'convenience'",
36113611
(StringRef))
36123612
ERROR(nonclass_convenience_init,none,

lib/AST/ASTContext.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3072,10 +3072,14 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
30723072
}
30733073

30743074
// Convenience initializers have a dynamic 'self' in '-swift-version 5'.
3075+
//
3076+
// NOTE: it's important that we check if it's a convenience init only after
3077+
// confirming it's not semantically final, or else there can be a request
3078+
// evaluator cycle to determine the init kind for actors, which are final.
30753079
if (Ctx.isSwiftVersionAtLeast(5)) {
3076-
if (wantDynamicSelf && CD->isConvenienceInit())
3080+
if (wantDynamicSelf)
30773081
if (auto *classDecl = selfTy->getClassOrBoundGenericClass())
3078-
if (!classDecl->isSemanticallyFinal())
3082+
if (!classDecl->isSemanticallyFinal() && CD->isConvenienceInit())
30793083
isDynamicSelf = true;
30803084
}
30813085
} else if (isa<DestructorDecl>(AFD)) {

lib/AST/Decl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3065,13 +3065,13 @@ void ValueDecl::setIsObjC(bool value) {
30653065
bool ValueDecl::isSemanticallyFinal() const {
30663066
// Actor types are semantically final.
30673067
if (auto classDecl = dyn_cast<ClassDecl>(this)) {
3068-
if (classDecl->isActor())
3068+
if (classDecl->isAnyActor())
30693069
return true;
30703070
}
30713071

30723072
// As are members of actor types.
30733073
if (auto classDecl = getDeclContext()->getSelfClassDecl()) {
3074-
if (classDecl->isActor())
3074+
if (classDecl->isAnyActor())
30753075
return true;
30763076
}
30773077

lib/SIL/IR/SILDeclRef.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -662,13 +662,14 @@ IsSerialized_t SILDeclRef::isSerialized() const {
662662
return IsSerialized;
663663

664664
// The allocating entry point for designated initializers are serialized
665-
// if the class is @usableFromInline or public.
665+
// if the class is @usableFromInline or public. Actors are excluded because
666+
// whether the init is designated is not clearly reflected in the source code.
666667
if (kind == SILDeclRef::Kind::Allocator) {
667668
auto *ctor = cast<ConstructorDecl>(d);
668-
if (ctor->isDesignatedInit() &&
669-
ctor->getDeclContext()->getSelfClassDecl()) {
670-
if (!ctor->hasClangNode())
671-
return IsSerialized;
669+
if (auto classDecl = ctor->getDeclContext()->getSelfClassDecl()) {
670+
if (!classDecl->isAnyActor() && ctor->isDesignatedInit())
671+
if (!ctor->hasClangNode())
672+
return IsSerialized;
672673
}
673674
}
674675

0 commit comments

Comments
 (0)