Skip to content

Commit

Permalink
[cxx-interop] Allow compiling with libc++ on Linux
Browse files Browse the repository at this point in the history
This makes sure that Swift respects `-Xcc -stdlib=libc++` flags.

Clang already has existing logic to discover the system-wide libc++ installation on Linux. We rely on that logic here.

Importing a Swift module that was built with a different C++ stdlib is not supported and emits an error.

The Cxx module can be imported when compiling with any C++ stdlib. The synthesized conformances, e.g. to CxxRandomAccessCollection also work. However, CxxStdlib currently cannot be imported when compiling with libc++, since on Linux it refers to symbols from libstdc++ which have different mangled names in libc++.

rdar://118357548 / #69825
  • Loading branch information
egorzhdan committed Aug 8, 2024
1 parent 536a889 commit dedf76c
Show file tree
Hide file tree
Showing 31 changed files with 352 additions and 28 deletions.
6 changes: 5 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
HasAnyUnavailableDuringLoweringValues : 1
);

SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+2,
/// If the module is compiled as static library.
StaticLibrary : 1,

Expand Down Expand Up @@ -780,6 +780,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
/// Whether this module has been built with C++ interoperability enabled.
HasCxxInteroperability : 1,

/// Whether this module uses the platform default C++ stdlib, or an
/// overridden C++ stdlib.
CXXStdlibKind : 2,

