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_lint_defs::builtin::{AARCH64_SOFTFLOAT_NEON, X86_SOFTFLOAT_SSE};
7use rustc_middle::middle::codegen_fn_attrs::{TargetFeature, TargetFeatureKind};
8use rustc_middle::query::Providers;
9use rustc_middle::ty::TyCtxt;
10use rustc_session::Session;
11use rustc_session::diagnostics::feature_err;
12use rustc_span::{Span, Symbol, edit_distance, sym};
13use rustc_target::spec::{Arch, SanitizerSet};
14use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
15use smallvec::SmallVec;
16
17use crate::diagnostics::{CrossArchFeatureNote, FeatureNotValid, FeatureNotValidHint};
18use crate::{diagnostics, 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 {
54                feature: feature_str,
55                span: feature_span,
56                hint,
57                cross_arch: {
58                    let arches = rustc_target::target_features::feature_to_arch_names(feature_str);
59                    match arches {
60                        [] => None,
61                        [arch] => Some(CrossArchFeatureNote::Single { feature: feature_str, arch }),
62                        [..] => Some(CrossArchFeatureNote::Multiple {
63                            feature: feature_str,
64                            arches: arches.into(),
65                        }),
66                    }
67                },
68            });
69            continue;
70        };
71
72        // Only allow target features whose feature gates have been enabled
73        // and which are permitted to be toggled.
74        if let Err(reason) = stability.toggle_allowed() {
75            tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr {
76                span: feature_span,
77                feature: feature_str,
78                reason,
79            });
80        } else if let Some(nightly_feature) = stability.requires_nightly(/* in_cfg */ false)
81            && !rust_features.enabled(nightly_feature)
82        {
83            let explain = if stability.is_cfg_stable_toggle_unstable() {
84                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the target feature `{0}` is allowed in cfg but unstable otherwise",
                feature))
    })format!("the target feature `{feature}` is allowed in cfg but unstable otherwise")
85            } else {
86                ::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")
87            };
88            feature_err(&tcx.sess, nightly_feature, feature_span, explain).emit();
89        } else {
90            // Add this and the implied features.
91            for &name in tcx.implied_target_features(feature) {
92                // But ensure the ABI does not forbid enabling this.
93                // Here we do assume that the backend doesn't add even more implied features
94                // we don't know about, at least no features that would have ABI effects!
95                // We skip this logic in rustdoc, where we want to allow all target features of
96                // all targets, so we can't check their ABI compatibility and anyway we are not
97                // generating code so "it's fine".
98                if !tcx.sess.opts.actually_rustdoc {
99                    if abi_feature_constraints.incompatible.contains(&name.as_str()) {
100                        // For "neon" specifically, we emit an FCW instead of a hard error.
101                        // See <https://github.com/rust-lang/rust/issues/134375>.
102                        // Similar for "sse" on x86.
103                        // See <https://github.com/rust-lang/rust/issues/117938>.
104                        if tcx.sess.target.arch == Arch::AArch64 && name.as_str() == "neon" {
105                            tcx.emit_node_span_lint(
106                                AARCH64_SOFTFLOAT_NEON,
107                                tcx.local_def_id_to_hir_id(did),
108                                feature_span,
109                                diagnostics::Aarch64SoftfloatNeon,
110                            );
111                        } else if #[allow(non_exhaustive_omitted_patterns)] match tcx.sess.target.arch {
    Arch::X86 | Arch::X86_64 => true,
    _ => false,
}matches!(tcx.sess.target.arch, Arch::X86 | Arch::X86_64)
112                            && name.as_str() == "sse"
113                        {
114                            tcx.emit_node_span_lint(
115                                X86_SOFTFLOAT_SSE,
116                                tcx.local_def_id_to_hir_id(did),
117                                feature_span,
118                                diagnostics::X86SoftfloatSse,
119                            );
120                        } else {
121                            tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr {
122                                span: feature_span,
123                                feature: name.as_str(),
124                                reason: "this feature is incompatible with the target ABI",
125                            });
126                        }
127                    }
128                }
129                let kind = if name != feature {
130                    TargetFeatureKind::Implied
131                } else if was_forced {
132                    TargetFeatureKind::Forced
133                } else {
134                    TargetFeatureKind::Enabled
135                };
136                target_features.push(TargetFeature { name, kind });
137
138                if !rust_target_features
139                    .get(name.as_str())
140                    .is_some_and(|s| s.toggle_allowed().is_ok())
141                {
142                    tcx.dcx().span_delayed_bug(
143                        feature_span,
144                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("internal-only feature {0} should not be toggled by `#[target_feature]`",
                name))
    })format!("internal-only feature {name} should not be toggled by `#[target_feature]`"),
