Skip to content

Commit cf33542

Browse files
committed
[Glibc] Use VFS to inject modulemap into Glibc include path
This will fix modularization issues caused by the presence of Glibc and libstdc++ in a single context. Some Glibc headers were getting hijacked by the libstdc++ module, and the decls in them were incorrectly determined to be a part of libstdc++. This caused compiler errors when trying to use those decls. After this change, we will be able to reference Glibc headers directly from the module map, without using an additional header (`SwiftGlibc.h`).
1 parent af713bc commit cf33542

File tree

4 files changed

+210
-74
lines changed

4 files changed

+210
-74
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ WARNING(nonmutating_without_mutable_fields,none,
117117

118118
ERROR(module_map_not_found, none, "module map file '%0' not found", (StringRef))
119119

120+
WARNING(glibc_not_found, none,
121+
"glibc not found for '%0'; C stdlib may be unavailable",
122+
(StringRef))
120123
WARNING(libstdcxx_not_found, none,
121124
"libstdc++ not found for '%0'; C++ stdlib may be unavailable",
122125
(StringRef))

lib/ClangImporter/ClangImporter.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,13 +648,6 @@ importer::getNormalInvocationArguments(
648648
break;
649649
}
650650
}
651-
652-
SmallString<128> buffer;
653-
if (auto path = getGlibcModuleMapPath(searchPathOpts, triple, buffer)) {
654-
invocationArgStrs.push_back((Twine("-fmodule-map-file=") + *path).str());
655-
} else {
656-
// FIXME: Emit a warning of some kind.
657-
}
658651
}
659652

