Skip to content

Commit 72af021

Browse files
committed
Move Apple linker args from rustc_target to rustc_codegen_ssa
They are dependent on the deployment target and SDK version, but having these in `rustc_target` makes it hard to introduce that dependency.
1 parent 02b1be1 commit 72af021

File tree

2 files changed

+134
-124
lines changed
  • compiler
    • rustc_codegen_ssa/src/back
    • rustc_target/src/spec/base/apple

2 files changed

+134
-124
lines changed

compiler/rustc_codegen_ssa/src/back/link.rs

+132-3
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ use rustc_session::{filesearch, Session};
3838
use rustc_span::symbol::Symbol;
3939
use rustc_target::spec::crt_objects::CrtObjects;
4040
use rustc_target::spec::{
41-
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
42-
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
43-
SplitDebuginfo,
41+
current_apple_deployment_target, Cc, LinkOutputKind, LinkSelfContainedComponents,
42+
LinkSelfContainedDefault, LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy,
43+
RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo,
4444
};
4545
use tempfile::Builder as TempFileBuilder;
4646
use tracing::{debug, info, warn};
@@ -2404,6 +2404,8 @@ fn add_order_independent_options(
24042404
// Take care of the flavors and CLI options requesting the `lld` linker.
24052405
add_lld_args(cmd, sess, flavor, self_contained_components);
24062406

2407+
add_apple_link_args(cmd, sess, flavor);
2408+
24072409
let apple_sdk_root = add_apple_sdk(cmd, sess, flavor);
24082410

24092411
add_link_script(cmd, sess, tmpdir, crate_type);
@@ -2956,6 +2958,133 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool
29562958
}
29572959
}
29582960

