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::lint::builtin::AARCH64_SOFTFLOAT_NEON;
11use rustc_session::parse::feature_err;
12use rustc_span::{Span, Symbol, sym};
13use rustc_target::spec::Arch;
14use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
15use smallvec::SmallVec;
1617use crate::errors::FeatureNotValid;
18use crate::{errors, 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 plus_hint = feature_str
36 .strip_prefix('+')
37 .is_some_and(|stripped| rust_target_features.contains_key(stripped));
38 tcx.dcx().emit_err(FeatureNotValid {
39 feature: feature_str,
40 span: feature_span,
41 plus_hint,
42 });
43continue;
44 };
4546// Only allow target features whose feature gates have been enabled
47 // and which are permitted to be toggled.
48if let Err(reason) = stability.toggle_allowed() {
49 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
50 span: feature_span,
51 feature: feature_str,
52 reason,
53 });
54 } else if let Some(nightly_feature) = stability.requires_nightly()
55 && !rust_features.enabled(nightly_feature)
56 {
57 feature_err(
58&tcx.sess,
59 nightly_feature,
60 feature_span,
61::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"),
62 )
63 .emit();
64 } else {
65// Add this and the implied features.
66for &name in tcx.implied_target_features(feature) {
67// But ensure the ABI does not forbid enabling this.
68 // Here we do assume that the backend doesn't add even more implied features
69 // we don't know about, at least no features that would have ABI effects!
70 // We skip this logic in rustdoc, where we want to allow all target features of
71 // all targets, so we can't check their ABI compatibility and anyway we are not
72 // generating code so "it's fine".
73if !tcx.sess.opts.actually_rustdoc {
74if abi_feature_constraints.incompatible.contains(&name.as_str()) {
75// For "neon" specifically, we emit an FCW instead of a hard error.
76 // See <https://github.com/rust-lang/rust/issues/134375>.
77if tcx.sess.target.arch == Arch::AArch64 && name.as_str() == "neon" {
78 tcx.emit_node_span_lint(
79 AARCH64_SOFTFLOAT_NEON,
80 tcx.local_def_id_to_hir_id(did),
81 feature_span,
82 errors::Aarch64SoftfloatNeon,
83 );
84 } else {
85 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
86 span: feature_span,
87 feature: name.as_str(),
88 reason: "this feature is incompatible with the target ABI",
89 });
90 }
91 }
92 }
93let kind = if name != feature {
94 TargetFeatureKind::Implied
95 } else if was_forced {
96 TargetFeatureKind::Forced
97 } else {
98 TargetFeatureKind::Enabled
99 };
100 target_features.push(TargetFeature { name, kind })
101 }
102 }
103 }
104}
105106/// Computes the set of target features used in a function for the purposes of
107/// inline assembly.
108fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet<Symbol> {
109let mut target_features = tcx.sess.unstable_target_features.clone();
110if tcx.def_kind(did).has_codegen_attrs() {
111let attrs = tcx.codegen_fn_attrs(did);
112target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
113match attrs.instruction_set {
114None => {}
115Some(InstructionSetAttr::ArmA32) => {
116// FIXME(#120456) - is `swap_remove` correct?
117target_features.swap_remove(&sym::thumb_mode);
118 }
119Some(InstructionSetAttr::ArmT32) => {
120target_features.insert(sym::thumb_mode);
121 }
122 }
123 }
124125tcx.arena.alloc(target_features)
126}
127128/// Checks the function annotated with `#[target_feature]` is not a safe
129/// trait method implementation, reporting an error if it is.
130pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) {
131if let DefKind::AssocFn = tcx.def_kind(id) {
132let parent_id = tcx.local_parent(id);
133if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
134tcx.dcx().emit_err(errors::TargetFeatureSafeTrait {
135 span: attr_span,
136 def: tcx.def_span(id),
137 });
138 }
139 }
140}
141142/// Parse the value of the target spec `features` field or `-Ctarget-feature`, also expanding
143/// implied features, and call the closure for each (expanded) Rust feature. If the list contains
144/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked.
145fn parse_rust_feature_list<'a>(
146 sess: &'a Session,
147 features: &'a str,
148 err_callback: impl Fn(&'a str),
149mut callback: impl FnMut(
150/* base_feature */ &'a str,
151/* with_implied */ FxHashSet<&'a str>,
152/* enable */ bool,
153 ),
154) {
155// A cache for the backwards implication map.
156let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
157158for feature in features.split(',') {
159if let Some(base_feature) = feature.strip_prefix('+') {
160// Skip features that are not target features, but rustc features.
161if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
162continue;
163 }
164165 callback(base_feature, sess.target.implied_target_features(base_feature), true)
166 } else if let Some(base_feature) = feature.strip_prefix('-') {
167// Skip features that are not target features, but rustc features.
168if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
169continue;
170 }
171172// If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
173 // contraposition. So we have to find all the reverse implications of `base_feature` and
174 // disable them, too.
175176let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
177let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
178for (f, _, is) in sess.target.rust_target_features() {
179for i in is.iter() {
180 set.entry(i).or_default().insert(f);
181 }
182 }
183 set
184 });
185186// Inverse implied target features have their own inverse implied target features, so we
187 // traverse the map until there are no more features to add.
188let mut features = FxHashSet::default();
189let mut new_features = <[_]>::into_vec(::alloc::boxed::box_new([base_feature]))vec![base_feature];
190while let Some(new_feature) = new_features.pop() {
191if features.insert(new_feature) {
192if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
193#[allow(rustc::potential_query_instability)]
194new_features.extend(implied_features)
195 }
196 }
197 }
198199 callback(base_feature, features, false)
200 } else if !feature.is_empty() {
201 err_callback(feature)
202 }
203 }
204}
205206/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically,
207/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
208/// 2nd component of the return value, respectively).
209///
210/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
211/// used for diagnostic purposes only.
212///
213/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is
214/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. Note that LLVM
215/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely
216/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU
217/// to target features.
218///
219/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
220pub fn cfg_target_feature<'a, const N: usize>(
221 sess: &Session,
222 to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
223mut target_base_has_feature: impl FnMut(&str) -> bool,
224) -> (Vec<Symbol>, Vec<Symbol>) {
225let known_features = sess.target.rust_target_features();
226227// Compute which of the known target features are enabled in the 'base' target machine. We only
228 // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
229let mut features: UnordSet<Symbol> = sess230 .target
231 .rust_target_features()
232 .iter()
233 .filter(|(feature, _, _)| target_base_has_feature(feature))
234 .flat_map(|(base_feature, _, _)| {
235// Expand the direct base feature into all transitively-implied features. Note that we
236 // cannot simply use the `implied` field of the tuple since that only contains
237 // directly-implied features.
238 //
239 // Iteration order is irrelevant because we're collecting into an `UnordSet`.
240#[allow(rustc::potential_query_instability)]
241sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f))
242 })
243 .collect();
244245let mut enabled_disabled_features = FxHashMap::default();
246247// Add enabled and remove disabled features.
248parse_rust_feature_list(
249sess,
250&sess.opts.cg.target_feature,
251/* err_callback */
252|feature| {
253sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
254 },
255 |base_feature, new_features, enable| {
256// Iteration order is irrelevant since this only influences an `FxHashMap`.
257#[allow(rustc::potential_query_instability)]
258enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
259260// Iteration order is irrelevant since this only influences an `UnordSet`.
261#[allow(rustc::potential_query_instability)]
262if enable {
263features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
264 } else {
265// Remove `new_features` from `features`.
266for new in new_features {
267 features.remove(&Symbol::intern(new));
268 }
269 }
270271// Check feature validity.
272let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
273match feature_state {
274None => {
275// This is definitely not a valid Rust feature name. Maybe it is a backend
276 // feature name? If so, give a better error message.
277let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
278let backend_features = to_backend_features(rust_feature);
279if backend_features.contains(&base_feature)
280 && !backend_features.contains(&rust_feature)
281 {
282Some(rust_feature)
283 } else {
284None285 }
286 });
287let unknown_feature = if let Some(rust_feature) = rust_feature {
288 errors::UnknownCTargetFeature {
289 feature: base_feature,
290 rust_feature: errors::PossibleFeature::Some { rust_feature },
291 }
292 } else {
293 errors::UnknownCTargetFeature {
294 feature: base_feature,
295 rust_feature: errors::PossibleFeature::None,
296 }
297 };
298sess.dcx().emit_warn(unknown_feature);
299 }
300Some((_, stability, _)) => {
301if let Err(reason) = stability.toggle_allowed() {
302sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
303 feature: base_feature,
304 enabled: if enable { "enabled" } else { "disabled" },
305reason,
306 });
307 } else if stability.requires_nightly().is_some() {
308// An unstable feature. Warn about using it. It makes little sense
309 // to hard-error here since we just warn about fully unknown
310 // features above.
311sess.dcx()
312 .emit_warn(errors::UnstableCTargetFeature { feature: base_feature });
313 }
314 }
315 }
316 },
317 );
318319if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
320sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
321 features: f,
322 span: None,
323 missing_features: None,
324 });
325 }
326327// Filter enabled features based on feature gates.
328let f = |allow_unstable| {
329sess.target
330 .rust_target_features()
331 .iter()
332 .filter_map(|(feature, gate, _)| {
333// The `allow_unstable` set is used by rustc internally to determine which target
334 // features are truly available, so we want to return even perma-unstable
335 // "forbidden" features.
336if allow_unstable337 || (gate.in_cfg()
338 && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
339 {
340Some(Symbol::intern(feature))
341 } else {
342None343 }
344 })
345 .filter(|feature| features.contains(&feature))
346 .collect()
347 };
348349 (f(true), f(false))
350}
351352/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
353/// combinations are allowed.
354pub fn check_tied_features(
355 sess: &Session,
356 features: &FxHashMap<&str, bool>,
357) -> Option<&'static [&'static str]> {
358if !features.is_empty() {
359for tied in sess.target.tied_target_features() {
360// Tied features must be set to the same value, or not set at all
361let mut tied_iter = tied.iter();
362let enabled = features.get(tied_iter.next().unwrap());
363if tied_iter.any(|f| enabled != features.get(f)) {
364return Some(tied);
365 }
366 }
367 }
368None369}
370371/// Translates the target spec `features` field into a backend target feature list.
372///
373/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
374/// accessible by that closure) to enable/disable the given Rust feature name.
375pub fn target_spec_to_backend_features<'a>(
376 sess: &'a Session,
377mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
378) {
379// Compute implied features
380let mut rust_features = ::alloc::vec::Vec::new()vec![];
381parse_rust_feature_list(
382sess,
383&sess.target.features,
384/* err_callback */
385|feature| {
386{
::core::panicking::panic_fmt(format_args!("Target spec contains invalid feature {0}",
feature));
};panic!("Target spec contains invalid feature {feature}");
387 },
388 |_base_feature, new_features, enable| {
389// FIXME emit an error for unknown features like cfg_target_feature would for -Ctarget-feature
390rust_features.extend(
391UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
392 );
393 },
394 );
395396// Add this to the backend features.
397for (enable, feature) in rust_features {
398 extend_backend_features(feature, enable);
399 }
400}
401402/// Translates the `-Ctarget-feature` flag into a backend target feature list.
403///
404/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
405/// accessible by that closure) to enable/disable the given Rust feature name.
406pub fn flag_to_backend_features<'a>(
407 sess: &'a Session,
408mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
409) {
410// Compute implied features
411let mut rust_features = ::alloc::vec::Vec::new()vec![];
412parse_rust_feature_list(
413sess,
414&sess.opts.cg.target_feature,
415/* err_callback */
416|_feature| {
417// Errors are already emitted in `cfg_target_feature`; avoid duplicates.
418},
419 |_base_feature, new_features, enable| {
420rust_features.extend(
421UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
422 );
423 },
424 );
425426// Add this to the backend features.
427for (enable, feature) in rust_features {
428 extend_backend_features(feature, enable);
429 }
430}
431432/// Computes the backend target features to be added to account for retpoline flags.
433/// Used by both LLVM and GCC since their target features are, conveniently, the same.
434pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
435// -Zretpoline without -Zretpoline-external-thunk enables
436 // retpoline-indirect-branches and retpoline-indirect-calls target features
437let unstable_opts = &sess.opts.unstable_opts;
438if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
439features.push("+retpoline-indirect-branches".into());
440features.push("+retpoline-indirect-calls".into());
441 }
442// -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
443 // retpoline-external-thunk, retpoline-indirect-branches and
444 // retpoline-indirect-calls target features
445if unstable_opts.retpoline_external_thunk {
446features.push("+retpoline-external-thunk".into());
447features.push("+retpoline-indirect-branches".into());
448features.push("+retpoline-indirect-calls".into());
449 }
450}
451452pub(crate) fn provide(providers: &mut Providers) {
453*providers = Providers {
454 rust_target_features: |tcx, cnum| {
455match (&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);
456if tcx.sess.opts.actually_rustdoc {
457// HACK: rustdoc would like to pretend that we have all the target features, so we
458 // have to merge all the lists into one. To ensure an unstable target never prevents
459 // a stable one from working, we merge the stability info of all instances of the
460 // same target feature name, with the "most stable" taking precedence. And then we
461 // hope that this doesn't cause issues anywhere else in the compiler...
462let mut result: UnordMap<String, Stability> = Default::default();
463for (name, stability) in rustc_target::target_features::all_rust_features() {
464use std::collections::hash_map::Entry;
465match result.entry(name.to_owned()) {
466 Entry::Vacant(vacant_entry) => {
467 vacant_entry.insert(stability);
468 }
469 Entry::Occupied(mut occupied_entry) => {
470// Merge the two stabilities, "more stable" taking precedence.
471match (occupied_entry.get(), stability) {
472 (Stability::Stable, _)
473 | (
474 Stability::Unstable { .. },
475 Stability::Unstable { .. } | Stability::Forbidden { .. },
476 )
477 | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => {
478// The stability in the entry is at least as good as the new
479 // one, just keep it.
480}
481_ => {
482// Overwrite stability.
483occupied_entry.insert(stability);
484 }
485 }
486 }
487 }
488 }
489result490 } else {
491tcx.sess
492 .target
493 .rust_target_features()
494 .iter()
495 .map(|(a, b, _)| (a.to_string(), *b))
496 .collect()
497 }
498 },
499 implied_target_features: |tcx, feature: Symbol| {
500let feature = feature.as_str();
501UnordSet::from(tcx.sess.target.implied_target_features(feature))
502 .into_sorted_stable_ord()
503 .into_iter()
504 .map(|s| Symbol::intern(s))
505 .collect()
506 },
507asm_target_features,
508 ..*providers509 }
510}