Skip to content

Commit f36d837

Browse files
committed
[clang-scan-deps] initial outline of the tool that runs preprocessor to find
dependencies over a JSON compilation database This commit introduces an outline for the clang-scan-deps tool that will be used to implement fast dependency discovery phase using implicit modules for explicit module builds. The initial version of the tool works by computing non-modular header dependencies for files in the compilation database without any optimizations (i.e. without source minimization from r362459). The tool spawns a number of worker threads to run the clang compiler workers in parallel. The immediate goal for clang-scan-deps is to create a ClangScanDeps library which will be used to build up this tool to use the source minimization and caching multi-threaded filesystem to implement the optimized non-incremental dependency scanning phase for a non-modular build. This will allow us to do benchmarks and comparisons for performance that the minimization and caching give us Differential Revision: https://reviews.llvm.org/D60233 llvm-svn: 363204
1 parent a1421e8 commit f36d837

8 files changed

Lines changed: 289 additions & 0 deletions

File tree

‎clang/test/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ list(APPEND CLANG_TEST_DEPS
5757
clang-rename
5858
clang-refactor
5959
clang-diff
60+
clang-scan-deps
6061
diagtool
6162
hmaptool
6263
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ifdef INCLUDE_HEADER2
2+
#include "header2.h"
3+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// header 2.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"directory": "DIR",
4+
"command": "clang -c DIR/regular_cdb.cpp -IInputs -MD -MF DIR/regular_cdb.d",
5+
"file": "DIR/regular_cdb.cpp"
6+
},
7+
{
8+
"directory": "DIR",
9+
"command": "clang -c DIR/regular_cdb.cpp -IInputs -D INCLUDE_HEADER2 -MD -MF DIR/regular_cdb2.d",
10+
"file": "DIR/regular_cdb.cpp"
11+
}
12+
]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: rm -rf %t.dir
2+
// RUN: rm -rf %t.cdb
3+
// RUN: mkdir -p %t.dir
4+
// RUN: cp %s %t.dir/regular_cdb.cpp
5+
// RUN: mkdir %t.dir/Inputs
6+
// RUN: cp %S/Inputs/header.h %t.dir/Inputs/header.h
7+
// RUN: cp %S/Inputs/header2.h %t.dir/Inputs/header2.h
8+
// RUN: sed -e "s|DIR|%/t.dir|g" %S/Inputs/regular_cdb.json > %t.cdb
9+
//
10+
// RUN: clang-scan-deps -compilation-database %t.cdb -j 1
11+
// RUN: cat %t.dir/regular_cdb.d | FileCheck %s
12+
// RUN: cat %t.dir/regular_cdb2.d | FileCheck --check-prefix=CHECK2 %s
13+
// RUN: rm -rf %t.dir/regular_cdb.d %t.dir/regular_cdb2.d
14+
//
15+
// RUN: clang-scan-deps -compilation-database %t.cdb -j 2
16+
// RUN: cat %t.dir/regular_cdb.d | FileCheck %s
17+
// RUN: cat %t.dir/regular_cdb2.d | FileCheck --check-prefix=CHECK2 %s
18+
19+
#include "header.h"
20+
21+
// CHECK: regular_cdb.cpp
22+
// CHECK-NEXT: Inputs{{/|\\}}header.h
23+
// CHECK-NOT: header2
24+
25+
// CHECK2: regular_cdb.cpp
26+
// CHECK2-NEXT: Inputs{{/|\\}}header.h
27+
// CHECK2-NEXT: Inputs{{/|\\}}header2.h

‎clang/tools/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_clang_subdirectory(clang-format-vs)
88
add_clang_subdirectory(clang-fuzzer)
99
add_clang_subdirectory(clang-import-test)
1010
add_clang_subdirectory(clang-offload-bundler)
11+
add_clang_subdirectory(clang-scan-deps)
1112

