Skip to content

Commit beeb45d

Browse files
committed
[IRGen] Look for a specialized deinit when forming a call in IRGen
When outlining a destroy operation, we form direct calls to the deinit of noncopyable types. For generic types, this was always calling into unspecialized generics, which is... deeply unfortunate. Look for a specialized deinit and call that instead. This eliminates a compiler assertion in Embedded Swift, and should improve performance with noncopyable generics elsewhere. Fixes rdar://159054138 and swiftlang#72627 / rdar://157131184.
1 parent ae8e95d commit beeb45d

File tree

3 files changed

+226
-5
lines changed

3 files changed

+226
-5
lines changed

lib/IRGen/GenType.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "swift/Basic/Platform.h"
2727
#include "swift/Basic/SourceManager.h"
2828
#include "swift/IRGen/Linking.h"
29+
#include "swift/SIL/GenericSpecializationMangler.h"
2930
#include "swift/SIL/SILModule.h"
3031
#include "llvm/IR/DerivedTypes.h"
3132
#include "llvm/ADT/SmallString.h"
@@ -2937,20 +2938,34 @@ static bool tryEmitDeinitCall(IRGenFunction &IGF,
29372938
return true;
29382939
}
29392940

2941+
auto deinitSILFn = deinitTable->getImplementation();
2942+
2943+
// Look for a specialization of deinit that we can call.
2944+
auto substitutions = ty->getContextSubstitutionMap();
2945+
if (!substitutions.empty() &&
2946+
!substitutions.getRecursiveProperties().hasArchetype()) {
2947+
Mangle::GenericSpecializationMangler mangler(
2948+
deinitSILFn->getASTContext(), deinitSILFn,
2949+
deinitSILFn->getSerializedKind());
2950+
2951+
auto specializedName = mangler.mangleReabstracted(
2952+
substitutions, /*needAlternativeMangling=*/false);
2953+
auto specializedFn = IGF.IGM.getSILModule().lookUpFunction(specializedName);
2954+
if (specializedFn)
2955+
deinitSILFn = specializedFn;
2956+
}
2957+
29402958
// The deinit should take a single value parameter of the nominal type, either
29412959
// by @owned or indirect @in convention.
2942-
auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitTable->getImplementation(),
2943-
NotForDefinition);
2944-
auto deinitTy = deinitTable->getImplementation()->getLoweredFunctionType();
2960+
auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitSILFn, NotForDefinition);
2961+
auto deinitTy = deinitSILFn->getLoweredFunctionType();
29452962
auto deinitFP = FunctionPointer::forDirect(IGF.IGM, deinitFn,
29462963
nullptr, deinitTy);
29472964
assert(deinitTy->getNumParameters() == 1
29482965
&& deinitTy->getNumResults() == 0
29492966
&& !deinitTy->hasError()
29502967
&& "deinit should have only one parameter");
29512968

