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