Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[embedded] Handle retain/retain ops inside deinit in Embedded Swift's swift_release #76231

Merged
merged 3 commits into from
Sep 16, 2024

Conversation

kubamracek
Copy link
Contributor

See attached testcase which crashes without the fix due to an infinite recursion. The scenario is that the deinit of the object contains a retain+release pair (on "self"). We need to handle this case in the Embedded Swift runtime's swift_release code.

Fixes #74697.

@kubamracek
Copy link
Contributor Author

@swift-ci please test

@@ -291,6 +291,10 @@ func swift_release_n_(object: UnsafeMutablePointer<HeapObject>?, n: UInt32) {

let resultingRefcount = subFetchAcquireRelease(refcount, n: Int(n)) & HeapObject.refcountMask
if resultingRefcount == 0 {
// Set the refcount to immortalRefCount before calling the object destroyer
// to prevent future retains/releases from having any effect.
storeRelaxed(refcount, newValue: HeapObject.immortalRefCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe worth commenting explicitly that there can only be one thread holding a reference to this object at this point (unless there's a bug somewhere), which is why a relaxed store is fine here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seconded. On another note, the non-embedded runtime continues tracking refcounts, and this allows it to raise a fatal error when self escapes from deinit by checking the refcount when the memory is freed. See here:

swift::fatalError(0,

Obviously this is an optional feature and not something you need to do now (or ever), but might be worth keeping in mind as a possible enhancement.

@@ -291,6 +291,10 @@ func swift_release_n_(object: UnsafeMutablePointer<HeapObject>?, n: UInt32) {

let resultingRefcount = subFetchAcquireRelease(refcount, n: Int(n)) & HeapObject.refcountMask
if resultingRefcount == 0 {
// Set the refcount to immortalRefCount before calling the object destroyer
// to prevent future retains/releases from having any effect.
storeRelaxed(refcount, newValue: HeapObject.immortalRefCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

Seconded. On another note, the non-embedded runtime continues tracking refcounts, and this allows it to raise a fatal error when self escapes from deinit by checking the refcount when the memory is freed. See here:

swift::fatalError(0,

Obviously this is an optional feature and not something you need to do now (or ever), but might be worth keeping in mind as a possible enhancement.

bar = nil
print("OK!")

// CHECK: OK!
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit lost as to how this test actually tests this scenario in question, and I worry it might silently stop testing this scenario with some compiler change. It might be better to use Unmanaged to do an explicit retain/release in a deinit {} block, or assign self to a global, call out to something opaque, then remove it.

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 more targeted test.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks great, thanks!

@kubamracek
Copy link
Contributor Author

@swift-ci please test

@kubamracek kubamracek merged commit 142df19 into swiftlang:main Sep 16, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Swift Embedded: Unexpected crash during class instance deallocation
3 participants