Skip to content

Commit 96a6d44

Browse files
committed
Enable mem2reg of enum types
Previously it was disabled because we have incomplete address lifetimes for such types, and transforming to value form in mem2reg would expose verification errors due to incompleteness. Enable this case here and fixup the lifetimes using the new lifetime completion utility.
1 parent 5a35479 commit 96a6d44

File tree

4 files changed

+120
-24
lines changed

4 files changed

+120
-24
lines changed

lib/SILOptimizer/Transforms/SILMem2Reg.cpp

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/Basic/TaggedUnion.h"
2626
#include "swift/SIL/BasicBlockDatastructures.h"
2727
#include "swift/SIL/Dominance.h"
28+
#include "swift/SIL/OSSALifetimeCompletion.h"
2829
#include "swift/SIL/Projection.h"
2930
#include "swift/SIL/SILBuilder.h"
3031
#include "swift/SIL/SILFunction.h"
@@ -1784,13 +1785,42 @@ void StackAllocationPromoter::promoteAllocationToPhi() {
17841785
}
17851786

17861787
void StackAllocationPromoter::run() {
1788+
auto *function = asi->getFunction();
1789+
17871790
// Reduce the number of load/stores in the function to minimum.
17881791
// After this phase we are left with up to one load and store
17891792
// per block and the last store is recorded.
17901793
pruneAllocStackUsage();
17911794

17921795
// Replace AllocStacks with Phi-nodes.
17931796
promoteAllocationToPhi();
1797+
1798+
// Make sure that all of the allocations were promoted into registers.
1799+
assert(isWriteOnlyAllocation(asi) && "Non-write uses left behind");
1800+
1801+
SmallVector<SILValue> valuesToComplete;
1802+
1803+
// Enum types may have incomplete lifetimes in address form, when promoted to
1804+
// value form after mem2reg, they will end up with incomplete ossa lifetimes.
1805+
// Use the lifetime completion utility to complete such lifetimes.
1806+
// First, collect the stored values to complete.
1807+
if (asi->getType().isOrHasEnum()) {
1808+
for (auto it : initializationPoints) {
1809+
auto *si = it.second;
1810+
auto src = si->getOperand(0);
1811+
valuesToComplete.push_back(src);
1812+
}
1813+
}
1814+
1815+
// ... and erase the allocation.
1816+
deleter.forceDeleteWithUsers(asi);
1817+
1818+
// Now, complete lifetimes!
1819+
OSSALifetimeCompletion completion(function, domInfo);
1820+
1821+
for (auto it : valuesToComplete) {
1822+
completion.completeOSSALifetime(it);
1823+
}
17941824
}
17951825

17961826
//===----------------------------------------------------------------------===//
@@ -2091,12 +2121,31 @@ bool MemoryToRegisters::promoteSingleAllocation(AllocStackInst *alloc) {
20912121
deleter.forceDeleteWithUsers(alloc);
20922122
return true;
20932123
} else {
2094-
// For enums we require that all uses are in the same block.
2095-
// Otherwise there could be a switch_enum of an optional where the none-case
2096-
// does not have a destroy of the enum value.
2097-
// After transforming such an alloc_stack, the value would leak in the none-
2098-
// case block.
2099-
if (f.hasOwnership() && alloc->getType().isOrHasEnum())
2124+
auto enableOptimizationForEnum = [](AllocStackInst *asi) {
2125+
if (asi->isLexical()) {
2126+
return false;
2127+
}
2128+
for (auto *use : asi->getUses()) {
2129+
auto *user = use->getUser();
2130+
if (!isa<StoreInst>(user) && !isa<StoreBorrowInst>(user)) {
2131+
continue;
2132+
}
2133+
auto stored = user->getOperand(CopyLikeInstruction::Src);
2134+
if (stored->isLexical()) {
2135+
return false;
2136+
}
2137+
}
2138+
return true;
2139+
};
2140+
// For stack locs of enum type that are lexical or with lexical stored
2141+
// values, we require that all uses are in the same block. This is because
2142+
// we can have incomplete lifetime of enum typed addresses, and on
2143+
// converting to value form this causes verification error. For all other
2144+
// stack locs of enum type, we use the lifetime completion utility to fix
2145+
// the lifetime. But when we have a lexical value, the utility can complete
2146+
// lifetimes on dead end blocks only.
2147+
if (f.hasOwnership() && alloc->getType().isOrHasEnum() &&
2148+
!enableOptimizationForEnum(alloc))
21002149
return false;
21012150
}
21022151

