Skip to content
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

Move Apple linker args from rustc_target to rustc_codegen_ssa #130435

Merged
merged 1 commit into from
Sep 27, 2024
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
133 changes: 132 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use rustc_target::spec::crt_objects::CrtObjects;
use rustc_target::spec::{
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SplitDebuginfo,
SplitDebuginfo, current_apple_deployment_target,
};
use tempfile::Builder as TempFileBuilder;
use tracing::{debug, info, warn};
Expand Down Expand Up @@ -2405,6 +2405,8 @@ fn add_order_independent_options(
// Take care of the flavors and CLI options requesting the `lld` linker.
add_lld_args(cmd, sess, flavor, self_contained_components);

add_apple_link_args(cmd, sess, flavor);

let apple_sdk_root = add_apple_sdk(cmd, sess, flavor);

add_link_script(cmd, sess, tmpdir, crate_type);
Expand Down Expand Up @@ -2957,6 +2959,135 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool
}
}

/// We need to communicate four things to the linker on Apple/Darwin targets:
/// - The architecture.
/// - The operating system (and that it's an Apple platform).
/// - The deployment target.
/// - The environment / ABI.
fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
if !sess.target.is_like_osx {
madsmtm marked this conversation as resolved.
Show resolved Hide resolved
return;
}
let LinkerFlavor::Darwin(cc, _) = flavor else {
return;
};

// `sess.target.arch` (`target_arch`) is not detailed enough.
let llvm_arch = sess.target.llvm_target.split_once('-').expect("LLVM target must have arch").0;
let target_os = &*sess.target.os;
let target_abi = &*sess.target.abi;

// The architecture name to forward to the linker.
//
// Supported architecture names can be found in the source:
// https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/src/abstraction/MachOFileAbstraction.hpp#L578-L648
//
// Intentially verbose to ensure that the list always matches correctly
// with the list in the source above.
let ld64_arch = match llvm_arch {
"armv7k" => "armv7k",
"armv7s" => "armv7s",
"arm64" => "arm64",
"arm64e" => "arm64e",
"arm64_32" => "arm64_32",
// ld64 doesn't understand i686, so fall back to i386 instead.
//
// Same story when linking with cc, since that ends up invoking ld64.
"i386" | "i686" => "i386",
"x86_64" => "x86_64",
"x86_64h" => "x86_64h",
_ => bug!("unsupported architecture in Apple target: {}", sess.target.llvm_target),
};

if cc == Cc::No {
// From the man page for ld64 (`man ld`):
// > The linker accepts universal (multiple-architecture) input files,
// > but always creates a "thin" (single-architecture), standard
// > Mach-O output file. The architecture for the output file is
// > specified using the -arch option.
//
// The linker has heuristics to determine the desired architecture,
// but to be safe, and to avoid a warning, we set the architecture
// explicitly.
cmd.link_args(&["-arch", ld64_arch]);

// Man page says that ld64 supports the following platform names:
// > - macos
// > - ios
// > - tvos
// > - watchos
// > - bridgeos
// > - visionos
// > - xros
// > - mac-catalyst
// > - ios-simulator
// > - tvos-simulator
// > - watchos-simulator
// > - visionos-simulator
// > - xros-simulator
// > - driverkit
let platform_name = match (target_os, target_abi) {
(os, "") => os,
("ios", "macabi") => "mac-catalyst",
("ios", "sim") => "ios-simulator",
("tvos", "sim") => "tvos-simulator",
("watchos", "sim") => "watchos-simulator",
("visionos", "sim") => "visionos-simulator",
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
};

let (major, minor, patch) = current_apple_deployment_target(&sess.target);
let min_version = format!("{major}.{minor}.{patch}");

// Lie about the SDK version, we don't know it here
let sdk_version = &*min_version;

// From the man page for ld64 (`man ld`):
// > This is set to indicate the platform, oldest supported version of
// > that platform that output is to be used on, and the SDK that the
// > output was built against.
//
// Like with `-arch`, the linker can figure out the platform versions
// itself from the binaries being linked, but to be safe, we specify
// the desired versions here explicitly.
cmd.link_args(&["-platform_version", platform_name, &*min_version, sdk_version]);
} else {
// cc == Cc::Yes
// We'd _like_ to use `-target` everywhere, since that can uniquely
// communicate all the required details, but that doesn't work on GCC,
// and since we don't know whether the `cc` compiler is Clang, GCC, or
// something else, we fall back to other options that also work on GCC
// when compiling for macOS.
//
// Targets other than macOS are ill-supported by GCC (it doesn't even
// support e.g. `-miphoneos-version-min`), so in those cases we can
// fairly safely use `-target`. See also the following, where it is
// made explicit that the recommendation by LLVM developers is to use
// `-target`: <https://github.com/llvm/llvm-project/issues/88271>
if target_os == "macos" {
// `-arch` communicates the architecture.
//
// CC forwards the `-arch` to the linker, so we use the same value
// here intentionally.
cmd.cc_args(&["-arch", ld64_arch]);

// The presence of `-mmacosx-version-min` makes CC default to
// macOS, and it sets the deployment target.
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
// Intentionally pass this as a single argument, Clang doesn't
// seem to like it otherwise.
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));

// macOS has no environment, so with these two, we've told CC the
// four desired parameters.
//
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
} else {
cmd.cc_args(&["-target", &sess.target.llvm_target]);
}
}
}

fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option<PathBuf> {
let arch = &sess.target.arch;
let os = &sess.target.os;
Expand Down
122 changes: 2 additions & 120 deletions compiler/rustc_target/src/spec/base/apple/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::env;
use std::num::ParseIntError;

use crate::spec::{
Cc, DebuginfoKind, FramePointer, LinkArgs, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType,
StaticCow, Target, TargetOptions, add_link_args, add_link_args_iter, cvs,
Cc, DebuginfoKind, FramePointer, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType, StaticCow,
Target, TargetOptions, cvs,
};

#[cfg(test)]
Expand Down Expand Up @@ -40,25 +40,6 @@ impl Arch {
}
}

/// The architecture name to forward to the linker.
fn ld_arch(self) -> &'static str {
// Supported architecture names can be found in the source:
// https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/src/abstraction/MachOFileAbstraction.hpp#L578-L648
match self {
Armv7k => "armv7k",
Armv7s => "armv7s",
Arm64 => "arm64",
Arm64e => "arm64e",
Arm64_32 => "arm64_32",
// ld64 doesn't understand i686, so fall back to i386 instead
//
// Same story when linking with cc, since that ends up invoking ld64.
I386 | I686 => "i386",
X86_64 => "x86_64",
X86_64h => "x86_64h",
}
}

pub(crate) fn target_arch(self) -> Cow<'static, str> {
Cow::Borrowed(match self {
Armv7k | Armv7s => "arm",
Expand Down Expand Up @@ -116,104 +97,6 @@ impl TargetAbi {
}
}

fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
// From the man page for ld64 (`man ld`):
// > The linker accepts universal (multiple-architecture) input files,
// > but always creates a "thin" (single-architecture), standard Mach-O
// > output file. The architecture for the output file is specified using
// > the -arch option.
//
// The linker has heuristics to determine the desired architecture, but to
// be safe, and to avoid a warning, we set the architecture explicitly.
let mut args =
TargetOptions::link_args(LinkerFlavor::Darwin(Cc::No, Lld::No), &["-arch", arch.ld_arch()]);

// From the man page for ld64 (`man ld`):
// > This is set to indicate the platform, oldest supported version of
// > that platform that output is to be used on, and the SDK that the
// > output was built against. platform [...] may be one of the following
// > strings:
// > - macos
// > - ios
// > - tvos
// > - watchos
// > - bridgeos
// > - visionos
// > - xros
// > - mac-catalyst
// > - ios-simulator
// > - tvos-simulator
// > - watchos-simulator
// > - visionos-simulator
// > - xros-simulator
// > - driverkit
//
// Like with `-arch`, the linker can figure out the platform versions
// itself from the binaries being linked, but to be safe, we specify the
// desired versions here explicitly.
let platform_name: StaticCow<str> = match abi {
TargetAbi::Normal => os.into(),
TargetAbi::Simulator => format!("{os}-simulator").into(),
TargetAbi::MacCatalyst => "mac-catalyst".into(),
};
let min_version: StaticCow<str> = {
let (major, minor, patch) = deployment_target(os, arch, abi);
format!("{major}.{minor}.{patch}").into()
};
// Lie about the SDK version, we don't know it here
let sdk_version = min_version.clone();
add_link_args_iter(
&mut args,
LinkerFlavor::Darwin(Cc::No, Lld::No),
["-platform_version".into(), platform_name, min_version, sdk_version].into_iter(),
);

// We need to communicate four things to the C compiler to be able to link:
// - The architecture.
// - The operating system (and that it's an Apple platform).
// - The deployment target.
// - The environment / ABI.
//
// We'd like to use `-target` everywhere, since that can uniquely
// communicate all of these, but that doesn't work on GCC, and since we
// don't know whether the `cc` compiler is Clang, GCC, or something else,
// we fall back to other options that also work on GCC when compiling for
// macOS.
//
// Targets other than macOS are ill-supported by GCC (it doesn't even
// support e.g. `-miphoneos-version-min`), so in those cases we can fairly
// safely use `-target`. See also the following, where it is made explicit
// that the recommendation by LLVM developers is to use `-target`:
// <https://github.com/llvm/llvm-project/issues/88271>
if os == "macos" {
// `-arch` communicates the architecture.
//
// CC forwards the `-arch` to the linker, so we use the same value
// here intentionally.
add_link_args(&mut args, LinkerFlavor::Darwin(Cc::Yes, Lld::No), &[
"-arch",
arch.ld_arch(),
]);
// The presence of `-mmacosx-version-min` makes CC default to macOS,
// and it sets the deployment target.
let (major, minor, patch) = deployment_target(os, arch, abi);
let opt = format!("-mmacosx-version-min={major}.{minor}.{patch}").into();
add_link_args_iter(&mut args, LinkerFlavor::Darwin(Cc::Yes, Lld::No), [opt].into_iter());
// macOS has no environment, so with these two, we've told CC all the
// desired parameters.
//
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
} else {
add_link_args_iter(
&mut args,
LinkerFlavor::Darwin(Cc::Yes, Lld::No),
["-target".into(), llvm_target(os, arch, abi)].into_iter(),
);
}

args
}

/// Get the base target options, LLVM target and `target_arch` from the three
/// things that uniquely identify Rust's Apple targets: The OS, the
/// architecture, and the ABI.
Expand All @@ -232,7 +115,6 @@ pub(crate) fn base(
// macOS has -dead_strip, which doesn't rely on function_sections
function_sections: false,
dynamic_linking: true,
pre_link_args: pre_link_args(os, arch, abi),
families: cvs!["unix"],
is_like_osx: true,
// LLVM notes that macOS 10.11+ and iOS 9+ default
Expand Down
Loading