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