/// Whether this module has been built with -allow-non-resilient-access.
AllowNonResilientAccess : 1,

Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/DiagnosticEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace swift {
class ValueDecl;
class SourceFile;

enum class CXXStdlibKind : uint8_t;
enum class DescriptivePatternKind : uint8_t;
enum class SelfAccessKind : uint8_t;
enum class ReferenceOwnership : uint8_t;
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,10 @@ ERROR(need_cxx_interop_to_import_module,none,
NOTE(enable_cxx_interop_docs,none,
"visit https://www.swift.org/documentation/cxx-interop/project-build-setup to learn how to enable C++ interoperability", ())

ERROR(cxx_stdlib_kind_mismatch,none,
"module %0 was built with %1, but current compilation uses %2",
(Identifier, StringRef, StringRef))

ERROR(modularization_issue_decl_moved,Fatal,
"reference to %select{top-level declaration|type}0 %1 broken by a context change; "
"%1 was expected to be in %2, but now a candidate is found only in %3",
Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "swift/AST/Type.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/BasicSourceInfo.h"
#include "swift/Basic/CXXStdlibKind.h"
#include "swift/Basic/Compiler.h"
#include "swift/Basic/Debug.h"
#include "swift/Basic/OptionSet.h"
Expand Down Expand Up @@ -705,6 +706,13 @@ class ModuleDecl
Bits.ModuleDecl.HasCxxInteroperability = enabled;
}

CXXStdlibKind getCXXStdlibKind() const {
return static_cast<CXXStdlibKind>(Bits.ModuleDecl.CXXStdlibKind);
}
void setCXXStdlibKind(CXXStdlibKind kind) {
Bits.ModuleDecl.CXXStdlibKind = static_cast<uint8_t>(kind);
}

/// \returns true if this module is a system module; note that the StdLib is
/// considered a system module.
bool isSystemModule() const {
Expand Down
3 changes: 2 additions & 1 deletion include/swift/AST/ModuleDependencies.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/AST/Import.h"
#include "swift/AST/LinkLibrary.h"
#include "swift/AST/SearchPathOptions.h"
#include "swift/Basic/CXXStdlibKind.h"
#include "swift/Basic/LLVM.h"
#include "clang/CAS/CASOptions.h"
#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
Expand Down Expand Up @@ -135,7 +136,7 @@ void registerBackDeployLibraries(
std::function<void(const LinkLibrary &)> RegistrationCallback);
void registerCxxInteropLibraries(
const llvm::Triple &Target, StringRef mainModuleName, bool hasStaticCxx,
bool hasStaticCxxStdlib,
bool hasStaticCxxStdlib, CXXStdlibKind cxxStdlibKind,
std::function<void(const LinkLibrary &)> RegistrationCallback);
} // namespace dependencies

Expand Down
48 changes: 48 additions & 0 deletions include/swift/Basic/CXXStdlibKind.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//===--- CXXStdlibKind.h ----------------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_CXX_STDLIB_KIND_H
#define SWIFT_BASIC_CXX_STDLIB_KIND_H

namespace swift {

enum class CXXStdlibKind : uint8_t {
Unknown = 0,

/// libc++ is the default C++ stdlib on Darwin platforms. It is also supported
/// on Linux when explicitly requested via `-Xcc -stdlib=libc++` flag.
Libcxx = 1,

/// libstdc++ is the default C++ stdlib on most Linux distributions.
Libstdcxx = 2,

/// msvcprt is used when targeting Windows.
Msvcprt = 3,
};

inline std::string to_string(CXXStdlibKind kind) {
switch (kind) {
case CXXStdlibKind::Unknown:
return "unknown C++ stdlib";
case CXXStdlibKind::Libcxx:
return "libc++";
case CXXStdlibKind::Libstdcxx:
return "libstdc++";
case CXXStdlibKind::Msvcprt:
return "msvcprt";
}
llvm_unreachable("unhandled CXXStdlibKind");
}

} // namespace swift

#endif // SWIFT_BASIC_CXX_STDLIB_KIND_H
11 changes: 11 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#define SWIFT_BASIC_LANGOPTIONS_H

#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/Basic/CXXStdlibKind.h"
#include "swift/Basic/Feature.h"
#include "swift/Basic/FixedBitSet.h"
#include "swift/Basic/FunctionBodySkipping.h"
Expand Down Expand Up @@ -321,6 +322,16 @@ namespace swift {
void setCxxInteropFromArgs(llvm::opt::ArgList &Args,
swift::DiagnosticEngine &Diags);

/// The C++ standard library used for the current build. This can differ
/// from the default C++ stdlib on a particular platform when `-Xcc
/// -stdlib=xyz` was passed to the compiler.
CXXStdlibKind CXXStdlib = CXXStdlibKind::Unknown;
CXXStdlibKind PlatformDefaultCXXStdlib = CXXStdlibKind::Unknown;

bool isUsingPlatformDefaultCXXStdlib() const {
return CXXStdlib == PlatformDefaultCXXStdlib;
}

bool CForeignReferenceTypes = false;

/// Imports getters and setters as computed properties.
Expand Down
24 changes: 24 additions & 0 deletions include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ namespace llvm {
class Triple;
class FileCollectorBase;
template<typename Fn> class function_ref;
namespace opt {
class InputArgList;
}
namespace vfs {
class FileSystem;
class OutputBackend;
Expand All @@ -51,6 +54,9 @@ namespace clang {
class DeclarationName;
class CompilerInvocation;
class TargetOptions;
namespace driver {
class Driver;
}
namespace tooling {
namespace dependencies {
struct ModuleDeps;
Expand All @@ -74,9 +80,11 @@ class EnumDecl;
class FuncDecl;
class ImportDecl;
class IRGenOptions;
class LangOptions;
class ModuleDecl;
struct ModuleDependencyID;
class NominalTypeDecl;
class SearchPathOptions;
class StructDecl;
class SwiftLookupTable;
class TypeDecl;
Expand Down Expand Up @@ -196,6 +204,22 @@ class ClangImporter final : public ClangModuleLoader {
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
const std::vector<std::string> &CC1Args);

/// Creates a Clang Driver based on the Swift compiler options.
///
/// \return a pair of the Clang Driver and the diagnostic engine, which needs
/// to be alive during the use of the Driver.
static std::pair<clang::driver::Driver,
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine>>
createClangDriver(
const LangOptions &LangOpts,
const ClangImporterOptions &ClangImporterOpts,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs = nullptr);

static llvm::opt::InputArgList
createClangArgs(const ClangImporterOptions &ClangImporterOpts,
const SearchPathOptions &SearchPathOpts,
clang::driver::Driver &clangDriver);

ClangImporter(const ClangImporter &) = delete;
ClangImporter(ClangImporter &&) = delete;
ClangImporter &operator=(const ClangImporter &) = delete;
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Frontend/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ class CompilerInvocation {
/// FIXME: Remove this after all the clients start sending it.
void setDefaultInProcessPluginServerPathIfNecessary();

/// Determine which C++ stdlib should be used for this compilation, and which
/// C++ stdlib is the default for the specified target.
void computeCXXStdlibOptions();

/// Computes the runtime resource path relative to the given Swift
/// executable.
static void computeRuntimeResourcePathFromExecutablePath(
Expand Down
9 changes: 9 additions & 0 deletions include/swift/Serialization/Validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define SWIFT_SERIALIZATION_VALIDATION_H

#include "swift/AST/Identifier.h"
#include "swift/Basic/CXXStdlibKind.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/Version.h"
#include "swift/Serialization/SerializationOptions.h"
Expand Down Expand Up @@ -141,6 +142,7 @@ class ExtendedValidationInfo {
unsigned IsAllowModuleWithCompilerErrorsEnabled : 1;
unsigned IsConcurrencyChecked : 1;
unsigned HasCxxInteroperability : 1;
unsigned CXXStdlibKind : 2;
unsigned AllowNonResilientAccess: 1;
unsigned SerializePackageEnabled: 1;
} Bits;
Expand Down Expand Up @@ -241,6 +243,13 @@ class ExtendedValidationInfo {
void setHasCxxInteroperability(bool val) {
Bits.HasCxxInteroperability = val;
}

CXXStdlibKind getCXXStdlibKind() const {
return static_cast<CXXStdlibKind>(Bits.CXXStdlibKind);
}
void setCXXStdlibKind(CXXStdlibKind kind) {
Bits.CXXStdlibKind = static_cast<uint8_t>(kind);
}
};

struct SearchPath {
Expand Down
1 change: 1 addition & 0 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
Bits.ModuleDecl.IsConcurrencyChecked = 0;
Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0;
Bits.ModuleDecl.HasCxxInteroperability = 0;
Bits.ModuleDecl.CXXStdlibKind = 0;
Bits.ModuleDecl.AllowNonResilientAccess = 0;
Bits.ModuleDecl.SerializePackageEnabled = 0;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/AST/ModuleDependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,11 @@ void
swift::dependencies::registerCxxInteropLibraries(
const llvm::Triple &Target,
StringRef mainModuleName,
bool hasStaticCxx, bool hasStaticCxxStdlib,
bool hasStaticCxx, bool hasStaticCxxStdlib, CXXStdlibKind cxxStdlibKind,
std::function<void(const LinkLibrary&)> RegistrationCallback) {
if (Target.isOSDarwin())
if (cxxStdlibKind == CXXStdlibKind::Libcxx)
RegistrationCallback(LinkLibrary("c++", LibraryKind::Library));
else if (Target.isOSLinux())
else if (cxxStdlibKind == CXXStdlibKind::Libstdcxx)
RegistrationCallback(LinkLibrary("stdc++", LibraryKind::Library));

// Do not try to link Cxx with itself.
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ModuleLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ void ModuleLoader::findOverlayFiles(SourceLoc diagLoc, ModuleDecl *module,
using namespace llvm::sys;
using namespace file_types;

// If an overlay for CxxStdlib was requested, only proceed if compiling with
// the platform-default C++ stdlib.
if (module->getName() == module->getASTContext().Id_CxxStdlib &&
!module->getASTContext().LangOpts.isUsingPlatformDefaultCXXStdlib())
return;

// If cross import information is passed on command-line, prefer use that.
auto &crossImports = module->getASTContext().SearchPathOpts.CrossImportInfo;
auto overlays = crossImports.find(module->getNameStr());
Expand Down
6 changes: 4 additions & 2 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4298,8 +4298,10 @@ ModuleDecl *ClangModuleUnit::getOverlayModule() const {
}
// If this Clang module is a part of the C++ stdlib, and we haven't loaded
// the overlay for it so far, it is a split libc++ module (e.g. std_vector).
// Load the CxxStdlib overlay explicitly.
if (!overlay && importer::isCxxStdModule(clangModule)) {
// Load the CxxStdlib overlay explicitly, if building with the
// platform-default C++ stdlib.
if (!overlay && importer::isCxxStdModule(clangModule) &&
Ctx.LangOpts.isUsingPlatformDefaultCXXStdlib()) {
ImportPath::Module::Builder builder(Ctx.Id_CxxStdlib);
overlay = owner.loadModule(SourceLoc(), std::move(builder).get());
}
Expand Down
51 changes: 33 additions & 18 deletions lib/ClangImporter/ClangIncludePaths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,18 @@ parseClangDriverArgs(const clang::driver::Driver &clangDriver,
return clangDriver.getOpts().ParseArgs(args, unused1, unused2);
}

static clang::driver::Driver
createClangDriver(const ASTContext &ctx,
const llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> &vfs) {
std::pair<clang::driver::Driver,
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine>>
ClangImporter::createClangDriver(
const LangOptions &LangOpts, const ClangImporterOptions &ClangImporterOpts,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {
auto *silentDiagConsumer = new clang::DiagnosticConsumer();
auto clangDiags = clang::CompilerInstance::createDiagnostics(
new clang::DiagnosticOptions());
clang::driver::Driver clangDriver(ctx.ClangImporterOpts.clangPath,
ctx.LangOpts.Target.str(), *clangDiags,
new clang::DiagnosticOptions(), silentDiagConsumer);
clang::driver::Driver clangDriver(ClangImporterOpts.clangPath,
LangOpts.Target.str(), *clangDiags,
"clang LLVM compiler", vfs);
return clangDriver;
return {std::move(clangDriver), clangDiags};
}

/// Given a list of include paths and a list of file names, finds the first
Expand Down Expand Up @@ -162,21 +165,23 @@ static std::optional<Path> findFirstIncludeDir(
return std::nullopt;
}

static llvm::opt::InputArgList
createClangArgs(const ASTContext &ctx, clang::driver::Driver &clangDriver) {
llvm::opt::InputArgList
ClangImporter::createClangArgs(const ClangImporterOptions &ClangImporterOpts,
const SearchPathOptions &SearchPathOpts,
clang::driver::Driver &clangDriver) {
// Flags passed to Swift with `-Xcc` might affect include paths.
std::vector<const char *> clangArgs;
for (const auto &each : ctx.ClangImporterOpts.ExtraArgs) {
for (const auto &each : ClangImporterOpts.ExtraArgs) {
clangArgs.push_back(each.c_str());
}
llvm::opt::InputArgList clangDriverArgs =
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();
auto sdkPath = SearchPathOpts.getSDKPath();
if (!sdkPath.empty())
clangDriver.SysRoot = sdkPath.str();
if (auto sysroot = ctx.SearchPathOpts.getSysRoot())
if (auto sysroot = SearchPathOpts.getSysRoot())
clangDriver.SysRoot = sysroot->str();
return clangDriverArgs;
}
Expand All @@ -188,8 +193,10 @@ getLibcFileMapping(ASTContext &ctx, StringRef modulemapFileName,
const llvm::Triple &triple = ctx.LangOpts.Target;

// Extract the libc path from Clang driver.
auto clangDriver = createClangDriver(ctx, vfs);
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
auto [clangDriver, clangDiagEngine] = ClangImporter::createClangDriver(
ctx.LangOpts, ctx.ClangImporterOpts, vfs);
auto clangDriverArgs = ClangImporter::createClangArgs(
ctx.ClangImporterOpts, ctx.SearchPathOpts, clangDriver);

llvm::opt::ArgStringList includeArgStrings;
const auto &clangToolchain =
Expand Down Expand Up @@ -256,10 +263,16 @@ static void getLibStdCxxFileMapping(
if (triple.isAndroid()
|| (triple.isMusl() && triple.getVendor() == llvm::Triple::Swift))
return;
// Make sure we are building with libstdc++. On platforms where libstdc++ is
// the default C++ stdlib, users can still compile with `-Xcc -stdlib=libc++`.
if (ctx.LangOpts.CXXStdlib != CXXStdlibKind::Libstdcxx)
return;

// Extract the libstdc++ installation path from Clang driver.
auto clangDriver = createClangDriver(ctx, vfs);
auto clangDriverArgs = createClangArgs(ctx, clangDriver);
auto [clangDriver, clangDiagEngine] = ClangImporter::createClangDriver(
ctx.LangOpts, ctx.ClangImporterOpts, vfs);
auto clangDriverArgs = ClangImporter::createClangArgs(
ctx.ClangImporterOpts, ctx.SearchPathOpts, clangDriver);

llvm::opt::ArgStringList stdlibArgStrings;
const auto &clangToolchain =
Expand Down Expand Up @@ -453,8 +466,10 @@ SmallVector<std::pair<std::string, std::string>, 2> GetWindowsFileMappings(
if (!Triple.isWindowsMSVCEnvironment())
return Mappings;

clang::driver::Driver Driver = createClangDriver(Context, driverVFS);
const llvm::opt::InputArgList Args = createClangArgs(Context, Driver);
auto [Driver, clangDiagEngine] = ClangImporter::createClangDriver(
Context.LangOpts, Context.ClangImporterOpts, driverVFS);
const llvm::opt::InputArgList Args = ClangImporter::createClangArgs(
Context.ClangImporterOpts, Context.SearchPathOpts, Driver);
const clang::driver::ToolChain &ToolChain = Driver.getToolChain(Args, Triple);
llvm::vfs::FileSystem &VFS = ToolChain.getVFS();

Expand Down
Loading

0 comments on commit dedf76c

Please sign in to comment.