Skip to content

Commit

Permalink
[InstrProf] Minimal Block Coverage
Browse files Browse the repository at this point in the history
This diff implements minimal block coverage instrumentation. When the `-pgo-block-coverage` option is used, basic blocks will be instrumented for block coverage using single byte booleans. The coverage of some basic blocks can be inferred from others, so not every basic block is instrumented. In fact, we found that only ~60% of basic blocks need to be instrumented. These differences lead to less size overhead when compared to instrumenting block counts. For example, block coverage on the clang binary has an overhead of 20 Mi (17%) compared to 56 Mi (47%) with block counts.

Even though block coverage profiles have less precision than block count profiles, they can still be used to guide optimizations. In `PGOUseFunc` we use block coverage to populate edge weights such that BFI gives nonzero counts to only covered blocks. We do this by 1) setting the entry count of covered functions to a large value, i.e., 10000 and 2) populating edge weights using block coverage. In the next diff https://reviews.llvm.org/D125743 we use BFI to guide the machine outliner to avoid outlining covered blocks. This `-pgo-block-coverage` option provides a trade off of generating less precise profiles for faster and smaller instrumented binaries.

The `BlockCoverageInference` class defines the algorithm to find the minimal set of basic blocks that need to be instrumented for coverage. This is different from the Kirchhoff circuit law optimization that is used for edge **counts** because that does not work for block **coverage**. The reason for this is that edge counts can be added together to find a missing count while block coverage cannot since they store boolean values. So we need a new algorithm to find which blocks must be instrumented.

The details on this algorithm can be found in this paper titled "Minimum Coverage Instrumentation": https://arxiv.org/abs/2208.13907

Special thanks to Julian Mestre for creating this block coverage inference algorithm.

Binary size of `clang` using `-O2`:

* Base
  * `.text`: 65.8 Mi
  * Total: 119 Mi
* IRPGO (`-fprofile-generate -mllvm -disable-vp -mllvm -debug-info-correlate`)
  * `.text`: 93.0 Mi
  * `__llvm_prf_cnts`: 14.5 Mi
  * Total: 175 Mi
* Minimal Block Coverage (`-fprofile-generate -mllvm -disable-vp -mllvm -debug-info-correlate -mllvm -pgo-block-coverage`)
  * `.text`: 82.1 Mi
  * `__llvm_prf_cnts`: 1.38 Mi
  * Total: 139 Mi

Reviewed By: spupyrev, kyulee

Differential Revision: https://reviews.llvm.org/D124490
  • Loading branch information
ellishg committed Mar 29, 2023
1 parent 8ac330b commit 167e8f8
Show file tree
Hide file tree
Showing 8 changed files with 868 additions and 78 deletions.
47 changes: 47 additions & 0 deletions compiler-rt/test/profile/instrprof-block-coverage.c
@@ -0,0 +1,47 @@
// RUN: %clang_pgogen -mllvm -pgo-block-coverage %s -o %t.out
// RUN: env LLVM_PROFILE_FILE=%t1.profraw %run %t.out 1
// RUN: env LLVM_PROFILE_FILE=%t2.profraw %run %t.out 2
// RUN: llvm-profdata merge -o %t.profdata %t1.profraw %t2.profraw
// RUN: %clang_profuse=%t.profdata -mllvm -pgo-verify-bfi -o - -S -emit-llvm %s 2>%t.errs | FileCheck %s --implicit-check-not="!prof"
// RUN: FileCheck %s < %t.errs --allow-empty --check-prefix=CHECK-ERROR

#include <stdlib.h>

// CHECK: @foo({{.*}})
// CHECK-SAME: !prof ![[PROF0:[0-9]+]]
void foo(int a) {
// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !prof ![[PROF1:[0-9]+]]
if (a % 2 == 0) {
//
} else {
//
}

// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !prof ![[PROF1]]
for (int i = 1; i < a; i++) {
// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !prof ![[PROF2:[0-9]+]]
if (a % 3 == 0) {
//
} else {
// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !prof ![[PROF2]]
if (a % 1001 == 0) {
return;
}
}
}

return;
}

// CHECK: @main({{.*}})
// CHECK-SAME: !prof ![[PROF0]]
int main(int argc, char *argv[]) {
foo(atoi(argv[1]));
return 0;
}

// CHECK-DAG: ![[PROF0]] = !{!"function_entry_count", i64 10000}
// CHECK-DAG: ![[PROF1]] = !{!"branch_weights", i32 1, i32 1}
// CHECK-DAG: ![[PROF2]] = !{!"branch_weights", i32 0, i32 1}

// CHECK-ERROR-NOT: warning: {{.*}}: Found inconsistent block coverage
@@ -0,0 +1,86 @@
//===-- BlockCoverageInference.h - Minimal Execution Coverage ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file finds the minimum set of blocks on a CFG that must be instrumented
/// to infer execution coverage for the whole graph.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_BLOCKCOVERAGEINFERENCE_H
#define LLVM_TRANSFORMS_INSTRUMENTATION_BLOCKCOVERAGEINFERENCE_H

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/raw_ostream.h"

namespace llvm {

class Function;
class BasicBlock;
class DotFuncBCIInfo;

class BlockCoverageInference {
friend class DotFuncBCIInfo;

public:
using BlockSet = SmallSetVector<const BasicBlock *, 4>;

BlockCoverageInference(const Function &F, bool ForceInstrumentEntry);

/// \return true if \p BB should be instrumented for coverage.
bool shouldInstrumentBlock(const BasicBlock &BB) const;

/// \return the set of blocks \p Deps such that \p BB is covered iff any
/// blocks in \p Deps are covered.
BlockSet getDependencies(const BasicBlock &BB) const;

/// \return a hash that depends on the set of instrumented blocks.
uint64_t getInstrumentedBlocksHash() const;

/// Dump the inference graph.
void dump(raw_ostream &OS) const;

/// View the inferred block coverage as a dot file.
/// Filled gray blocks are instrumented, red outlined blocks are found to be
/// covered, red edges show that a block's coverage can be inferred from its
/// successors, and blue edges show that a block's coverage can be inferred
/// from its predecessors.
void viewBlockCoverageGraph(
const DenseMap<const BasicBlock *, bool> *Coverage = nullptr) const;

private:
const Function &F;
bool ForceInstrumentEntry;

/// Maps blocks to a minimal list of predecessors that can be used to infer
/// this block's coverage.
DenseMap<const BasicBlock *, BlockSet> PredecessorDependencies;

/// Maps blocks to a minimal list of successors that can be used to infer
/// this block's coverage.
DenseMap<const BasicBlock *, BlockSet> SuccessorDependencies;

/// Compute \p PredecessorDependencies and \p SuccessorDependencies.
void findDependencies();

/// Find the set of basic blocks that are reachable from \p Start without the
/// basic block \p Avoid.
void getReachableAvoiding(const BasicBlock &Start, const BasicBlock &Avoid,
bool IsForward, BlockSet &Reachable) const;

static std::string getBlockNames(ArrayRef<const BasicBlock *> BBs);
static std::string getBlockNames(BlockSet BBs) {
return getBlockNames(ArrayRef<const BasicBlock *>(BBs.begin(), BBs.end()));
}
};

} // end namespace llvm

#endif // LLVM_TRANSFORMS_INSTRUMENTATION_BLOCKCOVERAGEINFERENCE_H

0 comments on commit 167e8f8

Please sign in to comment.