diff --git a/clang-tools-extra/CMakeLists.txt b/clang-tools-extra/CMakeLists.txt --- a/clang-tools-extra/CMakeLists.txt +++ b/clang-tools-extra/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) add_subdirectory(clang-tidy) +add_subdirectory(clang-misexpect) add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) diff --git a/clang-tools-extra/clang-misexpect/CMakeLists.txt b/clang-tools-extra/clang-misexpect/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-misexpect/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangMisExpect + ClangMisExpect.cpp + + LINK_LIBS + clangBasic + clangCodeGen + clangFrontend + clangFrontendTool + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-tools-extra/clang-misexpect/ClangMisExpect.h b/clang-tools-extra/clang-misexpect/ClangMisExpect.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-misexpect/ClangMisExpect.h @@ -0,0 +1,55 @@ +//===-- ClangMisExpect.h - ClangMisexpect -----------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements a method to create the FrontendActionFactory for the +// clang-misexpect tool. The factory consumes a compilation database and valid +// profiling data to run the compiler over a codebase and issue warnings +// generated from the -Wmisexpect compiler flags. +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace misexpect { + +enum ProfileKind { + Clang, + IR, + CSIR, + Sample, +}; + +class MisExpectFactory : public tooling::FrontendActionFactory { + using Path = std::string; + +public: + MisExpectFactory(Path Profile, ProfileKind ProfileType); + + bool runInvocation(std::shared_ptr Invocation, + FileManager *Files, + std::shared_ptr PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override; + + std::unique_ptr create() override; + +private: + Path ProfilePath; + ProfileKind ProfileType; +}; + +} // namespace misexpect +} // namespace clang diff --git a/clang-tools-extra/clang-misexpect/ClangMisExpect.cpp b/clang-tools-extra/clang-misexpect/ClangMisExpect.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-misexpect/ClangMisExpect.cpp @@ -0,0 +1,88 @@ +//===-- ClangMisExpect.cpp - ClangMisexpect ---------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements a method to create the FrontendActionFactory for the +// clang-misexpect tool. The factory consumes a compilation database and valid +// profiling data to run the compiler over a codebase and issue warnings +// generated from the -Wmisexpect compiler flags. +// +//===----------------------------------------------------------------------===// + +#include "ClangMisExpect.h" +#include "clang/Basic/CodeGenOptions.h" +#include "clang/CodeGen/CodeGenAction.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::tooling; +using namespace misexpect; + +#define DEBUG_TYPE "misexpect" + +MisExpectFactory::MisExpectFactory(Path ProfilePath, ProfileKind ProfileType) + : ProfilePath(ProfilePath), ProfileType(ProfileType) {} + +std::unique_ptr MisExpectFactory::create() { + return std::make_unique(); +} + +bool MisExpectFactory::runInvocation( + std::shared_ptr Invocation, FileManager *Files, + std::shared_ptr PCHContainerOps, + DiagnosticConsumer *DiagConsumer) { + // Only run the compiler through IR generation + Invocation->getFrontendOpts().ProgramAction = frontend::EmitLLVMOnly; + + // clear the existing profile flags and metadata + Invocation->getCodeGenOpts().setProfileUse(CodeGenOptions::ProfileNone); + Invocation->getCodeGenOpts().setProfileInstr(CodeGenOptions::ProfileNone); + Invocation->getCodeGenOpts().ProfileInstrumentUsePath = ""; + Invocation->getCodeGenOpts().SampleProfileFile = ""; + + // duplicate the logic in ExecuteCompilerInvocation to process llvm options + if (!Invocation->getFrontendOpts().LLVMArgs.empty()) { + unsigned NumArgs = Invocation->getFrontendOpts().LLVMArgs.size(); + auto Args = std::make_unique(NumArgs + 2); + Args[0] = "clang (LLVM option parsing)"; + for (unsigned i = 0; i != NumArgs; ++i) + Args[i + 1] = Invocation->getFrontendOpts().LLVMArgs[i].c_str(); + Args[NumArgs + 1] = nullptr; + llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); + } + + // set new profiling options based on profile type + switch (ProfileType) { + case ProfileKind::Clang: + Invocation->getCodeGenOpts().setProfileUse( + CodeGenOptions::ProfileClangInstr); + break; + case ProfileKind::IR: + Invocation->getCodeGenOpts().setProfileUse(CodeGenOptions::ProfileIRInstr); + break; + case ProfileKind::CSIR: + Invocation->getCodeGenOpts().setProfileUse( + CodeGenOptions::ProfileCSIRInstr); + break; + case ProfileKind::Sample: + Invocation->getCodeGenOpts().SampleProfileFile = ProfilePath; + break; + llvm_unreachable("Bad Profile Format given to clang-misexpect use one of " + "(clang, llvm, csllvm, sample)"); + }; + + if (ProfileType != ProfileKind::Sample) + Invocation->getCodeGenOpts().ProfileInstrumentUsePath = ProfilePath; + + return FrontendActionFactory::runInvocation(Invocation, Files, + PCHContainerOps, DiagConsumer); +} + +#undef DEBUG_TYPE diff --git a/clang-tools-extra/clang-misexpect/tool/CMakeLists.txt b/clang-tools-extra/clang-misexpect/tool/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-misexpect/tool/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS + AllTargetsAsmParsers + AllTargetsDescs + AllTargetsInfos + support + ) + +add_clang_tool(clang-misexpect + ClangMisExpectMain.cpp + ) +add_dependencies(clang-misexpect + clang-resource-headers + ) +target_link_libraries(clang-misexpect + PRIVATE + clangBasic + clangMisExpect + clangFrontend + clangCodeGen + clangTooling + clangToolingCore + clangToolingSyntax + ) + diff --git a/clang-tools-extra/clang-misexpect/tool/ClangMisExpectMain.cpp b/clang-tools-extra/clang-misexpect/tool/ClangMisExpectMain.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-misexpect/tool/ClangMisExpectMain.cpp @@ -0,0 +1,124 @@ +//===-- ClangMisExpectMain.cpp - ClangMisexpect -----------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the main function for clang misexpect. It uses a +// libTooling exectutor to check each file in the compiler_commands.json against +// a provided PGO profile. When profile counters disagree with the compiler's +// threshold values for likely and unlike branches clang-misexpect will issue a +// diagnostic message. +// +//===----------------------------------------------------------------------===// + +#include "../ClangMisExpect.h" +#include "clang/Basic/CodeGenOptions.h" +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/AllTUsExecution.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include + +using namespace clang; +using namespace clang::tooling; +using namespace clang::misexpect; +using Path = std::string; + +static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static llvm::cl::OptionCategory + ClangMisExpectCategory("clang-misexpect options"); + +static llvm::cl::opt ProfileDir( + "profile-dir", + llvm::cl::desc( + "Specify a path to the profile data to use during validation"), + llvm::cl::cat(ClangMisExpectCategory)); + +static llvm::cl::opt ProfFormat( + "profile-format", + llvm::cl::desc( + "Specify the format of the profile data used during validation"), + llvm::cl::init(Clang), + llvm::cl::values(clEnumValN(Clang, "clang", "Clang Instrumentation"), + clEnumValN(IR, "llvm", "IR Instrumentation"), + clEnumValN(CSIR, "csllvm", + "Context sensitive IR Instrumentation"), + clEnumValN(Sample, "sample", "Sampling Instrumentation")), + llvm::cl::cat(ClangMisExpectCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + ExecutorName.setInitialValue("all-TUs"); + // ExecutorName.setInitialValue("standalone"); + + auto Executor = + createExecutorFromCommandLineArgs(argc, argv, ClangMisExpectCategory); + + if (!Executor) { + llvm::errs() << "Failed to create executor --- " + << llvm::toString(Executor.takeError()) << "\n"; + return 1; + } + + llvm::errs() << "Executor Created ... \n"; + + CommonOptionsParser OptionsParser(argc, argv, ClangMisExpectCategory, + llvm::cl::ZeroOrMore); + + auto &OS = llvm::errs(); + OS << "Starting execution ... \n"; + auto ArgAdjuster = getStripPluginsAdjuster(); + auto StripProfileWarnings = [](const CommandLineArguments &Args, + StringRef /*unused*/ Unused) { + CommandLineArguments AdjustedArgs; + std::set FilteredArgs = {"-Wprofile-instr-unprofiled", + "-fcoverage-mapping", "-Werror"}; + for (size_t I = 0, E = Args.size(); I != E; I++) { + if (FilteredArgs.find(Args[I]) != FilteredArgs.end()) { + continue; + } + AdjustedArgs.push_back(Args[I]); + } + return AdjustedArgs; + }; + + ArgAdjuster = combineAdjusters(StripProfileWarnings, ArgAdjuster); + + ArgAdjuster = + combineAdjusters(getInsertArgumentAdjuster( + { + "-Wmisexpect", "-Wno-profile-instr-unprofiled", + "-Wno-profile-instr-out-of-date" + }, + tooling::ArgumentInsertPosition::END), + ArgAdjuster); + + auto Err = Executor->get()->execute( + std::make_unique(ProfileDir, ProfFormat), + ArgAdjuster); + if (Err) { + OS.changeColor(raw_ostream::Colors::RED, true); + OS << "Error: "; + OS.resetColor(); + OS << llvm::toString(std::move(Err)) << "\n"; + } + + llvm::errs() << "Execution complete\n"; + + // Emit collected data. + Executor->get()->getToolResults()->forEachResult( + [](llvm::StringRef Key, llvm::StringRef Value) { + llvm::errs() << "----" << Key.str() << "\n" << Value.str() << "\n"; + }); + return 0; +} diff --git a/clang/cmake/caches/Fuchsia-stage2.cmake b/clang/cmake/caches/Fuchsia-stage2.cmake --- a/clang/cmake/caches/Fuchsia-stage2.cmake +++ b/clang/cmake/caches/Fuchsia-stage2.cmake @@ -207,6 +207,7 @@ LTO clang-apply-replacements clang-doc + clang-misexpect clang-format clang-resource-headers clang-include-fixer diff --git a/llvm/lib/Transforms/Utils/MisExpect.cpp b/llvm/lib/Transforms/Utils/MisExpect.cpp --- a/llvm/lib/Transforms/Utils/MisExpect.cpp +++ b/llvm/lib/Transforms/Utils/MisExpect.cpp @@ -43,7 +43,8 @@ static cl::opt PGOWarnMisExpect( "pgo-warn-misexpect", cl::init(false), cl::Hidden, cl::desc("Use this option to turn on/off " - "warnings about incorrect usage of llvm.expect intrinsics.")); + "warnings about incorrect usage of llvm.expect intrinsics."), + cl::ZeroOrMore); } // namespace llvm