Skip to content

Commit 70971b5

Browse files
authored
Merge pull request #30508 from eeckstein/combine-enum
SILCombine: optimize creating enums with tuple payloads.
2 parents 7abbe70 + 95cacf8 commit 70971b5

File tree

3 files changed

+131
-18
lines changed

3 files changed

+131
-18
lines changed

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -945,13 +945,73 @@ SILInstruction *SILCombiner::visitStrongRetainInst(StrongRetainInst *SRI) {
945945
return nullptr;
946946
}
947947

948+
/// Create a value from stores to an address.
949+
///
950+
/// If there are only stores to \p addr, return the stored value. Also, if there
951+
/// are address projections, create aggregate instructions for it.
952+
/// If builder is null, it's just a dry-run to check if it's possible.
953+
static SILValue createValueFromAddr(SILValue addr, SILBuilder *builder,
954+
SILLocation loc) {
955+
SmallVector<SILValue, 4> elems;
956+
enum Kind {
957+
none, store, tuple
958+
} kind = none;
959+
960+
for (Operand *use : addr->getUses()) {
961+
SILInstruction *user = use->getUser();
962+
if (user->isDebugInstruction())
963+
continue;
964+
965+
auto *st = dyn_cast<StoreInst>(user);
966+
if (st && kind == none && st->getDest() == addr) {
967+
elems.push_back(st->getSrc());
968+
kind = store;
969+
// We cannot just return st->getSrc() here because we also have to check
970+
// if the store destination is the only use of addr.
971+
continue;
972+
}
973+
974+
if (auto *telem = dyn_cast<TupleElementAddrInst>(user)) {
975+
if (kind == none) {
976+
elems.resize(addr->getType().castTo<TupleType>()->getNumElements());
977+
kind = tuple;
978+
}
979+
if (kind == tuple) {
980+
if (elems[telem->getFieldNo()])
981+
return SILValue();
982+
elems[telem->getFieldNo()] = createValueFromAddr(telem, builder, loc);
983+
continue;
984+
}
985+
}
986+
// TODO: handle StructElementAddrInst to create structs.
987+
988+
return SILValue();
989+
}
990+
switch (kind) {
991+
case none:
992+
return SILValue();
993+
case store:
994+
assert(elems.size() == 1);
995+
return elems[0];
996+
case tuple:
997+
if (std::any_of(elems.begin(), elems.end(),
998+
[](SILValue v){ return !(bool)v; }))
999+
return SILValue();
1000+
if (builder) {
1001+
return builder->createTuple(loc, addr->getType().getObjectType(), elems);
1002+
}
1003+
// Just return anything not null for the dry-run.
1004+
return elems[0];
1005+
}
1006+
}
1007+
9481008
/// Simplify the following two frontend patterns:
9491009
///
9501010
/// %payload_addr = init_enum_data_addr %payload_allocation
9511011
/// store %payload to %payload_addr
9521012
/// inject_enum_addr %payload_allocation, $EnumType.case
9531013
///
954-
/// inject_enum_add %nopayload_allocation, $EnumType.case
1014+
/// inject_enum_addr %nopayload_allocation, $EnumType.case
9551015
///
9561016
/// for a concrete enum type $EnumType.case to:
9571017
///
@@ -1144,16 +1204,6 @@ SILCombiner::visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI) {
11441204
}
11451205
assert((EnumAddrIns == IEAI) &&
11461206
"Found InitEnumDataAddrInst differs from IEAI");
1147-
// Found the DataAddrInst to this enum payload. Check if it has only use.
1148-
if (!hasOneNonDebugUse(DataAddrInst))
1149-
return nullptr;
1150-
1151-
auto *SI = dyn_cast<StoreInst>(getSingleNonDebugUser(DataAddrInst));
1152-
auto *AI = dyn_cast<ApplyInst>(getSingleNonDebugUser(DataAddrInst));
1153-
if (!SI && !AI) {
1154-
return nullptr;
1155-
}
1156-
11571207
// Make sure the enum pattern instructions are the only ones which write to
11581208
// this location
11591209
if (!WriteSet.empty()) {
@@ -1201,18 +1251,30 @@ SILCombiner::visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI) {
12011251
}
12021252
}
12031253

