GCC AutoFDO profile reader - Initial support.
authorDiego Novillo <dnovillo@google.com>
Thu, 17 Sep 2015 00:17:24 +0000 (00:17 +0000)
committerDiego Novillo <dnovillo@google.com>
Thu, 17 Sep 2015 00:17:24 +0000 (00:17 +0000)
This adds enough machinery to support reading simple GCC AutoFDO
profiles. It now supports reading flat profiles (no function calls).
Subsequent patches will add support for:

- Inlined calls (in particular, the inline call stack is not traversed
  to accumulate samples).

- Working sets and modules. These are used mostly for GCC's LIPO
  optimizations, so they're not needed in LLVM atm. I'm not sure that
  we will ever need them. For now, I've if0'd around the calls.

The patch also adds support in GCOV.h for gcov version V704 (generated
by GCC's profile conversion tool).

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@247874 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/ProfileData/SampleProf.h
include/llvm/ProfileData/SampleProfReader.h
include/llvm/Support/GCOV.h
lib/ProfileData/SampleProf.cpp
lib/ProfileData/SampleProfReader.cpp
test/Transforms/SampleProfile/Inputs/gcc-simple.afdo [new file with mode: 0644]
test/Transforms/SampleProfile/gcc-simple.ll [new file with mode: 0644]

index 1b82e55..1eea95d 100644 (file)
@@ -32,7 +32,8 @@ enum class sampleprof_error {
   too_large,
   truncated,
   malformed,
-  unrecognized_format
+  unrecognized_format,
+  not_implemented
 };
 
 inline std::error_code make_error_code(sampleprof_error E) {
index c082a1a..770d25e 100644 (file)
@@ -24,6 +24,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/GCOV.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -57,7 +58,7 @@ namespace sampleprof {
 ///
 /// The reader supports two file formats: text and binary. The text format
 /// is useful for debugging and testing, while the binary format is more
-/// compact. They can both be used interchangeably.
+/// compact and I/O efficient. They can both be used interchangeably.
 class SampleProfileReader {
 public:
   SampleProfileReader(std::unique_ptr<MemoryBuffer> B, LLVMContext &C)
@@ -86,7 +87,7 @@ public:
   StringMap<FunctionSamples> &getProfiles() { return Profiles; }
 
   /// \brief Report a parse error message.
-  void reportParseError(int64_t LineNumber, Twine Msg) const {
+  void reportError(int64_t LineNumber, Twine Msg) const {
     Ctx.diagnose(DiagnosticInfoSampleProfile(Buffer->getBufferIdentifier(),
                                              LineNumber, Msg));
   }
@@ -163,6 +164,86 @@ protected:
   const uint8_t *End;
 };
 
+// Represents the source position in GCC sample profiles.
+struct SourceInfo {
+  SourceInfo()
+      : FuncName(), DirName(), FileName(), StartLine(0), Line(0),
+        Discriminator(0) {}
+
+  SourceInfo(StringRef FuncName, StringRef DirName, StringRef FileName,
+             uint32_t StartLine, uint32_t Line, uint32_t Discriminator)
+      : FuncName(FuncName), DirName(DirName), FileName(FileName),
+        StartLine(StartLine), Line(Line), Discriminator(Discriminator) {}
+
+  bool operator<(const SourceInfo &p) const;
+
+  uint32_t Offset() const { return ((Line - StartLine) << 16) | Discriminator; }
+
+  bool Malformed() const { return Line < StartLine; }
+
+  StringRef FuncName;
+  StringRef DirName;
+  StringRef FileName;
+  uint32_t StartLine;
+  uint32_t Line;
+  uint32_t Discriminator;
+};
+
+typedef std::vector<SourceInfo> SourceStack;
+
+// Supported histogram types in GCC.  Currently, we only need support for
+// call target histograms.
+enum HistType {
+  HIST_TYPE_INTERVAL,
+  HIST_TYPE_POW2,
+  HIST_TYPE_SINGLE_VALUE,
+  HIST_TYPE_CONST_DELTA,
+  HIST_TYPE_INDIR_CALL,
+  HIST_TYPE_AVERAGE,
+  HIST_TYPE_IOR,
+  HIST_TYPE_INDIR_CALL_TOPN
+};
+
+class SampleProfileReaderGCC : public SampleProfileReader {
+public:
+  SampleProfileReaderGCC(std::unique_ptr<MemoryBuffer> B, LLVMContext &C)
+      : SampleProfileReader(std::move(B), C), GcovBuffer(Buffer.get()) {}
+
+  /// \brief Read and validate the file header.
+  std::error_code readHeader() override;
+
+  /// \brief Read sample profiles from the associated file.
+  std::error_code read() override;
+
+  /// \brief Return true if \p Buffer is in the format supported by this class.
+  static bool hasFormat(const MemoryBuffer &Buffer);
+
+protected:
+  std::error_code readNameTable();
+  std::error_code addSourceCount(StringRef Name, const SourceStack &Src,
+                                 uint64_t Count);
+  std::error_code readOneFunctionProfile(const SourceStack &Stack, bool Update);
+  std::error_code readFunctionProfiles();
+  std::error_code readModuleGroup();
+  std::error_code readWorkingSet();
+  std::error_code skipNextWord();
+  template <typename T> ErrorOr<T> readNumber();
+  ErrorOr<StringRef> readString();
+
+  /// \brief Read the section tag and check that it's the same as \p Expected.
+  std::error_code readSectionTag(uint32_t Expected);
+
+  /// GCOV buffer containing the profile.
+  GCOVBuffer GcovBuffer;
+
+  /// Function names in this profile.
+  std::vector<std::string> Names;
+
+  /// GCOV tags used to separate sections in the profile file.
+  static const uint32_t GCOVTagAFDOFileNames = 0xaa000000;
+  static const uint32_t GCOVTagAFDOFunction = 0xac000000;
+};
+
 } // End namespace sampleprof
 
 } // End namespace llvm
index c2e34bd..4c35128 100644 (file)
@@ -30,7 +30,7 @@ class GCOVBlock;
 class FileInfo;
 
 namespace GCOV {
-enum GCOVVersion { V402, V404 };
+enum GCOVVersion { V402, V404, V704 };
 } // end GCOV namespace
 
 /// GCOVOptions - A struct for passing gcov options between functions.
@@ -90,6 +90,11 @@ public:
       Version = GCOV::V404;
       return true;
     }
+    if (VersionStr == "*704") {
+      Cursor += 4;
+      Version = GCOV::V704;
+      return true;
+    }
     errs() << "Unexpected version: " << VersionStr << ".\n";
     return false;
   }
