Skip to content

[Profiler] Avoid introducing empty unreachable regions #60579

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 2 commits into from
Aug 19, 2022
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
60 changes: 52 additions & 8 deletions lib/SIL/IR/SILProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,31 +341,65 @@ class CounterExpr {
/// Returns true if this is a Zero node.
bool isZero() const { return K == Kind::Zero; }

/// Returns true if the counter is semantically a Zero node. This considers
/// the simplified version of the counter that has eliminated redundant
/// operations.
bool isSemanticallyZero() const {
// Run the counter through the counter builder to simplify it, using a dummy
// mapping of unique counter indices for each node reference. The value of
// the indices doesn't matter, but we need to ensure that e.g subtraction
// of a node from itself cancels out.
llvm::coverage::CounterExpressionBuilder Builder;
llvm::DenseMap<ASTNode, unsigned> DummyIndices;
unsigned LastIdx = 0;
auto Counter = expand(Builder, [&](auto Node) {
if (!DummyIndices.count(Node)) {
DummyIndices[Node] = LastIdx;
LastIdx += 1;
}
return DummyIndices[Node];
});
return Counter.isZero();
}

/// Expand this node into an llvm::coverage::Counter.
///
/// Updates \c Builder with any expressions that are needed to represent this
/// counter.
llvm::coverage::Counter
expand(llvm::coverage::CounterExpressionBuilder &Builder,
llvm::DenseMap<ASTNode, unsigned> &Counters) const {
llvm::function_ref<unsigned(ASTNode)> GetCounterIdx) const {
switch (K) {
case Kind::Zero:
return llvm::coverage::Counter::getZero();
case Kind::Node:
return llvm::coverage::Counter::getCounter(Counters[Node]);
return llvm::coverage::Counter::getCounter(GetCounterIdx(Node));
case Kind::Add:
return Builder.add(LHS->expand(Builder, Counters),
RHS->expand(Builder, Counters));
return Builder.add(LHS->expand(Builder, GetCounterIdx),
RHS->expand(Builder, GetCounterIdx));
case Kind::Sub:
return Builder.subtract(LHS->expand(Builder, Counters),
RHS->expand(Builder, Counters));
return Builder.subtract(LHS->expand(Builder, GetCounterIdx),
RHS->expand(Builder, GetCounterIdx));
case Kind::Ref:
return LHS->expand(Builder, Counters);
return LHS->expand(Builder, GetCounterIdx);
}

llvm_unreachable("Unhandled Kind in switch.");
}

/// Expand this node into an llvm::coverage::Counter.
///
/// Updates \c Builder with any expressions that are needed to represent this
/// counter.
llvm::coverage::Counter
expand(llvm::coverage::CounterExpressionBuilder &Builder,
const llvm::DenseMap<ASTNode, unsigned> &Counters) const {
return expand(Builder, [&](auto Node) {
// FIXME: We ought to assert that the node is present.
return Counters.lookup(Node);
});
}

void print(raw_ostream &OS) const {
switch (K) {
case Kind::Zero:
Expand Down Expand Up @@ -780,7 +814,17 @@ struct CoverageMapping : public ASTWalker {
if (ControlFlowAdjust)
Count = &createCounter(CounterExpr::Sub(*Count, *ControlFlowAdjust));

RegionStack.emplace_back(ASTNode(), *Count, getEndLoc(Scope), None);
if (Count->isSemanticallyZero()) {
// If the counter is semantically zero, form an 'incomplete' region with
// no starting location. This prevents forming unreachable regions unless
// there is a following statement or expression to extend the region.
RegionStack.emplace_back(ASTNode(), *Count, None, None);
} else {
// Otherwise, we have a non-zero counter, so form a new region starting
// at the end of the previous scope. This ensures the user covers both
// branches of a condition.
RegionStack.emplace_back(ASTNode(), *Count, getEndLoc(Scope), None);
}
}

/// Push a region covering \c Node onto the stack.
Expand Down
71 changes: 71 additions & 0 deletions test/Profiler/coverage_if.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,74 @@ func foo(x : Bool) { // CHECK: [[@LINE]]:20 -> {{[0-9]+}}:2 : 0

foo(x: true);
foo(x: false);

// rdar://29390569 – Make sure we don't add a spurious unreachable empty region.

// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo1
func foo1() -> Int { // CHECK: [[@LINE]]:20 -> {{[0-9]+}}:2 : 0
if .random() { // CHECK: [[@LINE]]:6 -> [[@LINE]]:15 : 0
return 0 // CHECK: [[@LINE-1]]:16 -> [[@LINE+2]]:4 : 1
// CHECK: [[@LINE+1]]:4 -> {{[0-9]+}}:2 : (0 - 1)
} else if .random() { // CHECK: [[@LINE]]:13 -> [[@LINE]]:22 : (0 - 1)
return 1 // CHECK: [[@LINE-1]]:23 -> [[@LINE+1]]:4 : 2
} else { // CHECK: [[@LINE]]:4 -> {{[0-9]+}}:2 : ((0 - 1) - 2)
return 2 // CHECK: [[@LINE-1]]:10 -> [[@LINE+1]]:4 : ((0 - 1) - 2)
} // CHECK-NOT: zero
}

// ...but we will add an unreachable region if you write code there.
// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo2
func foo2() -> Int {
if .random() {
return 0
} else if .random() {
return 1
} else {
return 2
}
_ = foo1() // CHECK: [[@LINE]]:3 -> [[@LINE+1]]:2 : zero
}

// Make sure we don't add unreachable regions for a labeled jump either.
// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo3
func foo3() -> Int { // CHECK: [[@LINE]]:20 -> {{[0-9]+}}:2 : 0
x: do { // CHECK: [[@LINE]]:9 -> [[@LINE+6]]:4 : 0
if .random() { // CHECK: [[@LINE]]:8 -> [[@LINE]]:17 : 0
return 0 // CHECK: [[@LINE-1]]:18 -> [[@LINE+1]]:6 : 1
} else { // CHECK: [[@LINE]]:6 -> [[@LINE+3]]:4 : (0 - 1)
break x // CHECK: [[@LINE-1]]:12 -> [[@LINE+1]]:6 : (0 - 1)
} // CHECK-NOT: zero
}
return 2
}
// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo4
func foo4() -> Int {
x: do {
if .random() {
return 0
} else {
break x
}
let y = 0 // CHECK: [[@LINE]]:13 -> [[@LINE+1]]:4 : zero
}
return 2
}

// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo5
func foo5() throws -> Int { // CHECK: [[@LINE]]:27 -> {{[0-9]+}}:2 : 0
if .random() { // CHECK: [[@LINE]]:6 -> [[@LINE]]:15 : 0
return 0 // CHECK: [[@LINE-1]]:16 -> [[@LINE+1]]:4 : 1
} else { // CHECK: [[@LINE]]:4 -> {{[0-9]+}}:2 : (0 - 1)
struct S: Error {} // CHECK: [[@LINE-1]]:10 -> [[@LINE+2]]:4 : (0 - 1)
throw S()
} // CHECK-NOT: zero
}

// CHECK-LABEL: sil_coverage_map {{.*}}// coverage_if.foo6
func foo6(_ x: Int?) -> Int? { // CHECK: [[@LINE]]:30 -> {{[0-9]+}}:2 : 0
if let x = x {
return x // CHECK: [[@LINE-1]]:16 -> [[@LINE+1]]:4 : 1
} else { // CHECK: [[@LINE]]:4 -> {{[0-9]+}}:2 : (0 - 1)
return nil // CHECK: [[@LINE-1]]:10 -> [[@LINE+1]]:4 : (0 - 1)
} // CHECK-NOT: zero
}