Skip to content

Commit 888530a

Browse files
Rollup merge of rust-lang#131037 - madsmtm:move-llvm-target-versioning, r=petrochenkov
Move versioned Apple LLVM targets from `rustc_target` to `rustc_codegen_ssa` Fully specified LLVM targets contain the OS version on macOS/iOS/tvOS/watchOS/visionOS, and this version depends on the deployment target environment variables like `MACOSX_DEPLOYMENT_TARGET`, `IPHONEOS_DEPLOYMENT_TARGET` etc. We would like to move this to later in the compilation pipeline, both because it feels impure to access environment variables when fetching target information, but mostly because we need access to more information from rust-lang#130883 to do rust-lang#118204. See also rust-lang#129342 (comment) for some discussion. The first and second commit does the actual refactor, it should be a non-functional change, the third commit adds diagnostics for invalid deployment targets, which are now possible to do because we have access to the session. Tested with the same commands as in rust-lang#130435. r? `````@petrochenkov`````
2 parents ef972a3 + 1ef1af1 commit 888530a

File tree

15 files changed

+271
-165
lines changed

15 files changed

+271
-165
lines changed

compiler/rustc_codegen_cranelift/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use std::sync::Arc;
4040
use cranelift_codegen::isa::TargetIsa;
4141
use cranelift_codegen::settings::{self, Configurable};
4242
use rustc_codegen_ssa::CodegenResults;
43+
use rustc_codegen_ssa::back::versioned_llvm_target;
4344
use rustc_codegen_ssa::traits::CodegenBackend;
4445
use rustc_data_structures::profiling::SelfProfilerRef;
4546
use rustc_errors::ErrorGuaranteed;
@@ -260,7 +261,9 @@ impl CodegenBackend for CraneliftCodegenBackend {
260261
}
261262

