Skip to content

Commit 8ed9e8b

Browse files
committed
[cxx-interop] Support compiling using a custom C++ stdlib
1 parent 83c9fb8 commit 8ed9e8b

36 files changed

+493
-17
lines changed

include/swift/AST/Decl.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
695695
HasAnyUnavailableDuringLoweringValues : 1
696696
);
697697

698-
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
698+
SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1,
699699
/// If the module is compiled as static library.
700700
StaticLibrary : 1,
701701

@@ -754,7 +754,12 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
754754
HasCxxInteroperability : 1,
755755

756756
/// Whether this module has been built with -experimental-allow-non-resilient-access.
757-
AllowNonResilientAccess : 1
757+
AllowNonResilientAccess : 1,
758+
759+
/// Whether this module has been built using a sealed C++ interoperability configuration
760+
/// that's broadly incompatible with default C++ interoperability support. For example, this is
761+
/// set for modules that use a custom libc++ with C++ interoperability.
762+
HasSealedCxxInteroperability : 1
758763
);
759764

760765
SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2,

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ WARNING(libstdcxx_modulemap_not_found, none,
129129
"module map for libstdc++ not found for '%0'; C++ stdlib may be unavailable",
130130
(StringRef))
131131

132+
ERROR(libcxx_custom_prohibits_system_cxxstdlib_module, none, "transitive import of system's C++ stdlib module '%0' prohibited in custom C++ stdlib mode", (StringRef))
133+
132134
WARNING(api_pattern_attr_ignored, none,
133135
"'%0' swift attribute ignored on type '%1': type is not copyable or destructible",
134136
(StringRef, StringRef))

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,10 @@ ERROR(need_cxx_interop_to_import_module,none,
931931
(Identifier))
932932
NOTE(enable_cxx_interop_docs,none,
933933
"visit https://www.swift.org/documentation/cxx-interop/project-build-setup to learn how to enable C++ interoperability", ())
934+
ERROR(need_custom_cxx_stdlib_to_import_module,none,
935+
"module %0 uses system's C++ standard library, but "
936+
"current compilation uses a custom C++ standard library",
937+
(Identifier))
934938

935939
ERROR(modularization_issue_decl_moved,Fatal,
936940
"reference to %select{top-level declaration|type}0 %1 broken by a context change; "

include/swift/AST/Module.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,16 @@ class ModuleDecl
689689
Bits.ModuleDecl.HasCxxInteroperability = enabled;
690690
}
691691

