1
Fork 0

Rollup merge of #131037 - madsmtm:move-llvm-target-versioning, r=petrochenkov

Move versioned Apple LLVM targets from `rustc_target` to `rustc_codegen_ssa`

Fully specified LLVM targets contain the OS version on macOS/iOS/tvOS/watchOS/visionOS, and this version depends on the deployment target environment variables like `MACOSX_DEPLOYMENT_TARGET`, `IPHONEOS_DEPLOYMENT_TARGET` etc.

We would like to move this to later in the compilation pipeline, both because it feels impure to access environment variables when fetching target information, but mostly because we need access to more information from https://github.com/rust-lang/rust/pull/130883 to do https://github.com/rust-lang/rust/issues/118204. See also https://github.com/rust-lang/rust/pull/129342#issuecomment-2335156119 for some discussion.

The first and second commit does the actual refactor, it should be a non-functional change, the third commit adds diagnostics for invalid deployment targets, which are now possible to do because we have access to the session.

Tested with the same commands as in https://github.com/rust-lang/rust/pull/130435.

r? ``````@petrochenkov``````
This commit is contained in:
Matthias Krüger 2024-11-02 08:33:10 +01:00 committed by GitHub
commit bb544f863f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 271 additions and 165 deletions

View file

@ -0,0 +1,171 @@
use std::env;
use std::fmt::{Display, from_fn};
use std::num::ParseIntError;
use rustc_session::Session;
use rustc_target::spec::Target;
use crate::errors::AppleDeploymentTarget;
#[cfg(test)]
mod tests;
pub(super) fn macho_platform(target: &Target) -> u32 {
match (&*target.os, &*target.abi) {
("macos", _) => object::macho::PLATFORM_MACOS,
("ios", "macabi") => object::macho::PLATFORM_MACCATALYST,
("ios", "sim") => object::macho::PLATFORM_IOSSIMULATOR,
("ios", _) => object::macho::PLATFORM_IOS,
("watchos", "sim") => object::macho::PLATFORM_WATCHOSSIMULATOR,
("watchos", _) => object::macho::PLATFORM_WATCHOS,
("tvos", "sim") => object::macho::PLATFORM_TVOSSIMULATOR,
("tvos", _) => object::macho::PLATFORM_TVOS,
("visionos", "sim") => object::macho::PLATFORM_XROSSIMULATOR,
("visionos", _) => object::macho::PLATFORM_XROS,
_ => unreachable!("tried to get Mach-O platform for non-Apple target"),
}
}
/// Deployment target or SDK version.
///
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
type OSVersion = (u16, u8, u8);
/// Parse an OS version triple (SDK version or deployment target).
fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
if let Some((major, minor)) = version.split_once('.') {
let major = major.parse()?;
if let Some((minor, patch)) = minor.split_once('.') {
Ok((major, minor.parse()?, patch.parse()?))
} else {
Ok((major, minor.parse()?, 0))
}
} else {
Ok((version.parse()?, 0, 0))
}
}
pub fn pretty_version(version: OSVersion) -> impl Display {
let (major, minor, patch) = version;
from_fn(move |f| {
write!(f, "{major}.{minor}")?;
if patch != 0 {
write!(f, ".{patch}")?;
}
Ok(())
})
}
/// Minimum operating system versions currently supported by `rustc`.
fn os_minimum_deployment_target(os: &str) -> OSVersion {
// 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
// ```
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"),
}
}
/// 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>
fn minimum_deployment_target(target: &Target) -> OSVersion {
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, _, _) => os_minimum_deployment_target(os),
}
}
/// Name of the environment variable used to fetch the deployment target on the given OS.
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"),
}
}
/// Get the deployment target based on the standard environment variables, or fall back to the
/// minimum version supported by `rustc`.
pub fn deployment_target(sess: &Session) -> OSVersion {
let min = minimum_deployment_target(&sess.target);
let env_var = deployment_target_env_var(&sess.target.os);
if let Ok(deployment_target) = env::var(env_var) {
match parse_version(&deployment_target) {
Ok(version) => {
let os_min = os_minimum_deployment_target(&sess.target.os);
// It is common that the deployment target is set a bit too low, for example on
// macOS Aarch64 to also target older x86_64. So we only want to warn when variable
// is lower than the minimum OS supported by rustc, not when the variable is lower
// than the minimum for a specific target.
if version < os_min {
sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
env_var,
version: pretty_version(version).to_string(),
os_min: pretty_version(os_min).to_string(),
});
}
// Raise the deployment target to the minimum supported.
version.max(min)
}
Err(error) => {
sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
min
}
}
} else {
// If no deployment target variable is set, default to the minimum found above.
min
}
}
pub(super) fn add_version_to_llvm_target(
llvm_target: &str,
deployment_target: OSVersion,
) -> String {
let mut components = llvm_target.split("-");
let arch = components.next().expect("apple target should have arch");
let vendor = components.next().expect("apple target should have vendor");
let os = components.next().expect("apple target should have os");
let environment = components.next();
assert_eq!(components.next(), None, "too many LLVM triple components");
let (major, minor, patch) = deployment_target;
assert!(
!os.contains(|c: char| c.is_ascii_digit()),
"LLVM target must not already be versioned"
);
if let Some(env) = environment {
// Insert version into OS, before environment
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
} else {
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
}
}

