[cxx-interop] Synthesize C++ wrapper method when invoking a base method or accessor from a derived class in Swift #69222
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Explanation:
In order to provide access to base class members in an imported derived class in Swift, the clang importer in Swift clones member signatures (for methods, subscripts and property accessors) and synthesizes a Swift thunk that invokes the base member. The previous implementation for method calls and getter accessors relied on casting the derived Swift type to the base Swift type before invoking the base member using the
__swift_interopStaticCast
helper function. However, this meant that Swift copied the base C++ class, sometimes more than once during this process, i.e. the base member got invoked on a copy of a base type, and not the underlying base value inside of the original derived value. This caused numerous problems in some adopters, e.g. it was impossible to iterate over a subclass of a C++ container that used basebegin
andend
, as the iterator for sequence traversal that Swift constructed (in the C++ overlay) obtained begin and end iterators from copies of the container, which were immediately invalid after begin and end returned, as they were just temporary copies in the synthesized methods. This also presented a problem for non-copyable type support, and also was a general correctness/performance issue.This change resolves the issue by changing the way the base member is invoked. Instead of invoking it directly from the synthesized Swift thunk, the importer generates an additional inline C++ member function that invokes the base member or accesses the base field directly from C++, which does the cast of the derived value
this
without copying the base value. This C++ member function is then imported into Swift, and invoked from the synthesized Swift thunk using the derived value directly. This avoids any copies of the base value.This change handles method calls, getter accessors for subscripts and
.pointee
, and also getter accessors for base fields. C++ classes that derive from other classes also behave as expected, although their generated Swift thunks may go through multiple levels of generated derived-to-base C++ functions (e.g. derived-derived member calls derived member calls base member). Also, note that in order to preserve the "copy" semantics of a retainable foreign reference type C++ field, this change also allows the use ofcf_returns_retained
attribute to let C++ methods use@owned
semantics for returned FRTs. Such base field accesses invoke the retain function inside of the generated C++ inline member to ensure that the+1
result convention is established correctly.Fixes: #65876
rdar://115231857
Scope: C++ interop, importing base members of derived C++ classes into Swift.
Risk: Medium, non-trivial change to derived to base calls
Testing: Unit tests, some manual adopter testing
Original PR: #68846
Reviewer: @egorzhdan