|
| 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 | + |
0 commit comments