692+
/// Returns true if this module was built with sealed C++ interoperability
693+
/// configuration, that's broadly incompatible with default C++
694+
/// interoperability support.
695+
bool hasSealedCxxInteroperability() const {
696+
return Bits.ModuleDecl.HasSealedCxxInteroperability;
697+
}
698+
void setHasSealedCxxInteroperability(bool enabled = true) {
699+
Bits.ModuleDecl.HasSealedCxxInteroperability = enabled;
700+
}
701+
692702
/// \returns true if this module is a system module; note that the StdLib is
693703
/// considered a system module.
694704
bool isSystemModule() const {

include/swift/Basic/LangOptions.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ namespace swift {
328328
/// when importing Swift modules that enable C++ interoperability.
329329
bool RequireCxxInteropToImportCxxInteropModule = true;
330330

331+
/// Use a custom libc++ at the specified path when importing
332+
// and building Clang modules with C++ interoperability enabled.
333+
std::string cxxInteropCustomLibcxxPath;
334+
331335
/// On Darwin platforms, use the pre-stable ABI's mark bit for Swift
332336
/// classes instead of the stable ABI's bit. This is needed when
333337
/// targeting OSes prior to macOS 10.14.4 and iOS 12.2, where
@@ -763,6 +767,12 @@ namespace swift {
763767
return hashValue;
764768
}
765769

770+
/// Return true when using a custom C++ standard library for imported
771+
/// C and C++ modules.
772+
bool isUsingCustomCxxStdLib() const {
773+
return !cxxInteropCustomLibcxxPath.empty();
774+
}
775+
766776
private:
767777
llvm::SmallVector<std::string, 2> AtomicBitWidths;
768778
llvm::SmallVector<std::pair<PlatformConditionKind, std::string>, 10>

include/swift/Option/FrontendOptions.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,11 @@ def cxx_interop_disable_requirement_at_import :
939939
HelpText<"Do not require C++ interoperability to be enabled when importing a Swift module that enables C++ interoperability">,
940940
Flags<[FrontendOption, HelpHidden]>;
941941

942+
def cxx_interop_libcxx_path: JoinedOrSeparate<["-"], "clang-libcxx-path">,
943+
HelpText<"Use a custom libc++ as the C++ standard library when importing Clang modules">,
944+
MetaVarName<"<path>">,
945+
Flags<[FrontendOption, HelpHidden]>;
946+
942947
def use_malloc : Flag<["-"], "use-malloc">,
943948
HelpText<"Allocate internal data structures using malloc "
944949
"(for memory debugging)">;

include/swift/Serialization/Validation.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class ExtendedValidationInfo {
141141
unsigned IsConcurrencyChecked : 1;
142142
unsigned HasCxxInteroperability : 1;
143143
unsigned AllowNonResilientAccess: 1;
144+
unsigned HasSealedCxxInteroperability : 1;
144145
} Bits;
145146
public:
146147
ExtendedValidationInfo() : Bits() {}
@@ -235,6 +236,12 @@ class ExtendedValidationInfo {
235236
void setHasCxxInteroperability(bool val) {
236237
Bits.HasCxxInteroperability = val;
237238
}
239+
bool hasSealedCxxInteroperability() const {
240+
return Bits.HasSealedCxxInteroperability;
241+
}
242+
void setHasSealedCxxInteroperability(bool val) {
243+
Bits.HasSealedCxxInteroperability = val;
244+
}
238245
};
239246

240247
struct SearchPath {

lib/AST/Module.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx,
723723
Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0;
724724
Bits.ModuleDecl.HasCxxInteroperability = 0;
725725
Bits.ModuleDecl.AllowNonResilientAccess = 0;
726+
Bits.ModuleDecl.HasSealedCxxInteroperability = 0;
726727
}
727728

728729
void ModuleDecl::setIsSystemModule(bool flag) {

lib/ClangImporter/ClangImporter.cpp

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,24 @@ void importer::getNormalInvocationArguments(
506506
"-isystem", searchPathOpts.RuntimeResourcePath,
507507
});
508508

509+
if (ctx.LangOpts.isUsingCustomCxxStdLib()) {
510+
// Add a define that ensures Clang differentiates between imported Clang
511+
// modules with a
512+
// custom libc++ path and regular Clang modules that use system's C++
513+
// standard library.
514+
invocationArgStrs.insert(invocationArgStrs.end(),
515+
{"-D__swift_use_custom_libcxx__"});
516+
517+
// Add a placeholder custom C++ stdlib directory before other system include
518+
// directories. The importer will substitute the actual directory value
519+
// after preloading any modules that are incompatible with the custom C++
520+
// stdlib. It's important to put it before other system include directories
521+
// as even with -nostdinc++, platforms like Windows might still keep the
522+
// search paths for system's C++ headers active in the Clang instance.
523+
invocationArgStrs.insert(invocationArgStrs.end(),
524+
{"-isystem<placeholder-custom-cxx-stdlib-dir>"});
525+
}
526+
509527
// Enable Position Independence. `-fPIC` is not supported on Windows, which
510528
// is implicitly position independent.
511529
if (!triple.isOSWindows())
@@ -897,6 +915,12 @@ importer::addCommonInvocationArguments(
897915
invocationArgStrs.push_back("-fbuild-session-file=" + importerOpts.BuildSessionFilePath);
898916
}
899917

918+
if (ctx.LangOpts.isUsingCustomCxxStdLib()) {
919+
// Do not add system's C++ standard library include paths when using
920+
// a custom C++ standard libary.
921+
invocationArgStrs.push_back("-nostdinc++");
922+
}
923+
900924
for (auto extraArg : importerOpts.ExtraArgs) {
901925
invocationArgStrs.push_back(extraArg);
902926
}
@@ -1297,11 +1321,29 @@ ClangImporter::create(ASTContext &ctx,
12971321
return nullptr;
12981322
}
12991323

1324+
llvm::SmallVector<StringRef, 2> extraPreImports;
1325+
// Prebuild 'SwiftShims' before using a custom C++ stdlib, to
1326+
// ensure that SwiftShims won't depend on the custom C++ stdlib.
1327+
if (ctx.LangOpts.isUsingCustomCxxStdLib()) {
1328+
extraPreImports.push_back("SwiftShims");
1329+
// On Windows, prebuild 'ucrt' before using a custom C++ stdlib,
1330+
// to ensure there's no circular dependencies between the two
1331+
// modules.
1332+
if (ctx.LangOpts.Target.isOSWindows())
1333+
extraPreImports.push_back("ucrt");
1334+
}
1335+
13001336
{
13011337
// Create an almost-empty memory buffer.
1302-
auto sourceBuffer = llvm::MemoryBuffer::getMemBuffer(
1303-
"extern int __swift __attribute__((unavailable));",
1304-
Implementation::moduleImportBufferName);
1338+
SmallString<128> defaultBuffer(
1339+
"extern int __swift __attribute__((unavailable));");
1340+
for (const auto &import : extraPreImports) {
1341+
defaultBuffer.append("\n#pragma clang module import ");
1342+
defaultBuffer.append(import);
1343+
defaultBuffer.push_back('\n');
1344+
}
1345+
auto sourceBuffer = llvm::MemoryBuffer::getMemBufferCopy(
1346+
defaultBuffer, Implementation::moduleImportBufferName);
13051347
clang::PreprocessorOptions &ppOpts =
13061348
importer->Impl.Invocation->getPreprocessorOpts();
13071349
ppOpts.addRemappedFile(Implementation::moduleImportBufferName,
@@ -1481,6 +1523,34 @@ ClangImporter::create(ASTContext &ctx,
14811523
auto *CB = new HeaderImportCallbacks(importer->Impl);
14821524
clangPP.addPPCallbacks(std::unique_ptr<clang::PPCallbacks>(CB));
14831525

1526+
if (ctx.LangOpts.isUsingCustomCxxStdLib()) {
1527+
// Adjust the header search options to point to the custom C++ stdlib
1528+
// include directory instead of the placeholder custom C++ stdlib, after
1529+
// making sure some incompatible requirements are prebuilt already.
1530+
bool hasAdjusted = false;
1531+
for (auto &E : instance.getHeaderSearchOpts().UserEntries) {
1532+
if (E.Path == "<placeholder-custom-cxx-stdlib-dir>") {
1533+
E.Path = ctx.LangOpts.cxxInteropCustomLibcxxPath;
1534+
hasAdjusted = true;
1535+
}
1536+
}
1537+
(void)hasAdjusted; // in case of no asserts.
1538+
assert(hasAdjusted && "unable to set custom c++ stdlib include path");
1539+
// Recreate the header search info for the current instance to match
1540+
// the updated header search options.
1541+
clang::ApplyHeaderSearchOptions(
1542+
clangPP.getHeaderSearchInfo(), instance.getHeaderSearchOpts(),
1543+
instance.getLangOpts(), instance.getTarget().getTriple());
1544+
1545+
if (importerOpts.DumpClangDiagnostics) {
1546+
llvm::errs()
1547+
<< "Adjusted include paths to account for custom C++ stdlib:";
1548+
for (auto &E : instance.getHeaderSearchOpts().UserEntries)
1549+
llvm::errs() << " '" << E.Path << "'";
1550+
llvm::errs() << "\n";
1551+
}
1552+
}
1553+
14841554
// Create the selectors we'll be looking for.
14851555
auto &clangContext = importer->Impl.Instance->getASTContext();
14861556
importer->Impl.objectAtIndexedSubscript
@@ -2274,6 +2344,10 @@ ModuleDecl *ClangImporter::Implementation::loadModule(
22742344
path.front().Item.str().starts_with("std_"))
22752345
return nullptr;
22762346
if (path.front().Item == ctx.Id_CxxStdlib) {
2347+
// The 'CxxStdlib' module is not importable when using a custom
2348+
// C++ standard libary.
2349+
if (ctx.LangOpts.isUsingCustomCxxStdLib())
2350+
return nullptr;
22772351
ImportPath::Builder adjustedPath(ctx.getIdentifier("std"), importLoc);
22782352
adjustedPath.append(path.getSubmodulePath());
22792353
path = adjustedPath.copyTo(ctx).getModulePath(ImportKind::Module);
@@ -2640,6 +2714,27 @@ ClangModuleUnit *ClangImporter::Implementation::getWrapperForModule(
26402714
if (ClangModuleUnit *cached = cacheEntry.getPointer())
26412715
return cached;
26422716

2717+
// Ensure system's C++ stdlib modules aren't imported when
2718+
// using a custom C++ standard library. This is especially important for
2719+
// Windows with MSVC STL, as the MSVC C++ module is located in a directory
2720+
// shared with other system modules, and thus could be still found using
2721+
// Clang's header search. However, MSVC's 'std_config' module is except from
2722+
// this check as it contains some common config macros that are needed by
2723+
// other parts of the SDK.
2724+
if (SwiftContext.LangOpts.isUsingCustomCxxStdLib() &&
2725+
isCxxStdModule(underlying) &&
2726+
!(SwiftContext.LangOpts.Target.isOSWindows() &&
2727+
underlying->Name == "std_config")) {
2728+
auto maybeCustomDir = Instance->getFileManager().getOptionalDirectoryRef(
2729+
SwiftContext.LangOpts.cxxInteropCustomLibcxxPath);
2730+
if (underlying->Directory.has_value() && maybeCustomDir &&
2731+
!underlying->Directory->isSameRef(*maybeCustomDir)) {
2732+
diagnose(SourceLoc(),
2733+
diag::libcxx_custom_prohibits_system_cxxstdlib_module,
2734+
underlying->Name);
2735+
}
2736+
}
2737+
26432738
// FIXME: Handle hierarchical names better.
26442739
Identifier name = underlying->Name == "std"
26452740
? SwiftContext.Id_CxxStdlib

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,9 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
12411241
Opts.CxxInteropGettersSettersAsProperties = Args.hasArg(OPT_cxx_interop_getters_setters_as_properties);
12421242
Opts.RequireCxxInteropToImportCxxInteropModule =
12431243
!Args.hasArg(OPT_cxx_interop_disable_requirement_at_import);
1244+
if (const auto *A = Args.getLastArg(OPT_cxx_interop_libcxx_path)) {
1245+
Opts.cxxInteropCustomLibcxxPath = A->getValue();
1246+
}
12441247

12451248
Opts.VerifyAllSubstitutionMaps |= Args.hasArg(OPT_verify_all_substitution_maps);
12461249

lib/Frontend/Frontend.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,8 +1397,19 @@ ModuleDecl *CompilerInstance::getMainModule() const {
13971397
if (Invocation.getLangOptions().isSwiftVersionAtLeast(6))
13981398
MainModule->setIsConcurrencyChecked(true);
13991399
if (Invocation.getLangOptions().EnableCXXInterop &&
1400-
Invocation.getLangOptions().RequireCxxInteropToImportCxxInteropModule)
1401-
MainModule->setHasCxxInteroperability();
1400+
Invocation.getLangOptions().RequireCxxInteropToImportCxxInteropModule) {
1401+
// By default, mark this module as using C++ interoperability, which
1402+
// will require users to also turn on C++ interoperability.
1403+
if (Invocation.getLangOptions().RequireCxxInteropToImportCxxInteropModule)
1404+
MainModule->setHasCxxInteroperability();
1405+
// Using C++ interoperability with a custom C++ stdlib enforces
1406+
// a best-effort seal that prevents the use of C++ types accross module
1407+
// boundaries, while ensuring that type layout from such module will
1408+
// not be constructed incorrectly without correct C++ libraries when used
1409+
// accross a non-resilient module boundary.
1410+
if (Invocation.getLangOptions().isUsingCustomCxxStdLib())
1411+
MainModule->setHasSealedCxxInteroperability();
1412+
}
14021413
if (Invocation.getLangOptions().AllowNonResilientAccess)
14031414
MainModule->setAllowNonResilientAccess();
14041415

lib/IRGen/GenDecl.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,13 @@ void IRGenModule::emitSourceFile(SourceFile &SF) {
487487
// (std) if available.
488488
if (Context.LangOpts.EnableCXXInterop) {
489489
const llvm::Triple &target = Context.LangOpts.Target;
490-
if (target.isOSDarwin())
491-
this->addLinkLibrary(LinkLibrary("c++", LibraryKind::Library));
492-
else if (target.isOSLinux())
493-
this->addLinkLibrary(LinkLibrary("stdc++", LibraryKind::Library));
490+
bool useCustomCxxLib = Context.LangOpts.isUsingCustomCxxStdLib();
491+
if (!useCustomCxxLib) {
492+
if (target.isOSDarwin())
493+
this->addLinkLibrary(LinkLibrary("c++", LibraryKind::Library));
494+
else if (target.isOSLinux())
495+
this->addLinkLibrary(LinkLibrary("stdc++", LibraryKind::Library));
496+
}
494497

495498
// Do not try to link Cxx with itself.
496499
if (!getSwiftModule()->getName().is("Cxx")) {
@@ -504,11 +507,13 @@ void IRGenModule::emitSourceFile(SourceFile &SF) {
504507
}
505508

506509
// Do not try to link CxxStdlib with the C++ standard library, Cxx or
507-
// itself.
510+
// itself. Also, do not link CxxStdlib when using a custom C++
511+
// standard library.
508512
if (llvm::none_of(llvm::ArrayRef{"Cxx", "CxxStdlib", "std"},
509513
[M = getSwiftModule()->getName().str()](StringRef Name) {
510514
return M == Name;
511-
})) {
515+
}) &&
516+
!useCustomCxxLib) {
512517
// Only link with CxxStdlib on platforms where the overlay is available.
513518
switch (target.getOS()) {
514519
case llvm::Triple::Linux:

lib/Sema/TypeCheckAccess.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1989,9 +1989,11 @@ swift::getDisallowedOriginKind(const Decl *decl,
19891989
}
19901990

19911991
// C++ APIs do not support library evolution.
1992+
// FIXME: switch to seal?
19921993
if (SF->getASTContext().LangOpts.EnableCXXInterop && where.getDeclContext() &&
19931994
where.getDeclContext()->getAsDecl() &&
1994-
where.getDeclContext()->getAsDecl()->getModuleContext()->isResilient() &&
1995+
(where.getDeclContext()->getAsDecl()->getModuleContext()->isResilient() ||
1996+
!SF->getASTContext().LangOpts.cxxInteropCustomLibcxxPath.empty()) &&
19951997
decl->hasClangNode() && !decl->getModuleContext()->isSwiftShimsModule() &&
19961998
isFragileClangNode(decl->getClangNode()))
19971999
return DisallowedOriginKind::FragileCxxAPI;

lib/Serialization/Deserialization.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8102,13 +8102,18 @@ void ModuleFile::loadAllMembers(Decl *container, uint64_t contextData) {
81028102

81038103
SmallVector<Decl *, 16> members;
81048104
members.reserve(rawMemberIDs.size());
8105+
bool hasSealed =
8106+
container->getModuleContext()->hasSealedCxxInteroperability();
8107+
bool reportDeserializationErrors =
8108+
(hasSealed && !isa<ClassDecl>(container)) ||
8109+
!getContext().LangOpts.EnableDeserializationRecovery;
81058110
for (DeclID rawID : rawMemberIDs) {
81068111
Expected<Decl *> next = getDeclChecked(rawID);
81078112
if (next) {
81088113
assert(next.get() && "unchecked error deserializing next member");
81098114
members.push_back(next.get());
81108115
} else {
8111-
if (!getContext().LangOpts.EnableDeserializationRecovery)
8116+
if (reportDeserializationErrors)
81128117
fatal(next.takeError());
81138118

81148119
Decl *suppliedMissingMember = handleErrorAndSupplyMissingMember(

lib/Serialization/ModuleFile.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,12 @@ class ModuleFile
646646
return Core->Bits.HasCxxInteroperability;
647647
}
648648

649+
/// Whether this module was built with sealed C++ interoperability enabled,
650+
/// that's broadly incompatible with default C++ interoperability support.
651+
bool hasSealedCxxInteroperability() const {
652+
return Core->Bits.HasSealedCxxInteroperability;
653+
}
654+
649655
/// Whether the module is resilient. ('-enable-library-evolution')
650656
ResilienceStrategy getResilienceStrategy() const {
651657
return ResilienceStrategy(Core->Bits.ResilienceStrategy);

0 commit comments

Comments
 (0)