Skip to content

Commit 21905ea

Browse files
authored
Merge pull request swiftlang#29428 from eeckstein/opt-empty-collection
SIL optimizer: propagate count and capacity from empty Array/Set/Dictionary singletons.
2 parents 35563ca + 966d617 commit 21905ea

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

+67
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,62 @@ SILInstruction *SILCombiner::optimizeLoadFromStringLiteral(LoadInst *LI) {
594594
return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), str[index]);
595595
}
596596

597+
/// Returns true if \p LI loads a zero integer from the empty Array, Dictionary
598+
/// or Set singleton.
599+
static bool isZeroLoadFromEmptyCollection(LoadInst *LI) {
600+
auto intTy = LI->getType().getAs<BuiltinIntegerType>();
601+
if (!intTy)
602+
return false;
603+
604+
SILValue addr = LI->getOperand();
605+
606+
// Find the root object of the load-address.
607+
for (;;) {
608+
switch (addr->getKind()) {
609+
case ValueKind::GlobalAddrInst: {
610+
StringRef gName =
611+
cast<GlobalAddrInst>(addr)->getReferencedGlobal()->getName();
612+
return gName == "_swiftEmptyArrayStorage" ||
613+
gName == "_swiftEmptyDictionarySingleton" ||
614+
gName == "_swiftEmptySetSingleton";
615+
}
616+
case ValueKind::StructElementAddrInst: {
617+
auto *SEA = cast<StructElementAddrInst>(addr);
618+
// For Array, we only support "count". The value of "capacityAndFlags"
619+
// is not defined in the ABI and could change in another version of the
620+
// runtime (the capacity must be 0, but the flags may be not 0).
621+
if (SEA->getStructDecl()->getName().is("_SwiftArrayBodyStorage") &&
622+
!SEA->getField()->getName().is("count")) {
623+
return false;
624+
}
625+
addr = SEA->getOperand();
626+
break;
627+
}
628+
case ValueKind::RefElementAddrInst: {
629+
auto *REA = cast<RefElementAddrInst>(addr);
630+
Identifier className = REA->getClassDecl()->getName();
631+
// For Dictionary and Set we support "count" and "capacity".
632+
if (className.is("__RawDictionaryStorage") ||
633+
className.is("__RawSetStorage")) {
634+
Identifier fieldName = REA->getField()->getName();
635+
if (!fieldName.is("_count") && !fieldName.is("_capacity"))
636+
return false;
637+
}
638+
addr = REA->getOperand();
639+
break;
640+
}
641+
case ValueKind::UncheckedRefCastInst:
642+
case ValueKind::UpcastInst:
643+
case ValueKind::RawPointerToRefInst:
644+
case ValueKind::AddressToPointerInst:
645+
addr = cast<SingleValueInstruction>(addr)->getOperand(0);
646+
break;
647+
default:
648+
return false;
649+
}
650+
}
651+
}
652+
597653
SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
598654
// (load (upcast-ptr %x)) -> (upcast-ref (load %x))
599655
Builder.setCurrentDebugScope(LI->getDebugScope());
@@ -606,6 +662,17 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
606662
if (SILInstruction *I = optimizeLoadFromStringLiteral(LI))
607663
return I;
608664

665+
// Constant-propagate the 0 value when loading "count" or "capacity" from the
666+
// empty Array, Set or Dictionary storage.
667+
// On high-level SIL this optimization is also done by the
668+
// ArrayCountPropagation pass, but only for Array. And even for Array it's
669+
// sometimes needed to propagate the empty-array count when high-level
670+
// semantics function are already inlined.
671+
// Note that for non-empty arrays/sets/dictionaries, the count can be
672+
// propagated by redundant load elimination.
673+
if (isZeroLoadFromEmptyCollection(LI))
674+
return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), 0);
675+
609676
return nullptr;
610677
}
611678

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-swift-frontend -emit-sil -O %s | %FileCheck %s
2+
// RUN: %target-swift-frontend -emit-sil -Osize %s | %FileCheck %s
3+
4+
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
5+
6+
// This is an end-to-end test if the count and/or capacity from empty
7+
// array/set/dictionary singletons can be propagated.
8+
9+
// CHECK-LABEL: sil @{{.*}}testArray
10+
// CHECK-NOT: global_addr
11+
// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0
12+
// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}})
13+
// CHECK: return [[I]]
14+
public func testArray() -> Int {
15+
let d = Array<Int>()
16+
return d.count
17+
}
18+
19+
// CHECK-LABEL: sil @{{.*}}testDictionary
20+
// CHECK-NOT: global_addr
21+
// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0
22+
// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}})
23+
// CHECK: return [[I]]
24+
public func testDictionary() -> Int {
25+
let d = Dictionary<Int, Int>()
26+
return d.count + d.capacity
27+
}
28+
29+
// CHECK-LABEL: sil @{{.*}}testSet
30+
// CHECK-NOT: global_addr
31+
// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0
32+
// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}})
33+
// CHECK: return [[I]]
34+
public func testSet() -> Int {
35+
let d = Set<Int>()
36+
return d.count + d.capacity
37+
}
38+

test/SILOptimizer/optionset.swift

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ public func returnTestOptions() -> TestOptions {
2222
return [.first, .second, .third, .fourth]
2323
}
2424

25+
// CHECK: sil @{{.*}}returnEmptyTestOptions{{.*}}
26+
// CHECK-NEXT: bb0:
27+
// CHECK-NEXT: integer_literal {{.*}}, 0
28+
// CHECK-NEXT: struct $Int
29+
// CHECK-NEXT: struct $TestOptions
30+
// CHECK-NEXT: return
31+
public func returnEmptyTestOptions() -> TestOptions {
32+
return []
33+
}
34+
2535
// CHECK: alloc_global @{{.*}}globalTestOptions{{.*}}
2636
// CHECK-NEXT: global_addr
2737
// CHECK-NEXT: integer_literal {{.*}}, 15

0 commit comments

Comments
 (0)