diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index f6b7981395a5f..da67116e4e8a1 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -39,6 +39,7 @@ use std::sync::Arc; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{self, Configurable}; use rustc_codegen_ssa::CodegenResults; +use rustc_codegen_ssa::apple::versioned_llvm_target; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::ErrorGuaranteed; @@ -259,7 +260,9 @@ impl CodegenBackend for CraneliftCodegenBackend { } fn target_triple(sess: &Session) -> target_lexicon::Triple { - match sess.target.llvm_target.parse() { + // FIXME(madsmtm): Use `sess.target.llvm_target` once target-lexicon supports unversioned macOS. + // See + match versioned_llvm_target(sess).parse() { Ok(triple) => triple, Err(err) => sess.dcx().fatal(format!("target not recognized: {}", err)), } diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index afdd2b581b86e..9b794c6fbb98f 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -8,6 +8,7 @@ use libc::{c_char, c_int, c_void, size_t}; use llvm::{ LLVMRustLLVMHasZlibCompressionForDebugSymbols, LLVMRustLLVMHasZstdCompressionForDebugSymbols, }; +use rustc_codegen_ssa::apple::versioned_llvm_target; use rustc_codegen_ssa::back::link::ensure_removed; use rustc_codegen_ssa::back::write::{ BitcodeSection, CodegenContext, EmitObj, ModuleConfig, TargetMachineFactoryConfig, @@ -210,7 +211,7 @@ pub(crate) fn target_machine_factory( singlethread = false; } - let triple = SmallCStr::new(&sess.target.llvm_target); + let triple = SmallCStr::new(&versioned_llvm_target(sess)); let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); let features = CString::new(target_features.join(",")).unwrap(); let abi = SmallCStr::new(&sess.target.llvm_abiname); diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 2b8912d1db2ab..903e55e67346e 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -3,6 +3,7 @@ use std::cell::{Cell, RefCell}; use std::ffi::{CStr, c_uint}; use std::str; +use rustc_codegen_ssa::apple::versioned_llvm_target; use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh}; use rustc_codegen_ssa::errors as ssa_errors; use rustc_codegen_ssa::traits::*; @@ -165,7 +166,7 @@ pub(crate) unsafe fn create_module<'ll>( llvm::LLVMSetDataLayout(llmod, data_layout.as_ptr()); } - let llvm_target = SmallCStr::new(&sess.target.llvm_target); + let llvm_target = SmallCStr::new(&versioned_llvm_target(sess)); unsafe { llvm::LLVMRustSetNormalizedTarget(llmod, llvm_target.as_ptr()); } diff --git a/compiler/rustc_codegen_ssa/src/apple.rs b/compiler/rustc_codegen_ssa/src/apple.rs new file mode 100644 index 0000000000000..23acfde5fd4f5 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/apple.rs @@ -0,0 +1,69 @@ +use std::borrow::Cow; +use std::env; + +use rustc_session::Session; +use rustc_target::spec::{ + AppleOSVersion, apple_deployment_target_env_var, apple_minimum_deployment_target, + apple_parse_version, +}; + +#[cfg(test)] +mod tests; + +/// Get the deployment target based on the standard environment variables, or fall back to the +/// minimum version supported by `rustc`. +pub fn deployment_target(sess: &Session) -> AppleOSVersion { + let min = apple_minimum_deployment_target(&sess.target); + + if let Ok(deployment_target) = env::var(apple_deployment_target_env_var(&sess.target.os)) { + match apple_parse_version(&deployment_target) { + // It is common that the deployment target is set too low, e.g. on macOS Aarch64 to also + // target older x86_64, the user may set a lower deployment target than supported. + // + // To avoid such issues, we silently raise the deployment target here. + // FIXME: We want to show a warning when `version < os_min`. + Ok(version) => version.max(min), + // FIXME: Report erroneous environment variable to user. + Err(_) => min, + } + } else { + // If no deployment target variable is set, default to the minimum found above. + min + } +} + +fn add_version_to_llvm_target(llvm_target: &str, deployment_target: AppleOSVersion) -> String { + let mut components = llvm_target.split("-"); + let arch = components.next().expect("darwin target should have arch"); + let vendor = components.next().expect("darwin target should have vendor"); + let os = components.next().expect("darwin target should have os"); + let environment = components.next(); + assert_eq!(components.next(), None, "too many LLVM triple components"); + + let (major, minor, patch) = deployment_target; + + assert!( + !os.contains(|c: char| c.is_ascii_digit()), + "LLVM target must not already be versioned" + ); + + if let Some(env) = environment { + // Insert version into OS, before environment + format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}") + } else { + format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}") + } +} + +/// The target triple depends on the deployment target, and is required to +/// enable features such as cross-language LTO, and for picking the right +/// Mach-O commands. +/// +/// Certain optimizations also depend on the deployment target. +pub fn versioned_llvm_target(sess: &Session) -> Cow<'static, str> { + if sess.target.is_like_osx { + add_version_to_llvm_target(&sess.target.llvm_target, deployment_target(sess)).into() + } else { + sess.target.llvm_target.clone() + } +} diff --git a/compiler/rustc_codegen_ssa/src/apple/tests.rs b/compiler/rustc_codegen_ssa/src/apple/tests.rs new file mode 100644 index 0000000000000..1edf0f0034fbf --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/apple/tests.rs @@ -0,0 +1,13 @@ +use super::add_version_to_llvm_target; + +#[test] +fn test_add_version_to_llvm_target() { + assert_eq!( + add_version_to_llvm_target("aarch64-apple-macosx", (10, 14, 1)), + "aarch64-apple-macosx10.14.1" + ); + assert_eq!( + add_version_to_llvm_target("aarch64-apple-ios-simulator", (16, 1, 0)), + "aarch64-apple-ios16.1.0-simulator" + ); +} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index e7b1c63a82251..878cdff9300b9 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -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, current_apple_deployment_target, + SplitDebuginfo, }; use tempfile::Builder as TempFileBuilder; use tracing::{debug, info, warn}; @@ -50,6 +50,7 @@ use super::command::Command; use super::linker::{self, Linker}; use super::metadata::{MetadataPosition, create_wrapper_file}; use super::rpath::{self, RPathConfig}; +use crate::apple::{deployment_target, versioned_llvm_target}; use crate::{ CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors, looks_like_rust_object_file, @@ -2445,7 +2446,7 @@ fn add_order_independent_options( if flavor == LinkerFlavor::Llbc { cmd.link_args(&[ "--target", - sess.target.llvm_target.as_ref(), + &versioned_llvm_target(sess), "--target-cpu", &codegen_results.crate_info.target_cpu, ]); @@ -3037,7 +3038,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo _ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"), }; - let (major, minor, patch) = current_apple_deployment_target(&sess.target); + let (major, minor, patch) = deployment_target(sess); let min_version = format!("{major}.{minor}.{patch}"); // The SDK version is used at runtime when compiling with a newer SDK / version of Xcode: @@ -3107,7 +3108,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo // 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); + let (major, minor, patch) = deployment_target(sess); // 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}")); @@ -3117,7 +3118,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo // // We avoid `-m32`/`-m64`, as this is already encoded by `-arch`. } else { - cmd.cc_args(&["-target", &sess.target.llvm_target]); + cmd.cc_args(&["-target", &versioned_llvm_target(sess)]); } } } @@ -3343,7 +3344,7 @@ fn add_lld_args( // targeting a different linker flavor on macOS, and that's also always // the case when targeting WASM. if sess.target.linker_flavor != sess.host.linker_flavor { - cmd.cc_arg(format!("--target={}", sess.target.llvm_target)); + cmd.cc_arg(format!("--target={}", versioned_llvm_target(sess))); } } } diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 8857fda1e9728..4cfda916ec24f 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -238,7 +238,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Option object::write::MachOBuildVersion { +fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion { /// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz" /// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200 fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 { @@ -400,8 +400,8 @@ fn macho_object_build_version_for_target(target: &Target) -> object::write::Mach } let platform = - rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS"); - let min_os = rustc_target::spec::current_apple_deployment_target(target); + rustc_target::spec::current_apple_platform(&sess.target).expect("unknown Apple target OS"); + let min_os = crate::apple::deployment_target(sess); let mut build_version = object::write::MachOBuildVersion::default(); build_version.platform = platform; diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 162d14272a555..cae753ae50cee 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -44,6 +44,7 @@ use rustc_session::cstore::{self, CrateSource}; use rustc_session::utils::NativeLibKind; use rustc_span::symbol::Symbol; +pub mod apple; pub mod assert_module_sources; pub mod back; pub mod base; diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 76b7270d4b815..2ebcb245ed112 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -856,10 +856,8 @@ fn print_crate_info( } } DeploymentTarget => { - use rustc_target::spec::current_apple_deployment_target; - if sess.target.is_like_osx { - let (major, minor, patch) = current_apple_deployment_target(&sess.target); + let (major, minor, patch) = rustc_codegen_ssa::apple::deployment_target(sess); let patch = if patch != 0 { format!(".{patch}") } else { String::new() }; println_info!("deployment_target={major}.{minor}{patch}") } else { diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs index 73763cf034ca0..5ea77a29351c0 100644 --- a/compiler/rustc_target/src/spec/base/apple/mod.rs +++ b/compiler/rustc_target/src/spec/base/apple/mod.rs @@ -175,97 +175,62 @@ pub fn platform(target: &Target) -> Option { }) } -/// Hack for calling `deployment_target` outside of this module. -pub fn deployment_target_for_target(target: &Target) -> (u16, u8, u8) { - let arch = if target.llvm_target.starts_with("arm64e") { - Arch::Arm64e - } else if target.arch == "aarch64" { - Arch::Arm64 - } else { - // Dummy architecture, only used by `deployment_target` anyhow - Arch::X86_64 - }; - let abi = match &*target.abi { - "macabi" => TargetAbi::MacCatalyst, - "sim" => TargetAbi::Simulator, - "" => TargetAbi::Normal, - abi => unreachable!("invalid abi '{abi}' for Apple target"), - }; - deployment_target(&target.os, arch, abi) -} - -/// Get the deployment target based on the standard environment variables, or -/// fall back to a sane default. -fn deployment_target(os: &str, arch: Arch, abi: TargetAbi) -> (u16, u8, u8) { - // When bumping a version in here, remember to update the platform-support - // docs too. +/// Minimum operating system versions currently supported by `rustc`. +pub fn os_minimum_deployment_target(os: &str) -> OSVersion { + // When bumping a version in here, remember to update the platform-support docs too. // - // NOTE: If you are looking for the default deployment target, prefer - // `rustc --print deployment-target`, as the default here may change in - // future `rustc` versions. - - // Minimum operating system versions currently supported by `rustc`. - let os_min = match os { + // NOTE: The defaults may change in future `rustc` versions, so if you are looking for the + // default deployment target, prefer: + // ``` + // $ rustc --print deployment-target + // ``` + match os { "macos" => (10, 12, 0), "ios" => (10, 0, 0), "tvos" => (10, 0, 0), "watchos" => (5, 0, 0), "visionos" => (1, 0, 0), _ => unreachable!("tried to get deployment target for non-Apple platform"), - }; + } +} - // On certain targets it makes sense to raise the minimum OS version. - // - // This matches what LLVM does, see: - // - let min = match (os, arch, abi) { - ("macos", Arch::Arm64 | Arch::Arm64e, _) => (11, 0, 0), - ("ios", Arch::Arm64 | Arch::Arm64e, TargetAbi::MacCatalyst) => (14, 0, 0), - ("ios", Arch::Arm64 | Arch::Arm64e, TargetAbi::Simulator) => (14, 0, 0), - ("ios", Arch::Arm64e, TargetAbi::Normal) => (14, 0, 0), +/// The deployment target for the given target. +/// +/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense +/// to raise the minimum OS version. +/// +/// This matches what LLVM does, see in part: +/// +pub fn minimum_deployment_target(target: &Target) -> OSVersion { + match (&*target.os, &*target.arch, &*target.abi) { + ("macos", "aarch64", _) => (11, 0, 0), + ("ios", "aarch64", "macabi") => (14, 0, 0), + ("ios", "aarch64", "sim") => (14, 0, 0), + ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0), // Mac Catalyst defaults to 13.1 in Clang. - ("ios", _, TargetAbi::MacCatalyst) => (13, 1, 0), - ("tvos", Arch::Arm64 | Arch::Arm64e, TargetAbi::Simulator) => (14, 0, 0), - ("watchos", Arch::Arm64 | Arch::Arm64e, TargetAbi::Simulator) => (7, 0, 0), - _ => os_min, - }; + ("ios", _, "macabi") => (13, 1, 0), + ("tvos", "aarch64", "sim") => (14, 0, 0), + ("watchos", "aarch64", "sim") => (7, 0, 0), + (os, _, _) => os_minimum_deployment_target(os), + } +} - // The environment variable used to fetch the deployment target. - let env_var = match os { +/// Name of the environment variable used to fetch the deployment target on the given OS. +pub fn deployment_target_env_var(os: &str) -> &'static str { + match os { "macos" => "MACOSX_DEPLOYMENT_TARGET", "ios" => "IPHONEOS_DEPLOYMENT_TARGET", "watchos" => "WATCHOS_DEPLOYMENT_TARGET", "tvos" => "TVOS_DEPLOYMENT_TARGET", "visionos" => "XROS_DEPLOYMENT_TARGET", _ => unreachable!("tried to get deployment target env var for non-Apple platform"), - }; - - if let Ok(deployment_target) = env::var(env_var) { - match parse_version(&deployment_target) { - // It is common that the deployment target is set too low, e.g. on - // macOS Aarch64 to also target older x86_64, the user may set a - // lower deployment target than supported. - // - // To avoid such issues, we silently raise the deployment target - // here. - // FIXME: We want to show a warning when `version < os_min`. - Ok(version) => version.max(min), - // FIXME: Report erroneous environment variable to user. - Err(_) => min, - } - } else { - min } } /// Generate the target triple that we need to pass to LLVM and/or Clang. +/// +/// See `rustc_codegen_ssa::apple::llvm_target` for the full LLVM target. fn llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow { - // The target triple depends on the deployment target, and is required to - // enable features such as cross-language LTO, and for picking the right - // Mach-O commands. - // - // Certain optimizations also depend on the deployment target. - let (major, minor, patch) = deployment_target(os, arch, abi); let arch = arch.target_name(); // Convert to the "canonical" OS name used by LLVM: // https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L236-L282 @@ -282,7 +247,7 @@ fn llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow { TargetAbi::MacCatalyst => "-macabi", TargetAbi::Simulator => "-simulator", }; - format!("{arch}-apple-{os}{major}.{minor}.{patch}{environment}").into() + format!("{arch}-apple-{os}{environment}").into() } fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow]> { @@ -322,11 +287,13 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow]> { } } -/// Parse an OS version triple (SDK version or deployment target). +/// Deployment target or SDK version. /// -/// The size of the returned numbers here are limited by Mach-O's -/// `LC_BUILD_VERSION`. -fn parse_version(version: &str) -> Result<(u16, u8, u8), ParseIntError> { +/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`. +pub type OSVersion = (u16, u8, u8); + +/// Parse an OS version triple (SDK version or deployment target). +pub fn parse_version(version: &str) -> Result { if let Some((major, minor)) = version.split_once('.') { let major = major.parse()?; if let Some((minor, patch)) = minor.split_once('.') { diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index c557091242e98..6401806fdf9df 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -60,8 +60,10 @@ pub mod crt_objects; mod base; pub use base::apple::{ - deployment_target_for_target as current_apple_deployment_target, - platform as current_apple_platform, + OSVersion as AppleOSVersion, deployment_target_env_var as apple_deployment_target_env_var, + minimum_deployment_target as apple_minimum_deployment_target, + os_minimum_deployment_target as apple_os_minimum_deployment_target, + parse_version as apple_parse_version, platform as current_apple_platform, }; pub use base::avr_gnu::ef_avr_arch; @@ -1997,7 +1999,12 @@ impl TargetWarnings { /// Every field here must be specified, and has no default value. #[derive(PartialEq, Clone, Debug)] pub struct Target { - /// Target triple to pass to LLVM. + /// Unversioned target triple to pass to LLVM. + /// + /// Target triples can optionally contain an OS version (notably Apple targets), which rustc + /// cannot know without querying the environment. + /// + /// Use `rustc_codegen_ssa::apple::versioned_llvm_target` if you need the full LLVM target. pub llvm_target: StaticCow, /// Metadata about a target, for example the description or tier. /// Used for generating target documentation.