What do @guaranteed and @owned mean in SIL, which does Swift use by default for parameters and initialisers, and how does that explain retain and release traffic?
Predict first
What conventions does SILGen show for plain, taking and Holder.init?
import Swift
final class Box { var v = 0 }
@inline(never) func plain(_ b: Box) -> Int { b.v }
@inline(never) func taking(_ b: consuming Box) -> Int { b.v }
final class Holder { var b: Box; init(_ b: Box) { self.b = b } }
- plain: @guaranteed Box; taking: @owned Box; init: @owned Box
- all three @guaranteed
- all three @owned
- plain: @owned; taking: @guaranteed
Mental model. @guaranteed is lending a book that must be back on the shelf when the call ends. @owned is giving the book away: the receiver now has to shelve it or throw it out.
Every parameter in SIL has an ownership convention. @guaranteed means the caller keeps ownership and guarantees the value stays alive for the whole call (a “+0” argument): the callee may use it but must retain it itself if it wants to keep it. @owned means ownership is transferred to the callee (a “+1” argument), and the callee must consume or release it. The defaults are chosen for the common case: ordinary function and method parameters, and self, are @guaranteed, so passing a class reference to a function does not by itself retain it; initialiser arguments are @owned, because an initialiser usually stores them; and consuming changes a parameter to @owned, while borrowing is the explicit form of @guaranteed. In my output plain(_:) took @guaranteed Box, taking(_:) with consuming took @owned Box, and Holder.init took @owned Box.
That is why retain and release calls cluster where ownership changes: storing into a property, returning a value, or passing into an initialiser or consuming parameter, while a read-only call can avoid them. The optimiser also removes balanced pairs (an OSSA-based pass), so a count of swift_retain in -Onone says little about release performance. Use the conventions as a reading tool, not a promise: they are an implementation detail except where you spell out borrowing and consuming, which are the language-level ones.
Defaults are guaranteed for arguments, owned for initialiser arguments and consuming.
Common traps
- Counting retains in debug SIL and drawing performance conclusions. Look at
-Ooutput. - Adding
consumingto “speed things up” on a copyable type. It can force the caller to copy; use it where you really hand ownership over.
They might follow up with
- What does
inoutlook like? An@inoutaddress convention: exclusive access, no ownership transfer. - Where do noncopyable types fit? Their conventions are enforced, see the consume and borrowing question.
Related questions
- 1.8.5 What are consume, borrowing, and noncopyable types, at a high level?
- 5.1.4 How do you read SIL to tell static dispatch from dynamic dispatch?
- 3.2.1 What is the difference between strong, weak, unowned and unowned(unsafe)?
- 5.2.4 What is exclusivity enforcement, and what does it cost at runtime?
← Where does a captured variable live: on the stack or in a heap box… How big are Optional, enums and existentials in memory, and why is… →