From cf3354222d9f480de74db390f53a6dcc749fde14 Mon Sep 17 00:00:00 2001 From: Egor Zhdan Date: Fri, 1 Jul 2022 19:00:46 +0100 Subject: [PATCH] [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`). --- .../swift/AST/DiagnosticsClangImporter.def | 3 + lib/ClangImporter/ClangImporter.cpp | 7 - lib/ClangImporter/ClangIncludePaths.cpp | 262 ++++++++++++++---- lib/ClangImporter/ClangIncludePaths.h | 12 +- 4 files changed, 210 insertions(+), 74 deletions(-) diff --git a/include/swift/AST/DiagnosticsClangImporter.def b/include/swift/AST/DiagnosticsClangImporter.def index 6914d2e633059..52ce6815e0a8b 100644 --- a/include/swift/AST/DiagnosticsClangImporter.def +++ b/include/swift/AST/DiagnosticsClangImporter.def @@ -117,6 +117,9 @@ WARNING(nonmutating_without_mutable_fields,none, ERROR(module_map_not_found, none, "module map file '%0' not found", (StringRef)) +WARNING(glibc_not_found, none, + "glibc not found for '%0'; C stdlib may be unavailable", + (StringRef)) WARNING(libstdcxx_not_found, none, "libstdc++ not found for '%0'; C++ stdlib may be unavailable", (StringRef)) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 265b2261cd535..31422e88f92d0 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -648,13 +648,6 @@ importer::getNormalInvocationArguments( break; } } - - SmallString<128> buffer; - if (auto path = getGlibcModuleMapPath(searchPathOpts, triple, buffer)) { - invocationArgStrs.push_back((Twine("-fmodule-map-file=") + *path).str()); - } else { - // FIXME: Emit a warning of some kind. - } } if (searchPathOpts.getSDKPath().empty()) { diff --git a/lib/ClangImporter/ClangIncludePaths.cpp b/lib/ClangImporter/ClangIncludePaths.cpp index 92565a5ad14e2..6a6b9bb68c6e5 100644 --- a/lib/ClangImporter/ClangIncludePaths.cpp +++ b/lib/ClangImporter/ClangIncludePaths.cpp @@ -16,84 +16,143 @@ #include "swift/AST/DiagnosticsClangImporter.h" #include "swift/Basic/Platform.h" #include "clang/Driver/Driver.h" +#include "clang/Driver/ToolChain.h" #include "clang/Frontend/CompilerInstance.h" using namespace swift; -static Optional getModuleMapFilePath(StringRef name, - SearchPathOptions &Opts, - llvm::Triple triple, - SmallVectorImpl &buffer) { +using Path = SmallString<128>; + +static Optional getActualModuleMapPath(StringRef name, + SearchPathOptions &Opts, + const llvm::Triple &triple) { StringRef platform = swift::getPlatformNameForTriple(triple); StringRef arch = swift::getMajorArchitectureName(triple); + Path result; + StringRef SDKPath = Opts.getSDKPath(); if (!SDKPath.empty()) { - buffer.clear(); - buffer.append(SDKPath.begin(), SDKPath.end()); - llvm::sys::path::append(buffer, "usr", "lib", "swift"); - llvm::sys::path::append(buffer, platform, arch, name); + result.append(SDKPath.begin(), SDKPath.end()); + llvm::sys::path::append(result, "usr", "lib", "swift"); + llvm::sys::path::append(result, platform, arch, name); // Only specify the module map if that file actually exists. It may not; // for example in the case that `swiftc -target x86_64-unknown-linux-gnu // -emit-ir` is invoked using a Swift compiler not built for Linux targets. - if (llvm::sys::fs::exists(buffer)) - return StringRef(buffer.data(), buffer.size()); + if (llvm::sys::fs::exists(result)) + return result; } if (!Opts.RuntimeResourcePath.empty()) { - buffer.clear(); - buffer.append(Opts.RuntimeResourcePath.begin(), + result.clear(); + result.append(Opts.RuntimeResourcePath.begin(), Opts.RuntimeResourcePath.end()); - llvm::sys::path::append(buffer, platform, arch, name); + llvm::sys::path::append(result, platform, arch, name); // Only specify the module map if that file actually exists. It may not; // for example in the case that `swiftc -target x86_64-unknown-linux-gnu // -emit-ir` is invoked using a Swift compiler not built for Linux targets. - if (llvm::sys::fs::exists(buffer)) - return StringRef(buffer.data(), buffer.size()); + if (llvm::sys::fs::exists(result)) + return result; } return None; } -Optional -swift::getGlibcModuleMapPath(SearchPathOptions &Opts, llvm::Triple triple, - SmallVectorImpl &buffer) { - return getModuleMapFilePath("glibc.modulemap", Opts, triple, buffer); +/// Given an include path directory, returns a path to inject the module map to. +/// If a module map already exists, returns `None`. +static llvm::Optional getInjectedModuleMapPath(const Path &dir) { + Path legacyPath(dir); + llvm::sys::path::append(legacyPath, "module.map"); + if (llvm::sys::fs::exists(legacyPath)) + return None; + + Path path(dir); + llvm::sys::path::append(path, "module.modulemap"); + if (llvm::sys::fs::exists(path)) + return None; + + return path; } -static Optional -getLibStdCxxModuleMapPath(SearchPathOptions &opts, llvm::Triple triple, - SmallVectorImpl &buffer) { - return getModuleMapFilePath("libstdcxx.modulemap", opts, triple, buffer); +/// Finds the glibc.modulemap file relative to the provided resource dir. +/// +/// Note that the module map used for Glibc depends on the target we're +/// compiling for, and is not included in the resource directory with the other +/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap. +static Optional +getGlibcModuleMapPath(SearchPathOptions &Opts, const llvm::Triple &triple) { + return getActualModuleMapPath("glibc.modulemap", Opts, triple); } -SmallVector, 16> -swift::getClangInvocationFileMapping(ASTContext &ctx) { - using Path = SmallString<128>; +static Optional +getLibStdCxxModuleMapPath(SearchPathOptions &opts, const llvm::Triple &triple) { + return getActualModuleMapPath("libstdcxx.modulemap", opts, triple); +} - const llvm::Triple &triple = ctx.LangOpts.Target; - // We currently only need this when building for Linux. - if (!triple.isOSLinux()) - return {}; - // Android uses libc++. - if (triple.isAndroid()) - return {}; +static llvm::opt::InputArgList +parseClangDriverArgs(const clang::driver::Driver &clangDriver, + const ArrayRef args) { + unsigned unused1, unused2; + return clangDriver.getOpts().ParseArgs(args, unused1, unused2); +} - // Extract the libstdc++ installation path from Clang driver. +static clang::driver::Driver createClangDriver(const ASTContext &ctx) { auto clangDiags = clang::CompilerInstance::createDiagnostics( new clang::DiagnosticOptions()); clang::driver::Driver clangDriver(ctx.ClangImporterOpts.clangPath, - triple.str(), *clangDiags); + ctx.LangOpts.Target.str(), *clangDiags); + return clangDriver; +} + +/// Given a list of include paths and a list of file names, finds the first +/// include path that contains files with all the names. This is useful for +/// finding the include path for a specific library among a list of include +/// paths. +/// +/// \return a path without dots (`../`, './'). +static llvm::Optional +findFirstIncludeDir(const llvm::opt::InputArgList &args, + const ArrayRef expectedFileNames) { + // C++ stdlib paths are added as `-internal-isystem`. + std::vector includeDirs = + args.getAllArgValues(clang::driver::options::OPT_internal_isystem); + // C stdlib paths are added as `-internal-externc-isystem`. + llvm::append_range(includeDirs, + args.getAllArgValues( + clang::driver::options::OPT_internal_externc_isystem)); + + for (const auto &includeDir : includeDirs) { + Path dir(includeDir); + bool allExpectedExist = true; + for (auto expectedFileName : expectedFileNames) { + Path expectedFile(dir); + llvm::sys::path::append(expectedFile, expectedFileName); + if (!llvm::sys::fs::exists(expectedFile)) { + allExpectedExist = false; + break; + } + } + + if (allExpectedExist) { + // VFS does not allow mapping paths that contain `../` or `./`. + llvm::sys::path::remove_dots(dir, /*remove_dot_dot=*/true); + return dir; + } + } + return None; +} + +static llvm::opt::InputArgList +createClangArgs(const ASTContext &ctx, clang::driver::Driver &clangDriver) { // Flags passed to Swift with `-Xcc` might affect include paths. - unsigned unused1, unused2; std::vector clangArgs; for (const auto &each : ctx.ClangImporterOpts.ExtraArgs) { clangArgs.push_back(each.c_str()); } llvm::opt::InputArgList clangDriverArgs = - clangDriver.getOpts().ParseArgs(clangArgs, unused1, unused2); + parseClangDriverArgs(clangDriver, clangArgs); // If an SDK path was explicitly passed to Swift, make sure to pass it to // Clang driver as well. It affects the resulting include paths. auto sdkPath = ctx.SearchPathOpts.getSDKPath(); @@ -103,23 +162,106 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) { clangDriver.getOpts().getOption(clang::driver::options::OPT__sysroot), sdkPath, argIndex)); } - auto cxxStdlibDirs = - clangDriver.getLibStdCxxIncludePaths(clangDriverArgs, triple); - if (cxxStdlibDirs.empty()) { - ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str()); + return clangDriverArgs; +} + +static SmallVector, 2> +getGlibcFileMapping(ASTContext &ctx) { + const llvm::Triple &triple = ctx.LangOpts.Target; + // We currently only need this when building for Linux. + if (!triple.isOSLinux()) + return {}; + + // Extract the Glibc path from Clang driver. + auto clangDriver = createClangDriver(ctx); + auto clangDriverArgs = createClangArgs(ctx, clangDriver); + + llvm::opt::ArgStringList includeArgStrings; + const auto &clangToolchain = + clangDriver.getToolChain(clangDriverArgs, triple); + clangToolchain.AddClangSystemIncludeArgs(clangDriverArgs, includeArgStrings); + auto parsedIncludeArgs = parseClangDriverArgs(clangDriver, includeArgStrings); + + // Find the include path that contains Glibc headers. We use three arbitrarily + // chosen headers to determine if the include path actually contains Glibc. + // Ideally we would check that all of the headers referenced from the + // modulemap are present. + Path glibcDir; + if (auto dir = findFirstIncludeDir(parsedIncludeArgs, + {"inttypes.h", "unistd.h", "stdint.h"})) { + glibcDir = dir.getValue(); + } else { + ctx.Diags.diagnose(SourceLoc(), diag::glibc_not_found, triple.str()); return {}; } - Path cxxStdlibDir(cxxStdlibDirs.front()); - // VFS does not allow mapping paths that contain `../` or `./`. - llvm::sys::path::remove_dots(cxxStdlibDir, /*remove_dot_dot=*/true); - // Currently only a modulemap for libstdc++ is injected. - if (!ctx.LangOpts.EnableCXXInterop) + Path actualModuleMapPath; + if (auto path = getGlibcModuleMapPath(ctx.SearchPathOpts, triple)) + actualModuleMapPath = path.getValue(); + else + // FIXME: Emit a warning of some kind. + return {}; + + // Only inject the module map if it actually exists. It may not, for example + // if `swiftc -target x86_64-unknown-linux-gnu -emit-ir` is invoked using + // a Swift compiler not built for Linux targets. + if (!llvm::sys::fs::exists(actualModuleMapPath)) + // FIXME: emit a warning of some kind. + return {}; + + // TODO: remove the SwiftGlibc.h header and reference all Glibc headers + // directly from the modulemap. + Path actualHeaderPath = actualModuleMapPath; + llvm::sys::path::remove_filename(actualHeaderPath); + llvm::sys::path::append(actualHeaderPath, "SwiftGlibc.h"); + + Path injectedModuleMapPath(glibcDir); + llvm::sys::path::append(injectedModuleMapPath, "module.modulemap"); + + Path injectedHeaderPath(glibcDir); + llvm::sys::path::append(injectedHeaderPath, "SwiftGlibc.h"); + + return { + {std::string(injectedModuleMapPath), std::string(actualModuleMapPath)}, + {std::string(injectedHeaderPath), std::string(actualHeaderPath)}, + }; +} + +static SmallVector, 2> +getLibStdCxxFileMapping(ASTContext &ctx) { + assert(ctx.LangOpts.EnableCXXInterop && + "libstdc++ is only injected if C++ interop is enabled"); + + const llvm::Triple &triple = ctx.LangOpts.Target; + // We currently only need this when building for Linux. + if (!triple.isOSLinux()) + return {}; + // Android uses libc++. + if (triple.isAndroid()) + return {}; + + // Extract the libstdc++ installation path from Clang driver. + auto clangDriver = createClangDriver(ctx); + auto clangDriverArgs = createClangArgs(ctx, clangDriver); + + llvm::opt::ArgStringList stdlibArgStrings; + const auto &clangToolchain = + clangDriver.getToolChain(clangDriverArgs, triple); + clangToolchain.AddClangCXXStdlibIncludeArgs(clangDriverArgs, + stdlibArgStrings); + auto parsedStdlibArgs = parseClangDriverArgs(clangDriver, stdlibArgStrings); + + Path cxxStdlibDir; + if (auto dir = findFirstIncludeDir(parsedStdlibArgs, + {"cstdlib", "string", "vector"})) { + cxxStdlibDir = dir.getValue(); + } else { + ctx.Diags.diagnose(SourceLoc(), diag::libstdcxx_not_found, triple.str()); return {}; + } Path actualModuleMapPath; - Path buffer; - if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple, buffer)) + if (auto path = getLibStdCxxModuleMapPath(ctx.SearchPathOpts, triple)) actualModuleMapPath = path.getValue(); else return {}; @@ -140,14 +282,10 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) { // Inject a modulemap into VFS for the libstdc++ directory. // Only inject the module map if the module does not already exist at // {sysroot}/usr/include/module.{map,modulemap}. - Path injectedModuleMapLegacyPath(cxxStdlibDir); - llvm::sys::path::append(injectedModuleMapLegacyPath, "module.map"); - if (llvm::sys::fs::exists(injectedModuleMapLegacyPath)) - return {}; - - Path injectedModuleMapPath(cxxStdlibDir); - llvm::sys::path::append(injectedModuleMapPath, "module.modulemap"); - if (llvm::sys::fs::exists(injectedModuleMapPath)) + Path injectedModuleMapPath; + if (auto path = getInjectedModuleMapPath(cxxStdlibDir)) + injectedModuleMapPath = path.getValue(); + else return {}; Path injectedHeaderPath(cxxStdlibDir); @@ -158,3 +296,15 @@ swift::getClangInvocationFileMapping(ASTContext &ctx) { {std::string(injectedHeaderPath), std::string(actualHeaderPath)}, }; } + +SmallVector, 2> +swift::getClangInvocationFileMapping(ASTContext &ctx) { + SmallVector, 2> result; + + result.append(getGlibcFileMapping(ctx)); + + if (ctx.LangOpts.EnableCXXInterop) { + result.append(getLibStdCxxFileMapping(ctx)); + } + return result; +} diff --git a/lib/ClangImporter/ClangIncludePaths.h b/lib/ClangImporter/ClangIncludePaths.h index 86e6ab959f4bc..5c8ee711ac74e 100644 --- a/lib/ClangImporter/ClangIncludePaths.h +++ b/lib/ClangImporter/ClangIncludePaths.h @@ -14,23 +14,13 @@ #define SWIFT_CLANG_INCLUDE_PATHS_H #include "swift/AST/ASTContext.h" -#include "swift/AST/SearchPathOptions.h" namespace swift { -/// Finds the glibc.modulemap file relative to the provided resource dir. -/// -/// Note that the module map used for Glibc depends on the target we're -/// compiling for, and is not included in the resource directory with the other -/// implicit module maps. It's at {freebsd|linux}/{arch}/glibc.modulemap. -Optional getGlibcModuleMapPath(SearchPathOptions &Opts, - llvm::Triple triple, - SmallVectorImpl &buffer); - /// On Linux, some platform libraries (glibc, libstdc++) are not modularized. /// We inject modulemaps for those libraries into their include directories /// to allow using them from Swift. -SmallVector, 16> +SmallVector, 2> getClangInvocationFileMapping(ASTContext &ctx); } // namespace swift