From 08a1b4258cabe679f734e663becf4e8f0cff76c3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 3 Aug 2026 19:55:14 +0200 Subject: [PATCH 1/2] implement -Zllvm-target-feature --- compiler/rustc_codegen_llvm/src/back/write.rs | 7 ++-- .../rustc_codegen_llvm/src/diagnostics.rs | 7 ++++ compiler/rustc_codegen_llvm/src/llvm_util.rs | 29 +++++++++++++++-- compiler/rustc_session/src/options.rs | 4 +++ .../ui/target-feature/llvm-target-feature.rs | 32 +++++++++++++++++++ 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 tests/ui/target-feature/llvm-target-feature.rs diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 94883a94f089a..edf52e67b434b 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -100,14 +100,17 @@ fn write_output_file<'ll>( result.into_result().unwrap_or_else(|()| llvm_err(dcx, LlvmError::WriteOutput { path: output })) } +/// If `for_cfg` is `true` then we are creating this machine for the purpose of populating +/// [`rustc_codegen_ssa::TargetConfig`] based on what LLVM actually enables in this configuration. +/// `-Ctarget-feature` should be ignored in that case since it is already processed separately. pub(crate) fn create_informational_target_machine( sess: &Session, - only_base_features: bool, + for_cfg: bool, ) -> OwnedTargetMachine { let config = TargetMachineFactoryConfig { split_dwarf_file: None, output_obj_file: None }; // Can't use query system here quite yet because this function is invoked before the query // system/tcx is set up. - let features = llvm_util::global_llvm_features(sess, only_base_features); + let features = llvm_util::global_llvm_features(sess, for_cfg); target_machine_factory(sess, config::OptLevel::No, &features)(sess.dcx(), config) } diff --git a/compiler/rustc_codegen_llvm/src/diagnostics.rs b/compiler/rustc_codegen_llvm/src/diagnostics.rs index bcbafab585b40..50a11417513a5 100644 --- a/compiler/rustc_codegen_llvm/src/diagnostics.rs +++ b/compiler/rustc_codegen_llvm/src/diagnostics.rs @@ -245,3 +245,10 @@ pub(crate) struct IntrinsicWrongArch<'a> { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag("unknown feature specified for `-Zllvm-target-feature`: `{$feature}`")] +#[note("features must begin with a `+` to enable or `-` to disable it")] +pub(crate) struct UnknownLlvmTargetFeaturePrefix<'a> { + pub feature: &'a str, +} diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 9ad14925afb14..6892e616e1f11 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -646,7 +646,11 @@ fn llvm_features_by_flags(sess: &Session, features: &mut Vec) { /// The list of LLVM features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`, /// `--target` and similar). -pub(crate) fn global_llvm_features(sess: &Session, only_base_features: bool) -> Vec { +/// +/// If `for_cfg` is `true` then we are assembling the feature list for the purpose of populating +/// [`rustc_codegen_ssa::TargetConfig`] based on what LLVM actually enables in this configuration. +/// `-Ctarget-feature` should be ignored in that case since it is already processed separately. +pub(crate) fn global_llvm_features(sess: &Session, for_cfg: bool) -> Vec { // Features that come earlier are overridden by conflicting features later in the string. // Typically we'll want more explicit settings to override the implicit ones, so: // @@ -725,14 +729,33 @@ pub(crate) fn global_llvm_features(sess: &Session, only_base_features: bool) -> // Features implied by an implicit or explicit `--target`. target_features::target_spec_to_backend_features(sess, &mut extend_backend_features); - // -Ctarget-features - if !only_base_features { + // -Ctarget-features. Skipped for `cfg` as there we parse -Ctarget-features directly instead of + // going via an LLVM target machine (which avoids accidentally picking up LLVM-level target + // feature implications that we do not want). + if !for_cfg { target_features::flag_to_backend_features(sess, extend_backend_features); } // We add this in the "base target" so that these show up in `sess.unstable_target_features`. llvm_features_by_flags(sess, &mut features); + // `-Zllvm-target-features`, all the way at the end to overwrite everything. + // Should be picked up by `cfg` (e.g. if someone enables AVX this way). + for feature in sess.opts.unstable_opts.llvm_target_feature.split(',') { + if feature.is_empty() { + continue; + } + if feature.starts_with('+') || feature.starts_with('-') { + features.push(feature.to_owned()); + } else { + // LLVM seems to silently ignore entries without leading `+`/`-`. Let's emit a warning + // to avoid confusion. But only emit this warning once, under `for_cfg`. + if for_cfg { + sess.dcx().emit_warn(diagnostics::UnknownLlvmTargetFeaturePrefix { feature }); + } + } + } + features } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index c46b6418754fa..51d48b39fb35e 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2582,6 +2582,10 @@ options! { "a list of module flags to pass to LLVM (space separated)"), llvm_plugins: Vec = (Vec::new(), parse_list, [TRACKED], "a list LLVM plugins to enable (space separated)"), + llvm_target_feature: String = (String::new(), parse_target_feature, [TRACKED] { TARGET_MODIFIER: LlvmTargetFeature }, + "enable/disable LLVM-level target features. \ + This feature is unsafe and can cause ABI issues and compiler crashes, \ + because LLVM does not support all target feature combinations."), llvm_time_trace: bool = (false, parse_bool, [UNTRACKED], "generate JSON tracing data file from LLVM data (default: no)"), llvm_writable: bool = (false, parse_bool, [TRACKED], diff --git a/tests/ui/target-feature/llvm-target-feature.rs b/tests/ui/target-feature/llvm-target-feature.rs new file mode 100644 index 0000000000000..6b94574d3a8e4 --- /dev/null +++ b/tests/ui/target-feature/llvm-target-feature.rs @@ -0,0 +1,32 @@ +//! Sometimes `-Ctarget-cpu` can *disable* target features that would by default be enabled on the +//! current target. Ensure that we catch the case where those target features are important for the +//! ABI. + +//@ compile-flags: --crate-type=lib +//@ compile-flags: --target=x86_64-unknown-linux-gnu +//@ compile-flags: -Zllvm-target-feature=+avx2 +//@ needs-llvm-components: x86 + +//@ build-pass +//@ ignore-backends: gcc +//@ add-minicore + +#![feature(no_core, intrinsics, rustc_attrs)] +#![no_core] +#![allow(improper_ctypes_definitions)] + +extern crate minicore; +use minicore::*; + +// Also test the ABI checks by using `extern "C"` +#[no_mangle] // force codegen +pub extern "C" fn do_thing(x: simd::f32x8, y: simd::f32x8) -> simd::f32x8 { + #[rustc_intrinsic] + #[rustc_nounwind] + pub const unsafe fn simd_add(x: T, y: T) -> T; + + unsafe { simd_add(x, y) } +} + +#[cfg(not(target_feature = "avx2"))] +compile_error!("the avx2 cfg did not get set"); From c82207c46e5cefe4ddb000232aec281c28f0f6a8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 3 Aug 2026 19:59:25 +0200 Subject: [PATCH 2/2] improve error message on missing target feature prefix --- compiler/rustc_codegen_llvm/src/diagnostics.rs | 2 +- compiler/rustc_codegen_ssa/src/diagnostics.rs | 2 +- tests/ui/target-feature/missing-plusminus-2.rs | 2 +- tests/ui/target-feature/missing-plusminus-2.stderr | 2 +- tests/ui/target-feature/missing-plusminus-llvm.rs | 13 +++++++++++++ .../ui/target-feature/missing-plusminus-llvm.stderr | 6 ++++++ tests/ui/target-feature/missing-plusminus.rs | 2 +- tests/ui/target-feature/missing-plusminus.stderr | 2 +- 8 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 tests/ui/target-feature/missing-plusminus-llvm.rs create mode 100644 tests/ui/target-feature/missing-plusminus-llvm.stderr diff --git a/compiler/rustc_codegen_llvm/src/diagnostics.rs b/compiler/rustc_codegen_llvm/src/diagnostics.rs index 50a11417513a5..ea29683b9d289 100644 --- a/compiler/rustc_codegen_llvm/src/diagnostics.rs +++ b/compiler/rustc_codegen_llvm/src/diagnostics.rs @@ -247,7 +247,7 @@ pub(crate) struct IntrinsicWrongArch<'a> { } #[derive(Diagnostic)] -#[diag("unknown feature specified for `-Zllvm-target-feature`: `{$feature}`")] +#[diag("ignoring feature with missing prefix in `-Zllvm-target-feature`: `{$feature}`")] #[note("features must begin with a `+` to enable or `-` to disable it")] pub(crate) struct UnknownLlvmTargetFeaturePrefix<'a> { pub feature: &'a str, diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 6b182d795a9ec..e6aab553072f2 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1198,7 +1198,7 @@ pub(crate) struct XcrunSdkPathWarning { pub(crate) struct Aarch64SoftfloatNeon; #[derive(Diagnostic)] -#[diag("unknown feature specified for `-Ctarget-feature`: `{$feature}`")] +#[diag("ignoring feature with missing prefix in `-Ctarget-feature`: `{$feature}`")] #[note("features must begin with a `+` to enable or `-` to disable it")] pub(crate) struct UnknownCTargetFeaturePrefix<'a> { pub feature: &'a str, diff --git a/tests/ui/target-feature/missing-plusminus-2.rs b/tests/ui/target-feature/missing-plusminus-2.rs index 06291ab23ad5e..f8b1ff58d535c 100644 --- a/tests/ui/target-feature/missing-plusminus-2.rs +++ b/tests/ui/target-feature/missing-plusminus-2.rs @@ -5,4 +5,4 @@ #![feature(no_core)] #![no_core] -//~? WARN unknown feature specified for `-Ctarget-feature`: `rdrand` +//~? WARN ignoring feature with missing prefix in `-Ctarget-feature`: `rdrand` diff --git a/tests/ui/target-feature/missing-plusminus-2.stderr b/tests/ui/target-feature/missing-plusminus-2.stderr index 5ed2652a06df2..d2bc227ccff7c 100644 --- a/tests/ui/target-feature/missing-plusminus-2.stderr +++ b/tests/ui/target-feature/missing-plusminus-2.stderr @@ -1,4 +1,4 @@ -warning: unknown feature specified for `-Ctarget-feature`: `rdrand` +warning: ignoring feature with missing prefix in `-Ctarget-feature`: `rdrand` | = note: features must begin with a `+` to enable or `-` to disable it diff --git a/tests/ui/target-feature/missing-plusminus-llvm.rs b/tests/ui/target-feature/missing-plusminus-llvm.rs new file mode 100644 index 0000000000000..fe3c2cfcf2bd3 --- /dev/null +++ b/tests/ui/target-feature/missing-plusminus-llvm.rs @@ -0,0 +1,13 @@ +//@ compile-flags: -Zllvm-target-feature=banana --crate-type=rlib +//@ build-pass + +//@ ignore-backends: gcc +//@ add-minicore + +#![feature(no_core, intrinsics, rustc_attrs)] +#![no_core] + +extern crate minicore; +use minicore::*; + +//~? WARN ignoring feature with missing prefix in `-Zllvm-target-feature`: `banana` diff --git a/tests/ui/target-feature/missing-plusminus-llvm.stderr b/tests/ui/target-feature/missing-plusminus-llvm.stderr new file mode 100644 index 0000000000000..90ae882577e32 --- /dev/null +++ b/tests/ui/target-feature/missing-plusminus-llvm.stderr @@ -0,0 +1,6 @@ +warning: ignoring feature with missing prefix in `-Zllvm-target-feature`: `banana` + | + = note: features must begin with a `+` to enable or `-` to disable it + +warning: 1 warning emitted + diff --git a/tests/ui/target-feature/missing-plusminus.rs b/tests/ui/target-feature/missing-plusminus.rs index e8356e0fa3552..4613862c803d6 100644 --- a/tests/ui/target-feature/missing-plusminus.rs +++ b/tests/ui/target-feature/missing-plusminus.rs @@ -1,4 +1,4 @@ //@ compile-flags: -Ctarget-feature=banana --crate-type=rlib //@ build-pass -//~? WARN unknown feature specified for `-Ctarget-feature`: `banana` +//~? WARN ignoring feature with missing prefix in `-Ctarget-feature`: `banana` diff --git a/tests/ui/target-feature/missing-plusminus.stderr b/tests/ui/target-feature/missing-plusminus.stderr index 93abf35080579..c0f49516dd820 100644 --- a/tests/ui/target-feature/missing-plusminus.stderr +++ b/tests/ui/target-feature/missing-plusminus.stderr @@ -1,4 +1,4 @@ -warning: unknown feature specified for `-Ctarget-feature`: `banana` +warning: ignoring feature with missing prefix in `-Ctarget-feature`: `banana` | = note: features must begin with a `+` to enable or `-` to disable it