diff options
Diffstat (limited to 'compiler/rustc_codegen_ssa/src/back')
| -rw-r--r-- | compiler/rustc_codegen_ssa/src/back/apple.rs | 171 | ||||
| -rw-r--r-- | compiler/rustc_codegen_ssa/src/back/apple/tests.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_codegen_ssa/src/back/link.rs | 13 | ||||
| -rw-r--r-- | compiler/rustc_codegen_ssa/src/back/metadata.rs | 11 | ||||
| -rw-r--r-- | compiler/rustc_codegen_ssa/src/back/mod.rs | 21 |
5 files changed, 226 insertions, 11 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/apple.rs b/compiler/rustc_codegen_ssa/src/back/apple.rs new file mode 100644 index 00000000000..93d90cd16b2 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/back/apple.rs @@ -0,0 +1,171 @@ +use std::env; +use std::fmt::{Display, from_fn}; +use std::num::ParseIntError; + +use rustc_session::Session; +use rustc_target::spec::Target; + +use crate::errors::AppleDeploymentTarget; + +#[cfg(test)] +mod tests; + +pub(super) fn macho_platform(target: &Target) -> u32 { + match (&*target.os, &*target.abi) { + ("macos", _) => object::macho::PLATFORM_MACOS, + ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST, + ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR, + ("ios", _) => object::macho::PLATFORM_IOS, + ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR, + ("watchos", _) => object::macho::PLATFORM_WATCHOS, + ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR, + ("tvos", _) => object::macho::PLATFORM_TVOS, + ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR, + ("visionos", _) => object::macho::PLATFORM_XROS, + _ => unreachable!("tried to get Mach-O platform for non-Apple target"), + } +} + +/// Deployment target or SDK version. +/// +/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`. +type OSVersion = (u16, u8, u8); + +/// Parse an OS version triple (SDK version or deployment target). +fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> { + if let Some((major, minor)) = version.split_once('.') { + let major = major.parse()?; + if let Some((minor, patch)) = minor.split_once('.') { + Ok((major, minor.parse()?, patch.parse()?)) + } else { + Ok((major, minor.parse()?, 0)) + } + } else { + Ok((version.parse()?, 0, 0)) + } +} + +pub fn pretty_version(version: OSVersion) -> impl Display { + let (major, minor, patch) = version; + from_fn(move |f| { + write!(f, "{major}.{minor}")?; + if patch != 0 { + write!(f, ".{patch}")?; + } + Ok(()) + }) +} + +/// Minimum operating system versions currently supported by `rustc`. +fn os_minimum_deployment_target(os: &str) -> OSVersion { + // When bumping a version in here, remember to update the platform-support docs too. + // + // 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"), + } +} + +/// 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: +/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932> +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", _, "macabi") => (13, 1, 0), + ("tvos", "aarch64", "sim") => (14, 0, 0), + ("watchos", "aarch64", "sim") => (7, 0, 0), + (os, _, _) => os_minimum_deployment_target(os), + } +} + +/// Name of the environment variable used to fetch the deployment target on the given OS. +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"), + } +} + +/// 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) -> OSVersion { + let min = minimum_deployment_target(&sess.target); + let env_var = deployment_target_env_var(&sess.target.os); + + if let Ok(deployment_target) = env::var(env_var) { + match parse_version(&deployment_target) { + Ok(version) => { + let os_min = os_minimum_deployment_target(&sess.target.os); + // It is common that the deployment target is set a bit too low, for example on + // macOS Aarch64 to also target older x86_64. So we only want to warn when variable + // is lower than the minimum OS supported by rustc, not when the variable is lower + // than the minimum for a specific target. + if version < os_min { + sess.dcx().emit_warn(AppleDeploymentTarget::TooLow { + env_var, + version: pretty_version(version).to_string(), + os_min: pretty_version(os_min).to_string(), + }); + } + + // Raise the deployment target to the minimum supported. + version.max(min) + } + Err(error) => { + sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error }); + min + } + } + } else { + // If no deployment target variable is set, default to the minimum found above. + min + } +} + +pub(super) fn add_version_to_llvm_target( + llvm_target: &str, + deployment_target: OSVersion, +) -> String { + let mut components = llvm_target.split("-"); + let arch = components.next().expect("apple target should have arch"); + let vendor = components.next().expect("apple target should have vendor"); + let os = components.next().expect("apple 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}") + } +} diff --git a/compiler/rustc_codegen_ssa/src/back/apple/tests.rs b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs new file mode 100644 index 00000000000..7ccda5a8190 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/back/apple/tests.rs @@ -0,0 +1,21 @@ +use super::{add_version_to_llvm_target, parse_version}; + +#[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" + ); +} + +#[test] +fn test_parse_version() { + assert_eq!(parse_version("10"), Ok((10, 0, 0))); + assert_eq!(parse_version("10.12"), Ok((10, 12, 0))); + assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6))); + assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99))); +} diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 34dc599e4fd..b01a62b394b 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 super::{apple, versioned_llvm_target}; use crate::{ CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors, looks_like_rust_object_file, @@ -2447,7 +2448,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, ]); @@ -3039,7 +3040,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) = apple::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: @@ -3109,7 +3110,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) = apple::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}")); @@ -3119,7 +3120,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)]); } } } @@ -3345,7 +3346,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 796acb6796f..a7d95d56784 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -22,6 +22,8 @@ use rustc_span::sym; use rustc_target::abi::Endian; use rustc_target::spec::{RelocModel, Target, ef_avr_arch}; +use super::apple; + /// The default metadata loader. This is used by cg_llvm and cg_clif. /// /// # Metadata location @@ -238,7 +240,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E); } - file.set_macho_build_version(macho_object_build_version_for_target(&sess.target)) + file.set_macho_build_version(macho_object_build_version_for_target(sess)) } if binary_format == BinaryFormat::Coff { // Disable the default mangler to avoid mangling the special "@feat.00" symbol name. @@ -392,7 +394,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static /// /// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this /// returns the `MachOBuildVersion` for the target to do so. -fn macho_object_build_version_for_target(target: &Target) -> 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,9 +402,8 @@ fn macho_object_build_version_for_target(target: &Target) -> object::write::Mach (major << 16) | (minor << 8) | patch } - 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); + let platform = apple::macho_platform(&sess.target); + let min_os = apple::deployment_target(sess); let mut build_version = object::write::MachOBuildVersion::default(); build_version.platform = platform; diff --git a/compiler/rustc_codegen_ssa/src/back/mod.rs b/compiler/rustc_codegen_ssa/src/back/mod.rs index 2b3a2e3a369..64b5d4569ec 100644 --- a/compiler/rustc_codegen_ssa/src/back/mod.rs +++ b/compiler/rustc_codegen_ssa/src/back/mod.rs @@ -1,3 +1,8 @@ +use std::borrow::Cow; + +use rustc_session::Session; + +pub mod apple; pub mod archive; pub(crate) mod command; pub mod link; @@ -7,3 +12,19 @@ pub mod metadata; pub(crate) mod rpath; pub mod symbol_export; pub mod write; + +/// 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<'_, str> { + if sess.target.is_like_osx { + apple::add_version_to_llvm_target(&sess.target.llvm_target, apple::deployment_target(sess)) + .into() + } else { + // FIXME(madsmtm): Certain other targets also include a version, + // we might want to move that here as well. + Cow::Borrowed(&sess.target.llvm_target) + } +} |