660653
if (searchPathOpts.getSDKPath().empty()) {

lib/ClangImporter/ClangIncludePaths.cpp

Lines changed: 206 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,84 +16,143 @@
1616
#include "swift/AST/DiagnosticsClangImporter.h"
1717
#include "swift/Basic/Platform.h"
1818
#include "clang/Driver/Driver.h"
19+
#include "clang/Driver/ToolChain.h"
1920
#include "clang/Frontend/CompilerInstance.h"
2021

2122
using namespace swift;
2223

23-
static Optional<StringRef> getModuleMapFilePath(StringRef name,
24-
SearchPathOptions &Opts,
25-
llvm::Triple triple,
26-
SmallVectorImpl<char> &buffer) {
24+
using Path = SmallString<128>;
25+
26+
static Optional<Path> getActualModuleMapPath(StringRef name,
27+
SearchPathOptions &Opts,
28+
const llvm::Triple &triple) {
2729
StringRef platform = swift::getPlatformNameForTriple(triple);
2830
StringRef arch = swift::getMajorArchitectureName(triple);
2931

32+
Path result;
33+
3034
StringRef SDKPath = Opts.getSDKPath();
3135
if (!SDKPath.empty()) {
32-
buffer.clear();
33-
buffer.append(SDKPath.begin(), SDKPath.end());
34-
llvm::sys::path::append(buffer, "usr", "lib", "swift");
35-
llvm::sys::path::append(buffer, platform, arch, name);
36+
result.append(SDKPath.begin(), SDKPath.end());
37+
llvm::sys::path::append(result, "usr", "lib", "swift");
38+
llvm::sys::path::append(result, platform, arch, name);
3639

3740
// Only specify the module map if that file actually exists. It may not;
3841
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
3942
// -emit-ir` is invoked using a Swift compiler not built for Linux targets.
40-
if (llvm::sys::fs::exists(buffer))
41-
return StringRef(buffer.data(), buffer.size());
43+
if (llvm::sys::fs::exists(result))
44+
return result;
4245
}
4346

4447
if (!Opts.RuntimeResourcePath.empty()) {
45-
buffer.clear();
46-
buffer.append(Opts.RuntimeResourcePath.begin(),
48+
result.clear();
49+
result.append(Opts.RuntimeResourcePath.begin(),
4750
Opts.RuntimeResourcePath.end());
48-
llvm::sys::path::append(buffer, platform, arch, name);
51+
llvm::sys::path::append(result, platform, arch, name);
4952

5053
// Only specify the module map if that file actually exists. It may not;
5154
// for example in the case that `swiftc -target x86_64-unknown-linux-gnu
5255
// -emit-ir` is invoked using a Swift compiler not built for Linux targets.
53-
if (llvm::sys::fs::exists(buffer))
54-
return StringRef(buffer.data(), buffer.size());
56+
if (llvm::sys::fs::exists(result))
57+
return result;
5558
}
5659

5760
return None;
5861
}
5962

60-
Optional<StringRef>
61-
swift::getGlibcModuleMapPath(SearchPathOptions &Opts, llvm::Triple triple,
62-
SmallVectorImpl<char> &buffer) {
63-
return getModuleMapFilePath("glibc.modulemap", Opts, triple, buffer);
63+
/// Given an include path directory, returns a path to inject the module map to.
64+
/// If a module map already exists, returns `None`.
65+
static llvm::Optional<Path> getInjectedModuleMapPath(const Path &dir) {
66+
Path legacyPath(dir);
67+
llvm::sys::path::append(legacyPath, "module.map");
68+
if (llvm::sys::fs::exists(legacyPath))
69+
return None;
70+
71+
Path path(dir);
72+
llvm::sys::path::append(path, "module.modulemap");
73+
if (llvm::sys::fs::exists(path))
74+
return None;
75+
76+
return path;
6477
}
6578

66-
static Optional<StringRef>
67-
getLibStdCxxModuleMapPath(SearchPathOptions &opts, llvm::Triple triple,
68-
SmallVectorImpl<char> &buffer) {
69-
return getModuleMapFilePath("libstdcxx.modulemap", opts, triple, buffer);
79+
/// Finds the glibc.modulemap file relative to the provided resource dir.
80+
///
81+
/// Note that the module map used for Glibc depends on the target we're
82+
/// compiling for, and is not included in the resource directory with the other
83+
/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap.
84+
static Optional<Path>
85+
getGlibcModuleMapPath(SearchPathOptions &Opts, const llvm::Triple &triple) {
86+
return getActualModuleMapPath("glibc.modulemap", Opts, triple);
7087
}
7188

72-
SmallVector<std::pair<std::string, std::string>, 16>
73-
swift::getClangInvocationFileMapping(ASTContext &ctx) {
74-
using Path = SmallString<128>;
89+
static Optional<Path>
90+
getLibStdCxxModuleMapPath(SearchPathOptions &opts, const llvm::Triple &triple) {
91+
return getActualModuleMapPath("libstdcxx.modulemap", opts, triple);
92+
}
7593

76-
const llvm::Triple &triple = ctx.LangOpts.Target;
77-
// We currently only need this when building for Linux.
78-
if (!triple.isOSLinux())
79-
return {};
80-
// Android uses libc++.
81-
if (triple.isAndroid())
82-
return {};
94+
static llvm::opt::InputArgList
95+
parseClangDriverArgs(const clang::driver::Driver &clangDriver,
96+
const ArrayRef<const char *> args) {
97+
unsigned unused1, unused2;
98+
return clangDriver.getOpts().ParseArgs(args, unused1, unused2);
99+
}
83100

84-
// Extract the libstdc++ installation path from Clang driver.
101+
static clang::driver::Driver createClangDriver(const ASTContext &ctx) {
85102
auto clangDiags = clang::CompilerInstance::createDiagnostics(
86103
new clang::DiagnosticOptions());
87104
clang::driver::Driver clangDriver(ctx.ClangImporterOpts.clangPath,
88-
triple.str(), *clangDiags);
105+
ctx.LangOpts.Target.str(), *clangDiags);
106+
return clangDriver;
107+
}
108+
109+
/// Given a list of include paths and a list of file names, finds the first
110+
/// include path that contains files with all the names. This is useful for
111+
/// finding the include path for a specific library among a list of include
112+
/// paths.
113+
///
114+
/// \return a path without dots (`../`, './').
115+
static llvm::Optional<Path>
116+
findFirstIncludeDir(const llvm::opt::InputArgList &args,
117+
const ArrayRef<const char *> expectedFileNames) {
118+
// C++ stdlib paths are added as `-internal-isystem`.
119+
std::vector<std::string> includeDirs =
120+
args.getAllArgValues(clang::driver::options::OPT_internal_isystem);
121+
// C stdlib paths are added as `-internal-externc-isystem`.
122+
llvm::append_range(includeDirs,
123+
args.getAllArgValues(
124+
clang::driver::options::OPT_internal_externc_isystem));
125+
126+
for (const auto &includeDir : includeDirs) {
127+
Path dir(includeDir);
128+
bool allExpectedExist = true;
129+
for (auto expectedFileName : expectedFileNames) {
130+
Path expectedFile(dir);
131+
llvm::sys::path::append(expectedFile, expectedFileName);
132+
if (!llvm::sys::fs::exists(expectedFile)) {
133+
allExpectedExist = false;
134+
break;
135+
}
136+
}
137+
138+
if (allExpectedExist) {
139+
// VFS does not allow mapping paths that contain `../` or `./`.
140+
llvm::sys::path::remove_dots(dir, /*remove_dot_dot=*/true);
141+
return dir;
142+
}
143+
}
144+
return None;
145+
}
146+
147+
static llvm::opt::InputArgList
148+
createClangArgs(const ASTContext &ctx, clang::driver::Driver &clangDriver) {
89149
// Flags passed to Swift with `-Xcc` might affect include paths.
90-
unsigned unused1, unused2;
91150
std::vector<const char *> clangArgs;
92151
for (const auto &each : ctx.ClangImporterOpts.ExtraArgs) {
93152
clangArgs.push_back(each.c_str());
94153
}
95154
llvm::opt::InputArgList clangDriverArgs =
96-
clangDriver.getOpts().ParseArgs(clangArgs, unused1, unused2);
155+
parseClangDriverArgs(clangDriver, clangArgs);
97156
// If an SDK path was explicitly passed to Swift, make sure to pass it to
98157
// Clang driver as well. It affects the resulting include paths.
99158
auto sdkPath = ctx.SearchPathOpts.getSDKPath();
@@ -103,23 +162,106 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
103162
clangDriver.getOpts().getOption(clang::driver::options::OPT__sysroot),
104163
sdkPath, argIndex));
105164
}
106-
auto cxxStdlibDirs =
107-
clangDriver.getLibStdCxxIncludePaths(clangDriverArgs, triple);
108-
if (cxxStdlibDirs.empty()) {
109-
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
165+
return clangDriverArgs;
166+
}
167+
168+
static SmallVector<std::pair<std::string, std::string>, 2>
169+
getGlibcFileMapping(ASTContext &ctx) {
170+
const llvm::Triple &triple = ctx.LangOpts.Target;
171+
// We currently only need this when building for Linux.
172+
if (!triple.isOSLinux())
173+
return {};
174+
175+
// Extract the Glibc path from Clang driver.
176+
auto clangDriver = createClangDriver(ctx);
177+
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
178+
179+
llvm::opt::ArgStringList includeArgStrings;
180+
const auto &clangToolchain =
181+
clangDriver.getToolChain(clangDriverArgs, triple);
182+
clangToolchain.AddClangSystemIncludeArgs(clangDriverArgs, includeArgStrings);
183+
auto parsedIncludeArgs = parseClangDriverArgs(clangDriver, includeArgStrings);
184+
185+
// Find the include path that contains Glibc headers. We use three arbitrarily
186+
// chosen headers to determine if the include path actually contains Glibc.
187+
// Ideally we would check that all of the headers referenced from the
188+
// modulemap are present.
189+
Path glibcDir;
190+
if (auto dir = findFirstIncludeDir(parsedIncludeArgs,
191+
{"inttypes.h", "unistd.h", "stdint.h"})) {
192+
glibcDir = dir.getValue();
193+
} else {
194+
ctx.Diags.diagnose(SourceLoc(), diag::glibc_not_found, triple.str());
110195
return {};
111196
}
112-
Path cxxStdlibDir(cxxStdlibDirs.front());
113-
// VFS does not allow mapping paths that contain `../` or `./`.
114-
llvm::sys::path::remove_dots(cxxStdlibDir, /*remove_dot_dot=*/true);
115197

116-
// Currently only a modulemap for libstdc++ is injected.
117-
if (!ctx.LangOpts.EnableCXXInterop)
198+
Path actualModuleMapPath;
199+
if (auto path = getGlibcModuleMapPath(ctx.SearchPathOpts, triple))
200+
actualModuleMapPath = path.getValue();
201+
else
202+
// FIXME: Emit a warning of some kind.
203+
return {};
204+
205+
// Only inject the module map if it actually exists. It may not, for example
206+
// if `swiftc -target x86_64-unknown-linux-gnu -emit-ir` is invoked using
207+
// a Swift compiler not built for Linux targets.
208+
if (!llvm::sys::fs::exists(actualModuleMapPath))
209+
// FIXME: emit a warning of some kind.
210+
return {};
211+
212+
// TODO: remove the SwiftGlibc.h header and reference all Glibc headers
213+
// directly from the modulemap.
214+
Path actualHeaderPath = actualModuleMapPath;
215+
llvm::sys::path::remove_filename(actualHeaderPath);
216+
llvm::sys::path::append(actualHeaderPath, "SwiftGlibc.h");
217+
218+
Path injectedModuleMapPath(glibcDir);
219+
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
220+
221+
Path injectedHeaderPath(glibcDir);
222+
llvm::sys::path::append(injectedHeaderPath, "SwiftGlibc.h");
223+
224+
return {
225+
{std::string(injectedModuleMapPath), std::string(actualModuleMapPath)},
226+
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
227+
};
228+
}
229+
230+
static SmallVector<std::pair<std::string, std::string>, 2>
231+
getLibStdCxxFileMapping(ASTContext &ctx) {
232+
assert(ctx.LangOpts.EnableCXXInterop &&
233+
"libstdc++ is only injected if C++ interop is enabled");
234+
235+
const llvm::Triple &triple = ctx.LangOpts.Target;
236+
// We currently only need this when building for Linux.
237+
if (!triple.isOSLinux())
238+
return {};
239+
// Android uses libc++.
240+
if (triple.isAndroid())
241+
return {};
242+
243+
// Extract the libstdc++ installation path from Clang driver.
244+
auto clangDriver = createClangDriver(ctx);
245+
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
246+
247+
llvm::opt::ArgStringList stdlibArgStrings;
248+
const auto &clangToolchain =
249+
clangDriver.getToolChain(clangDriverArgs, triple);
250+
clangToolchain.AddClangCXXStdlibIncludeArgs(clangDriverArgs,
251+
stdlibArgStrings);
252+
auto parsedStdlibArgs = parseClangDriverArgs(clangDriver, stdlibArgStrings);
253+
254+
Path cxxStdlibDir;
255+
if (auto dir = findFirstIncludeDir(parsedStdlibArgs,
256+
{"cstdlib", "string", "vector"})) {
257+
cxxStdlibDir = dir.getValue();
258+
} else {
259+
ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str());
118260
return {};
261+
}
119262

120263
Path actualModuleMapPath;
121-
Path buffer;
122-
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple, buffer))
264+
if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple))
123265
actualModuleMapPath = path.getValue();
124266
else
125267
return {};
@@ -140,14 +282,10 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
140282
// Inject a modulemap into VFS for the libstdc++ directory.
141283
// Only inject the module map if the module does not already exist at
142284
// {sysroot}/usr/include/module.{map,modulemap}.
143-
Path injectedModuleMapLegacyPath(cxxStdlibDir);
144-
llvm::sys::path::append(injectedModuleMapLegacyPath, "module.map");
145-
if (llvm::sys::fs::exists(injectedModuleMapLegacyPath))
146-
return {};
147-
148-
Path injectedModuleMapPath(cxxStdlibDir);
149-
llvm::sys::path::append(injectedModuleMapPath, "module.modulemap");
150-
if (llvm::sys::fs::exists(injectedModuleMapPath))
285+
Path injectedModuleMapPath;
286+
if (auto path = getInjectedModuleMapPath(cxxStdlibDir))
287+
injectedModuleMapPath = path.getValue();
288+
else
151289
return {};
152290

