Skip to content

Commit feb32c6

Browse files
committed
Auto merge of rust-lang#134794 - RalfJung:abi-required-target-features, r=workingjubilee
Add a notion of "some ABIs require certain target features" I think I finally found the right shape for the data and checks that I recently added in rust-lang#133099, rust-lang#133417, rust-lang#134337: we have a notion of "this ABI requires the following list of target features, and it is incompatible with the following list of target features". Both `-Ctarget-feature` and `#[target_feature]` are updated to ensure we follow the rules of the ABI. This removes all the "toggleability" stuff introduced before, though we do keep the notion of a fully "forbidden" target feature -- this is needed to deal with target features that are actual ABI switches, and hence are needed to even compute the list of required target features. We always explicitly (un)set all required and in-conflict features, just to avoid potential trouble caused by the default features of whatever the base CPU is. We do this *before* applying `-Ctarget-feature` to maintain backward compatibility; this poses a slight risk of missing some implicit feature dependencies in LLVM but has the advantage of not breaking users that deliberately toggle ABI-relevant target features. They get a warning but the feature does get toggled the way they requested. For now, our logic supports x86, ARM, and RISC-V (just like the previous logic did). Unsurprisingly, RISC-V is the nicest. ;) As a side-effect this also (unstably) allows *enabling* `x87` when that is harmless. I used the opportunity to mark SSE2 as required on x86-64, to better match the actual logic in LLVM and because all x86-64 chips do have SSE2. This infrastructure also prepares us for requiring SSE on x86-32 when we want to use that for our ABI (and for float semantics sanity), see rust-lang#133611, but no such change is happening in this PR. r? `@workingjubilee`
2 parents b3b368a + 2e64b53 commit feb32c6

25 files changed

+831
-787
lines changed

compiler/rustc_codegen_gcc/src/errors.rs

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
2828
#[diag(codegen_gcc_forbidden_ctarget_feature)]
2929
pub(crate) struct ForbiddenCTargetFeature<'a> {
3030
pub feature: &'a str,
31+
pub enabled: &'a str,
3132
pub reason: &'a str,
3233
}
3334

compiler/rustc_codegen_gcc/src/gcc_util.rs

+124-81
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use std::iter::FromIterator;
2+
13
#[cfg(feature = "master")]
24
use gccjit::Context;
35
use rustc_codegen_ssa::codegen_attrs::check_tied_features;
46
use rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable;
5-
use rustc_data_structures::fx::FxHashMap;
6-
use rustc_middle::bug;
7+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8+
use rustc_data_structures::unord::UnordSet;
79
use rustc_session::Session;
810
use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
911
use smallvec::{SmallVec, smallvec};
@@ -37,82 +39,137 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
3739
let mut features = vec![];
3840

3941
// Features implied by an implicit or explicit `--target`.
40-
features.extend(
41-
sess.target
42-
.features
43-
.split(',')
44-
.filter(|v| !v.is_empty() && backend_feature_name(v).is_some())
45-
.map(String::from),
46-
);
42+
features.extend(sess.target.features.split(',').filter(|v| !v.is_empty()).map(String::from));
4743

4844
// -Ctarget-features
4945
let known_features = sess.target.rust_target_features();
5046
let mut featsmap = FxHashMap::default();
51-
let feats = sess
52-
.opts
53-
.cg
54-
.target_feature
55-
.split(',')
56-
.filter_map(|s| {
57-
let enable_disable = match s.chars().next() {
58-
None => return None,
59-
Some(c @ ('+' | '-')) => c,
60-
Some(_) => {
61-
if diagnostics {
62-
sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature: s });
63-
}
64-
return None;
65-
}
66-
};
6747

