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, sym};
13use rustc_target::spec::Arch;
14use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
15use smallvec::SmallVec;
16
17use crate::errors::FeatureNotValid;
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 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            });
43            continue;
44        };
45
46        // Only allow target features whose feature gates have been enabled
47        // and which are permitted to be toggled.
48        if 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                format!("the target feature `{feature}` is currently unstable"),
62            )
63            .emit();
64        } else {
65            // Add this and the implied features.
66            for &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".
73                if !tcx.sess.opts.actually_rustdoc {
74                    if 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>.
77                        if 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                }
93                let 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}
105
106/// 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> {
109    let mut target_features = tcx.sess.unstable_target_features.clone();
110    if tcx.def_kind(did).has_codegen_attrs() {
111        let attrs = tcx.codegen_fn_attrs(did);
112        target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
113        match attrs.instruction_set {
114            None => {}
115            Some(InstructionSetAttr::ArmA32) => {
116                // FIXME(#120456) - is `swap_remove` correct?
117                target_features.swap_remove(&sym::thumb_mode);
118            }
119            Some(InstructionSetAttr::ArmT32) => {
120                target_features.insert(sym::thumb_mode);
121            }
122        }
123    }
124
125    tcx.arena.alloc(target_features)
126}
127
128/// 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) {
131    if let DefKind::AssocFn = tcx.def_kind(id) {
132        let parent_id = tcx.local_parent(id);
133        if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
134            tcx.dcx().emit_err(errors::TargetFeatureSafeTrait {
135                span: attr_span,
136                def: tcx.def_span(id),
137            });
138        }
139    }
140}
141
142/// Parse the value of `-Ctarget-feature`, also expanding implied features,
143/// 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_flag<'a>(
146    sess: &'a Session,
147    err_callback: impl Fn(&'a str),
148    mut callback: impl FnMut(
149        /* base_feature */ &'a str,
150        /* with_implied */ FxHashSet<&'a str>,
151        /* enable */ bool,
152    ),
153) {
154    // A cache for the backwards implication map.
155    let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
156
157    for feature in sess.opts.cg.target_feature.split(',') {
158        if let Some(base_feature) = feature.strip_prefix('+') {
159            // Skip features that are not target features, but rustc features.
160            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
161                continue;
162            }
163
164            callback(base_feature, sess.target.implied_target_features(base_feature), true)
165        } else if let Some(base_feature) = feature.strip_prefix('-') {
166            // Skip features that are not target features, but rustc features.
167            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
168                continue;
169            }
170
171            // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
172            // contraposition. So we have to find all the reverse implications of `base_feature` and
173            // disable them, too.
174
175            let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
176                let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
177                for (f, _, is) in sess.target.rust_target_features() {
178                    for i in is.iter() {
179                        set.entry(i).or_default().insert(f);
180                    }
181                }
182                set
183            });
184
185            // Inverse implied target features have their own inverse implied target features, so we
186            // traverse the map until there are no more features to add.
187            let mut features = FxHashSet::default();
188            let mut new_features = vec![base_feature];
189            while let Some(new_feature) = new_features.pop() {
190                if features.insert(new_feature) {
191                    if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
192                        #[allow(rustc::potential_query_instability)]
193                        new_features.extend(implied_features)
194                    }
195                }
196            }
197
198            callback(base_feature, features, false)
199        } else if !feature.is_empty() {
200            err_callback(feature)
201        }
202    }
203}
204
205/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically,
206/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
207/// 2nd component of the return value, respectively).
208///
209/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
210/// used for diagnostic purposes only.
211///
212/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is
213/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. Note that LLVM
214/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely
215/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU
216/// to target features.
217///
218/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
219pub fn cfg_target_feature<'a, const N: usize>(
220    sess: &Session,
221    to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
222    mut target_base_has_feature: impl FnMut(&str) -> bool,
223) -> (Vec<Symbol>, Vec<Symbol>) {
224    let known_features = sess.target.rust_target_features();
225
226    // Compute which of the known target features are enabled in the 'base' target machine. We only
227    // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
228    let mut features: UnordSet<Symbol> = sess
229        .target
230        .rust_target_features()
231        .iter()
232        .filter(|(feature, _, _)| target_base_has_feature(feature))
233        .flat_map(|(base_feature, _, _)| {
234            // Expand the direct base feature into all transitively-implied features. Note that we
235            // cannot simply use the `implied` field of the tuple since that only contains
236            // directly-implied features.
237            //
238            // Iteration order is irrelevant because we're collecting into an `UnordSet`.
239            #[allow(rustc::potential_query_instability)]
240            sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f))
241        })
242        .collect();
243
244    let mut enabled_disabled_features = FxHashMap::default();
245
246    // Add enabled and remove disabled features.
247    parse_rust_feature_flag(
248        sess,
249        /* err_callback */
250        |feature| {
251            sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
252        },
253        |base_feature, new_features, enable| {
254            // Iteration order is irrelevant since this only influences an `FxHashMap`.
255            #[allow(rustc::potential_query_instability)]
256            enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
257
258            // Iteration order is irrelevant since this only influences an `UnordSet`.
259            #[allow(rustc::potential_query_instability)]
260            if enable {
261                features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
262            } else {
263                // Remove `new_features` from `features`.
264                for new in new_features {
265                    features.remove(&Symbol::intern(new));
266                }
267            }
268
269            // Check feature validity.
270            let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
271            match feature_state {
272                None => {
273                    // This is definitely not a valid Rust feature name. Maybe it is a backend
274                    // feature name? If so, give a better error message.
275                    let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
276                        let backend_features = to_backend_features(rust_feature);
277                        if backend_features.contains(&base_feature)
278                            && !backend_features.contains(&rust_feature)
279                        {
280                            Some(rust_feature)
281                        } else {
282                            None
283                        }
284                    });
285                    let unknown_feature = if let Some(rust_feature) = rust_feature {
286                        errors::UnknownCTargetFeature {
287                            feature: base_feature,
288                            rust_feature: errors::PossibleFeature::Some { rust_feature },
289                        }
290                    } else {
291                        errors::UnknownCTargetFeature {
292                            feature: base_feature,
293                            rust_feature: errors::PossibleFeature::None,
294                        }
295                    };
296                    sess.dcx().emit_warn(unknown_feature);
297                }
298                Some((_, stability, _)) => {
299                    if let Err(reason) = stability.toggle_allowed() {
300                        sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
301                            feature: base_feature,
302                            enabled: if enable { "enabled" } else { "disabled" },
303                            reason,
304                        });
305                    } else if stability.requires_nightly().is_some() {
306                        // An unstable feature. Warn about using it. It makes little sense
307                        // to hard-error here since we just warn about fully unknown
308                        // features above.
309                        sess.dcx()
310                            .emit_warn(errors::UnstableCTargetFeature { feature: base_feature });
311                    }
312                }
313            }
314        },
315    );
316
317    if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
318        sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
319            features: f,
320            span: None,
321            missing_features: None,
322        });
323    }
324
325    // Filter enabled features based on feature gates.
326    let f = |allow_unstable| {
327        sess.target
328            .rust_target_features()
329            .iter()
330            .filter_map(|(feature, gate, _)| {
331                // The `allow_unstable` set is used by rustc internally to determine which target
332                // features are truly available, so we want to return even perma-unstable
333                // "forbidden" features.
334                if allow_unstable
335                    || (gate.in_cfg()
336                        && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
337                {
338                    Some(Symbol::intern(feature))
339                } else {
340                    None
341                }
342            })
343            .filter(|feature| features.contains(&feature))
344            .collect()
345    };
346
347    (f(true), f(false))
348}
349
350/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
351/// combinations are allowed.
352pub fn check_tied_features(
353    sess: &Session,
354    features: &FxHashMap<&str, bool>,
355) -> Option<&'static [&'static str]> {
356    if !features.is_empty() {
357        for tied in sess.target.tied_target_features() {
358            // Tied features must be set to the same value, or not set at all
359            let mut tied_iter = tied.iter();
360            let enabled = features.get(tied_iter.next().unwrap());
361            if tied_iter.any(|f| enabled != features.get(f)) {
362                return Some(tied);
363            }
364        }
365    }
366    None
367}
368
369/// Translates the `-Ctarget-feature` flag into a backend target feature list.
370///
371/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
372/// accessible by that closure) to enable/disable the given Rust feature name.
373pub fn flag_to_backend_features<'a>(
374    sess: &'a Session,
375    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
376) {
377    // Compute implied features
378    let mut rust_features = vec![];
379    parse_rust_feature_flag(
380        sess,
381        /* err_callback */
382        |_feature| {
383            // Errors are already emitted in `cfg_target_feature`; avoid duplicates.
384        },
385        |_base_feature, new_features, enable| {
386            rust_features.extend(
387                UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
388            );
389        },
390    );
391
392    // Add this to the backend features.
393    for (enable, feature) in rust_features {
394        extend_backend_features(feature, enable);
395    }
396}
397
398/// Computes the backend target features to be added to account for retpoline flags.
399/// Used by both LLVM and GCC since their target features are, conveniently, the same.
400pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
401    // -Zretpoline without -Zretpoline-external-thunk enables
402    // retpoline-indirect-branches and retpoline-indirect-calls target features
403    let unstable_opts = &sess.opts.unstable_opts;
404    if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
405        features.push("+retpoline-indirect-branches".into());
406        features.push("+retpoline-indirect-calls".into());
407    }
408    // -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
409    // retpoline-external-thunk, retpoline-indirect-branches and
410    // retpoline-indirect-calls target features
411    if unstable_opts.retpoline_external_thunk {
412        features.push("+retpoline-external-thunk".into());
413        features.push("+retpoline-indirect-branches".into());
414        features.push("+retpoline-indirect-calls".into());
415    }
416}
417
418pub(crate) fn provide(providers: &mut Providers) {
419    *providers = Providers {
420        rust_target_features: |tcx, cnum| {
421            assert_eq!(cnum, LOCAL_CRATE);
422            if tcx.sess.opts.actually_rustdoc {
423                // HACK: rustdoc would like to pretend that we have all the target features, so we
424                // have to merge all the lists into one. To ensure an unstable target never prevents
425                // a stable one from working, we merge the stability info of all instances of the
426                // same target feature name, with the "most stable" taking precedence. And then we
427                // hope that this doesn't cause issues anywhere else in the compiler...
428                let mut result: UnordMap<String, Stability> = Default::default();
429                for (name, stability) in rustc_target::target_features::all_rust_features() {
430                    use std::collections::hash_map::Entry;
431                    match result.entry(name.to_owned()) {
432                        Entry::Vacant(vacant_entry) => {
433                            vacant_entry.insert(stability);
434                        }
435                        Entry::Occupied(mut occupied_entry) => {
436                            // Merge the two stabilities, "more stable" taking precedence.
437                            match (occupied_entry.get(), stability) {
438                                (Stability::Stable, _)
439                                | (
440                                    Stability::Unstable { .. },
441                                    Stability::Unstable { .. } | Stability::Forbidden { .. },
442                                )
443                                | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => {
444                                    // The stability in the entry is at least as good as the new
445                                    // one, just keep it.
446                                }
447                                _ => {
448                                    // Overwrite stability.
449                                    occupied_entry.insert(stability);
450                                }
451                            }
452                        }
453                    }
454                }
455                result
456            } else {
457                tcx.sess
458                    .target
459                    .rust_target_features()
460                    .iter()
461                    .map(|(a, b, _)| (a.to_string(), *b))
462                    .collect()
463            }
464        },
465        implied_target_features: |tcx, feature: Symbol| {
466            let feature = feature.as_str();
467            UnordSet::from(tcx.sess.target.implied_target_features(feature))
468                .into_sorted_stable_ord()
469                .into_iter()
470                .map(|s| Symbol::intern(s))
471                .collect()
472        },
473        asm_target_features,
474        ..*providers
475    }
476}