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;
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 continue;
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 continue;
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`. Note that LLVM
201/// may consider features to be implied that we do not and vice-versa. We want `cfg` to be entirely
202/// consistent with Rust feature implications, and thus only consult LLVM to expand the target CPU
203/// to target features.
204///
205/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
206pub fn cfg_target_feature(
207 sess: &Session,
208 mut target_base_has_feature: impl FnMut(&str) -> bool,
209) -> (Vec<Symbol>, Vec<Symbol>) {
210 // Compute which of the known target features are enabled in the 'base' target machine. We only
211 // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
212 let mut features: UnordSet<Symbol> = sess
213 .target
214 .rust_target_features()
215 .iter()
216 .filter(|(feature, _, _)| target_base_has_feature(feature))
217 .flat_map(|(base_feature, _, _)| {
218 // Expand the direct base feature into all transitively-implied features. Note that we
219 // cannot simply use the `implied` field of the tuple since that only contains
220 // directly-implied features.
221 //
222 // Iteration order is irrelevant because we're collecting into an `UnordSet`.
223 #[allow(rustc::potential_query_instability)]
224 sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f))
225 })
226 .collect();
227
228 // Add enabled and remove disabled features.
229 parse_rust_feature_flag(
230 sess,
231 /* err_callback */
232 |_| {
233 // Errors are already emitted in `flag_to_backend_features`; avoid duplicates.
234 },
235 |_base_feature, new_features, enabled| {
236 // Iteration order is irrelevant since this only influences an `UnordSet`.
237 #[allow(rustc::potential_query_instability)]
238 if enabled {
239 features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
240 } else {
241 // Remove `new_features` from `features`.
242 for new in new_features {
243 features.remove(&Symbol::intern(new));
244 }
245 }
246 },
247 );
248
249 // Filter enabled features based on feature gates.
250 let f = |allow_unstable| {
251 sess.target
252 .rust_target_features()
253 .iter()
254 .filter_map(|(feature, gate, _)| {
255 // The `allow_unstable` set is used by rustc internally to determine which target
256 // features are truly available, so we want to return even perma-unstable
257 // "forbidden" features.
258 if allow_unstable
259 || (gate.in_cfg()
260 && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
261 {
262 Some(Symbol::intern(feature))
263 } else {
264 None
265 }
266 })
267 .filter(|feature| features.contains(&feature))
268 .collect()
269 };
270
271 (f(true), f(false))
272}
273
274/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
275/// combinations are allowed.
276pub fn check_tied_features(
277 sess: &Session,
278 features: &FxHashMap<&str, bool>,
279) -> Option<&'static [&'static str]> {
280 if !features.is_empty() {
281 for tied in sess.target.tied_target_features() {
282 // Tied features must be set to the same value, or not set at all
283 let mut tied_iter = tied.iter();
284 let enabled = features.get(tied_iter.next().unwrap());
285 if tied_iter.any(|f| enabled != features.get(f)) {
286 return Some(tied);
287 }
288 }
289 }
290 None
291}
292
293/// Translates the `-Ctarget-feature` flag into a backend target feature list.
294///
295/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
296/// used for diagnostic purposes only.
297///
298/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
299/// accessible by that closure) to enable/disable the given Rust feature name.
300pub fn flag_to_backend_features<'a, const N: usize>(
301 sess: &'a Session,
302 diagnostics: bool,
303 to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
304 mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
305) {
306 let known_features = sess.target.rust_target_features();
307
308 // Compute implied features
309 let mut rust_features = vec![];
310 parse_rust_feature_flag(
311 sess,
312 /* err_callback */
313 |feature| {
314 if diagnostics {
315 sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
316 }
317 },
318 |base_feature, new_features, enable| {
319 rust_features.extend(
320 UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
321 );
322 // Check feature validity.
323 if diagnostics {
324 let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
325 match feature_state {
326 None => {
327 // This is definitely not a valid Rust feature name. Maybe it is a backend
328 // feature name? If so, give a better error message.
329 let rust_feature =
330 known_features.iter().find_map(|&(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 let unknown_feature = if let Some(rust_feature) = rust_feature {
341 errors::UnknownCTargetFeature {
342 feature: base_feature,
343 rust_feature: errors::PossibleFeature::Some { rust_feature },
344 }
345 } else {
346 errors::UnknownCTargetFeature {
347 feature: base_feature,
348 rust_feature: errors::PossibleFeature::None,
349 }
350 };
351 sess.dcx().emit_warn(unknown_feature);
352 }
353 Some((_, stability, _)) => {
354 if let Err(reason) = stability.toggle_allowed() {
355 sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
356 feature: base_feature,
357 enabled: if enable { "enabled" } else { "disabled" },
358 reason,
359 });
360 } else if stability.requires_nightly().is_some() {
361 // An unstable feature. Warn about using it. It makes little sense
362 // to hard-error here since we just warn about fully unknown
363 // features above.
364 sess.dcx().emit_warn(errors::UnstableCTargetFeature {
365 feature: base_feature,
366 });
367 }
368 }
369 }
370 }
371 },
372 );
373
374 if diagnostics {
375 // FIXME(nagisa): figure out how to not allocate a full hashmap here.
376 if let Some(f) = check_tied_features(
377 sess,
378 &FxHashMap::from_iter(rust_features.iter().map(|&(enable, feature)| (feature, enable))),
379 ) {
380 sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
381 features: f,
382 span: None,
383 missing_features: None,
384 });
385 }
386 }
387
388 // Add this to the backend features.
389 for (enable, feature) in rust_features {
390 extend_backend_features(feature, enable);
391 }
392}
393
394/// Computes the backend target features to be added to account for retpoline flags.
395/// Used by both LLVM and GCC since their target features are, conveniently, the same.
396pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
397 // -Zretpoline without -Zretpoline-external-thunk enables
398 // retpoline-indirect-branches and retpoline-indirect-calls target features
399 let unstable_opts = &sess.opts.unstable_opts;
400 if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
401 features.push("+retpoline-indirect-branches".into());
402 features.push("+retpoline-indirect-calls".into());
403 }
404 // -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
405 // retpoline-external-thunk, retpoline-indirect-branches and
406 // retpoline-indirect-calls target features
407 if unstable_opts.retpoline_external_thunk {
408 features.push("+retpoline-external-thunk".into());
409 features.push("+retpoline-indirect-branches".into());
410 features.push("+retpoline-indirect-calls".into());
411 }
412}
413
414pub(crate) fn provide(providers: &mut Providers) {
415 *providers = Providers {
416 rust_target_features: |tcx, cnum| {
417 assert_eq!(cnum, LOCAL_CRATE);
418 if tcx.sess.opts.actually_rustdoc {
419 // HACK: rustdoc would like to pretend that we have all the target features, so we
420 // have to merge all the lists into one. To ensure an unstable target never prevents
421 // a stable one from working, we merge the stability info of all instances of the
422 // same target feature name, with the "most stable" taking precedence. And then we
423 // hope that this doesn't cause issues anywhere else in the compiler...
424 let mut result: UnordMap<String, Stability> = Default::default();
425 for (name, stability) in rustc_target::target_features::all_rust_features() {
426 use std::collections::hash_map::Entry;
427 match result.entry(name.to_owned()) {
428 Entry::Vacant(vacant_entry) => {
429 vacant_entry.insert(stability);
430 }
431 Entry::Occupied(mut occupied_entry) => {
432 // Merge the two stabilities, "more stable" taking precedence.
433 match (occupied_entry.get(), stability) {
434 (Stability::Stable, _)
435 | (
436 Stability::Unstable { .. },
437 Stability::Unstable { .. } | Stability::Forbidden { .. },
438 )
439 | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => {
440 // The stability in the entry is at least as good as the new
441 // one, just keep it.
442 }
443 _ => {
444 // Overwrite stability.
445 occupied_entry.insert(stability);
446 }
447 }
448 }
449 }
450 }
451 result
452 } else {
453 tcx.sess
454 .target
455 .rust_target_features()
456 .iter()
457 .map(|(a, b, _)| (a.to_string(), *b))
458 .collect()
459 }
460 },
461 implied_target_features: |tcx, feature: Symbol| {
462 let feature = feature.as_str();
463 UnordSet::from(tcx.sess.target.implied_target_features(feature))
464 .into_sorted_stable_ord()
465 .into_iter()
466 .map(|s| Symbol::intern(s))
467 .collect()
468 },
469 asm_target_features,
470 ..*providers
471 }
472}