From e0e1eceb55e3b9ce1986885d7d98a32d47822201 Mon Sep 17 00:00:00 2001 From: Hal Finkel Date: Wed, 5 Oct 2016 22:10:35 +0000 Subject: [PATCH] Add an llvm-opt-report tool to generate basic source-annotated optimization summaries LLVM now has the ability to record information from optimization remarks in a machine-consumable YAML file for later analysis. This can be enabled in opt (see r282539), and D25225 adds a Clang flag to do the same. This patch adds llvm-opt-report, a tool to generate basic optimization "listing" files (annotated sources with information about what optimizations were performed) from one of these YAML inputs. D19678 proposed to add this capability directly to Clang, but this more-general YAML-based infrastructure was the direction we decided upon in that review thread. For this optimization report, I focused on making the output as succinct as possible while providing information on inlining and loop transformations. The goal here is that the source code should still be easily readable in the report. My primary inspiration here is the reports generated by Cray's tools (http://docs.cray.com/books/S-2496-4101/html-S-2496-4101/z1112823641oswald.html). These reports are highly regarded within the HPC community. Intel's compiler, for example, also has an optimization-report capability (https://software.intel.com/sites/default/files/managed/55/b1/new-compiler-optimization-reports.pdf). $ cat /tmp/v.c void bar(); void foo() { bar(); } void Test(int *res, int *c, int *d, int *p, int n) { int i; #pragma clang loop vectorize(assume_safety) for (i = 0; i < 1600; i++) { res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; } for (i = 0; i < 16; i++) { res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; } foo(); foo(); bar(); foo(); } D25225 adds -fsave-optimization-record (and -fsave-optimization-record=filename), and this would be used as follows: $ clang -O3 -o /tmp/v.o -c /tmp/v.c -fsave-optimization-record $ llvm-opt-report /tmp/v.yaml > /tmp/v.lst $ cat /tmp/v.lst < /tmp/v.c 2 | void bar(); 3 | void foo() { bar(); } 4 | 5 | void Test(int *res, int *c, int *d, int *p, int n) { 6 | int i; 7 | 8 | #pragma clang loop vectorize(assume_safety) 9 V4,2 | for (i = 0; i < 1600; i++) { 10 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; 11 | } 12 | 13 U16 | for (i = 0; i < 16; i++) { 14 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; 15 | } 16 | 17 I | foo(); 18 | 19 | foo(); bar(); foo(); I | ^ I | ^ 20 | } Each source line gets a prefix giving the line number, and a few columns for important optimizations: inlining, loop unrolling and loop vectorization. An 'I' is printed next to a line where a function was inlined, a 'U' next to an unrolled loop, and 'V' next to a vectorized loop. These are printed on the relevant code line when that seems unambiguous, or on subsequent lines when multiple potential options exist (messages, both positive and negative, from the same optimization with different column numbers are taken to indicate potential ambiguity). When on subsequent lines, a '^' is output in the relevant column. Annotated source for all relevant input files are put into the listing file (each starting with '<' and then the file name). You can disable having the unrolling/vectorization factors appear by using the -s flag. Differential Revision: https://reviews.llvm.org/D25262 llvm-svn: 283398 --- test/CMakeLists.txt | 1 + test/tools/llvm-opt-report/Inputs/or.c | 22 ++ test/tools/llvm-opt-report/Inputs/or.h | 17 + test/tools/llvm-opt-report/Inputs/or.yaml | 227 ++++++++++++ test/tools/llvm-opt-report/basic.test | 97 ++++++ tools/llvm-opt-report/CMakeLists.txt | 6 + tools/llvm-opt-report/OptReport.cpp | 401 ++++++++++++++++++++++ 7 files changed, 771 insertions(+) create mode 100644 test/tools/llvm-opt-report/Inputs/or.c create mode 100644 test/tools/llvm-opt-report/Inputs/or.h create mode 100644 test/tools/llvm-opt-report/Inputs/or.yaml create mode 100644 test/tools/llvm-opt-report/basic.test create mode 100644 tools/llvm-opt-report/CMakeLists.txt create mode 100644 tools/llvm-opt-report/OptReport.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e9b309585f..cf26abfe1c3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -49,6 +49,7 @@ set(LLVM_TEST_DEPENDS llvm-mcmarkup llvm-nm llvm-objdump + llvm-opt-report llvm-pdbdump llvm-profdata llvm-ranlib diff --git a/test/tools/llvm-opt-report/Inputs/or.c b/test/tools/llvm-opt-report/Inputs/or.c new file mode 100644 index 00000000000..0a3d859aca0 --- /dev/null +++ b/test/tools/llvm-opt-report/Inputs/or.c @@ -0,0 +1,22 @@ +void bar(); +void foo() { bar(); } + +#include "or.h" + +void Test(int *res, int *c, int *d, int *p, int n) { + int i; + +#pragma clang loop vectorize(assume_safety) + for (i = 0; i < 1600; i++) { + res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; + } + + for (i = 0; i < 16; i++) { + res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; + } + + foo(); + + foo(); bar(); foo(); +} + diff --git a/test/tools/llvm-opt-report/Inputs/or.h b/test/tools/llvm-opt-report/Inputs/or.h new file mode 100644 index 00000000000..2ae0b1e94cb --- /dev/null +++ b/test/tools/llvm-opt-report/Inputs/or.h @@ -0,0 +1,17 @@ +void TestH(int *res, int *c, int *d, int *p, int n) { + int i; + +#pragma clang loop vectorize(assume_safety) + for (i = 0; i < 1600; i++) { + res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; + } + + for (i = 0; i < 16; i++) { + res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; + } + + foo(); + + foo(); bar(); foo(); +} + diff --git a/test/tools/llvm-opt-report/Inputs/or.yaml b/test/tools/llvm-opt-report/Inputs/or.yaml new file mode 100644 index 00000000000..4bbd972d711 --- /dev/null +++ b/test/tools/llvm-opt-report/Inputs/or.yaml @@ -0,0 +1,227 @@ +--- !Missed +Pass: inline +Name: NoDefinition +DebugLoc: { File: Inputs/or.c, Line: 2, Column: 14 } +Function: foo +Args: + - Callee: bar + - String: ' will not be inlined into ' + - Caller: foo + - String: ' because its definition is unavailable' +... +--- !Missed +Pass: inline +Name: NoDefinition +DebugLoc: { File: Inputs/or.h, Line: 15, Column: 10 } +Function: TestH +Args: + - Callee: bar + - String: ' will not be inlined into ' + - Caller: TestH + - String: ' because its definition is unavailable' +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.h, Line: 13, Column: 3 } +Function: TestH +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: TestH + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.h, Line: 13, Column: 3 } +Function: TestH +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: TestH +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.h, Line: 15, Column: 3 } +Function: TestH +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: TestH + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.h, Line: 15, Column: 3 } +Function: TestH +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: TestH +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.h, Line: 15, Column: 17 } +Function: TestH +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: TestH + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.h, Line: 15, Column: 17 } +Function: TestH +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: TestH +... +--- !Passed +Pass: loop-unroll +Name: FullyUnrolled +DebugLoc: { File: Inputs/or.h, Line: 9, Column: 3 } +Function: TestH +Args: + - String: 'completely unrolled loop with ' + - UnrollCount: '16' + - String: ' iterations' +... +--- !Missed +Pass: inline +Name: NoDefinition +DebugLoc: { File: Inputs/or.c, Line: 20, Column: 10 } +Function: Test +Args: + - Callee: bar + - String: ' will not be inlined into ' + - Caller: Test + - String: ' because its definition is unavailable' +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.c, Line: 18, Column: 3 } +Function: Test +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: Test + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.c, Line: 18, Column: 3 } +Function: Test +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: Test +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.c, Line: 20, Column: 3 } +Function: Test +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: Test + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.c, Line: 20, Column: 3 } +Function: Test +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: Test +... +--- !Analysis +Pass: inline +Name: CanBeInlined +DebugLoc: { File: Inputs/or.c, Line: 20, Column: 17 } +Function: Test +Args: + - Callee: foo + - String: ' can be inlined into ' + - Caller: Test + - String: ' with cost=' + - Cost: '30' + - String: ' (threshold=' + - Threshold: '412' + - String: ')' +... +--- !Passed +Pass: inline +Name: Inlined +DebugLoc: { File: Inputs/or.c, Line: 20, Column: 17 } +Function: Test +Args: + - Callee: foo + - String: ' inlined into ' + - Caller: Test +... +--- !Passed +Pass: loop-unroll +Name: FullyUnrolled +DebugLoc: { File: Inputs/or.c, Line: 14, Column: 3 } +Function: Test +Args: + - String: 'completely unrolled loop with ' + - UnrollCount: '16' + - String: ' iterations' +... +--- !Passed +Pass: loop-vectorize +Name: Vectorized +DebugLoc: { File: Inputs/or.h, Line: 5, Column: 3 } +Function: TestH +Args: + - String: 'vectorized loop (vectorization width: ' + - VectorizationFactor: '4' + - String: ', interleaved count: ' + - InterleaveCount: '2' + - String: ')' +... +--- !Passed +Pass: loop-vectorize +Name: Vectorized +DebugLoc: { File: Inputs/or.c, Line: 10, Column: 3 } +Function: Test +Args: + - String: 'vectorized loop (vectorization width: ' + - VectorizationFactor: '4' + - String: ', interleaved count: ' + - InterleaveCount: '2' + - String: ')' +... diff --git a/test/tools/llvm-opt-report/basic.test b/test/tools/llvm-opt-report/basic.test new file mode 100644 index 00000000000..de02064aecd --- /dev/null +++ b/test/tools/llvm-opt-report/basic.test @@ -0,0 +1,97 @@ +RUN: llvm-opt-report -r %p %p/Inputs/or.yaml | FileCheck -strict-whitespace %s +RUN: llvm-opt-report -s -r %p %p/Inputs/or.yaml | FileCheck -strict-whitespace -check-prefix=CHECK-SUCCINCT %s + +; CHECK: < {{.*}}/Inputs/or.c +; CHECK-NEXT: 2 | void bar(); +; CHECK-NEXT: 3 | void foo() { bar(); } +; CHECK-NEXT: 4 | +; CHECK-NEXT: 5 | #include "or.h" +; CHECK-NEXT: 6 | +; CHECK-NEXT: 7 | void Test(int *res, int *c, int *d, int *p, int n) { +; CHECK-NEXT: 8 | int i; +; CHECK-NEXT: 9 | +; CHECK-NEXT: 10 | #pragma clang loop vectorize(assume_safety) +; CHECK-NEXT: 11 V4,2 | for (i = 0; i < 1600; i++) { +; CHECK-NEXT: 12 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +; CHECK-NEXT: 13 | } +; CHECK-NEXT: 14 | +; CHECK-NEXT: 15 U16 | for (i = 0; i < 16; i++) { +; CHECK-NEXT: 16 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +; CHECK-NEXT: 17 | } +; CHECK-NEXT: 18 | +; CHECK-NEXT: 19 I | foo(); +; CHECK-NEXT: 20 | +; CHECK-NEXT: 21 | foo(); bar(); foo(); +; CHECK-NEXT: I | ^ +; CHECK-NEXT: I | ^ +; CHECK-NEXT: 22 | } +; CHECK-NEXT: 23 | + +; CHECK: < {{.*}}/Inputs/or.h +; CHECK-NEXT: 2 | void TestH(int *res, int *c, int *d, int *p, int n) { +; CHECK-NEXT: 3 | int i; +; CHECK-NEXT: 4 | +; CHECK-NEXT: 5 | #pragma clang loop vectorize(assume_safety) +; CHECK-NEXT: 6 V4,2 | for (i = 0; i < 1600; i++) { +; CHECK-NEXT: 7 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +; CHECK-NEXT: 8 | } +; CHECK-NEXT: 9 | +; CHECK-NEXT: 10 U16 | for (i = 0; i < 16; i++) { +; CHECK-NEXT: 11 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +; CHECK-NEXT: 12 | } +; CHECK-NEXT: 13 | +; CHECK-NEXT: 14 I | foo(); +; CHECK-NEXT: 15 | +; CHECK-NEXT: 16 | foo(); bar(); foo(); +; CHECK-NEXT: I | ^ +; CHECK-NEXT: I | ^ +; CHECK-NEXT: 17 | } +; CHECK-NEXT: 18 | + +; CHECK-SUCCINCT: < {{.*}}/Inputs/or.c +CHECK-SUCCINCT-NEXT: 2 | void bar(); +CHECK-SUCCINCT-NEXT: 3 | void foo() { bar(); } +CHECK-SUCCINCT-NEXT: 4 | +CHECK-SUCCINCT-NEXT: 5 | #include "or.h" +CHECK-SUCCINCT-NEXT: 6 | +CHECK-SUCCINCT-NEXT: 7 | void Test(int *res, int *c, int *d, int *p, int n) { +CHECK-SUCCINCT-NEXT: 8 | int i; +CHECK-SUCCINCT-NEXT: 9 | +CHECK-SUCCINCT-NEXT: 10 | #pragma clang loop vectorize(assume_safety) +CHECK-SUCCINCT-NEXT: 11 V | for (i = 0; i < 1600; i++) { +CHECK-SUCCINCT-NEXT: 12 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +CHECK-SUCCINCT-NEXT: 13 | } +CHECK-SUCCINCT-NEXT: 14 | +CHECK-SUCCINCT-NEXT: 15 U | for (i = 0; i < 16; i++) { +CHECK-SUCCINCT-NEXT: 16 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +CHECK-SUCCINCT-NEXT: 17 | } +CHECK-SUCCINCT-NEXT: 18 | +CHECK-SUCCINCT-NEXT: 19 I | foo(); +CHECK-SUCCINCT-NEXT: 20 | +CHECK-SUCCINCT-NEXT: 21 | foo(); bar(); foo(); +CHECK-SUCCINCT-NEXT: I | ^ +CHECK-SUCCINCT-NEXT: I | ^ +CHECK-SUCCINCT-NEXT: 22 | } +CHECK-SUCCINCT-NEXT: 23 | + +CHECK-SUCCINCT: < {{.*}}/Inputs/or.h +CHECK-SUCCINCT-NEXT: 2 | void TestH(int *res, int *c, int *d, int *p, int n) { +CHECK-SUCCINCT-NEXT: 3 | int i; +CHECK-SUCCINCT-NEXT: 4 | +CHECK-SUCCINCT-NEXT: 5 | #pragma clang loop vectorize(assume_safety) +CHECK-SUCCINCT-NEXT: 6 V | for (i = 0; i < 1600; i++) { +CHECK-SUCCINCT-NEXT: 7 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +CHECK-SUCCINCT-NEXT: 8 | } +CHECK-SUCCINCT-NEXT: 9 | +CHECK-SUCCINCT-NEXT: 10 U | for (i = 0; i < 16; i++) { +CHECK-SUCCINCT-NEXT: 11 | res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; +CHECK-SUCCINCT-NEXT: 12 | } +CHECK-SUCCINCT-NEXT: 13 | +CHECK-SUCCINCT-NEXT: 14 I | foo(); +CHECK-SUCCINCT-NEXT: 15 | +CHECK-SUCCINCT-NEXT: 16 | foo(); bar(); foo(); +CHECK-SUCCINCT-NEXT: I | ^ +CHECK-SUCCINCT-NEXT: I | ^ +CHECK-SUCCINCT-NEXT: 17 | } +CHECK-SUCCINCT-NEXT: 18 | + diff --git a/tools/llvm-opt-report/CMakeLists.txt b/tools/llvm-opt-report/CMakeLists.txt new file mode 100644 index 00000000000..717043c7550 --- /dev/null +++ b/tools/llvm-opt-report/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_LINK_COMPONENTS Core Object Support) + +add_llvm_tool(llvm-opt-report + OptReport.cpp + ) + diff --git a/tools/llvm-opt-report/OptReport.cpp b/tools/llvm-opt-report/OptReport.cpp new file mode 100644 index 00000000000..0c3acb5e5f4 --- /dev/null +++ b/tools/llvm-opt-report/OptReport.cpp @@ -0,0 +1,401 @@ +//===------------------ llvm-opt-report/OptReport.cpp ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file implements a tool that can parse the YAML optimization +/// records and generate an optimization summary annotated source listing +/// report. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace llvm; +using namespace llvm::yaml; + +static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); + +// Mark all our options with this category, everything else (except for -version +// and -help) will be hidden. +static cl::OptionCategory + OptReportCategory("llvm-opt-report options"); + +static cl::opt + InputFileName(cl::Positional, cl::desc(""), cl::init("-"), + cl::cat(OptReportCategory)); + +static cl::opt + OutputFileName("o", cl::desc("Output file"), cl::init("-"), + cl::cat(OptReportCategory)); + +static cl::opt + InputRelDir("r", cl::desc("Root for relative input paths"), cl::init(""), + cl::cat(OptReportCategory)); + +static cl::opt + Succinct("s", cl::desc("Don't include vectorization factors, etc."), + cl::init(false), cl::cat(OptReportCategory)); + +namespace { +// For each location in the source file, the common per-transformation state +// collected. +struct OptReportLocationItemInfo { + bool Analyzed = false; + bool Transformed = false; + + OptReportLocationItemInfo &operator |= ( + const OptReportLocationItemInfo &RHS) { + Analyzed |= RHS.Analyzed; + Transformed |= RHS.Transformed; + + return *this; + } +}; + +// The per-location information collected for producing an optimization report. +struct OptReportLocationInfo { + OptReportLocationItemInfo Inlined; + OptReportLocationItemInfo Unrolled; + OptReportLocationItemInfo Vectorized; + + int VectorizationFactor = 1; + int InterleaveCount = 1; + int UnrollCount = 1; + + OptReportLocationInfo &operator |= (const OptReportLocationInfo &RHS) { + Inlined |= RHS.Inlined; + Unrolled |= RHS.Unrolled; + Vectorized |= RHS.Vectorized; + + VectorizationFactor = + std::max(VectorizationFactor, RHS.VectorizationFactor); + InterleaveCount = std::max(InterleaveCount, RHS.InterleaveCount); + UnrollCount = std::max(UnrollCount, RHS.UnrollCount); + + return *this; + } +}; + +typedef std::map>> LocationInfoTy; +} // anonymous namespace + +static void collectLocationInfo(yaml::Stream &Stream, + LocationInfoTy &LocationInfo) { + SmallVector Tmp; + + // Note: We're using the YAML parser here directly, instead of using the + // YAMLTraits implementation, because the YAMLTraits implementation does not + // support a way to handle only a subset of the input keys (it will error out + // if there is an input key that you don't map to your class), and + // furthermore, it does not provide a way to handle the Args sequence of + // key/value pairs, where the order must be captured and the 'String' key + // might be repeated. + for (auto &Doc : Stream) { + auto *Root = dyn_cast(Doc.getRoot()); + if (!Root) + continue; + + bool Transformed = Root->getRawTag() == "!Passed"; + std::string Pass, File; + int Line = 0, Column = 1; + + int VectorizationFactor = 1; + int InterleaveCount = 1; + int UnrollCount = 1; + + for (auto &RootChild : *Root) { + auto *Key = dyn_cast(RootChild.getKey()); + if (!Key) + continue; + StringRef KeyName = Key->getValue(Tmp); + if (KeyName == "Pass") { + auto *Value = dyn_cast(RootChild.getValue()); + if (!Value) + continue; + Pass = Value->getValue(Tmp); + } else if (KeyName == "DebugLoc") { + auto *DebugLoc = dyn_cast(RootChild.getValue()); + if (!DebugLoc) + continue; + + for (auto &DLChild : *DebugLoc) { + auto *DLKey = dyn_cast(DLChild.getKey()); + if (!DLKey) + continue; + StringRef DLKeyName = DLKey->getValue(Tmp); + if (DLKeyName == "File") { + auto *Value = dyn_cast(DLChild.getValue()); + if (!Value) + continue; + File = Value->getValue(Tmp); + } else if (DLKeyName == "Line") { + auto *Value = dyn_cast(DLChild.getValue()); + if (!Value) + continue; + Value->getValue(Tmp).getAsInteger(10, Line); + } else if (DLKeyName == "Column") { + auto *Value = dyn_cast(DLChild.getValue()); + if (!Value) + continue; + Value->getValue(Tmp).getAsInteger(10, Column); + } + } + } else if (KeyName == "Args") { + auto *Args = dyn_cast(RootChild.getValue()); + if (!Args) + continue; + for (auto &ArgChild : *Args) { + auto *ArgMap = dyn_cast(&ArgChild); + if (!ArgMap) + continue; + for (auto &ArgKV : *ArgMap) { + auto *ArgKey = dyn_cast(ArgKV.getKey()); + if (!ArgKey) + continue; + StringRef ArgKeyName = ArgKey->getValue(Tmp); + if (ArgKeyName == "VectorizationFactor") { + auto *Value = dyn_cast(ArgKV.getValue()); + if (!Value) + continue; + Value->getValue(Tmp).getAsInteger(10, VectorizationFactor); + } else if (ArgKeyName == "InterleaveCount") { + auto *Value = dyn_cast(ArgKV.getValue()); + if (!Value) + continue; + Value->getValue(Tmp).getAsInteger(10, InterleaveCount); + } else if (ArgKeyName == "UnrollCount") { + auto *Value = dyn_cast(ArgKV.getValue()); + if (!Value) + continue; + Value->getValue(Tmp).getAsInteger(10, UnrollCount); + } + } + } + } + } + + if (Line < 1 || File.empty()) + continue; + + // We track information on both actual and potential transformations. This + // way, if there are multiple possible things on a line that are, or could + // have been transformed, we can indicate that explicitly in the output. + auto UpdateLLII = [Transformed, VectorizationFactor, + InterleaveCount, + UnrollCount](OptReportLocationInfo &LI, + OptReportLocationItemInfo &LLII) { + LLII.Analyzed = true; + if (Transformed) { + LLII.Transformed = true; + + LI.VectorizationFactor = VectorizationFactor; + LI.InterleaveCount = InterleaveCount; + LI.UnrollCount = UnrollCount; + } + }; + + if (Pass == "inline") { + auto &LI = LocationInfo[File][Line][Column]; + UpdateLLII(LI, LI.Inlined); + } else if (Pass == "loop-unroll") { + auto &LI = LocationInfo[File][Line][Column]; + UpdateLLII(LI, LI.Unrolled); + } else if (Pass == "loop-vectorize") { + auto &LI = LocationInfo[File][Line][Column]; + UpdateLLII(LI, LI.Vectorized); + } + } +} + +static bool readLocationInfo(LocationInfoTy &LocationInfo) { + ErrorOr> Buf = + MemoryBuffer::getFileOrSTDIN(InputFileName); + if (std::error_code EC = Buf.getError()) { + errs() << "error: Can't open file " << InputFileName << ": " << + EC.message() << "\n"; + return false; + } + + SourceMgr SM; + yaml::Stream Stream(Buf.get()->getBuffer(), SM); + collectLocationInfo(Stream, LocationInfo); + + return true; +} + +static bool writeReport(LocationInfoTy &LocationInfo) { + std::error_code EC; + llvm::raw_fd_ostream OS(OutputFileName, EC, + llvm::sys::fs::F_Text); + if (EC) { + errs() << "error: Can't open file " << OutputFileName << ": " << + EC.message() << "\n"; + return false; + } + + bool FirstFile = true; + for (auto &FI : LocationInfo) { + SmallString<128> FileName(FI.first); + if (!InputRelDir.empty()) { + if (std::error_code EC = sys::fs::make_absolute(InputRelDir, FileName)) { + errs() << "error: Can't resolve file path to " << FileName << ": " << + EC.message() << "\n"; + return false; + } + } + + const auto &FileInfo = FI.second; + + ErrorOr> Buf = + MemoryBuffer::getFile(FileName); + if (std::error_code EC = Buf.getError()) { + errs() << "error: Can't open file " << FileName << ": " << + EC.message() << "\n"; + return false; + } + + if (FirstFile) + FirstFile = false; + else + OS << "\n"; + + OS << "< " << FileName << "\n"; + + // Figure out how many characters we need for the vectorization factors + // and similar. + OptReportLocationInfo MaxLI; + for (auto &FI : FileInfo) + for (auto &LI : FI.second) + MaxLI |= LI.second; + + unsigned VFDigits = llvm::utostr(MaxLI.VectorizationFactor).size(); + unsigned ICDigits = llvm::utostr(MaxLI.InterleaveCount).size(); + unsigned UCDigits = llvm::utostr(MaxLI.UnrollCount).size(); + + // Figure out how many characters we need for the line numbers. + int64_t NumLines = 0; + for (line_iterator LI(*Buf.get(), false); LI != line_iterator(); ++LI) + ++NumLines; + + unsigned LNDigits = llvm::utostr(NumLines).size(); + + for (line_iterator LI(*Buf.get(), false); LI != line_iterator(); ++LI) { + int64_t L = LI.line_number(); + OptReportLocationInfo LLI; + + std::map ColsInfo; + unsigned InlinedCols = 0, UnrolledCols = 0, VectorizedCols = 0; + + auto LII = FileInfo.find(L); + if (LII != FileInfo.end()) { + const auto &LineInfo = LII->second; + + for (auto &CI : LineInfo) { + int Col = CI.first; + ColsInfo[Col] = CI.second; + InlinedCols += CI.second.Inlined.Analyzed; + UnrolledCols += CI.second.Unrolled.Analyzed; + VectorizedCols += CI.second.Vectorized.Analyzed; + LLI |= CI.second; + } + } + + // We try to keep the output as concise as possible. If only one thing on + // a given line could have been inlined, vectorized, etc. then we can put + // the marker on the source line itself. If there are multiple options + // then we want to distinguish them by placing the marker for each + // transformation on a separate line following the source line. When we + // do this, we use a '^' character to point to the appropriate column in + // the source line. + + std::string USpaces(Succinct ? 0 : UCDigits, ' '); + std::string VSpaces(Succinct ? 0 : VFDigits + ICDigits + 1, ' '); + + auto UStr = [UCDigits](OptReportLocationInfo &LLI) { + std::string R; + raw_string_ostream RS(R); + if (!Succinct) + RS << llvm::format_decimal(LLI.UnrollCount, UCDigits); + return RS.str(); + }; + + auto VStr = [VFDigits, + ICDigits](OptReportLocationInfo &LLI) -> std::string { + std::string R; + raw_string_ostream RS(R); + if (!Succinct) + RS << llvm::format_decimal(LLI.VectorizationFactor, VFDigits) << + "," << llvm::format_decimal(LLI.InterleaveCount, ICDigits); + return RS.str(); + }; + + OS << llvm::format_decimal(L + 1, LNDigits) << " "; + OS << (LLI.Inlined.Transformed && InlinedCols < 2 ? "I" : " "); + OS << (LLI.Unrolled.Transformed && UnrolledCols < 2 ? + "U" + UStr(LLI) : " " + USpaces); + OS << (LLI.Vectorized.Transformed && VectorizedCols < 2 ? + "V" + VStr(LLI) : " " + VSpaces); + + OS << " | " << *LI << "\n"; + + for (auto &J : ColsInfo) { + if ((J.second.Inlined.Transformed && InlinedCols > 1) || + (J.second.Unrolled.Transformed && UnrolledCols > 1) || + (J.second.Vectorized.Transformed && VectorizedCols > 1)) { + OS << std::string(LNDigits + 1, ' '); + OS << (J.second.Inlined.Transformed && + InlinedCols > 1 ? "I" : " "); + OS << (J.second.Unrolled.Transformed && + UnrolledCols > 1 ? "U" + UStr(J.second) : " " + USpaces); + OS << (J.second.Vectorized.Transformed && + VectorizedCols > 1 ? "V" + VStr(J.second) : " " + VSpaces); + + OS << " | " << std::string(J.first - 1, ' ') << "^\n"; + } + } + } + } + + return true; +} + +int main(int argc, const char **argv) { + sys::PrintStackTraceOnErrorSignal(argv[0]); + + cl::HideUnrelatedOptions(OptReportCategory); + cl::ParseCommandLineOptions( + argc, argv, + "A tool to generate an optimization report from YAML optimization" + " record files.\n"); + + if (Help) + cl::PrintHelpMessage(); + + LocationInfoTy LocationInfo; + if (!readLocationInfo(LocationInfo)) + return 1; + if (!writeReport(LocationInfo)) + return 1; + + return 0; +} +