Skip to content

SIL optimizer: propagate count and capacity from empty Array/Set/Dictionary singletons. #29428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<BuiltinIntegerType>();
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<GlobalAddrInst>(addr)->getReferencedGlobal()->getName();
return gName == "_swiftEmptyArrayStorage" ||
gName == "_swiftEmptyDictionarySingleton" ||
gName == "_swiftEmptySetSingleton";
}
case ValueKind::StructElementAddrInst: {
auto *SEA = cast<StructElementAddrInst>(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<RefElementAddrInst>(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<SingleValueInstruction>(addr)->getOperand(0);
break;
default:
return false;
}
}
}

SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
// (load (upcast-ptr %x)) -> (upcast-ref (load %x))
Builder.setCurrentDebugScope(LI->getDebugScope());
Expand All @@ -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;
}

Expand Down
38 changes: 38 additions & 0 deletions test/SILOptimizer/empty_collection_count.swift
Original file line number Diff line number Diff line change
@@ -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<Int>()
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<Int, Int>()
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<Int>()
return d.count + d.capacity
}

10 changes: 10 additions & 0 deletions test/SILOptimizer/optionset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down