1213
add_clang_subdirectory(c-index-test)
1314

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
set(LLVM_LINK_COMPONENTS
2+
Core
3+
Support
4+
)
5+
6+
add_clang_tool(clang-scan-deps
7+
ClangScanDeps.cpp
8+
)
9+
10+
set(CLANG_SCAN_DEPS_LIB_DEPS
11+
clangAST
12+
clangBasic
13+
clangCodeGen
14+
clangDriver
15+
clangFrontend
16+
clangFrontendTool
17+
clangLex
18+
clangParse
19+
clangTooling
20+
)
21+
22+
target_link_libraries(clang-scan-deps
23+
PRIVATE
24+
${CLANG_SCAN_DEPS_LIB_DEPS}
25+
)
26+
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
//===-- ClangScanDeps.cpp - Implementation of clang-scan-deps -------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "clang/Frontend/CompilerInstance.h"
11+
#include "clang/Frontend/CompilerInvocation.h"
12+
#include "clang/Frontend/FrontendActions.h"
13+
#include "clang/Frontend/PCHContainerOperations.h"
14+
#include "clang/FrontendTool/Utils.h"
15+
#include "clang/Tooling/CommonOptionsParser.h"
16+
#include "clang/Tooling/JSONCompilationDatabase.h"
17+
#include "clang/Tooling/Tooling.h"
18+
#include "llvm/Support/FileSystem.h"
19+
#include "llvm/Support/InitLLVM.h"
20+
#include "llvm/Support/JSON.h"
21+
#include "llvm/Support/Options.h"
22+
#include "llvm/Support/Path.h"
23+
#include "llvm/Support/Program.h"
24+
#include "llvm/Support/Signals.h"
25+
#include "llvm/Support/Threading.h"
26+
#include <thread>
27+
28+
using namespace clang;
29+
30+
namespace {
31+
32+
/// A clang tool that runs the preprocessor only for the given compiler
33+
/// invocation.
34+
class PreprocessorOnlyTool : public tooling::ToolAction {
35+
public:
36+
PreprocessorOnlyTool(StringRef WorkingDirectory)
37+
: WorkingDirectory(WorkingDirectory) {}
38+
39+
bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
40+
FileManager *FileMgr,
41+
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
42+
DiagnosticConsumer *DiagConsumer) override {
43+
// Create a compiler instance to handle the actual work.
44+
CompilerInstance Compiler(std::move(PCHContainerOps));
45+
Compiler.setInvocation(std::move(Invocation));
46+
FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory;
47+
Compiler.setFileManager(FileMgr);
48+
49+
// Create the compiler's actual diagnostics engine.
50+
Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
51+
if (!Compiler.hasDiagnostics())
52+
return false;
53+
54+
Compiler.createSourceManager(*FileMgr);
55+
56+
auto Action = llvm::make_unique<PreprocessOnlyAction>();
57+
const bool Result = Compiler.ExecuteAction(*Action);
58+
FileMgr->clearStatCache();
59+
return Result;
60+
}
61+
62+
private:
63+
StringRef WorkingDirectory;
64+
};
65+
66+
/// A proxy file system that doesn't call `chdir` when changing the working
67+
/// directory of a clang tool.
68+
class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem {
69+
public:
70+
ProxyFileSystemWithoutChdir(
71+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
72+
: ProxyFileSystem(std::move(FS)) {}
73+
74+
llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
75+
assert(!CWD.empty() && "empty CWD");
76+
return CWD;
77+
}
78+
79+
std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
80+
CWD = Path.str();
81+
return {};
82+
}
83+
84+
private:
85+
std::string CWD;
86+
};
87+
88+
/// The high-level implementation of the dependency discovery tool that runs on
89+
/// an individual worker thread.
90+
class DependencyScanningTool {
91+
public:
92+
/// Construct a dependency scanning tool.
93+
///
94+
/// \param Compilations The reference to the compilation database that's
95+
/// used by the clang tool.
96+
DependencyScanningTool(const tooling::CompilationDatabase &Compilations)
97+
: Compilations(Compilations) {
98+
PCHContainerOps = std::make_shared<PCHContainerOperations>();
99+
BaseFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem());
100+
}
101+
102+
/// Computes the dependencies for the given file.
103+
///
104+
/// \returns True on error.
105+
bool runOnFile(const std::string &Input, StringRef CWD) {
106+
BaseFS->setCurrentWorkingDirectory(CWD);
107+
tooling::ClangTool Tool(Compilations, Input, PCHContainerOps, BaseFS);
108+
Tool.clearArgumentsAdjusters();
109+
Tool.setRestoreWorkingDir(false);
110+
PreprocessorOnlyTool Action(CWD);
111+
return Tool.run(&Action);
112+
}
113+
114+
private:
115+
const tooling::CompilationDatabase &Compilations;
116+
std::shared_ptr<PCHContainerOperations> PCHContainerOps;
117+
/// The real filesystem used as a base for all the operations performed by the
118+
/// tool.
119+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS;
120+
};
121+
122+
llvm::cl::opt<bool> Help("h", llvm::cl::desc("Alias for -help"),
123+
llvm::cl::Hidden);
124+
125+
llvm::cl::OptionCategory DependencyScannerCategory("Tool options");
126+
127+
llvm::cl::opt<unsigned>
128+
NumThreads("j", llvm::cl::Optional,
129+
llvm::cl::desc("Number of worker threads to use (default: use "
130+
"all concurrent threads)"),
131+
llvm::cl::init(0));
132+
133+
llvm::cl::opt<std::string>
134+
CompilationDB("compilation-database",
135+
llvm::cl::desc("Compilation database"), llvm::cl::Required,
136+
llvm::cl::cat(DependencyScannerCategory));
137+
138+
} // end anonymous namespace
139+
140+
int main(int argc, const char **argv) {
141+
llvm::InitLLVM X(argc, argv);
142+
llvm::cl::HideUnrelatedOptions(DependencyScannerCategory);
143+
if (!llvm::cl::ParseCommandLineOptions(argc, argv))
144+
return 1;
145+
146+
std::string ErrorMessage;
147+
std::unique_ptr<tooling::JSONCompilationDatabase> Compilations =
148+
tooling::JSONCompilationDatabase::loadFromFile(
149+
CompilationDB, ErrorMessage,
150+
tooling::JSONCommandLineSyntax::AutoDetect);
151+
if (!Compilations) {
152+
llvm::errs() << "error: " << ErrorMessage << "\n";
153+
return 1;
154+
}
155+
156+
llvm::cl::PrintOptionValues();
157+
158+
// By default the tool runs on all inputs in the CDB.
159+
std::vector<std::pair<std::string, std::string>> Inputs;
160+
for (const auto &Command : Compilations->getAllCompileCommands())
161+
Inputs.emplace_back(Command.Filename, Command.Directory);
162+
163+
// The command options are rewritten to run Clang in preprocessor only mode.
164+
auto AdjustingCompilations =
165+
llvm::make_unique<tooling::ArgumentsAdjustingCompilations>(
166+
std::move(Compilations));
167+
AdjustingCompilations->appendArgumentsAdjuster(
168+
[](const tooling::CommandLineArguments &Args, StringRef /*unused*/) {
169+
tooling::CommandLineArguments AdjustedArgs = Args;
170+
AdjustedArgs.push_back("-o");
171+
AdjustedArgs.push_back("/dev/null");
172+
AdjustedArgs.push_back("-Xclang");
173+
AdjustedArgs.push_back("-Eonly");
174+
AdjustedArgs.push_back("-Xclang");
175+
AdjustedArgs.push_back("-sys-header-deps");
176+
return AdjustedArgs;
177+
});
178+
179+
unsigned NumWorkers =
180+
NumThreads == 0 ? llvm::hardware_concurrency() : NumThreads;
181+
std::vector<std::unique_ptr<DependencyScanningTool>> WorkerTools;
182+
for (unsigned I = 0; I < NumWorkers; ++I)
183+
WorkerTools.push_back(
184+
llvm::make_unique<DependencyScanningTool>(*AdjustingCompilations));
185+
186+
std::vector<std::thread> WorkerThreads;
187+
std::atomic<bool> HadErrors(false);
188+
std::mutex Lock;
189+
size_t Index = 0;
190+
191+
llvm::outs() << "Running clang-scan-deps on " << Inputs.size()
192+
<< " files using " << NumWorkers << " workers\n";
193+
for (unsigned I = 0; I < NumWorkers; ++I) {
194+
WorkerThreads.emplace_back(
195+
[I, &Lock, &Index, &Inputs, &HadErrors, &WorkerTools]() {
196+
while (true) {
197+
std::string Input;
198+
StringRef CWD;
199+
// Take the next input.
200+
{
201+
std::unique_lock<std::mutex> LockGuard(Lock);
202+
if (Index >= Inputs.size())
203+
return;
204+
const auto &Compilation = Inputs[Index++];
205+
Input = Compilation.first;
206+
CWD = Compilation.second;
207+
}
208+
// Run the tool on it.
209+
if (WorkerTools[I]->runOnFile(Input, CWD))
210+
HadErrors = true;
211+
}
212+
});
213+
}
214+
for (auto &W : WorkerThreads)
215+
W.join();
216+
217+
return HadErrors;
218+
}

0 commit comments

Comments
 (0)