rustc_target/spec/base/apple/
mod.rs

1use std::borrow::Cow;
2use std::fmt::{Display, from_fn};
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6use crate::spec::{
7    BinaryFormat, Cc, DebuginfoKind, FloatAbi, FramePointer, LinkerFlavor, Lld, RustcAbi,
8    SplitDebuginfo, StackProbeType, StaticCow, Target, TargetOptions, cvs,
9};
10
11#[cfg(test)]
12mod tests;
13
14use Arch::*;
15#[allow(non_camel_case_types)]
16#[derive(Copy, Clone, PartialEq)]
17pub(crate) enum Arch {
18    Armv7k,
19    Armv7s,
20    Arm64,
21    Arm64e,
22    Arm64_32,
23    I386,
24    I686,
25    X86_64,
26    X86_64h,
27}
28
29impl Arch {
30    fn target_name(self) -> &'static str {
31        match self {
32            Armv7k => "armv7k",
33            Armv7s => "armv7s",
34            Arm64 => "arm64",
35            Arm64e => "arm64e",
36            Arm64_32 => "arm64_32",
37            I386 => "i386",
38            I686 => "i686",
39            X86_64 => "x86_64",
40            X86_64h => "x86_64h",
41        }
42    }
43
44    pub(crate) fn target_arch(self) -> Cow<'static, str> {
45        Cow::Borrowed(match self {
46            Armv7k | Armv7s => "arm",
47            Arm64 | Arm64e | Arm64_32 => "aarch64",
48            I386 | I686 => "x86",
49            X86_64 | X86_64h => "x86_64",
50        })
51    }
52
53    fn target_cpu(self, env: TargetEnv) -> &'static str {
54        match self {
55            Armv7k => "cortex-a8",
56            Armv7s => "swift", // iOS 10 is only supported on iPhone 5 or higher.
57            Arm64 => match env {
58                TargetEnv::Normal => "apple-a7",
59                TargetEnv::Simulator => "apple-a12",
60                TargetEnv::MacCatalyst => "apple-a12",
61            },
62            Arm64e => "apple-a12",
63            Arm64_32 => "apple-s4",
64            // Only macOS 10.12+ is supported, which means
65            // all x86_64/x86 CPUs must be running at least penryn
66            // https://github.com/llvm/llvm-project/blob/01f924d0e37a5deae51df0d77e10a15b63aa0c0f/clang/lib/Driver/ToolChains/Arch/X86.cpp#L79-L82
67            I386 | I686 => "penryn",
68            X86_64 => "penryn",
69            // Note: `core-avx2` is slightly more advanced than `x86_64h`, see
70            // comments (and disabled features) in `x86_64h_apple_darwin` for
71            // details. It is a higher baseline then `penryn` however.
72            X86_64h => "core-avx2",
73        }
74    }
75
76    fn stack_probes(self) -> StackProbeType {
77        match self {
78            Armv7k | Armv7s => StackProbeType::None,
79            Arm64 | Arm64e | Arm64_32 | I386 | I686 | X86_64 | X86_64h => StackProbeType::Inline,
80        }
81    }
82}
83
84#[derive(Copy, Clone, PartialEq)]
85pub(crate) enum TargetEnv {
86    Normal,
87    Simulator,
88    MacCatalyst,
89}
90
91impl TargetEnv {
92    fn target_env(self) -> &'static str {
93        match self {
94            Self::Normal => "",
95            Self::MacCatalyst => "macabi",
96            Self::Simulator => "sim",
97        }
98    }
99}
100
101/// Get the base target options, unversioned LLVM target and `target_arch` from the three
102/// things that uniquely identify Rust's Apple targets: The OS, the architecture, and the ABI.
103pub(crate) fn base(
104    os: &'static str,
105    arch: Arch,
106    env: TargetEnv,
107) -> (TargetOptions, StaticCow<str>, StaticCow<str>) {
108    let mut opts = TargetOptions {
109        llvm_floatabi: Some(FloatAbi::Hard),
110        os: os.into(),
111        env: env.target_env().into(),
112        // NOTE: We originally set `cfg(target_abi = "macabi")` / `cfg(target_abi = "sim")`,
113        // before it was discovered that those are actually environments:
114        // https://github.com/rust-lang/rust/issues/133331
115        //
116        // But let's continue setting them for backwards compatibility.
117        // FIXME(madsmtm): Warn about using these in the future.
118        abi: env.target_env().into(),
119        cpu: arch.target_cpu(env).into(),
120        link_env_remove: link_env_remove(os),
121        vendor: "apple".into(),
122        linker_flavor: LinkerFlavor::Darwin(Cc::Yes, Lld::No),
123        // macOS has -dead_strip, which doesn't rely on function_sections
124        function_sections: false,
125        dynamic_linking: true,
126        families: cvs!["unix"],
127        is_like_darwin: true,
128        binary_format: BinaryFormat::MachO,
129        // LLVM notes that macOS 10.11+ and iOS 9+ default
130        // to v4, so we do the same.
131        // https://github.com/llvm/llvm-project/blob/378778a0d10c2f8d5df8ceff81f95b6002984a4b/clang/lib/Driver/ToolChains/Darwin.cpp#L1203
132        default_dwarf_version: 4,
133        frame_pointer: match arch {
134            // clang ignores `-fomit-frame-pointer` for Armv7, it only accepts `-momit-leaf-frame-pointer`
135            Armv7k | Armv7s => FramePointer::Always,
136            // clang supports omitting frame pointers for the rest, but... don't?
137            Arm64 | Arm64e | Arm64_32 => FramePointer::NonLeaf,
138            I386 | I686 | X86_64 | X86_64h => FramePointer::Always,
139        },
140        has_rpath: true,
141        dll_suffix: ".dylib".into(),
142        archive_format: "darwin".into(),
143        // Thread locals became available with iOS 8 and macOS 10.7,
144        // and both are far below our minimum.
145        has_thread_local: true,
146        abi_return_struct_as_int: true,
147        emit_debug_gdb_scripts: false,
148        eh_frame_header: false,
149        stack_probes: arch.stack_probes(),
150
151        debuginfo_kind: DebuginfoKind::DwarfDsym,
152        // The historical default for macOS targets is to run `dsymutil` which
153        // generates a packed version of debuginfo split from the main file.
154        split_debuginfo: SplitDebuginfo::Packed,
155        supported_split_debuginfo: Cow::Borrowed(&[
156            SplitDebuginfo::Packed,
157            SplitDebuginfo::Unpacked,
158            SplitDebuginfo::Off,
159        ]),
160
161        // This environment variable is pretty magical but is intended for
162        // producing deterministic builds. This was first discovered to be used
163        // by the `ar` tool as a way to control whether or not mtime entries in
164        // the archive headers were set to zero or not. It appears that
165        // eventually the linker got updated to do the same thing and now reads
166        // this environment variable too in recent versions.
167        //
168        // For some more info see the commentary on #47086
169        link_env: Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"), Cow::Borrowed("1"))]),
170
171        ..Default::default()
172    };
173    if matches!(arch, Arch::I386 | Arch::I686) {
174        // All Apple x86-32 targets have SSE2.
175        opts.rustc_abi = Some(RustcAbi::X86Sse2);
176    }
177    (opts, unversioned_llvm_target(os, arch, env), arch.target_arch())
178}
179
180/// Generate part of the LLVM target triple.
181///
182/// See `rustc_codegen_ssa::back::versioned_llvm_target` for the full triple passed to LLVM and
183/// Clang.
184fn unversioned_llvm_target(os: &str, arch: Arch, env: TargetEnv) -> StaticCow<str> {
185    let arch = arch.target_name();
186    // Convert to the "canonical" OS name used by LLVM:
187    // https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L236-L282
188    let os = match os {
189        "macos" => "macosx",
190        "ios" => "ios",
191        "watchos" => "watchos",
192        "tvos" => "tvos",
193        "visionos" => "xros",
194        _ => unreachable!("tried to get LLVM target OS for non-Apple platform"),
195    };
196    let environment = match env {
197        TargetEnv::Normal => "",
198        TargetEnv::MacCatalyst => "-macabi",
199        TargetEnv::Simulator => "-simulator",
200    };
201    format!("{arch}-apple-{os}{environment}").into()
202}
203
204fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
205    // Apple platforms only officially support macOS as a host for any compilation.
206    //
207    // If building for macOS, we go ahead and remove any erroneous environment state
208    // that's only applicable to cross-OS compilation. Always leave anything for the
209    // host OS alone though.
210    if os == "macos" {
211        // `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at
212        // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld",
213        // although this is apparently ignored when using the linker at "/usr/bin/ld".
214        cvs!["IPHONEOS_DEPLOYMENT_TARGET", "TVOS_DEPLOYMENT_TARGET", "XROS_DEPLOYMENT_TARGET"]
215    } else {
216        // Otherwise if cross-compiling for a different OS/SDK (including Mac Catalyst), remove any part
217        // of the linking environment that's wrong and reversed.
218        cvs!["MACOSX_DEPLOYMENT_TARGET"]
219    }
220}
221
222/// Deployment target or SDK version.
223///
224/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
226pub struct OSVersion {
227    pub major: u16,
228    pub minor: u8,
229    pub patch: u8,
230}
231
232impl FromStr for OSVersion {
233    type Err = ParseIntError;
234
235    /// Parse an OS version triple (SDK version or deployment target).
236    fn from_str(version: &str) -> Result<Self, ParseIntError> {
237        if let Some((major, minor)) = version.split_once('.') {
238            let major = major.parse()?;
239            if let Some((minor, patch)) = minor.split_once('.') {
240                Ok(Self { major, minor: minor.parse()?, patch: patch.parse()? })
241            } else {
242                Ok(Self { major, minor: minor.parse()?, patch: 0 })
243            }
244        } else {
245            Ok(Self { major: version.parse()?, minor: 0, patch: 0 })
246        }
247    }
248}
249
250impl OSVersion {
251    pub fn new(major: u16, minor: u8, patch: u8) -> Self {
252        Self { major, minor, patch }
253    }
254
255    pub fn fmt_pretty(self) -> impl Display {
256        let Self { major, minor, patch } = self;
257        from_fn(move |f| {
258            write!(f, "{major}.{minor}")?;
259            if patch != 0 {
260                write!(f, ".{patch}")?;
261            }
262            Ok(())
263        })
264    }
265
266    pub fn fmt_full(self) -> impl Display {
267        let Self { major, minor, patch } = self;
268        from_fn(move |f| write!(f, "{major}.{minor}.{patch}"))
269    }
270
271    /// Minimum operating system versions currently supported by `rustc`.
272    pub fn os_minimum_deployment_target(os: &str) -> Self {
273        // When bumping a version in here, remember to update the platform-support docs too.
274        //
275        // NOTE: The defaults may change in future `rustc` versions, so if you are looking for the
276        // default deployment target, prefer:
277        // ```
278        // $ rustc --print deployment-target
279        // ```
280        let (major, minor, patch) = match os {
281            "macos" => (10, 12, 0),
282            "ios" => (10, 0, 0),
283            "tvos" => (10, 0, 0),
284            "watchos" => (5, 0, 0),
285            "visionos" => (1, 0, 0),
286            _ => unreachable!("tried to get deployment target for non-Apple platform"),
287        };
288        Self { major, minor, patch }
289    }
290
291    /// The deployment target for the given target.
292    ///
293    /// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense
294    /// to raise the minimum OS version.
295    ///
296    /// This matches what LLVM does, see in part:
297    /// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
298    pub fn minimum_deployment_target(target: &Target) -> Self {
299        let (major, minor, patch) = match (&*target.os, &*target.arch, &*target.env) {
300            ("macos", "aarch64", _) => (11, 0, 0),
301            ("ios", "aarch64", "macabi") => (14, 0, 0),
302            ("ios", "aarch64", "sim") => (14, 0, 0),
303            ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
304            // Mac Catalyst defaults to 13.1 in Clang.
305            ("ios", _, "macabi") => (13, 1, 0),
306            ("tvos", "aarch64", "sim") => (14, 0, 0),
307            ("watchos", "aarch64", "sim") => (7, 0, 0),
308            (os, _, _) => return Self::os_minimum_deployment_target(os),
309        };
310        Self { major, minor, patch }
311    }
312}
313
314/// Name of the environment variable used to fetch the deployment target on the given OS.
315pub fn deployment_target_env_var(os: &str) -> &'static str {
316    match os {
317        "macos" => "MACOSX_DEPLOYMENT_TARGET",
318        "ios" => "IPHONEOS_DEPLOYMENT_TARGET",
319        "watchos" => "WATCHOS_DEPLOYMENT_TARGET",
320        "tvos" => "TVOS_DEPLOYMENT_TARGET",
321        "visionos" => "XROS_DEPLOYMENT_TARGET",
322        _ => unreachable!("tried to get deployment target env var for non-Apple platform"),
323    }
324}