@@ -2108,11 +2157,6 @@ bool MemoryToRegisters::promoteSingleAllocation(AllocStackInst *alloc) {
21082157
StackAllocationPromoter(alloc, domInfo, domTreeLevels, ctx, deleter,
21092158
instructionsToDelete)
21102159
.run();
2111-
2112-
// Make sure that all of the allocations were promoted into registers.
2113-
assert(isWriteOnlyAllocation(alloc) && "Non-write uses left behind");
2114-
// ... and erase the allocation.
2115-
deleter.forceDeleteWithUsers(alloc);
21162160
return true;
21172161
}
21182162

test/SILOptimizer/mem2reg_lifetime_nontrivial.sil

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,6 @@ enum KlassOptional {
10611061
sil @return_optional_or_error : $@convention(thin) () -> (@owned KlassOptional, @error Error)
10621062

10631063
// CHECK-LABEL: sil [ossa] @test_deadphi4 :
1064-
// mem2reg cannot optimize an enum location which spans over multiple blocks.
10651064
// CHECK: alloc_stack
10661065
// CHECK-LABEL: } // end sil function 'test_deadphi4'
10671066
sil [ossa] @test_deadphi4 : $@convention(thin) (@owned KlassOptional) -> () {

test/SILOptimizer/mem2reg_ossa.sil

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -489,10 +489,10 @@ bb0:
489489
return %16 : $((), ())
490490
}
491491

492-
// CHECK-LABEL: sil [ossa] @dont_optimize_optional_in_multiple_blocks :
493-
// CHECK: alloc_stack
494-
// CHECK: } // end sil function 'dont_optimize_optional_in_multiple_blocks'
495-
sil [ossa] @dont_optimize_optional_in_multiple_blocks : $@convention(method) (@guaranteed Optional<Klass>) -> () {
492+
// CHECK-LABEL: sil [ossa] @test_optional_in_multiple_blocks :
493+
// CHECK-NOT: alloc_stack
494+
// CHECK: } // end sil function 'test_optional_in_multiple_blocks'
495+
sil [ossa] @test_optional_in_multiple_blocks : $@convention(method) (@guaranteed Optional<Klass>) -> () {
496496
bb0(%0 : @guaranteed $Optional<Klass>):
497497
%1 = copy_value %0 : $Optional<Klass>
498498
%32 = alloc_stack $Optional<Klass>
@@ -504,8 +504,62 @@ bb5:
504504
br bb7
505505

506506
bb6(%50 : @guaranteed $Klass):
507-
%53 = load [take] %32 : $*Optional<Klass>
507+
%53 = load [copy] %32 : $*Optional<Klass>
508+
destroy_value %53 : $Optional<Klass>
509+
destroy_addr %32 : $*Optional<Klass>
510+
dealloc_stack %32 : $*Optional<Klass>
511+
br bb7
512+
513+
bb7:
514+
%r = tuple ()
515+
return %r : $()
516+
}
517+
518+
// CHECK-LABEL: sil [ossa] @test_optional_in_multiple_blocks_lexical :
519+
// CHECK: alloc_stack
520+
// CHECK: } // end sil function 'test_optional_in_multiple_blocks_lexical'
521+
sil [ossa] @test_optional_in_multiple_blocks_lexical : $@convention(method) (@guaranteed Optional<Klass>) -> () {
522+
bb0(%0 : @guaranteed $Optional<Klass>):
523+
%1 = copy_value %0 : $Optional<Klass>
524+
%32 = alloc_stack [lexical] $Optional<Klass>
525+
store %1 to [init] %32 : $*Optional<Klass>
526+
switch_enum %0 : $Optional<Klass>, case #Optional.some!enumelt: bb6, case #Optional.none!enumelt: bb5
527+
528+
bb5:
529+
dealloc_stack %32 : $*Optional<Klass>
530+
br bb7
531+
532+
bb6(%50 : @guaranteed $Klass):
533+
%53 = load [copy] %32 : $*Optional<Klass>
534+
destroy_value %53 : $Optional<Klass>
535+
destroy_addr %32 : $*Optional<Klass>
536+
dealloc_stack %32 : $*Optional<Klass>
537+
br bb7
538+
539+
bb7:
540+
%r = tuple ()
541+
return %r : $()
542+
}
543+
544+
// CHECK-LABEL: sil [ossa] @test_optional_in_multiple_blocks_lexical_storedvalue :
545+
// CHECK: alloc_stack
546+
// CHECK: } // end sil function 'test_optional_in_multiple_blocks_lexical_storedvalue'
547+
sil [ossa] @test_optional_in_multiple_blocks_lexical_storedvalue : $@convention(method) (@owned Optional<Klass>) -> () {
548+
bb0(%0 : @owned $Optional<Klass>):
549+
%1 = copy_value %0 : $Optional<Klass>
550+
%32 = alloc_stack $Optional<Klass>
551+
store %0 to [init] %32 : $*Optional<Klass>
552+
switch_enum %1 : $Optional<Klass>, case #Optional.some!enumelt: bb6, case #Optional.none!enumelt: bb5
553+
554+
bb5:
555+
dealloc_stack %32 : $*Optional<Klass>
556+
br bb7
557+
558+
bb6(%50 : @owned $Klass):
559+
%53 = load [copy] %32 : $*Optional<Klass>
508560
destroy_value %53 : $Optional<Klass>
561+
destroy_value %50 : $Klass
562+
destroy_addr %32 : $*Optional<Klass>
509563
dealloc_stack %32 : $*Optional<Klass>
510564
br bb7
511565

test/SILOptimizer/mem2reg_ossa_nontrivial.sil

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ struct UInt8 {
5353
var _value : Builtin.Int8
5454
}
5555

56+
enum KlassOptional {
57+
case some(Klass)
58+
case none
59+
}
60+
5661
sil [ossa] @get_nontrivialstruct : $@convention(thin) () -> @owned NonTrivialStruct
5762
sil [ossa] @get_nontrivialenum : $@convention(thin) () -> @owned NonTrivialEnum
5863
sil [ossa] @get_optionalnontrivialstruct : $@convention(thin) () -> @owned FakeOptional<NonTrivialStruct>
@@ -1026,16 +1031,10 @@ bb12:
10261031
return %39 : $()
10271032
}
10281033

1029-
enum KlassOptional {
1030-
case some(Klass)
1031-
case none
1032-
}
1033-
10341034
sil @return_optional_or_error : $@convention(thin) () -> (@owned KlassOptional, @error Error)
10351035

10361036
// CHECK-LABEL: sil [ossa] @test_deadphi4 :
1037-
// mem2reg cannot optimize an enum location which spans over multiple blocks.
1038-
// CHECK: alloc_stack
1037+
// CHECK-NOT: alloc_stack
10391038
// CHECK-LABEL: } // end sil function 'test_deadphi4'
10401039
sil [ossa] @test_deadphi4 : $@convention(thin) (@owned KlassOptional) -> () {
10411040
bb0(%0 : @owned $KlassOptional):

0 commit comments

Comments
 (0)