IRGen: Capture NecessaryBindings in fixed boxes that may invoke deinits.

Noncopyable types may have user-defined code in their `deinit`s that requires
passing the type's generic parameters, so a box for a captured noncopyable type
needs to capture the generic environment even when the captured type is fixed-
layout. Fixes rdar://138958210.
This commit is contained in:
Joe Groff
2024-11-13 16:37:27 -08:00
parent ffb22d7287
commit b5e79d5590
7 changed files with 381 additions and 33 deletions

View File

@@ -568,8 +568,10 @@ llvm::Value *IRGenFunction::emitUnmanagedAlloc(const HeapLayout &layout,
const llvm::Twine &name,
llvm::Constant *captureDescriptor,
const HeapNonFixedOffsets *offsets) {
if (layout.isKnownEmpty())
if (layout.isKnownEmpty()
&& layout.isTriviallyDestroyable()) {
return IGM.RefCountedNull;
}
llvm::Value *metadata = layout.getPrivateMetadata(IGM, captureDescriptor);
llvm::Value *size, *alignMask;
@@ -1470,7 +1472,7 @@ public:
/// Allocate a box of the given type.
virtual OwnedAddress
allocate(IRGenFunction &IGF, SILType boxedType, GenericEnvironment *env,
allocate(IRGenFunction &IGF, GenericEnvironment *env, SILBoxType *box,
const llvm::Twine &name) const = 0;
/// Deallocate an uninitialized box.
@@ -1488,8 +1490,11 @@ public:
EmptyBoxTypeInfo(IRGenModule &IGM) : BoxTypeInfo(IGM) {}
OwnedAddress
allocate(IRGenFunction &IGF, SILType boxedType, GenericEnvironment *env,
allocate(IRGenFunction &IGF, GenericEnvironment *env, SILBoxType *box,
const llvm::Twine &name) const override {
auto boxedType = getSILBoxFieldType(
IGF.IGM.getMaximalTypeExpansionContext(),
box, IGF.IGM.getSILModule().Types, 0);
return OwnedAddress(IGF.getTypeInfo(boxedType).getUndefAddress(),
IGF.emitAllocEmptyBoxCall());
}
@@ -1513,8 +1518,11 @@ public:
NonFixedBoxTypeInfo(IRGenModule &IGM) : BoxTypeInfo(IGM) {}
OwnedAddress
allocate(IRGenFunction &IGF, SILType boxedType, GenericEnvironment *env,
allocate(IRGenFunction &IGF, GenericEnvironment *env, SILBoxType *boxType,
const llvm::Twine &name) const override {
auto boxedType = getSILBoxFieldType(
IGF.IGM.getMaximalTypeExpansionContext(),
boxType, IGF.IGM.getSILModule().Types, 0);
auto &ti = IGF.getTypeInfo(boxedType);
// Use the runtime to allocate a box of the appropriate size.
auto metadata = IGF.emitTypeMetadataRefForLayout(boxedType);
@@ -1552,14 +1560,18 @@ public:
FixedBoxTypeInfoBase(IRGenModule &IGM, HeapLayout &&layout)
: BoxTypeInfo(IGM), layout(std::move(layout))
{
// Empty layouts should always use EmptyBoxTypeInfo instead
assert(!layout.isKnownEmpty());
// Trivial empty layouts should always use EmptyBoxTypeInfo instead
assert(!layout.isKnownEmpty()
|| !layout.isTriviallyDestroyable());
}
OwnedAddress
allocate(IRGenFunction &IGF, SILType boxedType, GenericEnvironment *env,
allocate(IRGenFunction &IGF, GenericEnvironment *env, SILBoxType *box,
const llvm::Twine &name)
const override {
auto boxedType = getSILBoxFieldType(
IGF.IGM.getMaximalTypeExpansionContext(),
box, IGF.IGM.getSILModule().Types, 0);
// Allocate a new object using the layout.
auto boxedInterfaceType = boxedType;
if (env) {
@@ -1581,13 +1593,21 @@ public:
boxedInterfaceType = SILType::getPrimitiveType(
astType, boxedInterfaceType.getCategory());
}
auto boxDescriptor = IGF.IGM.getAddrOfBoxDescriptor(
boxedInterfaceType,
env ? env->getGenericSignature().getCanonicalSignature()
: CanGenericSignature());
llvm::Value *allocation = IGF.emitUnmanagedAlloc(layout, name,
boxDescriptor);
// Store metadata for the necessary bindings if present.
if (layout.hasBindings()) {
auto allocationAddr = layout.emitCastTo(IGF, allocation);
auto bindingsAddr = layout.getElement(layout.getBindingsIndex())
.project(IGF, allocationAddr, nullptr);
layout.getBindings().save(IGF, bindingsAddr, box->getSubstitutions());
}
Address rawAddr = project(IGF, allocation, boxedType);
return {rawAddr, allocation};
}
@@ -1637,22 +1657,87 @@ public:
/// Implementation of a box for a specific type.
class FixedBoxTypeInfo final : public FixedBoxTypeInfoBase {
static SILType getFieldType(IRGenModule &IGM, SILBoxType *T) {
return getSILBoxFieldType(IGM.getMaximalTypeExpansionContext(),
T, IGM.getSILModule().Types, 0);
}
static HeapLayout getHeapLayout(IRGenModule &IGM, SILBoxType *T) {
SmallVector<SILType> fieldTypes;
fieldTypes.push_back(getFieldType(IGM, T));
auto bindings = NecessaryBindings::forFixedBox(IGM, T);
unsigned bindingsIndex = 0;
SmallVector<const TypeInfo *, 4> fields;
fields.push_back(&IGM.getTypeInfo(fieldTypes[0]));
if (!bindings.empty()) {
bindingsIndex = 1;
auto bindingsSize = bindings.getBufferSize(IGM);
auto &bindingsTI = IGM.getOpaqueStorageTypeInfo(bindingsSize,
IGM.getPointerAlignment());
fieldTypes.push_back(SILType());
fields.push_back(&bindingsTI);
}
return HeapLayout(IGM, LayoutStrategy::Optimal,
fieldTypes, fields,
/* type to fill */ nullptr,
std::move(bindings), bindingsIndex);
}
public:
FixedBoxTypeInfo(IRGenModule &IGM, SILType T)
: FixedBoxTypeInfoBase(IGM,
HeapLayout(IGM, LayoutStrategy::Optimal, T, &IGM.getTypeInfo(T)))
FixedBoxTypeInfo(IRGenModule &IGM, SILBoxType *T)
: FixedBoxTypeInfoBase(IGM, getHeapLayout(IGM, T))
{}
};
} // end anonymous namespace
NecessaryBindings
NecessaryBindings::forFixedBox(IRGenModule &IGM, SILBoxType *box) {
// Don't need to bind metadata if the type is concrete.
if (!box->hasArchetype() && !box->hasTypeParameter()) {
return {};
}
auto fieldTy = getSILBoxFieldType(IGM.getMaximalTypeExpansionContext(),
box, IGM.getSILModule().Types, 0);
auto fieldTI = cast<FixedTypeInfo>(&IGM.getTypeInfo(fieldTy));
// If the type is trivially destroyable, or it's fixed-layout and copyable,
// then we can always destroy it without binding type metadata.
if (fieldTI->isTriviallyDestroyable(ResilienceExpansion::Maximal)
|| fieldTI->isCopyable(ResilienceExpansion::Maximal)) {
return {};
}
NecessaryBindings bindings(box->getSubstitutions(),
/*no escape*/ false);
// Collect bindings needed by a deinit-shaped function.
auto deinitParam = SILParameterInfo(
box->getLayout()->getFields()[0].getLoweredType(),
ParameterConvention::Indirect_In);
auto deinitFnTy = SILFunctionType::get(box->getLayout()->getGenericSignature(),
SILExtInfo(),
SILCoroutineKind::None,
ParameterConvention::Direct_Guaranteed,
deinitParam,
{}, {}, std::nullopt,
{}, {}, IGM.Context);
bindings.computeBindings(IGM, deinitFnTy, /*consider param sources*/ false);
return bindings;
}
const TypeInfo *TypeConverter::convertBoxType(SILBoxType *T) {
// We can share a type info for all dynamic-sized heap metadata.
// TODO: Multi-field boxes
assert(T->getLayout()->getFields().size() == 1
&& "multi-field boxes not implemented yet");
auto &eltTI = IGM.getTypeInfoForLowered(getSILBoxFieldLoweredType(
IGM.getMaximalTypeExpansionContext(), T, IGM.getSILModule().Types, 0));
auto eltTy = getSILBoxFieldLoweredType(
IGM.getMaximalTypeExpansionContext(), T, IGM.getSILModule().Types, 0);
auto &eltTI = IGM.getTypeInfoForLowered(eltTy);
if (!eltTI.isFixedSize()) {
if (!NonFixedBoxTI)
NonFixedBoxTI = new NonFixedBoxTypeInfo(IGM);
@@ -1662,12 +1747,16 @@ const TypeInfo *TypeConverter::convertBoxType(SILBoxType *T) {
// For fixed-sized types, we can emit concrete box metadata.
auto &fixedTI = cast<FixedTypeInfo>(eltTI);
// Because we assume in enum's that payloads with a Builtin.NativeReference
// Because we assume in enums that payloads with a Builtin.NativeReference
// which is also the type for indirect enum cases have extra inhabitants of
// pointers we can't have a nil pointer as a representation for an empty box
// type -- nil conflicts with the extra inhabitants. We return a static
// singleton empty box object instead.
if (fixedTI.isKnownEmpty(ResilienceExpansion::Maximal)) {
//
// (If the box needs no storage, but the type still carries a deinit,
// then we still need to trigger that deinit when the box is freed.)
if (fixedTI.isKnownEmpty(ResilienceExpansion::Maximal)
&& fixedTI.isTriviallyDestroyable(ResilienceExpansion::Maximal)) {
if (!EmptyBoxTI)
EmptyBoxTI = new EmptyBoxTypeInfo(IGM);
return EmptyBoxTI;
@@ -1701,9 +1790,8 @@ const TypeInfo *TypeConverter::convertBoxType(SILBoxType *T) {
// Produce a tailored box metadata for the type.
assert(T->getLayout()->getFields().size() == 1
&& "multi-field boxes not implemented yet");
return new FixedBoxTypeInfo(
IGM, getSILBoxFieldType(IGM.getMaximalTypeExpansionContext(),
T, IGM.getSILModule().Types, 0));
return new FixedBoxTypeInfo(IGM, T);
}
OwnedAddress
@@ -1713,12 +1801,7 @@ irgen::emitAllocateBox(IRGenFunction &IGF, CanSILBoxType boxType,
auto &boxTI = IGF.getTypeInfoForLowered(boxType).as<BoxTypeInfo>();
assert(boxType->getLayout()->getFields().size() == 1
&& "multi-field boxes not implemented yet");
return boxTI.allocate(
IGF,
getSILBoxFieldType(
IGF.IGM.getMaximalTypeExpansionContext(),
boxType, IGF.IGM.getSILModule().Types, 0),
env, name);
return boxTI.allocate(IGF, env, boxType, name);
}
void irgen::emitDeallocateBox(IRGenFunction &IGF,
@@ -1751,7 +1834,7 @@ Address irgen::emitAllocateExistentialBoxInBuffer(
// Get a box for the boxed value.
auto boxType = SILBoxType::get(boxedType.getASTType());
auto &boxTI = IGF.getTypeInfoForLowered(boxType).as<BoxTypeInfo>();
OwnedAddress owned = boxTI.allocate(IGF, boxedType, env, name);
OwnedAddress owned = boxTI.allocate(IGF, env, boxType, name);
Explosion box;
box.add(owned.getOwner());
boxTI.initialize(IGF, box,

View File

@@ -3648,9 +3648,13 @@ void NecessaryBindings::restore(IRGenFunction &IGF, Address buffer,
metadataState, SubMap);
}
void NecessaryBindings::save(IRGenFunction &IGF, Address buffer) const {
void NecessaryBindings::save(IRGenFunction &IGF, Address buffer,
std::optional<SubstitutionMap> replacementSubstitutions) const {
SubstitutionMap subsToPass = replacementSubstitutions.has_value()
? replacementSubstitutions.value()
: SubMap;
emitInitOfGenericRequirementsBuffer(IGF, getRequirements(), buffer,
MetadataState::Complete, SubMap,
MetadataState::Complete, subsToPass,
/*onHeapPacks=*/!NoEscape);
}

View File

@@ -63,6 +63,11 @@ public:
bool noEscape,
bool considerParameterSources);
/// Collect the necessary bindings to be able to destroy a value inside of a
/// fixed-layout boxed allocation.
static NecessaryBindings forFixedBox(IRGenModule &IGM,
SILBoxType *box);
void addRequirement(GenericRequirement requirement) {
auto type = requirement.getTypeParameter().subst(SubMap);
if (!type->hasArchetype())
@@ -86,7 +91,12 @@ public:
Size getBufferSize(IRGenModule &IGM) const;
/// Save the necessary bindings to the given buffer.
void save(IRGenFunction &IGF, Address buffer) const;
///
/// If `replacementSubs` has a value, then the bindings saved are taken from
/// the given substitution map instead of the substitutions
void save(IRGenFunction &IGF, Address buffer,
std::optional<SubstitutionMap> replacementSubs = std::nullopt)
const;
/// Restore the necessary bindings from the given buffer.
void restore(IRGenFunction &IGF, Address buffer, MetadataState state) const;

View File

@@ -190,12 +190,23 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional<CanType> type,
Ty = (typeToFill ? typeToFill : IGM.OpaqueTy);
}
} else {
// A heap object containing an empty but non-trivially-destroyable
// noncopyable type needs to exist in order to run deinits when the
bool nonEmpty = builder.addFields(Elements, strategy);
IsKnownTriviallyDestroyable
= triviallyDestroyable & builder.isTriviallyDestroyable();
IsKnownCopyable = copyable & builder.isCopyable();
// Special-case: there's nothing to store.
// In this case, produce an opaque type; this tends to cause lovely
// assertions.
if (!nonEmpty) {
//
// If a heap object contains an empty but non-trivially-destroyable type,
// then we still want to create a non-empty heap object, since the heap
// object's destructor will run deinits for the value.
if (!nonEmpty
&& (IsKnownTriviallyDestroyable || !requiresHeapHeader(layoutKind))) {
assert(!builder.empty() == requiresHeapHeader(layoutKind));
MinimumAlign = Alignment(1);
MinimumSize = Size(0);
@@ -203,10 +214,8 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional<CanType> type,
SpareBits.clear();
IsFixedLayout = true;
IsLoadable = true;
IsKnownTriviallyDestroyable = triviallyDestroyable & builder.isTriviallyDestroyable();
IsKnownBitwiseTakable = builder.isBitwiseTakable();
IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize();
IsKnownCopyable = copyable & builder.isCopyable();
Ty = (typeToFill ? typeToFill : IGM.OpaqueTy);
} else {
MinimumAlign = builder.getAlignment();
@@ -215,10 +224,8 @@ StructLayout::StructLayout(IRGenModule &IGM, std::optional<CanType> type,
SpareBits = builder.getSpareBits();
IsFixedLayout = builder.isFixedLayout();
IsLoadable = builder.isLoadable();
IsKnownTriviallyDestroyable = triviallyDestroyable & builder.isTriviallyDestroyable();
IsKnownBitwiseTakable = bitwiseTakable & builder.isBitwiseTakable();
IsKnownAlwaysFixedSize = builder.isAlwaysFixedSize();
IsKnownCopyable = copyable & builder.isCopyable();
if (typeToFill) {
builder.setAsBodyOfStruct(typeToFill);
Ty = typeToFill;

View File

@@ -0,0 +1,72 @@
// RUN: %target-swift-frontend -emit-irgen %s | %FileCheck %s
// CHECK: [[BOX_1:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_1:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_2:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_2:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_3:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_3:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_4:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_4:@[A-Za-z0-9.]+]],
@_silgen_name("mystery_destroy")
func mystery_destroy() {}
@_silgen_name("mystery_destroy_generic")
func mystery_destroy_generic(_: Any.Type) {}
@_silgen_name("mystery_borrow")
func mystery_borrow<T: ~Copyable>(_: borrowing T) {}
struct EmptyWithDeinit: ~Copyable {
deinit { mystery_destroy() }
}
// We can't treat the box as a stock empty box because we need to invoke
// the type's deinit.
// CHECK: define{{.*}} @"$s{{.*}}19capture_and_release
func capture_and_release() -> () -> () {
let ewd = EmptyWithDeinit()
// CHECK: call{{.*}} @swift_allocObject({{.*}} [[BOX_1]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_1]](
// CHECK: call {{.*}}15EmptyWithDeinitVfD
struct EmptyWithDeinitGeneric<T>: ~Copyable {
deinit { mystery_destroy_generic(T.self) }
}
// The "empty" box needs to capture generic parameters in order
// to invoke the type's deinit.
// CHECK-LABEL: define{{.*}} @"$s{{.*}}27capture_and_release_generic
func capture_and_release_generic<T>(_: T.Type) -> () -> () {
let ewd = EmptyWithDeinitGeneric<T>()
// CHECK: [[BOX:%.*]] = call{{.*}} @swift_allocObject({{.*}} [[BOX_2]],
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr [[BOX]], i32 0, i32 1
// CHECK: store ptr %T, ptr [[BINDINGS]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_2]](
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr %0, i32 0, i32 1
// CHECK: [[T:%.*]] = load ptr, ptr [[BINDINGS]],
// CHECK: call {{.*}}22EmptyWithDeinitGenericVfD"(ptr [[T]])
// Ensure that we capture the generic parameters even if the type indirectly
// contains deinit-bearing fields, but has no deinit of its own.
struct EmptyWithDeinitGenericIndirect<T>: ~Copyable {
var value = EmptyWithDeinitGeneric<T>()
}
// CHECK-LABEL: define{{.*}} @"$s{{.*}}36capture_and_release_generic_indirect
func capture_and_release_generic_indirect<T>(_: T.Type) -> () -> () {
let ewd = EmptyWithDeinitGenericIndirect<T>()
// CHECK: [[BOX:%.*]] = call{{.*}} @swift_allocObject({{.*}} [[BOX_4]],
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr [[BOX]], i32 0, i32 1
// CHECK: store ptr %T, ptr [[BINDINGS]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_4]](
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr %0, i32 0, i32 1
// CHECK: [[T:%.*]] = load ptr, ptr [[BINDINGS]],
// CHECK: call {{.*}}22EmptyWithDeinitGenericVfD"(ptr [[T]])

View File

@@ -0,0 +1,75 @@
// RUN: %target-swift-frontend -emit-irgen %s | %FileCheck %s
// CHECK: [[BOX_1:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_1:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_2:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_2:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_3:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_3:@[A-Za-z0-9.]+]],
// CHECK: [[BOX_4:@[A-Za-z0-9.]+]] = private constant %swift.full_boxmetadata { ptr [[DESTROY_BOX_4:@[A-Za-z0-9.]+]],
@_silgen_name("mystery_destroy")
func mystery_destroy() {}
@_silgen_name("mystery_destroy_generic")
func mystery_destroy_generic(_: Any.Type) {}
@_silgen_name("mystery_borrow")
func mystery_borrow<T: ~Copyable>(_: borrowing T) {}
struct FixedWithDeinit: ~Copyable {
var field = 0
deinit { mystery_destroy() }
}
// We can't treat the box as a stock empty box because we need to invoke
// the type's deinit.
// CHECK: define{{.*}} @"$s{{.*}}19capture_and_release
func capture_and_release() -> () -> () {
let ewd = FixedWithDeinit()
// CHECK: call{{.*}} @swift_allocObject({{.*}} [[BOX_1]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_1]](
// CHECK: call {{.*}}15FixedWithDeinitVfD
struct FixedWithDeinitGeneric<T>: ~Copyable {
var field = 0
deinit { mystery_destroy_generic(T.self) }
}
// The "empty" box needs to capture generic parameters in order
// to invoke the type's deinit.
// CHECK-LABEL: define{{.*}} @"$s{{.*}}27capture_and_release_generic
func capture_and_release_generic<T>(_: T.Type) -> () -> () {
let ewd = FixedWithDeinitGeneric<T>()
// CHECK: [[BOX:%.*]] = call{{.*}} @swift_allocObject({{.*}} [[BOX_2]],
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr [[BOX]], i32 0, i32 2
// CHECK: store ptr %T, ptr [[BINDINGS]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_2]](
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr %0, i32 0, i32 2
// CHECK: [[T:%.*]] = load ptr, ptr [[BINDINGS]],
// CHECK: call {{.*}}22FixedWithDeinitGenericVfD"({{.*}}, ptr [[T]])
// Ensure that we capture the generic parameters even if the type indirectly
// contains deinit-bearing fields, but has no deinit of its own.
struct FixedWithDeinitGenericIndirect<T>: ~Copyable {
var value = FixedWithDeinitGeneric<T>()
}
// CHECK-LABEL: define{{.*}} @"$s{{.*}}36capture_and_release_generic_indirect
func capture_and_release_generic_indirect<T>(_: T.Type) -> () -> () {
let ewd = FixedWithDeinitGenericIndirect<T>()
// CHECK: [[BOX:%.*]] = call{{.*}} @swift_allocObject({{.*}} [[BOX_4]],
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr [[BOX]], i32 0, i32 2
// CHECK: store ptr %T, ptr [[BINDINGS]],
return { mystery_borrow(ewd) }
}
// CHECK: define{{.*}} void [[DESTROY_BOX_4]](
// CHECK: [[BINDINGS:%.*]] = getelementptr{{.*}} ptr %0, i32 0, i32 2
// CHECK: [[T:%.*]] = load ptr, ptr [[BINDINGS]],
// CHECK: call {{.*}}22FixedWithDeinitGenericVfD"({{.*}}, ptr [[T]])

View File

@@ -0,0 +1,97 @@
// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test
struct EmptyWithDeinit: ~Copyable {
deinit { print("destroying empty") }
}
struct EmptyWithDeinitGeneric<T>: ~Copyable {
deinit { print("destroying \(T.self) empty") }
}
struct EmptyWithDeinitGenericIndirect<T>: ~Copyable {
let value = EmptyWithDeinitGeneric<T>()
}
struct FixedWithDeinit: ~Copyable {
var field = 0
deinit { print("destroying fixed") }
}
struct FixedWithDeinitGeneric<T>: ~Copyable {
var field = 0
deinit { print("destroying \(T.self) fixed") }
}
struct FixedWithDeinitGenericIndirect<T>: ~Copyable {
let value = FixedWithDeinitGeneric<T>()
}
@inline(never)
func borrow<T: ~Copyable>(_: borrowing T) {}
@inline(never)
func capture_and_release() -> () -> () {
let ewd = EmptyWithDeinit()
return { borrow(ewd) }
}
@inline(never)
func capture_and_release_generic<T>(_: T.Type) -> () -> () {
let ewd = EmptyWithDeinitGeneric<T>()
return { borrow(ewd) }
}
@inline(never)
func capture_and_release_generic_indirect<T>(_: T.Type) -> () -> () {
let ewd = EmptyWithDeinitGeneric<T>()
return { borrow(ewd) }
}
@inline(never)
func capture_and_release_fixed() -> () -> () {
let ewd = FixedWithDeinit()
return { borrow(ewd) }
}
@inline(never)
func capture_and_release_generic_fixed<T>(_: T.Type) -> () -> () {
let ewd = FixedWithDeinitGeneric<T>()
return { borrow(ewd) }
}
@inline(never)
func capture_and_release_generic_indirect_fixed<T>(_: T.Type) -> () -> () {
let ewd = FixedWithDeinitGeneric<T>()
return { borrow(ewd) }
}
func main() {
// CHECK: starting
print("starting")
do {
// CHECK-NEXT: destroying empty
_ = capture_and_release()
}
do {
// CHECK-NEXT: destroying Int empty
_ = capture_and_release_generic(Int.self)
}
do {
// CHECK-NEXT: destroying String empty
_ = capture_and_release_generic_indirect(String.self)
}
do {
// CHECK-NEXT: destroying fixed
_ = capture_and_release_fixed()
}
do {
// CHECK-NEXT: destroying Float fixed
_ = capture_and_release_generic_fixed(Float.self)
}
do {
// CHECK-NEXT: destroying Double fixed
_ = capture_and_release_generic_indirect_fixed(Double.self)
}
}
main()