blob: aa6d1ec7009e79ae2eb12d8568d08d947277d0f0 [file] [log] [blame]
use std::borrow::Cow;
use std::env;
use std::fmt::{Display, from_fn};
use std::num::ParseIntError;
use std::str::FromStr;
use crate::spec::{
BinaryFormat, Cc, DebuginfoKind, FloatAbi, FramePointer, LinkerFlavor, Lld, RustcAbi,
SplitDebuginfo, StackProbeType, StaticCow, Target, TargetOptions, cvs,
};
#[cfg(test)]
mod tests;
use Arch::*;
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq)]
pub(crate) enum Arch {
Armv7k,
Armv7s,
Arm64,
Arm64e,
Arm64_32,
I386,
I686,
X86_64,
X86_64h,
}
impl Arch {
fn target_name(self) -> &'static str {
match self {
Armv7k => "armv7k",
Armv7s => "armv7s",
Arm64 => "arm64",
Arm64e => "arm64e",
Arm64_32 => "arm64_32",
I386 => "i386",
I686 => "i686",
X86_64 => "x86_64",
X86_64h => "x86_64h",
}
}
pub(crate) fn target_arch(self) -> Cow<'static, str> {
Cow::Borrowed(match self {
Armv7k | Armv7s => "arm",
Arm64 | Arm64e | Arm64_32 => "aarch64",
I386 | I686 => "x86",
X86_64 | X86_64h => "x86_64",
})
}
fn target_cpu(self, abi: TargetAbi) -> &'static str {
match self {
Armv7k => "cortex-a8",
Armv7s => "swift", // iOS 10 is only supported on iPhone 5 or higher.
Arm64 => match abi {
TargetAbi::Normal => "apple-a7",
TargetAbi::Simulator => "apple-a12",
TargetAbi::MacCatalyst => "apple-a12",
},
Arm64e => "apple-a12",
Arm64_32 => "apple-s4",
// Only macOS 10.12+ is supported, which means
// all x86_64/x86 CPUs must be running at least penryn
// https://github.com/llvm/llvm-project/blob/01f924d0e37a5deae51df0d77e10a15b63aa0c0f/clang/lib/Driver/ToolChains/Arch/X86.cpp#L79-L82
I386 | I686 => "penryn",
X86_64 => "penryn",
// Note: `core-avx2` is slightly more advanced than `x86_64h`, see
// comments (and disabled features) in `x86_64h_apple_darwin` for
// details. It is a higher baseline then `penryn` however.
X86_64h => "core-avx2",
}
}
fn stack_probes(self) -> StackProbeType {
match self {
Armv7k | Armv7s => StackProbeType::None,
Arm64 | Arm64e | Arm64_32 | I386 | I686 | X86_64 | X86_64h => StackProbeType::Inline,
}
}
}
#[derive(Copy, Clone, PartialEq)]
pub(crate) enum TargetAbi {
Normal,
Simulator,
MacCatalyst,
}
impl TargetAbi {
fn target_abi(self) -> &'static str {
match self {
Self::Normal => "",
Self::MacCatalyst => "macabi",
Self::Simulator => "sim",
}
}
}
/// Get the base target options, unversioned 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 mut opts = TargetOptions {
abi: abi.target_abi().into(),
llvm_floatabi: Some(FloatAbi::Hard),
os: os.into(),
cpu: arch.target_cpu(abi).into(),
link_env_remove: link_env_remove(os),
vendor: "apple".into(),
linker_flavor: LinkerFlavor::Darwin(Cc::Yes, Lld::No),
// macOS has -dead_strip, which doesn't rely on function_sections
function_sections: false,
dynamic_linking: true,
families: cvs!["unix"],
is_like_darwin: true,
binary_format: BinaryFormat::MachO,
// LLVM notes that macOS 10.11+ and iOS 9+ default
// to v4, so we do the same.
// https://github.com/llvm/llvm-project/blob/378778a0d10c2f8d5df8ceff81f95b6002984a4b/clang/lib/Driver/ToolChains/Darwin.cpp#L1203
default_dwarf_version: 4,
frame_pointer: match arch {
// clang ignores `-fomit-frame-pointer` for Armv7, it only accepts `-momit-leaf-frame-pointer`
Armv7k | Armv7s => FramePointer::Always,
// clang supports omitting frame pointers for the rest, but... don't?
Arm64 | Arm64e | Arm64_32 => FramePointer::NonLeaf,
I386 | I686 | X86_64 | X86_64h => FramePointer::Always,
},
has_rpath: true,
dll_suffix: ".dylib".into(),
archive_format: "darwin".into(),
// Thread locals became available with iOS 8 and macOS 10.7,
// and both are far below our minimum.
has_thread_local: true,
abi_return_struct_as_int: true,
emit_debug_gdb_scripts: false,
eh_frame_header: false,
stack_probes: arch.stack_probes(),
debuginfo_kind: DebuginfoKind::DwarfDsym,
// The historical default for macOS targets is to run `dsymutil` which
// generates a packed version of debuginfo split from the main file.
split_debuginfo: SplitDebuginfo::Packed,
supported_split_debuginfo: Cow::Borrowed(&[
SplitDebuginfo::Packed,
SplitDebuginfo::Unpacked,
SplitDebuginfo::Off,
]),
// This environment variable is pretty magical but is intended for
// producing deterministic builds. This was first discovered to be used
// by the `ar` tool as a way to control whether or not mtime entries in
// the archive headers were set to zero or not. It appears that
// eventually the linker got updated to do the same thing and now reads
// this environment variable too in recent versions.
//
// For some more info see the commentary on #47086
link_env: Cow::Borrowed(&[(Cow::Borrowed("ZERO_AR_DATE"), Cow::Borrowed("1"))]),
..Default::default()
};
if matches!(arch, Arch::I386 | Arch::I686) {
// All Apple x86-32 targets have SSE2.
opts.rustc_abi = Some(RustcAbi::X86Sse2);
}
(opts, unversioned_llvm_target(os, arch, abi), arch.target_arch())
}
/// Generate part of the LLVM target triple.
///
/// See `rustc_codegen_ssa::back::versioned_llvm_target` for the full triple passed to LLVM and
/// Clang.
fn unversioned_llvm_target(os: &str, arch: Arch, abi: TargetAbi) -> StaticCow<str> {
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}{environment}").into()
}
fn link_env_remove(os: &'static str) -> StaticCow<[StaticCow<str>]> {
// Apple platforms only officially support macOS as a host for any compilation.
//
// If building for macOS, we go ahead and remove any erroneous environment state
// that's only applicable to cross-OS compilation. Always leave anything for the
// host OS alone though.
if os == "macos" {
let mut env_remove = Vec::with_capacity(2);
// Remove the `SDKROOT` environment variable if it's clearly set for the wrong platform, which
// may occur when we're linking a custom build script while targeting iOS for example.
if let Ok(sdkroot) = env::var("SDKROOT") {
if sdkroot.contains("iPhoneOS.platform")
|| sdkroot.contains("iPhoneSimulator.platform")
|| sdkroot.contains("AppleTVOS.platform")
|| sdkroot.contains("AppleTVSimulator.platform")
|| sdkroot.contains("WatchOS.platform")
|| sdkroot.contains("WatchSimulator.platform")
|| sdkroot.contains("XROS.platform")
|| sdkroot.contains("XRSimulator.platform")
{
env_remove.push("SDKROOT".into())
}
}
// Additionally, `IPHONEOS_DEPLOYMENT_TARGET` must not be set when using the Xcode linker at
// "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld",
// although this is apparently ignored when using the linker at "/usr/bin/ld".
env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".into());
env_remove.push("TVOS_DEPLOYMENT_TARGET".into());
env_remove.push("XROS_DEPLOYMENT_TARGET".into());
env_remove.into()
} else {
// Otherwise if cross-compiling for a different OS/SDK (including Mac Catalyst), remove any part
// of the linking environment that's wrong and reversed.
cvs!["MACOSX_DEPLOYMENT_TARGET"]
}
}
/// Deployment target or SDK version.
///
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct OSVersion {
pub major: u16,
pub minor: u8,
pub patch: u8,
}
impl FromStr for OSVersion {
type Err = ParseIntError;
/// Parse an OS version triple (SDK version or deployment target).
fn from_str(version: &str) -> Result<Self, ParseIntError> {
if let Some((major, minor)) = version.split_once('.') {
let major = major.parse()?;
if let Some((minor, patch)) = minor.split_once('.') {
Ok(Self { major, minor: minor.parse()?, patch: patch.parse()? })
} else {
Ok(Self { major, minor: minor.parse()?, patch: 0 })
}
} else {
Ok(Self { major: version.parse()?, minor: 0, patch: 0 })
}
}
}
impl OSVersion {
pub fn new(major: u16, minor: u8, patch: u8) -> Self {
Self { major, minor, patch }
}
pub fn fmt_pretty(self) -> impl Display {
let Self { major, minor, patch } = self;
from_fn(move |f| {
write!(f, "{major}.{minor}")?;
if patch != 0 {
write!(f, ".{patch}")?;
}
Ok(())
})
}
pub fn fmt_full(self) -> impl Display {
let Self { major, minor, patch } = self;
from_fn(move |f| write!(f, "{major}.{minor}.{patch}"))
}
/// Minimum operating system versions currently supported by `rustc`.
pub fn os_minimum_deployment_target(os: &str) -> Self {
// When bumping a version in here, remember to update the platform-support docs too.
//
// NOTE: The defaults may change in future `rustc` versions, so if you are looking for the
// default deployment target, prefer:
// ```
// $ rustc --print deployment-target
// ```
let (major, minor, patch) = 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"),
};
Self { major, minor, patch }
}
/// The deployment target for the given target.
///
/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense
/// to raise the minimum OS version.
///
/// This matches what LLVM does, see in part:
/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
pub fn minimum_deployment_target(target: &Target) -> Self {
let (major, minor, patch) = match (&*target.os, &*target.arch, &*target.abi) {
("macos", "aarch64", _) => (11, 0, 0),
("ios", "aarch64", "macabi") => (14, 0, 0),
("ios", "aarch64", "sim") => (14, 0, 0),
("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
// Mac Catalyst defaults to 13.1 in Clang.
("ios", _, "macabi") => (13, 1, 0),
("tvos", "aarch64", "sim") => (14, 0, 0),
("watchos", "aarch64", "sim") => (7, 0, 0),
(os, _, _) => return Self::os_minimum_deployment_target(os),
};
Self { major, minor, patch }
}
}
/// Name of the environment variable used to fetch the deployment target on the given OS.
pub fn deployment_target_env_var(os: &str) -> &'static str {
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"),
}
}