2961+
/// We need to communicate four things to the linker on Apple/Darwin targets:
2962+
/// - The architecture.
2963+
/// - The operating system (and that it's an Apple platform).
2964+
/// - The deployment target.
2965+
/// - The environment / ABI.
2966+
fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
2967+
if !sess.target.is_like_osx {
2968+
return;
2969+
}
2970+
2971+
// `sess.target.arch` (`target_arch`) is not detailed enough.
2972+
let llvm_arch = sess.target.llvm_target.split_once('-').expect("LLVM target must have arch").0;
2973+
let target_os = &*sess.target.os;
2974+
let target_abi = &*sess.target.abi;
2975+
2976+
// The architecture name to forward to the linker.
2977+
//
2978+
// Supported architecture names can be found in the source:
2979+
// https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/src/abstraction/MachOFileAbstraction.hpp#L578-L648
2980+
//
2981+
// Intentially verbose to ensure that the list always matches correctly
2982+
// with the list in the source above.
2983+
let ld64_arch = match llvm_arch {
2984+
"armv7k" => "armv7k",
2985+
"armv7s" => "armv7s",
2986+
"arm64" => "arm64",
2987+
"arm64e" => "arm64e",
2988+
"arm64_32" => "arm64_32",
2989+
// ld64 doesn't understand i686, so fall back to i386 instead.
2990+
//
2991+
// Same story when linking with cc, since that ends up invoking ld64.
2992+
"i386" | "i686" => "i386",
2993+
"x86_64" => "x86_64",
2994+
"x86_64h" => "x86_64h",
2995+
_ => bug!("unsupported architecture in Apple target: {}", sess.target.llvm_target),
2996+
};
2997+
2998+
if matches!(flavor, LinkerFlavor::Darwin(Cc::No, _)) {
2999+
// From the man page for ld64 (`man ld`):
3000+
// > The linker accepts universal (multiple-architecture) input files,
3001+
// > but always creates a "thin" (single-architecture), standard
3002+
// > Mach-O output file. The architecture for the output file is
3003+
// > specified using the -arch option.
3004+
//
3005+
// The linker has heuristics to determine the desired architecture,
3006+
// but to be safe, and to avoid a warning, we set the architecture
3007+
// explicitly.
3008+
cmd.link_args(&["-arch", ld64_arch]);
3009+
3010+
// Man page says that ld64 supports the following platform names:
3011+
// > - macos
3012+
// > - ios
3013+
// > - tvos
3014+
// > - watchos
3015+
// > - bridgeos
3016+
// > - visionos
3017+
// > - xros
3018+
// > - mac-catalyst
3019+
// > - ios-simulator
3020+
// > - tvos-simulator
3021+
// > - watchos-simulator
3022+
// > - visionos-simulator
3023+
// > - xros-simulator
3024+
// > - driverkit
3025+
let platform_name = match (target_os, target_abi) {
3026+
(os, "") => os,
3027+
("ios", "macabi") => "mac-catalyst",
3028+
("ios", "sim") => "ios-simulator",
3029+
("tvos", "sim") => "tvos-simulator",
3030+
("watchos", "sim") => "watchos-simulator",
3031+
("visionos", "sim") => "visionos-simulator",
3032+
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
3033+
};
3034+
3035+
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3036+
let min_version = format!("{major}.{minor}.{patch}");
3037+
3038+
// Lie about the SDK version, we don't know it here
3039+
let sdk_version = &*min_version;
3040+
3041+
// From the man page for ld64 (`man ld`):
3042+
// > This is set to indicate the platform, oldest supported version of
3043+
// > that platform that output is to be used on, and the SDK that the
3044+
// > output was built against.
3045+
//
3046+
// Like with `-arch`, the linker can figure out the platform versions
3047+
// itself from the binaries being linked, but to be safe, we specify
3048+
// the desired versions here explicitly.
3049+
cmd.link_args(&["-platform_version", platform_name, &*min_version, sdk_version]);
3050+
} else if matches!(flavor, LinkerFlavor::Darwin(Cc::Yes, _)) {
3051+
// We'd _like_ to use `-target` everywhere, since that can uniquely
3052+
// communicate all the required details, but that doesn't work on GCC,
3053+
// and since we don't know whether the `cc` compiler is Clang, GCC, or
3054+
// something else, we fall back to other options that also work on GCC
3055+
// when compiling for macOS.
3056+
//
3057+
// Targets other than macOS are ill-supported by GCC (it doesn't even
3058+
// support e.g. `-miphoneos-version-min`), so in those cases we can
3059+
// fairly safely use `-target`. See also the following, where it is
3060+
// made explicit that the recommendation by LLVM developers is to use
3061+
// `-target`: <https://github.com/llvm/llvm-project/issues/88271>
3062+
if target_os == "macos" {
3063+
// `-arch` communicates the architecture.
3064+
//
3065+
// CC forwards the `-arch` to the linker, so we use the same value
3066+
// here intentionally.
3067+
cmd.cc_args(&["-arch", ld64_arch]);
3068+
3069+
// The presence of `-mmacosx-version-min` makes CC default to
3070+
// macOS, and it sets the deployment target.
3071+
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3072+
// Intentionally pass this as a single argument, Clang doesn't
3073+
// seem to like it otherwise.
3074+
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
3075+
3076+
// macOS has no environment, so with these two, we've told CC the
3077+
// four desired parameters.
3078+
//
3079+
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
3080+
} else {
3081+
cmd.cc_args(&["-target", &sess.target.llvm_target]);
3082+
}
3083+
} else {
3084+
// FIXME: Are we doing the correct thing for non-Darwin linkers?
3085+
}
3086+
}
3087+
29593088
fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) -> Option<PathBuf> {
29603089
let arch = &sess.target.arch;
29613090
let os = &sess.target.os;

compiler/rustc_target/src/spec/base/apple/mod.rs

+2-121
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::env;
33
use std::num::ParseIntError;
44

55
use crate::spec::{
6-
add_link_args, add_link_args_iter, cvs, Cc, DebuginfoKind, FramePointer, LinkArgs,
7-
LinkerFlavor, Lld, SplitDebuginfo, StackProbeType, StaticCow, Target, TargetOptions,
6+
cvs, Cc, DebuginfoKind, FramePointer, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType,
7+
StaticCow, Target, TargetOptions,
88
};
99

1010
#[cfg(test)]
@@ -40,25 +40,6 @@ impl Arch {
4040
}
4141
}
4242