68-
// Get the backend feature name, if any.
69-
// This excludes rustc-specific features, that do not get passed down to GCC.
70-
let feature = backend_feature_name(s)?;
71-
// Warn against use of GCC specific feature names on the CLI.
48+
// Ensure that all ABI-required features are enabled, and the ABI-forbidden ones
49+
// are disabled.
50+
let abi_feature_constraints = sess.target.abi_required_features();
51+
let abi_incompatible_set =
52+
FxHashSet::from_iter(abi_feature_constraints.incompatible.iter().copied());
53+
54+
// Compute implied features
55+
let mut all_rust_features = vec![];
56+
for feature in sess.opts.cg.target_feature.split(',') {
57+
if let Some(feature) = feature.strip_prefix('+') {
58+
all_rust_features.extend(
59+
UnordSet::from(sess.target.implied_target_features(std::iter::once(feature)))
60+
.to_sorted_stable_ord()
61+
.iter()
62+
.map(|&&s| (true, s)),
63+
)
64+
} else if let Some(feature) = feature.strip_prefix('-') {
65+
// FIXME: Why do we not remove implied features on "-" here?
66+
// We do the equivalent above in `target_features_cfg`.
67+
// See <https://github.com/rust-lang/rust/issues/134792>.
68+
all_rust_features.push((false, feature));
69+
} else if !feature.is_empty() {
7270
if diagnostics {
73-
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
74-
match feature_state {
75-
None => {
76-
let rust_feature =
77-
known_features.iter().find_map(|&(rust_feature, _, _)| {
78-
let gcc_features = to_gcc_features(sess, rust_feature);
79-
if gcc_features.contains(&feature)
80-
&& !gcc_features.contains(&rust_feature)
81-
{
82-
Some(rust_feature)
83-
} else {
84-
None
85-
}
86-
});
87-
let unknown_feature = if let Some(rust_feature) = rust_feature {
88-
UnknownCTargetFeature {
89-
feature,
90-
rust_feature: PossibleFeature::Some { rust_feature },
91-
}
92-
} else {
93-
UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
94-
};
95-
sess.dcx().emit_warn(unknown_feature);
96-
}
97-
Some((_, stability, _)) => {
98-
if let Err(reason) =
99-
stability.toggle_allowed(&sess.target, enable_disable == '+')
71+
sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature });
72+
}
73+
}
74+
}
75+
// Remove features that are meant for rustc, not codegen.
76+
all_rust_features.retain(|(_, feature)| {
77+
// Retain if it is not a rustc feature
78+
!RUSTC_SPECIFIC_FEATURES.contains(feature)
79+
});
80+
81+
// Check feature validity.
82+
if diagnostics {
83+
for &(enable, feature) in &all_rust_features {
84+
let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
85+
match feature_state {
86+
None => {
87+
let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
88+
let gcc_features = to_gcc_features(sess, rust_feature);
89+
if gcc_features.contains(&feature) && !gcc_features.contains(&rust_feature)
10090
{
101-
sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
102-
} else if stability.requires_nightly().is_some() {
103-
// An unstable feature. Warn about using it. (It makes little sense
104-
// to hard-error here since we just warn about fully unknown
105-
// features above).
106-
sess.dcx().emit_warn(UnstableCTargetFeature { feature });
91+
Some(rust_feature)
92+
} else {
93+
None
10794
}
95+
});
96+
let unknown_feature = if let Some(rust_feature) = rust_feature {
97+
UnknownCTargetFeature {
98+
feature,
99+
rust_feature: PossibleFeature::Some { rust_feature },
100+
}
101+
} else {
102+
UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
103+
};
104+
sess.dcx().emit_warn(unknown_feature);
105+
}
106+
Some((_, stability, _)) => {
107+
if let Err(reason) = stability.toggle_allowed() {
108+
sess.dcx().emit_warn(ForbiddenCTargetFeature {
109+
feature,
110+
enabled: if enable { "enabled" } else { "disabled" },
111+
reason,
112+
});
113+
} else if stability.requires_nightly().is_some() {
114+
// An unstable feature. Warn about using it. (It makes little sense
115+
// to hard-error here since we just warn about fully unknown
116+
// features above).
117+
sess.dcx().emit_warn(UnstableCTargetFeature { feature });
108118
}
109119
}
120+
}
110121

111-
// FIXME(nagisa): figure out how to not allocate a full hashset here.
112-
featsmap.insert(feature, enable_disable == '+');
122+
// Ensure that the features we enable/disable are compatible with the ABI.
123+
if enable {
124+
if abi_incompatible_set.contains(feature) {
125+
sess.dcx().emit_warn(ForbiddenCTargetFeature {
126+
feature,
127+
enabled: "enabled",
128+
reason: "this feature is incompatible with the target ABI",
129+
});
130+
}
131+
} else {
132+
// FIXME: we have to request implied features here since
133+
// negative features do not handle implied features above.
134+
for &required in abi_feature_constraints.required.iter() {
135+
let implied = sess.target.implied_target_features(std::iter::once(required));
136+
if implied.contains(feature) {
137+
sess.dcx().emit_warn(ForbiddenCTargetFeature {
138+
feature,
139+
enabled: "disabled",
140+
reason: "this feature is required by the target ABI",
141+
});
142+
}
143+
}
113144
}
114145

