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