Skip to content

Commit a452f07

Browse files
committed
unify two -Ctarget-feature parsers
This does change the logic a bit: previously, we didn't forward reverse implications of negated features to the backend, instead relying on the backend to handle the implication itself.
1 parent 368b5d2 commit a452f07

File tree

6 files changed

+170
-121
lines changed

6 files changed

+170
-121
lines changed

compiler/rustc_codegen_ssa/src/target_features.rs

Lines changed: 134 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,58 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId,
159159
}
160160
}
161161

162+
/// Parse the value of `-Ctarget-feature`, also expanding implied features,
163+
/// and call the closure for each (expanded) Rust feature. If the list contains
164+
/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked.
165+
fn parse_rust_feature_flag<'a>(
166+
sess: &Session,
167+
target_feature_flag: &'a str,
168+
err_callback: impl Fn(&'a str),
169+
mut callback: impl FnMut(
170+
/* base_feature */ &'a str,
171+
/* with_implied */ FxHashSet<&'a str>,
172+
/* enable */ bool,
173+
),
174+
) {
175+
// A cache for the backwards implication map.
176+
let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
177+
178+
for feature in target_feature_flag.split(',') {
179+
if let Some(base_feature) = feature.strip_prefix('+') {
180+
callback(base_feature, sess.target.implied_target_features(base_feature), true)
181+
} else if let Some(base_feature) = feature.strip_prefix('-') {
182+
// If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical contraposition.
183+
// So we have to find all the reverse implications of `base_feature` and disable them, too.
184+
185+
let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
186+
let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
187+
for (f, _, is) in sess.target.rust_target_features() {
188+
for i in is.iter() {
189+
set.entry(i).or_default().insert(f);
190+
}
191+
}
192+
set
193+
});
194+
195+
// Inverse mplied target features have their own inverse implied target features, so we
196+
// traverse the map until there are no more features to add.
197+
let mut features = FxHashSet::default();
198+
let mut new_features = vec![base_feature];
199+
while let Some(new_feature) = new_features.pop() {
200+
if features.insert(new_feature) {
201+
if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
202+
new_features.extend(implied_features)
203+
}
204+
}
205+
}
206+
207+
callback(base_feature, features, false)
208+
} else if !feature.is_empty() {
209+
err_callback(feature)
210+
}
211+
}
212+
}
213+
162214
/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically,
163215
/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
164216
/// 2nd component of the return value, respectively).
@@ -175,7 +227,7 @@ pub fn cfg_target_feature(
175227
) -> (Vec<Symbol>, Vec<Symbol>) {
176228
// Compute which of the known target features are enabled in the 'base' target machine. We only
177229
// consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
178-
let mut features: FxHashSet<Symbol> = sess
230+
let mut features: UnordSet<Symbol> = sess
179231
.target
180232
.rust_target_features()
181233
.iter()
@@ -190,43 +242,23 @@ pub fn cfg_target_feature(
190242
.collect();
191243

192244
// Add enabled and remove disabled features.
193-
for (enabled, feature) in
194-
target_feature_flag.split(',').filter_map(|s| match s.chars().next() {
195-
Some('+') => Some((true, Symbol::intern(&s[1..]))),
196-
Some('-') => Some((false, Symbol::intern(&s[1..]))),
197-
_ => None,
198-
})
199-
{
200-
if enabled {
201-
// Also add all transitively implied features.
202-
203-
// We don't care about the order in `features` since the only thing we use it for is the
204-
// `features.contains` below.
245+
parse_rust_feature_flag(
246+
sess,
247+
target_feature_flag,
248+
/* err_callback */ |_| {},
249+
|_base_feature, new_features, enabled| {
250+
// Iteration order is irrelevant since this only influences an `UnordSet`.
205251
#[allow(rustc::potential_query_instability)]
206-
features.extend(
207-
sess.target
208-
.implied_target_features(feature.as_str())
209-
.iter()
210-
.map(|s| Symbol::intern(s)),
211-
);
212-
} else {
213-
// Remove transitively reverse-implied features.
214-
215-
// We don't care about the order in `features` since the only thing we use it for is the
216-
// `features.contains` below.
217-
#[allow(rustc::potential_query_instability)]
218-
features.retain(|f| {
219-
if sess.target.implied_target_features(f.as_str()).contains(&feature.as_str()) {
220-
// If `f` if implies `feature`, then `!feature` implies `!f`, so we have to
221-
// remove `f`. (This is the standard logical contraposition principle.)
222-
false
223-
} else {
224-
// We can keep `f`.
225-
true
252+
if enabled {
253+
features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
254+
} else {
255+
// Remove `new_features` from `features`.
256+
for new in new_features {
257+
features.remove(&Symbol::intern(new));
226258
}
227-
});
228-
}
229-
}
259+
}
260+
},
261+
);
230262

231263
// Filter enabled features based on feature gates.
232264
let f = |allow_unstable| {
@@ -289,85 +321,82 @@ pub fn flag_to_backend_features<'a, const N: usize>(
289321

290322
// Compute implied features
291323
let mut rust_features = vec![];
292-
for feature in sess.opts.cg.target_feature.split(',') {
293-
if let Some(feature) = feature.strip_prefix('+') {
294-
rust_features.extend(
295-
UnordSet::from(sess.target.implied_target_features(feature))
296-
.to_sorted_stable_ord()
297-
.iter()
298-
.map(|&&s| (true, s)),
299-
)
300-
} else if let Some(feature) = feature.strip_prefix('-') {
301-
// FIXME: Why do we not remove implied features on "-" here?
302-
// We do the equivalent above in `target_config`.
303-
// See <https://github.com/rust-lang/rust/issues/134792>.
304-
rust_features.push((false, feature));
305-
} else if !feature.is_empty() {
324+
parse_rust_feature_flag(
325+
sess,
326+
&sess.opts.cg.target_feature,
327+
/* err_callback */
328+
|feature| {
306329
if diagnostics {
307330
sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
308331
}
309-
}
310-
}
311-
// Remove features that are meant for rustc, not the backend.
312-
rust_features.retain(|(_, feature)| {
313-
// Retain if it is not a rustc feature
314-
!RUSTC_SPECIFIC_FEATURES.contains(feature)
315-
});
316-
317-
// Check feature validity.
318-
if diagnostics {
319-
let mut featsmap = FxHashMap::default();
332+
},
333+
|base_feature, new_features, enable| {
334+
// Skip features that are meant for rustc, not the backend.
335+
if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
336+
return;
337+
}
320338

321-
for &(enable, feature) in &rust_features {
322-
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
323-
match feature_state {
324-
None => {
325-
// This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
326-
// If so, give a better error message.
327-
let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
328-
let backend_features = to_backend_features(rust_feature);
329-
if backend_features.contains(&feature)
330-
&& !backend_features.contains(&rust_feature)
331-
{
332-
Some(rust_feature)
339+
rust_features.extend(
340+
UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
341+
);
342+
// Check feature validity.
343+
if diagnostics {
344+
let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
345+
match feature_state {
346+
None => {
347+
// This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
348+
// If so, give a better error message.
349+
let rust_feature =
350+
known_features.iter().find_map(|&(rust_feature, _, _)| {
351+
let backend_features = to_backend_features(rust_feature);
352+
if backend_features.contains(&base_feature)
353+
&& !backend_features.contains(&rust_feature)
354+
{
355+
Some(rust_feature)
356+
} else {
357+
None
358+
}
359+
});
360+
let unknown_feature = if let Some(rust_feature) = rust_feature {
361+
errors::UnknownCTargetFeature {
362+
feature: base_feature,
363+
rust_feature: errors::PossibleFeature::Some { rust_feature },
364+
}
333365
} else {
334-
None
335-
}
336-
});
337-
let unknown_feature = if let Some(rust_feature) = rust_feature {
338-
errors::UnknownCTargetFeature {
339-
feature,
340-
rust_feature: errors::PossibleFeature::Some { rust_feature },
341-
}
342-
} else {
343-
errors::UnknownCTargetFeature {
344-
feature,
345-
rust_feature: errors::PossibleFeature::None,
366+
errors::UnknownCTargetFeature {
367+
feature: base_feature,
368+
rust_feature: errors::PossibleFeature::None,
369+
}
370+
};
371+
sess.dcx().emit_warn(unknown_feature);
372+
}
373+
Some((_, stability, _)) => {
374+
if let Err(reason) = stability.toggle_allowed() {
375+
sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
376+
feature: base_feature,
377+
enabled: if enable { "enabled" } else { "disabled" },
378+
reason,
379+
});
380+
} else if stability.requires_nightly().is_some() {
381+
// An unstable feature. Warn about using it. It makes little sense
382+
// to hard-error here since we just warn about fully unknown
383+
// features above.
384+
sess.dcx().emit_warn(errors::UnstableCTargetFeature {
385+
feature: base_feature,
386+
});
346387
}
347-
};
348-
sess.dcx().emit_warn(unknown_feature);
349-
}
350-
Some((_, stability, _)) => {
351-
if let Err(reason) = stability.toggle_allowed() {
352-
sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
353-
feature,
354-
enabled: if enable { "enabled" } else { "disabled" },
355-
reason,
356-
});
357-
} else if stability.requires_nightly().is_some() {
358-
// An unstable feature. Warn about using it. It makes little sense
359-
// to hard-error here since we just warn about fully unknown
360-
// features above.
361-
sess.dcx().emit_warn(errors::UnstableCTargetFeature { feature });
362388
}
363389
}
364390
}
391+
},
392+
);
365393

366-
// FIXME(nagisa): figure out how to not allocate a full hashset here.
367-
featsmap.insert(feature, enable);
368-
}
369-
370-
if let Some(f) = check_tied_features(sess, &featsmap) {
394+
if diagnostics {
395+
// FIXME(nagisa): figure out how to not allocate a full hashmap here.
396+
if let Some(f) = check_tied_features(
397+
sess,
398+
&FxHashMap::from_iter(rust_features.iter().map(|&(enable, feature)| (feature, enable))),
399+
) {
371400
sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
372401
features: f,
373402
span: None,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ add-core-stubs
2+
//@ needs-llvm-components: x86
3+
//@ compile-flags: --target=x86_64-unknown-linux-gnu
4+
//@ compile-flags: -Ctarget-feature=-avx2
5+
6+
#![feature(no_core, lang_items)]
7+
#![crate_type = "lib"]
8+
#![no_core]
9+
10+
extern crate minicore;
11+
use minicore::*;
12+
13+
#[no_mangle]
14+
pub unsafe fn banana() {
15+
// CHECK-LABEL: @banana()
16+
// CHECK-SAME: [[BANANAATTRS:#[0-9]+]] {
17+
}
18+
19+
// CHECK: attributes [[BANANAATTRS]]
20+
// CHECK-SAME: -avx512

tests/codegen/target-feature-overrides.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-linelength
12
//@ add-core-stubs
23
//@ revisions: COMPAT INCOMPAT
34
//@ needs-llvm-components: x86
@@ -39,7 +40,7 @@ pub unsafe fn banana() -> u32 {
3940

4041
// CHECK: attributes [[APPLEATTRS]]
4142
// COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}"
42-
// INCOMPAT-SAME: "target-features"="-avx2,-avx,+avx,{{.*}}"
43+
// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}},+avx{{(,\+[^,]+)*}}"
4344
// CHECK: attributes [[BANANAATTRS]]
4445
// COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}"
45-
// INCOMPAT-SAME: "target-features"="-avx2,-avx"
46+
// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}}"

tests/codegen/tied-features-strength.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@
44
//@ compile-flags: --crate-type=rlib --target=aarch64-unknown-linux-gnu
55
//@ needs-llvm-components: aarch64
66

7+
// Rust made SVE require neon.
78
//@ [ENABLE_SVE] compile-flags: -C target-feature=+sve -Copt-level=0
8-
// ENABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+sve,?)|(\+neon,?)|(\+fp-armv8,?))*}}" }
9+
// ENABLE_SVE: attributes #0
10+
// ENABLE_SVE-SAME: +neon
11+
// ENABLE_SVE-SAME: +sve
912

13+
// However, disabling SVE does not disable neon.
1014
//@ [DISABLE_SVE] compile-flags: -C target-feature=-sve -Copt-level=0
11-
// DISABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-sve,?)|(\+neon,?))*}}" }
15+
// DISABLE_SVE: attributes #0
16+
// DISABLE_SVE-NOT: -neon
17+
// DISABLE_SVE-SAME: -sve
1218

19+
// OTOH, neon fn `fp-armv8` are fully tied; toggling neon must toggle `fp-armv8` the same way.
1320
//@ [DISABLE_NEON] compile-flags: -C target-feature=-neon -Copt-level=0
14-
// DISABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-fp-armv8,?)|(-neon,?))*}}" }
21+
// DISABLE_NEON: attributes #0
22+
// DISABLE_NEON-SAME: -neon
23+
// DISABLE_NEON-SAME: -fp-armv8
1524

1625
//@ [ENABLE_NEON] compile-flags: -C target-feature=+neon -Copt-level=0
1726
// ENABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fp-armv8,?)|(\+neon,?))*}}" }

tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,5 @@ warning: unstable feature specified for `-Ctarget-feature`: `d`
77
|
88
= note: this feature is not stably supported; its behavior can change in the future
99

10-
warning: unstable feature specified for `-Ctarget-feature`: `f`
11-
|
12-
= note: this feature is not stably supported; its behavior can change in the future
13-
14-
warning: unstable feature specified for `-Ctarget-feature`: `zicsr`
15-
|
16-
= note: this feature is not stably supported; its behavior can change in the future
17-
18-
warning: 4 warnings emitted
10+
warning: 2 warnings emitted
1911

tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,3 @@ pub trait Freeze {}
1818

1919
//~? WARN must be disabled to ensure that the ABI of the current target can be implemented correctly
2020
//~? WARN unstable feature specified for `-Ctarget-feature`
21-
//[riscv]~? WARN unstable feature specified for `-Ctarget-feature`
22-
//[riscv]~? WARN unstable feature specified for `-Ctarget-feature`

0 commit comments

Comments
 (0)