llvm::createPGOFuncNameMetadata(*Fn, FuncName);
}
+/// The version of the PGO hash algorithm.
+enum PGOHashVersion : unsigned {
+ PGO_HASH_V1,
+ PGO_HASH_V2,
+
+ // Keep this set to the latest hash version.
+ PGO_HASH_LATEST = PGO_HASH_V2
+};
+
namespace {
/// \brief Stable hasher for PGO region counters.
///
class PGOHash {
uint64_t Working;
unsigned Count;
+ PGOHashVersion HashVersion;
llvm::MD5 MD5;
static const int NumBitsPerType = 6;
BinaryOperatorLAnd,
BinaryOperatorLOr,
BinaryConditionalOperator,
+ // The preceding values are available with PGO_HASH_V1.
+
+ EndOfScope,
+ IfThenBranch,
+ IfElseBranch,
+ GotoStmt,
+ IndirectGotoStmt,
+ BreakStmt,
+ ContinueStmt,
+ ReturnStmt,
+ ThrowExpr,
+ UnaryOperatorLNot,
+ BinaryOperatorLT,
+ BinaryOperatorGT,
+ BinaryOperatorLE,
+ BinaryOperatorGE,
+ BinaryOperatorEQ,
+ BinaryOperatorNE,
+ // The preceding values are available with PGO_HASH_V2.
// Keep this last. It's for the static assert that follows.
LastHashType
};
static_assert(LastHashType <= TooBig, "Too many types in HashType");
- // TODO: When this format changes, take in a version number here, and use the
- // old hash calculation for file formats that used the old hash.
- PGOHash() : Working(0), Count(0) {}
+ PGOHash(PGOHashVersion HashVersion)
+ : Working(0), Count(0), HashVersion(HashVersion), MD5() {}
void combine(HashType Type);
uint64_t finalize();
+ PGOHashVersion getHashVersion() const { return HashVersion; }
};
const int PGOHash::NumBitsPerType;
const unsigned PGOHash::NumTypesPerWord;
const unsigned PGOHash::TooBig;
+/// Get the PGO hash version used in the given indexed profile.
+static PGOHashVersion getPGOHashVersion(llvm::IndexedInstrProfReader *PGOReader,
+ CodeGenModule &CGM) {
+ if (PGOReader->getVersion() <= 4)
+ return PGO_HASH_V1;
+ return PGO_HASH_V2;
+}
+
/// A RecursiveASTVisitor that fills a map of statements to PGO counters.
struct MapRegionCounters : public RecursiveASTVisitor<MapRegionCounters> {
+ using Base = RecursiveASTVisitor<MapRegionCounters>;
+
/// The next counter value to assign.
unsigned NextCounter;
/// The function hash.
/// The map of statements to counters.
llvm::DenseMap<const Stmt *, unsigned> &CounterMap;
- MapRegionCounters(llvm::DenseMap<const Stmt *, unsigned> &CounterMap)
- : NextCounter(0), CounterMap(CounterMap) {}
+ MapRegionCounters(PGOHashVersion HashVersion,
+ llvm::DenseMap<const Stmt *, unsigned> &CounterMap)
+ : NextCounter(0), Hash(HashVersion), CounterMap(CounterMap) {}
// Blocks and lambdas are handled as separate functions, so we need not
// traverse them in the parent context.
return true;
}
- bool VisitStmt(const Stmt *S) {
- auto Type = getHashType(S);
- if (Type == PGOHash::None)
- return true;
+ /// If \p S gets a fresh counter, update the counter mappings. Return the
+ /// V1 hash of \p S.
+ PGOHash::HashType updateCounterMappings(Stmt *S) {
+ auto Type = getHashType(PGO_HASH_V1, S);
+ if (Type != PGOHash::None)
+ CounterMap[S] = NextCounter++;
+ return Type;
+ }
- CounterMap[S] = NextCounter++;
- Hash.combine(Type);
+ /// Include \p S in the function hash.
+ bool VisitStmt(Stmt *S) {
+ auto Type = updateCounterMappings(S);
+ if (Hash.getHashVersion() != PGO_HASH_V1)
+ Type = getHashType(Hash.getHashVersion(), S);
+ if (Type != PGOHash::None)
+ Hash.combine(Type);
return true;
}
- PGOHash::HashType getHashType(const Stmt *S) {
+
+ bool TraverseIfStmt(IfStmt *If) {
+ // If we used the V1 hash, use the default traversal.
+ if (Hash.getHashVersion() == PGO_HASH_V1)
+ return Base::TraverseIfStmt(If);
+
+ // Otherwise, keep track of which branch we're in while traversing.
+ VisitStmt(If);
+ for (Stmt *CS : If->children()) {
+ if (!CS)
+ continue;
+ if (CS == If->getThen())
+ Hash.combine(PGOHash::IfThenBranch);
+ else if (CS == If->getElse())
+ Hash.combine(PGOHash::IfElseBranch);
+ TraverseStmt(CS);
+ }
+ Hash.combine(PGOHash::EndOfScope);
+ return true;
+ }
+
+// If the statement type \p N is nestable, and its nesting impacts profile
+// stability, define a custom traversal which tracks the end of the statement
+// in the hash (provided we're not using the V1 hash).
+#define DEFINE_NESTABLE_TRAVERSAL(N) \
+ bool Traverse##N(N *S) { \
+ Base::Traverse##N(S); \
+ if (Hash.getHashVersion() != PGO_HASH_V1) \
+ Hash.combine(PGOHash::EndOfScope); \
+ return true; \
+ }
+
+ DEFINE_NESTABLE_TRAVERSAL(WhileStmt)
+ DEFINE_NESTABLE_TRAVERSAL(DoStmt)
+ DEFINE_NESTABLE_TRAVERSAL(ForStmt)
+ DEFINE_NESTABLE_TRAVERSAL(CXXForRangeStmt)
+ DEFINE_NESTABLE_TRAVERSAL(ObjCForCollectionStmt)
+ DEFINE_NESTABLE_TRAVERSAL(CXXTryStmt)
+ DEFINE_NESTABLE_TRAVERSAL(CXXCatchStmt)
+
+ /// Get version \p HashVersion of the PGO hash for \p S.
+ PGOHash::HashType getHashType(PGOHashVersion HashVersion, const Stmt *S) {
switch (S->getStmtClass()) {
default:
break;
return PGOHash::BinaryOperatorLAnd;
if (BO->getOpcode() == BO_LOr)
return PGOHash::BinaryOperatorLOr;
+ if (HashVersion == PGO_HASH_V2) {
+ switch (BO->getOpcode()) {
+ default:
+ break;
+ case BO_LT:
+ return PGOHash::BinaryOperatorLT;
+ case BO_GT:
+ return PGOHash::BinaryOperatorGT;
+ case BO_LE:
+ return PGOHash::BinaryOperatorLE;
+ case BO_GE:
+ return PGOHash::BinaryOperatorGE;
+ case BO_EQ:
+ return PGOHash::BinaryOperatorEQ;
+ case BO_NE:
+ return PGOHash::BinaryOperatorNE;
+ }
+ }
break;
}
}
+
+ if (HashVersion == PGO_HASH_V2) {
+ switch (S->getStmtClass()) {
+ default:
+ break;
+ case Stmt::GotoStmtClass:
+ return PGOHash::GotoStmt;
+ case Stmt::IndirectGotoStmtClass:
+ return PGOHash::IndirectGotoStmt;
+ case Stmt::BreakStmtClass:
+ return PGOHash::BreakStmt;
+ case Stmt::ContinueStmtClass:
+ return PGOHash::ContinueStmt;
+ case Stmt::ReturnStmtClass:
+ return PGOHash::ReturnStmt;
+ case Stmt::CXXThrowExprClass:
+ return PGOHash::ThrowExpr;
+ case Stmt::UnaryOperatorClass: {
+ const UnaryOperator *UO = cast<UnaryOperator>(S);
+ if (UO->getOpcode() == UO_LNot)
+ return PGOHash::UnaryOperatorLNot;
+ break;
+ }
+ }
+ }
+
return PGOHash::None;
}
};
}
void CodeGenPGO::mapRegionCounters(const Decl *D) {
+ // Use the latest hash version when inserting instrumentation, but use the
+ // version in the indexed profile if we're reading PGO data.
+ PGOHashVersion HashVersion = PGO_HASH_LATEST;
+ if (auto *PGOReader = CGM.getPGOReader())
+ HashVersion = getPGOHashVersion(PGOReader, CGM);
+
RegionCounterMap.reset(new llvm::DenseMap<const Stmt *, unsigned>);
- MapRegionCounters Walker(*RegionCounterMap);
+ MapRegionCounters Walker(HashVersion, *RegionCounterMap);
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(D))
Walker.TraverseDecl(const_cast<FunctionDecl *>(FD));
else if (const ObjCMethodDecl *MD = dyn_cast_or_null<ObjCMethodDecl>(D))
--- /dev/null
+// REQUIRES: shell
+
+// Check that all of the hashes in this file are unique (i.e, that none of the
+// profiles for these functions are mutually interchangeable).
+//
+// RUN: llvm-profdata show -all-functions %S/Inputs/cxx-hash-v2.profdata.v5 | grep "Hash: 0x" | sort > %t.hashes
+// RUN: uniq %t.hashes > %t.hashes.unique
+// RUN: diff %t.hashes %t.hashes.unique
+
+// RUN: llvm-profdata merge %S/Inputs/cxx-hash-v2.proftext -o %t.profdata
+// RUN: %clang_cc1 -std=c++11 -fexceptions -fcxx-exceptions -triple x86_64-apple-macosx10.9 -main-file-name cxx-hash-v2.mm %s -o /dev/null -emit-llvm -fprofile-instrument-use-path=%t.profdata 2>&1 | FileCheck %s -allow-empty
+// RUN: %clang_cc1 -std=c++11 -fexceptions -fcxx-exceptions -triple x86_64-apple-macosx10.9 -main-file-name cxx-hash-v2.mm %s -o /dev/null -emit-llvm -fprofile-instrument-use-path=%S/Inputs/cxx-hash-v2.profdata.v5 2>&1 | FileCheck %s -allow-empty
+
+// CHECK-NOT: warning: profile data may be out of date
+
+int x;
+int arr[1] = {0};
+
+void loop_after_if_else() {
+ if (1)
+ x = 1;
+ else
+ x = 2;
+ while (0)
+ ++x;
+}
+
+void loop_in_then_block() {
+ if (1) {
+ while (0)
+ ++x;
+ } else {
+ x = 2;
+ }
+}
+
+void loop_in_else_block() {
+ if (1) {
+ x = 1;
+ } else {
+ while (0)
+ ++x;
+ }
+}
+
+void if_inside_of_for() {
+ for (x = 0; x < 0; ++x) {
+ x = 1;
+ if (1)
+ x = 2;
+ }
+}
+
+void if_outside_of_for() {
+ for (x = 0; x < 0; ++x)
+ x = 1;
+ if (1)
+ x = 2;
+}
+
+void if_inside_of_while() {
+ while (0) {
+ x = 1;
+ if (1)
+ x = 2;
+ }
+}
+
+void if_outside_of_while() {
+ while (0)
+ x = 1;
+ if (1)
+ x = 2;
+}
+
+void nested_dos() {
+ do {
+ do {
+ ++x;
+ } while (0);
+ } while (0);
+}
+
+void consecutive_dos() {
+ do {
+ } while (0);
+ do {
+ ++x;
+ } while (0);
+}
+
+void loop_empty() {
+ for (x = 0; x < 5; ++x) {}
+}
+
+void loop_return() {
+ for (x = 0; x < 5; ++x)
+ return;
+}
+
+void loop_continue() {
+ for (x = 0; x < 5; ++x)
+ continue;
+}
+
+void loop_break() {
+ for (x = 0; x < 5; ++x)
+ break;
+}
+
+void no_gotos() {
+ static void *dispatch[] = {&&done};
+ x = 0;
+done:
+ ++x;
+}
+
+void direct_goto() {
+ static void *dispatch[] = {&&done};
+ x = 0;
+ goto done;
+done:
+ ++x;
+}
+
+void indirect_goto() {
+ static void *dispatch[] = {&&done};
+ x = 0;
+ goto *dispatch[x];
+done:
+ ++x;
+}
+
+void nested_for_ranges() {
+ for (int a : arr)
+ for (int b : arr)
+ ++x;
+}
+
+void consecutive_for_ranges() {
+ for (int a : arr) {}
+ for (int b : arr)
+ ++x;
+}
+
+void nested_try_catch() {
+ try {
+ try {
+ ++x;
+ } catch (...) {}
+ } catch (...) {}
+}
+
+void consecutive_try_catch() {
+ try {} catch (...) {}
+ try {
+ ++x;
+ } catch (...) {}
+}
+
+void no_throw() {}
+
+void has_throw() {
+ throw 0;
+}
+
+void single_lnot() {
+ if (!x) {}
+}
+
+void double_lnot() {
+ if (!!x) {}
+}
+
+int main() {
+ return 0;
+}