262263
fn target_triple(sess: &Session) -> target_lexicon::Triple {
263-
match sess.target.llvm_target.parse() {
264+
// FIXME(madsmtm): Use `sess.target.llvm_target` once target-lexicon supports unversioned macOS.
265+
// See <https://github.com/bytecodealliance/target-lexicon/pull/113>
266+
match versioned_llvm_target(sess).parse() {
264267
Ok(triple) => triple,
265268
Err(err) => sess.dcx().fatal(format!("target not recognized: {}", err)),
266269
}

compiler/rustc_codegen_llvm/src/back/write.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use llvm::{
99
LLVMRustLLVMHasZlibCompressionForDebugSymbols, LLVMRustLLVMHasZstdCompressionForDebugSymbols,
1010
};
1111
use rustc_codegen_ssa::back::link::ensure_removed;
12+
use rustc_codegen_ssa::back::versioned_llvm_target;
1213
use rustc_codegen_ssa::back::write::{
1314
BitcodeSection, CodegenContext, EmitObj, ModuleConfig, TargetMachineFactoryConfig,
1415
TargetMachineFactoryFn,
@@ -211,7 +212,7 @@ pub(crate) fn target_machine_factory(
211212
singlethread = false;
212213
}
213214

214-
let triple = SmallCStr::new(&sess.target.llvm_target);
215+
let triple = SmallCStr::new(&versioned_llvm_target(sess));
215216
let cpu = SmallCStr::new(llvm_util::target_cpu(sess));
216217
let features = CString::new(target_features.join(",")).unwrap();
217218
let abi = SmallCStr::new(&sess.target.llvm_abiname);

compiler/rustc_codegen_llvm/src/context.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::cell::{Cell, RefCell};
33
use std::ffi::{CStr, c_uint};
44
use std::str;
55

6+
use rustc_codegen_ssa::back::versioned_llvm_target;
67
use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh};
78
use rustc_codegen_ssa::errors as ssa_errors;
89
use rustc_codegen_ssa::traits::*;
@@ -182,7 +183,7 @@ pub(crate) unsafe fn create_module<'ll>(
182183
llvm::LLVMSetDataLayout(llmod, data_layout.as_ptr());
183184
}
184185

185-
let llvm_target = SmallCStr::new(&sess.target.llvm_target);
186+
let llvm_target = SmallCStr::new(&versioned_llvm_target(sess));
186187
unsafe {
187188
llvm::LLVMRustSetNormalizedTarget(llmod, llvm_target.as_ptr());
188189
}

compiler/rustc_codegen_ssa/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not imp
22
33
codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error}
44
5+
codegen_ssa_apple_deployment_target_invalid =
6+
failed to parse deployment target specified in {$env_var}: {$error}
7+
8+
codegen_ssa_apple_deployment_target_too_low =
9+
deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}
10+
511
codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error}
612
713
codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use std::env;
2+
use std::fmt::{Display, from_fn};
3+
use std::num::ParseIntError;
4+
5+
use rustc_session::Session;
6+
use rustc_target::spec::Target;
7+
8+
use crate::errors::AppleDeploymentTarget;
9+
10+
#[cfg(test)]
11+
mod tests;
12+
13+
pub(super) fn macho_platform(target: &Target) -> u32 {
14+
match (&*target.os, &*target.abi) {
15+
("macos", _) => object::macho::PLATFORM_MACOS,
16+
("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
17+
("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
18+
("ios", _) => object::macho::PLATFORM_IOS,
19+
("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
20+
("watchos", _) => object::macho::PLATFORM_WATCHOS,
21+
("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
22+
("tvos", _) => object::macho::PLATFORM_TVOS,
23+
("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
24+
("visionos", _) => object::macho::PLATFORM_XROS,
25+
_ => unreachable!("tried to get Mach-O platform for non-Apple target"),
26+
}
27+
}
28+
29+
/// Deployment target or SDK version.
30+
///
31+
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
32+
type OSVersion = (u16, u8, u8);
33+
34+
/// Parse an OS version triple (SDK version or deployment target).
35+
fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
36+
if let Some((major, minor)) = version.split_once('.') {
37+
let major = major.parse()?;
38+
if let Some((minor, patch)) = minor.split_once('.') {
39+
Ok((major, minor.parse()?, patch.parse()?))
40+
} else {
41+
Ok((major, minor.parse()?, 0))
42+
}
43+
} else {
44+
Ok((version.parse()?, 0, 0))
45+
}
46+
}
47+
48+
pub fn pretty_version(version: OSVersion) -> impl Display {
49+
let (major, minor, patch) = version;
50+
from_fn(move |f| {
51+
write!(f, "{major}.{minor}")?;
52+
if patch != 0 {
53+
write!(f, ".{patch}")?;
54+
}
55+
Ok(())
56+
})
57+
}
58+
59+
/// Minimum operating system versions currently supported by `rustc`.
60+
fn os_minimum_deployment_target(os: &str) -> OSVersion {
61+
// When bumping a version in here, remember to update the platform-support docs too.
62+
//
63+
// NOTE: The defaults may change in future `rustc` versions, so if you are looking for the
64+
// default deployment target, prefer:
65+
// ```
66+
// $ rustc --print deployment-target
67+
// ```
68+
match os {
69+
"macos" => (10, 12, 0),
70+
"ios" => (10, 0, 0),
71+
"tvos" => (10, 0, 0),
72+
"watchos" => (5, 0, 0),
73+
"visionos" => (1, 0, 0),
74+
_ => unreachable!("tried to get deployment target for non-Apple platform"),
75+
}
76+
}
77+
78+
/// The deployment target for the given target.
79+
///
80+
/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense
81+
/// to raise the minimum OS version.
82+
///
83+
/// This matches what LLVM does, see in part:
84+
/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
85+
fn minimum_deployment_target(target: &Target) -> OSVersion {
86+
match (&*target.os, &*target.arch, &*target.abi) {
87+
("macos", "aarch64", _) => (11, 0, 0),
88+
("ios", "aarch64", "macabi") => (14, 0, 0),
89+
("ios", "aarch64", "sim") => (14, 0, 0),
90+
("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
91+
// Mac Catalyst defaults to 13.1 in Clang.
92+
("ios", _, "macabi") => (13, 1, 0),
93+
("tvos", "aarch64", "sim") => (14, 0, 0),
94+
("watchos", "aarch64", "sim") => (7, 0, 0),
95+
(os, _, _) => os_minimum_deployment_target(os),
96+
}
97+
}
98+
99+
/// Name of the environment variable used to fetch the deployment target on the given OS.
100+
fn deployment_target_env_var(os: &str) -> &'static str {
101+
match os {
102+
"macos" => "MACOSX_DEPLOYMENT_TARGET",
103+
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
104+
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
105+
"tvos" => "TVOS_DEPLOYMENT_TARGET",
106+
"visionos" => "XROS_DEPLOYMENT_TARGET",
107+
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
108+
}
109+
}
110+
111+
/// Get the deployment target based on the standard environment variables, or fall back to the
112+
/// minimum version supported by `rustc`.
113+
pub fn deployment_target(sess: &Session) -> OSVersion {
114+
let min = minimum_deployment_target(&sess.target);
115+
let env_var = deployment_target_env_var(&sess.target.os);
116+
117+
if let Ok(deployment_target) = env::var(env_var) {
118+
match parse_version(&deployment_target) {
119+
Ok(version) => {
120+
let os_min = os_minimum_deployment_target(&sess.target.os);
121+
// It is common that the deployment target is set a bit too low, for example on
122+
// macOS Aarch64 to also target older x86_64. So we only want to warn when variable
123+
// is lower than the minimum OS supported by rustc, not when the variable is lower
124+
// than the minimum for a specific target.
125+
if version < os_min {
126+
sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
127+
env_var,
128+
version: pretty_version(version).to_string(),
129+
os_min: pretty_version(os_min).to_string(),
130+
});
131+
}
132+
133+
// Raise the deployment target to the minimum supported.
134+
version.max(min)
135+
}
136+
Err(error) => {
137+
sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
138+
min
139+
}
140+
}
141+
} else {
142+
// If no deployment target variable is set, default to the minimum found above.
143+
min
144+
}
145+
}
146+
147+
pub(super) fn add_version_to_llvm_target(
148+
llvm_target: &str,
149+
deployment_target: OSVersion,
150+
) -> String {
151+
let mut components = llvm_target.split("-");
152+
let arch = components.next().expect("apple target should have arch");
153+
let vendor = components.next().expect("apple target should have vendor");
154+
let os = components.next().expect("apple target should have os");
155+
let environment = components.next();
156+
assert_eq!(components.next(), None, "too many LLVM triple components");
157+
158+
let (major, minor, patch) = deployment_target;
159+
160+
assert!(
161+
!os.contains(|c: char| c.is_ascii_digit()),
162+
"LLVM target must not already be versioned"
163+
);
164+
165+
if let Some(env) = environment {
166+
// Insert version into OS, before environment
167+
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
168+
} else {
169+
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
170+
}
171+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::{add_version_to_llvm_target, parse_version};
2+
3+
#[test]
4+
fn test_add_version_to_llvm_target() {
5+
assert_eq!(
6+
add_version_to_llvm_target("aarch64-apple-macosx", (10, 14, 1)),
7+
"aarch64-apple-macosx10.14.1"
8+
);
9+
assert_eq!(
10+
add_version_to_llvm_target("aarch64-apple-ios-simulator", (16, 1, 0)),
11+
"aarch64-apple-ios16.1.0-simulator"
12+
);
13+
}
14+
15+
#[test]
16+
fn test_parse_version() {
17+
assert_eq!(parse_version("10"), Ok((10, 0, 0)));
18+
assert_eq!(parse_version("10.12"), Ok((10, 12, 0)));
19+
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
20+
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
21+
}

compiler/rustc_codegen_ssa/src/back/link.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use rustc_target::spec::crt_objects::CrtObjects;
4040
use rustc_target::spec::{
4141
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
4242
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
43-
SplitDebuginfo, current_apple_deployment_target,
43+
SplitDebuginfo,
4444
};
4545
use tempfile::Builder as TempFileBuilder;
4646
use tracing::{debug, info, warn};
@@ -50,6 +50,7 @@ use super::command::Command;
5050
use super::linker::{self, Linker};
5151
use super::metadata::{MetadataPosition, create_wrapper_file};
5252
use super::rpath::{self, RPathConfig};
53+
use super::{apple, versioned_llvm_target};
5354
use crate::{
5455
CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors,
5556
looks_like_rust_object_file,
@@ -2447,7 +2448,7 @@ fn add_order_independent_options(
24472448
if flavor == LinkerFlavor::Llbc {
24482449
cmd.link_args(&[
24492450
"--target",
2450-
sess.target.llvm_target.as_ref(),
2451+
&versioned_llvm_target(sess),
24512452
"--target-cpu",
24522453
&codegen_results.crate_info.target_cpu,
24532454
]);
@@ -3039,7 +3040,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
30393040
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
30403041
};
30413042

3042-
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3043+
let (major, minor, patch) = apple::deployment_target(sess);
30433044
let min_version = format!("{major}.{minor}.{patch}");
30443045

30453046
// The SDK version is used at runtime when compiling with a newer SDK / version of Xcode:
@@ -3109,7 +3110,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
31093110

31103111
// The presence of `-mmacosx-version-min` makes CC default to
31113112
// macOS, and it sets the deployment target.
3112-
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
3113+
let (major, minor, patch) = apple::deployment_target(sess);
31133114
// Intentionally pass this as a single argument, Clang doesn't
31143115
// seem to like it otherwise.
31153116
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
@@ -3119,7 +3120,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
31193120
//
31203121
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
31213122
} else {
3122-
cmd.cc_args(&["-target", &sess.target.llvm_target]);
3123+
cmd.cc_args(&["-target", &versioned_llvm_target(sess)]);
31233124
}
31243125
}
31253126
}
@@ -3345,7 +3346,7 @@ fn add_lld_args(
33453346
// targeting a different linker flavor on macOS, and that's also always
33463347
// the case when targeting WASM.
33473348
if sess.target.linker_flavor != sess.host.linker_flavor {
3348-
cmd.cc_arg(format!("--target={}", sess.target.llvm_target));
3349+
cmd.cc_arg(format!("--target={}", versioned_llvm_target(sess)));
33493350
}
33503351
}
33513352
}

compiler/rustc_codegen_ssa/src/back/metadata.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use rustc_span::sym;
2222
use rustc_target::abi::Endian;
2323
use rustc_target::spec::{RelocModel, Target, ef_avr_arch};
2424

25+
use super::apple;
26+
2527
/// The default metadata loader. This is used by cg_llvm and cg_clif.
2628
///
2729
/// # Metadata location
@@ -238,7 +240,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
238240
file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E);
239241
}
240242

