about summary refs log tree commit diff
path: root/compiler/rustc_target/src/spec/base
diff options
context:
space:
mode:
authorMads Marquart <mads@marquart.dk>2024-09-05 06:45:02 +0200
committerMads Marquart <mads@marquart.dk>2024-09-05 06:47:13 +0200
commit5b51331abe0263a87b5381461543749772071d61 (patch)
tree5d0cd906a99939b234846680530d4c4f2e15b4e7 /compiler/rustc_target/src/spec/base
parent23cdb50e4f1c91ad76a3023e8a8c325e98c6348c (diff)
downloadrust-5b51331abe0263a87b5381461543749772071d61.tar.gz
rust-5b51331abe0263a87b5381461543749772071d61.zip
Apple: Refactor deployment target version parsing
- Merge minimum OS version list into one function (makes it easier to
  see the logic in it).
- Parse patch deployment target versions.
- Consistently specify deployment target in LLVM target (previously
  omitted on `aarch64-apple-watchos`).
Diffstat (limited to 'compiler/rustc_target/src/spec/base')
-rw-r--r--compiler/rustc_target/src/spec/base/apple/mod.rs262
-rw-r--r--compiler/rustc_target/src/spec/base/apple/tests.rs9
2 files changed, 134 insertions, 137 deletions
diff --git a/compiler/rustc_target/src/spec/base/apple/mod.rs b/compiler/rustc_target/src/spec/base/apple/mod.rs
index aef35ff6162..2ca6b7d32ca 100644
--- a/compiler/rustc_target/src/spec/base/apple/mod.rs
+++ b/compiler/rustc_target/src/spec/base/apple/mod.rs
@@ -1,5 +1,6 @@
 use std::borrow::Cow;
 use std::env;
+use std::num::ParseIntError;
 
 use crate::spec::{
     add_link_args, add_link_args_iter, cvs, Cc, DebuginfoKind, FramePointer, LinkArgs,
@@ -123,15 +124,8 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
     };
 
     let min_version: StaticCow<str> = {
-        let (major, minor) = match os {
-            "ios" => ios_deployment_target(arch, abi.target_abi()),
-            "tvos" => tvos_deployment_target(),
-            "watchos" => watchos_deployment_target(),
-            "visionos" => visionos_deployment_target(),
-            "macos" => macos_deployment_target(arch),
-            _ => unreachable!(),
-        };
-        format!("{major}.{minor}").into()
+        let (major, minor, patch) = deployment_target(os, arch, abi);
+        format!("{major}.{minor}.{patch}").into()
     };
     let sdk_version = min_version.clone();
 
@@ -163,15 +157,22 @@ fn pre_link_args(os: &'static str, arch: Arch, abi: TargetAbi) -> LinkArgs {
         add_link_args_iter(
             &mut args,
             LinkerFlavor::Darwin(Cc::Yes, Lld::No),
-            ["-target".into(), mac_catalyst_llvm_target(arch).into()].into_iter(),
+            ["-target".into(), llvm_target(os, arch, abi)].into_iter(),
         );
     }
 
     args
 }
 
