rustc_target/spec/base/apple/
mod.rs

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