Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions lib/IRGen/GenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2937,20 +2938,34 @@ 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(
Copy link
Contributor

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?

Copy link
Member Author

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 the SILMoveOnlyDeinit when we see a nominal type specialization that has a deinit.

Copy link
Member Author

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

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
&& deinitTy->getNumResults() == 0
&& !deinitTy->hasError()
&& "deinit should have only one parameter");

auto substitutions = ty->getContextSubstitutionMap();

CalleeInfo info(deinitTy,
deinitTy->substGenericArgs(IGF.getSILModule(),
substitutions,
Expand Down
42 changes: 42 additions & 0 deletions test/embedded/deinit-noncopyable-2.swift
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
}

164 changes: 164 additions & 0 deletions test/embedded/deinit-noncopyable.swift
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