2952-
auto substitutions = ty->getContextSubstitutionMap();
2953-
29542969
CalleeInfo info(deinitTy,
29552970
deinitTy->substGenericArgs(IGF.getSILModule(),
29562971
substitutions,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend %s -g -enable-experimental-feature Embedded -c -o %t/main.o
3+
// REQUIRES: swift_in_compiler
4+
// REQUIRES: swift_feature_Embedded
5+
6+
// https://github.com/swiftlang/swift/issues/72627 - crash with noncopyable
7+
// generic deinit.
8+
9+
final class Box<T: ~Copyable> {
10+
var value: T
11+
init(_ value: consuming T) {
12+
self.value = value
13+
}
14+
}
15+
16+
struct ListNode<Element: ~Copyable>: ~Copyable {
17+
typealias Link = Box<ListNode<Element>>?
18+
19+
var element: Element
20+
var next: Link
21+
}
22+
23+
struct List<Element: ~Copyable>: ~Copyable {
24+
typealias Link = Box<ListNode<Element>>?
25+
26+
var head: Link = nil
27+
28+
init(head: consuming Link = nil) {
29+
self.head = head
30+
}
31+
32+
mutating func push(_ element: consuming Element) {
33+
self = Self(head: Box(ListNode(element: element, next: self.head)))
34+
}
35+
}
36+
37+
public func main() {
38+
var myList = List<Int>()
39+
myList.push(1)
40+
let _ = consume myList
41+
}
42+
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
// RUN: %target-swift-frontend %t/Library.swift -g -enable-experimental-feature Embedded -enable-experimental-feature Lifetimes -c -parse-as-library -o %t/Library.o -emit-module
4+
// RUN: %target-swift-frontend -I %t %t/Application.swift -g -enable-experimental-feature Embedded -enable-experimental-feature Lifetimes -c -o %t/main.o
5+
// RUN: %target-clang %target-clang-resource-dir-opt %t/main.o -o %t/a.out -dead_strip
6+
// RUN: %target-run %t/a.out | %FileCheck %s
7+
// REQUIRES: swift_in_compiler
8+
// REQUIRES: executable_test
9+
// REQUIRES: swift_feature_Embedded
10+
// REQUIRES: swift_feature_Lifetimes
11+
12+
//--- Library.swift
13+
@safe public struct UniqueBuffer<Element: ~Copyable>: ~Copyable {
14+
@usableFromInline
15+
let buffer: UnsafeMutableBufferPointer<Element>
16+
17+
private init(_uninitializedCount count: Int) {
18+
buffer = UnsafeMutableBufferPointer.allocate(capacity: count)
19+
}
20+
21+
@inline(__always)
22+
@_alwaysEmitIntoClient
23+
deinit {
24+
buffer.deinitialize().deallocate()
25+
}
26+
27+
/// Allocate a new buffer with `count` elements and call the given `body` function to produce an element
28+
/// for each entry.
29+
public init<E>(count: Int, body: (Int) throws(E) -> Element) throws(E) {
30+
self.init(_uninitializedCount: count)
31+
32+
for i in 0..<count {
33+
do throws(E) {
34+
buffer[i] = try body(i)
35+
} catch {
36+
// The closure threw an error. We need to deinitialize every element we've initialized up to this point.
37+
for j in 0 ..< i {
38+
buffer.deinitializeElement(at: j)
39+
}
40+
41+
throw error
42+
}
43+
}
44+
}
45+
46+
/// The number of elements in the buffer.
47+
public var count: Int { buffer.count }
48+
49+
/// Access the ith element in the buffer.
50+
public subscript(_ i: Index) -> Element {
51+
unsafeAddress {
52+
precondition(i >= 0 && i < count)
53+
return UnsafePointer(buffer.baseAddress! + i)
54+
}
55+
56+
unsafeMutableAddress {
57+
precondition(i >= 0 && i < count)
58+
return buffer.baseAddress! + i
59+
}
60+
}
61+
62+
/// Index into this data structure.
63+
public typealias Index = Int
64+
65+
/// Indices into this buffer.
66+
public var indices: Range<Int> { 0..<count }
67+
68+
/// Produce a span covering all of the elements in the buffer.
69+
public var span: Span<Element> {
70+
@_lifetime(borrow self)
71+
borrowing get {
72+
buffer.span
73+
}
74+
}
75+
76+
/// Produce a mutable span covering all of the elements in the buffer.
77+
public var mutableSpan: MutableSpan<Element> {
78+
@_lifetime(&self)
79+
mutating get {
80+
buffer.mutableSpan
81+
}
82+
}
83+
84+
/// Run the body closure with an unsafe buffer pointer referencing the storage of this unique buffer.
85+
///
86+
/// Clients should prefer the `mutableSpan` property, which provides memory safety.
87+
@unsafe public mutating func withUnsafeMutableBufferPointer<T, E>(_ body: (UnsafeMutableBufferPointer<Element>) throws(E) -> T) throws(E) -> T {
88+
try body(buffer)
89+
}
90+
}
91+
92+
extension UniqueBuffer {
93+
/// Allocate a buffer with `count` elements, all of which are a copy of `Element`.
94+
public init(repeating element: Element, count: Int) {
95+
self.init(_uninitializedCount: count)
96+
buffer.initialize(repeating: element)
97+
}
98+
99+
/// Allocate a buffer that contains a copy of the elements in the given collection.
100+
public init(_ collection: some Collection<Element>) {
101+
self.init(_uninitializedCount: collection.count)
102+
_ = buffer.initialize(fromContentsOf: collection)
103+
}
104+
}
105+
106+
public enum BufferWrapper: ~Copyable {
107+
case buffer(UniqueBuffer<Int>)
108+
case empty
109+
}
110+
111+
extension BufferWrapper {
112+
public init(repeating: Int, count: Int) {
113+
self = .buffer(UniqueBuffer<Int>(repeating: 17, count: 15))
114+
}
115+
116+
public var count: Int {
117+
switch self {
118+
case .buffer(let unique): unique.count
119+
case .empty: 0
120+
}
121+
}
122+
123+
public subscript(index: Int) -> Int {
124+
switch self {
125+
case .buffer(let unique): unique[index]
126+
case .empty: fatalError("boom")
127+
}
128+
}
129+
}
130+
131+
public struct BufferOfWrappers: ~Copyable {
132+
let inner: UniqueBuffer<BufferWrapper>
133+
134+
public init() {
135+
inner = UniqueBuffer(count: 17) { index in
136+
.empty
137+
}
138+
}
139+
140+
public func countEm() -> Int {
141+
return inner.count
142+
}
143+
}
144+
145+
//--- Application.swift
146+
import Library
147+
148+
func test() {
149+
let bufferWrapper = BufferWrapper(repeating: 17, count: 16)
150+
var sum = 0
151+
for i in 0..<bufferWrapper.count {
152+
sum += bufferWrapper[i]
153+
}
154+
print(bufferWrapper.count)
155+
print(sum)
156+
157+
let anotherBuffer = BufferOfWrappers()
158+
print(anotherBuffer.countEm())
159+
}
160+
161+
test()
162+
// CHECK: 15
163+
// CHECK: 17
164+

0 commit comments

Comments
 (0)