about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-11-02 08:33:10 +0100
committerGitHub <noreply@github.com>2024-11-02 08:33:10 +0100
commitbb544f863f94c9842cbee5b0b86cc07184ef71ad (patch)
tree6d025cb26f1c0782206018d29dcf134a40de8834
parentef972a346668ed4234d1a43ed4ad7ca4e9c58d51 (diff)
parent1ef1af1c607fe268b5622041ff586ca603b8ec8d (diff)
downloadrust-bb544f863f94c9842cbee5b0b86cc07184ef71ad.tar.gz
rust-bb544f863f94c9842cbee5b0b86cc07184ef71ad.zip
Rollup merge of #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 https://github.com/rust-lang/rust/pull/130883 to do https://github.com/rust-lang/rust/issues/118204. See also https://github.com/rust-lang/rust/pull/129342#issuecomment-2335156119 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 https://github.com/rust-lang/rust/pull/130435.

r? ``````@petrochenkov``````
-rw-r--r--compiler/rustc_codegen_cranelift/src/lib.rs5
-rw-r--r--compiler/rustc_codegen_llvm/src/back/write.rs3
-rw-r--r--compiler/rustc_codegen_llvm/src/context.rs3
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl6
-rw-r--r--compiler/rustc_codegen_ssa/src/back/apple.rs171
-rw-r--r--compiler/rustc_codegen_ssa/src/back/apple/tests.rs21
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs13
-rw-r--r--compiler/rustc_codegen_ssa/src/back/metadata.rs11
-rw-r--r--compiler/rustc_codegen_ssa/src/back/mod.rs21
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs9
-rw-r--r--compiler/rustc_codegen_ssa/src/lib.rs1
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs10
-rw-r--r--compiler/rustc_target/src/spec/base/apple/mod.rs142
-rw-r--r--compiler/rustc_target/src/spec/base/apple/tests.rs9
-rw-r--r--compiler/rustc_target/src/spec/mod.rs11
15 files changed, 271 insertions, 165 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs
index 602b1b98200..fc3bd0abd78 100644
--- a/compiler/rustc_codegen_cranelift/src/lib.rs
+++ b/compiler/rustc_codegen_cranelift/src/lib.rs
@@ -40,6 +40,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::back::versioned_llvm_target;
 use rustc_codegen_ssa::traits::CodegenBackend;
 use rustc_data_structures::profiling::SelfProfilerRef;
 use rustc_errors::ErrorGuaranteed;
@@ -260,7 +261,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 <https://github.com/bytecodealliance/target-lexicon/pull/113>
+    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 cf7b16c9cc4..01e2c308ca4 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -9,6 +9,7 @@ use llvm::{
     LLVMRustLLVMHasZlibCompressionForDebugSymbols, LLVMRustLLVMHasZstdCompressionForDebugSymbols,
 };
 use rustc_codegen_ssa::back::link::ensure_removed;
+use rustc_codegen_ssa::back::versioned_llvm_target;
 use rustc_codegen_ssa::back::write::{
     BitcodeSection, CodegenContext, EmitObj, ModuleConfig, TargetMachineFactoryConfig,
     TargetMachineFactoryFn,
@@ -211,7 +212,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 13428b109d9..03f4fb527a8 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::back::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::*;
@@ -182,7 +183,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/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index d07274920fe..3b34eb063ec 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -2,6 +2,12 @@ codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not imp
 
 codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error}
 
+codegen_ssa_apple_deployment_target_invalid =
+    failed to parse deployment target specified in {$env_var}: {$error}
+
+codegen_ssa_apple_deployment_target_too_low =
+    deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}
+
 codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error}
 
 codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}
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)
+    }
+}
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index d67cf0e3a6d..cf8d1cfa0d1 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -2,6 +2,7 @@
 
 use std::borrow::Cow;
 use std::io::Error;
+use std::num::ParseIntError;
 use std::path::{Path, PathBuf};
 use std::process::ExitStatus;
 
@@ -540,6 +541,14 @@ pub(crate) struct UnsupportedArch<'a> {
 }
 
 #[derive(Diagnostic)]
