mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
72
test/IRGen/empty-noncopyable-with-deinit.swift
Normal file
72
test/IRGen/empty-noncopyable-with-deinit.swift
Normal 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]])
|
||||
75
test/IRGen/fixed-noncopyable-with-deinit.swift
Normal file
75
test/IRGen/fixed-noncopyable-with-deinit.swift
Normal 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]])
|
||||
|
||||
97
test/Interpreter/fixed-noncopyable-with-deinit-capture.swift
Normal file
97
test/Interpreter/fixed-noncopyable-with-deinit-capture.swift
Normal 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()
|
||||
Reference in New Issue
Block a user