diff --git a/include/swift/AST/DiagnosticsDriver.def b/include/swift/AST/DiagnosticsDriver.def index 52d864ca5fe52..b6b6a3cd1e484 100644 --- a/include/swift/AST/DiagnosticsDriver.def +++ b/include/swift/AST/DiagnosticsDriver.def @@ -186,6 +186,10 @@ ERROR(cannot_find_migration_script, none, ERROR(error_darwin_static_stdlib_not_supported, none, "-static-stdlib is no longer supported on Apple platforms", ()) +ERROR(error_darwin_only_supports_libcxx, none, + "The only C++ standard library supported on Apple platforms is libc++", + ()) + WARNING(warn_drv_darwin_sdk_invalid_settings, none, "SDK settings were ignored because 'SDKSettings.json' could not be parsed", ()) diff --git a/include/swift/Driver/ToolChain.h b/include/swift/Driver/ToolChain.h index cd8bebfdeceaa..cd55451a01c8b 100644 --- a/include/swift/Driver/ToolChain.h +++ b/include/swift/Driver/ToolChain.h @@ -294,6 +294,9 @@ class ToolChain { void getClangLibraryPath(const llvm::opt::ArgList &Args, SmallString<128> &LibPath) const; + // Returns the Clang driver executable to use for linking. + const char *getClangLinkerDriver(const llvm::opt::ArgList &Args) const; + /// Returns the name the clang library for a given sanitizer would have on /// the current toolchain. /// diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 226a26d64a367..ab1bcdd180b5f 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -552,6 +552,14 @@ def disable_direct_intramodule_dependencies : Flag<["-"], Flags<[FrontendOption, HelpHidden]>, HelpText<"Disable experimental dependency tracking that never cascades">; +def enable_experimental_cxx_interop : + Flag<["-"], "enable-experimental-cxx-interop">, + HelpText<"Allow importing C++ modules into Swift (experimental feature)">; + +def experimental_cxx_stdlib : + Separate<["-"], "experimental-cxx-stdlib">, + HelpText<"C++ standard library to use; forwarded to Clang's -stdlib flag">; + // Diagnostic control options def suppress_warnings : Flag<["-"], "suppress-warnings">, diff --git a/lib/Driver/DarwinToolChains.cpp b/lib/Driver/DarwinToolChains.cpp index a7153dc064e52..5d0ad84a348b0 100644 --- a/lib/Driver/DarwinToolChains.cpp +++ b/lib/Driver/DarwinToolChains.cpp @@ -791,6 +791,11 @@ toolchains::Darwin::constructInvocation(const DynamicLinkJobAction &job, Arguments.push_back("-arch"); Arguments.push_back(context.Args.MakeArgString(getTriple().getArchName())); + // On Darwin, we only support libc++. + if (context.Args.hasArg(options::OPT_enable_experimental_cxx_interop)) { + Arguments.push_back("-lc++"); + } + addArgsToLinkStdlib(Arguments, job, context); addProfileGenerationArgs(Arguments, context); @@ -934,6 +939,13 @@ toolchains::Darwin::validateArguments(DiagnosticEngine &diags, if (args.hasArg(options::OPT_static_stdlib)) { diags.diagnose(SourceLoc(), diag::error_darwin_static_stdlib_not_supported); } + + // If a C++ standard library is specified, it has to be libc++. + if (auto arg = args.getLastArg(options::OPT_experimental_cxx_stdlib)) { + if (StringRef(arg->getValue()) != "libc++") { + diags.diagnose(SourceLoc(), diag::error_darwin_only_supports_libcxx); + } + } } void diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp index 3e158c76274cf..2e1e320ebfe75 100644 --- a/lib/Driver/ToolChains.cpp +++ b/lib/Driver/ToolChains.cpp @@ -167,6 +167,17 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI, arguments.push_back("-disable-objc-interop"); } + // Add flags for C++ interop. + if (inputArgs.hasArg(options::OPT_enable_experimental_cxx_interop)) { + arguments.push_back("-enable-cxx-interop"); + } + if (const Arg *arg = + inputArgs.getLastArg(options::OPT_experimental_cxx_stdlib)) { + arguments.push_back("-Xcc"); + arguments.push_back( + inputArgs.MakeArgString(Twine("-stdlib=") + arg->getValue())); + } + // Handle the CPU and its preferences. inputArgs.AddLastArg(arguments, options::OPT_target_cpu); @@ -1330,6 +1341,26 @@ void ToolChain::getRuntimeLibraryPaths(SmallVectorImpl &runtimeLibP } } +const char *ToolChain::getClangLinkerDriver( + const llvm::opt::ArgList &Args) const { + // We don't use `clang++` unconditionally because we want to avoid pulling in + // a C++ standard library if it's not needed, in particular because the + // standard library that `clang++` selects by default may not be the one that + // is desired. + const char *LinkerDriver = + Args.hasArg(options::OPT_enable_experimental_cxx_interop) ? "clang++" + : "clang"; + if (const Arg *A = Args.getLastArg(options::OPT_tools_directory)) { + StringRef toolchainPath(A->getValue()); + + // If there is a linker driver in the toolchain folder, use that instead. + if (auto tool = llvm::sys::findProgramByName(LinkerDriver, {toolchainPath})) + LinkerDriver = Args.MakeArgString(tool.get()); + } + + return LinkerDriver; +} + bool ToolChain::sanitizerRuntimeLibExists(const ArgList &args, StringRef sanitizerName, bool shared) const { diff --git a/lib/Driver/UnixToolChains.cpp b/lib/Driver/UnixToolChains.cpp index 14a5e88a54de0..246ce6a8b7a2c 100644 --- a/lib/Driver/UnixToolChains.cpp +++ b/lib/Driver/UnixToolChains.cpp @@ -181,31 +181,9 @@ toolchains::GenericUnix::constructInvocation(const DynamicLinkJobAction &job, } // Configure the toolchain. - // - // By default use the system `clang` to perform the link. We use `clang` for - // the driver here because we do not wish to select a particular C++ runtime. - // Furthermore, until C++ interop is enabled, we cannot have a dependency on - // C++ code from pure Swift code. If linked libraries are C++ based, they - // should properly link C++. In the case of static linking, the user can - // explicitly specify the C++ runtime to link against. This is particularly - // important for platforms like android where as it is a Linux platform, the - // default C++ runtime is `libstdc++` which is unsupported on the target but - // as the builds are usually cross-compiled from Linux, libstdc++ is going to - // be present. This results in linking the wrong version of libstdc++ - // generating invalid binaries. It is also possible to use different C++ - // runtimes than the default C++ runtime for the platform (e.g. libc++ on - // Windows rather than msvcprt). When C++ interop is enabled, we will need to - // surface this via a driver flag. For now, opt for the simpler approach of - // just using `clang` and avoid a dependency on the C++ runtime. - const char *Clang = "clang"; if (const Arg *A = context.Args.getLastArg(options::OPT_tools_directory)) { StringRef toolchainPath(A->getValue()); - // If there is a clang in the toolchain folder, use that instead. - if (auto tool = llvm::sys::findProgramByName("clang", {toolchainPath})) { - Clang = context.Args.MakeArgString(tool.get()); - } - // Look for binutils in the toolchain folder. Arguments.push_back("-B"); Arguments.push_back(context.Args.MakeArgString(A->getValue())); @@ -307,6 +285,13 @@ toolchains::GenericUnix::constructInvocation(const DynamicLinkJobAction &job, } } + // Link against the desired C++ standard library. + if (const Arg *A = + context.Args.getLastArg(options::OPT_experimental_cxx_stdlib)) { + Arguments.push_back( + context.Args.MakeArgString(Twine("-stdlib=") + A->getValue())); + } + // Explicitly pass the target to the linker Arguments.push_back( context.Args.MakeArgString("--target=" + getTriple().str())); @@ -352,7 +337,7 @@ toolchains::GenericUnix::constructInvocation(const DynamicLinkJobAction &job, Arguments.push_back( context.Args.MakeArgString(context.Output.getPrimaryOutputFilename())); - InvocationInfo II{Clang, Arguments}; + InvocationInfo II{getClangLinkerDriver(context.Args), Arguments}; II.allowsResponseFiles = true; return II; diff --git a/lib/Driver/WindowsToolChains.cpp b/lib/Driver/WindowsToolChains.cpp index 749f0c902a80c..7cd26f17bc22d 100644 --- a/lib/Driver/WindowsToolChains.cpp +++ b/lib/Driver/WindowsToolChains.cpp @@ -84,32 +84,6 @@ toolchains::Windows::constructInvocation(const DynamicLinkJobAction &job, Arguments.push_back("/DEBUG"); } - // Configure the toolchain. - // - // By default use the system `clang` to perform the link. We use `clang` for - // the driver here because we do not wish to select a particular C++ runtime. - // Furthermore, until C++ interop is enabled, we cannot have a dependency on - // C++ code from pure Swift code. If linked libraries are C++ based, they - // should properly link C++. In the case of static linking, the user can - // explicitly specify the C++ runtime to link against. This is particularly - // important for platforms like android where as it is a Linux platform, the - // default C++ runtime is `libstdc++` which is unsupported on the target but - // as the builds are usually cross-compiled from Linux, libstdc++ is going to - // be present. This results in linking the wrong version of libstdc++ - // generating invalid binaries. It is also possible to use different C++ - // runtimes than the default C++ runtime for the platform (e.g. libc++ on - // Windows rather than msvcprt). When C++ interop is enabled, we will need to - // surface this via a driver flag. For now, opt for the simpler approach of - // just using `clang` and avoid a dependency on the C++ runtime. - const char *Clang = "clang"; - if (const Arg *A = context.Args.getLastArg(options::OPT_tools_directory)) { - StringRef toolchainPath(A->getValue()); - - // If there is a clang in the toolchain folder, use that instead. - if (auto tool = llvm::sys::findProgramByName("clang", {toolchainPath})) - Clang = context.Args.MakeArgString(tool.get()); - } - // Rely on `-libc` to correctly identify the MSVC Runtime Library. We use // `-nostartfiles` as that limits the difference to just the // `-defaultlib:libcmt` which is passed unconditionally with the `clang` @@ -159,6 +133,13 @@ toolchains::Windows::constructInvocation(const DynamicLinkJobAction &job, Arguments.push_back(context.Args.MakeArgString(context.OI.SDKPath)); } + // Link against the desired C++ standard library. + if (const Arg *A = + context.Args.getLastArg(options::OPT_experimental_cxx_stdlib)) { + Arguments.push_back(context.Args.MakeArgString( + Twine("-stdlib=") + A->getValue())); + } + if (job.getKind() == LinkKind::Executable) { if (context.OI.SelectedSanitizers & SanitizerKind::Address) addLinkRuntimeLib(context.Args, Arguments, @@ -196,7 +177,7 @@ toolchains::Windows::constructInvocation(const DynamicLinkJobAction &job, Arguments.push_back( context.Args.MakeArgString(context.Output.getPrimaryOutputFilename())); - InvocationInfo II{Clang, Arguments}; + InvocationInfo II{getClangLinkerDriver(context.Args), Arguments}; II.allowsResponseFiles = true; return II; diff --git a/test/Driver/cxx_interop.swift b/test/Driver/cxx_interop.swift new file mode 100644 index 0000000000000..7795e80b7777b --- /dev/null +++ b/test/Driver/cxx_interop.swift @@ -0,0 +1,10 @@ +// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.9 %s -enable-experimental-cxx-interop 2>^1 | %FileCheck -check-prefix ENABLE %s + +// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.9 %s -enable-experimental-cxx-interop -experimental-cxx-stdlib libc++ 2>^1 | %FileCheck -check-prefix STDLIB %s + +// ENABLE: swift +// ENABLE: -enable-cxx-interop + +// STDLIB: swift +// STDLIB-DAG: -enable-cxx-interop +// STDLIB-DAG: -Xcc -stdlib=libc++ diff --git a/test/Driver/linker.swift b/test/Driver/linker.swift index 991963830c960..339a3b964540d 100644 --- a/test/Driver/linker.swift +++ b/test/Driver/linker.swift @@ -101,6 +101,21 @@ // INFERRED_NAMED_DARWIN tests above: 'libLINKER.dylib'. // RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-macosx10.9 -emit-library %s -o libLINKER.dylib | %FileCheck -check-prefix INFERRED_NAME_DARWIN %s +// On Darwin, when C++ interop is turned on, we link against libc++ explicitly +// regardless of whether -experimental-cxx-stdlib is specified or not. So also +// run a test where C++ interop is turned off to make sure we don't link +// against libc++ in this case. +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-ios7.1 %s 2>&1 | %FileCheck -check-prefix IOS-no-cxx-interop %s +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-ios7.1 -enable-experimental-cxx-interop %s 2>&1 | %FileCheck -check-prefix IOS-cxx-interop-libcxx %s +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-ios7.1 -enable-experimental-cxx-interop -experimental-cxx-stdlib libc++ %s 2>&1 | %FileCheck -check-prefix IOS-cxx-interop-libcxx %s +// RUN: not %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-ios7.1 -enable-experimental-cxx-interop -experimental-cxx-stdlib libstdc++ %s 2>&1 | %FileCheck -check-prefix IOS-cxx-interop-libstdcxx %s + +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-unknown-linux-gnu -enable-experimental-cxx-interop %s 2>&1 | %FileCheck -check-prefix LINUX-cxx-interop %s +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-unknown-linux-gnu -enable-experimental-cxx-interop -experimental-cxx-stdlib libc++ %s 2>&1 | %FileCheck -check-prefix LINUX-cxx-interop-libcxx %s + +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-unknown-windows-msvc -enable-experimental-cxx-interop %s 2>&1 | %FileCheck -check-prefix WINDOWS-cxx-interop %s +// RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-unknown-windows-msvc -enable-experimental-cxx-interop -experimental-cxx-stdlib libc++ %s 2>&1 | %FileCheck -check-prefix WINDOWS-cxx-interop-libcxx %s + // Check reading the SDKSettings.json from an SDK // RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-macosx10.9 -sdk %S/Inputs/MacOSX10.15.versioned.sdk %s 2>&1 | %FileCheck -check-prefix MACOS_10_15 %s // RUN: %swiftc_driver -sdk "" -driver-print-jobs -target x86_64-apple-macosx10.9 -sdk %S/Inputs/MacOSX10.15.4.versioned.sdk %s 2>&1 | %FileCheck -check-prefix MACOS_10_15_4 %s @@ -424,6 +439,49 @@ // INFERRED_NAME_WINDOWS: -o LINKER.dll // INFERRED_NAME_WASI: -o libLINKER.so +// Instead of a single "NOT" check for this run, we would really want to check +// for all of the driver arguments that we _do_ expect, and then use an +// --implicit-check-not to check that -lc++ doesn't occur. +// However, --implicit-check-not has a bug where it fails to flag the +// unexpected text when it occurs after text matched by a CHECK-DAG; see +// https://bugs.llvm.org/show_bug.cgi?id=45629 +// For this reason, we use a single "NOT" check for the time being here. +// The same consideration applies to the Linux and Windows cases below. +// IOS-no-cxx-interop-NOT: -lc++ + +// IOS-cxx-interop-libcxx: swift +// IOS-cxx-interop-libcxx-DAG: -enable-cxx-interop +// IOS-cxx-interop-libcxx-DAG: -o [[OBJECTFILE:.*]] + +// IOS-cxx-interop-libcxx: {{(bin/)?}}ld{{"? }} +// IOS-cxx-interop-libcxx-DAG: [[OBJECTFILE]] +// IOS-cxx-interop-libcxx-DAG: -lc++ +// IOS-cxx-interop-libcxx: -o linker + +// IOS-cxx-interop-libstdcxx: error: The only C++ standard library supported on Apple platforms is libc++ + +// LINUX-cxx-interop-NOT: -stdlib + +// LINUX-cxx-interop-libcxx: swift +// LINUX-cxx-interop-libcxx-DAG: -enable-cxx-interop +// LINUX-cxx-interop-libcxx-DAG: -o [[OBJECTFILE:.*]] + +// LINUX-cxx-interop-libcxx: clang++{{(\.exe)?"? }} +// LINUX-cxx-interop-libcxx-DAG: [[OBJECTFILE]] +// LINUX-cxx-interop-libcxx-DAG: -stdlib=libc++ +// LINUX-cxx-interop-libcxx: -o linker + +// WINDOWS-cxx-interop-NOT: -stdlib + +// WINDOWS-cxx-interop-libcxx: swift +// WINDOWS-cxx-interop-libcxx-DAG: -enable-cxx-interop +// WINDOWS-cxx-interop-libcxx-DAG: -o [[OBJECTFILE:.*]] + +// WINDOWS-cxx-interop-libcxx: clang++{{(\.exe)?"? }} +// WINDOWS-cxx-interop-libcxx-DAG: [[OBJECTFILE]] +// WINDOWS-cxx-interop-libcxx-DAG: -stdlib=libc++ +// WINDOWS-cxx-interop-libcxx: -o linker + // Test ld detection. We use hard links to make sure // the Swift driver really thinks it's been moved.