+pub(crate) enum AppleDeploymentTarget {
+    #[diag(codegen_ssa_apple_deployment_target_invalid)]
+    Invalid { env_var: &'static str, error: ParseIntError },
+    #[diag(codegen_ssa_apple_deployment_target_too_low)]
+    TooLow { env_var: &'static str, version: String, os_min: String },
+}
+
+#[derive(Diagnostic)]
 pub(crate) enum AppleSdkRootError<'a> {
     #[diag(codegen_ssa_apple_sdk_error_sdk_path)]
     SdkPath { sdk_name: &'a str, error: Error },
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 73bfa9dbd10..7dc8ab38a97 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -6,6 +6,7 @@
 #![doc(rust_logo)]
 #![feature(assert_matches)]
 #![feature(box_patterns)]
+#![feature(debug_closure_helpers)]
 #![feature(file_buffered)]
 #![feature(if_let_guard)]
 #![feature(let_chains)]
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index e2585c02388..92b622fccf2 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -33,6 +33,7 @@ use std::time::{Instant, SystemTime};
 use std::{env, str};
 
 use rustc_ast as ast;
+use rustc_codegen_ssa::back::apple;
 use rustc_codegen_ssa::traits::CodegenBackend;
 use rustc_codegen_ssa::{CodegenErrors, CodegenResults};
 use rustc_data_structures::profiling::{
@@ -855,12 +856,11 @@ 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 patch = if patch != 0 { format!(".{patch}") } else { String::new() };
-                    println_info!("deployment_target={major}.{minor}{patch}")
+                    println_info!(
+                        "deployment_target={}",
+                        apple::pretty_version(apple::deployment_target(sess))
+                    )
                 } else {
                     #[allow(rustc::diagnostic_outside_of_impl)]
                     sess.dcx().fatal("only Apple targets currently support deployment version info")
diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs
index 73763cf034c..f45c8664093 100644
--- a/compiler/rustc_target/src/spec/base/apple/mod.rs
+++ b/compiler/rustc_target/src/spec/base/apple/mod.rs
@@ -1,10 +1,9 @@
 use std::borrow::Cow;
 use std::env;
-use std::num::ParseIntError;
 
 use crate::spec::{
     Cc, DebuginfoKind, FramePointer, LinkerFlavor, Lld, SplitDebuginfo, StackProbeType, StaticCow,
-    Target, TargetOptions, cvs,
+    TargetOptions, cvs,
 };
 
 #[cfg(test)]
@@ -97,9 +96,8 @@ impl TargetAbi {
     }
 }
 
-/// 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.
+/// Get the base target options, unversioned LLVM target and `target_arch` from the three
+/// things that uniquely identify Rust's Apple targets: The OS, the architecture, and the ABI.
 pub(crate) fn base(
     os: &'static str,
     arch: Arch,
@@ -155,117 +153,14 @@ pub(crate) fn base(
 
         ..Default::default()
     };
-    (opts, llvm_target(os, arch, abi), arch.target_arch())
+    (opts, unversioned_llvm_target(os, arch, abi), arch.target_arch())
 }
 
-pub fn platform(target: &Target) -> Option<u32> {
-    Some(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,
-        // FIXME: Upgrade to `object-rs` 0.33+ implementation with visionOS platform definition
-        ("visionos", "sim") => 12,
-        ("visionos", _) => 11,
-        _ => return None,
-    })
-}
-
-/// 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.
-    //
-    // 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 {
-        "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:
-    // <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
-    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),
-        // 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,
-    };
-
-    // The environment variable used to fetch the deployment target.
-    let env_var = 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.
-fn llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow<str> {
-    // 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);
+/// Generate part of the LLVM target triple.
+///
+/// See `rustc_codegen_ssa::back::versioned_llvm_target` for the full triple passed to LLVM and
+/// Clang.
+fn unversioned_llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow<str> {
     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 +177,7 @@ fn llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow<str> {
         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<str>]> {
@@ -321,20 +216,3 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
         cvs!["MACOSX_DEPLOYMENT_TARGET"]
     }
 }
-
-/// Parse an OS version triple (SDK version or deployment target).
-///
-/// 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> {
-    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))
-    }
-}
diff --git a/compiler/rustc_target/src/spec/base/apple/tests.rs b/compiler/rustc_target/src/spec/base/apple/tests.rs
index 9435b9a5bad..a7335c9bcae 100644
--- a/compiler/rustc_target/src/spec/base/apple/tests.rs
+++ b/compiler/rustc_target/src/spec/base/apple/tests.rs
@@ -1,4 +1,3 @@
-use super::parse_version;
 use crate::spec::targets::{
     aarch64_apple_darwin, aarch64_apple_ios_sim, aarch64_apple_visionos_sim,
     aarch64_apple_watchos_sim, i686_apple_darwin, x86_64_apple_darwin, x86_64_apple_ios,
@@ -40,11 +39,3 @@ fn macos_link_environment_unmodified() {
         ],);
     }
 }
-
-#[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_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 3e7b42d3d1c..d518ed60469 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -65,10 +65,6 @@ pub mod abi {
 }
 
 mod base;
-pub use base::apple::{
-    deployment_target_for_target as current_apple_deployment_target,
-    platform as current_apple_platform,
-};
 pub use base::avr_gnu::ef_avr_arch;
 
 /// Linker is called through a C/C++ compiler.
@@ -2009,7 +2005,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::back::versioned_llvm_target` if you need the full LLVM target.
     pub llvm_target: StaticCow<str>,
     /// Metadata about a target, for example the description or tier.
     /// Used for generating target documentation.