Skip to content

Commit bc78fcd

Browse files
authored
Merge pull request #39537 from eeckstein/fix-memory-lifetime-verifier-5.5
[5.5] Fix two false verification errors in the MemoryLifetimeVerifier
2 parents b75dcee + 0f2a7b7 commit bc78fcd

File tree

7 files changed

+166
-43
lines changed

7 files changed

+166
-43
lines changed

include/swift/SIL/MemoryLocations.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ void dumpBits(const SmallBitVector &bits);
4343
///
4444
/// Memory locations are limited to addresses which are guaranteed to
4545
/// be not aliased, like @in/inout parameters and alloc_stack.
46-
/// Currently only a certain set of address instructions are supported:
47-
/// Specifically those instructions which are going to be included when SIL
48-
/// supports opaque values.
46+
/// Currently only a certain set of address instructions are supported, for
47+
/// details see `MemoryLocations::analyzeLocationUsesRecursively` and
48+
/// `MemoryLocations::analyzeAddrProjection`.
4949
class MemoryLocations {
5050
public:
5151

@@ -288,7 +288,7 @@ class MemoryLocations {
288288
/// not covered by sub-fields.
289289
const Bits &getNonTrivialLocations();
290290

291-
/// Debug dump the MemoryLifetime internals.
291+
/// Debug dump the MemoryLocations internals.
292292
void dump() const;
293293

294294
private:

include/swift/SIL/SILType.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ class SILType {
294294
/// even though they are technically trivial.
295295
bool isTrivial(const SILFunction &F) const;
296296

297+
/// True if the type is an empty tuple or an empty struct or a tuple or
298+
/// struct containing only empty types.
299+
bool isEmpty(const SILFunction &F) const;
300+
297301
/// True if the type, or the referenced type of an address type, is known to
298302
/// be a scalar reference-counted type such as a class, box, or thick function
299303
/// type. Returns false for non-trivial aggregates.

lib/SIL/IR/SILType.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@ bool SILType::isTrivial(const SILFunction &F) const {
104104
return F.getTypeLowering(contextType).isTrivial();
105105
}
106106

107+
bool SILType::isEmpty(const SILFunction &F) const {
108+
if (auto tupleTy = getAs<TupleType>()) {
109+
// A tuple is empty if it either has no elements or if all elements are
110+
// empty.
111+
for (unsigned idx = 0, num = tupleTy->getNumElements(); idx < num; ++idx) {
112+
if (!getTupleElementType(idx).isEmpty(F))
113+
return false;
114+
}
115+
return true;
116+
}
117+
if (StructDecl *structDecl = getStructOrBoundGenericStruct()) {
118+
// Also, a struct is empty if it either has no fields or if all fields are
119+
// empty.
120+
SILModule &module = F.getModule();
121+
TypeExpansionContext typeEx = F.getTypeExpansionContext();
122+
for (VarDecl *field : structDecl->getStoredProperties()) {
123+
if (!getFieldType(field, module, typeEx).isEmpty(F))
124+
return false;
125+
}
126+
return true;
127+
}
128+
return false;
129+
}
130+
107131
bool SILType::isReferenceCounted(SILModule &M) const {
108132
return M.Types.getTypeLowering(*this,
109133
TypeExpansionContext::minimal())

lib/SIL/Utils/MemoryLocations.cpp

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,34 +67,6 @@ static bool allUsesInSameBlock(AllocStackInst *ASI) {
6767
return numDeallocStacks == 1;
6868
}
6969

70-
/// We don't handle empty tuples and empty structs.
71-
///
72-
/// Locations with empty types don't even need a store to count as
73-
/// "initialized". We don't handle such cases.
74-
static bool isEmptyType(SILType ty, SILFunction *function) {
75-
if (auto tupleTy = ty.getAs<TupleType>()) {
76-
// A tuple is empty if it either has no elements or if all elements are
77-
// empty.
78-
for (unsigned idx = 0, num = tupleTy->getNumElements(); idx < num; ++idx) {
79-
if (!isEmptyType(ty.getTupleElementType(idx), function))
80-
return false;
81-
}
82-
return true;
83-
}
84-
if (StructDecl *structDecl = ty.getStructOrBoundGenericStruct()) {
85-
// Also, a struct is empty if it either has no fields or if all fields are
86-
// empty.
87-
SILModule &module = function->getModule();
88-
TypeExpansionContext typeEx = function->getTypeExpansionContext();
89-
for (VarDecl *field : structDecl->getStoredProperties()) {
90-
if (!isEmptyType(ty.getFieldType(field, module, typeEx), function))
91-
return false;
92-
}
93-
return true;
94-
}
95-
return false;
96-
}
97-
9870
} // anonymous namespace
9971
} // namespace swift
10072

@@ -117,7 +89,7 @@ MemoryLocations::Location::Location(SILValue val, unsigned index, int parentIdx)
11789

11890
void MemoryLocations::Location::updateFieldCounters(SILType ty, int increment) {
11991
SILFunction *function = representativeValue->getFunction();
120-
if (!isEmptyType(ty, function)) {
92+
if (!ty.isEmpty(*function)) {
12193
numFieldsNotCoveredBySubfields += increment;
12294
if (!ty.isTrivial(*function))
12395
numNonTrivialFieldsNotCovered += increment;
@@ -215,7 +187,11 @@ void MemoryLocations::analyzeLocation(SILValue loc) {
215187
if (!handleTrivialLocations && loc->getType().isTrivial(*function))
216188
return;
217189

218-
if (isEmptyType(loc->getType(), function))
190+
/// We don't handle empty tuples and empty structs.
191+
///
192+
/// Locations with empty types don't even need a store to count as
193+
/// "initialized". We don't handle such cases.
194+
if (loc->getType().isEmpty(*function))
219195
return;
220196

221197
unsigned currentLocIdx = locations.size();
@@ -393,7 +369,7 @@ bool MemoryLocations::analyzeAddrProjection(
393369
SingleValueInstruction *projection, unsigned parentLocIdx,unsigned fieldNr,
394370
SmallVectorImpl<SILValue> &collectedVals, SubLocationMap &subLocationMap) {
395371

396-
if (isEmptyType(projection->getType(), projection->getFunction()))
372+
if (projection->getType().isEmpty(*projection->getFunction()))
397373
return false;
398374

399375
auto key = std::make_pair(parentLocIdx, fieldNr);

lib/SIL/Verifier/MemoryLifetimeVerifier.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,8 @@ namespace {
3131
/// A utility for verifying memory lifetime.
3232
///
3333
/// The MemoryLifetime utility checks the lifetime of memory locations.
34-
/// This is limited to memory locations which are guaranteed to be not aliased,
35-
/// like @in or @inout parameters. Also, alloc_stack locations are handled.
36-
///
37-
/// In addition to verification, the MemoryLifetime class can be used as utility
38-
/// (e.g. base class) for optimizations, which need to compute memory lifetime.
34+
/// This is limited to memory locations which can be handled by
35+
/// `MemoryLocations`.
3936
class MemoryLifetimeVerifier {
4037

4138
using Bits = MemoryLocations::Bits;
@@ -178,6 +175,22 @@ static bool isTrivialEnumElem(EnumElementDecl *elem, SILType enumType,
178175
enumType.getEnumElementType(elem, function).isTrivial(*function);
179176
}
180177

178+
static bool injectsNoPayloadCase(InjectEnumAddrInst *IEAI) {
179+
if (!IEAI->getElement()->hasAssociatedValues())
180+
return true;
181+
SILType enumType = IEAI->getOperand()->getType();
182+
SILFunction *function = IEAI->getFunction();
183+
SILType elemType = enumType.getEnumElementType(IEAI->getElement(), function);
184+
// Handle empty types (e.g. the empty tuple) as no-payload.
185+
return elemType.isEmpty(*function);
186+
}
187+
188+
static bool isOrHasEnum(SILType type) {
189+
return type.getASTType().findIf([](Type ty) {
190+
return ty->getEnumOrBoundGenericEnum() != nullptr;
191+
});
192+
}
193+
181194
bool MemoryLifetimeVerifier::storesTrivialEnum(int locIdx,
182195
SILBasicBlock::reverse_iterator start,
183196
SILBasicBlock::reverse_iterator end) {
@@ -191,7 +204,7 @@ bool MemoryLifetimeVerifier::storesTrivialEnum(int locIdx,
191204
if (auto *SI = dyn_cast<StoreInst>(&inst)) {
192205
const Location *loc = locations.getLocation(SI->getDest());
193206
if (loc && loc->isSubLocation(locIdx) &&
194-
SI->getSrc()->getType().getEnumOrBoundGenericEnum()) {
207+
isOrHasEnum(SI->getSrc()->getType())) {
195208
return SI->getOwnershipQualifier() == StoreOwnershipQualifier::Trivial;
196209
}
197210
}
@@ -343,7 +356,7 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
343356
case SILInstructionKind::InjectEnumAddrInst: {
344357
auto *IEAI = cast<InjectEnumAddrInst>(&I);
345358
int enumIdx = locations.getLocationIdx(IEAI->getOperand());
346-
if (enumIdx >= 0 && !IEAI->getElement()->hasAssociatedValues()) {
359+
if (enumIdx >= 0 && injectsNoPayloadCase(IEAI)) {
347360
// This is a bit tricky: an injected no-payload case means that the
348361
// "full" enum is initialized. So, for the purpose of dataflow, we
349362
// treat it like a full initialization of the payload data.
@@ -582,7 +595,7 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
582595
case SILInstructionKind::InjectEnumAddrInst: {
583596
auto *IEAI = cast<InjectEnumAddrInst>(&I);
584597
int enumIdx = locations.getLocationIdx(IEAI->getOperand());
585-
if (enumIdx >= 0 && !IEAI->getElement()->hasAssociatedValues()) {
598+
if (enumIdx >= 0 && injectsNoPayloadCase(IEAI)) {
586599
// Again, an injected no-payload case is treated like a "full"
587600
// initialization. See initDataflowInBlock().
588601
requireBitsClear(bits & nonTrivialLocations, IEAI->getOperand(), &I);

test/SIL/memory_lifetime.sil

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,17 @@ bb0(%0 : @owned $T):
464464
return %r : $()
465465
}
466466

467+
sil [ossa] @test_store_enum_tuple : $@convention(thin) (Int) -> () {
468+
bb0(%0 : $Int):
469+
%2 = enum $Optional<T>, #Optional.none!enumelt
470+
%3 = tuple (%0 : $Int, %2 : $Optional<T>)
471+
%8 = alloc_stack $(Int, Optional<T>)
472+
store %3 to [trivial] %8 : $*(Int, Optional<T>)
473+
dealloc_stack %8 : $*(Int, Optional<T>)
474+
%13 = tuple ()
475+
return %13 : $()
476+
}
477+
467478
sil [ossa] @test_select_enum_addr : $@convention(thin) (@in_guaranteed Optional<T>) -> Builtin.Int1 {
468479
bb0(%0 : $*Optional<T>):
469480
%1 = integer_literal $Builtin.Int1, -1
@@ -639,3 +650,35 @@ bb0(%0 : $Int, %1 : @guaranteed $@callee_guaranteed (@in_guaranteed (Int, ((), (
639650
dealloc_stack %2 : $*(Int, ((), ()))
640651
return %5 : $Int
641652
}
653+
654+
enum Result<T1, T2>{
655+
case success(T1)
656+
case failure(T2)
657+
}
658+
659+
sil @try_get_error : $@convention(thin) () -> @error Error
660+
661+
sil [ossa] @test_init_enum_empty_case : $@convention(thin) () -> @error Error {
662+
bb0:
663+
%0 = alloc_stack $Result<(), Error>
664+
%1 = function_ref @try_get_error : $@convention(thin) () -> @error Error
665+
try_apply %1() : $@convention(thin) () -> @error Error, normal bb1, error bb2
666+
667+
bb1(%3 : $()):
668+
inject_enum_addr %0 : $*Result<(), Error>, #Result.success!enumelt
669+
br bb3
670+
671+
bb2(%6 : @owned $Error):
672+
%7 = init_enum_data_addr %0 : $*Result<(), Error>, #Result.failure!enumelt
673+
store %6 to [init] %7 : $*Error
674+
inject_enum_addr %0 : $*Result<(), Error>, #Result.failure!enumelt
675+
br bb3
676+
677+
bb3:
678+
%11 = load [take] %0 : $*Result<(), Error>
679+
destroy_value %11 : $Result<(), Error>
680+
dealloc_stack %0 : $*Result<(), Error>
681+
%14 = tuple ()
682+
return %14 : $()
683+
}
684+

test/SIL/memory_lifetime_failures.sil

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,66 @@ bb0(%0 : $Bool):
538538
%10 = tuple ()
539539
return %10 : $()
540540
}
541+
542+
// MemoryLifetimeVerifier does not detect an error here due to reborrows
543+
sil [ossa] @test_load_borrow1 : $@convention(thin) (@in Optional<T>) -> () {
544+
bb0(%0 : $*Optional<T>):
545+
destroy_addr %0 : $*Optional<T>
546+
%1 = load_borrow %0 : $*Optional<T>
547+
br bb1(%1 : $Optional<T>)
548+
549+
bb1(%3 : @guaranteed $Optional<T>):
550+
end_borrow %3 : $Optional<T>
551+
br bb2
552+
553+
bb2:
554+
%r = tuple ()
555+
return %r : $()
556+
}
557+
558+
// CHECK: SIL memory lifetime failure in @test_load_borrow2: memory is not initialized, but should
559+
sil [ossa] @test_load_borrow2 : $@convention(thin) (@in Optional<T>) -> () {
560+
bb0(%0 : $*Optional<T>):
561+
destroy_addr %0 : $*Optional<T>
562+
%1 = load_borrow %0 : $*Optional<T>
563+
end_borrow %1 : $Optional<T>
564+
br bb1
565+
566+
bb1:
567+
%r = tuple ()
568+
return %r : $()
569+
}
570+
571+
enum Result<T1, T2>{
572+
case success(T1)
573+
case failure(T2)
574+
}
575+
576+
sil @try_get_error : $@convention(thin) () -> @error Error
577+
578+
// CHECK: SIL memory lifetime failure in @test_init_enum_trivial_case: memory is not initialized, but should
579+
sil [ossa] @test_init_enum_trivial_case : $@convention(thin) () -> @error Error {
580+
bb0:
581+
%0 = alloc_stack $Result<Int, Error>
582+
%1 = function_ref @try_get_error : $@convention(thin) () -> @error Error
583+
try_apply %1() : $@convention(thin) () -> @error Error, normal bb1, error bb2
584+
585+
bb1(%3 : $()):
586+
inject_enum_addr %0 : $*Result<Int, Error>, #Result.success!enumelt
587+
br bb3
588+
589+
590+
bb2(%7 : @owned $Error):
591+
%8 = init_enum_data_addr %0 : $*Result<Int, Error>, #Result.failure!enumelt
592+
store %7 to [init] %8 : $*Error
593+
inject_enum_addr %0 : $*Result<Int, Error>, #Result.failure!enumelt
594+
br bb3
595+
596+
bb3:
597+
%12 = load [take] %0 : $*Result<Int, Error>
598+
destroy_value %12 : $Result<Int, Error>
599+
dealloc_stack %0 : $*Result<Int, Error>
600+
%15 = tuple ()
601+
return %15 : $()
602+
}
603+

0 commit comments

Comments
 (0)