Swift structs are exposed by value to C++, but Swift structs sometimes have a size less than their stride, whereas C++ types always have their size rounded up to their stride:
struct Bad {
var first: UInt16
var second: UInt8
// C++ would pad this to 4 bytes, Swift does not
}
For the most part this is fine, but if a pointer is ever created to a Swift-side allocation and accessed from C++, bad things could happen. Does anything prevent that? Are Swift types always copied/moved elementwise, for example?
(Sorry if this is already answered somewhere, I didn’t see it in the big interop doc.)
C++ has a way to mitigate this within a larger class, right?
typedef struct {
uint16_t a;
uint8_t b;
} foo;
class bar {
[[no_unique_address]] foo m_foo; // or [[msvc::no_unique_address]]
uint8_t m_c;
};
int main() {
std::cout << sizeof (bar) << std::endl; // 4
// would be 6 without the attribute
}
Is it possible to apply this attribute automatically?
That’s a good point, I didn’t think about embedding into a larger type having some of the same problems. In general though it should always be fine to spend a little extra space in the C++ side; the problem happens if (a) C++ and Swift have different layouts for the fields of a larger type, (b) C++ tries to load from a Swift-managed address and the padding is unloadable (probably only affects ASan), or (c) C++ tries to write to the Swift storage and overwrites subsequent memory with garbage.
We know the struct can’t end on a page boundary because of its alignment (well, as long as both page sizes and alignments are required to be powers of two, which they are).
Either it uses 128-bit alignment, or it uses 32-bit or 64-bit alignment. But yes, I don’t know if Float80 is considered to have a size of 80 bits in Swift; it certainly does not have a stride of 80 bits. That makes it equivalent to the nested struct case, but with the twist that Float80 is a valid primitive type according to C++, so we get less control over it from the Swift side.
Swift structures are not represented using raw C++ structures that care about the specific field layout so the padding shouldn't matter. They're represented using opaque storage buffer allocated inline or on the heap with potential additional ownership flags. All stored properties are now also accessed through property getter accessors. We also currently don't support interoperability of pointers to Swift structures, or borrowing Swift owned structures in C++, although borrowing a Swift owned/allocated structure will be supported in the future.
One of the promises of the C++ interoperability was that there's no 'translation' layer (FFI et al). Though this is clearly still not that, to such an extent, it does sound like there is an abstraction layer nonetheless?
Does regular inlining (or similar), on the C++ side, effectively eliminate this abstraction layer in most cases? e.g. so that accessing members of a Swift struct containing two integers actually resolves to trivial indexed load instructions?
There's no translation layer, however, Swift value types do not map directly to C++ types so additional logic is required when working with owned or borrowed Swift value types in C++. This is due to the following:
Difference in move semantics - Swift's use of consuming move forces additional ownership tracking on the C++ side, which only supports non-consuming move (not yet implemented though).
Support for resiliency / opaque layout - the layout of some Swift types (resilient value types for example) is not known until runtime, so C++ has to account for that as well when handling Swift value types.
Swift's exclusitivity requirements - Swift code expects exclusive access to mutable values, which also needs to be accounted for on the C++ side when invoking Swift APIs (not yet implemented as well).
Past these semantic requirements, the additional abstraction layer right now is present predominantly when accessing stored properties or copying or destroying values from C++, as they always go through an accessor or a value witness table routine. These abstractions will be optimized in the future, i.e. a field will be accessed directly from C++ when it makes sense to do so instead of going through a property accessor, but we didn't have the bandwidth to work on optimizing them yet.
e.g. so that accessing members of a Swift struct containing two integers actually resolves to trivial indexed load instructions?
Yes, that's the long term goal for structures for which it's possible to do so.
Does regular inlining (or similar), on the C++ side, effectively eliminate this abstraction layer in most cases?
The current support for inlining doesn't work across language boundaries, specifically when going from C++ to Swift. We can inline when invoking C++ APIs from Swift, as Swift embeds Clang and emits IR into the same compilation unit. In the future it might be possible to support cross-language LTO to also help with inlining calls to Swift from C++.