Skip to content

Conversation

egorzhdan
Copy link
Contributor

Some foreign reference types such as IUnknown define retain/release operations as methods of the type.

Previously Swift only supported retain/release operations as standalone functions.

The syntax for member functions would be SWIFT_SHARED_REFERENCE(.doRetain, .doRelease).

rdar://160696723

@egorzhdan
Copy link
Contributor Author

@swift-ci please test

static DerivedRefCountedBox *createDerived(int value, int secondValue) {
return new DerivedRefCountedBox(value, secondValue);
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think you might be kind enough to also add some templated ref counted base types along the lines of cntrump/MixingLanguagesInAnXcodeProject@main...adetaylor:MixingLanguagesInAnXcodeProject:demonstrate-refcount-probs? Just to make sure this works (and continues to work) for cases like WebKit's? I suspect that the pattern is fairly common since it's a good way to do reference counting without the overhead of virtual functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, let's test that scenario explicitly.

}

void doRelease() const { refCount--; }
} SWIFT_SHARED_REFERENCE(.doRetainInBase, .doRelease);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when you have a base class FRT with virtual retain and release methods, and the derived class overrides its retain and release functions? And what about for pure virtual methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great point! In case this does not work as intended and not easy to fix, I believe we could just reject virtual refcounting functions for now and fix these in a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works! We should have a test for it indeed, I just added one.

@egorzhdan egorzhdan force-pushed the egorzhdan/retain-release-methods branch from a54fc65 to 7c9856d Compare September 17, 2025 14:55
Xazax-hun
Xazax-hun previously approved these changes Sep 17, 2025
Copy link
Contributor

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, some nits inline.

return RetainReleaseOperationKind::notAfunction;

if (operationFn->getParameters()->size() != 1)
if (operationFn->getParameters()->size() + operationFn->isInstanceMember() != 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we will allow static member functions? E.g.:

struct FRT {
  static void retain(FRT*) { ... }
};

I think we should either reject this or have a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added logic to reject static functions for now. I think we'll need to figure out what the design is for static functions and inheritance before we allow this.


RefCountedBox(int value) : value(value) {}

static RefCountedBox *create(int value) { return new RefCountedBox(value); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we now import ctors of foreign reference types so we could potentially simplify some of these examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, good point, removed these static factories.

// MARK: Retain in a base type, release in templated derived

template <typename T>
struct TemplatedDerivedHasRelease : BaseHasRetain {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to have a test following the curiously recurring template pattern where the base itself is templated. Similar to RefCountedBase in LLVM. We usually need the CRTP for the release operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for that scenario.

@Xazax-hun Xazax-hun dismissed their stale review September 17, 2025 15:10

I actually want the override case discussed/tested before this is merged.

@egorzhdan egorzhdan force-pushed the egorzhdan/retain-release-methods branch 2 times, most recently from 654ddf2 to 9c5de6f Compare September 17, 2025 16:58
@egorzhdan
Copy link
Contributor Author

@swift-ci please test

@egorzhdan
Copy link
Contributor Author

@swift-ci please test Linux

@egorzhdan
Copy link
Contributor Author

@swift-ci please test Windows

Copy link
Contributor

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, thanks!

paramType =
operationFn->getParameters()->get(0)->getInterfaceType();
// Unwrap if paramType is an OptionalType
if (Type optionalType = paramType->getOptionalObjectType()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you might be able to get rid of this branch via lookThroughSingleOptionalType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't actually touch this code in this PR, I'm not sure why GitHub shows it in the diff 😃

let a = RefCountedBox(123)
expectEqual(a.value, 123)
expectTrue(a.refCount > 0)
expectTrue(a.refCount < 10) // optimizations would affect the exact number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could compile via -Onone, although not sure if that produces something that is stable across all platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would still be somewhat susceptible to benign changes in IRGen and to minor differences between platforms. We've had some tests that checked for precise ref counts of these types, and they took a lot of time to maintain the correct expectations for the ref counts.

Some foreign reference types such as IUnknown define retain/release operations as methods of the type.

Previously Swift only supported retain/release operations as standalone functions.

The syntax for member functions would be `SWIFT_SHARED_REFERENCE(.doRetain, .doRelease)`.

rdar://160696723
@egorzhdan egorzhdan force-pushed the egorzhdan/retain-release-methods branch from 9c5de6f to e78ce61 Compare September 22, 2025 11:04
@egorzhdan
Copy link
Contributor Author

@swift-ci please test

@egorzhdan egorzhdan merged commit 6b4c7fc into swiftlang:main Sep 22, 2025
5 checks passed
@egorzhdan egorzhdan deleted the egorzhdan/retain-release-methods branch September 22, 2025 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants