1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_data_structures::unord::{UnordMap, UnordSet};
3use rustc_hir::attrs::InstructionSetAttr;
4use rustc_hir::def::DefKind;
5use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
6use rustc_middle::middle::codegen_fn_attrs::{TargetFeature, TargetFeatureKind};
7use rustc_middle::query::Providers;
8use rustc_middle::ty::TyCtxt;
9use rustc_session::Session;
10use rustc_session::diagnostics::feature_err;
11use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
12use rustc_span::{Span, Symbol, edit_distance, sym};
13use rustc_target::spec::{Arch, SanitizerSet};
14use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
15use smallvec::SmallVec;
1617use crate::diagnostics::{CrossArchFeatureNote, FeatureNotValid, FeatureNotValidHint};
18use crate::{diagnostics, target_features};
1920/// Compute the enabled target features from the `#[target_feature]` function attribute.
21/// Enabled target features are added to `target_features`.
22pub(crate) fn from_target_feature_attr(
23 tcx: TyCtxt<'_>,
24 did: LocalDefId,
25 features: &[(Symbol, Span)],
26 was_forced: bool,
27 rust_target_features: &UnordMap<String, target_features::Stability>,
28 target_features: &mut Vec<TargetFeature>,
29) {
30let rust_features = tcx.features();
31let abi_feature_constraints = tcx.sess.target.abi_required_features();
32for &(feature, feature_span) in features {
33let feature_str = feature.as_str();
34let Some(stability) = rust_target_features.get(feature_str) else {
35let hint = if let Some(stripped) = feature_str.strip_prefix('+')
36 && rust_target_features.contains_key(stripped)
37 {
38 FeatureNotValidHint::RemovePlusFromFeatureName { span: feature_span, stripped }
39 } else {
40// Show the 5 feature names that are most similar to the input.
41let mut valid_names: Vec<_> =
42 rust_target_features.keys().map(|name| name.as_str()).into_sorted_stable_ord();
43 valid_names.sort_by_key(|name| {
44 edit_distance::edit_distance(name, feature.as_str(), 5).unwrap_or(usize::MAX)
45 });
46 valid_names.truncate(5);
4748 FeatureNotValidHint::ValidFeatureNames {
49 possibilities: valid_names.into(),
50 and_more: rust_target_features.len().saturating_sub(5),
51 }
52 };
53 tcx.dcx().emit_err(FeatureNotValid {
54 feature: feature_str,
55 span: feature_span,
56 hint,
57 cross_arch: {
58let arches = rustc_target::target_features::feature_to_arch_names(feature_str);
59match arches {
60 [] => None,
61 [arch] => Some(CrossArchFeatureNote::Single { feature: feature_str, arch }),
62 [..] => Some(CrossArchFeatureNote::Multiple {
63 feature: feature_str,
64 arches: arches.into(),
65 }),
66 }
67 },
68 });
69continue;
70 };
7172// Only allow target features whose feature gates have been enabled
73 // and which are permitted to be toggled.
74if let Err(reason) = stability.toggle_allowed() {
75 tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr {
76 span: feature_span,
77 feature: feature_str,
78 reason,
79 });
80 } else if let Some(nightly_feature) = stability.requires_nightly(/* in_cfg */ false)
81 && !rust_features.enabled(nightly_feature)
82 {
83let explain = if stability.is_cfg_stable_toggle_unstable() {
84::alloc::__export::must_use({
::alloc::fmt::format(format_args!("the target feature `{0}` is allowed in cfg but unstable otherwise",
feature))
})format!("the target feature `{feature}` is allowed in cfg but unstable otherwise")85 } else {
86::alloc::__export::must_use({
::alloc::fmt::format(format_args!("the target feature `{0}` is currently unstable",
feature))
})format!("the target feature `{feature}` is currently unstable")87 };
88 feature_err(&tcx.sess, nightly_feature, feature_span, explain).emit();
89 } else {
90// Add this and the implied features.
91for &name in tcx.implied_target_features(feature) {
92// But ensure the ABI does not forbid enabling this.
93 // Here we do assume that the backend doesn't add even more implied features
94 // we don't know about, at least no features that would have ABI effects!
95 // We skip this logic in rustdoc, where we want to allow all target features of
96 // all targets, so we can't check their ABI compatibility and anyway we are not
97 // generating code so "it's fine".
98if !tcx.sess.opts.actually_rustdoc {
99if abi_feature_constraints.incompatible.contains(&name.as_str()) {
100// For "neon" specifically, we emit an FCW instead of a hard error.
101 // See <https://github.com/rust-lang/rust/issues/134375>.
102if tcx.sess.target.arch == Arch::AArch64 && name.as_str() == "neon" {
103 tcx.emit_node_span_lint(
104 AARCH64_SOFTFLOAT_NEON,
105 tcx.local_def_id_to_hir_id(did),
106 feature_span,
107 diagnostics::Aarch64SoftfloatNeon,
108 );
109 } else {
110 tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr {
111 span: feature_span,
112 feature: name.as_str(),
113 reason: "this feature is incompatible with the target ABI",
114 });
115 }
116 }
117 }
118let kind = if name != feature {
119 TargetFeatureKind::Implied
120 } else if was_forced {
121 TargetFeatureKind::Forced
122 } else {
123 TargetFeatureKind::Enabled
124 };
125 target_features.push(TargetFeature { name, kind });
126127if !rust_target_features
128 .get(name.as_str())
129 .is_some_and(|s| s.toggle_allowed().is_ok())
130 {
131 tcx.dcx().span_delayed_bug(
132 feature_span,
133::alloc::__export::must_use({
::alloc::fmt::format(format_args!("internal-only feature {0} should not be toggled by `#[target_feature]`",
name))
})format!("internal-only feature {name} should not be toggled by `#[target_feature]`"),
134 );
135 }
136 }
137 }
138 }
139}
140141/// Computes the set of target features used in a function for the purposes of
142/// inline assembly.
143fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet<Symbol> {
144let mut target_features = tcx.sess.internal_target_features.clone();
145if tcx.def_kind(did).has_codegen_attrs() {
146let attrs = tcx.codegen_fn_attrs(did);
147target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
148match attrs.instruction_set {
149None => {}
150Some(InstructionSetAttr::ArmA32) => {
151// FIXME(#120456) - is `swap_remove` correct?
152target_features.swap_remove(&sym::thumb_mode);
153 }
154Some(InstructionSetAttr::ArmT32) => {
155target_features.insert(sym::thumb_mode);
156 }
157 }
158 }
159160tcx.arena.alloc(target_features)
161}
162163/// Checks the function annotated with `#[target_feature]` is not a safe
164/// trait method implementation, reporting an error if it is.
165pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) {
166if let DefKind::AssocFn = tcx.def_kind(id) {
167let parent_id = tcx.local_parent(id);
168if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
169tcx.dcx().emit_err(diagnostics::TargetFeatureSafeTrait {
170 span: attr_span,
171 def: tcx.def_span(id),
172 });
173 }
174 }
175}
176177/// Parse the value of the target spec `features` field or `-Ctarget-feature`, calling the closure
178/// for each entry in the list, also expanding implied features (but only for actual Rust target
179/// features). If the list contains a syntactically invalid item (not starting with `+`/`-`) , the
180/// error callback is invoked.
181fn parse_rust_feature_list<'a>(
182 sess: &'a Session,
183 features: &'a str,
184 err_callback: impl Fn(&'a str),
185mut callback: impl FnMut(
186/* base_feature */ &'a str,
187/* with_implied */ Option<FxHashSet<&'a str>>,
188/* enable */ bool,
189 ),
190) {
191// A cache for the forward and backwards feature maps.
192let mut features_map: Option<FxHashMap<&str, _>> = None;
193let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
194195for feature in features.split(',') {
196if let Some(base_feature) = feature.strip_prefix('+') {
197// Skip features that are not target features, but rustc features.
198if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
199continue;
200 }
201202let features_map =
203 features_map.get_or_insert_with(|| sess.target.rust_target_features_map());
204205if !features_map.contains_key(&base_feature) {
206 callback(base_feature, None, true);
207continue;
208 }
209210let implied_features = sess.target.implied_target_features(base_feature, &features_map);
211 callback(base_feature, Some(implied_features), true)
212 } else if let Some(base_feature) = feature.strip_prefix('-') {
213// Skip features that are not target features, but rustc features.
214if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
215continue;
216 }
217218let features_map =
219 features_map.get_or_insert_with(|| sess.target.rust_target_features_map());
220221if !features_map.contains_key(&base_feature) {
222 callback(base_feature, None, false);
223continue;
224 }
225226// If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
227 // contraposition. So we have to find all the reverse implications of `base_feature` and
228 // disable them, too.
229230let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
231let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
232for (f, _, is) in sess.target.rust_target_features() {
233for i in is.iter() {
234 set.entry(i).or_default().insert(f);
235 }
236 }
237 set
238 });
239240// Inverse implied target features have their own inverse implied target features, so we
241 // traverse the map until there are no more features to add.
242let mut implied_features = FxHashSet::default();
243let mut new_features = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[base_feature]))vec![base_feature];
244while let Some(new_feature) = new_features.pop() {
245if implied_features.insert(new_feature) {
246if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
247#[allow(rustc::potential_query_instability)]
248new_features.extend(implied_features)
249 }
250 }
251 }
252253 callback(base_feature, Some(implied_features), false)
254 } else if !feature.is_empty() {
255 err_callback(feature)
256 }
257 }
258}
259260/// Utility function for a codegen backend to compute the set of all actually enabled Rust target
261/// features (which will be stored in `sess.internal_target_features`).
262///
263/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
264/// used for diagnostic purposes only.
265///
266/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is
267/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. Note that LLVM
268/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely
269/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU
270/// to target features.
271///
272/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
273pub fn internal_target_features<'a, const N: usize>(
274 sess: &Session,
275 to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
276mut target_base_has_feature: impl FnMut(&str) -> bool,
277) -> UnordSet<Symbol> {
278let features_map = sess.target.rust_target_features_map();
279280// Compute which of the known target features are enabled in the 'base' target machine: for
281 // every Rust target feature, ask the backend if it is enabled.
282let mut features: UnordSet<Symbol> = sess283 .target
284 .rust_target_features()
285 .iter()
286 .filter(|(feature, _, _)| target_base_has_feature(feature))
287 .flat_map(|(base_feature, _, _)| {
288// Expand the direct base feature into all transitively-implied features. Note that we
289 // cannot simply use the `implied` field of the tuple since that only contains
290 // directly-implied features.
291 //
292 // Iteration order is irrelevant because we're collecting into an `UnordSet`.
293#[allow(rustc::potential_query_instability)]
294sess.target
295 .implied_target_features(base_feature, &features_map)
296 .into_iter()
297 .map(|f| Symbol::intern(f))
298 })
299 .collect();
300301// State gathered for "tied features" check.
302let mut enabled_disabled_features = FxHashMap::default();
303304// Add enabled and remove disabled features.
305parse_rust_feature_list(
306sess,
307&sess.opts.cg.target_feature,
308/* err_callback */
309|feature| {
310sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature });
311 },
312 |base_feature, new_features, enable| {
313match features_map.get(base_feature) {
314None => {
315// This is definitely not a valid Rust feature name. We do not add it to
316 // `features`. Maybe it is a backend feature name? If so, give a better error
317 // message.
318let rust_feature = sess.target.rust_target_features().iter().find_map(
319 |&(rust_feature, _, _)| {
320let backend_features = to_backend_features(rust_feature);
321if backend_features.contains(&base_feature)
322 && !backend_features.contains(&rust_feature)
323 {
324Some(rust_feature)
325 } else {
326None327 }
328 },
329 );
330let unknown_feature = if let Some(rust_feature) = rust_feature {
331 diagnostics::UnknownCTargetFeature {
332 feature: base_feature,
333 rust_feature: diagnostics::PossibleFeature::Some { rust_feature },
334 }
335 } else {
336 diagnostics::UnknownCTargetFeature {
337 feature: base_feature,
338 rust_feature: diagnostics::PossibleFeature::None,
339 }
340 };
341sess.dcx().emit_warn(unknown_feature);
342 }
343Some((stability, _)) => {
344let new_features = new_features.unwrap();
345// Add feature to our set -- only if it is actually a recognized feature.
346 // Iteration order is irrelevant since this only influences an `FxHashMap`.
347#[allow(rustc::potential_query_instability)]
348enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
349350// Iteration order is irrelevant since this only influences an `UnordSet`.
351#[allow(rustc::potential_query_instability)]
352if enable {
353features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
354 } else {
355// Remove `new_features` from `features`.
356for new in new_features {
357 features.remove(&Symbol::intern(new));
358 }
359 }
360361// Check feature stability.
362if let Stability::InternalOnly { reason, hard_error } = stability {
363let diag = diagnostics::InternalOnlyCTargetFeature {
364 feature: base_feature,
365 enabled: if enable { "enabled" } else { "disabled" },
366reason,
367 future_compat_note: !hard_error,
368 };
369370if *hard_error {
371sess.dcx().emit_err(diag);
372 } else {
373sess.dcx().emit_warn(diag);
374 }
375 } else if stability.requires_nightly(/* in_cfg */ false).is_some() {
376// An unstable feature. Warn about using it. It makes little sense
377 // to hard-error here since we just warn about fully unknown
378 // features above.
379let note = if stability.is_cfg_stable_toggle_unstable() {
380"this feature is allowed in cfg but unstable otherwise"
381} else {
382"this feature is not stably supported"
383};
384sess.dcx().emit_warn(diagnostics::UnstableCTargetFeature {
385 feature: base_feature,
386note,
387 });
388 }
389 }
390 }
391 },
392 );
393394if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
395sess.dcx().emit_err(diagnostics::TargetFeatureDisableOrEnable {
396 features: f,
397 span: None,
398 missing_features: None,
399 });
400 }
401402features403}
404405/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
406/// combinations are allowed. Returns `Some` if a violation is found.
407pub fn check_tied_features(
408 sess: &Session,
409 features: &FxHashMap<&str, bool>,
410) -> Option<&'static [&'static str]> {
411if !features.is_empty() {
412for tied in sess.target.tied_target_features() {
413// Tied features must be set to the same value, or not set at all
414let mut tied_iter = tied.iter();
415let enabled = features.get(tied_iter.next().unwrap());
416if tied_iter.any(|f| enabled != features.get(f)) {
417return Some(tied);
418 }
419 }
420 }
421None422}
423424/// Translates the target spec `features` field into a backend target feature list.
425///
426/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
427/// accessible by that closure) to enable/disable the given Rust feature name.
428pub fn target_spec_to_backend_features<'a>(
429 sess: &'a Session,
430mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
431) {
432// This check handles SM versions that defaults (by LLVM) to unsupported (by Rust) PTX ISA versions.
433 // sm_70, sm_72 and sm_75 defaults to PTX ISA versions with major version 6, while sm_80 default to 7.0
434if sess.target.arch == Arch::Nvptx64435 && #[allow(non_exhaustive_omitted_patterns)] match sess.opts.cg.target_cpu.as_deref()
{
None | Some("sm_70") | Some("sm_72") | Some("sm_75") => true,
_ => false,
}matches!(
436 sess.opts.cg.target_cpu.as_deref(),
437None | Some("sm_70") | Some("sm_72") | Some("sm_75")
438 )439 {
440extend_backend_features("ptx70", true);
441 }
442443// Compute implied features
444parse_rust_feature_list(
445sess,
446&sess.target.features,
447/* err_callback */
448|feature| {
449{
::core::panicking::panic_fmt(format_args!("Target spec contains invalid feature {0} (missing `+`/`-` prefix)",
feature));
};panic!("Target spec contains invalid feature {feature} (missing `+`/`-` prefix)");
450 },
451 |base_feature, new_features, enable| {
452// FIXME emit an error for unknown features in the target spec like
453 // internal_target_features would for -Ctarget-feature.
454let new_features =
455new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature)));
456for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() {
457 extend_backend_features(new_feature, enable);
458 }
459 },
460 );
461}
462463/// Translates the `-Ctarget-feature` flag into a backend target feature list.
464///
465/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
466/// accessible by that closure) to enable/disable the given Rust feature name.
467pub fn flag_to_backend_features<'a>(
468 sess: &'a Session,
469mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
470) {
471parse_rust_feature_list(
472sess,
473&sess.opts.cg.target_feature,
474/* err_callback */
475|_feature| {
476// Errors are already emitted in `internal_target_features`; avoid duplicates.
477},
478 |base_feature, new_features, enable| {
479// Forward unknown features to the backend as that's what we have always done.
480let new_features =
481new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature)));
482for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() {
483 extend_backend_features(new_feature, enable);
484 }
485 },
486 );
487}
488489/// Computes the backend target features to be added to account for retpoline flags.
490/// Used by both LLVM and GCC since their target features are, conveniently, the same.
491pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
492// -Zretpoline without -Zretpoline-external-thunk enables
493 // retpoline-indirect-branches and retpoline-indirect-calls target features
494let unstable_opts = &sess.opts.unstable_opts;
495if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
496features.push("+retpoline-indirect-branches".into());
497features.push("+retpoline-indirect-calls".into());
498 }
499// -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
500 // retpoline-external-thunk, retpoline-indirect-branches and
501 // retpoline-indirect-calls target features
502if unstable_opts.retpoline_external_thunk {
503features.push("+retpoline-external-thunk".into());
504features.push("+retpoline-indirect-branches".into());
505features.push("+retpoline-indirect-calls".into());
506 }
507}
508509/// Computes the backend target features to be added to account for sanitizer flags.
510pub fn sanitizer_features_by_flags(sess: &Session, features: &mut Vec<String>) {
511// It's intentional that this is done only for non-kernel version of hwaddress. This matches
512 // clang behavior.
513if sess.sanitizers().contains(SanitizerSet::HWADDRESS) {
514features.push("+tagged-globals".into());
515 }
516}
517518pub(crate) fn provide(providers: &mut Providers) {
519*providers = Providers {
520 rust_target_features: |tcx, cnum| {
521{
match (&cnum, &LOCAL_CRATE) {
(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::None);
}
}
}
};assert_eq!(cnum, LOCAL_CRATE);
522if tcx.sess.opts.actually_rustdoc {
523// HACK: rustdoc would like to pretend that we have all the target features, so we
524 // have to merge all the lists into one. To ensure an unstable target never prevents
525 // a stable one from working, we merge the stability info of all instances of the
526 // same target feature name, with the "most stable" taking precedence. And then we
527 // hope that this doesn't cause issues anywhere else in the compiler...
528let mut result: UnordMap<String, Stability> = Default::default();
529for (name, stability) in rustc_target::target_features::all_rust_features() {
530use std::collections::hash_map::Entry;
531match result.entry(name.to_owned()) {
532 Entry::Vacant(vacant_entry) => {
533 vacant_entry.insert(stability);
534 }
535 Entry::Occupied(mut occupied_entry) => {
536// Merge the two stabilities, "more stable" taking precedence.
537match (occupied_entry.get(), stability) {
538 (Stability::Stable, _)
539 | (
540 Stability::Unstable { .. },
541 Stability::Unstable { .. } | Stability::InternalOnly { .. },
542 )
543 | (
544 Stability::InternalOnly { .. },
545 Stability::InternalOnly { .. },
546 ) => {
547// The stability in the entry is at least as good as the new
548 // one, just keep it.
549}
550_ => {
551// Overwrite stability.
552occupied_entry.insert(stability);
553 }
554 }
555 }
556 }
557 }
558result559 } else {
560tcx.sess
561 .target
562 .rust_target_features()
563 .iter()
564 .map(|(feat, stab, _)| (feat.to_string(), *stab))
565 .collect()
566 }
567 },
568 implied_target_features: |tcx, feature: Symbol| {
569if tcx.sess.opts.actually_rustdoc {
570// We can't handle implication when we are mixing all targets.
571return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[feature]))vec![feature];
572 }
573let features_map = tcx.sess.target.rust_target_features_map();
574let feature = feature.as_str();
575UnordSet::from(tcx.sess.target.implied_target_features(feature, &features_map))
576 .into_sorted_stable_ord()
577 .into_iter()
578 .map(|s| Symbol::intern(s))
579 .collect()
580 },
581asm_target_features,
582 ..*providers583 }
584}