From f3790def639b9801989dae4154b52f2625504f90 Mon Sep 17 00:00:00 2001 From: Egor Zhdan Date: Wed, 11 May 2022 23:29:35 +0100 Subject: [PATCH 1/2] [cxx-interop] Use VFS to inject modulemap into libstdc++ installation Previously the modulemap for the C++ stdlib on Linux was provided via `-fmodule-map-file=` Clang argument pointing to the modulemap file within the Swift toolchain. The modulemap file could not reference the stdlib headers directly, since the exact stdlib include directory varies across Linux versions (it generally looks like `/usr/include/c++/{gcc_version}`). So the modulemap file instead referenced a local header, which `#include <>`-ed the stdlib headers, relying on Clang include resolution. Unfortunately this did not work properly in the presence of another C++ module which included the stdlib headers: sometimes decls from the stdlib were hijacked by the other module, and were not treated as a part of the stdlib by Clang. This caused compile errors in Swift. This change uses LLVM VFS to inject the modulemap file into the libstdc++ directory. The modulemap file is now able to reference the stdlib headers directly, which fixes the issue. Credits to Rintaro Ishizaki for proposing a similar idea for SwiftGlibc back in 2016. --- lib/ClangImporter/ClangImporter.cpp | 98 ++++++++++++++++++-- stdlib/public/Cxx/libstdcxx.h | 136 ++++++++++++++-------------- 2 files changed, 157 insertions(+), 77 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 4dc428ca62ae1..8ed7b8c89a3eb 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -73,6 +73,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/YAMLParser.h" #include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/VirtualFileSystem.h" #include #include @@ -693,14 +694,6 @@ importer::getNormalInvocationArguments( } else { // FIXME: Emit a warning of some kind. } - - if (EnableCXXInterop) { - if (auto path = - getLibStdCxxModuleMapPath(searchPathOpts, triple, buffer)) { - invocationArgStrs.push_back( - (Twine("-fmodule-map-file=") + *path).str()); - } - } } if (searchPathOpts.getSDKPath().empty()) { @@ -870,6 +863,92 @@ importer::addCommonInvocationArguments( } } +/// 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. +static SmallVector> +getClangInvocationFileMapping(ASTContext &ctx) { + using Path = SmallString<128>; + + const llvm::Triple &triple = ctx.LangOpts.Target; + // We currently only need this when building for Linux. + if (!triple.isOSLinux()) + return {}; + + SearchPathOptions &searchPathOpts = ctx.SearchPathOpts; + + Path sdkPath(searchPathOpts.getSDKPath()); + if (sdkPath.empty()) + sdkPath = "/"; + + // Currently only a modulemap for libstdc++ is injected. + if (!ctx.LangOpts.EnableCXXInterop) + return {}; + + Path actualModuleMapPath; + Path buffer; + if (auto path = getLibStdCxxModuleMapPath(searchPathOpts, triple, buffer)) + actualModuleMapPath = path.getValue(); + else + 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 libstdcxx.h header and reference all libstdc++ headers + // directly from the modulemap. + Path actualHeaderPath = actualModuleMapPath; + llvm::sys::path::remove_filename(actualHeaderPath); + llvm::sys::path::append(actualHeaderPath, "libstdcxx.h"); + + Path cxxStdlibsRoot(sdkPath); + llvm::sys::path::append(cxxStdlibsRoot, "usr", "include", "c++"); + if (!llvm::sys::fs::exists(cxxStdlibsRoot)) + return {}; + + // Collect all installed versions of libstdc++. We currently have no way to + // know which libstdc++ version will be used for this Clang invocation. + // TODO: extract this information from the Clang driver. + SmallVector cxxStdlibDirs; + std::error_code errorCode; + for (llvm::vfs::directory_iterator + iter = ctx.SourceMgr.getFileSystem()->dir_begin(cxxStdlibsRoot, + errorCode), + endIter; + !errorCode && iter != endIter; iter = iter.increment(errorCode)) { + cxxStdlibDirs.push_back(Path(iter->path())); + } + + SmallVector> result; + // Inject a modulemap into the VFS for each of the libstdc++ versions. + for (const Path &cxxStdlibDir : cxxStdlibDirs) { + // 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)) + continue; + + Path injectedModuleMapPath = cxxStdlibDir; + llvm::sys::path::append(injectedModuleMapPath, "module.modulemap"); + if (llvm::sys::fs::exists(injectedModuleMapPath)) + continue; + + Path injectedHeaderPath = cxxStdlibDir; + llvm::sys::path::append(injectedHeaderPath, "libstdcxx.h"); + + result.push_back( + {std::string(injectedModuleMapPath), std::string(actualModuleMapPath)}); + result.push_back( + {std::string(injectedHeaderPath), std::string(actualHeaderPath)}); + } + return result; +} + bool ClangImporter::canReadPCH(StringRef PCHFilename) { if (!llvm::sys::fs::exists(PCHFilename)) return false; @@ -1122,9 +1201,10 @@ ClangImporter::create(ASTContext &ctx, } } + auto fileMapping = getClangInvocationFileMapping(ctx); // Wrap Swift's FS to allow Clang to override the working directory llvm::IntrusiveRefCntPtr VFS = - llvm::vfs::RedirectingFileSystem::create({}, true, + llvm::vfs::RedirectingFileSystem::create(fileMapping, true, *ctx.SourceMgr.getFileSystem()); // Create a new Clang compiler invocation. diff --git a/stdlib/public/Cxx/libstdcxx.h b/stdlib/public/Cxx/libstdcxx.h index 5083a9c356b32..388398095da36 100644 --- a/stdlib/public/Cxx/libstdcxx.h +++ b/stdlib/public/Cxx/libstdcxx.h @@ -1,79 +1,79 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "algorithm" +#include "bitset" +#include "complex" +#include "deque" +#include "exception" +#include "fstream" +#include "functional" +#include "iomanip" +#include "ios" +#include "iosfwd" +#include "iostream" +#include "istream" +#include "iterator" +#include "limits" +#include "list" +#include "locale" +#include "map" +#include "memory" +#include "new" +#include "numeric" +#include "ostream" +#include "queue" +#include "set" +#include "sstream" +#include "stack" +#include "stdexcept" +#include "streambuf" +#include "string" +#include "utility" +#include "typeinfo" +#include "valarray" +#include "vector" +#include "array" +#include "atomic" +#include "chrono" +#include "codecvt" +#include "condition_variable" +#include "forward_list" +#include "future" +#include "initializer_list" +#include "mutex" +#include "random" +#include "ratio" +#include "regex" +#include "scoped_allocator" +#include "system_error" +#include "thread" +#include "tuple" +#include "typeindex" +#include "type_traits" +#include "unordered_map" +#include "unordered_set" // C++17 and newer: -#if __has_include() -#include +#if __has_include("any") +#include "any" #endif -#if __has_include() -#include +#if __has_include("charconv") +#include "charconv" #endif -#if __has_include() -#include +#if __has_include("execution") +#include "execution" #endif -#if __has_include() -#include +#if __has_include("filesystem") +#include "filesystem" #endif -#if __has_include() -#include +#if __has_include("memory_resource") +#include "memory_resource" #endif -#if __has_include() -#include +#if __has_include("optional") +#include "optional" #endif -#if __has_include() -#include +#if __has_include("string_view") +#include "string_view" #endif -#if __has_include() -#include +#if __has_include("variant") +#include "variant" #endif From 20650eab33457a59c9b014b4e6ca5d2e125cd451 Mon Sep 17 00:00:00 2001 From: Egor Zhdan Date: Fri, 13 May 2022 16:29:25 +0100 Subject: [PATCH 2/2] [cxx-interop] Add `cassert` header to libstdc++ modulemap Previously this header was sometimes getting hijacked by the first header to include it. This caused Swift compiler failures on Linux when using any LLVM header that uses `assert`. Now `cassert` is referenced directly from a modulemap, which fixes the issue. --- stdlib/public/Cxx/libstdcxx.modulemap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/stdlib/public/Cxx/libstdcxx.modulemap b/stdlib/public/Cxx/libstdcxx.modulemap index 5a2dbff03adac..d8200ec6cf2e0 100644 --- a/stdlib/public/Cxx/libstdcxx.modulemap +++ b/stdlib/public/Cxx/libstdcxx.modulemap @@ -20,4 +20,13 @@ module std { header "libstdcxx.h" requires cplusplus export * + + /// C compatibility headers. + module compat { + module cassert { + header "cassert" + requires cplusplus + export * + } + } }