Skip to main content

rustc_codegen_ssa/back/
apple.rs

1use std::ffi::OsString;
2use std::path::PathBuf;
3use std::process::Command;
4
5use itertools::Itertools;
6use rustc_errors::msg;
7use rustc_middle::middle::exported_symbols::SymbolExportKind;
8use rustc_session::Session;
9pub(super) use rustc_target::spec::apple::OSVersion;
10use rustc_target::spec::{Arch, Env, Os, Target};
11use tracing::debug;
12
13use crate::errors::{XcrunError, XcrunSdkPathWarning};
14
15#[cfg(test)]
16mod tests;
17
18/// The canonical name of the desired SDK for a given target.
19pub(super) fn sdk_name(target: &Target) -> &'static str {
20    match (&target.os, &target.env) {
21        (Os::MacOs, Env::Unspecified) => "MacOSX",
22        (Os::IOs, Env::Unspecified) => "iPhoneOS",
23        (Os::IOs, Env::Sim) => "iPhoneSimulator",
24        // Mac Catalyst uses the macOS SDK
25        (Os::IOs, Env::MacAbi) => "MacOSX",
26        (Os::TvOs, Env::Unspecified) => "AppleTVOS",
27        (Os::TvOs, Env::Sim) => "AppleTVSimulator",
28        (Os::VisionOs, Env::Unspecified) => "XROS",
29        (Os::VisionOs, Env::Sim) => "XRSimulator",
30        (Os::WatchOs, Env::Unspecified) => "WatchOS",
31        (Os::WatchOs, Env::Sim) => "WatchSimulator",
32        (os, abi) => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("invalid os \'{0}\' / abi \'{1}\' combination for Apple target",
                os, abi)));
}unreachable!("invalid os '{os}' / abi '{abi}' combination for Apple target"),
33    }
34}
35
36pub(super) fn macho_platform(target: &Target) -> u32 {
37    match (&target.os, &target.env) {
38        (Os::MacOs, _) => object::macho::PLATFORM_MACOS,
39        (Os::IOs, Env::MacAbi) => object::macho::PLATFORM_MACCATALYST,
40        (Os::IOs, Env::Sim) => object::macho::PLATFORM_IOSSIMULATOR,
41        (Os::IOs, _) => object::macho::PLATFORM_IOS,
42        (Os::WatchOs, Env::Sim) => object::macho::PLATFORM_WATCHOSSIMULATOR,
43        (Os::WatchOs, _) => object::macho::PLATFORM_WATCHOS,
44        (Os::TvOs, Env::Sim) => object::macho::PLATFORM_TVOSSIMULATOR,
45        (Os::TvOs, _) => object::macho::PLATFORM_TVOS,
46        (Os::VisionOs, Env::Sim) => object::macho::PLATFORM_XROSSIMULATOR,
47        (Os::VisionOs, _) => object::macho::PLATFORM_XROS,
48        (os, env) => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("invalid os \'{0}\' / env \'{1}\' combination for Apple target",
                os, env)));
}unreachable!("invalid os '{os}' / env '{env}' combination for Apple target"),
49    }
50}
51
52/// Add relocation and section data needed for a symbol to be considered
53/// undefined by ld64.
54///
55/// The relocation must be valid, and hence must point to a valid piece of
56/// machine code, and hence this is unfortunately very architecture-specific.
57///
58///
59/// # New architectures
60///
61/// The values here are basically the same as emitted by the following program:
62///
63/// ```c
64/// // clang -c foo.c -target $CLANG_TARGET
65/// void foo(void);
66///
67/// extern int bar;
68///
69/// void* foobar[2] = {
70///     (void*)foo,
71///     (void*)&bar,
72///     // ...
73/// };
74/// ```
75///
76/// Can be inspected with:
77/// ```console
78/// objdump --macho --reloc foo.o
79/// objdump --macho --full-contents foo.o
80/// ```
81pub(super) fn add_data_and_relocation(
82    file: &mut object::write::Object<'_>,
83    section: object::write::SectionId,
84    symbol: object::write::SymbolId,
85    target: &Target,
86    kind: SymbolExportKind,
87) -> object::write::Result<()> {
88    let authenticated_pointer =
89        kind == SymbolExportKind::Text && target.llvm_target.starts_with("arm64e");
90
91    let data: &[u8] = match target.pointer_width {
92        _ if authenticated_pointer => &[0, 0, 0, 0, 0, 0, 0, 0x80],
93        32 => &[0; 4],
94        64 => &[0; 8],
95        pointer_width => {
    ::core::panicking::panic_fmt(format_args!("not implemented: {0}",
            format_args!("unsupported Apple pointer width {0:?}",
                pointer_width)));
}unimplemented!("unsupported Apple pointer width {pointer_width:?}"),
96    };
97
98    if target.arch == Arch::X86_64 {
99        // Force alignment for the entire section to be 16 on x86_64.
100        file.section_mut(section).append_data(&[], 16);
101    } else {
102        // Elsewhere, the section alignment is the same as the pointer width.
103        file.section_mut(section).append_data(&[], target.pointer_width as u64);
104    }
105
106    let offset = file.section_mut(section).append_data(data, data.len() as u64);
107
108    let flags = if authenticated_pointer {
109        object::write::RelocationFlags::MachO {
110            r_type: object::macho::ARM64_RELOC_AUTHENTICATED_POINTER,
111            r_pcrel: false,
112            r_length: 3,
113        }
114    } else if target.arch == Arch::Arm {
115        // FIXME(madsmtm): Remove once `object` supports 32-bit ARM relocations:
116        // https://github.com/gimli-rs/object/pull/757
117        object::write::RelocationFlags::MachO {
118            r_type: object::macho::ARM_RELOC_VANILLA,
119            r_pcrel: false,
120            r_length: 2,
121        }
122    } else {
123        object::write::RelocationFlags::Generic {
124            kind: object::RelocationKind::Absolute,
125            encoding: object::RelocationEncoding::Generic,
126            size: target.pointer_width as u8,
127        }
128    };
129
130    file.add_relocation(section, object::write::Relocation { offset, addend: 0, symbol, flags })?;
131
132    Ok(())
133}
134
135pub(super) fn add_version_to_llvm_target(
136    llvm_target: &str,
137    deployment_target: OSVersion,
138) -> String {
139    let mut components = llvm_target.split("-");
140    let arch = components.next().expect("apple target should have arch");
141    let vendor = components.next().expect("apple target should have vendor");
142    let os = components.next().expect("apple target should have os");
143    let environment = components.next();
144    match (&components.next(), &None) {
    (left_val, right_val) => {
        if !(*left_val == *right_val) {
            let kind = ::core::panicking::AssertKind::Eq;
            ::core::panicking::assert_failed(kind, &*left_val, &*right_val,
                ::core::option::Option::Some(format_args!("too many LLVM triple components")));
        }
    }
};assert_eq!(components.next(), None, "too many LLVM triple components");
145
146    if !!os.contains(|c: char| c.is_ascii_digit()) {
    {
        ::core::panicking::panic_fmt(format_args!("LLVM target must not already be versioned"));
    }
};assert!(
147        !os.contains(|c: char| c.is_ascii_digit()),
148        "LLVM target must not already be versioned"
149    );
150
151    let version = deployment_target.fmt_full();
152    if let Some(env) = environment {
153        // Insert version into OS, before environment
154        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}-{1}-{2}{3}-{4}", arch, vendor,
                os, version, env))
    })format!("{arch}-{vendor}-{os}{version}-{env}")
