The general consensus for Swift programming (as at May 2018, Swift 4.1, Xcode 9.3) is that structs should be preferred unless your logic explicitly calls for a shared reference to an object.
As we know, a problem with structs is that they're passed by-value, and so a copy is made when you pass a struct into, or return from a function. If you have a large struct (say with 12 properties in it) then this copying could get expensive.
This is usually defended by people saying that the swift compiler and/or LLVM can elide the copies (I.e. pass a reference to a struct, rather than copying it) and only needs to make a copy if you actually mutate the struct.
This is all well and good, but it's always talked about in theoretical terms - "As an optimisation, LLVM could elide the copies" and stuff like that.
My question is, can anyone tell us what actually happens? Does the compiler actually elide the copies, or is it just a theoretical future optimization that might exist one day? (For example, the C# compiler could also theoretically elide struct copies, but it never actually does this, and Microsoft recommends you don't use structs for things larger than 16 bytes [1])
If swift does elide struct copies, is there some explanation or heuristic as to if and when it does this?
Note: I'm talking about user-defined structs, not built in stdlib things like arrays and dictionaries
First, Swift does not use the platform's calling convention. On macOS, C, C++ and Objective-C all use the x86_64 System V ABI, but Swift doesn't. A notable change is that Swift's CC has four return GPRs (rax, rdx, rcx, r8) instead of just two.
It almost certainly gets more complicated when you mix in floating-point numbers, but if you go all integer and integer-like types (like pointers), structures are passed and returned by register, by copy, if they fit in the width of at most 4 registers. Above that, structures are passed and returned by address. In the case of a return value, the caller is responsible for setting up stack space and passing the address of that space to the callee as a hidden parameter.
As the Swift ABI isn't finalized, this is still subject to change, possibly.
However, merely passing pointers doesn't mean that no copies happen. For instance:
In this example, at
-O
on Swift 4.1,withLet
makes the following tradeoff:l.large
is copied to a local temporaryl
is released after the copy and beforedoSomething
is calledA copy would be unavoidable with a mutable or computed property (because their value can change across the duration of a call), but I imagine that it's in the realm of possibilities that
let
constants could be passed by address directly. However, in that case,l
would have to stay alive until afterdoSomething
has returned.