Index: include/clang/Driver/CC1Options.td =================================================================== --- include/clang/Driver/CC1Options.td +++ include/clang/Driver/CC1Options.td @@ -483,6 +483,9 @@ def arcmt_migrate : Flag<["-"], "arcmt-migrate">, HelpText<"Apply modifications and produces temporary files that conform to ARC">; +def opt_report_file : Separate<["-"], "opt-report-file">, + HelpText<"File name to use for optimization listing output">; + def print_stats : Flag<["-"], "print-stats">, HelpText<"Print performance metrics and statistics">; def fdump_record_layouts : Flag<["-"], "fdump-record-layouts">, Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -1078,6 +1078,12 @@ Group; def foperator_arrow_depth_EQ : Joined<["-"], "foperator-arrow-depth=">, Group; +def foptimization_report : Flag<["-"], "foptimization-report">, Group, + HelpText<"Generate an optimization report file">; +def fno_optimization_report : Flag<["-"], "fno-optimization-report">, + Group, Flags<[NoArgumentUnused]>; +def foptimization_report_EQ : Joined<["-"], "foptimization-report=">,Group, + HelpText<"Generate an optimization report file with the specified name">; def ftest_coverage : Flag<["-"], "ftest-coverage">, Group; def fvectorize : Flag<["-"], "fvectorize">, Group, HelpText<"Enable the loop vectorization passes">; Index: include/clang/Frontend/CompilerInstance.h =================================================================== --- include/clang/Frontend/CompilerInstance.h +++ include/clang/Frontend/CompilerInstance.h @@ -298,6 +298,13 @@ return *Invocation->getLangOpts(); } + OptReportInfo &getOptReportInfo() { + return Invocation->getOptReportInfo(); + } + const OptReportInfo &getOptReportInfo() const { + return Invocation->getOptReportInfo(); + } + PreprocessorOptions &getPreprocessorOpts() { return Invocation->getPreprocessorOpts(); } Index: include/clang/Frontend/CompilerInvocation.h =================================================================== --- include/clang/Frontend/CompilerInvocation.h +++ include/clang/Frontend/CompilerInvocation.h @@ -19,6 +19,7 @@ #include "clang/Frontend/FrontendOptions.h" #include "clang/Frontend/LangStandard.h" #include "clang/Frontend/MigratorOptions.h" +#include "clang/Frontend/OptReport.h" #include "clang/Frontend/PreprocessorOutputOptions.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/PreprocessorOptions.h" @@ -117,6 +118,9 @@ /// Options controlling the frontend itself. FrontendOptions FrontendOpts; + /// Optimization-report options and state. + OptReportInfo OptReport; + /// Options controlling preprocessed output. PreprocessorOutputOptions PreprocessorOutputOpts; @@ -196,6 +200,13 @@ return FrontendOpts; } + OptReportInfo &getOptReportInfo() { + return OptReport; + } + const OptReportInfo &getOptReportInfo() const { + return OptReport; + } + PreprocessorOutputOptions &getPreprocessorOutputOpts() { return PreprocessorOutputOpts; } Index: include/clang/Frontend/FrontendActions.h =================================================================== --- include/clang/Frontend/FrontendActions.h +++ include/clang/Frontend/FrontendActions.h @@ -236,7 +236,7 @@ bool hasPCHSupport() const override { return true; } }; - + } // end namespace clang #endif Index: include/clang/Frontend/OptReport.h =================================================================== --- /dev/null +++ include/clang/Frontend/OptReport.h @@ -0,0 +1,67 @@ +//===---- OptReport.h - Clang Optimization-Report Generation ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_FRONTEND_OPTREPORT_H_ +#define LLVM_CLANG_FRONTEND_OPTREPORT_H_ + +#include "clang/Basic/SourceLocation.h" +#include "clang/Frontend/FrontendAction.h" +#include +#include + +namespace clang { +// 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; + + OptReportLocationInfo &operator |= (const OptReportLocationInfo &RHS) { + Inlined |= RHS.Inlined; + Unrolled |= RHS.Unrolled; + Vectorized |= RHS.Vectorized; + + return *this; + } +}; + +// The parameters and accumulated state necessary to generate an optimization +// report. +struct OptReportInfo { + std::string FileName; + std::map LocationInfo; +}; + +class OptReportAction : public WrapperFrontendAction { +public: + OptReportAction(std::unique_ptr WrappedAction) + : WrapperFrontendAction(std::move(WrappedAction)) {} + +protected: + void EndSourceFileAction() override; + void GenerateReportFile(); +}; +} // end namespace clang + +#endif Index: lib/CodeGen/CodeGenAction.cpp =================================================================== --- lib/CodeGen/CodeGenAction.cpp +++ lib/CodeGen/CodeGenAction.cpp @@ -20,6 +20,7 @@ #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/OptReport.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SmallString.h" #include "llvm/Bitcode/ReaderWriter.h" @@ -31,6 +32,7 @@ #include "llvm/IRReader/IRReader.h" #include "llvm/Linker/Linker.h" #include "llvm/Pass.h" +#include "llvm/Support/Format.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/Timer.h" @@ -46,6 +48,7 @@ const CodeGenOptions &CodeGenOpts; const TargetOptions &TargetOpts; const LangOptions &LangOpts; + OptReportInfo &OptReport; raw_pwrite_stream *AsmOutStream; ASTContext *Context; @@ -66,13 +69,14 @@ const HeaderSearchOptions &HeaderSearchOpts, const PreprocessorOptions &PPOpts, const CodeGenOptions &CodeGenOpts, const TargetOptions &TargetOpts, const LangOptions &LangOpts, - bool TimePasses, const std::string &InFile, + OptReportInfo &OptReport, bool TimePasses, const std::string &InFile, const SmallVectorImpl> &LinkModules, raw_pwrite_stream *OS, LLVMContext &C, CoverageSourceInfo *CoverageInfo = nullptr) : Diags(Diags), Action(Action), CodeGenOpts(CodeGenOpts), - TargetOpts(TargetOpts), LangOpts(LangOpts), AsmOutStream(OS), - Context(nullptr), LLVMIRGeneration("LLVM IR Generation Time"), + TargetOpts(TargetOpts), LangOpts(LangOpts), OptReport(OptReport), + AsmOutStream(OS), Context(nullptr), + LLVMIRGeneration("LLVM IR Generation Time"), Gen(CreateLLVMCodeGen(Diags, InFile, HeaderSearchOpts, PPOpts, CodeGenOpts, C, CoverageInfo)) { llvm::TimePassesIsEnabled = TimePasses; @@ -254,6 +258,9 @@ const llvm::DiagnosticInfoOptimizationRemarkAnalysisAliasing &D); void OptimizationFailureHandler( const llvm::DiagnosticInfoOptimizationFailure &D); + + void OptimizationRemarkOptReportHandler( + const llvm::DiagnosticInfoOptimizationBase &D, bool Transformed = false); }; void BackendConsumer::anchor() {} @@ -513,6 +520,9 @@ if (CodeGenOpts.OptimizationRemarkPattern && CodeGenOpts.OptimizationRemarkPattern->match(D.getPassName())) EmitOptimizationMessage(D, diag::remark_fe_backend_optimization_remark); + + // Record optimization decisions for the listing file. + OptimizationRemarkOptReportHandler(D, true); } void BackendConsumer::OptimizationRemarkHandler( @@ -524,6 +534,9 @@ CodeGenOpts.OptimizationRemarkMissedPattern->match(D.getPassName())) EmitOptimizationMessage(D, diag::remark_fe_backend_optimization_remark_missed); + + // Record optimization decisions for the listing file. + OptimizationRemarkOptReportHandler(D); } void BackendConsumer::OptimizationRemarkHandler( @@ -537,6 +550,9 @@ CodeGenOpts.OptimizationRemarkAnalysisPattern->match(D.getPassName()))) EmitOptimizationMessage( D, diag::remark_fe_backend_optimization_remark_analysis); + + // Record optimization decisions for the listing file. + OptimizationRemarkOptReportHandler(D); } void BackendConsumer::OptimizationRemarkHandler( @@ -550,6 +566,9 @@ CodeGenOpts.OptimizationRemarkAnalysisPattern->match(D.getPassName()))) EmitOptimizationMessage( D, diag::remark_fe_backend_optimization_remark_analysis_fpcommute); + + // Record optimization decisions for the listing file. + OptimizationRemarkOptReportHandler(D); } void BackendConsumer::OptimizationRemarkHandler( @@ -563,6 +582,9 @@ CodeGenOpts.OptimizationRemarkAnalysisPattern->match(D.getPassName()))) EmitOptimizationMessage( D, diag::remark_fe_backend_optimization_remark_analysis_aliasing); + + // Record optimization decisions for the listing file. + OptimizationRemarkOptReportHandler(D); } void BackendConsumer::OptimizationFailureHandler( @@ -570,6 +592,48 @@ EmitOptimizationMessage(D, diag::warn_fe_backend_optimization_failure); } +void BackendConsumer::OptimizationRemarkOptReportHandler( + const llvm::DiagnosticInfoOptimizationBase &D, bool Transformed) { + if (OptReport.FileName.empty() || !D.isLocationAvailable()) + return; + + SourceManager &SourceMgr = Context->getSourceManager(); + FileManager &FileMgr = SourceMgr.getFileManager(); + + StringRef Filename; + unsigned Line, Column; + D.getLocation(&Filename, &Line, &Column); + const FileEntry *FE = FileMgr.getFile(Filename); + if (!FE || !Line) + return; + + // If -gcolumn-info was not used, Column will be 0. This upsets the + // source manager, so pass 1 if Column is not set. + SourceLocation DILoc = + SourceMgr.translateFileLineCol(FE, Line, Column ? Column : 1); + if (DILoc.isInvalid()) + return; + + // 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](OptReportLocationItemInfo &LLII) { + LLII.Analyzed = true; + if (Transformed) + LLII.Transformed = true; + }; + + // FIXME: The backend should use proper diagnostic subclasses here, + // and we should match those instead of looking at the pass name. + StringRef PassName = D.getPassName(); + if (PassName == "inline") + UpdateLLII(OptReport.LocationInfo[DILoc].Inlined); + else if (PassName == "loop-unroll") + UpdateLLII(OptReport.LocationInfo[DILoc].Unrolled); + else if (PassName == "loop-vectorize") + UpdateLLII(OptReport.LocationInfo[DILoc].Vectorized); +} + /// \brief This function is invoked when the backend needs /// to report something to the user. void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) { @@ -750,8 +814,8 @@ std::unique_ptr Result(new BackendConsumer( BA, CI.getDiagnostics(), CI.getHeaderSearchOpts(), CI.getPreprocessorOpts(), CI.getCodeGenOpts(), CI.getTargetOpts(), - CI.getLangOpts(), CI.getFrontendOpts().ShowTimers, InFile, LinkModules, - OS, *VMContext, CoverageInfo)); + CI.getLangOpts(), CI.getOptReportInfo(), CI.getFrontendOpts().ShowTimers, + InFile, LinkModules, OS, *VMContext, CoverageInfo)); BEConsumer = Result.get(); return std::move(Result); } Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -3141,23 +3141,29 @@ } } -static const char *SplitDebugName(const ArgList &Args, const InputInfo &Input) { +static const char *getAltExtOutputName(const ArgList &Args, + const InputInfo &Input, + const char *Ext) { Arg *FinalOutput = Args.getLastArg(options::OPT_o); if (FinalOutput && Args.hasArg(options::OPT_c)) { SmallString<128> T(FinalOutput->getValue()); - llvm::sys::path::replace_extension(T, "dwo"); + llvm::sys::path::replace_extension(T, Ext); return Args.MakeArgString(T); } else { // Use the compilation dir. SmallString<128> T( Args.getLastArgValue(options::OPT_fdebug_compilation_dir)); SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput())); - llvm::sys::path::replace_extension(F, "dwo"); + llvm::sys::path::replace_extension(F, Ext); T += F; return Args.MakeArgString(F); } } +static const char *SplitDebugName(const ArgList &Args, const InputInfo &Input) { + return getAltExtOutputName(Args, Input, "dwo"); +} + static void SplitDebugInfo(const ToolChain &TC, Compilation &C, const Tool &T, const JobAction &JA, const ArgList &Args, const InputInfo &Output, const char *OutFile) { @@ -3182,6 +3188,10 @@ C.addCommand(llvm::make_unique(JA, T, Exec, StripArgs, II)); } +static const char *getOptReportName(const ArgList &Args, const InputInfo &Input) { + return getAltExtOutputName(Args, Input, "lst"); +} + /// \brief Vectorize at all optimization levels greater than 1 except for -Oz. /// For -Oz the loop vectorizer is disable, while the slp vectorizer is enabled. static bool shouldEnableVectorizerAtOLevel(const ArgList &Args, bool isSlpVec) { @@ -5650,6 +5660,18 @@ CmdArgs.push_back("-fno-math-builtin"); } + if (Args.hasFlag(options::OPT_foptimization_report, + options::OPT_foptimization_report_EQ, + options::OPT_fno_optimization_report, false)) { + CmdArgs.push_back("-opt-report-file"); + + const Arg *A = Args.getLastArg(options::OPT_foptimization_report_EQ); + if (A) + CmdArgs.push_back(A->getValue()); + else + CmdArgs.push_back(getOptReportName(Args, Input)); + } + // Default to -fno-builtin-str{cat,cpy} on Darwin for ARM. // // FIXME: Now that PR4941 has been fixed this can be enabled. Index: lib/Frontend/CMakeLists.txt =================================================================== --- lib/Frontend/CMakeLists.txt +++ lib/Frontend/CMakeLists.txt @@ -32,6 +32,7 @@ LogDiagnosticPrinter.cpp ModuleDependencyCollector.cpp MultiplexConsumer.cpp + OptReport.cpp PCHContainerOperations.cpp PrintPreprocessedOutput.cpp SerializedDiagnosticPrinter.cpp Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -415,7 +415,8 @@ Opts.setProfileUse(CodeGenOptions::ProfileClangInstr); } -static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK, +static bool ParseCodeGenArgs(CodeGenOptions &Opts, OptReportInfo &OptReport, + ArgList &Args, InputKind IK, DiagnosticsEngine &Diags, const TargetOptions &TargetOpts) { using namespace options; @@ -488,6 +489,8 @@ Opts.DebugTypeExtRefs = Args.hasArg(OPT_dwarf_ext_refs); Opts.DebugExplicitImport = Triple.isPS4CPU(); + OptReport.FileName = Args.getLastArgValue(OPT_opt_report_file); + for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) Opts.DebugPrefixMap.insert(StringRef(Arg).split('=')); @@ -764,6 +767,10 @@ if (!Opts.SampleProfileFile.empty()) NeedLocTracking = true; + // To generate an optimization report, source location information is needed. + if (!OptReport.FileName.empty()) + NeedLocTracking = true; + // If the user requested a flag that requires source locations available in // the backend, make sure that the backend tracks source location information. if (NeedLocTracking && Opts.getDebugInfo() == codegenoptions::NoDebugInfo) @@ -2130,7 +2137,8 @@ // FIXME: We shouldn't have to pass the DashX option around here InputKind DashX = ParseFrontendArgs(Res.getFrontendOpts(), Args, Diags); ParseTargetArgs(Res.getTargetOpts(), Args, Diags); - Success &= ParseCodeGenArgs(Res.getCodeGenOpts(), Args, DashX, Diags, + Success &= ParseCodeGenArgs(Res.getCodeGenOpts(), Res.getOptReportInfo(), + Args, DashX, Diags, Res.getTargetOpts()); ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args); if (DashX == IK_AST || DashX == IK_LLVM_IR) { Index: lib/Frontend/OptReport.cpp =================================================================== --- /dev/null +++ lib/Frontend/OptReport.cpp @@ -0,0 +1,123 @@ +//===------------------------ OptReport.cpp -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/OptReport.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Format.h" + +using namespace clang; + +void OptReportAction::EndSourceFileAction() { + GenerateReportFile(); + WrapperFrontendAction::EndSourceFileAction(); +} + +void OptReportAction::GenerateReportFile() { + CompilerInstance &CI = getCompilerInstance(); + DiagnosticsEngine &Diags = CI.getDiagnostics(); + OptReportInfo &OptReport = CI.getOptReportInfo(); + if (OptReport.FileName.empty()) + return; + + std::error_code EC; + llvm::raw_fd_ostream OS(OptReport.FileName, EC, + llvm::sys::fs::F_Text); + if (EC) { + Diags.Report(diag::err_fe_error_opening) << OptReport.FileName << + EC.message(); + return; + } + + SourceManager &SourceMgr = CI.getSourceManager(); + std::set FileIDs; + for (auto &I : OptReport.LocationInfo) + FileIDs.insert(SourceMgr.getFileID(I.first)); + + for (auto &FID : FileIDs) { + SourceLocation FirstLoc = SourceMgr.getLocForStartOfFile(FID); + OS << "< " << SourceMgr.getFilename(FirstLoc) << "\n"; + + auto I = OptReport.LocationInfo.lower_bound(FirstLoc); + StringRef MB = SourceMgr.getBufferData(FID); + const SrcMgr::ContentCache * + Content = SourceMgr.getSLocEntry(FID).getFile().getContentCache(); + unsigned LNDigits = llvm::utostr(Content->NumLines).size(); + for (unsigned L = 0; L < Content->NumLines - 1; ++L) { + unsigned LStartOff = Content->SourceLineCache[L]; + unsigned LEndOff = (L == Content->NumLines) ? + Content->getSize() : + Content->SourceLineCache[L + 1]; + + std::map ColsInfo; + unsigned InlinedCols = 0, UnrolledCols = 0, VectorizedCols = 0; + + OptReportLocationInfo LLI; + if (I != OptReport.LocationInfo.end()) { + auto DI = SourceMgr.getDecomposedLoc(I->first); + while (I != OptReport.LocationInfo.end() && DI.first == FID && + DI.second < LStartOff) { + ++I; + if (I != OptReport.LocationInfo.end()) + DI = SourceMgr.getDecomposedLoc(I->first); + } + + while (I != OptReport.LocationInfo.end() && DI.first == FID && + DI.second >= LStartOff && DI.second < LEndOff) { + unsigned Col = SourceMgr.getColumnNumber(FID, DI.second); + ColsInfo[Col] = I->second; + InlinedCols += I->second.Inlined.Analyzed; + UnrolledCols += I->second.Unrolled.Analyzed; + VectorizedCols += I->second.Vectorized.Analyzed; + LLI |= I->second; + + ++I; + if (I != OptReport.LocationInfo.end()) + DI = SourceMgr.getDecomposedLoc(I->first); + } + } + + // 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. + + OS << llvm::format_decimal(L + 1, LNDigits) << " "; + OS << (LLI.Inlined.Transformed && InlinedCols < 2 ? "I" : " "); + OS << (LLI.Unrolled.Transformed && UnrolledCols < 2 ? "U" : " "); + OS << (LLI.Vectorized.Transformed && VectorizedCols < 2 ? "V" : " "); + + OS << " | " << MB.slice(LStartOff, LEndOff); + + 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" : " "); + OS << (J.second.Vectorized.Transformed && + VectorizedCols > 1 ? "V" : " "); + + OS << " | " << std::string(J.first - 1, ' ') << "^\n"; + } + } + + if (LEndOff == Content->getSize()) + OS << "\n"; + } + } +} + Index: lib/FrontendTool/ExecuteCompilerInvocation.cpp =================================================================== --- lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -165,6 +165,10 @@ Act = llvm::make_unique(std::move(Act), FEOpts.ASTMergeFiles); + // If an optimization report is requested, generate this after compilation. + if (!CI.getOptReportInfo().FileName.empty()) + Act = llvm::make_unique(std::move(Act)); + return Act; } Index: test/CodeGen/opt-report.c =================================================================== --- /dev/null +++ test/CodeGen/opt-report.c @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -O3 -triple x86_64-unknown-linux-gnu -target-cpu x86-64 %s -o %t -dwarf-column-info -opt-report-file %t.lst -emit-obj +// RUN: cat %t.lst | FileCheck %s +// REQUIRES: x86-registered-target + +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]; + } + +// CHECK: {{[0-9]+}} | #pragma clang loop vectorize(assume_safety) +// CHECK: {{[0-9]+}} V | for (i = 0; i < 1600; i++) { + + for (i = 0; i < 16; i++) { + res[i] = (p[i] == 0) ? res[i] : res[i] + d[i]; + } + + foo(); +// CHECK: {{[0-9]+}} I | foo(); + + foo(); bar(); foo(); +// CHECK: {{[0-9]+}} | foo(); bar(); foo(); +// CHECK-NEXT: I | ^ +// CHECK-NEXT: I | ^ +} + Index: test/Driver/opt-report.c =================================================================== --- /dev/null +++ test/Driver/opt-report.c @@ -0,0 +1,9 @@ +// RUN: %clang -### -S -o FOO -foptimization-report %s 2>&1 | FileCheck %s +// RUN: %clang -### -S -o FOO -foptimization-report=BAR.txt %s 2>&1 | FileCheck %s -check-prefix=CHECK-EQ + +// CHECK: "-cc1" +// CHECK: "-opt-report-file" "opt-report.lst" + +// CHECK-EQ: "-cc1" +// CHECK-EQ: "-opt-report-file" "BAR.txt" +