Basic Swift ownership-y questions

So far we have been mapping C++ types with non-trivial special members to Swift structs with value witness functions synthesized from the said special members.

However, we have been importing C++ types with non-trivial copy and destructor semantics as address-only, because instances of such types may depend on their address; the optimizer should not be allowed to arbitrarily explode them into constituent parts and then re-materialize at a different address without invoking the corresponding C++ special members. It would be interesting to investigate, to what extent there is a benefit to manually annotating some types as "movable with memmove" or "copyable with memcpy", and allowing them to be imported as loadable.

Taking that into account, right now C++ interop does not have an implication on the semantics of copy_value (or at least I believe so).

However, copy_addr does run arbitrary code (C++ special members, via the corresponding value witness). To what extent is that a problem?

The optimizer wants to be able to add/remove/move copy_addr instructions. I think there is no issue with that. We should expect C++ types in Swift to play by the Swift rules. Specifically, the value witnesses of mapped C++ types (and hence, underlying C++ special members) would not be guaranteed to be invoked at specific program points predictable from the source code. Trying to special-case value or lifetime behavior of C++ types in Swift is going to lead to non-composable effects (either when the C++ type is used in a Swift aggregate, or when it is passed to a Swift generic).

The optimizer wants to assume that copy_addr has weaker side-effects than an arbitrary function call. That is an issue, because C++ special members are arbitrary functions. I think restricting C++ special members to only "locally mutating" ones is going to be difficult, I believe designs that access global memory from special members are not uncommon. For example:

  • lazily initializing a global variable;

  • collecting some statistics about the objects of this type (say, a hash table would want to provide information like the total amount of memory consumed by the hash tables in the process, actual load factor, distribution of probing lengths etc.);

  • RAII objects (the Swift parser has lots of them, for example, swift::Parser::ContextChange, and of course llvm::SaveAndRestore in LLVM as an ultimate example).

@Andrew_Trick Could you provide more details about what types of semantic limitations are actually useful for the optimizer?

1 Like