diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp index 88940000040f0..194c0b8181e5a 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp @@ -594,6 +594,62 @@ SILInstruction *SILCombiner::optimizeLoadFromStringLiteral(LoadInst *LI) { return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), str[index]); } +/// Returns true if \p LI loads a zero integer from the empty Array, Dictionary +/// or Set singleton. +static bool isZeroLoadFromEmptyCollection(LoadInst *LI) { + auto intTy = LI->getType().getAs(); + if (!intTy) + return false; + + SILValue addr = LI->getOperand(); + + // Find the root object of the load-address. + for (;;) { + switch (addr->getKind()) { + case ValueKind::GlobalAddrInst: { + StringRef gName = + cast(addr)->getReferencedGlobal()->getName(); + return gName == "_swiftEmptyArrayStorage" || + gName == "_swiftEmptyDictionarySingleton" || + gName == "_swiftEmptySetSingleton"; + } + case ValueKind::StructElementAddrInst: { + auto *SEA = cast(addr); + // For Array, we only support "count". The value of "capacityAndFlags" + // is not defined in the ABI and could change in another version of the + // runtime (the capacity must be 0, but the flags may be not 0). + if (SEA->getStructDecl()->getName().is("_SwiftArrayBodyStorage") && + !SEA->getField()->getName().is("count")) { + return false; + } + addr = SEA->getOperand(); + break; + } + case ValueKind::RefElementAddrInst: { + auto *REA = cast(addr); + Identifier className = REA->getClassDecl()->getName(); + // For Dictionary and Set we support "count" and "capacity". + if (className.is("__RawDictionaryStorage") || + className.is("__RawSetStorage")) { + Identifier fieldName = REA->getField()->getName(); + if (!fieldName.is("_count") && !fieldName.is("_capacity")) + return false; + } + addr = REA->getOperand(); + break; + } + case ValueKind::UncheckedRefCastInst: + case ValueKind::UpcastInst: + case ValueKind::RawPointerToRefInst: + case ValueKind::AddressToPointerInst: + addr = cast(addr)->getOperand(0); + break; + default: + return false; + } + } +} + SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) { // (load (upcast-ptr %x)) -> (upcast-ref (load %x)) Builder.setCurrentDebugScope(LI->getDebugScope()); @@ -606,6 +662,17 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) { if (SILInstruction *I = optimizeLoadFromStringLiteral(LI)) return I; + // Constant-propagate the 0 value when loading "count" or "capacity" from the + // empty Array, Set or Dictionary storage. + // On high-level SIL this optimization is also done by the + // ArrayCountPropagation pass, but only for Array. And even for Array it's + // sometimes needed to propagate the empty-array count when high-level + // semantics function are already inlined. + // Note that for non-empty arrays/sets/dictionaries, the count can be + // propagated by redundant load elimination. + if (isZeroLoadFromEmptyCollection(LI)) + return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), 0); + return nullptr; } diff --git a/test/SILOptimizer/empty_collection_count.swift b/test/SILOptimizer/empty_collection_count.swift new file mode 100644 index 0000000000000..5f58344f20d49 --- /dev/null +++ b/test/SILOptimizer/empty_collection_count.swift @@ -0,0 +1,38 @@ +// RUN: %target-swift-frontend -emit-sil -O %s | %FileCheck %s +// RUN: %target-swift-frontend -emit-sil -Osize %s | %FileCheck %s + +// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib + +// This is an end-to-end test if the count and/or capacity from empty +// array/set/dictionary singletons can be propagated. + +// CHECK-LABEL: sil @{{.*}}testArray +// CHECK-NOT: global_addr +// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0 +// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}}) +// CHECK: return [[I]] +public func testArray() -> Int { + let d = Array() + return d.count +} + +// CHECK-LABEL: sil @{{.*}}testDictionary +// CHECK-NOT: global_addr +// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0 +// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}}) +// CHECK: return [[I]] +public func testDictionary() -> Int { + let d = Dictionary() + return d.count + d.capacity +} + +// CHECK-LABEL: sil @{{.*}}testSet +// CHECK-NOT: global_addr +// CHECK: [[Z:%[0-9]+]] = integer_literal $Builtin.Int{{[0-9]*}}, 0 +// CHECK: [[I:%[0-9]+]] = struct $Int ([[Z]] : $Builtin.Int{{[0-9]*}}) +// CHECK: return [[I]] +public func testSet() -> Int { + let d = Set() + return d.count + d.capacity +} + diff --git a/test/SILOptimizer/optionset.swift b/test/SILOptimizer/optionset.swift index ba74ab1f561e9..1ee43c2ef2135 100644 --- a/test/SILOptimizer/optionset.swift +++ b/test/SILOptimizer/optionset.swift @@ -22,6 +22,16 @@ public func returnTestOptions() -> TestOptions { return [.first, .second, .third, .fourth] } +// CHECK: sil @{{.*}}returnEmptyTestOptions{{.*}} +// CHECK-NEXT: bb0: +// CHECK-NEXT: integer_literal {{.*}}, 0 +// CHECK-NEXT: struct $Int +// CHECK-NEXT: struct $TestOptions +// CHECK-NEXT: return +public func returnEmptyTestOptions() -> TestOptions { + return [] +} + // CHECK: alloc_global @{{.*}}globalTestOptions{{.*}} // CHECK-NEXT: global_addr // CHECK-NEXT: integer_literal {{.*}}, 15