241-
file.set_macho_build_version(macho_object_build_version_for_target(&sess.target))
243+
file.set_macho_build_version(macho_object_build_version_for_target(sess))
242244
}
243245
if binary_format == BinaryFormat::Coff {
244246
// Disable the default mangler to avoid mangling the special "@feat.00" symbol name.
@@ -392,17 +394,16 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
392394
///
393395
/// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this
394396
/// returns the `MachOBuildVersion` for the target to do so.
395-
fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion {
397+
fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion {
396398
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
397399
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
398400
fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
399401
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
400402
(major << 16) | (minor << 8) | patch
401403
}
402404

403-
let platform =
404-
rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS");
405-
let min_os = rustc_target::spec::current_apple_deployment_target(target);
405+
let platform = apple::macho_platform(&sess.target);
406+
let min_os = apple::deployment_target(sess);
406407

407408
let mut build_version = object::write::MachOBuildVersion::default();
408409
build_version.platform = platform;

compiler/rustc_codegen_ssa/src/back/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use std::borrow::Cow;
2+
3+
use rustc_session::Session;
4+
5+
pub mod apple;
16
pub mod archive;
27
pub(crate) mod command;
38
pub mod link;
@@ -7,3 +12,19 @@ pub mod metadata;
712
pub(crate) mod rpath;
813
pub mod symbol_export;
914
pub mod write;
15+
16+
/// The target triple depends on the deployment target, and is required to
17+
/// enable features such as cross-language LTO, and for picking the right
18+
/// Mach-O commands.
19+
///
20+
/// Certain optimizations also depend on the deployment target.
21+
pub fn versioned_llvm_target(sess: &Session) -> Cow<'_, str> {
22+
if sess.target.is_like_osx {
23+
apple::add_version_to_llvm_target(&sess.target.llvm_target, apple::deployment_target(sess))
24+
.into()
25+
} else {
26+
// FIXME(madsmtm): Certain other targets also include a version,
27+
// we might want to move that here as well.
28+
Cow::Borrowed(&sess.target.llvm_target)
29+
}
30+
}

0 commit comments

Comments
 (0)