View file

@ -0,0 +1,21 @@
use super::{add_version_to_llvm_target, parse_version};
#[test]
fn test_add_version_to_llvm_target() {
assert_eq!(
add_version_to_llvm_target("aarch64-apple-macosx", (10, 14, 1)),
"aarch64-apple-macosx10.14.1"
);
assert_eq!(
add_version_to_llvm_target("aarch64-apple-ios-simulator", (16, 1, 0)),
"aarch64-apple-ios16.1.0-simulator"
);
}
#[test]
fn test_parse_version() {
assert_eq!(parse_version("10"), Ok((10, 0, 0)));
assert_eq!(parse_version("10.12"), Ok((10, 12, 0)));
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
}

View file

@ -40,7 +40,7 @@ use rustc_target::spec::crt_objects::CrtObjects;
use rustc_target::spec::{
Cc, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures,
LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SplitDebuginfo, current_apple_deployment_target,
SplitDebuginfo,
};
use tempfile::Builder as TempFileBuilder;
use tracing::{debug, info, warn};
@ -50,6 +50,7 @@ use super::command::Command;
use super::linker::{self, Linker};
use super::metadata::{MetadataPosition, create_wrapper_file};
use super::rpath::{self, RPathConfig};
use super::{apple, versioned_llvm_target};
use crate::{
CodegenResults, CompiledModule, CrateInfo, NativeLib, common, errors,
looks_like_rust_object_file,
@ -2447,7 +2448,7 @@ fn add_order_independent_options(
if flavor == LinkerFlavor::Llbc {
cmd.link_args(&[
"--target",
sess.target.llvm_target.as_ref(),
&versioned_llvm_target(sess),
"--target-cpu",
&codegen_results.crate_info.target_cpu,
]);
@ -3039,7 +3040,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
};
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
let (major, minor, patch) = apple::deployment_target(sess);
let min_version = format!("{major}.{minor}.{patch}");
// The SDK version is used at runtime when compiling with a newer SDK / version of Xcode:
@ -3109,7 +3110,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
// The presence of `-mmacosx-version-min` makes CC default to
// macOS, and it sets the deployment target.
let (major, minor, patch) = current_apple_deployment_target(&sess.target);
let (major, minor, patch) = apple::deployment_target(sess);
// Intentionally pass this as a single argument, Clang doesn't
// seem to like it otherwise.
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
@ -3119,7 +3120,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
//
// We avoid `-m32`/`-m64`, as this is already encoded by `-arch`.
} else {
cmd.cc_args(&["-target", &sess.target.llvm_target]);
cmd.cc_args(&["-target", &versioned_llvm_target(sess)]);
}
}
}
@ -3345,7 +3346,7 @@ fn add_lld_args(
// targeting a different linker flavor on macOS, and that's also always
// the case when targeting WASM.
if sess.target.linker_flavor != sess.host.linker_flavor {
cmd.cc_arg(format!("--target={}", sess.target.llvm_target));
cmd.cc_arg(format!("--target={}", versioned_llvm_target(sess)));
}
}
}