1204-
if (SI) {
1205-
assert((SI->getDest() == DataAddrInst) &&
1206-
"Can't find StoreInst with DataAddrInst as its destination");
1254+
// Check if we can replace all stores to the enum data with an enum of the
1255+
// stored value. We can also handle tuples as payloads, e.g.
1256+
//
1257+
// %payload_addr = init_enum_data_addr %enum_addr
1258+
// %elem0_addr = tuple_element_addr %payload_addr, 0
1259+
// %elem1_addr = tuple_element_addr %payload_addr, 1
1260+
// store %payload0 to %elem0_addr
1261+
// store %payload1 to %elem1_addr
1262+
// inject_enum_addr %enum_addr, $EnumType.case
1263+
//
1264+
if (createValueFromAddr(DataAddrInst, nullptr, DataAddrInst->getLoc())) {
1265+
SILValue en =
1266+
createValueFromAddr(DataAddrInst, &Builder, DataAddrInst->getLoc());
1267+
assert(en);
1268+
12071269
// In that case, create the payload enum/store.
12081270
EnumInst *E = Builder.createEnum(
1209-
DataAddrInst->getLoc(), SI->getSrc(), DataAddrInst->getElement(),
1271+
DataAddrInst->getLoc(), en, DataAddrInst->getElement(),
12101272
DataAddrInst->getOperand()->getType().getObjectType());
12111273
Builder.createStore(DataAddrInst->getLoc(), E, DataAddrInst->getOperand(),
12121274
StoreOwnershipQualifier::Unqualified);
12131275
// Cleanup.
1214-
eraseInstFromFunction(*SI);
1215-
eraseInstFromFunction(*DataAddrInst);
1276+
eraseUsesOfInstruction(DataAddrInst);
1277+
recursivelyDeleteTriviallyDeadInstructions(DataAddrInst, true);
12161278
return eraseInstFromFunction(*IEAI);
12171279
}
12181280

@@ -1230,7 +1292,9 @@ SILCombiner::visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI) {
12301292
// %1 = enum $EnumType, $EnumType.case, %load
12311293
// store %1 to %nopayload_addr
12321294
//
1233-
assert(AI && "Must have an apply");
1295+
auto *AI = dyn_cast_or_null<ApplyInst>(getSingleNonDebugUser(DataAddrInst));
1296+
if (!AI)
1297+
return nullptr;
12341298
unsigned ArgIdx = 0;
12351299
Operand *EnumInitOperand = nullptr;
12361300
for (auto &Opd : AI->getArgumentOperands()) {

test/SILOptimizer/opt_enumerate.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %target-swift-frontend %s -O -module-name=test -emit-sil | %FileCheck %s
2+
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
3+
4+
var gg = 0
5+
6+
@inline(never)
7+
func take(_ x: Int, _ y: Int) {
8+
gg = x + y
9+
}
10+
11+
// CHECK-LABEL: sil @$s4test23check_cond_fail_messageySiSaySiGF
12+
// CHECK: cond_fail {{.*}} "Index out of range"
13+
// CHECK: // end sil function '$s4test23check_cond_fail_messageySiSaySiGF'
14+
public func check_cond_fail_message(_ array: [Int]) -> Int {
15+
return array[2]
16+
}
17+
18+
// CHECK-LABEL: sil @$s4test22eliminate_bounds_checkyySaySiGF
19+
// CHECK-NOT: cond_fail {{.*}} "Index out of range"
20+
// CHECK: // end sil function '$s4test22eliminate_bounds_checkyySaySiGF'
21+
public func eliminate_bounds_check(_ array: [Int]) {
22+
for (index, x) in array.enumerated() {
23+
take(x, index)
24+
}
25+
}
26+

test/SILOptimizer/sil_combine_enums.sil

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,26 @@ bb3(%14 : $C):
473473
// CHECK: return
474474
return %16 : $T
475475
}
476+
477+
// CHECK-LABEL: sil @test_inject_tuple
478+
// CHECK: [[A:%[0-9]+]] = alloc_stack $Optional<(Int, Int)>
479+
// CHECK: [[T:%[0-9]+]] = tuple (%0 : $Int, %1 : $Int)
480+
// CHECK: [[E:%[0-9]+]] = enum $Optional<(Int, Int)>, #Optional.some!enumelt.1, [[T]] : $(Int, Int)
481+
// CHECK: store [[E]] to [[A]]
482+
// CHECK: [[L:%[0-9]+]] = load [[A]]
483+
// CHECK: return [[L]]
484+
// CHECK: } // end sil function 'test_inject_tuple'
485+
sil @test_inject_tuple : $@convention(thin) (Int, Int) -> Optional<(Int, Int)> {
486+
bb0(%0 : $Int, %1 : $Int):
487+
%17 = alloc_stack $Optional<(Int, Int)>
488+
%45 = init_enum_data_addr %17 : $*Optional<(Int, Int)>, #Optional.some!enumelt.1
489+
%46 = tuple_element_addr %45 : $*(Int, Int), 0
490+
%47 = tuple_element_addr %45 : $*(Int, Int), 1
491+
store %0 to %46 : $*Int
492+
store %1 to %47 : $*Int
493+
inject_enum_addr %17 : $*Optional<(Int, Int)>, #Optional.some!enumelt.1
494+
%r = load %17 : $*Optional<(Int, Int)>
495+
dealloc_stack %17 : $*Optional<(Int, Int)>
496+
return %r : $Optional<(Int, Int)>
497+
}
498+

0 commit comments

Comments
 (0)