Skip to content

Commit 9aa2de4

Browse files
authored
Merge pull request #41083 from kavon/inconvenienced-actors
[SE-327] Remove need for `convenience` for delegating initializers of an actor.
2 parents 374c7a3 + 2e865c8 commit 9aa2de4

17 files changed

+399
-59
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3572,7 +3572,7 @@ ERROR(designated_init_in_extension,none,
35723572
ERROR(cfclass_designated_init_in_extension,none,
35733573
"designated initializer cannot be declared in an extension of %0",
35743574
(DeclName))
3575-
ERROR(enumstruct_convenience_init,none,
3575+
ERROR(no_convenience_keyword_init,none,
35763576
"initializers in %0 are not marked with 'convenience'",
35773577
(StringRef))
35783578
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
@@ -3082,13 +3082,13 @@ void ValueDecl::setIsObjC(bool value) {
30823082
bool ValueDecl::isSemanticallyFinal() const {
30833083
// Actor types are semantically final.
30843084
if (auto classDecl = dyn_cast<ClassDecl>(this)) {
3085-
if (classDecl->isActor())
3085+
if (classDecl->isAnyActor())
30863086
return true;
30873087
}
30883088

30893089
// As are members of actor types.
30903090
if (auto classDecl = getDeclContext()->getSelfClassDecl()) {
3091-
if (classDecl->isActor())
3091+
if (classDecl->isAnyActor())
30923092
return true;
30933093
}
30943094

lib/SIL/IR/SILDeclRef.cpp

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

671671
// The allocating entry point for designated initializers are serialized
672-
// if the class is @usableFromInline or public.
672+
// if the class is @usableFromInline or public. Actors are excluded because
673+
// whether the init is designated is not clearly reflected in the source code.
673674
if (kind == SILDeclRef::Kind::Allocator) {
674675
auto *ctor = cast<ConstructorDecl>(d);
675-
if (ctor->isDesignatedInit() &&
676-
ctor->getDeclContext()->getSelfClassDecl()) {
677-
if (!ctor->hasClangNode())
678-
return IsSerialized;
676+
if (auto classDecl = ctor->getDeclContext()->getSelfClassDecl()) {
677+
if (!classDecl->isAnyActor() && ctor->isDesignatedInit())
678+
if (!ctor->hasClangNode())
679+
return IsSerialized;
679680
}
680681
}
681682

lib/Sema/TypeCheckDecl.cpp

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -379,40 +379,64 @@ CtorInitializerKind
379379
InitKindRequest::evaluate(Evaluator &evaluator, ConstructorDecl *decl) const {
380380
auto &diags = decl->getASTContext().Diags;
381381

382-
// Convenience inits are only allowed on classes and in extensions thereof.
383-
if (decl->getAttrs().hasAttribute<ConvenienceAttr>()) {
384-
if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
385-
auto classDecl = dyn_cast<ClassDecl>(nominal);
386-
387-
// Forbid convenience inits on Foreign CF types, as Swift does not yet
388-
// support user-defined factory inits.
389-
if (classDecl &&
390-
classDecl->getForeignClassKind() == ClassDecl::ForeignKind::CFType) {
391-
diags.diagnose(decl->getLoc(), diag::cfclass_convenience_init);
392-
}
382+
if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
383+
384+
// Convenience inits are only allowed on classes and in extensions thereof.
385+
if (auto convenAttr = decl->getAttrs().getAttribute<ConvenienceAttr>()) {
386+
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
387+
if (classDecl->isAnyActor()) {
388+
// For an actor "convenience" is not required, but we'll honor it.
389+
diags.diagnose(decl->getLoc(),
390+
diag::no_convenience_keyword_init, "actors")
391+
.fixItRemove(convenAttr->getLocation())
392+
.warnUntilSwiftVersion(6);
393+
394+
} else { // not an actor
395+
// Forbid convenience inits on Foreign CF types, as Swift does not yet
396+
// support user-defined factory inits.
397+
if (classDecl->getForeignClassKind() == ClassDecl::ForeignKind::CFType)
398+
diags.diagnose(decl->getLoc(), diag::cfclass_convenience_init);
399+
}
393400

394-
if (!classDecl) {
395-
auto ConvenienceLoc =
396-
decl->getAttrs().getAttribute<ConvenienceAttr>()->getLocation();
401+
} else { // not a ClassDecl
402+
auto ConvenienceLoc = convenAttr->getLocation();
397403

398-
// Produce a tailored diagnostic for structs and enums.
404+
// Produce a tailored diagnostic for structs and enums. They should
405+
// not have `convenience`.
399406
bool isStruct = dyn_cast<StructDecl>(nominal) != nullptr;
400407
if (isStruct || dyn_cast<EnumDecl>(nominal)) {
401-
diags.diagnose(decl->getLoc(), diag::enumstruct_convenience_init,
408+
diags.diagnose(decl->getLoc(), diag::no_convenience_keyword_init,
402409
isStruct ? "structs" : "enums")
403410
.fixItRemove(ConvenienceLoc);
404411
} else {
405-
diags.diagnose(decl->getLoc(), diag::nonclass_convenience_init,
406-
nominal->getName())
412+
diags.diagnose(decl->getLoc(), diag::no_convenience_keyword_init,
413+
nominal->getName().str())
407414
.fixItRemove(ConvenienceLoc);
408415
}
409416
return CtorInitializerKind::Designated;
410417
}
418+
419+
return CtorInitializerKind::Convenience;
411420
}
412421

413-
return CtorInitializerKind::Convenience;
422+
// if there is no `convenience` keyword...
423+
424+
// actors infer whether they are `convenience` from their body kind.
425+
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
426+
if (classDecl->isAnyActor()) {
427+
auto kind = decl->getDelegatingOrChainedInitKind();
428+
switch (kind.initKind) {
429+
case BodyInitKind::ImplicitChained:
430+
case BodyInitKind::Chained:
431+
case BodyInitKind::None:
432+
return CtorInitializerKind::Designated;
433+
434+
case BodyInitKind::Delegating:
435+
return CtorInitializerKind::Convenience;
436+
}
437+
}
438+
}
414439

415-
} else if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
416440
// A designated init for a class must be written within the class itself.
417441
//
418442
// This is because designated initializers of classes get a vtable entry,
@@ -437,10 +461,11 @@ InitKindRequest::evaluate(Evaluator &evaluator, ConstructorDecl *decl) const {
437461
return CtorInitializerKind::Convenience;
438462
}
439463
}
464+
} // end of Nominal context
440465

441-
if (decl->getDeclContext()->getExtendedProtocolDecl()) {
442-
return CtorInitializerKind::Convenience;
443-
}
466+
// initializers in protocol extensions must be convenience inits
467+
if (decl->getDeclContext()->getExtendedProtocolDecl()) {
468+
return CtorInitializerKind::Convenience;
444469
}
445470

446471
return CtorInitializerKind::Designated;

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,13 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
28322832
return false;
28332833
}
28342834

2835+
// do not skip the body of an actor initializer.
2836+
// they are checked to determine delegation status
2837+
if (auto *ctor = dyn_cast<ConstructorDecl>(AFD))
2838+
if (auto *nom = ctor->getParent()->getSelfNominalTypeDecl())
2839+
if (nom->isAnyActor())
2840+
return false;
2841+
28352842
// Skipping all bodies won't serialize anything, so can skip regardless
28362843
if (getASTContext().TypeCheckerOpts.SkipFunctionBodies ==
28372844
FunctionBodySkipping::All)

lib/Sema/TypeCheckStmt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,7 @@ static void checkClassConstructorBody(ClassDecl *classDecl,
17021702
ASTContext &ctx = classDecl->getASTContext();
17031703
bool wantSuperInitCall = false;
17041704
bool isDelegating = false;
1705+
17051706
auto initKindAndExpr = ctor->getDelegatingOrChainedInitKind();
17061707
switch (initKindAndExpr.initKind) {
17071708
case BodyInitKind::Delegating:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
// This input is useful to ensure the delegation status of
3+
// an actor's initializer does not affect ABI stability
4+
public actor BigFoot {
5+
6+
public let name: String
7+
8+
private init(withName name: String) {
9+
self.name = name
10+
}
11+
12+
public init?() {
13+
#if DELEGATES
14+
self.init(withName: "Sasquatch")
15+
#else
16+
return nil
17+
#endif
18+
}
19+
}

0 commit comments

Comments
 (0)