Skip to content

Commit 142df19

Browse files
authored
Merge pull request #76231 from kubamracek/embedded-deinit-release
[embedded] Handle retain/retain ops inside deinit in Embedded Swift's swift_release
2 parents 7961e8d + b4431db commit 142df19

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

stdlib/public/core/EmbeddedRuntime.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,17 @@ func swift_release_n_(object: UnsafeMutablePointer<HeapObject>?, n: UInt32) {
291291

292292
let resultingRefcount = subFetchAcquireRelease(refcount, n: Int(n)) & HeapObject.refcountMask
293293
if resultingRefcount == 0 {
294+
// Set the refcount to immortalRefCount before calling the object destroyer
295+
// to prevent future retains/releases from having any effect. Unlike the
296+
// full Swift runtime, we don't track the refcount inside deinit, so we
297+
// won't be able to detect escapes or over-releases of `self` in deinit. We
298+
// might want to reconsider that in the future.
299+
//
300+
// There can only be one thread with a reference at this point (because
301+
// we're releasing the last existing reference), so a relaxed store is
302+
// enough.
303+
storeRelaxed(refcount, newValue: HeapObject.immortalRefCount)
304+
294305
_swift_embedded_invoke_heap_object_destroy(object)
295306
} else if resultingRefcount < 0 {
296307
fatalError("negative refcount")
@@ -344,6 +355,9 @@ fileprivate func storeRelease(_ atomic: UnsafeMutablePointer<Int>, newValue: Int
344355
Builtin.atomicstore_release_Word(atomic._rawValue, newValue._builtinWordValue)
345356
}
346357

358+
fileprivate func storeRelaxed(_ atomic: UnsafeMutablePointer<Int>, newValue: Int) {
359+
Builtin.atomicstore_monotonic_Word(atomic._rawValue, newValue._builtinWordValue)
360+
}
347361

348362
/// Exclusivity checking
349363

test/embedded/deinit-release.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend %s -enable-experimental-feature Embedded -O -c -o %t/main.o
3+
// RUN: %target-clang %t/main.o -o %t/a.out -dead_strip
4+
// RUN: %target-run %t/a.out | %FileCheck %s
5+
6+
// REQUIRES: swift_in_compiler
7+
// REQUIRES: executable_test
8+
// REQUIRES: OS=macosx || OS=linux-gnu
9+
10+
class C {}
11+
12+
struct Foo {
13+
let foo: C = C()
14+
let bar: C = C()
15+
}
16+
17+
class Bar {}
18+
class SubBar: Bar {
19+
var qwe = Foo()
20+
}
21+
22+
var bar: SubBar? = SubBar()
23+
bar = nil
24+
print("OK!")
25+
26+
// CHECK: OK!

test/embedded/deinit-release2.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend %s -enable-experimental-feature Embedded -O -c -o %t/main.o
3+
// RUN: %target-clang %t/main.o -o %t/a.out -dead_strip
4+
// RUN: %target-run %t/a.out | %FileCheck %s
5+
6+
// REQUIRES: swift_in_compiler
7+
// REQUIRES: executable_test
8+
// REQUIRES: OS=macosx || OS=linux-gnu
9+
10+
public var global_c: C? = nil
11+
12+
@inline(never) func opaque() { print(global_c == nil ? "global is nil" : "global is not nil") }
13+
14+
public class C {
15+
init() { print("init") }
16+
deinit {
17+
print("deinit")
18+
global_c = self
19+
opaque()
20+
global_c = nil
21+
opaque()
22+
print("end of deinit")
23+
}
24+
}
25+
26+
var bar: C? = C()
27+
bar = nil
28+
print("OK!")
29+
30+
// CHECK: init
31+
// CHECK: deinit
32+
// CHECK: global is not nil
33+
// CHECK: global is nil
34+
// CHECK: end of deinit
35+
// CHECK: OK!

0 commit comments

Comments
 (0)