155    } else {
156        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}-{1}-{2}{3}", arch, vendor, os,
                version))
    })format!("{arch}-{vendor}-{os}{version}")
157    }
158}
159
160pub(super) fn get_sdk_root(sess: &Session) -> Option<PathBuf> {
161    let sdk_name = sdk_name(&sess.target);
162
163    // Attempt to invoke `xcrun` to find the SDK.
164    //
165    // Note that when cross-compiling from e.g. Linux, the `xcrun` binary may sometimes be provided
166    // as a shim by a cross-compilation helper tool. It usually isn't, but we still try nonetheless.
167    match xcrun_show_sdk_path(sdk_name, false) {
168        Ok((path, stderr)) => {
169            // Emit extra stderr, such as if `-verbose` was passed, or if `xcrun` emitted a warning.
170            if !stderr.is_empty() {
171                sess.dcx().emit_warn(XcrunSdkPathWarning { sdk_name, stderr });
172            }
173            Some(path)
174        }
175        Err(err) => {
176            // Failure to find the SDK is not a hard error, since the user might have specified it
177            // in a manner unknown to us (moreso if cross-compiling):
178            // - A compiler driver like `zig cc` which links using an internally bundled SDK.
179            // - Extra linker arguments (`-Clink-arg=-syslibroot`).
180            // - A custom linker or custom compiler driver.
181            //
182            // Though we still warn, since such cases are uncommon, and it is very hard to debug if
183            // you do not know the details.
184            //
185            // FIXME(madsmtm): Make this a lint, to allow deny warnings to work.
186            // (Or fix <https://github.com/rust-lang/rust/issues/21204>).
187            let mut diag = sess.dcx().create_warn(err);
188            diag.note(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("the SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file"))msg!("the SDK is needed by the linker to know where to find symbols in system libraries and for embedding the SDK version in the final object file"));
189
190            // Recognize common error cases, and give more Rust-specific error messages for those.
191            if let Some(developer_dir) = xcode_select_developer_dir() {
192                diag.arg("developer_dir", &developer_dir);
193                diag.note(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("found active developer directory at \"{$developer_dir}\""))msg!("found active developer directory at \"{$developer_dir}\""));
194                if developer_dir.as_os_str().to_string_lossy().contains("CommandLineTools") {
195                    if sdk_name != "MacOSX" {
196                        diag.help(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("when compiling for iOS, tvOS, visionOS or watchOS, you need a full installation of Xcode"))msg!("when compiling for iOS, tvOS, visionOS or watchOS, you need a full installation of Xcode"));
197                    }
198                }
199            } else {
200                diag.help(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("pass the path of an Xcode installation via the DEVELOPER_DIR environment variable, or an SDK with the SDKROOT environment variable"))msg!("pass the path of an Xcode installation via the DEVELOPER_DIR environment variable, or an SDK with the SDKROOT environment variable"));
201            }
202
203            diag.emit();
204            None
205        }
206    }
207}
208
209/// Invoke `xcrun --sdk $sdk_name --show-sdk-path` to get the SDK path.
210///
211/// The exact logic that `xcrun` uses is unspecified (see `man xcrun` for a few details), and may
212/// change between macOS and Xcode versions, but it roughly boils down to finding the active
213/// developer directory, and then invoking `xcodebuild -sdk $sdk_name -version` to get the SDK
214/// details.
215///
216/// Finding the developer directory is roughly done by looking at, in order:
217/// - The `DEVELOPER_DIR` environment variable.
218/// - The `/var/db/xcode_select_link` symlink (set by `xcode-select --switch`).
219/// - `/Applications/Xcode.app` (hardcoded fallback path).
220/// - `/Library/Developer/CommandLineTools` (hardcoded fallback path).
221///
222/// Note that `xcrun` caches its result, but with a cold cache this whole operation can be quite
223/// slow, especially so the first time it's run after a reboot.
224fn xcrun_show_sdk_path(
225    sdk_name: &'static str,
226    verbose: bool,
227) -> Result<(PathBuf, String), XcrunError> {
228    // Intentionally invoke the `xcrun` in PATH, since e.g. nixpkgs provide an `xcrun` shim, so we
229    // don't want to require `/usr/bin/xcrun`.
230    let mut cmd = Command::new("xcrun");
231    if verbose {
232        cmd.arg("--verbose");
233    }
234    // The `--sdk` parameter is the same as in xcodebuild, namely either an absolute path to an SDK,
235    // or the (lowercase) canonical name of an SDK.
236    cmd.arg("--sdk");
237    cmd.arg(&sdk_name.to_lowercase());
238    cmd.arg("--show-sdk-path");
239
240    // We do not stream stdout/stderr lines directly to the user, since whether they are warnings or
241    // errors depends on the status code at the end.
242    let output = cmd.output().map_err(|error| XcrunError::FailedInvoking {
243        sdk_name,
244        command_formatted: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", cmd))
    })format!("{cmd:?}"),
245        error,
246    })?;
247
248    // It is fine to do lossy conversion here, non-UTF-8 paths are quite rare on macOS nowadays
249    // (only possible with the HFS+ file system), and we only use it for error messages.
250    let stderr = String::from_utf8_lossy_owned(output.stderr);
251    if !stderr.is_empty() {
252        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_codegen_ssa/src/back/apple.rs:252",
                        "rustc_codegen_ssa::back::apple", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_codegen_ssa/src/back/apple.rs"),
                        ::tracing_core::__macro_support::Option::Some(252u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::apple"),
                        ::tracing_core::field::FieldSet::new(&["message", "stderr"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("original xcrun stderr")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&stderr as
                                            &dyn Value))])
            });
    } else { ; }
};debug!(stderr, "original xcrun stderr");
253    }
254
255    // Some versions of `xcodebuild` output beefy errors when invoked via `xcrun`,
256    // but these are usually red herrings.
257    let stderr = stderr
258        .lines()
259        .filter(|line| {
260            !line.contains("Writing error result bundle")
261                && !line.contains("Requested but did not find extension point with identifier")
262        })
263        .join("\n");
264
265    if output.status.success() {
266        Ok((stdout_to_path(output.stdout), stderr))
267    } else {
268        // Output both stdout and stderr, since shims of `xcrun` (such as the one provided by
269        // nixpkgs), do not always use stderr for errors.
270        let stdout = String::from_utf8_lossy_owned(output.stdout).trim().to_string();
271        Err(XcrunError::Unsuccessful {
272            sdk_name,
273            command_formatted: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", cmd))
    })format!("{cmd:?}"),
274            stdout,
275            stderr,
276        })
277    }
278}
279
280/// Invoke `xcode-select --print-path`, and return the current developer directory.
281///
282/// NOTE: We don't do any error handling here, this is only used as a canary in diagnostics (`xcrun`
283/// will have already emitted the relevant error information).
284fn xcode_select_developer_dir() -> Option<PathBuf> {
285    let mut cmd = Command::new("xcode-select");
286    cmd.arg("--print-path");
287    let output = cmd.output().ok()?;
288    if !output.status.success() {
289        return None;
290    }
291    Some(stdout_to_path(output.stdout))
292}
293
294fn stdout_to_path(mut stdout: Vec<u8>) -> PathBuf {
295    // Remove trailing newline.
296    if let Some(b'\n') = stdout.last() {
297        let _ = stdout.pop().unwrap();
298    }
299    #[cfg(unix)]
300    let path = <OsString as std::os::unix::ffi::OsStringExt>::from_vec(stdout);
301    #[cfg(not(unix))] // Not so important, this is mostly used on macOS
302    let path = OsString::from(String::from_utf8(stdout).expect("stdout must be UTF-8"));
303    PathBuf::from(path)
304}