1use std::env;
2use std::fmt::{Display, from_fn};
3use std::num::ParseIntError;
4
5use rustc_middle::middle::exported_symbols::SymbolExportKind;
6use rustc_session::Session;
7use rustc_target::spec::Target;
8
9use crate::errors::AppleDeploymentTarget;
10
11#[cfg(test)]
12mod tests;
13
14pub(super) fn macho_platform(target: &Target) -> u32 {
15 match (&*target.os, &*target.abi) {
16 ("macos", _) => object::macho::PLATFORM_MACOS,
17 ("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
18 ("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
19 ("ios", _) => object::macho::PLATFORM_IOS,
20 ("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
21 ("watchos", _) => object::macho::PLATFORM_WATCHOS,
22 ("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
23 ("tvos", _) => object::macho::PLATFORM_TVOS,
24 ("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
25 ("visionos", _) => object::macho::PLATFORM_XROS,
26 _ => unreachable!("tried to get Mach-O platform for non-Apple target"),
27 }
28}
29
30pub(super) fn add_data_and_relocation(
60 file: &mut object::write::Object<'_>,
61 section: object::write::SectionId,
62 symbol: object::write::SymbolId,
63 target: &Target,
64 kind: SymbolExportKind,
65) -> object::write::Result<()> {
66 let authenticated_pointer =
67 kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
68
69 let data: &[u8] = match target.pointer_width {
70 _ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
71 32 => &[0; 4],
72 64 => &[0; 8],
73 pointer_width => unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
74 };
75
76 if target.arch == "x86_64" {
77 file.section_mut(section).append_data(&[], 16);
79 } else {
80 file.section_mut(section).append_data(&[], target.pointer_width as u64);
82 }
83
84 let offset = file.section_mut(section).append_data(data, data.len() as u64);
85
86 let flags = if authenticated_pointer {
87 object::write::RelocationFlags::MachO {
88 r_type: object::macho::ARM64_RELOC_AUTHENTICATED_POINTER,
89 r_pcrel: false,
90 r_length: 3,
91 }
92 } else if target.arch == "arm" {
93 object::write::RelocationFlags::MachO {
96 r_type: object::macho::ARM_RELOC_VANILLA,
97 r_pcrel: false,
98 r_length: 2,
99 }
100 } else {
101 object::write::RelocationFlags::Generic {
102 kind: object::RelocationKind::Absolute,
103 encoding: object::RelocationEncoding::Generic,
104 size: target.pointer_width as u8,
105 }
106 };
107
108 file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
109
110 Ok(())
111}
112
113type OSVersion = (u16, u8, u8);
117
118fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
120 if let Some((major, minor)) = version.split_once('.') {
121 let major = major.parse()?;
122 if let Some((minor, patch)) = minor.split_once('.') {
123 Ok((major, minor.parse()?, patch.parse()?))
124 } else {
125 Ok((major, minor.parse()?, 0))
126 }
127 } else {
128 Ok((version.parse()?, 0, 0))
129 }
130}
131
132pub fn pretty_version(version: OSVersion) -> impl Display {
133 let (major, minor, patch) = version;
134 from_fn(move |f| {
135 write!(f, "{major}.{minor}")?;
136 if patch != 0 {
137 write!(f, ".{patch}")?;
138 }
139 Ok(())
140 })
141}
142
143fn os_minimum_deployment_target(os: &str) -> OSVersion {
145 match os {
153 "macos" => (10, 12, 0),
154 "ios" => (10, 0, 0),
155 "tvos" => (10, 0, 0),
156 "watchos" => (5, 0, 0),
157 "visionos" => (1, 0, 0),
158 _ => unreachable!("tried to get deployment target for non-Apple platform"),
159 }
160}
161
162fn minimum_deployment_target(target: &Target) -> OSVersion {
170 match (&*target.os, &*target.arch, &*target.abi) {
171 ("macos", "aarch64", _) => (11, 0, 0),
172 ("ios", "aarch64", "macabi") => (14, 0, 0),
173 ("ios", "aarch64", "sim") => (14, 0, 0),
174 ("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
175 ("ios", _, "macabi") => (13, 1, 0),
177 ("tvos", "aarch64", "sim") => (14, 0, 0),
178 ("watchos", "aarch64", "sim") => (7, 0, 0),
179 (os, _, _) => os_minimum_deployment_target(os),
180 }
181}
182
183pub fn deployment_target_env_var(os: &str) -> &'static str {
185 match os {
186 "macos" => "MACOSX_DEPLOYMENT_TARGET",
187 "ios" => "IPHONEOS_DEPLOYMENT_TARGET",
188 "watchos" => "WATCHOS_DEPLOYMENT_TARGET",
189 "tvos" => "TVOS_DEPLOYMENT_TARGET",
190 "visionos" => "XROS_DEPLOYMENT_TARGET",
191 _ => unreachable!("tried to get deployment target env var for non-Apple platform"),
192 }
193}
194
195pub fn deployment_target(sess: &Session) -> OSVersion {
198 let min = minimum_deployment_target(&sess.target);
199 let env_var = deployment_target_env_var(&sess.target.os);
200
201 if let Ok(deployment_target) = env::var(env_var) {
202 match parse_version(&deployment_target) {
203 Ok(version) => {
204 let os_min = os_minimum_deployment_target(&sess.target.os);
205 if version < os_min {
210 sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
211 env_var,
212 version: pretty_version(version).to_string(),
213 os_min: pretty_version(os_min).to_string(),
214 });
215 }
216
217 version.max(min)
219 }
220 Err(error) => {
221 sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
222 min
223 }
224 }
225 } else {
226 min
228 }
229}
230
231pub(super) fn add_version_to_llvm_target(
232 llvm_target: &str,
233 deployment_target: OSVersion,
234) -> String {
235 let mut components = llvm_target.split("-");
236 let arch = components.next().expect("apple target should have arch");
237 let vendor = components.next().expect("apple target should have vendor");
238 let os = components.next().expect("apple target should have os");
239 let environment = components.next();
240 assert_eq!(components.next(), None, "too many LLVM triple components");
241
242 let (major, minor, patch) = deployment_target;
243
244 assert!(
245 !os.contains(|c: char| c.is_ascii_digit()),
246 "LLVM target must not already be versioned"
247 );
248
249 if let Some(env) = environment {
250 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
252 } else {
253 format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
254 }
255}