115-
// ... otherwise though we run through `to_gcc_features` when
146+
// FIXME(nagisa): figure out how to not allocate a full hashset here.
147+
featsmap.insert(feature, enable);
148+
}
149+
}
150+
151+
// To be sure the ABI-relevant features are all in the right state, we explicitly
152+
// (un)set them here. This means if the target spec sets those features wrong,
153+
// we will silently correct them rather than silently producing wrong code.
154+
// (The target sanity check tries to catch this, but we can't know which features are
155+
// enabled in GCC by default so we can't be fully sure about that check.)
156+
// We add these at the beginning of the list so that `-Ctarget-features` can
157+
// still override it... that's unsound, but more compatible with past behavior.
158+
all_rust_features.splice(
159+
0..0,
160+
abi_feature_constraints
161+
.required
162+
.iter()
163+
.map(|&f| (true, f))
164+
.chain(abi_feature_constraints.incompatible.iter().map(|&f| (false, f))),
165+
);
166+
167+
// Translate this into GCC features.
168+
let feats = all_rust_features
169+
.iter()
170+
.filter_map(|&(enable, feature)| {
171+
let enable_disable = if enable { '+' } else { '-' };
172+
// We run through `to_gcc_features` when
116173
// passing requests down to GCC. This means that all in-language
117174
// features also work on the command line instead of having two
118175
// different names when the GCC name and the Rust name differ.
@@ -146,26 +203,12 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
146203
features
147204
}
148205

149-
/// Returns a feature name for the given `+feature` or `-feature` string.
150-
///
151-
/// Only allows features that are backend specific (i.e. not [`RUSTC_SPECIFIC_FEATURES`].)
152-
fn backend_feature_name(s: &str) -> Option<&str> {
153-
// features must start with a `+` or `-`.
154-
let feature = s.strip_prefix(&['+', '-'][..]).unwrap_or_else(|| {
155-
bug!("target feature `{}` must begin with a `+` or `-`", s);
156-
});
157-
// Rustc-specific feature requests like `+crt-static` or `-crt-static`
158-
// are not passed down to GCC.
159-
if RUSTC_SPECIFIC_FEATURES.contains(&feature) {
160-
return None;
161-
}
162-
Some(feature)
163-
}
164-
165206
// To find a list of GCC's names, check https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
166207
pub fn to_gcc_features<'a>(sess: &Session, s: &'a str) -> SmallVec<[&'a str; 2]> {
167208
let arch = if sess.target.arch == "x86_64" { "x86" } else { &*sess.target.arch };
168209
match (arch, s) {
210+
// FIXME: seems like x87 does not exist?
211+
("x86", "x87") => smallvec![],
169212
("x86", "sse4.2") => smallvec!["sse4.2", "crc32"],
170213
("x86", "pclmulqdq") => smallvec!["pclmul"],
171214
("x86", "rdrand") => smallvec!["rdrnd"],

compiler/rustc_codegen_llvm/messages.ftl

+1-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ codegen_llvm_dynamic_linking_with_lto =
1010
codegen_llvm_fixed_x18_invalid_arch = the `-Zfixed-x18` flag is not supported on the `{$arch}` architecture
1111
1212
codegen_llvm_forbidden_ctarget_feature =
13-
target feature `{$feature}` cannot be toggled with `-Ctarget-feature`: {$reason}
13+
target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}
1414
.note = this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
1515
codegen_llvm_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
1616
@@ -24,8 +24,6 @@ codegen_llvm_invalid_minimum_alignment_not_power_of_two =
2424
codegen_llvm_invalid_minimum_alignment_too_large =
2525
invalid minimum global alignment: {$align} is too large
2626
27-
codegen_llvm_invalid_target_feature_prefix = target feature `{$feature}` must begin with a `+` or `-`"
28-
2927
codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}"
3028
codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err}
3129

compiler/rustc_codegen_llvm/src/errors.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
3737
#[note(codegen_llvm_forbidden_ctarget_feature_issue)]
3838
pub(crate) struct ForbiddenCTargetFeature<'a> {
3939
pub feature: &'a str,
40+
pub enabled: &'a str,
4041
pub reason: &'a str,
4142
}
4243

@@ -213,12 +214,6 @@ pub(crate) struct MismatchedDataLayout<'a> {
213214
pub llvm_layout: &'a str,
214215
}
215216

216-
#[derive(Diagnostic)]
217-
#[diag(codegen_llvm_invalid_target_feature_prefix)]
218-
pub(crate) struct InvalidTargetFeaturePrefix<'a> {
219-
pub feature: &'a str,
220-
}
221-
222217
#[derive(Diagnostic)]
223218
#[diag(codegen_llvm_fixed_x18_invalid_arch)]
224219
pub(crate) struct FixedX18InvalidArch<'a> {

0 commit comments

Comments
 (0)