rustc_codegen_ssa/
target_features.rs

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