Skip to content

Commit 266ea3f

Browse files
Disable stack promotion for classes with isolated deinit
1 parent 85c9a89 commit 266ea3f

File tree

6 files changed

+42
-28
lines changed

6 files changed

+42
-28
lines changed

SwiftCompilerSources/Sources/AST/Declarations.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ final public class StructDecl: NominalTypeDecl {
5757

5858
final public class ClassDecl: NominalTypeDecl {
5959
public var superClass: Type? { Type(bridgedOrNil: bridged.Class_getSuperclass()) }
60+
61+
final public var destructor: DestructorDecl {
62+
bridged.Class_getDestructor().getAs(DestructorDecl.self)
63+
}
6064
}
6165

6266
final public class ProtocolDecl: NominalTypeDecl {}
@@ -85,7 +89,9 @@ public class AbstractFunctionDecl: ValueDecl {}
8589

8690
final public class ConstructorDecl: AbstractFunctionDecl {}
8791

88-
final public class DestructorDecl: AbstractFunctionDecl {}
92+
final public class DestructorDecl: AbstractFunctionDecl {
93+
final public var isIsolated: Bool { bridged.Destructor_isIsolated() }
94+
}
8995

9096
public class FuncDecl: AbstractFunctionDecl {}
9197

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/StackPromotion.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import AST
1314
import SIL
1415

1516
/// Promotes heap allocated objects to the stack.
@@ -74,6 +75,17 @@ private func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
7475
return false
7576
}
7677

