1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_data_structures::unord::{UnordMap, UnordSet};
3use rustc_hir::attrs::InstructionSetAttr;
4use rustc_hir::def::DefKind;
5use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
6use rustc_middle::middle::codegen_fn_attrs::{TargetFeature, TargetFeatureKind};
7use rustc_middle::query::Providers;
8use rustc_middle::ty::TyCtxt;
9use rustc_session::Session;
10use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
11use rustc_session::parse::feature_err;
12use rustc_span::{Span, Symbol, sym};
13use rustc_target::spec::Arch;
14use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
15use smallvec::SmallVec;
16
17use crate::errors::FeatureNotValid;
18use crate::{errors, target_features};
19
20pub(crate) fn from_target_feature_attr(
23 tcx: TyCtxt<'_>,
24 did: LocalDefId,
25 features: &[(Symbol, Span)],
26 was_forced: bool,
27 rust_target_features: &UnordMap<String, target_features::Stability>,
28 target_features: &mut Vec<TargetFeature>,
29) {
30 let rust_features = tcx.features();
31 let abi_feature_constraints = tcx.sess.target.abi_required_features();
32 for &(feature, feature_span) in features {
33 let feature_str = feature.as_str();
34 let Some(stability) = rust_target_features.get(feature_str) else {
35 let plus_hint = feature_str
36 .strip_prefix('+')
37 .is_some_and(|stripped| rust_target_features.contains_key(stripped));
38 tcx.dcx().emit_err(FeatureNotValid {
39 feature: feature_str,
40 span: feature_span,
41 plus_hint,
42 });
43 continue;
44 };
45
46 if let Err(reason) = stability.toggle_allowed() {
49 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
50 span: feature_span,
51 feature: feature_str,
52 reason,
53 });
54 } else if let Some(nightly_feature) = stability.requires_nightly()
55 && !rust_features.enabled(nightly_feature)
56 {
57 feature_err(
58 &tcx.sess,
59 nightly_feature,
60 feature_span,
61 format!("the target feature `{feature}` is currently unstable"),
62 )
63 .emit();
64 } else {
65 for &name in tcx.implied_target_features(feature) {
67 if !tcx.sess.opts.actually_rustdoc {
74 if abi_feature_constraints.incompatible.contains(&name.as_str()) {
75 if tcx.sess.target.arch == Arch::AArch64 && name.as_str() == "neon" {
78 tcx.emit_node_span_lint(
79 AARCH64_SOFTFLOAT_NEON,
80 tcx.local_def_id_to_hir_id(did),
81 feature_span,
82 errors::Aarch64SoftfloatNeon,
83 );
84 } else {
85 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
86 span: feature_span,
87 feature: name.as_str(),
88 reason: "this feature is incompatible with the target ABI",
89 });
90 }
91 }
92 }
93 let kind = if name != feature {
94 TargetFeatureKind::Implied
95 } else if was_forced {
96 TargetFeatureKind::Forced
97 } else {
98 TargetFeatureKind::Enabled
99 };
100 target_features.push(TargetFeature { name, kind })
101 }
102 }
103 }
104}
105
106fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet<Symbol> {
109 let mut target_features = tcx.sess.unstable_target_features.clone();
110 if tcx.def_kind(did).has_codegen_attrs() {
111 let attrs = tcx.codegen_fn_attrs(did);
112 target_features.extend(attrs.target_features.iter().map(|feature| feature.name));
113 match attrs.instruction_set {
114 None => {}
115 Some(InstructionSetAttr::ArmA32) => {
116 target_features.swap_remove(&sym::thumb_mode);
118 }
119 Some(InstructionSetAttr::ArmT32) => {
120 target_features.insert(sym::thumb_mode);
121 }
122 }
123 }
124
125 tcx.arena.alloc(target_features)
126}
127
128pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, attr_span: Span) {
131 if let DefKind::AssocFn = tcx.def_kind(id) {
132 let parent_id = tcx.local_parent(id);
133 if let DefKind::Trait | DefKind::Impl { of_trait: true } = tcx.def_kind(parent_id) {
134 tcx.dcx().emit_err(errors::TargetFeatureSafeTrait {
135 span: attr_span,
136 def: tcx.def_span(id),
137 });
138 }
139 }
140}
141
142fn parse_rust_feature_list<'a>(
146 sess: &'a Session,
147 features: &'a str,
148 err_callback: impl Fn(&'a str),
149 mut callback: impl FnMut(
150 &'a str,
151 FxHashSet<&'a str>,
152 bool,
153 ),
154) {
155 let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
157
158 for feature in features.split(',') {
159 if let Some(base_feature) = feature.strip_prefix('+') {
160 if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
162 continue;
163 }
164
165 callback(base_feature, sess.target.implied_target_features(base_feature), true)
166 } else if let Some(base_feature) = feature.strip_prefix('-') {
167 if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
169 continue;
170 }
171
172 let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
177 let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
178 for (f, _, is) in sess.target.rust_target_features() {
179 for i in is.iter() {
180 set.entry(i).or_default().insert(f);
181 }
182 }
183 set
184 });
185
186 let mut features = FxHashSet::default();
189 let mut new_features = vec![base_feature];
190 while let Some(new_feature) = new_features.pop() {
191 if features.insert(new_feature) {
192 if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
193 #[allow(rustc::potential_query_instability)]
194 new_features.extend(implied_features)
195 }
196 }
197 }
198
199 callback(base_feature, features, false)
200 } else if !feature.is_empty() {
201 err_callback(feature)
202 }
203 }
204}
205
206pub fn cfg_target_feature<'a, const N: usize>(
221 sess: &Session,
222 to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
223 mut target_base_has_feature: impl FnMut(&str) -> bool,
224) -> (Vec<Symbol>, Vec<Symbol>) {
225 let known_features = sess.target.rust_target_features();
226
227 let mut features: UnordSet<Symbol> = sess
230 .target
231 .rust_target_features()
232 .iter()
233 .filter(|(feature, _, _)| target_base_has_feature(feature))
234 .flat_map(|(base_feature, _, _)| {
235 #[allow(rustc::potential_query_instability)]
241 sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f))
242 })
243 .collect();
244
245 let mut enabled_disabled_features = FxHashMap::default();
246
247 parse_rust_feature_list(
249 sess,
250 &sess.opts.cg.target_feature,
251 |feature| {
253 sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
254 },
255 |base_feature, new_features, enable| {
256 #[allow(rustc::potential_query_instability)]
258 enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable)));
259
260 #[allow(rustc::potential_query_instability)]
262 if enable {
263 features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
264 } else {
265 for new in new_features {
267 features.remove(&Symbol::intern(new));
268 }
269 }
270
271 let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
273 match feature_state {
274 None => {
275 let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
278 let backend_features = to_backend_features(rust_feature);
279 if backend_features.contains(&base_feature)
280 && !backend_features.contains(&rust_feature)
281 {
282 Some(rust_feature)
283 } else {
284 None
285 }
286 });
287 let unknown_feature = if let Some(rust_feature) = rust_feature {
288 errors::UnknownCTargetFeature {
289 feature: base_feature,
290 rust_feature: errors::PossibleFeature::Some { rust_feature },
291 }
292 } else {
293 errors::UnknownCTargetFeature {
294 feature: base_feature,
295 rust_feature: errors::PossibleFeature::None,
296 }
297 };
298 sess.dcx().emit_warn(unknown_feature);
299 }
300 Some((_, stability, _)) => {
301 if let Err(reason) = stability.toggle_allowed() {
302 sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
303 feature: base_feature,
304 enabled: if enable { "enabled" } else { "disabled" },
305 reason,
306 });
307 } else if stability.requires_nightly().is_some() {
308 sess.dcx()
312 .emit_warn(errors::UnstableCTargetFeature { feature: base_feature });
313 }
314 }
315 }
316 },
317 );
318
319 if let Some(f) = check_tied_features(sess, &enabled_disabled_features) {
320 sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
321 features: f,
322 span: None,
323 missing_features: None,
324 });
325 }
326
327 let f = |allow_unstable| {
329 sess.target
330 .rust_target_features()
331 .iter()
332 .filter_map(|(feature, gate, _)| {
333 if allow_unstable
337 || (gate.in_cfg()
338 && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
339 {
340 Some(Symbol::intern(feature))
341 } else {
342 None
343 }
344 })
345 .filter(|feature| features.contains(&feature))
346 .collect()
347 };
348
349 (f(true), f(false))
350}
351
352pub fn check_tied_features(
355 sess: &Session,
356 features: &FxHashMap<&str, bool>,
357) -> Option<&'static [&'static str]> {
358 if !features.is_empty() {
359 for tied in sess.target.tied_target_features() {
360 let mut tied_iter = tied.iter();
362 let enabled = features.get(tied_iter.next().unwrap());
363 if tied_iter.any(|f| enabled != features.get(f)) {
364 return Some(tied);
365 }
366 }
367 }
368 None
369}
370
371pub fn target_spec_to_backend_features<'a>(
376 sess: &'a Session,
377 mut extend_backend_features: impl FnMut(&'a str, bool),
378) {
379 let mut rust_features = vec![];
381 parse_rust_feature_list(
382 sess,
383 &sess.target.features,
384 |feature| {
386 panic!("Target spec contains invalid feature {feature}");
387 },
388 |_base_feature, new_features, enable| {
389 rust_features.extend(
391 UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
392 );
393 },
394 );
395
396 for (enable, feature) in rust_features {
398 extend_backend_features(feature, enable);
399 }
400}
401
402pub fn flag_to_backend_features<'a>(
407 sess: &'a Session,
408 mut extend_backend_features: impl FnMut(&'a str, bool),
409) {
410 let mut rust_features = vec![];
412 parse_rust_feature_list(
413 sess,
414 &sess.opts.cg.target_feature,
415 |_feature| {
417 },
419 |_base_feature, new_features, enable| {
420 rust_features.extend(
421 UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
422 );
423 },
424 );
425
426 for (enable, feature) in rust_features {
428 extend_backend_features(feature, enable);
429 }
430}
431
432pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
435 let unstable_opts = &sess.opts.unstable_opts;
438 if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
439 features.push("+retpoline-indirect-branches".into());
440 features.push("+retpoline-indirect-calls".into());
441 }
442 if unstable_opts.retpoline_external_thunk {
446 features.push("+retpoline-external-thunk".into());
447 features.push("+retpoline-indirect-branches".into());
448 features.push("+retpoline-indirect-calls".into());
449 }
450}
451
452pub(crate) fn provide(providers: &mut Providers) {
453 *providers = Providers {
454 rust_target_features: |tcx, cnum| {
455 assert_eq!(cnum, LOCAL_CRATE);
456 if tcx.sess.opts.actually_rustdoc {
457 let mut result: UnordMap<String, Stability> = Default::default();
463 for (name, stability) in rustc_target::target_features::all_rust_features() {
464 use std::collections::hash_map::Entry;
465 match result.entry(name.to_owned()) {
466 Entry::Vacant(vacant_entry) => {
467 vacant_entry.insert(stability);
468 }
469 Entry::Occupied(mut occupied_entry) => {
470 match (occupied_entry.get(), stability) {
472 (Stability::Stable, _)
473 | (
474 Stability::Unstable { .. },
475 Stability::Unstable { .. } | Stability::Forbidden { .. },
476 )
477 | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => {
478 }
481 _ => {
482 occupied_entry.insert(stability);
484 }
485 }
486 }
487 }
488 }
489 result
490 } else {
491 tcx.sess
492 .target
493 .rust_target_features()
494 .iter()
495 .map(|(a, b, _)| (a.to_string(), *b))
496 .collect()
497 }
498 },
499 implied_target_features: |tcx, feature: Symbol| {
500 let feature = feature.as_str();
501 UnordSet::from(tcx.sess.target.implied_target_features(feature))
502 .into_sorted_stable_ord()
503 .into_iter()
504 .map(|s| Symbol::intern(s))
505 .collect()
506 },
507 asm_target_features,
508 ..*providers
509 }
510}