43-
/// The architecture name to forward to the linker.
44-
fn ld_arch(self) -> &'static str {
45-
// Supported architecture names can be found in the source:
46-
// https://github.com/apple-oss-distributions/ld64/blob/ld64-951.9/src/abstraction/MachOFileAbstraction.hpp#L578-L648
47-
match self {
48-
Armv7k => "armv7k",
49-
Armv7s => "armv7s",
50-
Arm64 => "arm64",
51-
Arm64e => "arm64e",
52-
Arm64_32 => "arm64_32",
53-
// ld64 doesn't understand i686, so fall back to i386 instead
54-
//
55-
// Same story when linking with cc, since that ends up invoking ld64.
56-
I386 | I686 => "i386",
57-
X86_64 => "x86_64",
58-
X86_64h => "x86_64h",
59-
}
60-
}
61-
6243
pub(crate) fn target_arch(self) -> Cow<'static, str> {
6344
Cow::Borrowed(match self {
6445
Armv7k | Armv7s => "arm",
@@ -116,105 +97,6 @@ impl TargetAbi {
11697
}
11798
}
11899

119-
fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
120-
// From the man page for ld64 (`man ld`):
121-
// > The linker accepts universal (multiple-architecture) input files,
122-
// > but always creates a "thin" (single-architecture), standard Mach-O
123-
// > output file. The architecture for the output file is specified using
124-
// > the -arch option.
125-
//
126-
// The linker has heuristics to determine the desired architecture, but to
127-
// be safe, and to avoid a warning, we set the architecture explicitly.
128-
let mut args =
129-
TargetOptions::link_args(LinkerFlavor::Darwin(Cc::No, Lld::No), &["-arch", arch.ld_arch()]);
130-
131-
// From the man page for ld64 (`man ld`):
132-
// > This is set to indicate the platform, oldest supported version of
133-
// > that platform that output is to be used on, and the SDK that the
134-
// > output was built against. platform [...] may be one of the following
135-
// > strings:
136-
// > - macos
137-
// > - ios
138-
// > - tvos
139-
// > - watchos
140-
// > - bridgeos
141-
// > - visionos
142-
// > - xros
143-
// > - mac-catalyst
144-
// > - ios-simulator
145-
// > - tvos-simulator
146-
// > - watchos-simulator
147-
// > - visionos-simulator
148-
// > - xros-simulator
149-
// > - driverkit
150-
//
151-
// Like with `-arch`, the linker can figure out the platform versions
152-
// itself from the binaries being linked, but to be safe, we specify the
153-
// desired versions here explicitly.
154-
let platform_name: StaticCow<str> = match abi {
155-
TargetAbi::Normal => os.into(),
156-
TargetAbi::Simulator => format!("{os}-simulator").into(),
157-
TargetAbi::MacCatalyst => "mac-catalyst".into(),
158-
};
159-
let min_version: StaticCow<str> = {
160-
let (major, minor, patch) = deployment_target(os, arch, abi);
161-
format!("{major}.{minor}.{patch}").into()
162-
};
163-
// Lie about the SDK version, we don't know it here
164-
let sdk_version = min_version.clone();
165-
add_link_args_iter(
166-
&mut args,
167-
LinkerFlavor::Darwin(Cc::No, Lld::No),
168-
["-platform_version".into(), platform_name, min_version, sdk_version].into_iter(),
169-
);
170-
171-
// We need to communicate four things to the C compiler to be able to link:
172-
// - The architecture.
173-
// - The operating system (and that it's an Apple platform).
174-
// - The deployment target.
175-
// - The environment / ABI.
176-
//
177-
// We'd like to use `-target` everywhere, since that can uniquely
178-
// communicate all of these, but that doesn't work on GCC, and since we
179-
// don't know whether the `cc` compiler is Clang, GCC, or something else,
180-
// we fall back to other options that also work on GCC when compiling for
181-
// macOS.
182-
//
183-
// Targets other than macOS are ill-supported by GCC (it doesn't even
184-
// support e.g. `-miphoneos-version-min`), so in those cases we can fairly
185-
// safely use `-target`. See also the following, where it is made explicit
186-
// that the recommendation by LLVM developers is to use `-target`:
187-
// <https://github.com/llvm/llvm-project/issues/88271>
188-
if os == "macos" {
189-
// `-arch` communicates the architecture.
190-
//
191-
// CC forwards the `-arch` to the linker, so we use the same value
192-
// here intentionally.
193-
add_link_args(
194-
&mut args,
195-
LinkerFlavor::Darwin(Cc::Yes, Lld::No),
196-
&["-arch", arch.ld_arch()],
197-
);
198-
// The presence of `-mmacosx-version-min` makes CC default to macOS,
199-
// and it sets the deployment target.
200-
let (major, minor, patch) = deployment_target(os, arch, abi);
201-
let opt = format!("-mmacosx-version-min={major}.{minor}.{patch}").into();
202-
add_link_args_iter(&mut args, LinkerFlavor::Darwin(Cc::Yes, Lld::No), [opt].into_iter());
203-
// macOS has no environment, so with these two, we've told CC all the
204-
// desired parameters.
205-
//
206-
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
207-
} else {
208-
add_link_args_iter(
209-
&mut args,
210-
LinkerFlavor::Darwin(Cc::Yes, Lld::No),
211-
["-target".into(), llvm_target(os, arch, abi)].into_iter(),
212-
);
213-
}
214-
215-
args
216-
}
217-
218100
/// Get the base target options, LLVM target and `target_arch` from the three
219101
/// things that uniquely identify Rust's Apple targets: The OS, the
220102
/// architecture, and the ABI.
@@ -233,7 +115,6 @@ pub(crate) fn base(
233115
// macOS has -dead_strip, which doesn't rely on function_sections
234116
function_sections: false,
235117
dynamic_linking: true,
236-
pre_link_args: pre_link_args(os, arch, abi),
237118
families: cvs!["unix"],
238119
is_like_osx: true,
239120
// LLVM notes that macOS 10.11+ and iOS 9+ default

0 commit comments

Comments
 (0)