View file

@ -22,6 +22,8 @@ use rustc_span::sym;
use rustc_target::abi::Endian;
use rustc_target::spec::{RelocModel, Target, ef_avr_arch};
use super::apple;
/// The default metadata loader. This is used by cg_llvm and cg_clif.
///
/// # Metadata location
@ -238,7 +240,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E);
}
file.set_macho_build_version(macho_object_build_version_for_target(&sess.target))
file.set_macho_build_version(macho_object_build_version_for_target(sess))
}
if binary_format == BinaryFormat::Coff {
// Disable the default mangler to avoid mangling the special "@feat.00" symbol name.
@ -392,7 +394,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
///
/// Since Xcode 15, Apple's LD apparently requires object files to use this load command, so this
/// returns the `MachOBuildVersion` for the target to do so.
fn macho_object_build_version_for_target(target: &Target) -> object::write::MachOBuildVersion {
fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion {
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
@ -400,9 +402,8 @@ fn macho_object_build_version_for_target(target: &Target) -> object::write::Mach
(major << 16) | (minor << 8) | patch
}
let platform =
rustc_target::spec::current_apple_platform(target).expect("unknown Apple target OS");
let min_os = rustc_target::spec::current_apple_deployment_target(target);
let platform = apple::macho_platform(&sess.target);
let min_os = apple::deployment_target(sess);
let mut build_version = object::write::MachOBuildVersion::default();
build_version.platform = platform;

View file

@ -1,3 +1,8 @@
use std::borrow::Cow;
use rustc_session::Session;
pub mod apple;
pub mod archive;
pub(crate) mod command;
pub mod link;
@ -7,3 +12,19 @@ pub mod metadata;
pub(crate) mod rpath;
pub mod symbol_export;
pub mod write;
/// The target triple depends on the deployment target, and is required to
/// enable features such as cross-language LTO, and for picking the right
/// Mach-O commands.
///
/// Certain optimizations also depend on the deployment target.
pub fn versioned_llvm_target(sess: &Session) -> Cow<'_, str> {
if sess.target.is_like_osx {
apple::add_version_to_llvm_target(&sess.target.llvm_target, apple::deployment_target(sess))
.into()
} else {
// FIXME(madsmtm): Certain other targets also include a version,
// we might want to move that here as well.
Cow::Borrowed(&sess.target.llvm_target)
}
}

View file

@ -2,6 +2,7 @@
use std::borrow::Cow;
use std::io::Error;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
@ -539,6 +540,14 @@ pub(crate) struct UnsupportedArch<'a> {
pub os: &'a str,
}
#[derive(Diagnostic)]
pub(crate) enum AppleDeploymentTarget {
#[diag(codegen_ssa_apple_deployment_target_invalid)]
Invalid { env_var: &'static str, error: ParseIntError },
#[diag(codegen_ssa_apple_deployment_target_too_low)]
TooLow { env_var: &'static str, version: String, os_min: String },
}
#[derive(Diagnostic)]
pub(crate) enum AppleSdkRootError<'a> {
#[diag(codegen_ssa_apple_sdk_error_sdk_path)]

View file

@ -6,6 +6,7 @@
#![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(debug_closure_helpers)]
#![feature(file_buffered)]
#![feature(if_let_guard)]
#![feature(let_chains)]