index 920c48a..027fa81 100644 (file)
@@ -38,6 +38,8 @@ class SampleProfErrorCategoryType : public std::error_category {
       return "Malformed profile data";
     case sampleprof_error::unrecognized_format:
       return "Unrecognized profile encoding format";
+    case sampleprof_error::not_implemented:
+      return "Unimplemented feature";
     }
     llvm_unreachable("A value of sampleprof_error has no message.");
   }
index b39bfd6..70964cb 100644 (file)
@@ -173,8 +173,8 @@ std::error_code SampleProfileReaderText::read() {
     // should not begin with a number.
     SmallVector<StringRef, 4> Matches;
     if (!HeadRE.match(*LineIt, &Matches)) {
-      reportParseError(LineIt.line_number(),
-                       "Expected 'mangled_name:NUM:NUM', found " + *LineIt);
+      reportError(LineIt.line_number(),
+                  "Expected 'mangled_name:NUM:NUM', found " + *LineIt);
       return sampleprof_error::malformed;
     }
     assert(Matches.size() == 4);
@@ -192,9 +192,9 @@ std::error_code SampleProfileReaderText::read() {
     // EOF or when we see the start of the next function.
     while (!LineIt.is_at_eof() && isdigit((*LineIt)[0])) {
       if (!LineSampleRE.match(*LineIt, &Matches)) {
-        reportParseError(
-            LineIt.line_number(),
-            "Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " + *LineIt);
+        reportError(LineIt.line_number(),
+                    "Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " +
+                        *LineIt);
         return sampleprof_error::malformed;
       }
       assert(Matches.size() == 5);
@@ -210,8 +210,8 @@ std::error_code SampleProfileReaderText::read() {
       while (CallsLine != "") {
         SmallVector<StringRef, 3> CallSample;
         if (!CallSampleRE.match(CallsLine, &CallSample)) {
-          reportParseError(LineIt.line_number(),
-                           "Expected 'mangled_name:NUM', found " + CallsLine);
+          reportError(LineIt.line_number(),
+                      "Expected 'mangled_name:NUM', found " + CallsLine);
           return sampleprof_error::malformed;
         }
         StringRef CalledFunction = CallSample[1];
@@ -243,7 +243,7 @@ template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() {
     EC = sampleprof_error::success;
 
   if (EC) {
-    reportParseError(0, EC.message());
+    reportError(0, EC.message());
     return EC;
   }
 
@@ -256,7 +256,7 @@ ErrorOr<StringRef> SampleProfileReaderBinary::readString() {
   StringRef Str(reinterpret_cast<const char *>(Data));
   if (Data + Str.size() + 1 > End) {
     EC = sampleprof_error::truncated;
-    reportParseError(0, EC.message());
+    reportError(0, EC.message());
     return EC;
   }
 
@@ -353,6 +353,264 @@ bool SampleProfileReaderBinary::hasFormat(const MemoryBuffer &Buffer) {
   return Magic == SPMagic();
 }
 
+bool SourceInfo::operator<(const SourceInfo &P) const {
+  if (Line != P.Line)
+    return Line < P.Line;
+  if (StartLine != P.StartLine)
+    return StartLine < P.StartLine;
+  if (Discriminator != P.Discriminator)
+    return Discriminator < P.Discriminator;
+  return FuncName < P.FuncName;
+}
+
+std::error_code SampleProfileReaderGCC::skipNextWord() {
+  uint32_t dummy;
+  if (!GcovBuffer.readInt(dummy))
+    return sampleprof_error::truncated;
+  return sampleprof_error::success;
+}
+
+template <typename T> ErrorOr<T> SampleProfileReaderGCC::readNumber() {
+  if (sizeof(T) <= sizeof(uint32_t)) {
+    uint32_t Val;
+    if (GcovBuffer.readInt(Val) && Val <= std::numeric_limits<T>::max())
+      return static_cast<T>(Val);
+  } else if (sizeof(T) <= sizeof(uint64_t)) {
+    uint64_t Val;
+    if (GcovBuffer.readInt64(Val) && Val <= std::numeric_limits<T>::max())
+      return static_cast<T>(Val);
+  }
+
+  std::error_code EC = sampleprof_error::malformed;
+  reportError(0, EC.message());
+  return EC;
+}
+
+ErrorOr<StringRef> SampleProfileReaderGCC::readString() {
+  StringRef Str;
+  if (!GcovBuffer.readString(Str))
+    return sampleprof_error::truncated;
+  return Str;
+}
+
+std::error_code SampleProfileReaderGCC::readHeader() {
+  // Read the magic identifier.
+  if (!GcovBuffer.readGCDAFormat())
+    return sampleprof_error::unrecognized_format;
+
+  // Read the version number. Note - the GCC reader does not validate this
+  // version, but the profile creator generates v704.
+  GCOV::GCOVVersion version;
+  if (!GcovBuffer.readGCOVVersion(version))
+    return sampleprof_error::unrecognized_format;
+
+  if (version != GCOV::V704)
+    return sampleprof_error::unsupported_version;
+
+  // Skip the empty integer.
+  if (std::error_code EC = skipNextWord())
+    return EC;
+
+  return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::readSectionTag(uint32_t Expected) {
+  uint32_t Tag;
+  if (!GcovBuffer.readInt(Tag))
+    return sampleprof_error::truncated;
+
+  if (Tag != Expected)
+    return sampleprof_error::malformed;
+
+  if (std::error_code EC = skipNextWord())
+    return EC;
+
+  return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::readNameTable() {
+  if (std::error_code EC = readSectionTag(GCOVTagAFDOFileNames))
+    return EC;
+
+  uint32_t Size;
+  if (!GcovBuffer.readInt(Size))
+    return sampleprof_error::truncated;
+
+  for (uint32_t I = 0; I < Size; ++I) {
+    StringRef Str;
+    if (!GcovBuffer.readString(Str))
+      return sampleprof_error::truncated;
+    Names.push_back(Str);
+  }
+
+  return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::readFunctionProfiles() {
+  if (std::error_code EC = readSectionTag(GCOVTagAFDOFunction))
+    return EC;
+
+  uint32_t NumFunctions;
+  if (!GcovBuffer.readInt(NumFunctions))
+    return sampleprof_error::truncated;
+
+  SourceStack Stack;
+  for (uint32_t I = 0; I < NumFunctions; ++I)
+    if (std::error_code EC = readOneFunctionProfile(Stack, true))
+      return EC;
+
+  return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::addSourceCount(StringRef Name,
+                                                       const SourceStack &Src,
+                                                       uint64_t Count) {
+  if (Src.size() == 0 || Src[0].Malformed())
+    return sampleprof_error::malformed;
+  FunctionSamples &FProfile = Profiles[Name];
+  FProfile.addTotalSamples(Count);
+  // FIXME(dnovillo) - Properly update inline stack for FnName.
+  FProfile.addBodySamples(Src[0].Line, Src[0].Discriminator, Count);
+  return sampleprof_error::success;
+}
+
+
+std::error_code
+SampleProfileReaderGCC::readOneFunctionProfile(const SourceStack &Stack,
+                                               bool Update) {
+  uint64_t HeadCount = 0;
+  if (Stack.size() == 0)
+    if (!GcovBuffer.readInt64(HeadCount))
+      return sampleprof_error::truncated;
+
+  uint32_t NameIdx;
+  if (!GcovBuffer.readInt(NameIdx))
+    return sampleprof_error::truncated;
+
+  StringRef Name(Names[NameIdx]);
+
+  uint32_t NumPosCounts;
+  if (!GcovBuffer.readInt(NumPosCounts))
+    return sampleprof_error::truncated;
+
+  uint32_t NumCallSites;
+  if (!GcovBuffer.readInt(NumCallSites))
+    return sampleprof_error::truncated;
+
+  if (Stack.size() == 0) {
+    FunctionSamples &FProfile = Profiles[Name];
+    FProfile.addHeadSamples(HeadCount);
+    if (FProfile.getTotalSamples() > 0)
+      Update = false;
+  }
+
+  for (uint32_t I = 0; I < NumPosCounts; ++I) {
+    uint32_t Offset;
+    if (!GcovBuffer.readInt(Offset))
+      return sampleprof_error::truncated;
+
+    uint32_t NumTargets;
+    if (!GcovBuffer.readInt(NumTargets))
+      return sampleprof_error::truncated;
+
+    uint64_t Count;
+    if (!GcovBuffer.readInt64(Count))
+      return sampleprof_error::truncated;
+
+    SourceInfo Info(Name, "", "", 0, Offset >> 16, Offset & 0xffff);
+    SourceStack NewStack;
+    NewStack.push_back(Info);
+    NewStack.insert(NewStack.end(), Stack.begin(), Stack.end());
+    if (Update)
+      addSourceCount(NewStack[NewStack.size() - 1].FuncName, NewStack, Count);
+
+    for (uint32_t J = 0; J < NumTargets; J++) {
+      uint32_t HistVal;
+      if (!GcovBuffer.readInt(HistVal))
+        return sampleprof_error::truncated;
+
+      if (HistVal != HIST_TYPE_INDIR_CALL_TOPN)
+        return sampleprof_error::malformed;
+
+      uint64_t TargetIdx;
+      if (!GcovBuffer.readInt64(TargetIdx))
+        return sampleprof_error::truncated;
+      StringRef TargetName(Names[TargetIdx]);
+
+      uint64_t TargetCount;
+      if (!GcovBuffer.readInt64(TargetCount))
+        return sampleprof_error::truncated;
+
+      if (Update) {
+        FunctionSamples &TargetProfile = Profiles[TargetName];
+        TargetProfile.addBodySamples(NewStack[0].Line,
+                                     NewStack[0].Discriminator, TargetCount);
+      }
+    }
+  }
+
+  for (uint32_t I = 0; I < NumCallSites; I++) {
+    // The offset is encoded as:
+    //   high 16 bits: line offset to the start of the function.
+    //   low 16 bits: discriminator.
+    uint32_t Offset;
+    if (!GcovBuffer.readInt(Offset))
+      return sampleprof_error::truncated;
+    SourceInfo Info(Name, "", "", 0, Offset >> 16, Offset & 0xffff);
+    SourceStack NewStack;
+    NewStack.push_back(Info);
+    NewStack.insert(NewStack.end(), Stack.begin(), Stack.end());
+    if (std::error_code EC = readOneFunctionProfile(NewStack, Update))
+      return EC;
+  }
+
+  return sampleprof_error::success;
+}
+
+std::error_code SampleProfileReaderGCC::readModuleGroup() {
+  // FIXME(dnovillo) - Module support still not implemented.
+  return sampleprof_error::not_implemented;
+}
+
+std::error_code SampleProfileReaderGCC::readWorkingSet() {
+  // FIXME(dnovillo) - Working sets still not implemented.
+  return sampleprof_error::not_implemented;
+}
+
+
+/// \brief Read a GCC AutoFDO profile.
+///
+/// This format is generated by the Linux Perf conversion tool at
+/// https://github.com/google/autofdo.
+std::error_code SampleProfileReaderGCC::read() {
+  // Read the string table.
+  if (std::error_code EC = readNameTable())
+    return EC;
+
+  // Read the source profile.
+  if (std::error_code EC = readFunctionProfiles())
+    return EC;
+
+  // FIXME(dnovillo) - Module groups and working set support are not
+  // yet implemented.
+#if 0
+  // Read the module group file.
+  if (std::error_code EC = readModuleGroup())
+    return EC;
+
+  // Read the working set.
+  if (std::error_code EC = readWorkingSet())
+    return EC;
+#endif
+
+  return sampleprof_error::success;
+}
+
+bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) {
+  StringRef Magic(reinterpret_cast<const char *>(Buffer.getBufferStart()));
+  return Magic == "adcg*704";
+}
+
 /// \brief Prepare a memory buffer for the contents of \p Filename.
 ///
 /// \returns an error code indicating the status of the buffer.
@@ -389,6 +647,8 @@ SampleProfileReader::create(StringRef Filename, LLVMContext &C) {
   std::unique_ptr<SampleProfileReader> Reader;
   if (SampleProfileReaderBinary::hasFormat(*Buffer))
     Reader.reset(new SampleProfileReaderBinary(std::move(Buffer), C));
+  else if (SampleProfileReaderGCC::hasFormat(*Buffer))
+    Reader.reset(new SampleProfileReaderGCC(std::move(Buffer), C));
   else
     Reader.reset(new SampleProfileReaderText(std::move(Buffer), C));
 
diff --git a/test/Transforms/SampleProfile/Inputs/gcc-simple.afdo b/test/Transforms/SampleProfile/Inputs/gcc-simple.afdo
new file mode 100644 (file)
index 0000000..93f22ce
Binary files /dev/null and b/test/Transforms/SampleProfile/Inputs/gcc-simple.afdo differ
diff --git a/test/Transforms/SampleProfile/gcc-simple.ll b/test/Transforms/SampleProfile/gcc-simple.ll
new file mode 100644 (file)
index 0000000..9a22eb2
--- /dev/null
@@ -0,0 +1,218 @@
+; RUN: opt < %s -sample-profile -sample-profile-file=%S/Inputs/gcc-simple.afdo -S | FileCheck %s
+;
+; Original code:
+;
+; #include <stdlib.h>
+;
+; long long int foo(long i) {
+;   if (rand() < 500) return 2; else if (rand() > 5000) return 10; else return 90;
+; }
+;
+; int main() {
+;   long long int sum = 0;
+;   for (int k = 0; k < 3000; k++)
+;     for (int i = 0; i < 200000; i++) sum += foo(i);
+;   return sum > 0 ? 0 : 1;
+; }
+;
+; This test was compiled down to bytecode at -O0 to avoid inlining foo() into
+; main(). The profile was generated using a GCC-generated binary (also compiled
+; at -O0). The conversion from the Linux Perf profile to the GCC autofdo
+; profile used the converter at https://github.com/google/autofdo
+;
+; $ gcc -g -O0 gcc-simple.cc -o gcc-simple
+; $ perf record -b ./gcc-simple
+; $ create_gcov --binary=gcc-simple --gcov=gcc-simple.afdo
+
+define i64 @_Z3fool(i64 %i) #0 {
+; CHECK: !prof ![[EC1:[0-9]+]]
+entry:
+  %retval = alloca i64, align 8
+  %i.addr = alloca i64, align 8
+  store i64 %i, i64* %i.addr, align 8
+  call void @llvm.dbg.declare(metadata i64* %i.addr, metadata !16, metadata !17), !dbg !18
+  %call = call i32 @rand() #3, !dbg !19
+  %cmp = icmp slt i32 %call, 500, !dbg !21
+  br i1 %cmp, label %if.then, label %if.else, !dbg !22
+; CHECK: !prof ![[PROF1:[0-9]+]]
+
+if.then:                                          ; preds = %entry
+  store i64 2, i64* %retval, align 8, !dbg !23
+  br label %return, !dbg !23
+
+if.else:                                          ; preds = %entry
+  %call1 = call i32 @rand() #3, !dbg !25
+  %cmp2 = icmp sgt i32 %call1, 5000, !dbg !28
+  br i1 %cmp2, label %if.then.3, label %if.else.4, !dbg !29
+; CHECK: !prof ![[PROF2:[0-9]+]]
+
+if.then.3:                                        ; preds = %if.else
+  store i64 10, i64* %retval, align 8, !dbg !30
+  br label %return, !dbg !30
+
+if.else.4:                                        ; preds = %if.else
+  store i64 90, i64* %retval, align 8, !dbg !32
+  br label %return, !dbg !32
+
+return:                                           ; preds = %if.else.4, %if.then.3, %if.then
+  %0 = load i64, i64* %retval, align 8, !dbg !34
+  ret i64 %0, !dbg !34
+}
+
+; Function Attrs: nounwind readnone
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
+
+; Function Attrs: nounwind
+declare i32 @rand() #2
+
+; Function Attrs: nounwind uwtable
+define i32 @main() #0 {
+; CHECK: !prof ![[EC2:[0-9]+]]
+entry:
+  %retval = alloca i32, align 4
+  %sum = alloca i64, align 8
+  %k = alloca i32, align 4
+  %i = alloca i32, align 4
+  store i32 0, i32* %retval, align 4
+  call void @llvm.dbg.declare(metadata i64* %sum, metadata !35, metadata !17), !dbg !36
+  store i64 0, i64* %sum, align 8, !dbg !36
+  call void @llvm.dbg.declare(metadata i32* %k, metadata !37, metadata !17), !dbg !39
+  store i32 0, i32* %k, align 4, !dbg !39
+  br label %for.cond, !dbg !40
+
+for.cond:                                         ; preds = %for.inc.4, %entry
+  %0 = load i32, i32* %k, align 4, !dbg !41
+  %cmp = icmp slt i32 %0, 3000, !dbg !45
+  br i1 %cmp, label %for.body, label %for.end.6, !dbg !46
+; CHECK: !prof ![[PROF3:[0-9]+]]
+
+for.body:                                         ; preds = %for.cond
+  call void @llvm.dbg.declare(metadata i32* %i, metadata !47, metadata !17), !dbg !49
+  store i32 0, i32* %i, align 4, !dbg !49
+  br label %for.cond.1, !dbg !50
+
+for.cond.1:                                       ; preds = %for.inc, %for.body
+  %1 = load i32, i32* %i, align 4, !dbg !51
+  %cmp2 = icmp slt i32 %1, 200000, !dbg !55
+  br i1 %cmp2, label %for.body.3, label %for.end, !dbg !56
+; CHECK: !prof ![[PROF4:[0-9]+]]
+
+for.body.3:                                       ; preds = %for.cond.1
+  %2 = load i32, i32* %i, align 4, !dbg !57
+  %conv = sext i32 %2 to i64, !dbg !57
+  %call = call i64 @_Z3fool(i64 %conv), !dbg !59
+  %3 = load i64, i64* %sum, align 8, !dbg !60
+  %add = add nsw i64 %3, %call, !dbg !60
+  store i64 %add, i64* %sum, align 8, !dbg !60
+  br label %for.inc, !dbg !61
+
+for.inc:                                          ; preds = %for.body.3
+  %4 = load i32, i32* %i, align 4, !dbg !62
+  %inc = add nsw i32 %4, 1, !dbg !62
+  store i32 %inc, i32* %i, align 4, !dbg !62
+  br label %for.cond.1, !dbg !64
+
+for.end:                                          ; preds = %for.cond.1
+  br label %for.inc.4, !dbg !65
+
+for.inc.4:                                        ; preds = %for.end
+  %5 = load i32, i32* %k, align 4, !dbg !67
+  %inc5 = add nsw i32 %5, 1, !dbg !67
+  store i32 %inc5, i32* %k, align 4, !dbg !67
+  br label %for.cond, !dbg !68
+
+for.end.6:                                        ; preds = %for.cond
+  %6 = load i64, i64* %sum, align 8, !dbg !69
+  %cmp7 = icmp sgt i64 %6, 0, !dbg !70
+  %cond = select i1 %cmp7, i32 0, i32 1, !dbg !69
+  ret i32 %cond, !dbg !71
+}
+
+; CHECK ![[EC1]] = !{!"function_entry_count", i64 24108}
+; CHECK ![[PROF1]] = !{!"branch_weights", i32 1, i32 30124}
+; CHECK ![[PROF2]] = !{!"branch_weights", i32 30177, i32 29579}
+; CHECK ![[EC2]] = !{!"function_entry_count", i64 0}
+; CHECK ![[PROF3]] = !{!"branch_weights", i32 1, i32 1}
+; CHECK ![[PROF4]] = !{!"branch_weights", i32 1, i32 20238}
+
+attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone }
+attributes #2 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!13, !14}
+!llvm.ident = !{!15}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 3.8.0 (trunk 247554) (llvm/trunk 247557)", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !2, subprograms: !3)
+!1 = !DIFile(filename: "discriminator.cc", directory: "/usr/local/google/home/dnovillo/llvm/test/autofdo")
+!2 = !{}
+!3 = !{!4, !9}
+!4 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fool", scope: !1, file: !1, line: 3, type: !5, isLocal: false, isDefinition: true, scopeLine: 3, flags: DIFlagPrototyped, isOptimized: false, function: i64 (i64)* @_Z3fool, variables: !2)
+!5 = !DISubroutineType(types: !6)
+!6 = !{!7, !8}
+!7 = !DIBasicType(name: "long long int", size: 64, align: 64, encoding: DW_ATE_signed)
+!8 = !DIBasicType(name: "long int", size: 64, align: 64, encoding: DW_ATE_signed)
+!9 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 7, type: !10, isLocal: false, isDefinition: true, scopeLine: 7, flags: DIFlagPrototyped, isOptimized: false, function: i32 ()* @main, variables: !2)
+!10 = !DISubroutineType(types: !11)
+!11 = !{!12}
+!12 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed)
+!13 = !{i32 2, !"Dwarf Version", i32 4}
+!14 = !{i32 2, !"Debug Info Version", i32 3}
+!15 = !{!"clang version 3.8.0 (trunk 247554) (llvm/trunk 247557)"}
+!16 = !DILocalVariable(name: "i", arg: 1, scope: !4, file: !1, line: 3, type: !8)
+!17 = !DIExpression()
+!18 = !DILocation(line: 3, column: 24, scope: !4)
+!19 = !DILocation(line: 4, column: 7, scope: !20)
+!20 = distinct !DILexicalBlock(scope: !4, file: !1, line: 4, column: 7)
+!21 = !DILocation(line: 4, column: 14, scope: !20)
+!22 = !DILocation(line: 4, column: 7, scope: !4)
+!23 = !DILocation(line: 4, column: 21, scope: !24)
+!24 = !DILexicalBlockFile(scope: !20, file: !1, discriminator: 1)
+!25 = !DILocation(line: 4, column: 40, scope: !26)
+!26 = !DILexicalBlockFile(scope: !27, file: !1, discriminator: 2)
+!27 = distinct !DILexicalBlock(scope: !20, file: !1, line: 4, column: 40)
+!28 = !DILocation(line: 4, column: 47, scope: !27)
+!29 = !DILocation(line: 4, column: 40, scope: !20)
+!30 = !DILocation(line: 4, column: 55, scope: !31)
+!31 = !DILexicalBlockFile(scope: !27, file: !1, discriminator: 3)
+!32 = !DILocation(line: 4, column: 71, scope: !33)
+!33 = !DILexicalBlockFile(scope: !27, file: !1, discriminator: 4)
+!34 = !DILocation(line: 5, column: 1, scope: !4)
+!35 = !DILocalVariable(name: "sum", scope: !9, file: !1, line: 8, type: !7)
+!36 = !DILocation(line: 8, column: 17, scope: !9)
+!37 = !DILocalVariable(name: "k", scope: !38, file: !1, line: 9, type: !12)
+!38 = distinct !DILexicalBlock(scope: !9, file: !1, line: 9, column: 3)
+!39 = !DILocation(line: 9, column: 12, scope: !38)
+!40 = !DILocation(line: 9, column: 8, scope: !38)
+!41 = !DILocation(line: 9, column: 19, scope: !42)
+!42 = !DILexicalBlockFile(scope: !43, file: !1, discriminator: 2)
+!43 = !DILexicalBlockFile(scope: !44, file: !1, discriminator: 1)
+!44 = distinct !DILexicalBlock(scope: !38, file: !1, line: 9, column: 3)
+!45 = !DILocation(line: 9, column: 21, scope: !44)
+!46 = !DILocation(line: 9, column: 3, scope: !38)
+!47 = !DILocalVariable(name: "i", scope: !48, file: !1, line: 10, type: !12)
+!48 = distinct !DILexicalBlock(scope: !44, file: !1, line: 10, column: 5)
+!49 = !DILocation(line: 10, column: 14, scope: !48)
+!50 = !DILocation(line: 10, column: 10, scope: !48)
+!51 = !DILocation(line: 10, column: 21, scope: !52)
+!52 = !DILexicalBlockFile(scope: !53, file: !1, discriminator: 5)
+!53 = !DILexicalBlockFile(scope: !54, file: !1, discriminator: 1)
+!54 = distinct !DILexicalBlock(scope: !48, file: !1, line: 10, column: 5)
+!55 = !DILocation(line: 10, column: 23, scope: !54)
+!56 = !DILocation(line: 10, column: 5, scope: !48)
+!57 = !DILocation(line: 10, column: 49, scope: !58)
+!58 = !DILexicalBlockFile(scope: !54, file: !1, discriminator: 2)
+!59 = !DILocation(line: 10, column: 45, scope: !54)
+!60 = !DILocation(line: 10, column: 42, scope: !54)
+!61 = !DILocation(line: 10, column: 38, scope: !54)
+!62 = !DILocation(line: 10, column: 34, scope: !63)
+!63 = !DILexicalBlockFile(scope: !54, file: !1, discriminator: 4)
+!64 = !DILocation(line: 10, column: 5, scope: !54)
+!65 = !DILocation(line: 10, column: 50, scope: !66)
+!66 = !DILexicalBlockFile(scope: !48, file: !1, discriminator: 3)
+!67 = !DILocation(line: 9, column: 30, scope: !44)
+!68 = !DILocation(line: 9, column: 3, scope: !44)
+!69 = !DILocation(line: 11, column: 10, scope: !9)
+!70 = !DILocation(line: 11, column: 14, scope: !9)
+!71 = !DILocation(line: 11, column: 3, scope: !9)