Skip to content

Commit 99f440a

Browse files
feat: Auto-detect Clang directories when using NVCC (#436)
Tested the fix manually against apache/mxnet
1 parent 17ef973 commit 99f440a

File tree

2 files changed

+184
-125
lines changed

2 files changed

+184
-125
lines changed

indexer/CompilationDatabase.cc

Lines changed: 179 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,44 @@ struct CompletedProcess {
3737
}
3838
};
3939

40+
} // namespace
41+
42+
static CompletedProcess runProcess(std::vector<std::string> &args,
43+
const char *logContext) {
44+
CompletedProcess out{.exitCode = EXIT_FAILURE,
45+
.error = std::nullopt,
46+
.stdoutLines = {},
47+
.stderrLines = {}};
48+
boost::process::ipstream stdoutStream, stderrStream;
49+
BOOST_TRY {
50+
spdlog::debug("{}{}invoking '{}'", logContext ? logContext : "",
51+
logContext ? " by " : "", fmt::join(args, " "));
52+
boost::process::child worker(args, boost::process::std_out > stdoutStream,
53+
boost::process::std_err > stderrStream);
54+
worker.wait();
55+
out.exitCode = worker.exit_code();
56+
}
57+
BOOST_CATCH(boost::process::process_error & ex) {
58+
out.error = ex;
59+
}
60+
BOOST_CATCH_END
61+
std::string line;
62+
while (std::getline(stdoutStream, line) && !line.empty()) {
63+
out.stdoutLines.push_back(line);
64+
}
65+
while (std::getline(stderrStream, line) && !line.empty()) {
66+
out.stderrLines.push_back(line);
67+
}
68+
return out;
69+
}
70+
71+
namespace {
72+
73+
using AbsolutePath = scip_clang::AbsolutePath;
4074
using ToolchainInfo = scip_clang::compdb::ToolchainInfo;
4175
using CompilerKind = scip_clang::compdb::CompilerKind;
4276

43-
struct ClangToolchainInfo : ToolchainInfo {
77+
struct ClangToolchainInfo : public ToolchainInfo {
4478
std::string resourceDir;
4579
std::vector<std::string> findResourceDirInvocation;
4680
std::string compilerDriverPath;
@@ -84,9 +118,57 @@ struct ClangToolchainInfo : ToolchainInfo {
84118
commandLine.push_back("-resource-dir");
85119
commandLine.push_back(this->resourceDir);
86120
}
121+
122+
static std::unique_ptr<ClangToolchainInfo>
123+
tryInfer(const AbsolutePath &compilerPath) {
124+
std::vector<std::string> findResourceDirInvocation = {
125+
compilerPath.asStringRef(), "-print-resource-dir"};
126+
auto printResourceDirResult = ::runProcess(
127+
findResourceDirInvocation, "attempting to find resource dir");
128+
if (!printResourceDirResult.isSuccess()) {
129+
return nullptr;
130+
}
131+
if (printResourceDirResult.stdoutLines.empty()) {
132+
spdlog::warn("{} succeeded but returned an empty result",
133+
fmt::join(findResourceDirInvocation, " "));
134+
return nullptr;
135+
}
136+
auto resourceDir = std::string(
137+
absl::StripAsciiWhitespace(printResourceDirResult.stdoutLines.front()));
138+
spdlog::debug("got resource dir {} from {}", resourceDir,
139+
compilerPath.asStringRef());
140+
141+
std::vector<std::string> findDriverInvocation = {compilerPath.asStringRef(),
142+
"-###"};
143+
auto hashHashHashResult = ::runProcess(
144+
findDriverInvocation, "attempting to find installed directory");
145+
std::string compilerDriverPath = "";
146+
if (hashHashHashResult.isSuccess()) {
147+
for (auto &line : hashHashHashResult.stderrLines) {
148+
auto clangDriverDir = absl::StripPrefix(line, "InstalledDir: ");
149+
if (clangDriverDir.length() != line.length()) {
150+
compilerDriverPath = scip_clang::joinPath(
151+
absl::StripAsciiWhitespace(clangDriverDir), "clang");
152+
spdlog::debug("found compiler driver at {}", compilerDriverPath);
153+
break;
154+
}
155+
}
156+
}
157+
if (compilerDriverPath.empty()) {
158+
spdlog::warn(
159+
"failed to determine compiler path using -### for compiler at '{}'",
160+
compilerPath.asStringRef());
161+
ToolchainInfo::logStdlibWarning();
162+
return nullptr;
163+
}
164+
165+
return std::make_unique<ClangToolchainInfo>(
166+
resourceDir, findResourceDirInvocation, compilerDriverPath,
167+
findDriverInvocation);
168+
}
87169
};
88170

89-
struct GccToolchainInfo : ToolchainInfo {
171+
struct GccToolchainInfo : public ToolchainInfo {
90172
std::string installDir;
91173
std::vector<std::string> findInstallDirInvocation;
92174

@@ -116,13 +198,68 @@ struct GccToolchainInfo : ToolchainInfo {
116198
// gcc-7 adds headers like limits.h and syslimits.h in include-fixed
117199
commandLine.push_back(fmt::format("-I{}/include-fixed", this->installDir));
118200
}
201+
202+
static std::unique_ptr<GccToolchainInfo>
203+
tryInfer(const AbsolutePath &compilerPath) {
204+
std::vector<std::string> findSearchDirsInvocation = {
205+
compilerPath.asStringRef(), "-print-search-dirs"};
206+
auto printSearchDirsResult = ::runProcess(findSearchDirsInvocation,
207+
"attempting to find search dirs");
208+
if (!printSearchDirsResult.isSuccess()) {
209+
return nullptr;
210+
}
211+
std::string installDir;
212+
absl::c_any_of(printSearchDirsResult.stdoutLines,
213+
[&](const std::string &line) -> bool {
214+
if (line.starts_with("install:")) {
215+
installDir = absl::StripAsciiWhitespace(
216+
absl::StripPrefix(line, "install:"));
217+
return true;
218+
}
219+
return false;
220+
});
221+
if (installDir.empty()) {
222+
spdlog::warn(
223+
"missing 'install:' line in -print-search-dirs from GCC(-like?) {}",
224+
compilerPath.asStringRef());
225+
ToolchainInfo::logStdlibWarning();
226+
return nullptr;
227+
}
228+
spdlog::debug("found gcc install directory at {}", installDir);
229+
return std::make_unique<GccToolchainInfo>(installDir,
230+
findSearchDirsInvocation);
231+
}
119232
};
120233

121-
struct NvccToolchainInfo : ToolchainInfo {
122-
scip_clang::AbsolutePath cudaDir;
234+
struct NvccToolchainInfo : public ToolchainInfo {
235+
AbsolutePath cudaDir;
236+
237+
/// Identify where the clang toolchain is based on PATH, if possible.
238+
/// Without the appropriate Clang headers, it seems like the frontend
239+
/// doesn't even construct the appropriate CUDAKernelCallExpr values.
240+
std::unique_ptr<ClangToolchainInfo> clangInfo;
123241

124-
NvccToolchainInfo(scip_clang::AbsolutePath cudaDir)
125-
: ToolchainInfo(), cudaDir(cudaDir) {}
242+
NvccToolchainInfo(AbsolutePath cudaDir)
243+
: ToolchainInfo(), cudaDir(cudaDir), clangInfo(nullptr) {
244+
// TODO: In principle, we could pick up Clang from -ccbin but that
245+
// requires more plumbing; it would require using the -ccbin arg
246+
// as part of the hash map key for toolchainInfoMap. So instead,
247+
// for now, just require that the same Clang be available on PATH.
248+
auto clangPath = boost::process::search_path("clang").native();
249+
if (!clangPath.empty()) {
250+
auto clangAbsPath =
251+
AbsolutePath(std::string(clangPath.data(), clangPath.size()));
252+
this->clangInfo = ClangToolchainInfo::tryInfer(clangAbsPath);
253+
}
254+
if (clangInfo) {
255+
return;
256+
}
257+
spdlog::error("clang not found on PATH; may be unable to locate headers "
258+
"like __clang_cuda_runtime_wrapper.h");
259+
spdlog::warn("code navigation for kernel call expressions may not work in "
260+
"the absence of Clang CUDA headers");
261+
ToolchainInfo::logStdlibWarning();
262+
}
126263

127264
virtual CompilerKind kind() const override {
128265
return CompilerKind::Nvcc;
@@ -147,139 +284,58 @@ struct NvccToolchainInfo : ToolchainInfo {
147284
commandLine.push_back(
148285
fmt::format("-isystem{}{}include", this->cudaDir.asStringRef(),
149286
std::filesystem::path::preferred_separator));
287+
if (this->clangInfo) {
288+
this->clangInfo->adjustCommandLine(commandLine);
289+
}
290+
}
291+
292+
static std::unique_ptr<NvccToolchainInfo>
293+
tryInfer(const AbsolutePath &compilerPath) {
294+
std::vector<std::string> argv = {compilerPath.asStringRef(), "--version"};
295+
auto compilerVersionResult = ::runProcess(argv, "checking for NVCC");
296+
if (compilerVersionResult.isSuccess()
297+
&& !compilerVersionResult.stdoutLines.empty()
298+
&& absl::StrContains(compilerVersionResult.stdoutLines[0], "NVIDIA")) {
299+
if (auto binDir = compilerPath.asRef().prefix()) {
300+
if (auto cudaDir = binDir->prefix()) {
301+
return std::make_unique<NvccToolchainInfo>(AbsolutePath(*cudaDir));
302+
}
303+
}
304+
}
305+
return nullptr;
150306
}
151307
};
152308

153309
} // namespace
154310

155-
static CompletedProcess runProcess(std::vector<std::string> &args,
156-
const char *logContext) {
157-
CompletedProcess out{.exitCode = EXIT_FAILURE,
158-
.error = std::nullopt,
159-
.stdoutLines = {},
160-
.stderrLines = {}};
161-
boost::process::ipstream stdoutStream, stderrStream;
162-
BOOST_TRY {
163-
spdlog::debug("{}{}invoking '{}'", logContext ? logContext : "",
164-
logContext ? " by " : "", fmt::join(args, " "));
165-
boost::process::child worker(args, boost::process::std_out > stdoutStream,
166-
boost::process::std_err > stderrStream);
167-
worker.wait();
168-
out.exitCode = worker.exit_code();
169-
}
170-
BOOST_CATCH(boost::process::process_error & ex) {
171-
out.error = ex;
172-
}
173-
BOOST_CATCH_END
174-
std::string line;
175-
while (std::getline(stdoutStream, line) && !line.empty()) {
176-
out.stdoutLines.push_back(line);
177-
}
178-
while (std::getline(stderrStream, line) && !line.empty()) {
179-
out.stderrLines.push_back(line);
180-
}
181-
return out;
311+
/*static*/ void ToolchainInfo::logDiagnosticsHint() {
312+
spdlog::info("compilation errors are suppressed by default, but can be "
313+
"turned on using --show-compiler-diagnostics");
182314
}
183315

184-
/*static*/ std::unique_ptr<ToolchainInfo>
185-
ToolchainInfo::infer(const scip_clang::AbsolutePath &compilerPath) {
186-
187-
auto noteStdlib = []() {
188-
spdlog::warn("may be unable to locate standard library headers");
189-
spdlog::info("compilation errors are suppressed by default, but can be "
190-
"turned on using --show-compiler-diagnostics");
191-
};
192-
193-
std::vector<std::string> findResourceDirInvocation = {
194-
compilerPath.asStringRef(), "-print-resource-dir"};
195-
auto failure = std::unique_ptr<ToolchainInfo>(nullptr);
196-
197-
auto printResourceDirResult = ::runProcess(findResourceDirInvocation,
198-
"attempting to find resource dir");
199-
if (printResourceDirResult.isSuccess()) {
200-
if (printResourceDirResult.stdoutLines.empty()) {
201-
spdlog::warn("{} succeeded but returned an empty result",
202-
fmt::join(findResourceDirInvocation, " "));
203-
return failure;
204-
}
205-
auto resourceDir = std::string(
206-
absl::StripAsciiWhitespace(printResourceDirResult.stdoutLines.front()));
207-
spdlog::debug("got resource dir {} from {}", resourceDir,
208-
compilerPath.asStringRef());
209-
210-
std::vector<std::string> findDriverInvocation = {compilerPath.asStringRef(),
211-
"-###"};
212-
auto hashHashHashResult = ::runProcess(
213-
findDriverInvocation, "attempting to find installed directory");
214-
std::string compilerDriverPath = "";
215-
if (hashHashHashResult.isSuccess()) {
216-
for (auto &line : hashHashHashResult.stderrLines) {
217-
auto clangDriverDir = absl::StripPrefix(line, "InstalledDir: ");
218-
if (clangDriverDir.length() != line.length()) {
219-
compilerDriverPath = scip_clang::joinPath(
220-
absl::StripAsciiWhitespace(clangDriverDir), "clang");
221-
spdlog::debug("found compiler driver at {}", compilerDriverPath);
222-
break;
223-
}
224-
}
225-
}
226-
if (compilerDriverPath.empty()) {
227-
spdlog::warn(
228-
"failed to determine compiler path using -### for compiler at '{}'",
229-
compilerPath.asStringRef());
230-
noteStdlib();
231-
return failure;
232-
}
316+
/*static*/ void ToolchainInfo::logStdlibWarning() {
317+
spdlog::warn("may be unable to locate standard library headers");
318+
ToolchainInfo::logDiagnosticsHint();
319+
}
233320

234-
return std::make_unique<ClangToolchainInfo>(
235-
resourceDir, findResourceDirInvocation, compilerDriverPath,
236-
findDriverInvocation);
321+
/*static*/ std::unique_ptr<ToolchainInfo>
322+
ToolchainInfo::infer(const AbsolutePath &compilerPath) {
323+
if (auto clangInfo = ClangToolchainInfo::tryInfer(compilerPath)) {
324+
return clangInfo;
237325
}
238326

239-
std::vector<std::string> findSearchDirsInvocation = {
240-
compilerPath.asStringRef(), "-print-search-dirs"};
241-
auto printSearchDirsResult =
242-
::runProcess(findSearchDirsInvocation, "attempting to find search dirs");
243-
if (printSearchDirsResult.isSuccess()) {
244-
std::string installDir;
245-
absl::c_any_of(printSearchDirsResult.stdoutLines,
246-
[&](const std::string &line) -> bool {
247-
if (line.starts_with("install:")) {
248-
installDir = absl::StripAsciiWhitespace(
249-
absl::StripPrefix(line, "install:"));
250-
return true;
251-
}
252-
return false;
253-
});
254-
if (installDir.empty()) {
255-
spdlog::warn(
256-
"missing 'install:' line in -print-search-dirs from GCC(-like?) {}",
257-
compilerPath.asStringRef());
258-
noteStdlib();
259-
return failure;
260-
}
261-
spdlog::debug("found gcc install directory at {}", installDir);
262-
return std::make_unique<GccToolchainInfo>(installDir,
263-
findSearchDirsInvocation);
327+
if (auto gccInfo = GccToolchainInfo::tryInfer(compilerPath)) {
328+
return gccInfo;
264329
}
265330

266-
std::vector<std::string> argv = {compilerPath.asStringRef(), "--version"};
267-
auto compilerVersionResult = ::runProcess(argv, "checking for NVCC");
268-
if (compilerVersionResult.isSuccess()
269-
&& !compilerVersionResult.stdoutLines.empty()
270-
&& absl::StrContains(compilerVersionResult.stdoutLines[0], "NVIDIA")) {
271-
if (auto binDir = compilerPath.asRef().prefix()) {
272-
if (auto cudaDir = binDir->prefix()) {
273-
return std::make_unique<NvccToolchainInfo>(
274-
scip_clang::AbsolutePath(*cudaDir));
275-
}
276-
}
331+
if (auto nvccInfo = NvccToolchainInfo::tryInfer(compilerPath)) {
332+
return nvccInfo;
277333
}
278334

279335
spdlog::warn("compiler at '{}' is not one of clang/clang++/gcc/g++/nvcc",
280336
compilerPath.asStringRef());
281-
noteStdlib();
282-
return failure;
337+
ToolchainInfo::logStdlibWarning();
338+
return nullptr;
283339
}
284340

285341
namespace scip_clang {

indexer/CompilationDatabase.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,11 @@ struct ToolchainInfo {
129129
/// ccache).
130130
///
131131
/// Returns nullptr if we failed to create a well-formed toolchain object.
132-
static std::unique_ptr<ToolchainInfo>
133-
infer(const scip_clang::AbsolutePath &compilerPath);
132+
static std::unique_ptr<ToolchainInfo> infer(const AbsolutePath &compilerPath);
133+
134+
static void logStdlibWarning();
135+
136+
static void logDiagnosticsHint();
134137
};
135138

136139
/// The settings used to customize the parsed results generated from

0 commit comments

Comments
 (0)