Trying to understand POD (plain old datatypes)

I'm trying to understand some things about plain old datatypes.

  1. My understanding is they are basically value types, that consist of only POD members. So there's a chain, all the way down to the most basic parts of a type, which would then be Int64, Bool, etc. Is that right?

  2. Presumably also, if any type or member is generic, then all it's type parameters must also be PODs?

  3. The point of this distinction is that PODs then don't contain reference types of any kind anywhere in their parts, so copying them and passing them around is just a simple matter of copying the memory. You never need to worry about reference counting, etc. as a result, the value witness table for PODs becomes redundant and most operations can be a no-op. Is that right?

  4. If that's the case, is there a mechanism in the compiler that detects if a type is a POD, and maybe sets a flag on the type, something that I can observe in action?

  5. Finally, if all my above assumptions or understandings are correct, is there an optimiser transformation that takes types, maybe in canonical SIL, and decides if they are PODs... then if so, removes all of the value witness tables/metadata access, etc.?

Sorry if all of these are dumb questions!

1 Like

I’ll let someone else explain more, but a quick note that for Swift types the compiler tends to use the term “trivial” rather than “POD”, as well as “bitwise copyable” and similar. So those are terms to look for in SIL.

2 Likes

Thanks @jrose

Can anyone answer some of my specific questions about POD/trivial types?

There does'nt seem to be a document. Looking for a bit more color....

Int64, Bool, etc., are actually Swift structs themselves that conform to a variety of protocols, and that may contain a member that is a low-level primitive type like an int, float, etc., which is considered an implementation detail.

I'll also leave the details to experts, but from my understanding 1 and 2 are both correct. For 4, you might want to take a look at the _isPOD runtime function (declared in stdlib/public/core/Builtins.swift).

1 Like

https://github.com/apple/swift/blob/main/docs/ABIStabilityManifesto.md#type-properties

Some types have interesting properties:

  • A type is trivial, also known as POD ("plain ol' data"), if it merely stores data and has no extra copy, move, or destruction semantics. Trivial objects can be copied by replicating their bits, and are destroyed through deallocation. A type is trivial only if all data members are also trivial.
  • A type is bitwise movable if there are no side table references dependent on its address. A move operation can occur when an object is copied from one location into another and the original location is no longer used. Bitwise movable objects are moved by performing a bitwise copy and then invalidating the original location. A type is bitwise movable only if all its data members are also bitwise movable. All trivial types are bitwise movable.

An example of a trivial type is a Point struct that contains two Double fields: an x coordinate and a y coordinate. This struct is trivial, as it can be copied merely by copying its bits and its destruction performs no extra operations.

An example of a bitwise movable, but non-trivial, type is a struct that contains a reference to a class instance. Objects of that type cannot be copied merely by copying their bits, because a retain operation must be performed on the reference. Upon destruction, such objects must perform a release. However, the object can be moved from one address to another by copying its bits provided the original location is invalidated, keeping the overall retain count unchanged.

An example of a type that is neither trivial nor bitwise movable is a struct containing a weak reference. Weak references are tracked in a side table so that they can be nil-ed out when the referenced object is destroyed. When moving an object of such type from one address to another, the side table must be updated to refer to the weak reference's new address.

13 Likes

Great reply @benrimmington. Love it!! (I particularly like that you mentioned bitwise movable, which is something I would not have thought to look at and might have confused me.)

I think I'm pretty much at the point of grepping the swift source code for any further answers.

That said... one more question for anyone. :slight_smile: ...

Does anyone know where to look for transformations that remove value witness table access for POD datatypes?

(In case it wasn't clear, I was just linking to and quoting from the ABI Stability Manifesto document, which was compiled by @Michael_Ilseman.)

2 Likes

Do you have any more questions regarding POD, ABI, and the compilation process?

The witness table entries still have to exist to support run-time generics. My guess would be that this falls out naturally from regular inlining and devirtualization. CC @Michael_Gottesman who is more familiar with the details of the SIL optimizer.

I think that we do get rid of some witness table entires at the SIL level. For value witness table info this is the sort of thing that isn't exposed at the SIL level today, so LLVM would have to be the place (today) where it would be implemented.

I think you guys have covered the basics. The next step is for me to go through the source with all this knowledge.

I suppose one more confusion I have...

If a type is sort of POD-ish but it has a generic type parameter, it cannot be a POD because the generic type used in some cases might be a class or a closure, etc.

I have always sort of assumed that in the type system the generic version of a type and specialisations of it are kept as separate types internally in the compiler. Presumably each has its own, separate, isPOD and bitwiseTakeable flags?

In client code, if the developer writes code, specifying a simple POD type for the generic type parameter, will that set the isPOD flag on the specialised type? And will it then create simpler value witness table entries. Like pass through entries?

Sorry if my question is not clear!

Specialization has layout constraints such as @_specialize(where T: _Trivial).

There are some examples in test/attr/attr_specialize.swift, but not in the standard library.

1 Like

Also looking for this. Thanks. Any further update?

Apologies for summoning this thread from the past, I have a question about dynamically sized value types: String, Array, etc.

On one hand they aren't reference types (formally), and yet they aren't POD (_isPOD(String.self) returns false).

Is the reason for this that they aren't fixed size? Or is it not about the size but about some operation that must happen when copying a string (or an array)?

String and Array are fixed size. String is 16 bytes and Array is 8 bytes on a 64-bit platform. They are not POD because they store a reference to a heap-allocated buffer; when a String or Array is copied or destroyed, the reference count on this buffer must be updated.

3 Likes