Skip to content

x64: handle ISA features more completely #11272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions cranelift/assembler-x64/meta/src/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ impl core::fmt::Display for Inst {
custom,
} = self;
write!(f, "{name}: {format} => {encoding}")?;
if !features.is_empty() {
write!(f, " [{features}]")?;
}
write!(f, " [{features}]")?;
if let Some(alternate) = alternate {
write!(f, " (alternate: {alternate})")?;
}
Expand Down
111 changes: 56 additions & 55 deletions cranelift/assembler-x64/meta/src/dsl/features.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,65 @@
//! A DSL for describing x64 CPU features.

use core::fmt;
use std::ops::BitOr;
use std::ops::{BitAnd, BitOr};

/// A collection of CPU features.
/// A boolean term of CPU features.
///
/// An instruction is valid when _any_ of the features in the collection are
/// enabled; i.e., the collection is an `OR` expression.
/// An instruction is valid when the boolean term (a recursive tree of `AND` and
/// `OR` terms) is satisfied.
///
/// ```
/// # use cranelift_assembler_x64_meta::dsl::{Features, Feature};
/// let fs = Feature::_64b | Feature::compat;
/// assert_eq!(fs.to_string(), "_64b | compat");
/// ```
///
/// Duplicate features are not allowed and will cause a panic.
///
/// ```should_panic
/// # use cranelift_assembler_x64_meta::dsl::Feature;
/// let fs = Feature::_64b | Feature::_64b;
/// assert_eq!(fs.to_string(), "(_64b | compat)");
/// ```
#[derive(PartialEq)]
pub struct Features(Vec<Feature>);
pub enum Features {
And(Box<Features>, Box<Features>),
Or(Box<Features>, Box<Features>),
Feature(Feature),
}

