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 coursellvm::SaveAndRestorein LLVM as an ultimate example).
@Andrew_Trick Could you provide more details about what types of semantic limitations are actually useful for the optimizer?