153291
Path injectedHeaderPath(cxxStdlibDir);
@@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) {
158296
{std::string(injectedHeaderPath), std::string(actualHeaderPath)},
159297
};
160298
}
299+
300+
SmallVector<std::pair<std::string, std::string>, 2>
301+
swift::getClangInvocationFileMapping(ASTContext &ctx) {
302+
SmallVector<std::pair<std::string, std::string>, 2> result;
303+
304+
result.append(getGlibcFileMapping(ctx));
305+
306+
if (ctx.LangOpts.EnableCXXInterop) {
307+
result.append(getLibStdCxxFileMapping(ctx));
308+
}
309+
return result;
310+
}

lib/ClangImporter/ClangIncludePaths.h

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,13 @@
1414
#define SWIFT_CLANG_INCLUDE_PATHS_H
1515

1616
#include "swift/AST/ASTContext.h"
17-
#include "swift/AST/SearchPathOptions.h"
1817

1918
namespace swift {
2019

21-
/// Finds the glibc.modulemap file relative to the provided resource dir.
22-
///
23-
/// Note that the module map used for Glibc depends on the target we're
24-
/// compiling for, and is not included in the resource directory with the other
25-
/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap.
26-
Optional<StringRef> getGlibcModuleMapPath(SearchPathOptions &Opts,
27-
llvm::Triple triple,
28-
SmallVectorImpl<char> &buffer);
29-
3020
/// On Linux, some platform libraries (glibc, libstdc++) are not modularized.
3121
/// We inject modulemaps for those libraries into their include directories
3222
/// to allow using them from Swift.
33-
SmallVector<std::pair<std::string, std::string>, 16>
23+
SmallVector<std::pair<std::string, std::string>, 2>
3424
getClangInvocationFileMapping(ASTContext &ctx);
3525

3626
} // namespace swift

0 commit comments

Comments
 (0)