-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[IRGen] Look for a specialized deinit when forming a call in IRGen #83906
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
Merged
DougGregor
merged 1 commit into
swiftlang:main
from
DougGregor:specialized-deinit-in-destroy-outlining
Aug 26, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// RUN: %empty-directory(%t) | ||
// RUN: %target-swift-frontend %s -g -enable-experimental-feature Embedded -c -o %t/main.o | ||
// REQUIRES: swift_in_compiler | ||
// REQUIRES: swift_feature_Embedded | ||
|
||
// https://github.com/swiftlang/swift/issues/72627 - crash with noncopyable | ||
// generic deinit. | ||
|
||
final class Box<T: ~Copyable> { | ||
var value: T | ||
init(_ value: consuming T) { | ||
self.value = value | ||
} | ||
} | ||
|
||
struct ListNode<Element: ~Copyable>: ~Copyable { | ||
typealias Link = Box<ListNode<Element>>? | ||
|
||
var element: Element | ||
var next: Link | ||
} | ||
|
||
struct List<Element: ~Copyable>: ~Copyable { | ||
typealias Link = Box<ListNode<Element>>? | ||
|
||
var head: Link = nil | ||
|
||
init(head: consuming Link = nil) { | ||
self.head = head | ||
} | ||
|
||
mutating func push(_ element: consuming Element) { | ||
self = Self(head: Box(ListNode(element: element, next: self.head))) | ||
} | ||
} | ||
|
||
public func main() { | ||
var myList = List<Int>() | ||
myList.push(1) | ||
let _ = consume myList | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// RUN: %empty-directory(%t) | ||
// RUN: split-file %s %t | ||
// 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 | ||
// RUN: %target-swift-frontend -I %t %t/Application.swift -g -enable-experimental-feature Embedded -enable-experimental-feature Lifetimes -c -o %t/main.o | ||
// RUN: %target-clang %target-clang-resource-dir-opt %t/main.o -o %t/a.out -dead_strip | ||
// RUN: %target-run %t/a.out | %FileCheck %s | ||
// REQUIRES: swift_in_compiler | ||
// REQUIRES: executable_test | ||
// REQUIRES: swift_feature_Embedded | ||
// REQUIRES: swift_feature_Lifetimes | ||
|
||
//--- Library.swift | ||
@safe public struct UniqueBuffer<Element: ~Copyable>: ~Copyable { | ||
@usableFromInline | ||
let buffer: UnsafeMutableBufferPointer<Element> | ||
|
||
private init(_uninitializedCount count: Int) { | ||
buffer = UnsafeMutableBufferPointer.allocate(capacity: count) | ||
} | ||
|
||
@inline(__always) | ||
@_alwaysEmitIntoClient | ||
deinit { | ||
buffer.deinitialize().deallocate() | ||
} | ||
|
||
/// Allocate a new buffer with `count` elements and call the given `body` function to produce an element | ||
/// for each entry. | ||
public init<E>(count: Int, body: (Int) throws(E) -> Element) throws(E) { | ||
self.init(_uninitializedCount: count) | ||
|
||
for i in 0..<count { | ||
do throws(E) { | ||
buffer[i] = try body(i) | ||
} catch { | ||
// The closure threw an error. We need to deinitialize every element we've initialized up to this point. | ||
for j in 0 ..< i { | ||
buffer.deinitializeElement(at: j) | ||
} | ||
|
||
throw error | ||
} | ||
} | ||
} | ||
|
||
/// The number of elements in the buffer. | ||
public var count: Int { buffer.count } | ||
|
||
/// Access the ith element in the buffer. | ||
public subscript(_ i: Index) -> Element { | ||
unsafeAddress { | ||
precondition(i >= 0 && i < count) | ||
return UnsafePointer(buffer.baseAddress! + i) | ||
} | ||
|
||
unsafeMutableAddress { | ||
precondition(i >= 0 && i < count) | ||
return buffer.baseAddress! + i | ||
} | ||
} | ||
|
||
/// Index into this data structure. | ||
public typealias Index = Int | ||
|
||
/// Indices into this buffer. | ||
public var indices: Range<Int> { 0..<count } | ||
|
||
/// Produce a span covering all of the elements in the buffer. | ||
public var span: Span<Element> { | ||
@_lifetime(borrow self) | ||
borrowing get { | ||
buffer.span | ||
} | ||
} | ||
|
||
/// Produce a mutable span covering all of the elements in the buffer. | ||
public var mutableSpan: MutableSpan<Element> { | ||
@_lifetime(&self) | ||
mutating get { | ||
buffer.mutableSpan | ||
} | ||
} | ||
|
||
/// Run the body closure with an unsafe buffer pointer referencing the storage of this unique buffer. | ||
/// | ||
/// Clients should prefer the `mutableSpan` property, which provides memory safety. | ||
@unsafe public mutating func withUnsafeMutableBufferPointer<T, E>(_ body: (UnsafeMutableBufferPointer<Element>) throws(E) -> T) throws(E) -> T { | ||
try body(buffer) | ||
} | ||
} | ||
|
||
extension UniqueBuffer { | ||
/// Allocate a buffer with `count` elements, all of which are a copy of `Element`. | ||
public init(repeating element: Element, count: Int) { | ||
self.init(_uninitializedCount: count) | ||
buffer.initialize(repeating: element) | ||
} | ||
|
||
/// Allocate a buffer that contains a copy of the elements in the given collection. | ||
public init(_ collection: some Collection<Element>) { | ||
self.init(_uninitializedCount: collection.count) | ||
_ = buffer.initialize(fromContentsOf: collection) | ||
} | ||
} | ||
|
||
public enum BufferWrapper: ~Copyable { | ||
case buffer(UniqueBuffer<Int>) | ||
case empty | ||
} | ||
|
||
extension BufferWrapper { | ||
public init(repeating: Int, count: Int) { | ||
self = .buffer(UniqueBuffer<Int>(repeating: 17, count: 15)) | ||
} | ||
|
||
public var count: Int { | ||
switch self { | ||
case .buffer(let unique): unique.count | ||
case .empty: 0 | ||
} | ||
} | ||
|
||
public subscript(index: Int) -> Int { | ||
switch self { | ||
case .buffer(let unique): unique[index] | ||
case .empty: fatalError("boom") | ||
} | ||
} | ||
} | ||
|
||
public struct BufferOfWrappers: ~Copyable { | ||
let inner: UniqueBuffer<BufferWrapper> | ||
|
||
public init() { | ||
inner = UniqueBuffer(count: 17) { index in | ||
.empty | ||
} | ||
} | ||
|
||
public func countEm() -> Int { | ||
return inner.count | ||
} | ||
} | ||
|
||
//--- Application.swift | ||
import Library | ||
|
||
func test() { | ||
let bufferWrapper = BufferWrapper(repeating: 17, count: 16) | ||
var sum = 0 | ||
for i in 0..<bufferWrapper.count { | ||
sum += bufferWrapper[i] | ||
} | ||
print(bufferWrapper.count) | ||
print(sum) | ||
|
||
let anotherBuffer = BufferOfWrappers() | ||
print(anotherBuffer.countEm()) | ||
} | ||
|
||
test() | ||
// CHECK: 15 | ||
// CHECK: 17 | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of gross, is there a better way where we actually store the specializations in a table somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is totally gross, I agree. A better answer I was considering is to allow
SILMoveOnlyDeinit
to carry a substitution map, and make it possible to look up(nominal type, substitution map)
pairs in the module + serialized module. We would specialize theSILMoveOnlyDeinit
when we see a nominal type specialization that has adeinit
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to start with my hack to unblock some stuff