Swift Argument Passing Protocol

Something that is not particularly well-documented in the Swift project is how to call swift functions. Swift has a relatively simple argument passing protocol, but the simplicity is ruined by special cases that make it rather inconvenient to work with at times.

First, let’s start with the simplicity. Swift has two general ways to pass arguments: by reference or by value. Generally speaking, value types get passed by value and reference types get passed by reference. See, isn’t that easy?

So what is a reference type in Swift? Simple: any class is a reference type. What’s a value type? Simple, structs, enums, and scalar types (Bool, Int, Float, etc).

Now the special cases – value types get passed by value if and only if the size of the value type is 3 machine pointers or fewer, if they exceed that then a copy will get passed by reference. Why is this a special case? Because pretty much every current ABI for current processors says that value types are passed by value if the size of the value type is 2 machine pointers or fewer. Great. Thanks, Swift, you’re the best.

Now, when I say that values get passed – there’s more to it. Since Swift uses reference counting for its memory management, there is infrastructure that you need to care about quite a bit. Whenever a function gets called in Swift, the caller will bump the reference count of any reference types (or reference types contained within value types) and the callee with drop the reference count before returning. The problem with this, and this is an important one, is that if you are calling a Swift function, you must bump the reference count of everything you pass in. If you don’t, the Swift with dutifully decrement the reference count on the way out and may cause the object to get disposed out from underneath you.

OK great – supposing that I have a Swift data type, how do I make this happen? If you have a reference type, this is easy. In the library libSwiftCore, there is an exported function called swift_retain to do just this. To drop the count (if you were called by Swift), you would call swift_release. The problem (and trust me, there are problems every step of the way) is when you get called by Swift. You’re supposed to drop the reference count on the way out. Fortunately, swift_release is also there for you.

Great, we have swift_retain and swift_release in our tool belt. We’re all set, right? Nope. Like I mentioned, we care quite a bit about value types that contain reference types. You would like swift_retain and swift_release to work on value types too, but they don’t – and there’s a really good reason why. Value types are supposed to contain the minimum amount of information possible. For example, in Swift a Bool is really one bit (usually padded out to a byte or more). Swift doesn’t attach the overhead of object layout to value types. Instead, for every type in Swift, there are a couple of data structures available to the compiler/runtime that are used for that information. The main three are the Metatype (or Metadata), Nominal Type Descriptor, and Value Witness Table.  Between these three blocks of data in Swift, you can find the following information:

  • Fields
  • Field Types
  • Size of the type
  • How to manipulate the data type

To get the Metatype of any particular data type in Swift, there is a function you can access a symbol something like __TMd<more mangling> which is the type Metadata for that type. The Metatype has a pointer to the nominal type descriptor, which will tell you the size of the type.

The Value Witness Table can also be accessed from a function associated with the type. It will be called something like __TWV<more mangling>. It’s not a function (no F after the T), it is a symbol associated with the table itself. Wait. Value Witness What? What the heck is it?

It is a set of 19 function pointers to methods that do a set of operations on Swift value types, such as “copy this Swift value type to this uninitialized location” or “copy this Swift value type to this previously initialized location” or “take this Swift value type from this location (dropping reference counts) and copy it to this uninitialized location”. And in here are the pieces that you need to effect a swift_retain or swift_release on a value type. When Swift calls swift with a value type, it uses these methods (usually inlined) to do a copy-to-uninitialized for passing the value type.  The callee will do the similar call to do a take operation, which will drop the reference count.

So if you want to pass a Swift value type to a Swift method, you will need to make a scratch copy using one of the copy-to-uninitialized flavors in the Value Witness Table of the type.

Great! Fantastic – all set, ready to go!

Not so fast. Things get interesting if you’re dealing with generic types.

See, the argument passing rules change quite a bit with generic types.

If you’re calling a function with generic arguments in Swift, all generic types are passed by reference, even value types. This makes sense because if I have the following function in Swift:

 

public struct Foo<T> {
    public var x:T
    public init(a:T) {
        x = a
    }
}
public func printX<T>(value:Foo<T>) {
    print(value.x)
}

How big is value in printX? Does is fit in 3 machine pointers or fewer? We don’t know. Sorry, sucks to be you.
The question is, how does Swift know how to do this? You would think, “I know, I just need to get the Metatype and the Value Witness table and then I can…” Not so fast. In generic types, these symbols don’t exist. To get the Metatype, you need to call a function to get it. This function is named something like __TFMa<more mangling> which is the Metatype accessor function, but you can’t simply call it as a function of no arguments. And here, once again, Swift gets, well interesting. Usually, Swift functions are explicit in the name mangling as to how many arguments they take and the types. Any function that takes generic arguments may take extra implicit arguments tacked onto the end. The Metatype accessor is one of those. If a Swift type takes 0 generic arguments, the Metatype accessor takes 0 extra arguments. If the Swift type has generic parameters, then the Metatype accessor takes 1 argument for every generic parameter on that type and that argument is…ta-da, the Metatype for the generic specialization. So if I’m calling the Metatype accessor for Foo, I need to pass in the Metatype for Int. This will return a (possibly) dynamically constructed Metatype for Foo. Great – so there must be a similar method in there to get the Value Witness Table too, right? No. Sorry. No soup for you.
If you want to get the Value Witness Table of a generic type, you first need to get the Metatype. Then you need to look one machine pointer behind that to get a pointer to the Value Witness Table. In fact, if you’re accessing a non-generic type’s Metadata through the __TMd… symbol, a pointer to the Value Witness Table is stored a machine pointer behind it as well. Nifty.
Did I mention special cases earlier? I think I did. Let’s have some more.
Suppose I want to call printX() as outlined above. I’m sure you’ve figured out that I can’t just call it because printX needs the Metatype of T in order to get the Value Witness Table in order to drop a reference count on value on the value out, if needed. So as it turns out, given any generic Swift function, you need to pass in the metatypes of the generic parameters, one for each unique parameter after all the regular parameters. But that’s OK, since we now know how to do that.
But.
Did I mention special cases? Yeah. Suppose that I have this:

public func myFunc<T>(a:someClass<T>, b: T) {
    a.performFabulousTrick(b);
}

How many extra arguments are there? 1? Yes? No. 0. See, when you have a reference type specialized on a generic parameter, the Metatype of that generic parameter is embedded within the instance itself and the Swift compiler knows how to get at it, so it’s not needed as a parameter to myFunc.
So the rule is this: for every generic parameter in a function, f, you need to pass in the Metatype for the specialization of the type appended after all the arguments, unless that generic parameter is specialized in a reference type parameter.

So if you’re thinking to yourself, hey – there’s this cool Swift library that I’d like to call from my code – it’s just a function, right? Think again. First, the Swift ABI is not compatible with standard platform ABIs. Second, some of the other calling conventions get interesting because of reference counting. Third and finally, generics will make your life very interesting. So why did you want to do this again?

One thought on “Swift Argument Passing Protocol”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.