Skip to content

Commit 66b4bf3

Browse files
authored
x64: handle ISA features more completely (#11272)
* asm: generate boolean terms of CPU features As discussed [here], we will soon need the ability to express more complex combinations of CPU features. These are best expressed as boolean terms: e.g., `(32-bit OR 64-bit) AND ...`, `(32-bit OR 64-bit) AND ((AVX512VL AND AVX512F) OR AVX10.1)`. This change modifies the generated code to have a `Inst::is_available` method which contains a Rust-ified version of the instruction's boolean term. To do this, we now pass in a `Features` trait, which the instruction can query to see if its desired feature set is available. [here]: https://bytecodealliance.zulipchat.com/#narrow/channel/217117-cranelift/topic/boolean.20terms.20for.20x64.20features * x64: wire up `Inst::is_available` in Cranelift This change makes us of the assembler's new generated `Inst::is_available` methods to check an instruction's feature set in a more succinct (and likely quicker) way. Unfortunately, this does not allow us to print the missing ISA requirements on failure--something to address later. * Rename `Features` to `AvailableFeatures` * Remove unused `InstructionSet` * asm: fix all feature definitions This is a mechanical transformation converting all instruction definitions. Now, instructions should have a correct boolean term describe the features required: e.g., `(_64b | compat) & avx`. * x64: replace `use_*` with `has_*` when checking ISA features In Cranelift, the `has_*` flags of `isa::x64::settings::Flags` indicate that the CPU _has_ some capability; the `use_*` flags indicate that Cranelift _should emit_ instructions using those capabilities. Further, the `use_*` flags may turned on by the presence of more than one `has_*` flags; e.g., when `has_avx` and `has_avx2` are available, `use_avx2` is enabled. Now that Cranelift's new x64 assembler understands boolean terms, we no longer need the `use_*` flags for checking if an instruction can be emitted: instead, we should use the `has_*` flags and rely on the logic encoded in `Inst::is_available`. * asm: materialize `Features` via `Inst::features` For better error messages (and just for general use of CPU features, see discussion [here]), this change adds `Inst::features`--a way to explicitly examine the boolean term for an instruction. This function returns a `&'static Features` that contains the `AND` and `OR` branches defining when an instruction is available. This is all generated into something that looks like: ```rust pub fn features(&self) -> &'static Features { const F1: &'static Features = &Features::Feature(Feature::_64b); const F2: &'static Features = &Features::Feature(Feature::compat); const F0: &'static Features = &Features::Or(F1, F2); F0 } ``` This change makes use of `for_each_feature` more: we build up the `AvailableFeatures` trait and the `Feature` enum from it. This should be a bit more direct than searching through the generated code (?). [here]: #11272 (comment)
1 parent 00dd668 commit 66b4bf3

File tree

38 files changed

+966
-710
lines changed

38 files changed

+966
-710
lines changed

cranelift/assembler-x64/meta/src/dsl.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,7 @@ impl core::fmt::Display for Inst {
127127
custom,
128128
} = self;
129129
write!(f, "{name}: {format} => {encoding}")?;
130-
if !features.is_empty() {
131-
write!(f, " [{features}]")?;
132-
}
130+
write!(f, " [{features}]")?;
133131
if let Some(alternate) = alternate {
134132
write!(f, " (alternate: {alternate})")?;
135133
}

cranelift/assembler-x64/meta/src/dsl/features.rs

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,65 @@
11
//! A DSL for describing x64 CPU features.
22
33
use core::fmt;
4-
use std::ops::BitOr;
4+
use std::ops::{BitAnd, BitOr};
55

6-
/// A collection of CPU features.
6+
/// A boolean term of CPU features.
77
///
8-
/// An instruction is valid when _any_ of the features in the collection are
9-
/// enabled; i.e., the collection is an `OR` expression.
8+
/// An instruction is valid when the boolean term (a recursive tree of `AND` and
9+
/// `OR` terms) is satisfied.
1010
///
1111
/// ```
1212
/// # use cranelift_assembler_x64_meta::dsl::{Features, Feature};
1313
/// let fs = Feature::_64b | Feature::compat;
14-
/// assert_eq!(fs.to_string(), "_64b | compat");
15-
/// ```
16-
///
17-
/// Duplicate features are not allowed and will cause a panic.
18-
///
19-
/// ```should_panic
20-
/// # use cranelift_assembler_x64_meta::dsl::Feature;
21-
/// let fs = Feature::_64b | Feature::_64b;
14+
/// assert_eq!(fs.to_string(), "(_64b | compat)");
2215
/// ```
2316
#[derive(PartialEq)]
24-
pub struct Features(Vec<Feature>);
17+
pub enum Features {
18+
And(Box<Features>, Box<Features>),
19+
Or(Box<Features>, Box<Features>),
20+
Feature(Feature),
21+
}
2522

2623
impl Features {
27-
#[must_use]
28-
pub fn is_empty(&self) -> bool {
29-
self.0.is_empty()
30-
}
31-
32-
pub fn iter(&self) -> impl Iterator<Item = &Feature> {
33-
self.0.iter()
24+
pub(crate) fn is_sse(&self) -> bool {
25+
use Feature::*;
26+
match self {
27+
Features::And(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
28+
Features::Or(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
29+
Features::Feature(feature) => {
30+
matches!(feature, sse | sse2 | sse3 | ssse3 | sse41 | sse42)
31+
}
32+
}
3433
}
34+
}
3535

36-
pub fn contains(&self, feature: Feature) -> bool {
37-
self.0.contains(&feature)
36+
impl fmt::Display for Features {
37+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38+
match self {
39+
Features::And(lhs, rhs) => write!(f, "({lhs} & {rhs})"),
40+
Features::Or(lhs, rhs) => write!(f, "({lhs} | {rhs})"),
41+
Features::Feature(feature) => write!(f, "{feature:#?}"),
42+
}
3843
}
44+
}
3945

40-
pub(crate) fn is_sse(&self) -> bool {
41-
use Feature::*;
42-
self.0
43-
.iter()
44-
.any(|f| matches!(f, sse | sse2 | sse3 | ssse3 | sse41 | sse42))
46+
impl<T> BitOr<T> for Features
47+
where
48+
T: Into<Features>,
49+
{
50+
type Output = Features;
51+
fn bitor(self, rhs: T) -> Self::Output {
52+
Features::Or(Box::new(self), Box::new(rhs.into()))
4553
}
4654
}
4755

48-
impl fmt::Display for Features {
49-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50-
write!(
51-
f,
52-
"{}",
53-
self.0
54-
.iter()
55-
.map(ToString::to_string)
56-
.collect::<Vec<_>>()
57-
.join(" | ")
58-
)
56+
impl<T> BitAnd<T> for Features
57+
where
58+
T: Into<Features>,
59+
{
60+
type Output = Features;
61+
fn bitand(self, rhs: T) -> Self::Output {
62+
Features::And(Box::new(self), Box::new(rhs.into()))
5963
}
6064
}
6165

@@ -132,30 +136,27 @@ impl fmt::Display for Feature {
132136
}
133137

134138
impl From<Feature> for Features {
135-
fn from(flag: Feature) -> Self {
136-
Features(vec![flag])
137-
}
138-
}
139-
140-
impl From<Option<Feature>> for Features {
141-
fn from(flag: Option<Feature>) -> Self {
142-
Features(flag.into_iter().collect())
139+
fn from(f: Feature) -> Self {
140+
Features::Feature(f)
143141
}
144142
}
145143

146-
impl BitOr for Feature {
144+
impl<T> BitAnd<T> for Feature
145+
where
146+
T: Into<Features>,
147+
{
147148
type Output = Features;
148-
fn bitor(self, rhs: Self) -> Self::Output {
149-
assert_ne!(self, rhs, "duplicate feature: {self:?}");
150-
Features(vec![self, rhs])
149+
fn bitand(self, rhs: T) -> Self::Output {
150+
Features::from(self) & rhs.into()
151151
}
152152
}
153153

154-
impl BitOr<Feature> for Features {
154+
impl<T> BitOr<T> for Feature
155+
where
156+
T: Into<Features>,
157+
{
155158
type Output = Features;
156-
fn bitor(mut self, rhs: Feature) -> Self::Output {
157-
assert!(!self.0.contains(&rhs), "duplicate feature: {rhs:?}");
158-
self.0.push(rhs);
159-
self
159+
fn bitor(self, rhs: T) -> Self::Output {
160+
Features::from(self) | rhs.into()
160161
}
161162
}

cranelift/assembler-x64/meta/src/generate.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) {
1515
generate_inst_display_impl(f, insts);
1616
generate_inst_encode_impl(f, insts);
1717
generate_inst_visit_impl(f, insts);
18+
generate_inst_is_available_impl(f, insts);
1819
generate_inst_features_impl(f, insts);
1920

2021
// Generate per-instruction structs.
@@ -27,8 +28,8 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) {
2728
f.empty_line();
2829
}
2930

30-
// Generate the `Feature` enum.
31-
dsl::Feature::generate_enum(f);
31+
// Generate the `Feature` trait.
32+
dsl::Feature::generate_macro(f);
3233
}
3334

3435
/// `enum Inst { ... }`
@@ -117,10 +118,27 @@ fn generate_inst_visit_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
117118
fmtln!(f, "}}");
118119
}
119120

121+
/// `impl Inst { fn is_available... }`
122+
fn generate_inst_is_available_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
123+
f.add_block("impl<R: Registers> Inst<R>", |f| {
124+
f.add_block(
125+
"pub fn is_available(&self, f: &impl AvailableFeatures) -> bool",
126+
|f| {
127+
f.add_block("match self", |f| {
128+
for inst in insts {
129+
let variant_name = inst.name();
130+
fmtln!(f, "Self::{variant_name}(i) => i.is_available(f),");
131+
}
132+
});
133+
},
134+
);
135+
});
136+
}
137+
120138
/// `impl Inst { fn features... }`
121139
fn generate_inst_features_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
122140
f.add_block("impl<R: Registers> Inst<R>", |f| {
123-
f.add_block("pub fn features(&self) -> Vec<Feature>", |f| {
141+
f.add_block("pub fn features(&self) -> &'static Features", |f| {
124142
f.add_block("match self", |f| {
125143
for inst in insts {
126144
let variant_name = inst.name();
Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,101 @@
11
//! Generate feature-related Rust code.
22
33
use super::{Formatter, fmtln};
4-
use crate::{dsl, generate::generate_derive};
4+
use crate::dsl;
55

66
impl dsl::Feature {
7-
/// `pub enum Feature { ... }`
7+
/// `macro_rules! for_each_feature { ... }`
88
///
9-
/// This function recreates the `Feature` struct itself in the generated
10-
/// code.
11-
pub fn generate_enum(f: &mut Formatter) {
9+
/// This function generates a macro to allow generating code for each CPU
10+
/// feature.
11+
pub(crate) fn generate_macro(f: &mut Formatter) {
1212
fmtln!(f, "#[doc(hidden)]");
13-
generate_derive(f);
14-
fmtln!(f, "#[derive(PartialEq)]"); // Add more helpful derives.
15-
f.add_block("pub enum Feature", |f| {
16-
for feature in dsl::ALL_FEATURES {
17-
fmtln!(f, "{feature},");
18-
}
13+
fmtln!(f, "#[macro_export]");
14+
f.add_block("macro_rules! for_each_feature", |f| {
15+
f.add_block("($m:ident) =>", |f| {
16+
f.add_block("$m!", |f| {
17+
for feature in dsl::ALL_FEATURES {
18+
fmtln!(f, "{feature}");
19+
}
20+
});
21+
});
1922
});
2023
}
2124
}
25+
26+
impl dsl::Features {
27+
/// E.g., `features.is_sse2() && features.is_64b()`
28+
///
29+
/// Generate a boolean expression that checks if the features are available.
30+
pub(crate) fn generate_boolean_expr(&self, name: &str) -> String {
31+
use dsl::Features::*;
32+
match self {
33+
And(lhs, rhs) => {
34+
let lhs = lhs.generate_inner_boolean_expr(name);
35+
let rhs = rhs.generate_inner_boolean_expr(name);
36+
format!("{lhs} && {rhs}")
37+
}
38+
Or(lhs, rhs) => {
39+
let lhs = lhs.generate_inner_boolean_expr(name);
40+
let rhs = rhs.generate_inner_boolean_expr(name);
41+
format!("{lhs} || {rhs}")
42+
}
43+
Feature(feature) => {
44+
format!("{name}.{feature}()")
45+
}
46+
}
47+
}
48+
49+
// This adds parentheses for inner terms.
50+
fn generate_inner_boolean_expr(&self, name: &str) -> String {
51+
use dsl::Features::*;
52+
match self {
53+
And(lhs, rhs) => {
54+
let lhs = lhs.generate_inner_boolean_expr(name);
55+
let rhs = rhs.generate_inner_boolean_expr(name);
56+
format!("({lhs} && {rhs})")
57+
}
58+
Or(lhs, rhs) => {
59+
let lhs = lhs.generate_inner_boolean_expr(name);
60+
let rhs = rhs.generate_inner_boolean_expr(name);
61+
format!("({lhs} || {rhs})")
62+
}
63+
Feature(feature) => format!("{name}.{feature}()"),
64+
}
65+
}
66+
67+
/// E.g., `Features::Or(Features::Feature(compat), Features::Feature(64b))`
68+
///
69+
/// Generate a Rust constructor expression that contains the feature
70+
/// boolean term.
71+
pub(crate) fn generate_constructor_expr(&self, f: &mut Formatter) {
72+
let mut index = 0;
73+
let name = self.generate_inner_constructor_expr(f, &mut index);
74+
fmtln!(f, "{name}");
75+
}
76+
77+
fn generate_inner_constructor_expr(&self, f: &mut Formatter, index: &mut u32) -> String {
78+
use dsl::Features::*;
79+
80+
let name = format!("F{index}");
81+
*index += 1;
82+
83+
let const_expr = format!("const {name}: &'static Features");
84+
match self {
85+
And(lhs, rhs) => {
86+
let lhs = lhs.generate_inner_constructor_expr(f, index);
87+
let rhs = rhs.generate_inner_constructor_expr(f, index);
88+
fmtln!(f, "{const_expr} = &Features::And({lhs}, {rhs});");
89+
}
90+
Or(lhs, rhs) => {
91+
let lhs = lhs.generate_inner_constructor_expr(f, index);
92+
let rhs = rhs.generate_inner_constructor_expr(f, index);
93+
fmtln!(f, "{const_expr} = &Features::Or({lhs}, {rhs});");
94+
}
95+
Feature(feature) => {
96+
fmtln!(f, "{const_expr} = &Features::Feature(Feature::{feature});");
97+
}
98+
}
99+
name
100+
}
101+
}

cranelift/assembler-x64/meta/src/generate/inst.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ impl dsl::Inst {
6565
f.empty_line();
6666
self.generate_visit_function(f);
6767
f.empty_line();
68+
self.generate_is_available_function(f);
69+
f.empty_line();
6870
self.generate_features_function(f);
6971
});
7072
}
@@ -222,16 +224,23 @@ impl dsl::Inst {
222224
});
223225
}
224226

225-
/// `fn features(&self) -> Vec<Flag> { ... }`
227+
/// `fn is_available(&self, ...) -> bool { ... }`
228+
fn generate_is_available_function(&self, f: &mut Formatter) {
229+
fmtln!(f, "#[must_use]");
230+
f.add_block(
231+
"pub fn is_available(&self, features: &impl AvailableFeatures) -> bool",
232+
|f| {
233+
let expr = self.features.generate_boolean_expr("features");
234+
fmtln!(f, "{expr}");
235+
},
236+
);
237+
}
238+
239+
/// `fn features(&self) -> Features { ... }`
226240
fn generate_features_function(&self, f: &mut Formatter) {
227241
fmtln!(f, "#[must_use]");
228-
f.add_block("pub fn features(&self) -> Vec<Feature>", |f| {
229-
let flags = self
230-
.features
231-
.iter()
232-
.map(|f| format!("Feature::{f}"))
233-
.collect::<Vec<_>>();
234-
fmtln!(f, "vec![{}]", flags.join(", "));
242+
f.add_block("pub fn features(&self) -> &'static Features", |f| {
243+
self.features.generate_constructor_expr(f);
235244
});
236245
}
237246

cranelift/assembler-x64/meta/src/instructions/abs.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ use crate::dsl::{align, evex, fmt, inst, r, rex, vex, w};
44
#[rustfmt::skip] // Keeps instructions on a single line.
55
pub fn list() -> Vec<Inst> {
66
vec![
7-
inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), _64b | compat | ssse3).alt(avx, "vpabsb_a"),
8-
inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), _64b | compat | avx),
7+
inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), (_64b | compat) & ssse3).alt(avx, "vpabsb_a"),
8+
inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), (_64b | compat) & avx),
99
// FIXME: uncomment once the avx512bw feature is bound
10-
// inst("vpabsb", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1C).r(), _64b | compat | avx512vl | avx512bw),
11-
inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), _64b | compat | ssse3).alt(avx, "vpabsw_a"),
12-
inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), _64b | compat | avx),
10+
// inst("vpabsb", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1C).r(), (_64b | compat) & avx512vl & avx512bw),
11+
inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), (_64b | compat) & ssse3).alt(avx, "vpabsw_a"),
12+
inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), (_64b | compat) & avx),
1313
// FIXME: uncomment once the avx512bw feature is bound
14-
// inst("vpabsw", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1D).r(), _64b | compat | avx512vl | avx512bw),
15-
inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), _64b | compat | ssse3).alt(avx, "vpabsd_a"),
16-
inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), _64b | compat | avx),
17-
inst("vpabsd", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x1E).r(), _64b | compat | avx512vl | avx512f),
18-
inst("vpabsq", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x1F).r(), _64b | compat | avx512vl | avx512f),
14+
// inst("vpabsw", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1D).r(), (_64b | compat) & avx512vl & avx512bw),
15+
inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), (_64b | compat) & ssse3).alt(avx, "vpabsd_a"),
16+
inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), (_64b | compat) & avx),
17+
inst("vpabsd", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x1E).r(), (_64b | compat) & avx512vl & avx512f),
18+
inst("vpabsq", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x1F).r(), (_64b | compat) & avx512vl & avx512f),
1919
]
2020
}

0 commit comments

Comments
 (0)