Skip to content

Link against the C++ standard library when C++ interop is enabled #30914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsDriver.def
Original file line number Diff line number Diff line change
Expand Up @@ -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",
())
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Driver/ToolChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">,
Expand Down
12 changes: 12 additions & 0 deletions lib/Driver/DarwinToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions lib/Driver/ToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -1330,6 +1341,26 @@ void ToolChain::getRuntimeLibraryPaths(SmallVectorImpl<std::string> &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 {
Expand Down
31 changes: 8 additions & 23 deletions lib/Driver/UnixToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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;
Expand Down
35 changes: 8 additions & 27 deletions lib/Driver/WindowsToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions test/Driver/cxx_interop.swift
Original file line number Diff line number Diff line change
@@ -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++
58 changes: 58 additions & 0 deletions test/Driver/linker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down