Skip to main content

rustc_codegen_ssa/
target_features.rs

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;
16
17use crate::errors::{FeatureNotValid, FeatureNotValidHint};
18use crate::{errors, target_features};
19
20/// 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) {
30    let rust_features = tcx.features();
31    let abi_feature_constraints = tcx.sess.target.abi_required_features();
32    for &(feature, feature_span) in features {
33        let feature_str = feature.as_str();
34        let Some(stability) = rust_target_features.get(feature_str) else {
35            let 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.
41                let 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);
47
48                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 });
54            continue;
55        };
56
57        // Only allow target features whose feature gates have been enabled
58        // and which are permitted to be toggled.
59        if 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.
77            for &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".
84                if !tcx.sess.opts.actually_rustdoc {
85                    if 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>.
88                        if 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                }
104                let 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}
116
117/// 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> {
120    let mut target_features = tcx.sess.unstable_target_features.clone();
121    if tcx.def_kind(did).has_codegen_attrs() {
122        let attrs = tcx.codegen_fn_attrs(did);
123        target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
124        match attrs.instruction_set {
125            None => {}
126            Some(InstructionSetAttr::ArmA32) => {
127                // FIXME(#120456) - is `swap_remove` correct?
128                target_features.swap_remove(&sym::thumb_mode);
129            }
130            Some(InstructionSetAttr::ArmT32) => {
131                target_features.insert(sym::thumb_mode);
132            }
133        }
134    }
135
136    tcx.arena.alloc(target_features)
137}
138
139/// 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) {
142    if let DefKind::AssocFn = tcx.def_kind(id) {
143        let parent_id = tcx.local_parent(id);
144        if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
145            tcx.dcx().emit_err(errors::TargetFeatureSafeTrait {
146                span: attr_span,
147                def: tcx.def_span(id),
148            });
149        }
150    }
151}
152
153/// 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),
160    mut 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.
167    let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
168
169    for feature in features.split(',') {
170        if let Some(base_feature) = feature.strip_prefix('+') {
171            // Skip features that are not target features, but rustc features.
172            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
173                continue;
174            }
175
176            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.
179            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
180                continue;
181            }
182
183            // 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.
186
187            let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
188                let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
189                for (f, _, is) in sess.target.rust_target_features() {
190                    for i in is.iter() {
191                        set.entry(i).or_default().insert(f);
192                    }
193                }
194                set
195            });
196
197            // 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.
199            let mut features = FxHashSet::default();
200            let 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];
201            while let Some(new_feature) = new_features.pop() {
202                if features.insert(new_feature) {
203                    if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
204                        #[allow(rustc::potential_query_instability)]
205                        new_features.extend(implied_features)
206                    }
207                }
208            }
209
210            callback(base_feature, features, false)
211        } else if !feature.is_empty() {
212            err_callback(feature)
213        }
214    }
215}
216
217/// 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]>,
234    mut target_base_has_feature: impl FnMut(&str) -> bool,
235) -> (Vec<Symbol>, Vec<Symbol>) {
236    let known_features = sess.target.rust_target_features();
237
238    // 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.
240    let mut features: UnordSet<Symbol> = sess
241        .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)]
252            sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f))
253        })
254        .collect();
255
256    let mut enabled_disabled_features = FxHashMap::default();
257
258    // Add enabled and remove disabled features.
259    parse_rust_feature_list(
260        sess,
261        &sess.opts.cg.target_feature,
262        /* err_callback */
263        |feature| {
264            sess.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)]
269            enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
270
271            // Iteration order is irrelevant since this only influences an `UnordSet`.
272            #[allow(rustc::potential_query_instability)]
273            if enable {
274                features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
275            } else {
276                // Remove `new_features` from `features`.
277                for new in new_features {
278                    features.remove(&Symbol::intern(new));
279                }
280            }
281
282            // Check feature validity.
283            let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
284            match feature_state {
285                None => {
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.
288                    let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
289                        let backend_features = to_backend_features(rust_feature);
290                        if backend_features.contains(&base_feature)
291                            && !backend_features.contains(&rust_feature)
292                        {
293                            Some(rust_feature)
294                        } else {
295                            None
296                        }
297                    });
298                    let 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                    };
309                    sess.dcx().emit_warn(unknown_feature);
310                }
311                Some((_, stability, _)) => {
312                    if let Err(reason) = stability.toggle_allowed() {
313                        sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
314                            feature: base_feature,
315                            enabled: if enable { "enabled" } else { "disabled" },
316                            reason,
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.
322                        sess.dcx()
323                            .emit_warn(errors::UnstableCTargetFeature { feature: base_feature });
324                    }
325                }
326            }
327        },
328    );
329
330    if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
331        sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
332            features: f,
333            span: None,
334            missing_features: None,
335        });
336    }
337
338    // Filter enabled features based on feature gates.
339    let f = |allow_unstable| {
340        sess.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.
347                if allow_unstable
348                    || (gate.in_cfg()
349                        && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
350                {
351                    Some(Symbol::intern(feature))
352                } else {
353                    None
354                }
355            })
356            .filter(|feature| features.contains(&feature))
357            .collect()
358    };
359
360    (f(true), f(false))
361}
362
363/// 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]> {
369    if !features.is_empty() {
370        for tied in sess.target.tied_target_features() {
371            // Tied features must be set to the same value, or not set at all
372            let mut tied_iter = tied.iter();
373            let enabled = features.get(tied_iter.next().unwrap());
374            if tied_iter.any(|f| enabled != features.get(f)) {
375                return Some(tied);
376            }
377        }
378    }
379    None
380}
381
382/// 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,
388    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
389) {
390    // Compute implied features
391    let mut rust_features = ::alloc::vec::Vec::new()vec![];
392    parse_rust_feature_list(
393        sess,
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
401            rust_features.extend(
402                UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
403            );
404        },
405    );
406
407    // Add this to the backend features.
408    for (enable, feature) in rust_features {
409        extend_backend_features(feature, enable);
410    }
411}
412
413/// 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,
419    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
420) {
421    // Compute implied features
422    let mut rust_features = ::alloc::vec::Vec::new()vec![];
423    parse_rust_feature_list(
424        sess,
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| {
431            rust_features.extend(
432                UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
433            );
434        },
435    );
436
437    // Add this to the backend features.
438    for (enable, feature) in rust_features {
439        extend_backend_features(feature, enable);
440    }
441}
442
443/// 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
448    let unstable_opts = &sess.opts.unstable_opts;
449    if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
450        features.push("+retpoline-indirect-branches".into());
451        features.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
456    if unstable_opts.retpoline_external_thunk {
457        features.push("+retpoline-external-thunk".into());
458        features.push("+retpoline-indirect-branches".into());
459        features.push("+retpoline-indirect-calls".into());
460    }
461}
462
463pub(crate) fn provide(providers: &mut Providers) {
464    *providers = Providers {
465        rust_target_features: |tcx, cnum| {
466            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);
467            if 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...
473                let mut result: UnordMap<String, Stability> = Default::default();
474                for (name, stability) in rustc_target::target_features::all_rust_features() {
475                    use std::collections::hash_map::Entry;
476                    match 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.
482                            match (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.
494                                    occupied_entry.insert(stability);
495                                }
496                            }
497                        }
498                    }
499                }
500                result
501            } else {
502                tcx.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| {
511            let feature = feature.as_str();
512            UnordSet::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        },
518        asm_target_features,
519        ..*providers
520    }
521}