78+
if let dtor = (allocRef.type.nominal as? ClassDecl)?.destructor {
79+
if dtor.isIsolated {
80+
// Classes (including actors) with isolated deinit can escape implicitly.
81+
//
82+
// We could optimize this further and allow promotion if we can prove that
83+
// deinit will take fast path (i.e. it will not schedule a job).
84+
// But for now, let's keep things simple and disable promotion conservatively.
85+
return false
86+
}
87+
}
88+
7789
// The most important check: does the object escape the current function?
7890
if allocRef.isEscaping(context) {
7991
return false

include/swift/AST/ASTBridging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ struct BridgedDeclObj {
334334
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedDeclObj NominalType_getValueTypeDestructor() const;
335335
BRIDGED_INLINE bool Struct_hasUnreferenceableStorage() const;
336336
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType Class_getSuperclass() const;
337+
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj Class_getDestructor() const;
338+
BRIDGED_INLINE bool Destructor_isIsolated() const;
337339
};
338340

339341
struct BridgedASTNode {

include/swift/AST/ASTBridgingImpl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ BridgedASTType BridgedDeclObj::Class_getSuperclass() const {
8888
return {getAs<swift::ClassDecl>()->getSuperclass().getPointer()};
8989
}
9090

91+
BridgedDeclObj BridgedDeclObj::Class_getDestructor() const {
92+
return {getAs<swift::ClassDecl>()->getDestructor()};
93+
}
94+
95+
bool BridgedDeclObj::Destructor_isIsolated() const {
96+
auto dd = getAs<swift::DestructorDecl>();
97+
auto ai = swift::getActorIsolation(dd);
98+
return ai.isActorIsolated();
99+
}
100+
91101
//===----------------------------------------------------------------------===//
92102
// MARK: BridgedSubscriptDecl
93103
//===----------------------------------------------------------------------===//

test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ class ClassNoOp: Probe {
126126

127127
let tests = TestSuite("Isolated Deinit")
128128

129-
// Dummy global variable to suppress stack propagation
130-
// TODO: Remove it after disabling allocation on stack for classes with isolated deinit
131-
var x: AnyObject? = nil
132-
func preventAllocationOnStack(_ object: AnyObject) {
133-
x = object
134-
x = nil
135-
}
136-
137129
if #available(SwiftStdlib 5.1, *) {
138130
tests.test("class sync fast path") {
139131
let group = DispatchGroup()
@@ -142,7 +134,7 @@ if #available(SwiftStdlib 5.1, *) {
142134
// FIXME: isolated deinit should be clearing task locals
143135
await TL.$number.withValue(42) {
144136
await AnotherActor.shared.performTesting {
145-
preventAllocationOnStack(ClassNoOp(expectedNumber: 42, group: group))
137+
_ = ClassNoOp(expectedNumber: 42, group: group)
146138
}
147139
}
148140
}
@@ -154,7 +146,7 @@ if #available(SwiftStdlib 5.1, *) {
154146
group.enter(1)
155147
Task {
156148
TL.$number.withValue(99) {
157-
preventAllocationOnStack(ClassNoOp(expectedNumber: 0, group: group))
149+
_ = ClassNoOp(expectedNumber: 0, group: group)
158150
}
159151
}
160152
group.wait()
@@ -168,7 +160,7 @@ if #available(SwiftStdlib 5.1, *) {
168160
TL.$number.withValue(99) {
169161
// Despite last release happening not on the actor itself,
170162
// this is still a fast path due to optimisation for deallocating actors.
171-
preventAllocationOnStack(ActorNoOp(expectedNumber: 99, group: group))
163+
_ = ActorNoOp(expectedNumber: 99, group: group)
172164
}
173165
}
174166
group.wait()
@@ -180,7 +172,7 @@ if #available(SwiftStdlib 5.1, *) {
180172
Task {
181173
TL.$number.withValue(99) {
182174
// Using ProxyActor breaks optimization
183-
preventAllocationOnStack(ProxyActor(expectedNumber: 0, group: group))
175+
_ = ProxyActor(expectedNumber: 0, group: group)
184176
}
185177
}
186178
group.wait()
@@ -190,8 +182,8 @@ if #available(SwiftStdlib 5.1, *) {
190182
let group = DispatchGroup()
191183
group.enter(2)
192184
Task {
193-
preventAllocationOnStack(ActorNoOp(expectedNumber: 0, group: group))
194-
preventAllocationOnStack(ClassNoOp(expectedNumber: 0, group: group))
185+
_ = ActorNoOp(expectedNumber: 0, group: group)
186+
_ = ClassNoOp(expectedNumber: 0, group: group)
195187
}
196188
group.wait()
197189
}

test/Concurrency/voucher_propagation.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,6 @@ func adopt(voucher: voucher_t?) {
266266
os_release(voucher_adopt(os_retain(voucher)))
267267
}
268268

269-
// Dummy global variable to suppress stack propagation
270-
// TODO: Remove it after disabling allocation on stack for classes with isolated deinit
271-
var x: AnyObject? = nil
272-
func preventAllocationOnStack(_ object: AnyObject) {
273-
x = object
274-
x = nil
275-
}
276-
277269
let tests = TestSuite("Voucher Propagation")
278270

279271
if #available(SwiftStdlib 5.1, *) {
@@ -444,15 +436,15 @@ if #available(SwiftStdlib 5.1, *) {
444436
Task {
445437
await AnotherActor.shared.performTesting {
446438
adopt(voucher: v1)
447-
preventAllocationOnStack(ClassWithIsolatedDeinit(expectedVoucher: v1, group: group))
439+
_ = ClassWithIsolatedDeinit(expectedVoucher: v1, group: group)
448440
}
449441
await AnotherActor.shared.performTesting {
450442
adopt(voucher: v2)
451-
preventAllocationOnStack(ActorWithSelfIsolatedDeinit(expectedVoucher: v2, group: group))
443+
_ = ActorWithSelfIsolatedDeinit(expectedVoucher: v2, group: group)
452444
}
453445
await AnotherActor.shared.performTesting {
454446
adopt(voucher: v3)
455-
preventAllocationOnStack(ActorWithDeinitIsolatedOnAnother(expectedVoucher: v3, group: group))
447+
_ = ActorWithDeinitIsolatedOnAnother(expectedVoucher: v3, group: group)
456448
}
457449
}
458450
group.wait()
@@ -467,11 +459,11 @@ if #available(SwiftStdlib 5.1, *) {
467459
Task {
468460
do {
469461
adopt(voucher: v1)
470-
preventAllocationOnStack(ActorWithDeinitIsolatedOnAnother(expectedVoucher: v1, group: group))
462+
_ = ActorWithDeinitIsolatedOnAnother(expectedVoucher: v1, group: group)
471463
}
472464
do {
473465
adopt(voucher: v2)
474-
preventAllocationOnStack(ClassWithIsolatedDeinit(expectedVoucher: v2, group: group))
466+
_ = ClassWithIsolatedDeinit(expectedVoucher: v2, group: group)
475467
}
476468
}
477469
group.wait()

0 commit comments

Comments
 (0)