145                    );
146                }
147            }
148        }
149    }
150}
151
152/// Computes the set of target features used in a function for the purposes of
153/// inline assembly.
154fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet<Symbol> {
155    let mut target_features = tcx.sess.internal_target_features.clone();
156    if tcx.def_kind(did).has_codegen_attrs() {
157        let attrs = tcx.codegen_fn_attrs(did);
158        target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
159        match attrs.instruction_set {
160            None => {}
161            Some(InstructionSetAttr::ArmA32) => {
162                // FIXME(#120456) - is `swap_remove` correct?
163                target_features.swap_remove(&sym::thumb_mode);
164            }
165            Some(InstructionSetAttr::ArmT32) => {
166                target_features.insert(sym::thumb_mode);
167            }
168        }
169    }
170
171    tcx.arena.alloc(target_features)
172}
173
174/// Checks the function annotated with `#[target_feature]` is not a safe
175/// trait method implementation, reporting an error if it is.
176pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) {
177    if let DefKind::AssocFn = tcx.def_kind(id) {
178        let parent_id = tcx.local_parent(id);
179        if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
180            tcx.dcx().emit_err(diagnostics::TargetFeatureSafeTrait {
181                span: attr_span,
182                def: tcx.def_span(id),
183            });
184        }
185    }
186}
187
188/// Parse the value of the target spec `features` field or `-Ctarget-feature`, calling the closure
189/// for each entry in the list, also expanding implied features (but only for actual Rust target
190/// features). If the list contains a syntactically invalid item (not starting with `+`/`-`) , the
191/// error callback is invoked.
192fn parse_rust_feature_list<'a>(
193    sess: &'a Session,
194    features: &'a str,
195    err_callback: impl Fn(&'a str),
196    mut callback: impl FnMut(
197        /* base_feature */ &'a str,
198        /* with_implied */ Option<FxHashSet<&'a str>>,
199        /* enable */ bool,
200    ),
201) {
202    // A cache for the forward and backwards feature maps.
203    let mut features_map: Option<FxHashMap<&str, _>> = None;
204    let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
205
206    for feature in features.split(',') {
207        if let Some(base_feature) = feature.strip_prefix('+') {
208            // Skip features that are not target features, but rustc features.
209            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
210                continue;
211            }
212
213            let features_map =
214                features_map.get_or_insert_with(|| sess.target.rust_target_features_map());
215
216            if !features_map.contains_key(&base_feature) {
217                callback(base_feature, None, true);
218                continue;
219            }
220
221            let implied_features = sess.target.implied_target_features(base_feature, &features_map);
222            callback(base_feature, Some(implied_features), true)
223        } else if let Some(base_feature) = feature.strip_prefix('-') {
224            // Skip features that are not target features, but rustc features.
225            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
226                continue;
227            }
228
229            let features_map =
230                features_map.get_or_insert_with(|| sess.target.rust_target_features_map());
231
232            if !features_map.contains_key(&base_feature) {
233                callback(base_feature, None, false);
234                continue;
235            }
236
237            // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
238            // contraposition. So we have to find all the reverse implications of `base_feature` and
239            // disable them, too.
240
241            let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
242                let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
243                for (f, _, is) in sess.target.rust_target_features() {
244                    for i in is.iter() {
245                        set.entry(i).or_default().insert(f);
246                    }
247                }
248                set
249            });
250
251            // Inverse implied target features have their own inverse implied target features, so we
252            // traverse the map until there are no more features to add.
253            let mut implied_features = FxHashSet::default();
254            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];
255            while let Some(new_feature) = new_features.pop() {
256                if implied_features.insert(new_feature) {
257                    if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
258                        #[allow(rustc::potential_query_instability)]
259                        new_features.extend(implied_features)
260                    }
261                }
262            }
263
264            callback(base_feature, Some(implied_features), false)
265        } else if !feature.is_empty() {
266            err_callback(feature)
267        }
268    }
269}
270
271/// Utility function for a codegen backend to compute the set of all actually enabled Rust target
272/// features (which will be stored in `sess.internal_target_features`).
273///
274/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
275/// used for diagnostic purposes only.
276///
277/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is
278/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`. Note that LLVM
279/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely
280/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU
281/// to target features.
282///
283/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
284pub fn internal_target_features<'a, const N: usize>(
285    sess: &Session,
286    to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
287    mut target_base_has_feature: impl FnMut(&str) -> bool,
288) -> UnordSet<Symbol> {
289    let features_map = sess.target.rust_target_features_map();
290
291    // Compute which of the known target features are enabled in the 'base' target machine: for
292    // every Rust target feature, ask the backend if it is enabled.
293    let mut features: UnordSet<Symbol> = sess
294        .target
295        .rust_target_features()
296        .iter()
297        .filter(|(feature, _, _)| target_base_has_feature(feature))
298        .flat_map(|(base_feature, _, _)| {
299            // Expand the direct base feature into all transitively-implied features. Note that we
300            // cannot simply use the `implied` field of the tuple since that only contains
301            // directly-implied features.
302            //
303            // Iteration order is irrelevant because we're collecting into an `UnordSet`.
304            #[allow(rustc::potential_query_instability)]
305            sess.target
306                .implied_target_features(base_feature, &features_map)
307                .into_iter()
308                .map(|f| Symbol::intern(f))
309        })
310        .collect();
311
312    // State gathered for "tied features" check.
313    let mut enabled_disabled_features = FxHashMap::default();
314
315    // Add enabled and remove disabled features.
316    parse_rust_feature_list(
317        sess,
318        &sess.opts.cg.target_feature,
319        /* err_callback */
320        |feature| {
321            sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature });
322        },
323        |base_feature, new_features, enable| {
324            match features_map.get(base_feature) {
325                None => {
326                    // This is definitely not a valid Rust feature name. We do not add it to
327                    // `features`. Maybe it is a backend feature name? If so, give a better error
328                    // message.
329                    let rust_feature = sess.target.rust_target_features().iter().find_map(
330                        |&(rust_feature, _, _)| {
331                            let backend_features = to_backend_features(rust_feature);
332                            if backend_features.contains(&base_feature)
333                                && !backend_features.contains(&rust_feature)
334                            {
335                                Some(rust_feature)
336                            } else {
337                                None
338                            }
339                        },
340                    );
341                    let unknown_feature = if let Some(rust_feature) = rust_feature {
342                        diagnostics::UnknownCTargetFeature {
343                            feature: base_feature,
344                            rust_feature: diagnostics::PossibleFeature::Some { rust_feature },
345                        }
346                    } else {
347                        diagnostics::UnknownCTargetFeature {
348                            feature: base_feature,
349                            rust_feature: diagnostics::PossibleFeature::None,
350                        }
351                    };
352                    sess.dcx().emit_warn(unknown_feature);
353                }
354                Some((stability, _)) => {
355                    let new_features = new_features.unwrap();
356                    // Add feature to our set -- only if it is actually a recognized feature.
357                    // Iteration order is irrelevant since this only influences an `FxHashMap`.
358                    #[allow(rustc::potential_query_instability)]
359                    enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
360
361                    // Iteration order is irrelevant since this only influences an `UnordSet`.
362                    #[allow(rustc::potential_query_instability)]
363                    if enable {
364                        features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
365                    } else {
366                        // Remove `new_features` from `features`.
367                        for new in new_features {
368                            features.remove(&Symbol::intern(new));
369                        }
370                    }
371
372                    // Check feature stability.
373                    if let Stability::InternalOnly { reason, hard_error } = stability {
374                        let diag = diagnostics::InternalOnlyCTargetFeature {
375                            feature: base_feature,
376                            enabled: if enable { "enabled" } else { "disabled" },
377                            reason,
378                            future_compat_note: !hard_error,
379                        };
380
381                        if *hard_error {
382                            sess.dcx().emit_err(diag);
383                        } else {
384                            sess.dcx().emit_warn(diag);
385                        }
386                    } else if stability.requires_nightly(/* in_cfg */ false).is_some() {
387                        // An unstable feature. Warn about using it. It makes little sense
388                        // to hard-error here since we just warn about fully unknown
389                        // features above.
390                        let note = if stability.is_cfg_stable_toggle_unstable() {
391                            "this feature is allowed in cfg but unstable otherwise"
392                        } else {
393                            "this feature is not stably supported"
394                        };
395                        sess.dcx().emit_warn(diagnostics::UnstableCTargetFeature {
396                            feature: base_feature,
397                            note,
398                        });
399                    }
400                }
401            }
402        },
403    );
404
405    if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
406        sess.dcx().emit_err(diagnostics::TargetFeatureDisableOrEnable {
407            features: f,
408            span: None,
409            missing_features: None,
410        });
411    }
412
413    features
414}
415
416/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
417/// combinations are allowed. Returns `Some` if a violation is found.
418pub fn check_tied_features(
419    sess: &Session,
420    features: &FxHashMap<&str, bool>,
421) -> Option<&'static [&'static str]> {
422    if !features.is_empty() {
423        for tied in sess.target.tied_target_features() {
424            // Tied features must be set to the same value, or not set at all
425            let mut tied_iter = tied.iter();
426            let enabled = features.get(tied_iter.next().unwrap());
427            if tied_iter.any(|f| enabled != features.get(f)) {
428                return Some(tied);
429            }
430        }
431    }
432    None
433}
434
435/// Translates the target spec `features` field into a backend target feature list.
436///
437/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
438/// accessible by that closure) to enable/disable the given Rust feature name.
439pub fn target_spec_to_backend_features<'a>(
440    sess: &'a Session,
441    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
442) {
443    // This check handles SM versions that defaults (by LLVM) to unsupported (by Rust) PTX ISA versions.
444    // sm_70, sm_72 and sm_75 defaults to PTX ISA versions with major version 6, while sm_80 default to 7.0
445    if sess.target.arch == Arch::Nvptx64
446        && #[allow(non_exhaustive_omitted_patterns)] match sess.opts.cg.target_cpu.as_deref()
    {
    None | Some("sm_70") | Some("sm_72") | Some("sm_75") => true,
    _ => false,
}matches!(
447            sess.opts.cg.target_cpu.as_deref(),
448            None | Some("sm_70") | Some("sm_72") | Some("sm_75")
449        )
450    {
451        extend_backend_features("ptx70", true);
452    }
453
454    // Compute implied features
455    parse_rust_feature_list(
456        sess,
457        &sess.target.features,
458        /* err_callback */
459        |feature| {
460            {
    ::core::panicking::panic_fmt(format_args!("Target spec contains invalid feature {0} (missing `+`/`-` prefix)",
            feature));
};panic!("Target spec contains invalid feature {feature} (missing `+`/`-` prefix)");
461        },
462        |base_feature, new_features, enable| {
463            // FIXME emit an error for unknown features in the target spec like
464            // internal_target_features would for -Ctarget-feature.
465            let new_features =
466                new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature)));
467            for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() {
468                extend_backend_features(new_feature, enable);
469            }
470        },
471    );
472}
473
474/// Translates the `-Ctarget-feature` flag into a backend target feature list.
475///
476/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
477/// accessible by that closure) to enable/disable the given Rust feature name.
478pub fn flag_to_backend_features<'a>(
479    sess: &'a Session,
480    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
481) {
482    parse_rust_feature_list(
483        sess,
484        &sess.opts.cg.target_feature,
485        /* err_callback */
486        |_feature| {
487            // Errors are already emitted in `internal_target_features`; avoid duplicates.
488        },
489        |base_feature, new_features, enable| {
490            // Forward unknown features to the backend as that's what we have always done.
491            let new_features =
492                new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature)));
493            for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() {
494                extend_backend_features(new_feature, enable);
495            }
496        },
497    );
498}
499
500/// Computes the backend target features to be added to account for retpoline flags.
501/// Used by both LLVM and GCC since their target features are, conveniently, the same.
502pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
503    // -Zretpoline without -Zretpoline-external-thunk enables
504    // retpoline-indirect-branches and retpoline-indirect-calls target features
505    let unstable_opts = &sess.opts.unstable_opts;
506    if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
507        features.push("+retpoline-indirect-branches".into());
508        features.push("+retpoline-indirect-calls".into());
509    }
510    // -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
511    // retpoline-external-thunk, retpoline-indirect-branches and
512    // retpoline-indirect-calls target features
513    if unstable_opts.retpoline_external_thunk {
514        features.push("+retpoline-external-thunk".into());
515        features.push("+retpoline-indirect-branches".into());
516        features.push("+retpoline-indirect-calls".into());
517    }
518}
519
520/// Computes the backend target features to be added to account for sanitizer flags.
521pub fn sanitizer_features_by_flags(sess: &Session, features: &mut Vec<String>) {
522    // It's intentional that this is done only for non-kernel version of hwaddress. This matches
523    // clang behavior.
524    if sess.sanitizers().contains(SanitizerSet::HWADDRESS) {
525        features.push("+tagged-globals".into());
526    }
527}
528
529pub(crate) fn provide(providers: &mut Providers) {
530    *providers = Providers {
531        all_rust_target_features: |tcx, cnum| {
532            {
    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);
533            if tcx.sess.opts.actually_rustdoc {
534                // HACK: rustdoc would like to pretend that we have all the target features, so we
535                // have to merge all the lists into one. To ensure an unstable target never prevents
536                // a stable one from working, we merge the stability info of all instances of the
537                // same target feature name, with the "most stable" taking precedence. And then we
538                // hope that this doesn't cause issues anywhere else in the compiler...
539                let mut result: UnordMap<String, Stability> = Default::default();
540                for (name, stability) in rustc_target::target_features::all_rust_features() {
541                    use std::collections::hash_map::Entry;
542                    match result.entry(name.to_owned()) {
543                        Entry::Vacant(vacant_entry) => {
544                            vacant_entry.insert(stability);
545                        }
546                        Entry::Occupied(mut occupied_entry) => {
547                            // Merge the two stabilities, "more stable" taking precedence.
548                            match (occupied_entry.get(), stability) {
549                                (Stability::Stable, _)
550                                | (
551                                    Stability::Unstable { .. },
552                                    Stability::Unstable { .. } | Stability::InternalOnly { .. },
553                                )
554                                | (
555                                    Stability::InternalOnly { .. },
556                                    Stability::InternalOnly { .. },
557                                ) => {
558                                    // The stability in the entry is at least as good as the new
559                                    // one, just keep it.
560                                }
561                                _ => {
562                                    // Overwrite stability.
563                                    occupied_entry.insert(stability);
564                                }
565                            }
566                        }
567                    }
568                }
569                result
570            } else {
571                tcx.sess
572                    .target
573                    .rust_target_features()
574                    .iter()
575                    .map(|(feat, stab, _)| (feat.to_string(), *stab))
576                    .collect()
577            }
578        },
579        implied_target_features: |tcx, feature: Symbol| {
580            if tcx.sess.opts.actually_rustdoc {
581                // We can't handle implication when we are mixing all targets.
582                return ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [feature]))vec![feature];
583            }
584            let features_map = tcx.sess.target.rust_target_features_map();
585            let feature = feature.as_str();
586            UnordSet::from(tcx.sess.target.implied_target_features(feature, &features_map))
587                .into_sorted_stable_ord()
588                .into_iter()
589                .map(|s| Symbol::intern(s))
590                .collect()
591        },
592        asm_target_features,
593        ..*providers
594    }
595}