-pub(crate) fn opts(os: &'static str, arch: Arch, abi: TargetAbi) -> TargetOptions {
-    TargetOptions {
+/// 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.
+pub(crate) fn base(
+    os: &'static str,
+    arch: Arch,
+    abi: TargetAbi,
+) -> (TargetOptions, StaticCow<str>, StaticCow<str>) {
+    let opts = TargetOptions {
         abi: abi.target_abi().into(),
         os: os.into(),
         cpu: arch.target_cpu(abi).into(),
@@ -221,10 +222,11 @@ pub(crate) fn opts(os: &'static str, arch: Arch, abi: TargetAbi) -> TargetOption
         link_env: Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"), Cow::Borrowed("1"))]),
 
         ..Default::default()
-    }
+    };
+    (opts, llvm_target(os, arch, abi), arch.target_arch())
 }
 
-pub fn sdk_version(platform: u32) -> Option<(u32, u32)> {
+pub fn sdk_version(platform: u32) -> Option<(u16, u8)> {
     // NOTE: These values are from an arbitrary point in time but shouldn't make it into the final
     // binary since the final link command will have the current SDK version passed to it.
     match platform {
@@ -258,58 +260,108 @@ pub fn platform(target: &Target) -> Option<u32> {
     })
 }
 
-pub fn deployment_target(target: &Target) -> Option<(u32, u32)> {
-    let (major, minor) = match &*target.os {
-        "macos" => {
-            // This does not need to be specific. It just needs to handle x86 vs M1.
-            let arch = match target.arch.as_ref() {
-                "x86" | "x86_64" => X86_64,
-                "arm64e" => Arm64e,
-                _ => Arm64,
-            };
-            macos_deployment_target(arch)
-        }
-        "ios" => {
-            let arch = match target.arch.as_ref() {
-                "arm64e" => Arm64e,
-                _ => Arm64,
-            };
-            ios_deployment_target(arch, &target.abi)
-        }
-        "watchos" => watchos_deployment_target(),
-        "tvos" => tvos_deployment_target(),
-        "visionos" => visionos_deployment_target(),
-        _ => 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
     };
-
-    Some((major, minor))
+    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)
 }
 
-fn from_set_deployment_target(var_name: &str) -> Option<(u32, u32)> {
-    let deployment_target = env::var(var_name).ok()?;
-    let (unparsed_major, unparsed_minor) = deployment_target.split_once('.')?;
-    let (major, minor) = (unparsed_major.parse().ok()?, unparsed_minor.parse().ok()?);
+/// 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"),
+    };
 
-    Some((major, minor))
-}
+    // On certain targets it makes sense to raise the minimum OS version.
+    let min = match (os, arch, abi) {
+        // Use 11.0 on Aarch64 as that's the earliest version with M1 support.
+        ("macos", Arch::Arm64 | Arch::Arm64e, _) => (11, 0, 0),
+        ("ios", Arch::Arm64e, _) => (14, 0, 0),
+        // Mac Catalyst defaults to 13.1 in Clang.
+        ("ios", _, TargetAbi::MacCatalyst) => (13, 1, 0),
+        _ => os_min,
+    };
 
-fn macos_default_deployment_target(arch: Arch) -> (u32, u32) {
-    match arch {
-        Arm64 | Arm64e => (11, 0),
-        _ => (10, 12),
-    }
-}
+    // 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"),
+    };
 
-fn macos_deployment_target(arch: Arch) -> (u32, u32) {
-    // If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
-    // Note: If bumping this version, remember to update it in the rustc/platform-support docs.
-    from_set_deployment_target("MACOSX_DEPLOYMENT_TARGET")
-        .unwrap_or_else(|| macos_default_deployment_target(arch))
+    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
+    }
 }
 
-pub(crate) fn macos_llvm_target(arch: Arch) -> String {
-    let (major, minor) = macos_deployment_target(arch);
-    format!("{}-apple-macosx{}.{}.0", arch.target_name(), major, minor)
+/// 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);
+    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
+    let os = match os {
+        "macos" => "macosx",
+        "ios" => "ios",
+        "watchos" => "watchos",
+        "tvos" => "tvos",
+        "visionos" => "xros",
+        _ => unreachable!("tried to get LLVM target OS for non-Apple platform"),
+    };
+    let environment = match abi {
+        TargetAbi::Normal => "",
+        TargetAbi::MacCatalyst => "-macabi",
+        TargetAbi::Simulator => "-simulator",
+    };
+    format!("{arch}-apple-{os}{major}.{minor}.{patch}{environment}").into()
 }
 
 fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
@@ -349,83 +401,19 @@ fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
     }
 }
 