impl Features {
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

pub fn iter(&self) -> impl Iterator<Item = &Feature> {
self.0.iter()
pub(crate) fn is_sse(&self) -> bool {
use Feature::*;
match self {
Features::And(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
Features::Or(lhs, rhs) => lhs.is_sse() || rhs.is_sse(),
Features::Feature(feature) => {
matches!(feature, sse | sse2 | sse3 | ssse3 | sse41 | sse42)
}
}
}
}

pub fn contains(&self, feature: Feature) -> bool {
self.0.contains(&feature)
impl fmt::Display for Features {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Features::And(lhs, rhs) => write!(f, "({lhs} & {rhs})"),
Features::Or(lhs, rhs) => write!(f, "({lhs} | {rhs})"),
Features::Feature(feature) => write!(f, "{feature:#?}"),
}
}
}

pub(crate) fn is_sse(&self) -> bool {
use Feature::*;
self.0
.iter()
.any(|f| matches!(f, sse | sse2 | sse3 | ssse3 | sse41 | sse42))
impl<T> BitOr<T> for Features
where
T: Into<Features>,
{
type Output = Features;
fn bitor(self, rhs: T) -> Self::Output {
Features::Or(Box::new(self), Box::new(rhs.into()))
}
}

impl fmt::Display for Features {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(" | ")
)
impl<T> BitAnd<T> for Features
where
T: Into<Features>,
{
type Output = Features;
fn bitand(self, rhs: T) -> Self::Output {
Features::And(Box::new(self), Box::new(rhs.into()))
}
}

Expand Down Expand Up @@ -126,30 +130,27 @@ impl fmt::Display for Feature {
}

impl From<Feature> for Features {
fn from(flag: Feature) -> Self {
Features(vec![flag])
}
}

impl From<Option<Feature>> for Features {
fn from(flag: Option<Feature>) -> Self {
Features(flag.into_iter().collect())
fn from(f: Feature) -> Self {
Features::Feature(f)
}
}

impl BitOr for Feature {
impl<T> BitAnd<T> for Feature
where
T: Into<Features>,
{
type Output = Features;
fn bitor(self, rhs: Self) -> Self::Output {
assert_ne!(self, rhs, "duplicate feature: {self:?}");
Features(vec![self, rhs])
fn bitand(self, rhs: T) -> Self::Output {
Features::from(self) & rhs.into()
}
}

impl BitOr<Feature> for Features {
impl<T> BitOr<T> for Feature
where
T: Into<Features>,
{
type Output = Features;
fn bitor(mut self, rhs: Feature) -> Self::Output {
assert!(!self.0.contains(&rhs), "duplicate feature: {rhs:?}");
self.0.push(rhs);
self
fn bitor(self, rhs: T) -> Self::Output {
Features::from(self) | rhs.into()
}
}
30 changes: 17 additions & 13 deletions cranelift/assembler-x64/meta/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) {
generate_inst_display_impl(f, insts);
generate_inst_encode_impl(f, insts);
generate_inst_visit_impl(f, insts);
generate_inst_features_impl(f, insts);
generate_inst_is_available_impl(f, insts);

// Generate per-instruction structs.
f.empty_line();
Expand All @@ -27,8 +27,9 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) {
f.empty_line();
}

// Generate the `Feature` enum.
dsl::Feature::generate_enum(f);
// Generate the `Feature` trait.
dsl::Feature::generate_trait(f);
dsl::Feature::generate_macro(f);
}

/// `enum Inst { ... }`
Expand Down Expand Up @@ -117,16 +118,19 @@ fn generate_inst_visit_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
fmtln!(f, "}}");
}

/// `impl Inst { fn features... }`
fn generate_inst_features_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
/// `impl Inst { fn is_available... }`
fn generate_inst_is_available_impl(f: &mut Formatter, insts: &[dsl::Inst]) {
f.add_block("impl<R: Registers> Inst<R>", |f| {
f.add_block("pub fn features(&self) -> Vec<Feature>", |f| {
f.add_block("match self", |f| {
for inst in insts {
let variant_name = inst.name();
fmtln!(f, "Self::{variant_name}(i) => i.features(),");
}
});
});
f.add_block(
"pub fn is_available(&self, f: &impl AvailableFeatures) -> bool",
|f| {
f.add_block("match self", |f| {
for inst in insts {
let variant_name = inst.name();
fmtln!(f, "Self::{variant_name}(i) => i.is_available(f),");
}
});
},
);
});
}
76 changes: 67 additions & 9 deletions cranelift/assembler-x64/meta/src/generate/features.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,79 @@
//! Generate feature-related Rust code.

use super::{Formatter, fmtln};
use crate::{dsl, generate::generate_derive};
use crate::dsl;

impl dsl::Feature {
/// `pub enum Feature { ... }`
/// `pub trait Features { ... }`
///
/// This function recreates the `Feature` struct itself in the generated
/// code.
pub fn generate_enum(f: &mut Formatter) {
/// This function generates a `Features` trait that users can implement to
/// query if instructions are available on a target CPU.
pub(crate) fn generate_trait(f: &mut Formatter) {
fmtln!(f, "#[doc(hidden)]");
generate_derive(f);
fmtln!(f, "#[derive(PartialEq)]"); // Add more helpful derives.
f.add_block("pub enum Feature", |f| {
f.add_block("pub trait AvailableFeatures", |f| {
for feature in dsl::ALL_FEATURES {
fmtln!(f, "{feature},");
fmtln!(f, "fn {feature}(&self) -> bool;");
}
});
}

/// `macro_rules! for_each_feature { ... }`
///
/// This function generates a macro to allow generating code for each CPU
/// feature.
pub(crate) fn generate_macro(f: &mut Formatter) {
fmtln!(f, "#[doc(hidden)]");
fmtln!(f, "#[macro_export]");
f.add_block("macro_rules! for_each_feature", |f| {
f.add_block("($m:ident) =>", |f| {
f.add_block("$m!", |f| {
for feature in dsl::ALL_FEATURES {
fmtln!(f, "{feature}");
}
});
});
});
}
}

impl dsl::Features {
/// E.g., `features.is_sse2() && features.is_64b()`
///
/// Generate a boolean expression that checks if the features are available.
pub(crate) fn generate_boolean_expr(&self, name: &str) -> String {
use dsl::Features::*;
match self {
And(lhs, rhs) => {
let lhs = lhs.generate_inner_boolean_expr(name);
let rhs = rhs.generate_inner_boolean_expr(name);
format!("{lhs} && {rhs}")
}
Or(lhs, rhs) => {
let lhs = lhs.generate_inner_boolean_expr(name);
let rhs = rhs.generate_inner_boolean_expr(name);
format!("{lhs} || {rhs}")
}
Feature(feature) => {
format!("{name}.{feature}()")
}
}
}

// This adds parentheses for inner terms.
fn generate_inner_boolean_expr(&self, name: &str) -> String {
use dsl::Features::*;
match self {
And(lhs, rhs) => {
let lhs = lhs.generate_inner_boolean_expr(name);
let rhs = rhs.generate_inner_boolean_expr(name);
format!("({lhs} && {rhs})")
}
Or(lhs, rhs) => {
let lhs = lhs.generate_inner_boolean_expr(name);
let rhs = rhs.generate_inner_boolean_expr(name);
format!("({lhs} || {rhs})")
}
Feature(feature) => format!("{name}.{feature}()"),
}
}
}
17 changes: 8 additions & 9 deletions cranelift/assembler-x64/meta/src/generate/inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,16 @@ impl dsl::Inst {
});
}

/// `fn features(&self) -> Vec<Flag> { ... }`
/// `fn is_available(&self, ...) -> bool { ... }`
fn generate_features_function(&self, f: &mut Formatter) {
fmtln!(f, "#[must_use]");
f.add_block("pub fn features(&self) -> Vec<Feature>", |f| {
let flags = self
.features
.iter()
.map(|f| format!("Feature::{f}"))
.collect::<Vec<_>>();
fmtln!(f, "vec![{}]", flags.join(", "));
});
f.add_block(
"pub fn is_available(&self, features: &impl AvailableFeatures) -> bool",
|f| {
let expr = self.features.generate_boolean_expr("features");
fmtln!(f, "{expr}");
},
);
}

/// `impl Display for <inst> { ... }`
Expand Down
12 changes: 6 additions & 6 deletions cranelift/assembler-x64/meta/src/instructions/abs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::dsl::{align, fmt, inst, r, rex, vex, w};
#[rustfmt::skip] // Keeps instructions on a single line.
pub fn list() -> Vec<Inst> {
vec![
inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), _64b | compat | ssse3).alt(avx, "vpabsb_a"),
inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), _64b | compat | avx),
inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), _64b | compat | ssse3).alt(avx, "vpabsw_a"),
inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), _64b | compat | avx),
inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), _64b | compat | ssse3).alt(avx, "vpabsd_a"),
inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), _64b | compat | avx),
inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), (_64b | compat) & ssse3).alt(avx, "vpabsb_a"),
inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), (_64b | compat) & avx),
inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), (_64b | compat) & ssse3).alt(avx, "vpabsw_a"),
inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), (_64b | compat) & avx),
inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), (_64b | compat) & ssse3).alt(avx, "vpabsd_a"),
inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), (_64b | compat) & avx),
]
}
Loading
Loading