mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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 #72627 / rdar://157131184.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#include "swift/Basic/Platform.h"
|
||||
#include "swift/Basic/SourceManager.h"
|
||||
#include "swift/IRGen/Linking.h"
|
||||
#include "swift/SIL/GenericSpecializationMangler.h"
|
||||
#include "swift/SIL/SILModule.h"
|
||||
#include "llvm/IR/DerivedTypes.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
@@ -2937,11 +2938,27 @@ static bool tryEmitDeinitCall(IRGenFunction &IGF,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto deinitSILFn = deinitTable->getImplementation();
|
||||
|
||||
// Look for a specialization of deinit that we can call.
|
||||
auto substitutions = ty->getContextSubstitutionMap();
|
||||
if (!substitutions.empty() &&
|
||||
!substitutions.getRecursiveProperties().hasArchetype()) {
|
||||
Mangle::GenericSpecializationMangler mangler(
|
||||
deinitSILFn->getASTContext(), deinitSILFn,
|
||||
deinitSILFn->getSerializedKind());
|
||||
|
||||
auto specializedName = mangler.mangleReabstracted(
|
||||
substitutions, /*needAlternativeMangling=*/false);
|
||||
auto specializedFn = IGF.IGM.getSILModule().lookUpFunction(specializedName);
|
||||
if (specializedFn)
|
||||
deinitSILFn = specializedFn;
|
||||
}
|
||||
|
||||
// The deinit should take a single value parameter of the nominal type, either
|
||||
// by @owned or indirect @in convention.
|
||||
auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitTable->getImplementation(),
|
||||
NotForDefinition);
|
||||
auto deinitTy = deinitTable->getImplementation()->getLoweredFunctionType();
|
||||
auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitSILFn, NotForDefinition);
|
||||
auto deinitTy = deinitSILFn->getLoweredFunctionType();
|
||||
auto deinitFP = FunctionPointer::forDirect(IGF.IGM, deinitFn,
|
||||
nullptr, deinitTy);
|
||||
assert(deinitTy->getNumParameters() == 1
|
||||
@@ -2949,8 +2966,6 @@ static bool tryEmitDeinitCall(IRGenFunction &IGF,
|
||||
&& !deinitTy->hasError()
|
||||
&& "deinit should have only one parameter");
|
||||
|
||||
auto substitutions = ty->getContextSubstitutionMap();
|
||||
|
||||
CalleeInfo info(deinitTy,
|
||||
deinitTy->substGenericArgs(IGF.getSILModule(),
|
||||
substitutions,
|
||||
|
||||
42
test/embedded/deinit-noncopyable-2.swift
Normal file
42
test/embedded/deinit-noncopyable-2.swift
Normal file
@@ -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
|
||||
}
|
||||
|
||||
164
test/embedded/deinit-noncopyable.swift
Normal file
164
test/embedded/deinit-noncopyable.swift
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user