-fn ios_deployment_target(arch: Arch, abi: &str) -> (u32, u32) {
-    // If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
-    // Note: If bumping this version, remember to update it in the rustc/platform-support docs.
-    let (major, minor) = match (arch, abi) {
-        (Arm64e, _) => (14, 0),
-        // Mac Catalyst defaults to 13.1 in Clang.
-        (_, "macabi") => (13, 1),
-        _ => (10, 0),
-    };
-    from_set_deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((major, minor))
-}
-
-pub(crate) fn ios_llvm_target(arch: Arch) -> String {
-    // Modern iOS tooling extracts information about deployment target
-    // from LC_BUILD_VERSION. This load command will only be emitted when
-    // we build with a version specific `llvm_target`, with the version
-    // set high enough. Luckily one LC_BUILD_VERSION is enough, for Xcode
-    // to pick it up (since std and core are still built with the fallback
-    // of version 7.0 and hence emit the old LC_IPHONE_MIN_VERSION).
-    let (major, minor) = ios_deployment_target(arch, "");
-    format!("{}-apple-ios{}.{}.0", arch.target_name(), major, minor)
-}
-
-pub(crate) fn mac_catalyst_llvm_target(arch: Arch) -> String {
-    let (major, minor) = ios_deployment_target(arch, "macabi");
-    format!("{}-apple-ios{}.{}.0-macabi", arch.target_name(), major, minor)
-}
-
-pub(crate) fn ios_sim_llvm_target(arch: Arch) -> String {
-    let (major, minor) = ios_deployment_target(arch, "sim");
-    format!("{}-apple-ios{}.{}.0-simulator", arch.target_name(), major, minor)
-}
-
-fn tvos_deployment_target() -> (u32, u32) {
-    // If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
-    // Note: If bumping this version, remember to update it in the rustc platform-support docs.
-    from_set_deployment_target("TVOS_DEPLOYMENT_TARGET").unwrap_or((10, 0))
-}
-
-pub(crate) fn tvos_llvm_target(arch: Arch) -> String {
-    let (major, minor) = tvos_deployment_target();
-    format!("{}-apple-tvos{}.{}.0", arch.target_name(), major, minor)
-}
-
-pub(crate) fn tvos_sim_llvm_target(arch: Arch) -> String {
-    let (major, minor) = tvos_deployment_target();
-    format!("{}-apple-tvos{}.{}.0-simulator", arch.target_name(), major, minor)
-}
-
-fn watchos_deployment_target() -> (u32, u32) {
-    // If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
-    // Note: If bumping this version, remember to update it in the rustc platform-support docs.
-    from_set_deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0))
-}
-
-pub(crate) fn watchos_llvm_target(arch: Arch) -> String {
-    let (major, minor) = watchos_deployment_target();
-    format!("{}-apple-watchos{}.{}.0", arch.target_name(), major, minor)
-}
-
-pub(crate) fn watchos_sim_llvm_target(arch: Arch) -> String {
-    let (major, minor) = watchos_deployment_target();
-    format!("{}-apple-watchos{}.{}.0-simulator", arch.target_name(), major, minor)
-}
-
-fn visionos_deployment_target() -> (u32, u32) {
-    // If you are looking for the default deployment target, prefer `rustc --print deployment-target`.
-    // Note: If bumping this version, remember to update it in the rustc platform-support docs.
-    from_set_deployment_target("XROS_DEPLOYMENT_TARGET").unwrap_or((1, 0))
-}
-
-pub(crate) fn visionos_llvm_target(arch: Arch) -> String {
-    let (major, minor) = visionos_deployment_target();
-    format!("{}-apple-visionos{}.{}.0", arch.target_name(), major, minor)
-}
-
-pub(crate) fn visionos_sim_llvm_target(arch: Arch) -> String {
-    let (major, minor) = visionos_deployment_target();
-    format!("{}-apple-visionos{}.{}.0-simulator", arch.target_name(), major, minor)
+/// 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 7a985ad4dc0..d55b44e3c9e 100644
--- a/compiler/rustc_target/src/spec/base/apple/tests.rs
+++ b/compiler/rustc_target/src/spec/base/apple/tests.rs
@@ -1,3 +1,4 @@
+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,
@@ -42,3 +43,11 @@ 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)));
+}