Merge pull request #3009 from bjorn3/bye_x86_backend
[RFC] Remove the old x86 backend
This commit is contained in:
@@ -63,7 +63,6 @@ unwind = ["gimli"]
|
||||
# If no ISA targets are explicitly enabled, the ISA target for the host machine is enabled.
|
||||
x86 = []
|
||||
arm64 = []
|
||||
riscv = []
|
||||
s390x = []
|
||||
arm32 = [] # Work-in-progress codegen backend for ARM.
|
||||
|
||||
@@ -71,14 +70,10 @@ arm32 = [] # Work-in-progress codegen backend for ARM.
|
||||
# backend is the default now.
|
||||
experimental_x64 = []
|
||||
|
||||
# Make the old x86 backend the default.
|
||||
old-x86-backend = []
|
||||
|
||||
# Option to enable all architectures.
|
||||
all-arch = [
|
||||
"x86",
|
||||
"arm64",
|
||||
"riscv",
|
||||
"s390x"
|
||||
]
|
||||
|
||||
|
||||
@@ -1,755 +0,0 @@
|
||||
use crate::cdsl::instructions::{InstSpec, Instruction, InstructionPredicate};
|
||||
use crate::cdsl::operands::{OperandKind, OperandKindFields};
|
||||
use crate::cdsl::types::ValueType;
|
||||
use crate::cdsl::typevar::{TypeSetBuilder, TypeVar};
|
||||
|
||||
use cranelift_entity::{entity_impl, PrimaryMap, SparseMap, SparseMapValue};
|
||||
|
||||
use std::fmt;
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
pub(crate) enum Expr {
|
||||
Var(VarIndex),
|
||||
Literal(Literal),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn maybe_literal(&self) -> Option<&Literal> {
|
||||
match &self {
|
||||
Expr::Literal(lit) => Some(lit),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_var(&self) -> Option<VarIndex> {
|
||||
if let Expr::Var(var) = &self {
|
||||
Some(*var)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_var(&self) -> VarIndex {
|
||||
self.maybe_var()
|
||||
.expect("tried to unwrap a non-Var content in Expr::unwrap_var")
|
||||
}
|
||||
|
||||
pub fn to_rust_code(&self, var_pool: &VarPool) -> String {
|
||||
match self {
|
||||
Expr::Var(var_index) => var_pool.get(*var_index).to_rust_code(),
|
||||
Expr::Literal(literal) => literal.to_rust_code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An AST definition associates a set of variables with the values produced by an expression.
|
||||
pub(crate) struct Def {
|
||||
pub apply: Apply,
|
||||
pub defined_vars: Vec<VarIndex>,
|
||||
}
|
||||
|
||||
impl Def {
|
||||
pub fn to_comment_string(&self, var_pool: &VarPool) -> String {
|
||||
let results = self
|
||||
.defined_vars
|
||||
.iter()
|
||||
.map(|&x| var_pool.get(x).name.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let results = if results.len() == 1 {
|
||||
results[0].to_string()
|
||||
} else {
|
||||
format!("({})", results.join(", "))
|
||||
};
|
||||
|
||||
format!("{} := {}", results, self.apply.to_comment_string(var_pool))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DefPool {
|
||||
pool: PrimaryMap<DefIndex, Def>,
|
||||
}
|
||||
|
||||
impl DefPool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: PrimaryMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn get(&self, index: DefIndex) -> &Def {
|
||||
self.pool.get(index).unwrap()
|
||||
}
|
||||
pub fn next_index(&self) -> DefIndex {
|
||||
self.pool.next_key()
|
||||
}
|
||||
pub fn create_inst(&mut self, apply: Apply, defined_vars: Vec<VarIndex>) -> DefIndex {
|
||||
self.pool.push(Def {
|
||||
apply,
|
||||
defined_vars,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct DefIndex(u32);
|
||||
entity_impl!(DefIndex);
|
||||
|
||||
/// A definition which would lead to generate a block creation.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Block {
|
||||
/// Instruction index after which the block entry is set.
|
||||
pub location: DefIndex,
|
||||
/// Variable holding the new created block.
|
||||
pub name: VarIndex,
|
||||
}
|
||||
|
||||
pub(crate) struct BlockPool {
|
||||
pool: SparseMap<DefIndex, Block>,
|
||||
}
|
||||
|
||||
impl SparseMapValue<DefIndex> for Block {
|
||||
fn key(&self) -> DefIndex {
|
||||
self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockPool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: SparseMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn get(&self, index: DefIndex) -> Option<&Block> {
|
||||
self.pool.get(index)
|
||||
}
|
||||
pub fn create_block(&mut self, name: VarIndex, location: DefIndex) {
|
||||
if self.pool.contains_key(location) {
|
||||
panic!("Attempt to insert 2 blocks after the same instruction")
|
||||
}
|
||||
self.pool.insert(Block { location, name });
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pool.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Implement IntoIterator such that we can iterate over blocks which are in the block pool.
|
||||
impl<'a> IntoIterator for &'a BlockPool {
|
||||
type Item = <&'a SparseMap<DefIndex, Block> as IntoIterator>::Item;
|
||||
type IntoIter = <&'a SparseMap<DefIndex, Block> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.pool.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Literal {
|
||||
/// A value of an enumerated immediate operand.
|
||||
///
|
||||
/// Some immediate operand kinds like `intcc` and `floatcc` have an enumerated range of values
|
||||
/// corresponding to a Rust enum type. An `Enumerator` object is an AST leaf node representing one
|
||||
/// of the values.
|
||||
Enumerator {
|
||||
rust_type: &'static str,
|
||||
value: &'static str,
|
||||
},
|
||||
|
||||
/// A bitwise value of an immediate operand, used for bitwise exact floating point constants.
|
||||
Bits { rust_type: &'static str, value: u64 },
|
||||
|
||||
/// A value of an integer immediate operand.
|
||||
Int(i64),
|
||||
|
||||
/// A empty list of variable set of arguments.
|
||||
EmptyVarArgs,
|
||||
}
|
||||
|
||||
impl Literal {
|
||||
pub fn enumerator_for(kind: &OperandKind, value: &'static str) -> Self {
|
||||
let value = match &kind.fields {
|
||||
OperandKindFields::ImmEnum(values) => values.get(value).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"nonexistent value '{}' in enumeration '{}'",
|
||||
value, kind.rust_type
|
||||
)
|
||||
}),
|
||||
_ => panic!("enumerator is for enum values"),
|
||||
};
|
||||
Literal::Enumerator {
|
||||
rust_type: kind.rust_type,
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bits(kind: &OperandKind, bits: u64) -> Self {
|
||||
match kind.fields {
|
||||
OperandKindFields::ImmValue => {}
|
||||
_ => panic!("bits_of is for immediate scalar types"),
|
||||
}
|
||||
Literal::Bits {
|
||||
rust_type: kind.rust_type,
|
||||
value: bits,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constant(kind: &OperandKind, value: i64) -> Self {
|
||||
match kind.fields {
|
||||
OperandKindFields::ImmValue => {}
|
||||
_ => panic!("constant is for immediate scalar types"),
|
||||
}
|
||||
Literal::Int(value)
|
||||
}
|
||||
|
||||
pub fn empty_vararg() -> Self {
|
||||
Literal::EmptyVarArgs
|
||||
}
|
||||
|
||||
pub fn to_rust_code(&self) -> String {
|
||||
match self {
|
||||
Literal::Enumerator { rust_type, value } => format!("{}::{}", rust_type, value),
|
||||
Literal::Bits { rust_type, value } => format!("{}::with_bits({:#x})", rust_type, value),
|
||||
Literal::Int(val) => val.to_string(),
|
||||
Literal::EmptyVarArgs => "&[]".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum PatternPosition {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
|
||||
/// A free variable.
|
||||
///
|
||||
/// When variables are used in `XForms` with source and destination patterns, they are classified
|
||||
/// as follows:
|
||||
///
|
||||
/// Input values: Uses in the source pattern with no preceding def. These may appear as inputs in
|
||||
/// the destination pattern too, but no new inputs can be introduced.
|
||||
///
|
||||
/// Output values: Variables that are defined in both the source and destination pattern. These
|
||||
/// values may have uses outside the source pattern, and the destination pattern must compute the
|
||||
/// same value.
|
||||
///
|
||||
/// Intermediate values: Values that are defined in the source pattern, but not in the destination
|
||||
/// pattern. These may have uses outside the source pattern, so the defining instruction can't be
|
||||
/// deleted immediately.
|
||||
///
|
||||
/// Temporary values are defined only in the destination pattern.
|
||||
pub(crate) struct Var {
|
||||
pub name: String,
|
||||
|
||||
/// The `Def` defining this variable in a source pattern.
|
||||
pub src_def: Option<DefIndex>,
|
||||
|
||||
/// The `Def` defining this variable in a destination pattern.
|
||||
pub dst_def: Option<DefIndex>,
|
||||
|
||||
/// TypeVar representing the type of this variable.
|
||||
type_var: Option<TypeVar>,
|
||||
|
||||
/// Is this the original type variable, or has it be redefined with set_typevar?
|
||||
is_original_type_var: bool,
|
||||
}
|
||||
|
||||
impl Var {
|
||||
fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
src_def: None,
|
||||
dst_def: None,
|
||||
type_var: None,
|
||||
is_original_type_var: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this an input value to the src pattern?
|
||||
pub fn is_input(&self) -> bool {
|
||||
self.src_def.is_none() && self.dst_def.is_none()
|
||||
}
|
||||
|
||||
/// Is this an output value, defined in both src and dst patterns?
|
||||
pub fn is_output(&self) -> bool {
|
||||
self.src_def.is_some() && self.dst_def.is_some()
|
||||
}
|
||||
|
||||
/// Is this an intermediate value, defined only in the src pattern?
|
||||
pub fn is_intermediate(&self) -> bool {
|
||||
self.src_def.is_some() && self.dst_def.is_none()
|
||||
}
|
||||
|
||||
/// Is this a temp value, defined only in the dst pattern?
|
||||
pub fn is_temp(&self) -> bool {
|
||||
self.src_def.is_none() && self.dst_def.is_some()
|
||||
}
|
||||
|
||||
/// Get the def of this variable according to the position.
|
||||
pub fn get_def(&self, position: PatternPosition) -> Option<DefIndex> {
|
||||
match position {
|
||||
PatternPosition::Source => self.src_def,
|
||||
PatternPosition::Destination => self.dst_def,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_def(&mut self, position: PatternPosition, def: DefIndex) {
|
||||
assert!(
|
||||
self.get_def(position).is_none(),
|
||||
"redefinition of variable {}",
|
||||
self.name
|
||||
);
|
||||
match position {
|
||||
PatternPosition::Source => {
|
||||
self.src_def = Some(def);
|
||||
}
|
||||
PatternPosition::Destination => {
|
||||
self.dst_def = Some(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type variable representing the type of this variable.
|
||||
pub fn get_or_create_typevar(&mut self) -> TypeVar {
|
||||
match &self.type_var {
|
||||
Some(tv) => tv.clone(),
|
||||
None => {
|
||||
// Create a new type var in which we allow all types.
|
||||
let tv = TypeVar::new(
|
||||
format!("typeof_{}", self.name),
|
||||
format!("Type of the pattern variable {:?}", self),
|
||||
TypeSetBuilder::all(),
|
||||
);
|
||||
self.type_var = Some(tv.clone());
|
||||
self.is_original_type_var = true;
|
||||
tv
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_typevar(&self) -> Option<TypeVar> {
|
||||
self.type_var.clone()
|
||||
}
|
||||
pub fn set_typevar(&mut self, tv: TypeVar) {
|
||||
self.is_original_type_var = if let Some(previous_tv) = &self.type_var {
|
||||
*previous_tv == tv
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.type_var = Some(tv);
|
||||
}
|
||||
|
||||
/// Check if this variable has a free type variable. If not, the type of this variable is
|
||||
/// computed from the type of another variable.
|
||||
pub fn has_free_typevar(&self) -> bool {
|
||||
match &self.type_var {
|
||||
Some(tv) => tv.base.is_none() && self.is_original_type_var,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_rust_code(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
fn rust_type(&self) -> String {
|
||||
self.type_var.as_ref().unwrap().to_rust_code()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Var {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
fmt.write_fmt(format_args!(
|
||||
"Var({}{}{})",
|
||||
self.name,
|
||||
if self.src_def.is_some() { ", src" } else { "" },
|
||||
if self.dst_def.is_some() { ", dst" } else { "" }
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct VarIndex(u32);
|
||||
entity_impl!(VarIndex);
|
||||
|
||||
pub(crate) struct VarPool {
|
||||
pool: PrimaryMap<VarIndex, Var>,
|
||||
}
|
||||
|
||||
impl VarPool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: PrimaryMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn get(&self, index: VarIndex) -> &Var {
|
||||
self.pool.get(index).unwrap()
|
||||
}
|
||||
pub fn get_mut(&mut self, index: VarIndex) -> &mut Var {
|
||||
self.pool.get_mut(index).unwrap()
|
||||
}
|
||||
pub fn create(&mut self, name: impl Into<String>) -> VarIndex {
|
||||
self.pool.push(Var::new(name.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains constants created in the AST that must be inserted into the true [ConstantPool] when
|
||||
/// the legalizer code is generated. The constant data is named in the order it is inserted;
|
||||
/// inserting data using [insert] will avoid duplicates.
|
||||
///
|
||||
/// [ConstantPool]: ../../../cranelift_codegen/ir/constant/struct.ConstantPool.html
|
||||
/// [insert]: ConstPool::insert
|
||||
pub(crate) struct ConstPool {
|
||||
pool: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ConstPool {
|
||||
/// Create an empty constant pool.
|
||||
pub fn new() -> Self {
|
||||
Self { pool: vec![] }
|
||||
}
|
||||
|
||||
/// Create a name for a constant from its position in the pool.
|
||||
fn create_name(position: usize) -> String {
|
||||
format!("const{}", position)
|
||||
}
|
||||
|
||||
/// Insert constant data into the pool, returning the name of the variable used to reference it.
|
||||
/// This method will search for data that matches the new data and return the existing constant
|
||||
/// name to avoid duplicates.
|
||||
pub fn insert(&mut self, data: Vec<u8>) -> String {
|
||||
let possible_position = self.pool.iter().position(|d| d == &data);
|
||||
let position = if let Some(found_position) = possible_position {
|
||||
found_position
|
||||
} else {
|
||||
let new_position = self.pool.len();
|
||||
self.pool.push(data);
|
||||
new_position
|
||||
};
|
||||
ConstPool::create_name(position)
|
||||
}
|
||||
|
||||
/// Iterate over the name/value pairs in the pool.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (String, &Vec<u8>)> {
|
||||
self.pool
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (ConstPool::create_name(i), v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply an instruction to arguments.
|
||||
///
|
||||
/// An `Apply` AST expression is created by using function call syntax on instructions. This
|
||||
/// applies to both bound and unbound polymorphic instructions.
|
||||
pub(crate) struct Apply {
|
||||
pub inst: Instruction,
|
||||
pub args: Vec<Expr>,
|
||||
pub value_types: Vec<ValueType>,
|
||||
}
|
||||
|
||||
impl Apply {
|
||||
pub fn new(target: InstSpec, args: Vec<Expr>) -> Self {
|
||||
let (inst, value_types) = match target {
|
||||
InstSpec::Inst(inst) => (inst, Vec::new()),
|
||||
InstSpec::Bound(bound_inst) => (bound_inst.inst, bound_inst.value_types),
|
||||
};
|
||||
|
||||
// Apply should only operate on concrete value types, not "any".
|
||||
let value_types = value_types
|
||||
.into_iter()
|
||||
.map(|vt| vt.expect("shouldn't be Any"))
|
||||
.collect();
|
||||
|
||||
// Basic check on number of arguments.
|
||||
assert!(
|
||||
inst.operands_in.len() == args.len(),
|
||||
"incorrect number of arguments in instruction {}",
|
||||
inst.name
|
||||
);
|
||||
|
||||
// Check that the kinds of Literals arguments match the expected operand.
|
||||
for &imm_index in &inst.imm_opnums {
|
||||
let arg = &args[imm_index];
|
||||
if let Some(literal) = arg.maybe_literal() {
|
||||
let op = &inst.operands_in[imm_index];
|
||||
match &op.kind.fields {
|
||||
OperandKindFields::ImmEnum(values) => {
|
||||
if let Literal::Enumerator { value, .. } = literal {
|
||||
assert!(
|
||||
values.iter().any(|(_key, v)| v == value),
|
||||
"Nonexistent enum value '{}' passed to field of kind '{}' -- \
|
||||
did you use the right enum?",
|
||||
value,
|
||||
op.kind.rust_type
|
||||
);
|
||||
} else {
|
||||
panic!(
|
||||
"Passed non-enum field value {:?} to field of kind {}",
|
||||
literal, op.kind.rust_type
|
||||
);
|
||||
}
|
||||
}
|
||||
OperandKindFields::ImmValue => match &literal {
|
||||
Literal::Enumerator { value, .. } => panic!(
|
||||
"Expected immediate value in immediate field of kind '{}', \
|
||||
obtained enum value '{}'",
|
||||
op.kind.rust_type, value
|
||||
),
|
||||
Literal::Bits { .. } | Literal::Int(_) | Literal::EmptyVarArgs => {}
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
"Literal passed to non-literal field of kind {}",
|
||||
op.kind.rust_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
inst,
|
||||
args,
|
||||
value_types,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_comment_string(&self, var_pool: &VarPool) -> String {
|
||||
let args = self
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| arg.to_rust_code(var_pool))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let mut inst_and_bound_types = vec![self.inst.name.to_string()];
|
||||
inst_and_bound_types.extend(self.value_types.iter().map(|vt| vt.to_string()));
|
||||
let inst_name = inst_and_bound_types.join(".");
|
||||
|
||||
format!("{}({})", inst_name, args)
|
||||
}
|
||||
|
||||
pub fn inst_predicate(&self, var_pool: &VarPool) -> InstructionPredicate {
|
||||
let mut pred = InstructionPredicate::new();
|
||||
for (format_field, &op_num) in self
|
||||
.inst
|
||||
.format
|
||||
.imm_fields
|
||||
.iter()
|
||||
.zip(self.inst.imm_opnums.iter())
|
||||
{
|
||||
let arg = &self.args[op_num];
|
||||
if arg.maybe_var().is_some() {
|
||||
// Ignore free variables for now.
|
||||
continue;
|
||||
}
|
||||
pred = pred.and(InstructionPredicate::new_is_field_equal_ast(
|
||||
&*self.inst.format,
|
||||
format_field,
|
||||
arg.to_rust_code(var_pool),
|
||||
));
|
||||
}
|
||||
|
||||
// Add checks for any bound secondary type variables. We can't check the controlling type
|
||||
// variable this way since it may not appear as the type of an operand.
|
||||
if self.value_types.len() > 1 {
|
||||
let poly = self
|
||||
.inst
|
||||
.polymorphic_info
|
||||
.as_ref()
|
||||
.expect("must have polymorphic info if it has bounded types");
|
||||
for (bound_type, type_var) in
|
||||
self.value_types[1..].iter().zip(poly.other_typevars.iter())
|
||||
{
|
||||
pred = pred.and(InstructionPredicate::new_typevar_check(
|
||||
&self.inst, type_var, bound_type,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pred
|
||||
}
|
||||
|
||||
/// Same as `inst_predicate()`, but also check the controlling type variable.
|
||||
pub fn inst_predicate_with_ctrl_typevar(&self, var_pool: &VarPool) -> InstructionPredicate {
|
||||
let mut pred = self.inst_predicate(var_pool);
|
||||
|
||||
if !self.value_types.is_empty() {
|
||||
let bound_type = &self.value_types[0];
|
||||
let poly = self.inst.polymorphic_info.as_ref().unwrap();
|
||||
let type_check = if poly.use_typevar_operand {
|
||||
InstructionPredicate::new_typevar_check(&self.inst, &poly.ctrl_typevar, bound_type)
|
||||
} else {
|
||||
InstructionPredicate::new_ctrl_typevar_check(&bound_type)
|
||||
};
|
||||
pred = pred.and(type_check);
|
||||
}
|
||||
|
||||
pred
|
||||
}
|
||||
|
||||
pub fn rust_builder(&self, defined_vars: &[VarIndex], var_pool: &VarPool) -> String {
|
||||
let mut args = self
|
||||
.args
|
||||
.iter()
|
||||
.map(|expr| expr.to_rust_code(var_pool))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
// Do we need to pass an explicit type argument?
|
||||
if let Some(poly) = &self.inst.polymorphic_info {
|
||||
if !poly.use_typevar_operand {
|
||||
args = format!("{}, {}", var_pool.get(defined_vars[0]).rust_type(), args);
|
||||
}
|
||||
}
|
||||
|
||||
format!("{}({})", self.inst.snake_name(), args)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple helpers for legalize actions construction.
|
||||
|
||||
pub(crate) enum DummyExpr {
|
||||
Var(DummyVar),
|
||||
Literal(Literal),
|
||||
Constant(DummyConstant),
|
||||
Apply(InstSpec, Vec<DummyExpr>),
|
||||
Block(DummyVar),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DummyVar {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Into<DummyExpr> for DummyVar {
|
||||
fn into(self) -> DummyExpr {
|
||||
DummyExpr::Var(self)
|
||||
}
|
||||
}
|
||||
impl Into<DummyExpr> for Literal {
|
||||
fn into(self) -> DummyExpr {
|
||||
DummyExpr::Literal(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DummyConstant(pub(crate) Vec<u8>);
|
||||
|
||||
pub(crate) fn constant(data: Vec<u8>) -> DummyConstant {
|
||||
DummyConstant(data)
|
||||
}
|
||||
|
||||
impl Into<DummyExpr> for DummyConstant {
|
||||
fn into(self) -> DummyExpr {
|
||||
DummyExpr::Constant(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn var(name: &str) -> DummyVar {
|
||||
DummyVar {
|
||||
name: name.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DummyDef {
|
||||
pub expr: DummyExpr,
|
||||
pub defined_vars: Vec<DummyVar>,
|
||||
}
|
||||
|
||||
pub(crate) struct ExprBuilder {
|
||||
expr: DummyExpr,
|
||||
}
|
||||
|
||||
impl ExprBuilder {
|
||||
pub fn apply(inst: InstSpec, args: Vec<DummyExpr>) -> Self {
|
||||
let expr = DummyExpr::Apply(inst, args);
|
||||
Self { expr }
|
||||
}
|
||||
|
||||
pub fn assign_to(self, defined_vars: Vec<DummyVar>) -> DummyDef {
|
||||
DummyDef {
|
||||
expr: self.expr,
|
||||
defined_vars,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block(name: DummyVar) -> Self {
|
||||
let expr = DummyExpr::Block(name);
|
||||
Self { expr }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! def_rhs {
|
||||
// inst(a, b, c)
|
||||
($inst:ident($($src:expr),*)) => {
|
||||
ExprBuilder::apply($inst.into(), vec![$($src.clone().into()),*])
|
||||
};
|
||||
|
||||
// inst.type(a, b, c)
|
||||
($inst:ident.$type:ident($($src:expr),*)) => {
|
||||
ExprBuilder::apply($inst.bind($type).into(), vec![$($src.clone().into()),*])
|
||||
};
|
||||
}
|
||||
|
||||
// Helper macro to define legalization recipes.
|
||||
macro_rules! def {
|
||||
// x = ...
|
||||
($dest:ident = $($tt:tt)*) => {
|
||||
def_rhs!($($tt)*).assign_to(vec![$dest.clone()])
|
||||
};
|
||||
|
||||
// (x, y, ...) = ...
|
||||
(($($dest:ident),*) = $($tt:tt)*) => {
|
||||
def_rhs!($($tt)*).assign_to(vec![$($dest.clone()),*])
|
||||
};
|
||||
|
||||
// An instruction with no results.
|
||||
($($tt:tt)*) => {
|
||||
def_rhs!($($tt)*).assign_to(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
// Helper macro to define legalization recipes.
|
||||
macro_rules! block {
|
||||
// a basic block definition, splitting the current block in 2.
|
||||
($block: ident) => {
|
||||
ExprBuilder::block($block).assign_to(Vec::new())
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cdsl::ast::ConstPool;
|
||||
|
||||
#[test]
|
||||
fn const_pool_returns_var_names() {
|
||||
let mut c = ConstPool::new();
|
||||
assert_eq!(c.insert([0, 1, 2].to_vec()), "const0");
|
||||
assert_eq!(c.insert([1, 2, 3].to_vec()), "const1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_pool_avoids_duplicates() {
|
||||
let data = [0, 1, 2].to_vec();
|
||||
let mut c = ConstPool::new();
|
||||
assert_eq!(c.pool.len(), 0);
|
||||
|
||||
assert_eq!(c.insert(data.clone()), "const0");
|
||||
assert_eq!(c.pool.len(), 1);
|
||||
|
||||
assert_eq!(c.insert(data), "const0");
|
||||
assert_eq!(c.pool.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_pool_iterates() {
|
||||
let mut c = ConstPool::new();
|
||||
c.insert([0, 1, 2].to_vec());
|
||||
c.insert([3, 4, 5].to_vec());
|
||||
|
||||
let mut iter = c.iter();
|
||||
assert_eq!(iter.next(), Some(("const0".to_owned(), &vec![0, 1, 2])));
|
||||
assert_eq!(iter.next(), Some(("const1".to_owned(), &vec![3, 4, 5])));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
use std::collections::{hash_map, HashMap, HashSet};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use crate::cdsl::encodings::Encoding;
|
||||
use crate::cdsl::types::{LaneType, ValueType};
|
||||
use crate::cdsl::xform::{TransformGroup, TransformGroupIndex};
|
||||
|
||||
pub(crate) struct CpuMode {
|
||||
pub name: &'static str,
|
||||
default_legalize: Option<TransformGroupIndex>,
|
||||
monomorphic_legalize: Option<TransformGroupIndex>,
|
||||
typed_legalize: HashMap<ValueType, TransformGroupIndex>,
|
||||
pub encodings: Vec<Encoding>,
|
||||
}
|
||||
|
||||
impl CpuMode {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
default_legalize: None,
|
||||
monomorphic_legalize: None,
|
||||
typed_legalize: HashMap::new(),
|
||||
encodings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_encodings(&mut self, encodings: Vec<Encoding>) {
|
||||
assert!(self.encodings.is_empty(), "clobbering encodings");
|
||||
self.encodings = encodings;
|
||||
}
|
||||
|
||||
pub fn legalize_monomorphic(&mut self, group: &TransformGroup) {
|
||||
assert!(self.monomorphic_legalize.is_none());
|
||||
self.monomorphic_legalize = Some(group.id);
|
||||
}
|
||||
pub fn legalize_default(&mut self, group: &TransformGroup) {
|
||||
assert!(self.default_legalize.is_none());
|
||||
self.default_legalize = Some(group.id);
|
||||
}
|
||||
pub fn legalize_value_type(&mut self, lane_type: impl Into<ValueType>, group: &TransformGroup) {
|
||||
assert!(self
|
||||
.typed_legalize
|
||||
.insert(lane_type.into(), group.id)
|
||||
.is_none());
|
||||
}
|
||||
pub fn legalize_type(&mut self, lane_type: impl Into<LaneType>, group: &TransformGroup) {
|
||||
assert!(self
|
||||
.typed_legalize
|
||||
.insert(lane_type.into().into(), group.id)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
pub fn get_default_legalize_code(&self) -> TransformGroupIndex {
|
||||
self.default_legalize
|
||||
.expect("a finished CpuMode must have a default legalize code")
|
||||
}
|
||||
pub fn get_legalize_code_for(&self, typ: &Option<ValueType>) -> TransformGroupIndex {
|
||||
match typ {
|
||||
Some(typ) => self
|
||||
.typed_legalize
|
||||
.get(typ)
|
||||
.copied()
|
||||
.unwrap_or_else(|| self.get_default_legalize_code()),
|
||||
None => self
|
||||
.monomorphic_legalize
|
||||
.unwrap_or_else(|| self.get_default_legalize_code()),
|
||||
}
|
||||
}
|
||||
pub fn get_legalized_types(&self) -> hash_map::Keys<ValueType, TransformGroupIndex> {
|
||||
self.typed_legalize.keys()
|
||||
}
|
||||
|
||||
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the directly
|
||||
/// reachable set of TransformGroup this TargetIsa uses.
|
||||
pub fn direct_transform_groups(&self) -> Vec<TransformGroupIndex> {
|
||||
let mut set = HashSet::new();
|
||||
if let Some(i) = &self.default_legalize {
|
||||
set.insert(*i);
|
||||
}
|
||||
if let Some(i) = &self.monomorphic_legalize {
|
||||
set.insert(*i);
|
||||
}
|
||||
set.extend(self.typed_legalize.values().cloned());
|
||||
let mut ret = Vec::from_iter(set);
|
||||
ret.sort();
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
use crate::cdsl::instructions::{
|
||||
InstSpec, Instruction, InstructionPredicate, InstructionPredicateNode,
|
||||
InstructionPredicateNumber, InstructionPredicateRegistry, ValueTypeOrAny,
|
||||
};
|
||||
use crate::cdsl::recipes::{EncodingRecipeNumber, Recipes};
|
||||
use crate::cdsl::settings::SettingPredicateNumber;
|
||||
use crate::cdsl::types::ValueType;
|
||||
use std::rc::Rc;
|
||||
use std::string::ToString;
|
||||
|
||||
/// Encoding for a concrete instruction.
|
||||
///
|
||||
/// An `Encoding` object ties an instruction opcode with concrete type variables together with an
|
||||
/// encoding recipe and encoding encbits.
|
||||
///
|
||||
/// The concrete instruction can be in three different forms:
|
||||
///
|
||||
/// 1. A naked opcode: `trap` for non-polymorphic instructions.
|
||||
/// 2. With bound type variables: `iadd.i32` for polymorphic instructions.
|
||||
/// 3. With operands providing constraints: `icmp.i32(intcc.eq, x, y)`.
|
||||
///
|
||||
/// If the instruction is polymorphic, all type variables must be provided.
|
||||
pub(crate) struct EncodingContent {
|
||||
/// The `Instruction` or `BoundInstruction` being encoded.
|
||||
inst: InstSpec,
|
||||
|
||||
/// The `EncodingRecipe` to use.
|
||||
pub recipe: EncodingRecipeNumber,
|
||||
|
||||
/// Additional encoding bits to be interpreted by `recipe`.
|
||||
pub encbits: u16,
|
||||
|
||||
/// An instruction predicate that must be true to allow selecting this encoding.
|
||||
pub inst_predicate: Option<InstructionPredicateNumber>,
|
||||
|
||||
/// An ISA predicate that must be true to allow selecting this encoding.
|
||||
pub isa_predicate: Option<SettingPredicateNumber>,
|
||||
|
||||
/// The value type this encoding has been bound to, for encodings of polymorphic instructions.
|
||||
pub bound_type: Option<ValueType>,
|
||||
}
|
||||
|
||||
impl EncodingContent {
|
||||
pub fn inst(&self) -> &Instruction {
|
||||
self.inst.inst()
|
||||
}
|
||||
pub fn to_rust_comment(&self, recipes: &Recipes) -> String {
|
||||
format!("[{}#{:02x}]", recipes[self.recipe].name, self.encbits)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Encoding = Rc<EncodingContent>;
|
||||
|
||||
pub(crate) struct EncodingBuilder {
|
||||
inst: InstSpec,
|
||||
recipe: EncodingRecipeNumber,
|
||||
encbits: u16,
|
||||
inst_predicate: Option<InstructionPredicate>,
|
||||
isa_predicate: Option<SettingPredicateNumber>,
|
||||
bound_type: Option<ValueType>,
|
||||
}
|
||||
|
||||
impl EncodingBuilder {
|
||||
pub fn new(inst: InstSpec, recipe: EncodingRecipeNumber, encbits: u16) -> Self {
|
||||
let (inst_predicate, bound_type) = match &inst {
|
||||
InstSpec::Bound(inst) => {
|
||||
let other_typevars = &inst.inst.polymorphic_info.as_ref().unwrap().other_typevars;
|
||||
|
||||
assert_eq!(
|
||||
inst.value_types.len(),
|
||||
other_typevars.len() + 1,
|
||||
"partially bound polymorphic instruction"
|
||||
);
|
||||
|
||||
// Add secondary type variables to the instruction predicate.
|
||||
let value_types = &inst.value_types;
|
||||
let mut inst_predicate: Option<InstructionPredicate> = None;
|
||||
for (typevar, value_type) in other_typevars.iter().zip(value_types.iter().skip(1)) {
|
||||
let value_type = match value_type {
|
||||
ValueTypeOrAny::Any => continue,
|
||||
ValueTypeOrAny::ValueType(vt) => vt,
|
||||
};
|
||||
let type_predicate =
|
||||
InstructionPredicate::new_typevar_check(&inst.inst, typevar, value_type);
|
||||
inst_predicate = Some(type_predicate.into());
|
||||
}
|
||||
|
||||
// Add immediate value predicates
|
||||
for (immediate_value, immediate_operand) in inst
|
||||
.immediate_values
|
||||
.iter()
|
||||
.zip(inst.inst.operands_in.iter().filter(|o| o.is_immediate()))
|
||||
{
|
||||
let immediate_predicate = InstructionPredicate::new_is_field_equal(
|
||||
&inst.inst.format,
|
||||
immediate_operand.kind.rust_field_name,
|
||||
immediate_value.to_string(),
|
||||
);
|
||||
inst_predicate = if let Some(type_predicate) = inst_predicate {
|
||||
Some(type_predicate.and(immediate_predicate))
|
||||
} else {
|
||||
Some(immediate_predicate.into())
|
||||
}
|
||||
}
|
||||
|
||||
let ctrl_type = value_types[0]
|
||||
.clone()
|
||||
.expect("Controlling type shouldn't be Any");
|
||||
(inst_predicate, Some(ctrl_type))
|
||||
}
|
||||
|
||||
InstSpec::Inst(inst) => {
|
||||
assert!(
|
||||
inst.polymorphic_info.is_none(),
|
||||
"unbound polymorphic instruction"
|
||||
);
|
||||
(None, None)
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
inst,
|
||||
recipe,
|
||||
encbits,
|
||||
inst_predicate,
|
||||
isa_predicate: None,
|
||||
bound_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inst_predicate(mut self, inst_predicate: InstructionPredicateNode) -> Self {
|
||||
let inst_predicate = Some(match self.inst_predicate {
|
||||
Some(node) => node.and(inst_predicate),
|
||||
None => inst_predicate.into(),
|
||||
});
|
||||
self.inst_predicate = inst_predicate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn isa_predicate(mut self, isa_predicate: SettingPredicateNumber) -> Self {
|
||||
assert!(self.isa_predicate.is_none());
|
||||
self.isa_predicate = Some(isa_predicate);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(
|
||||
self,
|
||||
recipes: &Recipes,
|
||||
inst_pred_reg: &mut InstructionPredicateRegistry,
|
||||
) -> Encoding {
|
||||
let inst_predicate = self.inst_predicate.map(|pred| inst_pred_reg.insert(pred));
|
||||
|
||||
let inst = self.inst.inst();
|
||||
assert!(
|
||||
Rc::ptr_eq(&inst.format, &recipes[self.recipe].format),
|
||||
"Inst {} and recipe {} must have the same format!",
|
||||
inst.name,
|
||||
recipes[self.recipe].name
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
inst.is_branch && !inst.is_indirect_branch,
|
||||
recipes[self.recipe].branch_range.is_some(),
|
||||
"Inst {}'s is_branch contradicts recipe {} branch_range!",
|
||||
inst.name,
|
||||
recipes[self.recipe].name
|
||||
);
|
||||
|
||||
Rc::new(EncodingContent {
|
||||
inst: self.inst,
|
||||
recipe: self.recipe,
|
||||
encbits: self.encbits,
|
||||
inst_predicate,
|
||||
isa_predicate: self.isa_predicate,
|
||||
bound_type: self.bound_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -70,18 +70,6 @@ impl fmt::Display for InstructionFormat {
|
||||
}
|
||||
|
||||
impl InstructionFormat {
|
||||
pub fn imm_by_name(&self, name: &'static str) -> &FormatField {
|
||||
self.imm_fields
|
||||
.iter()
|
||||
.find(|&field| field.member == name)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"unexpected immediate field named {} in instruction format {}",
|
||||
name, self.name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a tuple that uniquely identifies the structure.
|
||||
pub fn structure(&self) -> FormatStructure {
|
||||
FormatStructure {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
use cranelift_codegen_shared::condcodes::IntCC;
|
||||
use cranelift_entity::{entity_impl, PrimaryMap};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::cdsl::camel_case;
|
||||
use crate::cdsl::formats::{FormatField, InstructionFormat};
|
||||
use crate::cdsl::formats::InstructionFormat;
|
||||
use crate::cdsl::operands::Operand;
|
||||
use crate::cdsl::type_inference::Constraint;
|
||||
use crate::cdsl::types::{LaneType, ReferenceType, ValueType, VectorType};
|
||||
use crate::cdsl::types::{LaneType, ReferenceType, ValueType};
|
||||
use crate::cdsl::typevar::TypeVar;
|
||||
|
||||
use crate::shared::formats::Formats;
|
||||
use crate::shared::types::{Bool, Float, Int, Reference};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
@@ -24,46 +21,18 @@ pub(crate) type AllInstructions = PrimaryMap<OpcodeNumber, Instruction>;
|
||||
|
||||
pub(crate) struct InstructionGroupBuilder<'all_inst> {
|
||||
all_instructions: &'all_inst mut AllInstructions,
|
||||
own_instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl<'all_inst> InstructionGroupBuilder<'all_inst> {
|
||||
pub fn new(all_instructions: &'all_inst mut AllInstructions) -> Self {
|
||||
Self {
|
||||
all_instructions,
|
||||
own_instructions: Vec::new(),
|
||||
}
|
||||
Self { all_instructions }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, builder: InstructionBuilder) {
|
||||
let opcode_number = OpcodeNumber(self.all_instructions.next_key().as_u32());
|
||||
let inst = builder.build(opcode_number);
|
||||
// Note this clone is cheap, since Instruction is a Rc<> wrapper for InstructionContent.
|
||||
self.own_instructions.push(inst.clone());
|
||||
self.all_instructions.push(inst);
|
||||
}
|
||||
|
||||
pub fn build(self) -> InstructionGroup {
|
||||
InstructionGroup {
|
||||
instructions: self.own_instructions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every instruction must belong to exactly one instruction group. A given
|
||||
/// target architecture can support instructions from multiple groups, and it
|
||||
/// does not necessarily support all instructions in a group.
|
||||
pub(crate) struct InstructionGroup {
|
||||
instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl InstructionGroup {
|
||||
pub fn by_name(&self, name: &'static str) -> &Instruction {
|
||||
self.instructions
|
||||
.iter()
|
||||
.find(|inst| inst.name == name)
|
||||
.unwrap_or_else(|| panic!("instruction with name '{}' does not exist", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructions can have parameters bound to them to specialize them for more specific encodings
|
||||
@@ -146,17 +115,6 @@ impl InstructionContent {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_typevars(&self) -> Vec<&TypeVar> {
|
||||
match &self.polymorphic_info {
|
||||
Some(poly) => {
|
||||
let mut result = vec![&poly.ctrl_typevar];
|
||||
result.extend(&poly.other_typevars);
|
||||
result
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Instruction = Rc<InstructionContent>;
|
||||
@@ -317,11 +275,6 @@ impl InstructionBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn clobbers_all_regs(mut self, val: bool) -> Self {
|
||||
self.clobbers_all_regs = val;
|
||||
self
|
||||
}
|
||||
|
||||
fn build(self, opcode_number: OpcodeNumber) -> Instruction {
|
||||
let operands_in = self.operands_in.unwrap_or_else(Vec::new);
|
||||
let operands_out = self.operands_out.unwrap_or_else(Vec::new);
|
||||
@@ -383,37 +336,10 @@ impl InstructionBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// A thin wrapper like Option<ValueType>, but with more precise semantics.
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum ValueTypeOrAny {
|
||||
ValueType(ValueType),
|
||||
Any,
|
||||
}
|
||||
|
||||
impl ValueTypeOrAny {
|
||||
pub fn expect(self, msg: &str) -> ValueType {
|
||||
match self {
|
||||
ValueTypeOrAny::ValueType(vt) => vt,
|
||||
ValueTypeOrAny::Any => panic!("Unexpected Any: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of bits in the vector
|
||||
type VectorBitWidth = u64;
|
||||
|
||||
/// An parameter used for binding instructions to specific types or values
|
||||
pub(crate) enum BindParameter {
|
||||
Any,
|
||||
Lane(LaneType),
|
||||
Vector(LaneType, VectorBitWidth),
|
||||
Reference(ReferenceType),
|
||||
Immediate(Immediate),
|
||||
}
|
||||
|
||||
/// Constructor for more easily building vector parameters from any lane type
|
||||
pub(crate) fn vector(parameter: impl Into<LaneType>, vector_size: VectorBitWidth) -> BindParameter {
|
||||
BindParameter::Vector(parameter.into(), vector_size)
|
||||
}
|
||||
|
||||
impl From<Int> for BindParameter {
|
||||
@@ -446,22 +372,13 @@ impl From<Reference> for BindParameter {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Immediate> for BindParameter {
|
||||
fn from(imm: Immediate) -> Self {
|
||||
BindParameter::Immediate(imm)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Immediate {
|
||||
// When needed, this enum should be expanded to include other immediate types (e.g. u8, u128).
|
||||
IntCC(IntCC),
|
||||
}
|
||||
pub(crate) enum Immediate {}
|
||||
|
||||
impl Display for Immediate {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
|
||||
match self {
|
||||
Immediate::IntCC(x) => write!(f, "IntCC::{:?}", x),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,7 +386,7 @@ impl Display for Immediate {
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BoundInstruction {
|
||||
pub inst: Instruction,
|
||||
pub value_types: Vec<ValueTypeOrAny>,
|
||||
pub value_types: Vec<ValueType>,
|
||||
pub immediate_values: Vec<Immediate>,
|
||||
}
|
||||
|
||||
@@ -530,28 +447,10 @@ impl Bindable for BoundInstruction {
|
||||
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction {
|
||||
let mut modified = self.clone();
|
||||
match parameter.into() {
|
||||
BindParameter::Any => modified.value_types.push(ValueTypeOrAny::Any),
|
||||
BindParameter::Lane(lane_type) => modified
|
||||
.value_types
|
||||
.push(ValueTypeOrAny::ValueType(lane_type.into())),
|
||||
BindParameter::Vector(lane_type, vector_size_in_bits) => {
|
||||
let num_lanes = vector_size_in_bits / lane_type.lane_bits();
|
||||
assert!(
|
||||
num_lanes >= 2,
|
||||
"Minimum lane number for bind_vector is 2, found {}.",
|
||||
num_lanes,
|
||||
);
|
||||
let vector_type = ValueType::Vector(VectorType::new(lane_type, num_lanes));
|
||||
modified
|
||||
.value_types
|
||||
.push(ValueTypeOrAny::ValueType(vector_type));
|
||||
}
|
||||
BindParameter::Lane(lane_type) => modified.value_types.push(lane_type.into()),
|
||||
BindParameter::Reference(reference_type) => {
|
||||
modified
|
||||
.value_types
|
||||
.push(ValueTypeOrAny::ValueType(reference_type.into()));
|
||||
modified.value_types.push(reference_type.into());
|
||||
}
|
||||
BindParameter::Immediate(immediate) => modified.immediate_values.push(immediate),
|
||||
}
|
||||
modified.verify_bindings().unwrap();
|
||||
modified
|
||||
@@ -763,545 +662,6 @@ fn is_ctrl_typevar_candidate(
|
||||
Ok(other_typevars)
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum FormatPredicateKind {
|
||||
/// Is the field member equal to the expected value (stored here)?
|
||||
IsEqual(String),
|
||||
|
||||
/// Is the immediate instruction format field representable as an n-bit two's complement
|
||||
/// integer? (with width: first member, scale: second member).
|
||||
/// The predicate is true if the field is in the range: `-2^(width-1) -- 2^(width-1)-1` and a
|
||||
/// multiple of `2^scale`.
|
||||
IsSignedInt(usize, usize),
|
||||
|
||||
/// Is the immediate instruction format field representable as an n-bit unsigned integer? (with
|
||||
/// width: first member, scale: second member).
|
||||
/// The predicate is true if the field is in the range: `0 -- 2^width - 1` and a multiple of
|
||||
/// `2^scale`.
|
||||
IsUnsignedInt(usize, usize),
|
||||
|
||||
/// Is the immediate format field member an integer equal to zero?
|
||||
IsZeroInt,
|
||||
/// Is the immediate format field member equal to zero? (float32 version)
|
||||
IsZero32BitFloat,
|
||||
|
||||
/// Is the immediate format field member equal to zero? (float64 version)
|
||||
IsZero64BitFloat,
|
||||
|
||||
/// Is the immediate format field member equal zero in all lanes?
|
||||
IsAllZeroes,
|
||||
|
||||
/// Does the immediate format field member have ones in all bits of all lanes?
|
||||
IsAllOnes,
|
||||
|
||||
/// Has the value list (in member_name) the size specified in parameter?
|
||||
LengthEquals(usize),
|
||||
|
||||
/// Is the referenced function colocated?
|
||||
IsColocatedFunc,
|
||||
|
||||
/// Is the referenced data object colocated?
|
||||
IsColocatedData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct FormatPredicateNode {
|
||||
format_name: &'static str,
|
||||
member_name: &'static str,
|
||||
kind: FormatPredicateKind,
|
||||
}
|
||||
|
||||
impl FormatPredicateNode {
|
||||
fn new(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
kind: FormatPredicateKind,
|
||||
) -> Self {
|
||||
let member_name = format.imm_by_name(field_name).member;
|
||||
Self {
|
||||
format_name: format.name,
|
||||
member_name,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_raw(
|
||||
format: &InstructionFormat,
|
||||
member_name: &'static str,
|
||||
kind: FormatPredicateKind,
|
||||
) -> Self {
|
||||
Self {
|
||||
format_name: format.name,
|
||||
member_name,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
fn destructuring_member_name(&self) -> &'static str {
|
||||
match &self.kind {
|
||||
FormatPredicateKind::LengthEquals(_) => {
|
||||
// Length operates on the argument value list.
|
||||
assert!(self.member_name == "args");
|
||||
"ref args"
|
||||
}
|
||||
_ => self.member_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn rust_predicate(&self) -> String {
|
||||
match &self.kind {
|
||||
FormatPredicateKind::IsEqual(arg) => {
|
||||
format!("predicates::is_equal({}, {})", self.member_name, arg)
|
||||
}
|
||||
FormatPredicateKind::IsSignedInt(width, scale) => format!(
|
||||
"predicates::is_signed_int({}, {}, {})",
|
||||
self.member_name, width, scale
|
||||
),
|
||||
FormatPredicateKind::IsUnsignedInt(width, scale) => format!(
|
||||
"predicates::is_unsigned_int({}, {}, {})",
|
||||
self.member_name, width, scale
|
||||
),
|
||||
FormatPredicateKind::IsZeroInt => {
|
||||
format!("predicates::is_zero_int({})", self.member_name)
|
||||
}
|
||||
FormatPredicateKind::IsZero32BitFloat => {
|
||||
format!("predicates::is_zero_32_bit_float({})", self.member_name)
|
||||
}
|
||||
FormatPredicateKind::IsZero64BitFloat => {
|
||||
format!("predicates::is_zero_64_bit_float({})", self.member_name)
|
||||
}
|
||||
FormatPredicateKind::IsAllZeroes => format!(
|
||||
"predicates::is_all_zeroes(func.dfg.constants.get({}))",
|
||||
self.member_name
|
||||
),
|
||||
FormatPredicateKind::IsAllOnes => format!(
|
||||
"predicates::is_all_ones(func.dfg.constants.get({}))",
|
||||
self.member_name
|
||||
),
|
||||
FormatPredicateKind::LengthEquals(num) => format!(
|
||||
"predicates::has_length_of({}, {}, func)",
|
||||
self.member_name, num
|
||||
),
|
||||
FormatPredicateKind::IsColocatedFunc => {
|
||||
format!("predicates::is_colocated_func({}, func)", self.member_name,)
|
||||
}
|
||||
FormatPredicateKind::IsColocatedData => {
|
||||
format!("predicates::is_colocated_data({}, func)", self.member_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum TypePredicateNode {
|
||||
/// Is the value argument (at the index designated by the first member) the same type as the
|
||||
/// type name (second member)?
|
||||
TypeVarCheck(usize, String),
|
||||
|
||||
/// Is the controlling type variable the same type as the one designated by the type name
|
||||
/// (only member)?
|
||||
CtrlTypeVarCheck(String),
|
||||
}
|
||||
|
||||
impl TypePredicateNode {
|
||||
fn rust_predicate(&self, func_str: &str) -> String {
|
||||
match self {
|
||||
TypePredicateNode::TypeVarCheck(index, value_type_name) => format!(
|
||||
"{}.dfg.value_type(args[{}]) == {}",
|
||||
func_str, index, value_type_name
|
||||
),
|
||||
TypePredicateNode::CtrlTypeVarCheck(value_type_name) => {
|
||||
format!("{}.dfg.ctrl_typevar(inst) == {}", func_str, value_type_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A basic node in an instruction predicate: either an atom, or an AND of two conditions.
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum InstructionPredicateNode {
|
||||
FormatPredicate(FormatPredicateNode),
|
||||
|
||||
TypePredicate(TypePredicateNode),
|
||||
|
||||
/// An AND-combination of two or more other predicates.
|
||||
And(Vec<InstructionPredicateNode>),
|
||||
|
||||
/// An OR-combination of two or more other predicates.
|
||||
Or(Vec<InstructionPredicateNode>),
|
||||
}
|
||||
|
||||
impl InstructionPredicateNode {
|
||||
fn rust_predicate(&self, func_str: &str) -> String {
|
||||
match self {
|
||||
InstructionPredicateNode::FormatPredicate(node) => node.rust_predicate(),
|
||||
InstructionPredicateNode::TypePredicate(node) => node.rust_predicate(func_str),
|
||||
InstructionPredicateNode::And(nodes) => nodes
|
||||
.iter()
|
||||
.map(|x| x.rust_predicate(func_str))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" && "),
|
||||
InstructionPredicateNode::Or(nodes) => nodes
|
||||
.iter()
|
||||
.map(|x| x.rust_predicate(func_str))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" || "),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_destructuring_member_name(&self) -> &str {
|
||||
match self {
|
||||
InstructionPredicateNode::FormatPredicate(format_pred) => {
|
||||
format_pred.destructuring_member_name()
|
||||
}
|
||||
_ => panic!("Only for leaf format predicates"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_name(&self) -> &str {
|
||||
match self {
|
||||
InstructionPredicateNode::FormatPredicate(format_pred) => format_pred.format_name,
|
||||
_ => panic!("Only for leaf format predicates"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_type_predicate(&self) -> bool {
|
||||
match self {
|
||||
InstructionPredicateNode::FormatPredicate(_)
|
||||
| InstructionPredicateNode::And(_)
|
||||
| InstructionPredicateNode::Or(_) => false,
|
||||
InstructionPredicateNode::TypePredicate(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> {
|
||||
let mut ret = Vec::new();
|
||||
match self {
|
||||
InstructionPredicateNode::And(nodes) | InstructionPredicateNode::Or(nodes) => {
|
||||
for node in nodes {
|
||||
ret.extend(node.collect_leaves());
|
||||
}
|
||||
}
|
||||
_ => ret.push(self),
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct InstructionPredicate {
|
||||
node: Option<InstructionPredicateNode>,
|
||||
}
|
||||
|
||||
impl Into<InstructionPredicate> for InstructionPredicateNode {
|
||||
fn into(self) -> InstructionPredicate {
|
||||
InstructionPredicate { node: Some(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionPredicate {
|
||||
pub fn new() -> Self {
|
||||
Self { node: None }
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> InstructionPredicateNode {
|
||||
self.node.unwrap()
|
||||
}
|
||||
|
||||
pub fn new_typevar_check(
|
||||
inst: &Instruction,
|
||||
type_var: &TypeVar,
|
||||
value_type: &ValueType,
|
||||
) -> InstructionPredicateNode {
|
||||
let index = inst
|
||||
.value_opnums
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &op_num)| inst.operands_in[op_num].type_var().unwrap() == type_var)
|
||||
.unwrap()
|
||||
.0;
|
||||
InstructionPredicateNode::TypePredicate(TypePredicateNode::TypeVarCheck(
|
||||
index,
|
||||
value_type.rust_name(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_ctrl_typevar_check(value_type: &ValueType) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::TypePredicate(TypePredicateNode::CtrlTypeVarCheck(
|
||||
value_type.rust_name(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_field_equal(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
imm_value: String,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsEqual(imm_value),
|
||||
))
|
||||
}
|
||||
|
||||
/// Used only for the AST module, which directly passes in the format field.
|
||||
pub fn new_is_field_equal_ast(
|
||||
format: &InstructionFormat,
|
||||
field: &FormatField,
|
||||
imm_value: String,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw(
|
||||
format,
|
||||
field.member,
|
||||
FormatPredicateKind::IsEqual(imm_value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_signed_int(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
width: usize,
|
||||
scale: usize,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsSignedInt(width, scale),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_unsigned_int(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
width: usize,
|
||||
scale: usize,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsUnsignedInt(width, scale),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_zero_int(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsZeroInt,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_zero_32bit_float(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsZero32BitFloat,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_zero_64bit_float(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsZero64BitFloat,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_all_zeroes(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsAllZeroes,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_all_ones(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsAllOnes,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_length_equals(format: &InstructionFormat, size: usize) -> InstructionPredicateNode {
|
||||
assert!(
|
||||
format.has_value_list,
|
||||
"the format must be variadic in number of arguments"
|
||||
);
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw(
|
||||
format,
|
||||
"args",
|
||||
FormatPredicateKind::LengthEquals(size),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_colocated_func(
|
||||
format: &InstructionFormat,
|
||||
field_name: &'static str,
|
||||
) -> InstructionPredicateNode {
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
format,
|
||||
field_name,
|
||||
FormatPredicateKind::IsColocatedFunc,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_is_colocated_data(formats: &Formats) -> InstructionPredicateNode {
|
||||
let format = &formats.unary_global_value;
|
||||
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
|
||||
&*format,
|
||||
"global_value",
|
||||
FormatPredicateKind::IsColocatedData,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn and(mut self, new_node: InstructionPredicateNode) -> Self {
|
||||
let node = self.node;
|
||||
let mut and_nodes = match node {
|
||||
Some(node) => match node {
|
||||
InstructionPredicateNode::And(nodes) => nodes,
|
||||
InstructionPredicateNode::Or(_) => {
|
||||
panic!("Can't mix and/or without implementing operator precedence!")
|
||||
}
|
||||
_ => vec![node],
|
||||
},
|
||||
_ => Vec::new(),
|
||||
};
|
||||
and_nodes.push(new_node);
|
||||
self.node = Some(InstructionPredicateNode::And(and_nodes));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn or(mut self, new_node: InstructionPredicateNode) -> Self {
|
||||
let node = self.node;
|
||||
let mut or_nodes = match node {
|
||||
Some(node) => match node {
|
||||
InstructionPredicateNode::Or(nodes) => nodes,
|
||||
InstructionPredicateNode::And(_) => {
|
||||
panic!("Can't mix and/or without implementing operator precedence!")
|
||||
}
|
||||
_ => vec![node],
|
||||
},
|
||||
_ => Vec::new(),
|
||||
};
|
||||
or_nodes.push(new_node);
|
||||
self.node = Some(InstructionPredicateNode::Or(or_nodes));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rust_predicate(&self, func_str: &str) -> Option<String> {
|
||||
self.node.as_ref().map(|root| root.rust_predicate(func_str))
|
||||
}
|
||||
|
||||
/// Returns the type predicate if this is one, or None otherwise.
|
||||
pub fn type_predicate(&self, func_str: &str) -> Option<String> {
|
||||
let node = self.node.as_ref().unwrap();
|
||||
if node.is_type_predicate() {
|
||||
Some(node.rust_predicate(func_str))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns references to all the nodes that are leaves in the condition (i.e. by flattening
|
||||
/// AND/OR).
|
||||
pub fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> {
|
||||
self.node.as_ref().unwrap().collect_leaves()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct InstructionPredicateNumber(u32);
|
||||
entity_impl!(InstructionPredicateNumber);
|
||||
|
||||
pub(crate) type InstructionPredicateMap =
|
||||
PrimaryMap<InstructionPredicateNumber, InstructionPredicate>;
|
||||
|
||||
/// A registry of predicates to help deduplicating them, during Encodings construction. When the
|
||||
/// construction process is over, it needs to be extracted with `extract` and associated to the
|
||||
/// TargetIsa.
|
||||
pub(crate) struct InstructionPredicateRegistry {
|
||||
/// Maps a predicate number to its actual predicate.
|
||||
map: InstructionPredicateMap,
|
||||
|
||||
/// Inverse map: maps a predicate to its predicate number. This is used before inserting a
|
||||
/// predicate, to check whether it already exists.
|
||||
inverted_map: HashMap<InstructionPredicate, InstructionPredicateNumber>,
|
||||
}
|
||||
|
||||
impl InstructionPredicateRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: PrimaryMap::new(),
|
||||
inverted_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, predicate: InstructionPredicate) -> InstructionPredicateNumber {
|
||||
match self.inverted_map.get(&predicate) {
|
||||
Some(&found) => found,
|
||||
None => {
|
||||
let key = self.map.push(predicate.clone());
|
||||
self.inverted_map.insert(predicate, key);
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn extract(self) -> InstructionPredicateMap {
|
||||
self.map
|
||||
}
|
||||
}
|
||||
|
||||
/// An instruction specification, containing an instruction that has bound types or not.
|
||||
pub(crate) enum InstSpec {
|
||||
Inst(Instruction),
|
||||
Bound(BoundInstruction),
|
||||
}
|
||||
|
||||
impl InstSpec {
|
||||
pub fn inst(&self) -> &Instruction {
|
||||
match &self {
|
||||
InstSpec::Inst(inst) => inst,
|
||||
InstSpec::Bound(bound_inst) => &bound_inst.inst,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bindable for InstSpec {
|
||||
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction {
|
||||
match self {
|
||||
InstSpec::Inst(inst) => inst.bind(parameter.into()),
|
||||
InstSpec::Bound(inst) => inst.bind(parameter.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InstSpec> for &Instruction {
|
||||
fn into(self) -> InstSpec {
|
||||
InstSpec::Inst(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InstSpec> for BoundInstruction {
|
||||
fn into(self) -> InstSpec {
|
||||
InstSpec::Bound(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -1359,13 +719,6 @@ mod test {
|
||||
inst.bind(LaneType::Int(I32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_bound_instructions_can_bind_immediates() {
|
||||
let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]);
|
||||
let bound_inst = inst.bind(Immediate::IntCC(IntCC::Equal));
|
||||
assert!(bound_inst.verify_bindings().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn ensure_instructions_fail_to_bind() {
|
||||
@@ -1382,14 +735,4 @@ mod test {
|
||||
let inst = build_fake_instruction(vec![in1], vec![]);
|
||||
inst.bind(LaneType::Int(I32)).bind(LaneType::Int(I64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn ensure_instructions_fail_to_bind_too_many_immediates() {
|
||||
let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]);
|
||||
inst.bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal)))
|
||||
.bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal)));
|
||||
// Trying to bind too many immediates to an instruction should fail; note that the immediate
|
||||
// values are nonsensical but irrelevant to the purpose of this test.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +1,12 @@
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use crate::cdsl::cpu_modes::CpuMode;
|
||||
use crate::cdsl::instructions::InstructionPredicateMap;
|
||||
use crate::cdsl::recipes::Recipes;
|
||||
use crate::cdsl::regs::IsaRegs;
|
||||
use crate::cdsl::settings::SettingGroup;
|
||||
use crate::cdsl::xform::{TransformGroupIndex, TransformGroups};
|
||||
|
||||
pub(crate) struct TargetIsa {
|
||||
pub name: &'static str,
|
||||
pub settings: SettingGroup,
|
||||
pub regs: IsaRegs,
|
||||
pub recipes: Recipes,
|
||||
pub cpu_modes: Vec<CpuMode>,
|
||||
pub encodings_predicates: InstructionPredicateMap,
|
||||
|
||||
/// TransformGroupIndex are global to all the ISAs, while we want to have indices into the
|
||||
/// local array of transform groups that are directly used. We use this map to get this
|
||||
/// information.
|
||||
pub local_transform_groups: Vec<TransformGroupIndex>,
|
||||
}
|
||||
|
||||
impl TargetIsa {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
settings: SettingGroup,
|
||||
regs: IsaRegs,
|
||||
recipes: Recipes,
|
||||
cpu_modes: Vec<CpuMode>,
|
||||
encodings_predicates: InstructionPredicateMap,
|
||||
) -> Self {
|
||||
// Compute the local TransformGroup index.
|
||||
let mut local_transform_groups = Vec::new();
|
||||
for cpu_mode in &cpu_modes {
|
||||
let transform_groups = cpu_mode.direct_transform_groups();
|
||||
for group_index in transform_groups {
|
||||
// find() is fine here: the number of transform group is < 5 as of June 2019.
|
||||
if local_transform_groups
|
||||
.iter()
|
||||
.find(|&val| group_index == *val)
|
||||
.is_none()
|
||||
{
|
||||
local_transform_groups.push(group_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
name,
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
local_transform_groups,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the
|
||||
/// transitive set of TransformGroup this TargetIsa uses.
|
||||
pub fn transitive_transform_groups(
|
||||
&self,
|
||||
all_groups: &TransformGroups,
|
||||
) -> Vec<TransformGroupIndex> {
|
||||
let mut set = HashSet::new();
|
||||
|
||||
for &root in self.local_transform_groups.iter() {
|
||||
set.insert(root);
|
||||
let mut base = root;
|
||||
// Follow the chain of chain_with.
|
||||
while let Some(chain_with) = &all_groups.get(base).chain_with {
|
||||
set.insert(*chain_with);
|
||||
base = *chain_with;
|
||||
}
|
||||
}
|
||||
|
||||
let mut vec = Vec::from_iter(set);
|
||||
vec.sort();
|
||||
vec
|
||||
}
|
||||
|
||||
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the directly
|
||||
/// reachable set of TransformGroup this TargetIsa uses.
|
||||
pub fn direct_transform_groups(&self) -> &Vec<TransformGroupIndex> {
|
||||
&self.local_transform_groups
|
||||
}
|
||||
|
||||
pub fn translate_group_index(&self, group_index: TransformGroupIndex) -> usize {
|
||||
self.local_transform_groups
|
||||
.iter()
|
||||
.position(|&val| val == group_index)
|
||||
.expect("TransformGroup unused by this TargetIsa!")
|
||||
pub fn new(name: &'static str, settings: SettingGroup) -> Self {
|
||||
Self { name, settings }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,14 @@
|
||||
//! This module defines the classes that are used to define Cranelift
|
||||
//! instructions and other entities.
|
||||
|
||||
#[macro_use]
|
||||
pub mod ast;
|
||||
pub mod cpu_modes;
|
||||
pub mod encodings;
|
||||
pub mod formats;
|
||||
pub mod instructions;
|
||||
pub mod isa;
|
||||
pub mod operands;
|
||||
pub mod recipes;
|
||||
pub mod regs;
|
||||
pub mod settings;
|
||||
pub mod type_inference;
|
||||
pub mod types;
|
||||
pub mod typevar;
|
||||
pub mod xform;
|
||||
|
||||
/// A macro that converts boolean settings into predicates to look more natural.
|
||||
#[macro_export]
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use cranelift_entity::{entity_impl, PrimaryMap};
|
||||
|
||||
use crate::cdsl::formats::InstructionFormat;
|
||||
use crate::cdsl::instructions::InstructionPredicate;
|
||||
use crate::cdsl::regs::RegClassIndex;
|
||||
use crate::cdsl::settings::SettingPredicateNumber;
|
||||
|
||||
/// A specific register in a register class.
|
||||
///
|
||||
/// A register is identified by the top-level register class it belongs to and
|
||||
/// its first register unit.
|
||||
///
|
||||
/// Specific registers are used to describe constraints on instructions where
|
||||
/// some operands must use a fixed register.
|
||||
///
|
||||
/// Register instances can be created with the constructor, or accessed as
|
||||
/// attributes on the register class: `GPR.rcx`.
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct Register {
|
||||
pub regclass: RegClassIndex,
|
||||
pub unit: u8,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn new(regclass: RegClassIndex, unit: u8) -> Self {
|
||||
Self { regclass, unit }
|
||||
}
|
||||
}
|
||||
|
||||
/// An operand that must be in a stack slot.
|
||||
///
|
||||
/// A `Stack` object can be used to indicate an operand constraint for a value
|
||||
/// operand that must live in a stack slot.
|
||||
#[derive(Copy, Clone, Hash, PartialEq)]
|
||||
pub(crate) struct Stack {
|
||||
pub regclass: RegClassIndex,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
pub fn new(regclass: RegClassIndex) -> Self {
|
||||
Self { regclass }
|
||||
}
|
||||
pub fn stack_base_mask(self) -> &'static str {
|
||||
// TODO: Make this configurable instead of just using the SP.
|
||||
"StackBaseMask(1)"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq)]
|
||||
pub(crate) struct BranchRange {
|
||||
pub inst_size: u64,
|
||||
pub range: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq)]
|
||||
pub(crate) enum OperandConstraint {
|
||||
RegClass(RegClassIndex),
|
||||
FixedReg(Register),
|
||||
TiedInput(usize),
|
||||
Stack(Stack),
|
||||
}
|
||||
|
||||
impl Into<OperandConstraint> for RegClassIndex {
|
||||
fn into(self) -> OperandConstraint {
|
||||
OperandConstraint::RegClass(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OperandConstraint> for Register {
|
||||
fn into(self) -> OperandConstraint {
|
||||
OperandConstraint::FixedReg(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OperandConstraint> for usize {
|
||||
fn into(self) -> OperandConstraint {
|
||||
OperandConstraint::TiedInput(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<OperandConstraint> for Stack {
|
||||
fn into(self) -> OperandConstraint {
|
||||
OperandConstraint::Stack(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A recipe for encoding instructions with a given format.
|
||||
///
|
||||
/// Many different instructions can be encoded by the same recipe, but they
|
||||
/// must all have the same instruction format.
|
||||
///
|
||||
/// The `operands_in` and `operands_out` arguments are tuples specifying the register
|
||||
/// allocation constraints for the value operands and results respectively. The
|
||||
/// possible constraints for an operand are:
|
||||
///
|
||||
/// - A `RegClass` specifying the set of allowed registers.
|
||||
/// - A `Register` specifying a fixed-register operand.
|
||||
/// - An integer indicating that this result is tied to a value operand, so
|
||||
/// they must use the same register.
|
||||
/// - A `Stack` specifying a value in a stack slot.
|
||||
///
|
||||
/// The `branch_range` argument must be provided for recipes that can encode
|
||||
/// branch instructions. It is an `(origin, bits)` tuple describing the exact
|
||||
/// range that can be encoded in a branch instruction.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct EncodingRecipe {
|
||||
/// Short mnemonic name for this recipe.
|
||||
pub name: String,
|
||||
|
||||
/// Associated instruction format.
|
||||
pub format: Rc<InstructionFormat>,
|
||||
|
||||
/// Base number of bytes in the binary encoded instruction.
|
||||
pub base_size: u64,
|
||||
|
||||
/// Tuple of register constraints for value operands.
|
||||
pub operands_in: Vec<OperandConstraint>,
|
||||
|
||||
/// Tuple of register constraints for results.
|
||||
pub operands_out: Vec<OperandConstraint>,
|
||||
|
||||
/// Function name to use when computing actual size.
|
||||
pub compute_size: &'static str,
|
||||
|
||||
/// `(origin, bits)` range for branches.
|
||||
pub branch_range: Option<BranchRange>,
|
||||
|
||||
/// This instruction clobbers `iflags` and `fflags`; true by default.
|
||||
pub clobbers_flags: bool,
|
||||
|
||||
/// Instruction predicate.
|
||||
pub inst_predicate: Option<InstructionPredicate>,
|
||||
|
||||
/// ISA predicate.
|
||||
pub isa_predicate: Option<SettingPredicateNumber>,
|
||||
|
||||
/// Rust code for binary emission.
|
||||
pub emit: Option<String>,
|
||||
}
|
||||
|
||||
// Implement PartialEq ourselves: take all the fields into account but the name.
|
||||
impl PartialEq for EncodingRecipe {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.format, &other.format)
|
||||
&& self.base_size == other.base_size
|
||||
&& self.operands_in == other.operands_in
|
||||
&& self.operands_out == other.operands_out
|
||||
&& self.compute_size == other.compute_size
|
||||
&& self.branch_range == other.branch_range
|
||||
&& self.clobbers_flags == other.clobbers_flags
|
||||
&& self.inst_predicate == other.inst_predicate
|
||||
&& self.isa_predicate == other.isa_predicate
|
||||
&& self.emit == other.emit
|
||||
}
|
||||
}
|
||||
|
||||
// To allow using it in a hashmap.
|
||||
impl Eq for EncodingRecipe {}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct EncodingRecipeNumber(u32);
|
||||
entity_impl!(EncodingRecipeNumber);
|
||||
|
||||
pub(crate) type Recipes = PrimaryMap<EncodingRecipeNumber, EncodingRecipe>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct EncodingRecipeBuilder {
|
||||
pub name: String,
|
||||
format: Rc<InstructionFormat>,
|
||||
pub base_size: u64,
|
||||
pub operands_in: Option<Vec<OperandConstraint>>,
|
||||
pub operands_out: Option<Vec<OperandConstraint>>,
|
||||
pub compute_size: Option<&'static str>,
|
||||
pub branch_range: Option<BranchRange>,
|
||||
pub emit: Option<String>,
|
||||
clobbers_flags: Option<bool>,
|
||||
inst_predicate: Option<InstructionPredicate>,
|
||||
isa_predicate: Option<SettingPredicateNumber>,
|
||||
}
|
||||
|
||||
impl EncodingRecipeBuilder {
|
||||
pub fn new(name: impl Into<String>, format: &Rc<InstructionFormat>, base_size: u64) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
format: format.clone(),
|
||||
base_size,
|
||||
operands_in: None,
|
||||
operands_out: None,
|
||||
compute_size: None,
|
||||
branch_range: None,
|
||||
emit: None,
|
||||
clobbers_flags: None,
|
||||
inst_predicate: None,
|
||||
isa_predicate: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Setters.
|
||||
pub fn operands_in(mut self, constraints: Vec<impl Into<OperandConstraint>>) -> Self {
|
||||
assert!(self.operands_in.is_none());
|
||||
self.operands_in = Some(
|
||||
constraints
|
||||
.into_iter()
|
||||
.map(|constr| constr.into())
|
||||
.collect(),
|
||||
);
|
||||
self
|
||||
}
|
||||
pub fn operands_out(mut self, constraints: Vec<impl Into<OperandConstraint>>) -> Self {
|
||||
assert!(self.operands_out.is_none());
|
||||
self.operands_out = Some(
|
||||
constraints
|
||||
.into_iter()
|
||||
.map(|constr| constr.into())
|
||||
.collect(),
|
||||
);
|
||||
self
|
||||
}
|
||||
pub fn clobbers_flags(mut self, flag: bool) -> Self {
|
||||
assert!(self.clobbers_flags.is_none());
|
||||
self.clobbers_flags = Some(flag);
|
||||
self
|
||||
}
|
||||
pub fn emit(mut self, code: impl Into<String>) -> Self {
|
||||
assert!(self.emit.is_none());
|
||||
self.emit = Some(code.into());
|
||||
self
|
||||
}
|
||||
pub fn branch_range(mut self, range: (u64, u64)) -> Self {
|
||||
assert!(self.branch_range.is_none());
|
||||
self.branch_range = Some(BranchRange {
|
||||
inst_size: range.0,
|
||||
range: range.1,
|
||||
});
|
||||
self
|
||||
}
|
||||
pub fn isa_predicate(mut self, pred: SettingPredicateNumber) -> Self {
|
||||
assert!(self.isa_predicate.is_none());
|
||||
self.isa_predicate = Some(pred);
|
||||
self
|
||||
}
|
||||
pub fn inst_predicate(mut self, inst_predicate: impl Into<InstructionPredicate>) -> Self {
|
||||
assert!(self.inst_predicate.is_none());
|
||||
self.inst_predicate = Some(inst_predicate.into());
|
||||
self
|
||||
}
|
||||
pub fn compute_size(mut self, compute_size: &'static str) -> Self {
|
||||
assert!(self.compute_size.is_none());
|
||||
self.compute_size = Some(compute_size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> EncodingRecipe {
|
||||
let operands_in = self.operands_in.unwrap_or_default();
|
||||
let operands_out = self.operands_out.unwrap_or_default();
|
||||
|
||||
// The number of input constraints must match the number of format input operands.
|
||||
if !self.format.has_value_list {
|
||||
assert!(
|
||||
operands_in.len() == self.format.num_value_operands,
|
||||
"missing operand constraints for recipe {} (format {})",
|
||||
self.name,
|
||||
self.format.name
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure tied inputs actually refer to existing inputs.
|
||||
for constraint in operands_in.iter().chain(operands_out.iter()) {
|
||||
if let OperandConstraint::TiedInput(n) = *constraint {
|
||||
assert!(n < operands_in.len());
|
||||
}
|
||||
}
|
||||
|
||||
let compute_size = match self.compute_size {
|
||||
Some(compute_size) => compute_size,
|
||||
None => "base_size",
|
||||
};
|
||||
|
||||
let clobbers_flags = self.clobbers_flags.unwrap_or(true);
|
||||
|
||||
EncodingRecipe {
|
||||
name: self.name,
|
||||
format: self.format,
|
||||
base_size: self.base_size,
|
||||
operands_in,
|
||||
operands_out,
|
||||
compute_size,
|
||||
branch_range: self.branch_range,
|
||||
clobbers_flags,
|
||||
inst_predicate: self.inst_predicate,
|
||||
isa_predicate: self.isa_predicate,
|
||||
emit: self.emit,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
use cranelift_codegen_shared::constants;
|
||||
use cranelift_entity::{entity_impl, EntityRef, PrimaryMap};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct RegBankIndex(u32);
|
||||
entity_impl!(RegBankIndex);
|
||||
|
||||
pub(crate) struct RegBank {
|
||||
pub name: &'static str,
|
||||
pub first_unit: u8,
|
||||
pub units: u8,
|
||||
pub names: Vec<&'static str>,
|
||||
pub prefix: &'static str,
|
||||
pub pressure_tracking: bool,
|
||||
pub pinned_reg: Option<u16>,
|
||||
pub toprcs: Vec<RegClassIndex>,
|
||||
pub classes: Vec<RegClassIndex>,
|
||||
}
|
||||
|
||||
impl RegBank {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
first_unit: u8,
|
||||
units: u8,
|
||||
names: Vec<&'static str>,
|
||||
prefix: &'static str,
|
||||
pressure_tracking: bool,
|
||||
pinned_reg: Option<u16>,
|
||||
) -> Self {
|
||||
RegBank {
|
||||
name,
|
||||
first_unit,
|
||||
units,
|
||||
names,
|
||||
prefix,
|
||||
pressure_tracking,
|
||||
pinned_reg,
|
||||
toprcs: Vec::new(),
|
||||
classes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unit_by_name(&self, name: &'static str) -> u8 {
|
||||
let unit = if let Some(found) = self.names.iter().position(|®_name| reg_name == name) {
|
||||
found
|
||||
} else {
|
||||
// Try to match without the bank prefix.
|
||||
assert!(name.starts_with(self.prefix));
|
||||
let name_without_prefix = &name[self.prefix.len()..];
|
||||
if let Some(found) = self
|
||||
.names
|
||||
.iter()
|
||||
.position(|®_name| reg_name == name_without_prefix)
|
||||
{
|
||||
found
|
||||
} else {
|
||||
// Ultimate try: try to parse a number and use this in the array, eg r15 on x86.
|
||||
if let Ok(as_num) = name_without_prefix.parse::<u8>() {
|
||||
assert!(
|
||||
as_num < self.units,
|
||||
"trying to get {}, but bank only has {} registers!",
|
||||
name,
|
||||
self.units
|
||||
);
|
||||
as_num as usize
|
||||
} else {
|
||||
panic!("invalid register name {}", name);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.first_unit + (unit as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub(crate) struct RegClassIndex(u32);
|
||||
entity_impl!(RegClassIndex);
|
||||
|
||||
pub(crate) struct RegClass {
|
||||
pub name: &'static str,
|
||||
pub index: RegClassIndex,
|
||||
pub width: u8,
|
||||
pub bank: RegBankIndex,
|
||||
pub toprc: RegClassIndex,
|
||||
pub count: u8,
|
||||
pub start: u8,
|
||||
pub subclasses: Vec<RegClassIndex>,
|
||||
}
|
||||
|
||||
impl RegClass {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
index: RegClassIndex,
|
||||
width: u8,
|
||||
bank: RegBankIndex,
|
||||
toprc: RegClassIndex,
|
||||
count: u8,
|
||||
start: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
index,
|
||||
width,
|
||||
bank,
|
||||
toprc,
|
||||
count,
|
||||
start,
|
||||
subclasses: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a bit-mask of subclasses, including self.
|
||||
pub fn subclass_mask(&self) -> u64 {
|
||||
let mut m = 1 << self.index.index();
|
||||
for rc in self.subclasses.iter() {
|
||||
m |= 1 << rc.index();
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
/// Compute a bit-mask of the register units allocated by this register class.
|
||||
pub fn mask(&self, bank_first_unit: u8) -> Vec<u32> {
|
||||
let mut u = (self.start + bank_first_unit) as usize;
|
||||
let mut out_mask = vec![0, 0, 0];
|
||||
for _ in 0..self.count {
|
||||
out_mask[u / 32] |= 1 << (u % 32);
|
||||
u += self.width as usize;
|
||||
}
|
||||
out_mask
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum RegClassProto {
|
||||
TopLevel(RegBankIndex),
|
||||
SubClass(RegClassIndex),
|
||||
}
|
||||
|
||||
pub(crate) struct RegClassBuilder {
|
||||
pub name: &'static str,
|
||||
pub width: u8,
|
||||
pub count: u8,
|
||||
pub start: u8,
|
||||
pub proto: RegClassProto,
|
||||
}
|
||||
|
||||
impl RegClassBuilder {
|
||||
pub fn new_toplevel(name: &'static str, bank: RegBankIndex) -> Self {
|
||||
Self {
|
||||
name,
|
||||
width: 1,
|
||||
count: 0,
|
||||
start: 0,
|
||||
proto: RegClassProto::TopLevel(bank),
|
||||
}
|
||||
}
|
||||
pub fn subclass_of(
|
||||
name: &'static str,
|
||||
parent_index: RegClassIndex,
|
||||
start: u8,
|
||||
stop: u8,
|
||||
) -> Self {
|
||||
assert!(stop >= start);
|
||||
Self {
|
||||
name,
|
||||
width: 0,
|
||||
count: stop - start,
|
||||
start,
|
||||
proto: RegClassProto::SubClass(parent_index),
|
||||
}
|
||||
}
|
||||
pub fn count(mut self, count: u8) -> Self {
|
||||
self.count = count;
|
||||
self
|
||||
}
|
||||
pub fn width(mut self, width: u8) -> Self {
|
||||
match self.proto {
|
||||
RegClassProto::TopLevel(_) => self.width = width,
|
||||
RegClassProto::SubClass(_) => panic!("Subclasses inherit their parent's width."),
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RegBankBuilder {
|
||||
pub name: &'static str,
|
||||
pub units: u8,
|
||||
pub names: Vec<&'static str>,
|
||||
pub prefix: &'static str,
|
||||
pub pressure_tracking: Option<bool>,
|
||||
pub pinned_reg: Option<u16>,
|
||||
}
|
||||
|
||||
impl RegBankBuilder {
|
||||
pub fn new(name: &'static str, prefix: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
units: 0,
|
||||
names: vec![],
|
||||
prefix,
|
||||
pressure_tracking: None,
|
||||
pinned_reg: None,
|
||||
}
|
||||
}
|
||||
pub fn units(mut self, units: u8) -> Self {
|
||||
self.units = units;
|
||||
self
|
||||
}
|
||||
pub fn names(mut self, names: Vec<&'static str>) -> Self {
|
||||
self.names = names;
|
||||
self
|
||||
}
|
||||
pub fn track_pressure(mut self, track: bool) -> Self {
|
||||
self.pressure_tracking = Some(track);
|
||||
self
|
||||
}
|
||||
pub fn pinned_reg(mut self, unit: u16) -> Self {
|
||||
assert!(unit < u16::from(self.units));
|
||||
self.pinned_reg = Some(unit);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct IsaRegsBuilder {
|
||||
pub banks: PrimaryMap<RegBankIndex, RegBank>,
|
||||
pub classes: PrimaryMap<RegClassIndex, RegClass>,
|
||||
}
|
||||
|
||||
impl IsaRegsBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
banks: PrimaryMap::new(),
|
||||
classes: PrimaryMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_bank(&mut self, builder: RegBankBuilder) -> RegBankIndex {
|
||||
let first_unit = if self.banks.is_empty() {
|
||||
0
|
||||
} else {
|
||||
let last = &self.banks.last().unwrap();
|
||||
let first_available_unit = (last.first_unit + last.units) as i8;
|
||||
let units = builder.units;
|
||||
let align = if units.is_power_of_two() {
|
||||
units
|
||||
} else {
|
||||
units.next_power_of_two()
|
||||
} as i8;
|
||||
(first_available_unit + align - 1) & -align
|
||||
} as u8;
|
||||
|
||||
self.banks.push(RegBank::new(
|
||||
builder.name,
|
||||
first_unit,
|
||||
builder.units,
|
||||
builder.names,
|
||||
builder.prefix,
|
||||
builder
|
||||
.pressure_tracking
|
||||
.expect("Pressure tracking must be explicitly set"),
|
||||
builder.pinned_reg,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn add_class(&mut self, builder: RegClassBuilder) -> RegClassIndex {
|
||||
let class_index = self.classes.next_key();
|
||||
|
||||
// Finish delayed construction of RegClass.
|
||||
let (bank, toprc, start, width) = match builder.proto {
|
||||
RegClassProto::TopLevel(bank_index) => {
|
||||
self.banks
|
||||
.get_mut(bank_index)
|
||||
.unwrap()
|
||||
.toprcs
|
||||
.push(class_index);
|
||||
(bank_index, class_index, builder.start, builder.width)
|
||||
}
|
||||
RegClassProto::SubClass(parent_class_index) => {
|
||||
assert!(builder.width == 0);
|
||||
let (bank, toprc, start, width) = {
|
||||
let parent = self.classes.get(parent_class_index).unwrap();
|
||||
(parent.bank, parent.toprc, parent.start, parent.width)
|
||||
};
|
||||
for reg_class in self.classes.values_mut() {
|
||||
if reg_class.toprc == toprc {
|
||||
reg_class.subclasses.push(class_index);
|
||||
}
|
||||
}
|
||||
let subclass_start = start + builder.start * width;
|
||||
(bank, toprc, subclass_start, width)
|
||||
}
|
||||
};
|
||||
|
||||
let reg_bank_units = self.banks.get(bank).unwrap().units;
|
||||
assert!(start < reg_bank_units);
|
||||
|
||||
let count = if builder.count != 0 {
|
||||
builder.count
|
||||
} else {
|
||||
reg_bank_units / width
|
||||
};
|
||||
|
||||
let reg_class = RegClass::new(builder.name, class_index, width, bank, toprc, count, start);
|
||||
self.classes.push(reg_class);
|
||||
|
||||
let reg_bank = self.banks.get_mut(bank).unwrap();
|
||||
reg_bank.classes.push(class_index);
|
||||
|
||||
class_index
|
||||
}
|
||||
|
||||
/// Checks that the set of register classes satisfies:
|
||||
///
|
||||
/// 1. Closed under intersection: The intersection of any two register
|
||||
/// classes in the set is either empty or identical to a member of the
|
||||
/// set.
|
||||
/// 2. There are no identical classes under different names.
|
||||
/// 3. Classes are sorted topologically such that all subclasses have a
|
||||
/// higher index that the superclass.
|
||||
pub fn build(self) -> IsaRegs {
|
||||
for reg_bank in self.banks.values() {
|
||||
for i1 in reg_bank.classes.iter() {
|
||||
for i2 in reg_bank.classes.iter() {
|
||||
if i1 >= i2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let rc1 = self.classes.get(*i1).unwrap();
|
||||
let rc2 = self.classes.get(*i2).unwrap();
|
||||
|
||||
let rc1_mask = rc1.mask(0);
|
||||
let rc2_mask = rc2.mask(0);
|
||||
|
||||
assert!(
|
||||
rc1.width != rc2.width || rc1_mask != rc2_mask,
|
||||
"no duplicates"
|
||||
);
|
||||
if rc1.width != rc2.width {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut intersect = Vec::new();
|
||||
for (a, b) in rc1_mask.iter().zip(rc2_mask.iter()) {
|
||||
intersect.push(a & b);
|
||||
}
|
||||
if intersect == vec![0; intersect.len()] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Classes must be topologically ordered, so the intersection can't be the
|
||||
// superclass.
|
||||
assert!(intersect != rc1_mask);
|
||||
|
||||
// If the intersection is the second one, then it must be a subclass.
|
||||
if intersect == rc2_mask {
|
||||
assert!(self
|
||||
.classes
|
||||
.get(*i1)
|
||||
.unwrap()
|
||||
.subclasses
|
||||
.iter()
|
||||
.any(|x| *x == *i2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.classes.len() <= constants::MAX_NUM_REG_CLASSES,
|
||||
"Too many register classes"
|
||||
);
|
||||
|
||||
let num_toplevel = self
|
||||
.classes
|
||||
.values()
|
||||
.filter(|x| x.toprc == x.index && self.banks.get(x.bank).unwrap().pressure_tracking)
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
num_toplevel <= constants::MAX_TRACKED_TOP_RCS,
|
||||
"Too many top-level register classes"
|
||||
);
|
||||
|
||||
IsaRegs::new(self.banks, self.classes)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct IsaRegs {
|
||||
pub banks: PrimaryMap<RegBankIndex, RegBank>,
|
||||
pub classes: PrimaryMap<RegClassIndex, RegClass>,
|
||||
}
|
||||
|
||||
impl IsaRegs {
|
||||
fn new(
|
||||
banks: PrimaryMap<RegBankIndex, RegBank>,
|
||||
classes: PrimaryMap<RegClassIndex, RegClass>,
|
||||
) -> Self {
|
||||
Self { banks, classes }
|
||||
}
|
||||
|
||||
pub fn class_by_name(&self, name: &str) -> RegClassIndex {
|
||||
self.classes
|
||||
.values()
|
||||
.find(|&class| class.name == name)
|
||||
.unwrap_or_else(|| panic!("register class {} not found", name))
|
||||
.index
|
||||
}
|
||||
|
||||
pub fn regunit_by_name(&self, class_index: RegClassIndex, name: &'static str) -> u8 {
|
||||
let bank_index = self.classes.get(class_index).unwrap().bank;
|
||||
self.banks.get(bank_index).unwrap().unit_by_name(name)
|
||||
}
|
||||
}
|
||||
@@ -150,14 +150,6 @@ impl SettingGroup {
|
||||
}
|
||||
panic!("Should have found bool setting by name.");
|
||||
}
|
||||
|
||||
pub fn predicate_by_name(&self, name: &'static str) -> SettingPredicateNumber {
|
||||
self.predicates
|
||||
.iter()
|
||||
.find(|pred| pred.name == name)
|
||||
.unwrap_or_else(|| panic!("unknown predicate {}", name))
|
||||
.number
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the basic information needed to track the specific parts of a setting when building
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use crate::cdsl::ast::{Def, DefIndex, DefPool, Var, VarIndex, VarPool};
|
||||
use crate::cdsl::typevar::{DerivedFunc, TypeSet, TypeVar};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::FromIterator;
|
||||
use crate::cdsl::typevar::TypeVar;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum Constraint {
|
||||
@@ -11,651 +7,4 @@ pub(crate) enum Constraint {
|
||||
/// 1) They have the same number of lanes
|
||||
/// 2) In a lane tv1 has at least as many bits as tv2.
|
||||
WiderOrEq(TypeVar, TypeVar),
|
||||
|
||||
/// Constraint specifying that two derived type vars must have the same runtime type.
|
||||
Eq(TypeVar, TypeVar),
|
||||
|
||||
/// Constraint specifying that a type var must belong to some typeset.
|
||||
InTypeset(TypeVar, TypeSet),
|
||||
}
|
||||
|
||||
impl Constraint {
|
||||
fn translate_with<F: Fn(&TypeVar) -> TypeVar>(&self, func: F) -> Constraint {
|
||||
match self {
|
||||
Constraint::WiderOrEq(lhs, rhs) => {
|
||||
let lhs = func(&lhs);
|
||||
let rhs = func(&rhs);
|
||||
Constraint::WiderOrEq(lhs, rhs)
|
||||
}
|
||||
Constraint::Eq(lhs, rhs) => {
|
||||
let lhs = func(&lhs);
|
||||
let rhs = func(&rhs);
|
||||
Constraint::Eq(lhs, rhs)
|
||||
}
|
||||
Constraint::InTypeset(tv, ts) => {
|
||||
let tv = func(&tv);
|
||||
Constraint::InTypeset(tv, ts.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new constraint by replacing type vars by their hashmap equivalent.
|
||||
fn translate_with_map(
|
||||
&self,
|
||||
original_to_own_typevar: &HashMap<&TypeVar, TypeVar>,
|
||||
) -> Constraint {
|
||||
self.translate_with(|tv| substitute(original_to_own_typevar, tv))
|
||||
}
|
||||
|
||||
/// Creates a new constraint by replacing type vars by their canonical equivalent.
|
||||
fn translate_with_env(&self, type_env: &TypeEnvironment) -> Constraint {
|
||||
self.translate_with(|tv| type_env.get_equivalent(tv))
|
||||
}
|
||||
|
||||
fn is_trivial(&self) -> bool {
|
||||
match self {
|
||||
Constraint::WiderOrEq(lhs, rhs) => {
|
||||
// Trivially true.
|
||||
if lhs == rhs {
|
||||
return true;
|
||||
}
|
||||
|
||||
let ts1 = lhs.get_typeset();
|
||||
let ts2 = rhs.get_typeset();
|
||||
|
||||
// Trivially true.
|
||||
if ts1.is_wider_or_equal(&ts2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Trivially false.
|
||||
if ts1.is_narrower(&ts2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Trivially false.
|
||||
if (&ts1.lanes & &ts2.lanes).is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.is_concrete()
|
||||
}
|
||||
Constraint::Eq(lhs, rhs) => lhs == rhs || self.is_concrete(),
|
||||
Constraint::InTypeset(_, _) => {
|
||||
// The way InTypeset are made, they would always be trivial if we were applying the
|
||||
// same logic as the Python code did, so ignore this.
|
||||
self.is_concrete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true iff all the referenced type vars are singletons.
|
||||
fn is_concrete(&self) -> bool {
|
||||
match self {
|
||||
Constraint::WiderOrEq(lhs, rhs) => {
|
||||
lhs.singleton_type().is_some() && rhs.singleton_type().is_some()
|
||||
}
|
||||
Constraint::Eq(lhs, rhs) => {
|
||||
lhs.singleton_type().is_some() && rhs.singleton_type().is_some()
|
||||
}
|
||||
Constraint::InTypeset(tv, _) => tv.singleton_type().is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
fn typevar_args(&self) -> Vec<&TypeVar> {
|
||||
match self {
|
||||
Constraint::WiderOrEq(lhs, rhs) => vec![lhs, rhs],
|
||||
Constraint::Eq(lhs, rhs) => vec![lhs, rhs],
|
||||
Constraint::InTypeset(tv, _) => vec![tv],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TypeEnvRank {
|
||||
Singleton = 5,
|
||||
Input = 4,
|
||||
Intermediate = 3,
|
||||
Output = 2,
|
||||
Temp = 1,
|
||||
Internal = 0,
|
||||
}
|
||||
|
||||
/// Class encapsulating the necessary bookkeeping for type inference.
|
||||
pub(crate) struct TypeEnvironment {
|
||||
vars: HashSet<VarIndex>,
|
||||
ranks: HashMap<TypeVar, TypeEnvRank>,
|
||||
equivalency_map: HashMap<TypeVar, TypeVar>,
|
||||
pub constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
impl TypeEnvironment {
|
||||
fn new() -> Self {
|
||||
TypeEnvironment {
|
||||
vars: HashSet::new(),
|
||||
ranks: HashMap::new(),
|
||||
equivalency_map: HashMap::new(),
|
||||
constraints: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn register(&mut self, var_index: VarIndex, var: &mut Var) {
|
||||
self.vars.insert(var_index);
|
||||
let rank = if var.is_input() {
|
||||
TypeEnvRank::Input
|
||||
} else if var.is_intermediate() {
|
||||
TypeEnvRank::Intermediate
|
||||
} else if var.is_output() {
|
||||
TypeEnvRank::Output
|
||||
} else {
|
||||
assert!(var.is_temp());
|
||||
TypeEnvRank::Temp
|
||||
};
|
||||
self.ranks.insert(var.get_or_create_typevar(), rank);
|
||||
}
|
||||
|
||||
fn add_constraint(&mut self, constraint: Constraint) {
|
||||
if self.constraints.iter().any(|item| *item == constraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check extra conditions for InTypeset constraints.
|
||||
if let Constraint::InTypeset(tv, _) = &constraint {
|
||||
assert!(
|
||||
tv.base.is_none(),
|
||||
"type variable is {:?}, while expecting none",
|
||||
tv
|
||||
);
|
||||
assert!(
|
||||
tv.name.starts_with("typeof_"),
|
||||
"Name \"{}\" should start with \"typeof_\"",
|
||||
tv.name
|
||||
);
|
||||
}
|
||||
|
||||
self.constraints.push(constraint);
|
||||
}
|
||||
|
||||
/// Returns the canonical representative of the equivalency class of the given argument, or
|
||||
/// duplicates it if it's not there yet.
|
||||
pub fn get_equivalent(&self, tv: &TypeVar) -> TypeVar {
|
||||
let mut tv = tv;
|
||||
while let Some(found) = self.equivalency_map.get(tv) {
|
||||
tv = found;
|
||||
}
|
||||
match &tv.base {
|
||||
Some(parent) => self
|
||||
.get_equivalent(&parent.type_var)
|
||||
.derived(parent.derived_func),
|
||||
None => tv.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the rank of tv in the partial order:
|
||||
/// - TVs directly associated with a Var get their rank from the Var (see register()).
|
||||
/// - Internally generated non-derived TVs implicitly get the lowest rank (0).
|
||||
/// - Derived variables get their rank from their free typevar.
|
||||
/// - Singletons have the highest rank.
|
||||
/// - TVs associated with vars in a source pattern have a higher rank than TVs associated with
|
||||
/// temporary vars.
|
||||
fn rank(&self, tv: &TypeVar) -> u8 {
|
||||
let actual_tv = match tv.base {
|
||||
Some(_) => tv.free_typevar(),
|
||||
None => Some(tv.clone()),
|
||||
};
|
||||
|
||||
let rank = match actual_tv {
|
||||
Some(actual_tv) => match self.ranks.get(&actual_tv) {
|
||||
Some(rank) => Some(*rank),
|
||||
None => {
|
||||
assert!(
|
||||
!actual_tv.name.starts_with("typeof_"),
|
||||
"variable {} should be explicitly ranked",
|
||||
actual_tv.name
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let rank = match rank {
|
||||
Some(rank) => rank,
|
||||
None => {
|
||||
if tv.singleton_type().is_some() {
|
||||
TypeEnvRank::Singleton
|
||||
} else {
|
||||
TypeEnvRank::Internal
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rank as u8
|
||||
}
|
||||
|
||||
/// Record the fact that the free tv1 is part of the same equivalence class as tv2. The
|
||||
/// canonical representative of the merged class is tv2's canonical representative.
|
||||
fn record_equivalent(&mut self, tv1: TypeVar, tv2: TypeVar) {
|
||||
assert!(tv1.base.is_none());
|
||||
assert!(self.get_equivalent(&tv1) == tv1);
|
||||
if let Some(tv2_base) = &tv2.base {
|
||||
// Ensure there are no cycles.
|
||||
assert!(self.get_equivalent(&tv2_base.type_var) != tv1);
|
||||
}
|
||||
self.equivalency_map.insert(tv1, tv2);
|
||||
}
|
||||
|
||||
/// Get the free typevars in the current type environment.
|
||||
pub fn free_typevars(&self, var_pool: &mut VarPool) -> Vec<TypeVar> {
|
||||
let mut typevars = Vec::new();
|
||||
typevars.extend(self.equivalency_map.keys().cloned());
|
||||
typevars.extend(
|
||||
self.vars
|
||||
.iter()
|
||||
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
|
||||
);
|
||||
|
||||
let set: HashSet<TypeVar> = HashSet::from_iter(
|
||||
typevars
|
||||
.iter()
|
||||
.map(|tv| self.get_equivalent(tv).free_typevar())
|
||||
.filter(|opt_tv| {
|
||||
// Filter out singleton types.
|
||||
opt_tv.is_some()
|
||||
})
|
||||
.map(|tv| tv.unwrap()),
|
||||
);
|
||||
Vec::from_iter(set)
|
||||
}
|
||||
|
||||
/// Normalize by collapsing any roots that don't correspond to a concrete type var AND have a
|
||||
/// single type var derived from them or equivalent to them.
|
||||
///
|
||||
/// e.g. if we have a root of the tree that looks like:
|
||||
///
|
||||
/// typeof_a typeof_b
|
||||
/// \\ /
|
||||
/// typeof_x
|
||||
/// |
|
||||
/// half_width(1)
|
||||
/// |
|
||||
/// 1
|
||||
///
|
||||
/// we want to collapse the linear path between 1 and typeof_x. The resulting graph is:
|
||||
///
|
||||
/// typeof_a typeof_b
|
||||
/// \\ /
|
||||
/// typeof_x
|
||||
fn normalize(&mut self, var_pool: &mut VarPool) {
|
||||
let source_tvs: HashSet<TypeVar> = HashSet::from_iter(
|
||||
self.vars
|
||||
.iter()
|
||||
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
|
||||
);
|
||||
|
||||
let mut children: HashMap<TypeVar, HashSet<TypeVar>> = HashMap::new();
|
||||
|
||||
// Insert all the parents found by the derivation relationship.
|
||||
for type_var in self.equivalency_map.values() {
|
||||
if type_var.base.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parent_tv = type_var.free_typevar();
|
||||
if parent_tv.is_none() {
|
||||
// Ignore this type variable, it's a singleton.
|
||||
continue;
|
||||
}
|
||||
let parent_tv = parent_tv.unwrap();
|
||||
|
||||
children
|
||||
.entry(parent_tv)
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(type_var.clone());
|
||||
}
|
||||
|
||||
// Insert all the explicit equivalency links.
|
||||
for (equivalent_tv, canon_tv) in self.equivalency_map.iter() {
|
||||
children
|
||||
.entry(canon_tv.clone())
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(equivalent_tv.clone());
|
||||
}
|
||||
|
||||
// Remove links that are straight paths up to typevar of variables.
|
||||
for free_root in self.free_typevars(var_pool) {
|
||||
let mut root = &free_root;
|
||||
while !source_tvs.contains(&root)
|
||||
&& children.contains_key(&root)
|
||||
&& children.get(&root).unwrap().len() == 1
|
||||
{
|
||||
let child = children.get(&root).unwrap().iter().next().unwrap();
|
||||
assert_eq!(self.equivalency_map[child], root.clone());
|
||||
self.equivalency_map.remove(child);
|
||||
root = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a clean type environment from self, that only mentions type vars associated with
|
||||
/// real variables.
|
||||
fn extract(self, var_pool: &mut VarPool) -> TypeEnvironment {
|
||||
let vars_tv: HashSet<TypeVar> = HashSet::from_iter(
|
||||
self.vars
|
||||
.iter()
|
||||
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
|
||||
);
|
||||
|
||||
let mut new_equivalency_map: HashMap<TypeVar, TypeVar> = HashMap::new();
|
||||
for tv in &vars_tv {
|
||||
let canon_tv = self.get_equivalent(tv);
|
||||
if *tv != canon_tv {
|
||||
new_equivalency_map.insert(tv.clone(), canon_tv.clone());
|
||||
}
|
||||
|
||||
// Sanity check: the translated type map should only refer to real variables.
|
||||
assert!(vars_tv.contains(tv));
|
||||
let canon_free_tv = canon_tv.free_typevar();
|
||||
assert!(canon_free_tv.is_none() || vars_tv.contains(&canon_free_tv.unwrap()));
|
||||
}
|
||||
|
||||
let mut new_constraints: HashSet<Constraint> = HashSet::new();
|
||||
for constraint in &self.constraints {
|
||||
let constraint = constraint.translate_with_env(&self);
|
||||
if constraint.is_trivial() || new_constraints.contains(&constraint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanity check: translated constraints should refer only to real variables.
|
||||
for arg in constraint.typevar_args() {
|
||||
let arg_free_tv = arg.free_typevar();
|
||||
assert!(arg_free_tv.is_none() || vars_tv.contains(&arg_free_tv.unwrap()));
|
||||
}
|
||||
|
||||
new_constraints.insert(constraint);
|
||||
}
|
||||
|
||||
TypeEnvironment {
|
||||
vars: self.vars,
|
||||
ranks: self.ranks,
|
||||
equivalency_map: new_equivalency_map,
|
||||
constraints: Vec::from_iter(new_constraints),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces an external type variable according to the following rules:
|
||||
/// - if a local copy is present in the map, return it.
|
||||
/// - or if it's derived, create a local derived one that recursively substitutes the parent.
|
||||
/// - or return itself.
|
||||
fn substitute(map: &HashMap<&TypeVar, TypeVar>, external_type_var: &TypeVar) -> TypeVar {
|
||||
match map.get(&external_type_var) {
|
||||
Some(own_type_var) => own_type_var.clone(),
|
||||
None => match &external_type_var.base {
|
||||
Some(parent) => {
|
||||
let parent_substitute = substitute(map, &parent.type_var);
|
||||
TypeVar::derived(&parent_substitute, parent.derived_func)
|
||||
}
|
||||
None => external_type_var.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize a (potentially derived) typevar using the following rules:
|
||||
///
|
||||
/// - vector and width derived functions commute
|
||||
/// {HALF,DOUBLE}VECTOR({HALF,DOUBLE}WIDTH(base)) ->
|
||||
/// {HALF,DOUBLE}WIDTH({HALF,DOUBLE}VECTOR(base))
|
||||
///
|
||||
/// - half/double pairs collapse
|
||||
/// {HALF,DOUBLE}WIDTH({DOUBLE,HALF}WIDTH(base)) -> base
|
||||
/// {HALF,DOUBLE}VECTOR({DOUBLE,HALF}VECTOR(base)) -> base
|
||||
fn canonicalize_derivations(tv: TypeVar) -> TypeVar {
|
||||
let base = match &tv.base {
|
||||
Some(base) => base,
|
||||
None => return tv,
|
||||
};
|
||||
|
||||
let derived_func = base.derived_func;
|
||||
|
||||
if let Some(base_base) = &base.type_var.base {
|
||||
let base_base_tv = &base_base.type_var;
|
||||
match (derived_func, base_base.derived_func) {
|
||||
(DerivedFunc::HalfWidth, DerivedFunc::DoubleWidth)
|
||||
| (DerivedFunc::DoubleWidth, DerivedFunc::HalfWidth)
|
||||
| (DerivedFunc::HalfVector, DerivedFunc::DoubleVector)
|
||||
| (DerivedFunc::DoubleVector, DerivedFunc::HalfVector) => {
|
||||
// Cancelling bijective transformations. This doesn't hide any overflow issues
|
||||
// since derived type sets are checked upon derivaion, and base typesets are only
|
||||
// allowed to shrink.
|
||||
return canonicalize_derivations(base_base_tv.clone());
|
||||
}
|
||||
(DerivedFunc::HalfWidth, DerivedFunc::HalfVector)
|
||||
| (DerivedFunc::HalfWidth, DerivedFunc::DoubleVector)
|
||||
| (DerivedFunc::DoubleWidth, DerivedFunc::DoubleVector)
|
||||
| (DerivedFunc::DoubleWidth, DerivedFunc::HalfVector) => {
|
||||
// Arbitrarily put WIDTH derivations before VECTOR derivations, since they commute.
|
||||
return canonicalize_derivations(
|
||||
base_base_tv
|
||||
.derived(derived_func)
|
||||
.derived(base_base.derived_func),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
canonicalize_derivations(base.type_var.clone()).derived(derived_func)
|
||||
}
|
||||
|
||||
/// Given typevars tv1 and tv2 (which could be derived from one another), constrain their typesets
|
||||
/// to be the same. When one is derived from the other, repeat the constrain process until
|
||||
/// a fixed point is reached.
|
||||
fn constrain_fixpoint(tv1: &TypeVar, tv2: &TypeVar) {
|
||||
loop {
|
||||
let old_tv1_ts = tv1.get_typeset().clone();
|
||||
tv2.constrain_types(tv1.clone());
|
||||
if tv1.get_typeset() == old_tv1_ts {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let old_tv2_ts = tv2.get_typeset();
|
||||
tv1.constrain_types(tv2.clone());
|
||||
// The above loop should ensure that all reference cycles have been handled.
|
||||
assert!(old_tv2_ts == tv2.get_typeset());
|
||||
}
|
||||
|
||||
/// Unify tv1 and tv2 in the given type environment. tv1 must have a rank greater or equal to tv2's
|
||||
/// one, modulo commutations.
|
||||
fn unify(tv1: &TypeVar, tv2: &TypeVar, type_env: &mut TypeEnvironment) -> Result<(), String> {
|
||||
let tv1 = canonicalize_derivations(type_env.get_equivalent(tv1));
|
||||
let tv2 = canonicalize_derivations(type_env.get_equivalent(tv2));
|
||||
|
||||
if tv1 == tv2 {
|
||||
// Already unified.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if type_env.rank(&tv2) < type_env.rank(&tv1) {
|
||||
// Make sure tv1 always has the smallest rank, since real variables have the higher rank
|
||||
// and we want them to be the canonical representatives of their equivalency classes.
|
||||
return unify(&tv2, &tv1, type_env);
|
||||
}
|
||||
|
||||
constrain_fixpoint(&tv1, &tv2);
|
||||
|
||||
if tv1.get_typeset().size() == 0 || tv2.get_typeset().size() == 0 {
|
||||
return Err(format!(
|
||||
"Error: empty type created when unifying {} and {}",
|
||||
tv1.name, tv2.name
|
||||
));
|
||||
}
|
||||
|
||||
let base = match &tv1.base {
|
||||
Some(base) => base,
|
||||
None => {
|
||||
type_env.record_equivalent(tv1, tv2);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(inverse) = base.derived_func.inverse() {
|
||||
return unify(&base.type_var, &tv2.derived(inverse), type_env);
|
||||
}
|
||||
|
||||
type_env.add_constraint(Constraint::Eq(tv1, tv2));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform type inference on one Def in the current type environment and return an updated type
|
||||
/// environment or error.
|
||||
///
|
||||
/// At a high level this works by creating fresh copies of each formal type var in the Def's
|
||||
/// instruction's signature, and unifying the formal typevar with the corresponding actual typevar.
|
||||
fn infer_definition(
|
||||
def: &Def,
|
||||
var_pool: &mut VarPool,
|
||||
type_env: TypeEnvironment,
|
||||
last_type_index: &mut usize,
|
||||
) -> Result<TypeEnvironment, String> {
|
||||
let apply = &def.apply;
|
||||
let inst = &apply.inst;
|
||||
|
||||
let mut type_env = type_env;
|
||||
let free_formal_tvs = inst.all_typevars();
|
||||
|
||||
let mut original_to_own_typevar: HashMap<&TypeVar, TypeVar> = HashMap::new();
|
||||
for &tv in &free_formal_tvs {
|
||||
assert!(original_to_own_typevar
|
||||
.insert(
|
||||
tv,
|
||||
TypeVar::copy_from(tv, format!("own_{}", last_type_index))
|
||||
)
|
||||
.is_none());
|
||||
*last_type_index += 1;
|
||||
}
|
||||
|
||||
// Update the mapping with any explicity bound type vars:
|
||||
for (i, value_type) in apply.value_types.iter().enumerate() {
|
||||
let singleton = TypeVar::new_singleton(value_type.clone());
|
||||
assert!(original_to_own_typevar
|
||||
.insert(free_formal_tvs[i], singleton)
|
||||
.is_some());
|
||||
}
|
||||
|
||||
// Get fresh copies for each typevar in the signature (both free and derived).
|
||||
let mut formal_tvs = Vec::new();
|
||||
formal_tvs.extend(inst.value_results.iter().map(|&i| {
|
||||
substitute(
|
||||
&original_to_own_typevar,
|
||||
inst.operands_out[i].type_var().unwrap(),
|
||||
)
|
||||
}));
|
||||
formal_tvs.extend(inst.value_opnums.iter().map(|&i| {
|
||||
substitute(
|
||||
&original_to_own_typevar,
|
||||
inst.operands_in[i].type_var().unwrap(),
|
||||
)
|
||||
}));
|
||||
|
||||
// Get the list of actual vars.
|
||||
let mut actual_vars = Vec::new();
|
||||
actual_vars.extend(inst.value_results.iter().map(|&i| def.defined_vars[i]));
|
||||
actual_vars.extend(
|
||||
inst.value_opnums
|
||||
.iter()
|
||||
.map(|&i| apply.args[i].unwrap_var()),
|
||||
);
|
||||
|
||||
// Get the list of the actual TypeVars.
|
||||
let mut actual_tvs = Vec::new();
|
||||
for var_index in actual_vars {
|
||||
let var = var_pool.get_mut(var_index);
|
||||
type_env.register(var_index, var);
|
||||
actual_tvs.push(var.get_or_create_typevar());
|
||||
}
|
||||
|
||||
// Make sure we start unifying with the control type variable first, by putting it at the
|
||||
// front of both vectors.
|
||||
if let Some(poly) = &inst.polymorphic_info {
|
||||
let own_ctrl_tv = &original_to_own_typevar[&poly.ctrl_typevar];
|
||||
let ctrl_index = formal_tvs.iter().position(|tv| tv == own_ctrl_tv).unwrap();
|
||||
if ctrl_index != 0 {
|
||||
formal_tvs.swap(0, ctrl_index);
|
||||
actual_tvs.swap(0, ctrl_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Unify each actual type variable with the corresponding formal type variable.
|
||||
for (actual_tv, formal_tv) in actual_tvs.iter().zip(&formal_tvs) {
|
||||
if let Err(msg) = unify(actual_tv, formal_tv, &mut type_env) {
|
||||
return Err(format!(
|
||||
"fail ti on {} <: {}: {}",
|
||||
actual_tv.name, formal_tv.name, msg
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Add any instruction specific constraints.
|
||||
for constraint in &inst.constraints {
|
||||
type_env.add_constraint(constraint.translate_with_map(&original_to_own_typevar));
|
||||
}
|
||||
|
||||
Ok(type_env)
|
||||
}
|
||||
|
||||
/// Perform type inference on an transformation. Return an updated type environment or error.
|
||||
pub(crate) fn infer_transform(
|
||||
src: DefIndex,
|
||||
dst: &[DefIndex],
|
||||
def_pool: &DefPool,
|
||||
var_pool: &mut VarPool,
|
||||
) -> Result<TypeEnvironment, String> {
|
||||
let mut type_env = TypeEnvironment::new();
|
||||
let mut last_type_index = 0;
|
||||
|
||||
// Execute type inference on the source pattern.
|
||||
type_env = infer_definition(def_pool.get(src), var_pool, type_env, &mut last_type_index)
|
||||
.map_err(|err| format!("In src pattern: {}", err))?;
|
||||
|
||||
// Collect the type sets once after applying the source patterm; we'll compare the typesets
|
||||
// after we've also considered the destination pattern, and will emit supplementary InTypeset
|
||||
// checks if they don't match.
|
||||
let src_typesets = type_env
|
||||
.vars
|
||||
.iter()
|
||||
.map(|&var_index| {
|
||||
let var = var_pool.get_mut(var_index);
|
||||
let tv = type_env.get_equivalent(&var.get_or_create_typevar());
|
||||
(var_index, tv.get_typeset())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Execute type inference on the destination pattern.
|
||||
for (i, &def_index) in dst.iter().enumerate() {
|
||||
let def = def_pool.get(def_index);
|
||||
type_env = infer_definition(def, var_pool, type_env, &mut last_type_index)
|
||||
.map_err(|err| format!("line {}: {}", i, err))?;
|
||||
}
|
||||
|
||||
for (var_index, src_typeset) in src_typesets {
|
||||
let var = var_pool.get(var_index);
|
||||
if !var.has_free_typevar() {
|
||||
continue;
|
||||
}
|
||||
let tv = type_env.get_equivalent(&var.get_typevar().unwrap());
|
||||
let new_typeset = tv.get_typeset();
|
||||
assert!(
|
||||
new_typeset.is_subset(&src_typeset),
|
||||
"type sets can only get narrower"
|
||||
);
|
||||
if new_typeset != src_typeset {
|
||||
type_env.add_constraint(Constraint::InTypeset(tv.clone(), new_typeset.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
type_env.normalize(var_pool);
|
||||
|
||||
Ok(type_env.extract(var_pool))
|
||||
}
|
||||
|
||||
@@ -237,20 +237,6 @@ impl LaneType {
|
||||
ValueType::Vector(VectorType::new(self, lanes.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_float(self) -> bool {
|
||||
match self {
|
||||
LaneType::Float(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_int(self) -> bool {
|
||||
match self {
|
||||
LaneType::Int(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LaneType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::hash;
|
||||
use std::iter::FromIterator;
|
||||
@@ -269,52 +269,6 @@ impl TypeVar {
|
||||
pub fn merge_lanes(&self) -> TypeVar {
|
||||
self.derived(DerivedFunc::MergeLanes)
|
||||
}
|
||||
|
||||
/// Constrain the range of types this variable can assume to a subset of those in the typeset
|
||||
/// ts.
|
||||
/// May mutate itself if it's not derived, or its parent if it is.
|
||||
pub fn constrain_types_by_ts(&self, type_set: TypeSet) {
|
||||
match &self.base {
|
||||
Some(base) => {
|
||||
base.type_var
|
||||
.constrain_types_by_ts(type_set.preimage(base.derived_func));
|
||||
}
|
||||
None => {
|
||||
self.content
|
||||
.borrow_mut()
|
||||
.type_set
|
||||
.inplace_intersect_with(&type_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constrain the range of types this variable can assume to a subset of those `other` can
|
||||
/// assume.
|
||||
/// May mutate itself if it's not derived, or its parent if it is.
|
||||
pub fn constrain_types(&self, other: TypeVar) {
|
||||
if self == &other {
|
||||
return;
|
||||
}
|
||||
self.constrain_types_by_ts(other.get_typeset());
|
||||
}
|
||||
|
||||
/// Get a Rust expression that computes the type of this type variable.
|
||||
pub fn to_rust_code(&self) -> String {
|
||||
match &self.base {
|
||||
Some(base) => format!(
|
||||
"{}.{}().unwrap()",
|
||||
base.type_var.to_rust_code(),
|
||||
base.derived_func.name()
|
||||
),
|
||||
None => {
|
||||
if let Some(singleton) = self.singleton_type() {
|
||||
singleton.rust_name()
|
||||
} else {
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TypeVar> for &TypeVar {
|
||||
@@ -392,19 +346,6 @@ impl DerivedFunc {
|
||||
DerivedFunc::MergeLanes => "merge_lanes",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the inverse function of this one, if it is a bijection.
|
||||
pub fn inverse(self) -> Option<DerivedFunc> {
|
||||
match self {
|
||||
DerivedFunc::HalfWidth => Some(DerivedFunc::DoubleWidth),
|
||||
DerivedFunc::DoubleWidth => Some(DerivedFunc::HalfWidth),
|
||||
DerivedFunc::HalfVector => Some(DerivedFunc::DoubleVector),
|
||||
DerivedFunc::DoubleVector => Some(DerivedFunc::HalfVector),
|
||||
DerivedFunc::MergeLanes => Some(DerivedFunc::SplitLanes),
|
||||
DerivedFunc::SplitLanes => Some(DerivedFunc::MergeLanes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
@@ -594,94 +535,6 @@ impl TypeSet {
|
||||
assert_eq!(types.len(), 1);
|
||||
types.remove(0)
|
||||
}
|
||||
|
||||
/// Return the inverse image of self across the derived function func.
|
||||
fn preimage(&self, func: DerivedFunc) -> TypeSet {
|
||||
if self.size() == 0 {
|
||||
// The inverse of the empty set is itself.
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
match func {
|
||||
DerivedFunc::LaneOf => {
|
||||
let mut copy = self.clone();
|
||||
copy.lanes =
|
||||
NumSet::from_iter((0..=MAX_LANES.trailing_zeros()).map(|i| u16::pow(2, i)));
|
||||
copy
|
||||
}
|
||||
DerivedFunc::AsBool => {
|
||||
let mut copy = self.clone();
|
||||
if self.bools.contains(&1) {
|
||||
copy.ints = NumSet::from_iter(vec![8, 16, 32, 64, 128]);
|
||||
copy.floats = NumSet::from_iter(vec![32, 64]);
|
||||
} else {
|
||||
copy.ints = &self.bools - &NumSet::from_iter(vec![1]);
|
||||
copy.floats = &self.bools & &NumSet::from_iter(vec![32, 64]);
|
||||
// If b1 is not in our typeset, than lanes=1 cannot be in the pre-image, as
|
||||
// as_bool() of scalars is always b1.
|
||||
copy.lanes = &self.lanes - &NumSet::from_iter(vec![1]);
|
||||
}
|
||||
copy
|
||||
}
|
||||
DerivedFunc::HalfWidth => self.double_width(),
|
||||
DerivedFunc::DoubleWidth => self.half_width(),
|
||||
DerivedFunc::HalfVector => self.double_vector(),
|
||||
DerivedFunc::DoubleVector => self.half_vector(),
|
||||
DerivedFunc::SplitLanes => self.double_width().half_vector(),
|
||||
DerivedFunc::MergeLanes => self.half_width().double_vector(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inplace_intersect_with(&mut self, other: &TypeSet) {
|
||||
self.lanes = &self.lanes & &other.lanes;
|
||||
self.ints = &self.ints & &other.ints;
|
||||
self.floats = &self.floats & &other.floats;
|
||||
self.bools = &self.bools & &other.bools;
|
||||
self.refs = &self.refs & &other.refs;
|
||||
|
||||
let mut new_specials = Vec::new();
|
||||
for spec in &self.specials {
|
||||
if let Some(spec) = other.specials.iter().find(|&other_spec| other_spec == spec) {
|
||||
new_specials.push(*spec);
|
||||
}
|
||||
}
|
||||
self.specials = new_specials;
|
||||
}
|
||||
|
||||
pub fn is_subset(&self, other: &TypeSet) -> bool {
|
||||
self.lanes.is_subset(&other.lanes)
|
||||
&& self.ints.is_subset(&other.ints)
|
||||
&& self.floats.is_subset(&other.floats)
|
||||
&& self.bools.is_subset(&other.bools)
|
||||
&& self.refs.is_subset(&other.refs)
|
||||
&& {
|
||||
let specials: HashSet<SpecialType> = HashSet::from_iter(self.specials.clone());
|
||||
let other_specials = HashSet::from_iter(other.specials.clone());
|
||||
specials.is_subset(&other_specials)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_wider_or_equal(&self, other: &TypeSet) -> bool {
|
||||
set_wider_or_equal(&self.ints, &other.ints)
|
||||
&& set_wider_or_equal(&self.floats, &other.floats)
|
||||
&& set_wider_or_equal(&self.bools, &other.bools)
|
||||
&& set_wider_or_equal(&self.refs, &other.refs)
|
||||
}
|
||||
|
||||
pub fn is_narrower(&self, other: &TypeSet) -> bool {
|
||||
set_narrower(&self.ints, &other.ints)
|
||||
&& set_narrower(&self.floats, &other.floats)
|
||||
&& set_narrower(&self.bools, &other.bools)
|
||||
&& set_narrower(&self.refs, &other.refs)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_wider_or_equal(s1: &NumSet, s2: &NumSet) -> bool {
|
||||
!s1.is_empty() && !s2.is_empty() && s1.iter().min() >= s2.iter().max()
|
||||
}
|
||||
|
||||
fn set_narrower(s1: &NumSet, s2: &NumSet) -> bool {
|
||||
!s1.is_empty() && !s2.is_empty() && s1.iter().min() < s2.iter().max()
|
||||
}
|
||||
|
||||
impl fmt::Debug for TypeSet {
|
||||
@@ -806,18 +659,6 @@ impl TypeSetBuilder {
|
||||
self.specials,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn all() -> TypeSet {
|
||||
TypeSetBuilder::new()
|
||||
.ints(Interval::All)
|
||||
.floats(Interval::All)
|
||||
.bools(Interval::All)
|
||||
.refs(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.specials(ValueType::all_special_types().collect())
|
||||
.includes_scalars(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -1054,136 +895,6 @@ fn test_forward_images() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backward_images() {
|
||||
let empty_set = TypeSetBuilder::new().build();
|
||||
|
||||
// LaneOf.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..1)
|
||||
.ints(8..8)
|
||||
.floats(32..32)
|
||||
.build()
|
||||
.preimage(DerivedFunc::LaneOf),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(Interval::All)
|
||||
.ints(8..8)
|
||||
.floats(32..32)
|
||||
.build()
|
||||
);
|
||||
assert_eq!(empty_set.preimage(DerivedFunc::LaneOf), empty_set);
|
||||
|
||||
// AsBool.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..4)
|
||||
.bools(1..128)
|
||||
.build()
|
||||
.preimage(DerivedFunc::AsBool),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..4)
|
||||
.ints(Interval::All)
|
||||
.bools(Interval::All)
|
||||
.floats(Interval::All)
|
||||
.build()
|
||||
);
|
||||
|
||||
// Double vector.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..1)
|
||||
.ints(8..8)
|
||||
.build()
|
||||
.preimage(DerivedFunc::DoubleVector)
|
||||
.size(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..16)
|
||||
.ints(8..16)
|
||||
.floats(32..32)
|
||||
.build()
|
||||
.preimage(DerivedFunc::DoubleVector),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..8)
|
||||
.ints(8..16)
|
||||
.floats(32..32)
|
||||
.build(),
|
||||
);
|
||||
|
||||
// Half vector.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(256..256)
|
||||
.ints(8..8)
|
||||
.build()
|
||||
.preimage(DerivedFunc::HalfVector)
|
||||
.size(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(64..128)
|
||||
.bools(1..32)
|
||||
.build()
|
||||
.preimage(DerivedFunc::HalfVector),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(128..256)
|
||||
.bools(1..32)
|
||||
.build(),
|
||||
);
|
||||
|
||||
// Half width.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.ints(128..128)
|
||||
.floats(64..64)
|
||||
.bools(128..128)
|
||||
.build()
|
||||
.preimage(DerivedFunc::HalfWidth)
|
||||
.size(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(64..256)
|
||||
.bools(1..64)
|
||||
.build()
|
||||
.preimage(DerivedFunc::HalfWidth),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(64..256)
|
||||
.bools(16..128)
|
||||
.build(),
|
||||
);
|
||||
|
||||
// Double width.
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.ints(8..8)
|
||||
.floats(32..32)
|
||||
.bools(1..8)
|
||||
.build()
|
||||
.preimage(DerivedFunc::DoubleWidth)
|
||||
.size(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..16)
|
||||
.ints(8..16)
|
||||
.floats(32..64)
|
||||
.build()
|
||||
.preimage(DerivedFunc::DoubleWidth),
|
||||
TypeSetBuilder::new()
|
||||
.simd_lanes(1..16)
|
||||
.ints(8..8)
|
||||
.floats(32..32)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_typeset_singleton_panic_nonsingleton_types() {
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
use crate::cdsl::ast::{
|
||||
Apply, BlockPool, ConstPool, DefIndex, DefPool, DummyDef, DummyExpr, Expr, PatternPosition,
|
||||
VarIndex, VarPool,
|
||||
};
|
||||
use crate::cdsl::instructions::Instruction;
|
||||
use crate::cdsl::type_inference::{infer_transform, TypeEnvironment};
|
||||
use crate::cdsl::typevar::TypeVar;
|
||||
|
||||
use cranelift_entity::{entity_impl, PrimaryMap};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
/// An instruction transformation consists of a source and destination pattern.
|
||||
///
|
||||
/// Patterns are expressed in *register transfer language* as tuples of Def or Expr nodes. A
|
||||
/// pattern may optionally have a sequence of TypeConstraints, that additionally limit the set of
|
||||
/// cases when it applies.
|
||||
///
|
||||
/// The source pattern can contain only a single instruction.
|
||||
pub(crate) struct Transform {
|
||||
pub src: DefIndex,
|
||||
pub dst: Vec<DefIndex>,
|
||||
pub var_pool: VarPool,
|
||||
pub def_pool: DefPool,
|
||||
pub block_pool: BlockPool,
|
||||
pub const_pool: ConstPool,
|
||||
pub type_env: TypeEnvironment,
|
||||
}
|
||||
|
||||
type SymbolTable = HashMap<String, VarIndex>;
|
||||
|
||||
impl Transform {
|
||||
fn new(src: DummyDef, dst: Vec<DummyDef>) -> Self {
|
||||
let mut var_pool = VarPool::new();
|
||||
let mut def_pool = DefPool::new();
|
||||
let mut block_pool = BlockPool::new();
|
||||
let mut const_pool = ConstPool::new();
|
||||
|
||||
let mut input_vars: Vec<VarIndex> = Vec::new();
|
||||
let mut defined_vars: Vec<VarIndex> = Vec::new();
|
||||
|
||||
// Maps variable names to our own Var copies.
|
||||
let mut symbol_table: SymbolTable = SymbolTable::new();
|
||||
|
||||
// Rewrite variables in src and dst using our own copies.
|
||||
let src = rewrite_def_list(
|
||||
PatternPosition::Source,
|
||||
vec![src],
|
||||
&mut symbol_table,
|
||||
&mut input_vars,
|
||||
&mut defined_vars,
|
||||
&mut var_pool,
|
||||
&mut def_pool,
|
||||
&mut block_pool,
|
||||
&mut const_pool,
|
||||
)[0];
|
||||
|
||||
let num_src_inputs = input_vars.len();
|
||||
|
||||
let dst = rewrite_def_list(
|
||||
PatternPosition::Destination,
|
||||
dst,
|
||||
&mut symbol_table,
|
||||
&mut input_vars,
|
||||
&mut defined_vars,
|
||||
&mut var_pool,
|
||||
&mut def_pool,
|
||||
&mut block_pool,
|
||||
&mut const_pool,
|
||||
);
|
||||
|
||||
// Sanity checks.
|
||||
for &var_index in &input_vars {
|
||||
assert!(
|
||||
var_pool.get(var_index).is_input(),
|
||||
"'{:?}' used as both input and def",
|
||||
var_pool.get(var_index)
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
input_vars.len() == num_src_inputs,
|
||||
"extra input vars in dst pattern: {:?}",
|
||||
input_vars
|
||||
.iter()
|
||||
.map(|&i| var_pool.get(i))
|
||||
.skip(num_src_inputs)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Perform type inference and cleanup.
|
||||
let type_env = infer_transform(src, &dst, &def_pool, &mut var_pool).unwrap();
|
||||
|
||||
// Sanity check: the set of inferred free type variables should be a subset of the type
|
||||
// variables corresponding to Vars appearing in the source pattern.
|
||||
{
|
||||
let free_typevars: HashSet<TypeVar> =
|
||||
HashSet::from_iter(type_env.free_typevars(&mut var_pool));
|
||||
let src_tvs = HashSet::from_iter(
|
||||
input_vars
|
||||
.clone()
|
||||
.iter()
|
||||
.chain(
|
||||
defined_vars
|
||||
.iter()
|
||||
.filter(|&&var_index| !var_pool.get(var_index).is_temp()),
|
||||
)
|
||||
.map(|&var_index| var_pool.get(var_index).get_typevar())
|
||||
.filter(|maybe_var| maybe_var.is_some())
|
||||
.map(|var| var.unwrap()),
|
||||
);
|
||||
if !free_typevars.is_subset(&src_tvs) {
|
||||
let missing_tvs = (&free_typevars - &src_tvs)
|
||||
.iter()
|
||||
.map(|tv| tv.name.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
panic!("Some free vars don't appear in src: {}", missing_tvs);
|
||||
}
|
||||
}
|
||||
|
||||
for &var_index in input_vars.iter().chain(defined_vars.iter()) {
|
||||
let var = var_pool.get_mut(var_index);
|
||||
let canon_tv = type_env.get_equivalent(&var.get_or_create_typevar());
|
||||
var.set_typevar(canon_tv);
|
||||
}
|
||||
|
||||
Self {
|
||||
src,
|
||||
dst,
|
||||
var_pool,
|
||||
def_pool,
|
||||
block_pool,
|
||||
const_pool,
|
||||
type_env,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_legalize(&self) {
|
||||
let def = self.def_pool.get(self.src);
|
||||
for &var_index in def.defined_vars.iter() {
|
||||
let defined_var = self.var_pool.get(var_index);
|
||||
assert!(
|
||||
defined_var.is_output(),
|
||||
"{:?} not defined in the destination pattern",
|
||||
defined_var
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts, if not present, a name in the `symbol_table`. Then returns its index in the variable
|
||||
/// pool `var_pool`. If the variable was not present in the symbol table, then add it to the list of
|
||||
/// `defined_vars`.
|
||||
fn var_index(
|
||||
name: &str,
|
||||
symbol_table: &mut SymbolTable,
|
||||
defined_vars: &mut Vec<VarIndex>,
|
||||
var_pool: &mut VarPool,
|
||||
) -> VarIndex {
|
||||
let name = name.to_string();
|
||||
match symbol_table.get(&name) {
|
||||
Some(&existing_var) => existing_var,
|
||||
None => {
|
||||
// Materialize the variable.
|
||||
let new_var = var_pool.create(name.clone());
|
||||
symbol_table.insert(name, new_var);
|
||||
defined_vars.push(new_var);
|
||||
new_var
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a list of symbols defined in a Def, rewrite them to local symbols. Yield the new locals.
|
||||
fn rewrite_defined_vars(
|
||||
position: PatternPosition,
|
||||
dummy_def: &DummyDef,
|
||||
def_index: DefIndex,
|
||||
symbol_table: &mut SymbolTable,
|
||||
defined_vars: &mut Vec<VarIndex>,
|
||||
var_pool: &mut VarPool,
|
||||
) -> Vec<VarIndex> {
|
||||
let mut new_defined_vars = Vec::new();
|
||||
for var in &dummy_def.defined_vars {
|
||||
let own_var = var_index(&var.name, symbol_table, defined_vars, var_pool);
|
||||
var_pool.get_mut(own_var).set_def(position, def_index);
|
||||
new_defined_vars.push(own_var);
|
||||
}
|
||||
new_defined_vars
|
||||
}
|
||||
|
||||
/// Find all uses of variables in `expr` and replace them with our own local symbols.
|
||||
fn rewrite_expr(
|
||||
position: PatternPosition,
|
||||
dummy_expr: DummyExpr,
|
||||
symbol_table: &mut SymbolTable,
|
||||
input_vars: &mut Vec<VarIndex>,
|
||||
var_pool: &mut VarPool,
|
||||
const_pool: &mut ConstPool,
|
||||
) -> Apply {
|
||||
let (apply_target, dummy_args) = if let DummyExpr::Apply(apply_target, dummy_args) = dummy_expr
|
||||
{
|
||||
(apply_target, dummy_args)
|
||||
} else {
|
||||
panic!("we only rewrite apply expressions");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
apply_target.inst().operands_in.len(),
|
||||
dummy_args.len(),
|
||||
"number of arguments in instruction {} is incorrect\nexpected: {:?}",
|
||||
apply_target.inst().name,
|
||||
apply_target
|
||||
.inst()
|
||||
.operands_in
|
||||
.iter()
|
||||
.map(|operand| format!("{}: {}", operand.name, operand.kind.rust_type))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let mut args = Vec::new();
|
||||
for (i, arg) in dummy_args.into_iter().enumerate() {
|
||||
match arg {
|
||||
DummyExpr::Var(var) => {
|
||||
let own_var = var_index(&var.name, symbol_table, input_vars, var_pool);
|
||||
let var = var_pool.get(own_var);
|
||||
assert!(
|
||||
var.is_input() || var.get_def(position).is_some(),
|
||||
"{:?} used as both input and def",
|
||||
var
|
||||
);
|
||||
args.push(Expr::Var(own_var));
|
||||
}
|
||||
DummyExpr::Literal(literal) => {
|
||||
assert!(!apply_target.inst().operands_in[i].is_value());
|
||||
args.push(Expr::Literal(literal));
|
||||
}
|
||||
DummyExpr::Constant(constant) => {
|
||||
let const_name = const_pool.insert(constant.0);
|
||||
// Here we abuse var_index by passing an empty, immediately-dropped vector to
|
||||
// `defined_vars`; the reason for this is that unlike the `Var` case above,
|
||||
// constants will create a variable that is not an input variable (it is tracked
|
||||
// instead by ConstPool).
|
||||
let const_var = var_index(&const_name, symbol_table, &mut vec![], var_pool);
|
||||
args.push(Expr::Var(const_var));
|
||||
}
|
||||
DummyExpr::Apply(..) => {
|
||||
panic!("Recursive apply is not allowed.");
|
||||
}
|
||||
DummyExpr::Block(_block) => {
|
||||
panic!("Blocks are not valid arguments.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Apply::new(apply_target, args)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rewrite_def_list(
|
||||
position: PatternPosition,
|
||||
dummy_defs: Vec<DummyDef>,
|
||||
symbol_table: &mut SymbolTable,
|
||||
input_vars: &mut Vec<VarIndex>,
|
||||
defined_vars: &mut Vec<VarIndex>,
|
||||
var_pool: &mut VarPool,
|
||||
def_pool: &mut DefPool,
|
||||
block_pool: &mut BlockPool,
|
||||
const_pool: &mut ConstPool,
|
||||
) -> Vec<DefIndex> {
|
||||
let mut new_defs = Vec::new();
|
||||
// Register variable names of new blocks first as a block name can be used to jump forward. Thus
|
||||
// the name has to be registered first to avoid misinterpreting it as an input-var.
|
||||
for dummy_def in dummy_defs.iter() {
|
||||
if let DummyExpr::Block(ref var) = dummy_def.expr {
|
||||
var_index(&var.name, symbol_table, defined_vars, var_pool);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the definitions and blocks, to map variables names to inputs or outputs.
|
||||
for dummy_def in dummy_defs {
|
||||
let def_index = def_pool.next_index();
|
||||
|
||||
let new_defined_vars = rewrite_defined_vars(
|
||||
position,
|
||||
&dummy_def,
|
||||
def_index,
|
||||
symbol_table,
|
||||
defined_vars,
|
||||
var_pool,
|
||||
);
|
||||
if let DummyExpr::Block(var) = dummy_def.expr {
|
||||
let var_index = *symbol_table
|
||||
.get(&var.name)
|
||||
.or_else(|| {
|
||||
panic!(
|
||||
"Block {} was not registered during the first visit",
|
||||
var.name
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
var_pool.get_mut(var_index).set_def(position, def_index);
|
||||
block_pool.create_block(var_index, def_index);
|
||||
} else {
|
||||
let new_apply = rewrite_expr(
|
||||
position,
|
||||
dummy_def.expr,
|
||||
symbol_table,
|
||||
input_vars,
|
||||
var_pool,
|
||||
const_pool,
|
||||
);
|
||||
|
||||
assert!(
|
||||
def_pool.next_index() == def_index,
|
||||
"shouldn't have created new defs in the meanwhile"
|
||||
);
|
||||
assert_eq!(
|
||||
new_apply.inst.value_results.len(),
|
||||
new_defined_vars.len(),
|
||||
"number of Var results in instruction is incorrect"
|
||||
);
|
||||
|
||||
new_defs.push(def_pool.create_inst(new_apply, new_defined_vars));
|
||||
}
|
||||
}
|
||||
new_defs
|
||||
}
|
||||
|
||||
/// A group of related transformations.
|
||||
pub(crate) struct TransformGroup {
|
||||
pub name: &'static str,
|
||||
pub doc: &'static str,
|
||||
pub chain_with: Option<TransformGroupIndex>,
|
||||
pub isa_name: Option<&'static str>,
|
||||
pub id: TransformGroupIndex,
|
||||
|
||||
/// Maps Instruction camel_case names to custom legalization functions names.
|
||||
pub custom_legalizes: HashMap<String, &'static str>,
|
||||
pub transforms: Vec<Transform>,
|
||||
}
|
||||
|
||||
impl TransformGroup {
|
||||
pub fn rust_name(&self) -> String {
|
||||
match self.isa_name {
|
||||
Some(_) => {
|
||||
// This is a function in the same module as the LEGALIZE_ACTIONS table referring to
|
||||
// it.
|
||||
self.name.to_string()
|
||||
}
|
||||
None => format!("crate::legalizer::{}", self.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub(crate) struct TransformGroupIndex(u32);
|
||||
entity_impl!(TransformGroupIndex);
|
||||
|
||||
pub(crate) struct TransformGroupBuilder {
|
||||
name: &'static str,
|
||||
doc: &'static str,
|
||||
chain_with: Option<TransformGroupIndex>,
|
||||
isa_name: Option<&'static str>,
|
||||
pub custom_legalizes: HashMap<String, &'static str>,
|
||||
pub transforms: Vec<Transform>,
|
||||
}
|
||||
|
||||
impl TransformGroupBuilder {
|
||||
pub fn new(name: &'static str, doc: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
doc,
|
||||
chain_with: None,
|
||||
isa_name: None,
|
||||
custom_legalizes: HashMap::new(),
|
||||
transforms: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chain_with(mut self, next_id: TransformGroupIndex) -> Self {
|
||||
assert!(self.chain_with.is_none());
|
||||
self.chain_with = Some(next_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn isa(mut self, isa_name: &'static str) -> Self {
|
||||
assert!(self.isa_name.is_none());
|
||||
self.isa_name = Some(isa_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a custom legalization action for `inst`.
|
||||
///
|
||||
/// The `func_name` parameter is the fully qualified name of a Rust function which takes the
|
||||
/// same arguments as the `isa::Legalize` actions.
|
||||
///
|
||||
/// The custom function will be called to legalize `inst` and any return value is ignored.
|
||||
pub fn custom_legalize(&mut self, inst: &Instruction, func_name: &'static str) {
|
||||
assert!(
|
||||
self.custom_legalizes
|
||||
.insert(inst.camel_name.clone(), func_name)
|
||||
.is_none(),
|
||||
"custom legalization action for {} inserted twice",
|
||||
inst.name
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a legalization pattern to this group.
|
||||
pub fn legalize(&mut self, src: DummyDef, dst: Vec<DummyDef>) {
|
||||
let transform = Transform::new(src, dst);
|
||||
transform.verify_legalize();
|
||||
self.transforms.push(transform);
|
||||
}
|
||||
|
||||
pub fn build_and_add_to(self, owner: &mut TransformGroups) -> TransformGroupIndex {
|
||||
let next_id = owner.next_key();
|
||||
owner.add(TransformGroup {
|
||||
name: self.name,
|
||||
doc: self.doc,
|
||||
isa_name: self.isa_name,
|
||||
id: next_id,
|
||||
chain_with: self.chain_with,
|
||||
custom_legalizes: self.custom_legalizes,
|
||||
transforms: self.transforms,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TransformGroups {
|
||||
groups: PrimaryMap<TransformGroupIndex, TransformGroup>,
|
||||
}
|
||||
|
||||
impl TransformGroups {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
groups: PrimaryMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn add(&mut self, new_group: TransformGroup) -> TransformGroupIndex {
|
||||
for group in self.groups.values() {
|
||||
assert!(
|
||||
group.name != new_group.name,
|
||||
"trying to insert {} for the second time",
|
||||
new_group.name
|
||||
);
|
||||
}
|
||||
self.groups.push(new_group)
|
||||
}
|
||||
pub fn get(&self, id: TransformGroupIndex) -> &TransformGroup {
|
||||
&self.groups[id]
|
||||
}
|
||||
fn next_key(&self) -> TransformGroupIndex {
|
||||
self.groups.next_key()
|
||||
}
|
||||
pub fn by_name(&self, name: &'static str) -> &TransformGroup {
|
||||
for group in self.groups.values() {
|
||||
if group.name == name {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
panic!("transform group with name {} not found", name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_double_custom_legalization() {
|
||||
use crate::cdsl::formats::InstructionFormatBuilder;
|
||||
use crate::cdsl::instructions::{AllInstructions, InstructionBuilder, InstructionGroupBuilder};
|
||||
|
||||
let nullary = InstructionFormatBuilder::new("nullary").build();
|
||||
|
||||
let mut dummy_all = AllInstructions::new();
|
||||
let mut inst_group = InstructionGroupBuilder::new(&mut dummy_all);
|
||||
inst_group.push(InstructionBuilder::new("dummy", "doc", &nullary));
|
||||
|
||||
let inst_group = inst_group.build();
|
||||
let dummy_inst = inst_group.by_name("dummy");
|
||||
|
||||
let mut transform_group = TransformGroupBuilder::new("test", "doc");
|
||||
transform_group.custom_legalize(&dummy_inst, "custom 1");
|
||||
transform_group.custom_legalize(&dummy_inst, "custom 2");
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
//! Generate binary emission code for each ISA.
|
||||
|
||||
use cranelift_entity::EntityRef;
|
||||
|
||||
use crate::error;
|
||||
use crate::srcgen::Formatter;
|
||||
|
||||
use crate::cdsl::recipes::{EncodingRecipe, OperandConstraint, Recipes};
|
||||
|
||||
/// Generate code to handle a single recipe.
|
||||
///
|
||||
/// - Unpack the instruction data, knowing the format.
|
||||
/// - Determine register locations for operands with register constraints.
|
||||
/// - Determine stack slot locations for operands with stack constraints.
|
||||
/// - Call hand-written code for the actual emission.
|
||||
fn gen_recipe(recipe: &EncodingRecipe, fmt: &mut Formatter) {
|
||||
let inst_format = &recipe.format;
|
||||
let num_value_ops = inst_format.num_value_operands;
|
||||
|
||||
// TODO: Set want_args to true for only MultiAry instructions instead of all formats with value
|
||||
// list.
|
||||
let want_args = inst_format.has_value_list
|
||||
|| recipe.operands_in.iter().any(|c| match c {
|
||||
OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true,
|
||||
OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false,
|
||||
});
|
||||
assert!(!want_args || num_value_ops > 0 || inst_format.has_value_list);
|
||||
|
||||
let want_outs = recipe.operands_out.iter().any(|c| match c {
|
||||
OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true,
|
||||
OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false,
|
||||
});
|
||||
|
||||
let is_regmove = ["RegMove", "RegSpill", "RegFill"].contains(&inst_format.name);
|
||||
|
||||
// Unpack the instruction data.
|
||||
fmtln!(fmt, "if let InstructionData::{} {{", inst_format.name);
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("opcode,");
|
||||
for f in &inst_format.imm_fields {
|
||||
fmtln!(fmt, "{},", f.member);
|
||||
}
|
||||
if want_args {
|
||||
if inst_format.has_value_list || num_value_ops > 1 {
|
||||
fmt.line("ref args,");
|
||||
} else {
|
||||
fmt.line("arg,");
|
||||
}
|
||||
}
|
||||
fmt.line("..");
|
||||
|
||||
fmt.outdented_line("} = *inst_data {");
|
||||
|
||||
// Pass recipe arguments in this order: inputs, imm_fields, outputs.
|
||||
let mut args = String::new();
|
||||
|
||||
if want_args && !is_regmove {
|
||||
if inst_format.has_value_list {
|
||||
fmt.line("let args = args.as_slice(&func.dfg.value_lists);");
|
||||
} else if num_value_ops == 1 {
|
||||
fmt.line("let args = [arg];");
|
||||
}
|
||||
args += &unwrap_values(&recipe.operands_in, "in", "args", fmt);
|
||||
}
|
||||
|
||||
for f in &inst_format.imm_fields {
|
||||
args += &format!(", {}", f.member);
|
||||
}
|
||||
|
||||
// Unwrap interesting output arguments.
|
||||
if want_outs {
|
||||
if recipe.operands_out.len() == 1 {
|
||||
fmt.line("let results = [func.dfg.first_result(inst)];")
|
||||
} else {
|
||||
fmt.line("let results = func.dfg.inst_results(inst);");
|
||||
}
|
||||
args += &unwrap_values(&recipe.operands_out, "out", "results", fmt);
|
||||
}
|
||||
|
||||
// Optimization: Only update the register diversion tracker for regmove instructions.
|
||||
if is_regmove {
|
||||
fmt.line("divert.apply(inst_data);")
|
||||
}
|
||||
|
||||
match &recipe.emit {
|
||||
Some(emit) => {
|
||||
fmt.multi_line(emit);
|
||||
fmt.line("return;");
|
||||
}
|
||||
None => {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"return recipe_{}(func, inst, sink, bits{});",
|
||||
recipe.name.to_lowercase(),
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
fmt.line("}");
|
||||
}
|
||||
|
||||
/// Emit code that unwraps values living in registers or stack slots.
|
||||
///
|
||||
/// :param args: Input or output constraints.
|
||||
/// :param prefix: Prefix to be used for the generated local variables.
|
||||
/// :param values: Name of slice containing the values to be unwrapped.
|
||||
/// :returns: Comma separated list of the generated variables
|
||||
fn unwrap_values(
|
||||
args: &[OperandConstraint],
|
||||
prefix: &str,
|
||||
values_slice: &str,
|
||||
fmt: &mut Formatter,
|
||||
) -> String {
|
||||
let mut varlist = String::new();
|
||||
for (i, cst) in args.iter().enumerate() {
|
||||
match cst {
|
||||
OperandConstraint::RegClass(_reg_class) => {
|
||||
let v = format!("{}_reg{}", prefix, i);
|
||||
varlist += &format!(", {}", v);
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let {} = divert.reg({}[{}], &func.locations);",
|
||||
v,
|
||||
values_slice,
|
||||
i
|
||||
);
|
||||
}
|
||||
OperandConstraint::Stack(stack) => {
|
||||
let v = format!("{}_stk{}", prefix, i);
|
||||
varlist += &format!(", {}", v);
|
||||
fmtln!(fmt, "let {} = StackRef::masked(", v);
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"divert.stack({}[{}], &func.locations),",
|
||||
values_slice,
|
||||
i
|
||||
);
|
||||
fmt.line(format!("{},", stack.stack_base_mask()));
|
||||
fmt.line("&func.stack_slots,");
|
||||
});
|
||||
fmt.line(").unwrap();");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
varlist
|
||||
}
|
||||
|
||||
fn gen_isa(isa_name: &str, recipes: &Recipes, fmt: &mut Formatter) {
|
||||
fmt.doc_comment(format!(
|
||||
"Emit binary machine code for `inst` for the {} ISA.",
|
||||
isa_name
|
||||
));
|
||||
|
||||
if recipes.is_empty() {
|
||||
fmt.line("pub fn emit_inst<CS: CodeSink + ?Sized>(");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("func: &Function,");
|
||||
fmt.line("inst: Inst,");
|
||||
fmt.line("_divert: &mut RegDiversions,");
|
||||
fmt.line("_sink: &mut CS,");
|
||||
fmt.line("_isa: &dyn TargetIsa,");
|
||||
});
|
||||
fmt.line(") {");
|
||||
fmt.indent(|fmt| {
|
||||
// No encoding recipes: Emit a stub.
|
||||
fmt.line("bad_encoding(func, inst)");
|
||||
});
|
||||
fmt.line("}");
|
||||
return;
|
||||
}
|
||||
|
||||
fmt.line("#[allow(unused_variables, unreachable_code)]");
|
||||
fmt.line("pub fn emit_inst<CS: CodeSink + ?Sized>(");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("func: &Function,");
|
||||
fmt.line("inst: Inst,");
|
||||
fmt.line("divert: &mut RegDiversions,");
|
||||
fmt.line("sink: &mut CS,");
|
||||
fmt.line("isa: &dyn TargetIsa,")
|
||||
});
|
||||
|
||||
fmt.line(") {");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("let encoding = func.encodings[inst];");
|
||||
fmt.line("let bits = encoding.bits();");
|
||||
fmt.line("let inst_data = &func.dfg[inst];");
|
||||
fmt.line("match encoding.recipe() {");
|
||||
fmt.indent(|fmt| {
|
||||
for (i, recipe) in recipes.iter() {
|
||||
fmt.comment(format!("Recipe {}", recipe.name));
|
||||
fmtln!(fmt, "{} => {{", i.index());
|
||||
fmt.indent(|fmt| {
|
||||
gen_recipe(recipe, fmt);
|
||||
});
|
||||
fmt.line("}");
|
||||
}
|
||||
fmt.line("_ => {},");
|
||||
});
|
||||
fmt.line("}");
|
||||
|
||||
// Allow for unencoded ghost instructions. The verifier will check details.
|
||||
fmt.line("if encoding.is_legal() {");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("bad_encoding(func, inst);");
|
||||
});
|
||||
fmt.line("}");
|
||||
});
|
||||
fmt.line("}");
|
||||
}
|
||||
|
||||
pub(crate) fn generate(
|
||||
isa_name: &str,
|
||||
recipes: &Recipes,
|
||||
binemit_filename: &str,
|
||||
out_dir: &str,
|
||||
) -> Result<(), error::Error> {
|
||||
let mut fmt = Formatter::new();
|
||||
gen_isa(isa_name, recipes, &mut fmt);
|
||||
fmt.update_file(binemit_filename, out_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,734 +0,0 @@
|
||||
//! Generate transformations to legalize instructions without encodings.
|
||||
use crate::cdsl::ast::{Def, DefPool, Expr, VarPool};
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::operands::Operand;
|
||||
use crate::cdsl::type_inference::Constraint;
|
||||
use crate::cdsl::typevar::{TypeSet, TypeVar};
|
||||
use crate::cdsl::xform::{Transform, TransformGroup, TransformGroups};
|
||||
|
||||
use crate::error;
|
||||
use crate::gen_inst::gen_typesets_table;
|
||||
use crate::srcgen::Formatter;
|
||||
use crate::unique_table::UniqueTable;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
/// Given a `Def` node, emit code that extracts all the instruction fields from
|
||||
/// `pos.func.dfg[iref]`.
|
||||
///
|
||||
/// Create local variables named after the `Var` instances in `node`.
|
||||
///
|
||||
/// Also create a local variable named `predicate` with the value of the evaluated instruction
|
||||
/// predicate, or `true` if the node has no predicate.
|
||||
fn unwrap_inst(transform: &Transform, fmt: &mut Formatter) -> bool {
|
||||
let var_pool = &transform.var_pool;
|
||||
let def_pool = &transform.def_pool;
|
||||
|
||||
let def = def_pool.get(transform.src);
|
||||
let apply = &def.apply;
|
||||
let inst = &apply.inst;
|
||||
let iform = &inst.format;
|
||||
|
||||
fmt.comment(format!(
|
||||
"Unwrap fields from instruction format {}",
|
||||
def.to_comment_string(&transform.var_pool)
|
||||
));
|
||||
|
||||
// Extract the Var arguments.
|
||||
let arg_names = apply
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(arg_num, _)| {
|
||||
// Variable args are specially handled after extracting args.
|
||||
!inst.operands_in[*arg_num].is_varargs()
|
||||
})
|
||||
.map(|(arg_num, arg)| match &arg {
|
||||
Expr::Var(var_index) => var_pool.get(*var_index).name.as_ref(),
|
||||
Expr::Literal(_) => {
|
||||
let n = inst.imm_opnums.iter().position(|&i| i == arg_num).unwrap();
|
||||
iform.imm_fields[n].member
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
// May we need "args" in the values consumed by predicates?
|
||||
let emit_args = iform.num_value_operands >= 1 || iform.has_value_list;
|
||||
|
||||
// We need a tuple:
|
||||
// - if there's at least one value operand, then we emit a variable for the value, and the
|
||||
// value list as args.
|
||||
// - otherwise, if there's the count of immediate operands added to the presence of a value list exceeds one.
|
||||
let need_tuple = if iform.num_value_operands >= 1 {
|
||||
true
|
||||
} else {
|
||||
let mut imm_and_varargs = inst
|
||||
.operands_in
|
||||
.iter()
|
||||
.filter(|op| op.is_immediate_or_entityref())
|
||||
.count();
|
||||
if iform.has_value_list {
|
||||
imm_and_varargs += 1;
|
||||
}
|
||||
imm_and_varargs > 1
|
||||
};
|
||||
|
||||
let maybe_args = if emit_args { ", args" } else { "" };
|
||||
let defined_values = format!("{}{}", arg_names, maybe_args);
|
||||
|
||||
let tuple_or_value = if need_tuple {
|
||||
format!("({})", defined_values)
|
||||
} else {
|
||||
defined_values
|
||||
};
|
||||
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let {} = if let ir::InstructionData::{} {{",
|
||||
tuple_or_value,
|
||||
iform.name
|
||||
);
|
||||
|
||||
fmt.indent(|fmt| {
|
||||
// Fields are encoded directly.
|
||||
for field in &iform.imm_fields {
|
||||
fmtln!(fmt, "{},", field.member);
|
||||
}
|
||||
|
||||
if iform.has_value_list || iform.num_value_operands > 1 {
|
||||
fmt.line("ref args,");
|
||||
} else if iform.num_value_operands == 1 {
|
||||
fmt.line("arg,");
|
||||
}
|
||||
|
||||
fmt.line("..");
|
||||
fmt.outdented_line("} = pos.func.dfg[inst] {");
|
||||
|
||||
if iform.has_value_list {
|
||||
fmt.line("let args = args.as_slice(&pos.func.dfg.value_lists);");
|
||||
} else if iform.num_value_operands == 1 {
|
||||
fmt.line("let args = [arg];")
|
||||
}
|
||||
|
||||
// Generate the values for the tuple.
|
||||
let emit_one_value =
|
||||
|fmt: &mut Formatter, needs_comma: bool, op_num: usize, op: &Operand| {
|
||||
let comma = if needs_comma { "," } else { "" };
|
||||
if op.is_immediate_or_entityref() {
|
||||
let n = inst.imm_opnums.iter().position(|&i| i == op_num).unwrap();
|
||||
fmtln!(fmt, "{}{}", iform.imm_fields[n].member, comma);
|
||||
} else if op.is_value() {
|
||||
let n = inst.value_opnums.iter().position(|&i| i == op_num).unwrap();
|
||||
fmtln!(fmt, "pos.func.dfg.resolve_aliases(args[{}]),", n);
|
||||
} else {
|
||||
// This is a value list argument or a varargs.
|
||||
assert!(iform.has_value_list || op.is_varargs());
|
||||
}
|
||||
};
|
||||
|
||||
if need_tuple {
|
||||
fmt.line("(");
|
||||
fmt.indent(|fmt| {
|
||||
for (op_num, op) in inst.operands_in.iter().enumerate() {
|
||||
let needs_comma = emit_args || op_num + 1 < inst.operands_in.len();
|
||||
emit_one_value(fmt, needs_comma, op_num, op);
|
||||
}
|
||||
if emit_args {
|
||||
fmt.line("args");
|
||||
}
|
||||
});
|
||||
fmt.line(")");
|
||||
} else {
|
||||
// Only one of these can be true at the same time, otherwise we'd need a tuple.
|
||||
emit_one_value(fmt, false, 0, &inst.operands_in[0]);
|
||||
if emit_args {
|
||||
fmt.line("args");
|
||||
}
|
||||
}
|
||||
|
||||
fmt.outdented_line("} else {");
|
||||
fmt.line(r#"unreachable!("bad instruction format")"#);
|
||||
});
|
||||
fmtln!(fmt, "};");
|
||||
fmt.empty_line();
|
||||
|
||||
assert_eq!(inst.operands_in.len(), apply.args.len());
|
||||
for (i, op) in inst.operands_in.iter().enumerate() {
|
||||
if op.is_varargs() {
|
||||
let name = &var_pool
|
||||
.get(apply.args[i].maybe_var().expect("vararg without name"))
|
||||
.name;
|
||||
let n = inst
|
||||
.imm_opnums
|
||||
.iter()
|
||||
.chain(inst.value_opnums.iter())
|
||||
.max()
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
fmtln!(fmt, "let {} = &Vec::from(&args[{}..]);", name, n);
|
||||
}
|
||||
}
|
||||
|
||||
for &op_num in &inst.value_opnums {
|
||||
let arg = &apply.args[op_num];
|
||||
if let Some(var_index) = arg.maybe_var() {
|
||||
let var = var_pool.get(var_index);
|
||||
if var.has_free_typevar() {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let typeof_{} = pos.func.dfg.value_type({});",
|
||||
var.name,
|
||||
var.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the definition creates results, detach the values and place them in locals.
|
||||
let mut replace_inst = false;
|
||||
if !def.defined_vars.is_empty() {
|
||||
if def.defined_vars
|
||||
== def_pool
|
||||
.get(var_pool.get(def.defined_vars[0]).dst_def.unwrap())
|
||||
.defined_vars
|
||||
{
|
||||
// Special case: The instruction replacing node defines the exact same values.
|
||||
fmt.comment(format!(
|
||||
"Results handled by {}.",
|
||||
def_pool
|
||||
.get(var_pool.get(def.defined_vars[0]).dst_def.unwrap())
|
||||
.to_comment_string(var_pool)
|
||||
));
|
||||
|
||||
fmt.line("let r = pos.func.dfg.inst_results(inst);");
|
||||
for (i, &var_index) in def.defined_vars.iter().enumerate() {
|
||||
let var = var_pool.get(var_index);
|
||||
fmtln!(fmt, "let {} = &r[{}];", var.name, i);
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let typeof_{} = pos.func.dfg.value_type(*{});",
|
||||
var.name,
|
||||
var.name
|
||||
);
|
||||
}
|
||||
|
||||
replace_inst = true;
|
||||
} else {
|
||||
// Boring case: Detach the result values, capture them in locals.
|
||||
for &var_index in &def.defined_vars {
|
||||
fmtln!(fmt, "let {};", var_pool.get(var_index).name);
|
||||
}
|
||||
|
||||
fmt.line("{");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("let r = pos.func.dfg.inst_results(inst);");
|
||||
for i in 0..def.defined_vars.len() {
|
||||
let var = var_pool.get(def.defined_vars[i]);
|
||||
fmtln!(fmt, "{} = r[{}];", var.name, i);
|
||||
}
|
||||
});
|
||||
fmt.line("}");
|
||||
|
||||
for &var_index in &def.defined_vars {
|
||||
let var = var_pool.get(var_index);
|
||||
if var.has_free_typevar() {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let typeof_{} = pos.func.dfg.value_type({});",
|
||||
var.name,
|
||||
var.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
replace_inst
|
||||
}
|
||||
|
||||
fn build_derived_expr(tv: &TypeVar) -> String {
|
||||
let base = match &tv.base {
|
||||
Some(base) => base,
|
||||
None => {
|
||||
assert!(tv.name.starts_with("typeof_"));
|
||||
return format!("Some({})", tv.name);
|
||||
}
|
||||
};
|
||||
let base_expr = build_derived_expr(&base.type_var);
|
||||
format!(
|
||||
"{}.map(|t: crate::ir::Type| t.{}())",
|
||||
base_expr,
|
||||
base.derived_func.name()
|
||||
)
|
||||
}
|
||||
|
||||
/// Emit rust code for the given check.
|
||||
///
|
||||
/// The emitted code is a statement redefining the `predicate` variable like this:
|
||||
/// let predicate = predicate && ...
|
||||
fn emit_runtime_typecheck<'a>(
|
||||
constraint: &'a Constraint,
|
||||
type_sets: &mut UniqueTable<'a, TypeSet>,
|
||||
fmt: &mut Formatter,
|
||||
) {
|
||||
match constraint {
|
||||
Constraint::InTypeset(tv, ts) => {
|
||||
let ts_index = type_sets.add(&ts);
|
||||
fmt.comment(format!(
|
||||
"{} must belong to {:?}",
|
||||
tv.name,
|
||||
type_sets.get(ts_index)
|
||||
));
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let predicate = predicate && TYPE_SETS[{}].contains({});",
|
||||
ts_index,
|
||||
tv.name
|
||||
);
|
||||
}
|
||||
Constraint::Eq(tv1, tv2) => {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let predicate = predicate && match ({}, {}) {{",
|
||||
build_derived_expr(tv1),
|
||||
build_derived_expr(tv2)
|
||||
);
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("(Some(a), Some(b)) => a == b,");
|
||||
fmt.comment("On overflow, constraint doesn\'t apply");
|
||||
fmt.line("_ => false,");
|
||||
});
|
||||
fmtln!(fmt, "};");
|
||||
}
|
||||
Constraint::WiderOrEq(tv1, tv2) => {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let predicate = predicate && match ({}, {}) {{",
|
||||
build_derived_expr(tv1),
|
||||
build_derived_expr(tv2)
|
||||
);
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("(Some(a), Some(b)) => a.wider_or_equal(b),");
|
||||
fmt.comment("On overflow, constraint doesn\'t apply");
|
||||
fmt.line("_ => false,");
|
||||
});
|
||||
fmtln!(fmt, "};");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if `node` represents one of the value splitting instructions: `isplit` or `vsplit.
|
||||
/// These instructions are lowered specially by the `legalize::split` module.
|
||||
fn is_value_split(def: &Def) -> bool {
|
||||
let name = &def.apply.inst.name;
|
||||
name == "isplit" || name == "vsplit"
|
||||
}
|
||||
|
||||
fn emit_dst_inst(def: &Def, def_pool: &DefPool, var_pool: &VarPool, fmt: &mut Formatter) {
|
||||
let defined_vars = {
|
||||
let vars = def
|
||||
.defined_vars
|
||||
.iter()
|
||||
.map(|&var_index| var_pool.get(var_index).name.as_ref())
|
||||
.collect::<Vec<&str>>();
|
||||
if vars.len() == 1 {
|
||||
vars[0].to_string()
|
||||
} else {
|
||||
format!("({})", vars.join(", "))
|
||||
}
|
||||
};
|
||||
|
||||
if is_value_split(def) {
|
||||
// Split instructions are not emitted with the builder, but by calling special functions in
|
||||
// the `legalizer::split` module. These functions will eliminate concat-split patterns.
|
||||
fmt.line("let curpos = pos.position();");
|
||||
fmt.line("let srcloc = pos.srcloc();");
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let {} = split::{}(pos.func, cfg, curpos, srcloc, {});",
|
||||
defined_vars,
|
||||
def.apply.inst.snake_name(),
|
||||
def.apply.args[0].to_rust_code(var_pool)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if def.defined_vars.is_empty() {
|
||||
// This node doesn't define any values, so just insert the new instruction.
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pos.ins().{};",
|
||||
def.apply.rust_builder(&def.defined_vars, var_pool)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(src_def0) = var_pool.get(def.defined_vars[0]).src_def {
|
||||
if def.defined_vars == def_pool.get(src_def0).defined_vars {
|
||||
// The replacement instruction defines the exact same values as the source pattern.
|
||||
// Unwrapping would have left the results intact. Replace the whole instruction.
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let {} = pos.func.dfg.replace(inst).{};",
|
||||
defined_vars,
|
||||
def.apply.rust_builder(&def.defined_vars, var_pool)
|
||||
);
|
||||
|
||||
// We need to bump the cursor so following instructions are inserted *after* the
|
||||
// replaced instruction.
|
||||
fmt.line("if pos.current_inst() == Some(inst) {");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("pos.next_inst();");
|
||||
});
|
||||
fmt.line("}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a new instruction.
|
||||
let mut builder = format!("let {} = pos.ins()", defined_vars);
|
||||
|
||||
if def.defined_vars.len() == 1 && var_pool.get(def.defined_vars[0]).is_output() {
|
||||
// Reuse the single source result value.
|
||||
builder = format!(
|
||||
"{}.with_result({})",
|
||||
builder,
|
||||
var_pool.get(def.defined_vars[0]).to_rust_code()
|
||||
);
|
||||
} else if def
|
||||
.defined_vars
|
||||
.iter()
|
||||
.any(|&var_index| var_pool.get(var_index).is_output())
|
||||
{
|
||||
// There are more than one output values that can be reused.
|
||||
let array = def
|
||||
.defined_vars
|
||||
.iter()
|
||||
.map(|&var_index| {
|
||||
let var = var_pool.get(var_index);
|
||||
if var.is_output() {
|
||||
format!("Some({})", var.name)
|
||||
} else {
|
||||
"None".into()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
builder = format!("{}.with_results([{}])", builder, array);
|
||||
}
|
||||
|
||||
fmtln!(
|
||||
fmt,
|
||||
"{}.{};",
|
||||
builder,
|
||||
def.apply.rust_builder(&def.defined_vars, var_pool)
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit code for `transform`, assuming that the opcode of transform's root instruction
|
||||
/// has already been matched.
|
||||
///
|
||||
/// `inst: Inst` is the variable to be replaced. It is pointed to by `pos: Cursor`.
|
||||
/// `dfg: DataFlowGraph` is available and mutable.
|
||||
fn gen_transform<'a>(
|
||||
replace_inst: bool,
|
||||
transform: &'a Transform,
|
||||
type_sets: &mut UniqueTable<'a, TypeSet>,
|
||||
fmt: &mut Formatter,
|
||||
) {
|
||||
// Evaluate the instruction predicate if any.
|
||||
let apply = &transform.def_pool.get(transform.src).apply;
|
||||
|
||||
let inst_predicate = apply
|
||||
.inst_predicate_with_ctrl_typevar(&transform.var_pool)
|
||||
.rust_predicate("pos.func");
|
||||
|
||||
let has_extra_constraints = !transform.type_env.constraints.is_empty();
|
||||
if has_extra_constraints {
|
||||
// Extra constraints rely on the predicate being a variable that we can rebind as we add
|
||||
// more constraint predicates.
|
||||
if let Some(pred) = &inst_predicate {
|
||||
fmt.multi_line(&format!("let predicate = {};", pred));
|
||||
} else {
|
||||
fmt.line("let predicate = true;");
|
||||
}
|
||||
}
|
||||
|
||||
// Emit any runtime checks; these will rebind `predicate` emitted right above.
|
||||
for constraint in &transform.type_env.constraints {
|
||||
emit_runtime_typecheck(constraint, type_sets, fmt);
|
||||
}
|
||||
|
||||
let do_expand = |fmt: &mut Formatter| {
|
||||
// Emit any constants that must be created before use.
|
||||
for (name, value) in transform.const_pool.iter() {
|
||||
fmtln!(
|
||||
fmt,
|
||||
"let {} = pos.func.dfg.constants.insert(vec!{:?}.into());",
|
||||
name,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
// If we are adding some blocks, we need to recall the original block, such that we can
|
||||
// recompute it.
|
||||
if !transform.block_pool.is_empty() {
|
||||
fmt.line("let orig_block = pos.current_block().unwrap();");
|
||||
}
|
||||
|
||||
// If we're going to delete `inst`, we need to detach its results first so they can be
|
||||
// reattached during pattern expansion.
|
||||
if !replace_inst {
|
||||
fmt.line("pos.func.dfg.clear_results(inst);");
|
||||
}
|
||||
|
||||
// Emit new block creation.
|
||||
for block in &transform.block_pool {
|
||||
let var = transform.var_pool.get(block.name);
|
||||
fmtln!(fmt, "let {} = pos.func.dfg.make_block();", var.name);
|
||||
}
|
||||
|
||||
// Emit the destination pattern.
|
||||
for &def_index in &transform.dst {
|
||||
if let Some(block) = transform.block_pool.get(def_index) {
|
||||
let var = transform.var_pool.get(block.name);
|
||||
fmtln!(fmt, "pos.insert_block({});", var.name);
|
||||
}
|
||||
emit_dst_inst(
|
||||
transform.def_pool.get(def_index),
|
||||
&transform.def_pool,
|
||||
&transform.var_pool,
|
||||
fmt,
|
||||
);
|
||||
}
|
||||
|
||||
// Insert a new block after the last instruction, if needed.
|
||||
let def_next_index = transform.def_pool.next_index();
|
||||
if let Some(block) = transform.block_pool.get(def_next_index) {
|
||||
let var = transform.var_pool.get(block.name);
|
||||
fmtln!(fmt, "pos.insert_block({});", var.name);
|
||||
}
|
||||
|
||||
// Delete the original instruction if we didn't have an opportunity to replace it.
|
||||
if !replace_inst {
|
||||
fmt.line("let removed = pos.remove_inst();");
|
||||
fmt.line("debug_assert_eq!(removed, inst);");
|
||||
}
|
||||
|
||||
if transform.block_pool.is_empty() {
|
||||
if transform.def_pool.get(transform.src).apply.inst.is_branch {
|
||||
// A branch might have been legalized into multiple branches, so we need to recompute
|
||||
// the cfg.
|
||||
fmt.line("cfg.recompute_block(pos.func, pos.current_block().unwrap());");
|
||||
}
|
||||
} else {
|
||||
// Update CFG for the new blocks.
|
||||
fmt.line("cfg.recompute_block(pos.func, orig_block);");
|
||||
for block in &transform.block_pool {
|
||||
let var = transform.var_pool.get(block.name);
|
||||
fmtln!(fmt, "cfg.recompute_block(pos.func, {});", var.name);
|
||||
}
|
||||
}
|
||||
|
||||
fmt.line("return true;");
|
||||
};
|
||||
|
||||
// Guard the actual expansion by `predicate`.
|
||||
if has_extra_constraints {
|
||||
fmt.line("if predicate {");
|
||||
fmt.indent(|fmt| {
|
||||
do_expand(fmt);
|
||||
});
|
||||
fmt.line("}");
|
||||
} else if let Some(pred) = &inst_predicate {
|
||||
fmt.multi_line(&format!("if {} {{", pred));
|
||||
fmt.indent(|fmt| {
|
||||
do_expand(fmt);
|
||||
});
|
||||
fmt.line("}");
|
||||
} else {
|
||||
// Unconditional transform (there was no predicate), just emit it.
|
||||
do_expand(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_transform_group<'a>(
|
||||
group: &'a TransformGroup,
|
||||
transform_groups: &TransformGroups,
|
||||
type_sets: &mut UniqueTable<'a, TypeSet>,
|
||||
fmt: &mut Formatter,
|
||||
) {
|
||||
fmt.doc_comment(group.doc);
|
||||
fmt.line("#[allow(unused_variables,unused_assignments,unused_imports,non_snake_case)]");
|
||||
|
||||
// Function arguments.
|
||||
fmtln!(fmt, "pub fn {}(", group.name);
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("inst: crate::ir::Inst,");
|
||||
fmt.line("func: &mut crate::ir::Function,");
|
||||
fmt.line("cfg: &mut crate::flowgraph::ControlFlowGraph,");
|
||||
fmt.line("isa: &dyn crate::isa::TargetIsa,");
|
||||
});
|
||||
fmtln!(fmt, ") -> bool {");
|
||||
|
||||
// Function body.
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("use crate::ir::InstBuilder;");
|
||||
fmt.line("use crate::cursor::{Cursor, FuncCursor};");
|
||||
fmt.line("let mut pos = FuncCursor::new(func).at_inst(inst);");
|
||||
fmt.line("pos.use_srcloc(inst);");
|
||||
|
||||
// Group the transforms by opcode so we can generate a big switch.
|
||||
// Preserve ordering.
|
||||
let mut inst_to_transforms = HashMap::new();
|
||||
for transform in &group.transforms {
|
||||
let def_index = transform.src;
|
||||
let inst = &transform.def_pool.get(def_index).apply.inst;
|
||||
inst_to_transforms
|
||||
.entry(inst.camel_name.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(transform);
|
||||
}
|
||||
|
||||
let mut sorted_inst_names = Vec::from_iter(inst_to_transforms.keys());
|
||||
sorted_inst_names.sort();
|
||||
|
||||
fmt.line("{");
|
||||
fmt.indent(|fmt| {
|
||||
fmt.line("match pos.func.dfg[inst].opcode() {");
|
||||
fmt.indent(|fmt| {
|
||||
for camel_name in sorted_inst_names {
|
||||
fmtln!(fmt, "ir::Opcode::{} => {{", camel_name);
|
||||
fmt.indent(|fmt| {
|
||||
let transforms = inst_to_transforms.get(camel_name).unwrap();
|
||||
|
||||
// Unwrap the source instruction, create local variables for the input variables.
|
||||
let replace_inst = unwrap_inst(&transforms[0], fmt);
|
||||
fmt.empty_line();
|
||||
|
||||
for (i, transform) in transforms.iter().enumerate() {
|
||||
if i > 0 {
|
||||
fmt.empty_line();
|
||||
}
|
||||
gen_transform(replace_inst, transform, type_sets, fmt);
|
||||
}
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
fmt.empty_line();
|
||||
}
|
||||
|
||||
// Emit the custom transforms. The Rust compiler will complain about any overlap with
|
||||
// the normal transforms.
|
||||
let mut sorted_custom_legalizes = Vec::from_iter(&group.custom_legalizes);
|
||||
sorted_custom_legalizes.sort();
|
||||
for (inst_camel_name, func_name) in sorted_custom_legalizes {
|
||||
fmtln!(fmt, "ir::Opcode::{} => {{", inst_camel_name);
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, "{}(inst, func, cfg, isa);", func_name);
|
||||
fmt.line("return true;");
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
fmt.empty_line();
|
||||
}
|
||||
|
||||
// We'll assume there are uncovered opcodes.
|
||||
fmt.line("_ => {},");
|
||||
});
|
||||
fmt.line("}");
|
||||
});
|
||||
fmt.line("}");
|
||||
|
||||
// If we fall through, nothing was expanded; call the chain if any.
|
||||
match &group.chain_with {
|
||||
Some(group_id) => fmtln!(
|
||||
fmt,
|
||||
"{}(inst, func, cfg, isa)",
|
||||
transform_groups.get(*group_id).rust_name()
|
||||
),
|
||||
None => fmt.line("false"),
|
||||
};
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
fmt.empty_line();
|
||||
}
|
||||
|
||||
/// Generate legalization functions for `isa` and add any shared `TransformGroup`s
|
||||
/// encountered to `shared_groups`.
|
||||
///
|
||||
/// Generate `TYPE_SETS` and `LEGALIZE_ACTIONS` tables.
|
||||
fn gen_isa(
|
||||
isa: &TargetIsa,
|
||||
transform_groups: &TransformGroups,
|
||||
shared_group_names: &mut HashSet<&'static str>,
|
||||
fmt: &mut Formatter,
|
||||
) {
|
||||
let mut type_sets = UniqueTable::new();
|
||||
for group_index in isa.transitive_transform_groups(transform_groups) {
|
||||
let group = transform_groups.get(group_index);
|
||||
match group.isa_name {
|
||||
Some(isa_name) => {
|
||||
assert!(
|
||||
isa_name == isa.name,
|
||||
"ISA-specific legalizations must be used by the same ISA"
|
||||
);
|
||||
gen_transform_group(group, transform_groups, &mut type_sets, fmt);
|
||||
}
|
||||
None => {
|
||||
shared_group_names.insert(group.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gen_typesets_table(&type_sets, fmt);
|
||||
|
||||
let direct_groups = isa.direct_transform_groups();
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = [",
|
||||
direct_groups.len()
|
||||
);
|
||||
fmt.indent(|fmt| {
|
||||
for &group_index in direct_groups {
|
||||
fmtln!(fmt, "{},", transform_groups.get(group_index).rust_name());
|
||||
}
|
||||
});
|
||||
fmtln!(fmt, "];");
|
||||
}
|
||||
|
||||
/// Generate the legalizer files.
|
||||
pub(crate) fn generate(
|
||||
isas: &[TargetIsa],
|
||||
transform_groups: &TransformGroups,
|
||||
extra_legalization_groups: &[&'static str],
|
||||
filename_prefix: &str,
|
||||
out_dir: &str,
|
||||
) -> Result<(), error::Error> {
|
||||
let mut shared_group_names = HashSet::new();
|
||||
|
||||
for isa in isas {
|
||||
let mut fmt = Formatter::new();
|
||||
gen_isa(isa, transform_groups, &mut shared_group_names, &mut fmt);
|
||||
fmt.update_file(format!("{}-{}.rs", filename_prefix, isa.name), out_dir)?;
|
||||
}
|
||||
|
||||
// Add extra legalization groups that were explicitly requested.
|
||||
for group in extra_legalization_groups {
|
||||
shared_group_names.insert(group);
|
||||
}
|
||||
|
||||
// Generate shared legalize groups.
|
||||
let mut fmt = Formatter::new();
|
||||
// Generate shared legalize groups.
|
||||
let mut type_sets = UniqueTable::new();
|
||||
let mut sorted_shared_group_names = Vec::from_iter(shared_group_names);
|
||||
sorted_shared_group_names.sort();
|
||||
for group_name in &sorted_shared_group_names {
|
||||
let group = transform_groups.by_name(group_name);
|
||||
gen_transform_group(group, transform_groups, &mut type_sets, &mut fmt);
|
||||
}
|
||||
gen_typesets_table(&type_sets, &mut fmt);
|
||||
fmt.update_file(format!("{}r.rs", filename_prefix), out_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
//! Generate the ISA-specific registers.
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::regs::{RegBank, RegClass};
|
||||
use crate::error;
|
||||
use crate::srcgen::Formatter;
|
||||
use cranelift_entity::EntityRef;
|
||||
|
||||
fn gen_regbank(fmt: &mut Formatter, reg_bank: &RegBank) {
|
||||
let names = if !reg_bank.names.is_empty() {
|
||||
format!(r#""{}""#, reg_bank.names.join(r#"", ""#))
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
fmtln!(fmt, "RegBank {");
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, r#"name: "{}","#, reg_bank.name);
|
||||
fmtln!(fmt, "first_unit: {},", reg_bank.first_unit);
|
||||
fmtln!(fmt, "units: {},", reg_bank.units);
|
||||
fmtln!(fmt, "names: &[{}],", names);
|
||||
fmtln!(fmt, r#"prefix: "{}","#, reg_bank.prefix);
|
||||
fmtln!(fmt, "first_toprc: {},", reg_bank.toprcs[0].index());
|
||||
fmtln!(fmt, "num_toprcs: {},", reg_bank.toprcs.len());
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pressure_tracking: {},",
|
||||
if reg_bank.pressure_tracking {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
);
|
||||
});
|
||||
fmtln!(fmt, "},");
|
||||
}
|
||||
|
||||
fn gen_regclass(isa: &TargetIsa, reg_class: &RegClass, fmt: &mut Formatter) {
|
||||
let reg_bank = isa.regs.banks.get(reg_class.bank).unwrap();
|
||||
|
||||
let mask: Vec<String> = reg_class
|
||||
.mask(reg_bank.first_unit)
|
||||
.iter()
|
||||
.map(|x| format!("0x{:08x}", x))
|
||||
.collect();
|
||||
let mask = mask.join(", ");
|
||||
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pub static {}_DATA: RegClassData = RegClassData {{",
|
||||
reg_class.name
|
||||
);
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, r#"name: "{}","#, reg_class.name);
|
||||
fmtln!(fmt, "index: {},", reg_class.index.index());
|
||||
fmtln!(fmt, "width: {},", reg_class.width);
|
||||
fmtln!(fmt, "bank: {},", reg_class.bank.index());
|
||||
fmtln!(fmt, "toprc: {},", reg_class.toprc.index());
|
||||
fmtln!(fmt, "first: {},", reg_bank.first_unit + reg_class.start);
|
||||
fmtln!(fmt, "subclasses: {:#x},", reg_class.subclass_mask());
|
||||
fmtln!(fmt, "mask: [{}],", mask);
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pinned_reg: {:?},",
|
||||
reg_bank
|
||||
.pinned_reg
|
||||
.map(|index| index + reg_bank.first_unit as u16 + reg_class.start as u16)
|
||||
);
|
||||
fmtln!(fmt, "info: &INFO,");
|
||||
});
|
||||
fmtln!(fmt, "};");
|
||||
|
||||
fmtln!(fmt, "#[allow(dead_code)]");
|
||||
fmtln!(
|
||||
fmt,
|
||||
"pub static {}: RegClass = &{}_DATA;",
|
||||
reg_class.name,
|
||||
reg_class.name
|
||||
);
|
||||
}
|
||||
|
||||
fn gen_regbank_units(reg_bank: &RegBank, fmt: &mut Formatter) {
|
||||
for unit in 0..reg_bank.units {
|
||||
let v = unit + reg_bank.first_unit;
|
||||
if (unit as usize) < reg_bank.names.len() {
|
||||
fmtln!(fmt, "{} = {},", reg_bank.names[unit as usize], v);
|
||||
continue;
|
||||
}
|
||||
fmtln!(fmt, "{}{} = {},", reg_bank.prefix, unit, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_isa(isa: &TargetIsa, fmt: &mut Formatter) {
|
||||
// Emit RegInfo.
|
||||
fmtln!(fmt, "pub static INFO: RegInfo = RegInfo {");
|
||||
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, "banks: &[");
|
||||
// Bank descriptors.
|
||||
fmt.indent(|fmt| {
|
||||
for reg_bank in isa.regs.banks.values() {
|
||||
gen_regbank(fmt, ®_bank);
|
||||
}
|
||||
});
|
||||
fmtln!(fmt, "],");
|
||||
// References to register classes.
|
||||
fmtln!(fmt, "classes: &[");
|
||||
fmt.indent(|fmt| {
|
||||
for reg_class in isa.regs.classes.values() {
|
||||
fmtln!(fmt, "&{}_DATA,", reg_class.name);
|
||||
}
|
||||
});
|
||||
fmtln!(fmt, "],");
|
||||
});
|
||||
fmtln!(fmt, "};");
|
||||
|
||||
// Register class descriptors.
|
||||
for rc in isa.regs.classes.values() {
|
||||
gen_regclass(&isa, rc, fmt);
|
||||
}
|
||||
|
||||
// Emit constants for all the register units.
|
||||
fmtln!(fmt, "#[allow(dead_code, non_camel_case_types)]");
|
||||
fmtln!(fmt, "#[derive(Clone, Copy)]");
|
||||
fmtln!(fmt, "pub enum RU {");
|
||||
fmt.indent(|fmt| {
|
||||
for reg_bank in isa.regs.banks.values() {
|
||||
gen_regbank_units(reg_bank, fmt);
|
||||
}
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
|
||||
// Emit Into conversion for the RU class.
|
||||
fmtln!(fmt, "impl Into<RegUnit> for RU {");
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, "fn into(self) -> RegUnit {");
|
||||
fmt.indent(|fmt| {
|
||||
fmtln!(fmt, "self as RegUnit");
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
});
|
||||
fmtln!(fmt, "}");
|
||||
}
|
||||
|
||||
pub(crate) fn generate(isa: &TargetIsa, filename: &str, out_dir: &str) -> Result<(), error::Error> {
|
||||
let mut fmt = Formatter::new();
|
||||
gen_isa(&isa, &mut fmt);
|
||||
fmt.update_file(filename, out_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::cdsl::instructions::InstructionPredicateMap;
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::recipes::Recipes;
|
||||
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
|
||||
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
|
||||
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
@@ -11,61 +8,8 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup {
|
||||
setting.build()
|
||||
}
|
||||
|
||||
fn define_regs() -> IsaRegs {
|
||||
let mut regs = IsaRegsBuilder::new();
|
||||
|
||||
let builder = RegBankBuilder::new("FloatRegs", "s")
|
||||
.units(64)
|
||||
.track_pressure(true);
|
||||
let float_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("IntRegs", "r")
|
||||
.units(16)
|
||||
.track_pressure(true);
|
||||
let int_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("FlagRegs", "")
|
||||
.units(1)
|
||||
.names(vec!["nzcv"])
|
||||
.track_pressure(false);
|
||||
let flag_reg = regs.add_bank(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("S", float_regs).count(32);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("D", float_regs).width(2);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("Q", float_regs).width(4);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
|
||||
regs.add_class(builder);
|
||||
|
||||
regs.build()
|
||||
}
|
||||
|
||||
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
|
||||
let settings = define_settings(&shared_defs.settings);
|
||||
let regs = define_regs();
|
||||
|
||||
let cpu_modes = vec![];
|
||||
|
||||
// TODO implement arm32 recipes.
|
||||
let recipes = Recipes::new();
|
||||
|
||||
// TODO implement arm32 encodings and predicates.
|
||||
let encodings_predicates = InstructionPredicateMap::new();
|
||||
|
||||
TargetIsa::new(
|
||||
"arm32",
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
)
|
||||
TargetIsa::new("arm32", settings)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::cdsl::instructions::InstructionPredicateMap;
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::recipes::Recipes;
|
||||
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
|
||||
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
|
||||
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
@@ -14,57 +11,8 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup {
|
||||
setting.build()
|
||||
}
|
||||
|
||||
fn define_registers() -> IsaRegs {
|
||||
let mut regs = IsaRegsBuilder::new();
|
||||
|
||||
// The `x31` regunit serves as the stack pointer / zero register depending on context. We
|
||||
// reserve it and don't model the difference.
|
||||
let builder = RegBankBuilder::new("IntRegs", "x")
|
||||
.units(32)
|
||||
.track_pressure(true);
|
||||
let int_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("FloatRegs", "v")
|
||||
.units(32)
|
||||
.track_pressure(true);
|
||||
let float_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("FlagRegs", "")
|
||||
.units(1)
|
||||
.names(vec!["nzcv"])
|
||||
.track_pressure(false);
|
||||
let flag_reg = regs.add_bank(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
|
||||
regs.add_class(builder);
|
||||
|
||||
regs.build()
|
||||
}
|
||||
|
||||
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
|
||||
let settings = define_settings(&shared_defs.settings);
|
||||
let regs = define_registers();
|
||||
|
||||
let cpu_modes = vec![];
|
||||
|
||||
// TODO implement arm64 recipes.
|
||||
let recipes = Recipes::new();
|
||||
|
||||
// TODO implement arm64 encodings and predicates.
|
||||
let encodings_predicates = InstructionPredicateMap::new();
|
||||
|
||||
TargetIsa::new(
|
||||
"arm64",
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
)
|
||||
TargetIsa::new("arm64", settings)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ use std::fmt;
|
||||
|
||||
mod arm32;
|
||||
mod arm64;
|
||||
mod riscv;
|
||||
mod s390x;
|
||||
pub(crate) mod x86;
|
||||
|
||||
/// Represents known ISA target.
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum Isa {
|
||||
Riscv,
|
||||
X86,
|
||||
Arm32,
|
||||
Arm64,
|
||||
@@ -31,7 +29,6 @@ impl Isa {
|
||||
/// Creates isa target from arch.
|
||||
pub fn from_arch(arch: &str) -> Option<Self> {
|
||||
match arch {
|
||||
"riscv" => Some(Isa::Riscv),
|
||||
"aarch64" => Some(Isa::Arm64),
|
||||
"s390x" => Some(Isa::S390x),
|
||||
x if ["x86_64", "i386", "i586", "i686"].contains(&x) => Some(Isa::X86),
|
||||
@@ -42,7 +39,7 @@ impl Isa {
|
||||
|
||||
/// Returns all supported isa targets.
|
||||
pub fn all() -> &'static [Isa] {
|
||||
&[Isa::Riscv, Isa::X86, Isa::Arm32, Isa::Arm64, Isa::S390x]
|
||||
&[Isa::X86, Isa::Arm32, Isa::Arm64, Isa::S390x]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +47,6 @@ impl fmt::Display for Isa {
|
||||
// These names should be kept in sync with the crate features.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Isa::Riscv => write!(f, "riscv"),
|
||||
Isa::X86 => write!(f, "x86"),
|
||||
Isa::Arm32 => write!(f, "arm32"),
|
||||
Isa::Arm64 => write!(f, "arm64"),
|
||||
@@ -62,7 +58,6 @@ impl fmt::Display for Isa {
|
||||
pub(crate) fn define(isas: &[Isa], shared_defs: &mut SharedDefinitions) -> Vec<TargetIsa> {
|
||||
isas.iter()
|
||||
.map(|isa| match isa {
|
||||
Isa::Riscv => riscv::define(shared_defs),
|
||||
Isa::X86 => x86::define(shared_defs),
|
||||
Isa::Arm32 => arm32::define(shared_defs),
|
||||
Isa::Arm64 => arm64::define(shared_defs),
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
use crate::cdsl::ast::{Apply, Expr, Literal, VarPool};
|
||||
use crate::cdsl::encodings::{Encoding, EncodingBuilder};
|
||||
use crate::cdsl::instructions::{
|
||||
Bindable, BoundInstruction, InstSpec, InstructionPredicateNode, InstructionPredicateRegistry,
|
||||
};
|
||||
use crate::cdsl::recipes::{EncodingRecipeNumber, Recipes};
|
||||
use crate::cdsl::settings::SettingGroup;
|
||||
|
||||
use crate::shared::types::Bool::B1;
|
||||
use crate::shared::types::Float::{F32, F64};
|
||||
use crate::shared::types::Int::{I16, I32, I64, I8};
|
||||
use crate::shared::types::Reference::{R32, R64};
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
|
||||
use super::recipes::RecipeGroup;
|
||||
|
||||
pub(crate) struct PerCpuModeEncodings<'defs> {
|
||||
pub inst_pred_reg: InstructionPredicateRegistry,
|
||||
pub enc32: Vec<Encoding>,
|
||||
pub enc64: Vec<Encoding>,
|
||||
recipes: &'defs Recipes,
|
||||
}
|
||||
|
||||
impl<'defs> PerCpuModeEncodings<'defs> {
|
||||
fn new(recipes: &'defs Recipes) -> Self {
|
||||
Self {
|
||||
inst_pred_reg: InstructionPredicateRegistry::new(),
|
||||
enc32: Vec::new(),
|
||||
enc64: Vec::new(),
|
||||
recipes,
|
||||
}
|
||||
}
|
||||
fn enc(
|
||||
&self,
|
||||
inst: impl Into<InstSpec>,
|
||||
recipe: EncodingRecipeNumber,
|
||||
bits: u16,
|
||||
) -> EncodingBuilder {
|
||||
EncodingBuilder::new(inst.into(), recipe, bits)
|
||||
}
|
||||
fn add32(&mut self, encoding: EncodingBuilder) {
|
||||
self.enc32
|
||||
.push(encoding.build(self.recipes, &mut self.inst_pred_reg));
|
||||
}
|
||||
fn add64(&mut self, encoding: EncodingBuilder) {
|
||||
self.enc64
|
||||
.push(encoding.build(self.recipes, &mut self.inst_pred_reg));
|
||||
}
|
||||
}
|
||||
|
||||
// The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit instructions have 11 as
|
||||
// the two low bits, with bits 6:2 determining the base opcode.
|
||||
//
|
||||
// Encbits for the 32-bit recipes are opcode[6:2] | (funct3 << 5) | ...
|
||||
// The functions below encode the encbits.
|
||||
|
||||
fn load_bits(funct3: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
funct3 << 5
|
||||
}
|
||||
|
||||
fn store_bits(funct3: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
0b01000 | (funct3 << 5)
|
||||
}
|
||||
|
||||
fn branch_bits(funct3: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
0b11000 | (funct3 << 5)
|
||||
}
|
||||
|
||||
fn jalr_bits() -> u16 {
|
||||
// This was previously accepting an argument funct3 of 3 bits and used the following formula:
|
||||
//0b11001 | (funct3 << 5)
|
||||
0b11001
|
||||
}
|
||||
|
||||
fn jal_bits() -> u16 {
|
||||
0b11011
|
||||
}
|
||||
|
||||
fn opimm_bits(funct3: u16, funct7: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
0b00100 | (funct3 << 5) | (funct7 << 8)
|
||||
}
|
||||
|
||||
fn opimm32_bits(funct3: u16, funct7: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
0b00110 | (funct3 << 5) | (funct7 << 8)
|
||||
}
|
||||
|
||||
fn op_bits(funct3: u16, funct7: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
assert!(funct7 <= 0b111_1111);
|
||||
0b01100 | (funct3 << 5) | (funct7 << 8)
|
||||
}
|
||||
|
||||
fn op32_bits(funct3: u16, funct7: u16) -> u16 {
|
||||
assert!(funct3 <= 0b111);
|
||||
assert!(funct7 <= 0b111_1111);
|
||||
0b01110 | (funct3 << 5) | (funct7 << 8)
|
||||
}
|
||||
|
||||
fn lui_bits() -> u16 {
|
||||
0b01101
|
||||
}
|
||||
|
||||
pub(crate) fn define<'defs>(
|
||||
shared_defs: &'defs SharedDefinitions,
|
||||
isa_settings: &SettingGroup,
|
||||
recipes: &'defs RecipeGroup,
|
||||
) -> PerCpuModeEncodings<'defs> {
|
||||
// Instructions shorthands.
|
||||
let shared = &shared_defs.instructions;
|
||||
|
||||
let band = shared.by_name("band");
|
||||
let band_imm = shared.by_name("band_imm");
|
||||
let bor = shared.by_name("bor");
|
||||
let bor_imm = shared.by_name("bor_imm");
|
||||
let br_icmp = shared.by_name("br_icmp");
|
||||
let brz = shared.by_name("brz");
|
||||
let brnz = shared.by_name("brnz");
|
||||
let bxor = shared.by_name("bxor");
|
||||
let bxor_imm = shared.by_name("bxor_imm");
|
||||
let call = shared.by_name("call");
|
||||
let call_indirect = shared.by_name("call_indirect");
|
||||
let copy = shared.by_name("copy");
|
||||
let copy_nop = shared.by_name("copy_nop");
|
||||
let copy_to_ssa = shared.by_name("copy_to_ssa");
|
||||
let fill = shared.by_name("fill");
|
||||
let fill_nop = shared.by_name("fill_nop");
|
||||
let iadd = shared.by_name("iadd");
|
||||
let iadd_imm = shared.by_name("iadd_imm");
|
||||
let iconst = shared.by_name("iconst");
|
||||
let icmp = shared.by_name("icmp");
|
||||
let icmp_imm = shared.by_name("icmp_imm");
|
||||
let imul = shared.by_name("imul");
|
||||
let ishl = shared.by_name("ishl");
|
||||
let ishl_imm = shared.by_name("ishl_imm");
|
||||
let isub = shared.by_name("isub");
|
||||
let jump = shared.by_name("jump");
|
||||
let regmove = shared.by_name("regmove");
|
||||
let spill = shared.by_name("spill");
|
||||
let sshr = shared.by_name("sshr");
|
||||
let sshr_imm = shared.by_name("sshr_imm");
|
||||
let ushr = shared.by_name("ushr");
|
||||
let ushr_imm = shared.by_name("ushr_imm");
|
||||
let return_ = shared.by_name("return");
|
||||
|
||||
// Recipes shorthands, prefixed with r_.
|
||||
let r_copytossa = recipes.by_name("copytossa");
|
||||
let r_fillnull = recipes.by_name("fillnull");
|
||||
let r_icall = recipes.by_name("Icall");
|
||||
let r_icopy = recipes.by_name("Icopy");
|
||||
let r_ii = recipes.by_name("Ii");
|
||||
let r_iicmp = recipes.by_name("Iicmp");
|
||||
let r_iret = recipes.by_name("Iret");
|
||||
let r_irmov = recipes.by_name("Irmov");
|
||||
let r_iz = recipes.by_name("Iz");
|
||||
let r_gp_sp = recipes.by_name("GPsp");
|
||||
let r_gp_fi = recipes.by_name("GPfi");
|
||||
let r_r = recipes.by_name("R");
|
||||
let r_ricmp = recipes.by_name("Ricmp");
|
||||
let r_rshamt = recipes.by_name("Rshamt");
|
||||
let r_sb = recipes.by_name("SB");
|
||||
let r_sb_zero = recipes.by_name("SBzero");
|
||||
let r_stacknull = recipes.by_name("stacknull");
|
||||
let r_u = recipes.by_name("U");
|
||||
let r_uj = recipes.by_name("UJ");
|
||||
let r_uj_call = recipes.by_name("UJcall");
|
||||
|
||||
// Predicates shorthands.
|
||||
let use_m = isa_settings.predicate_by_name("use_m");
|
||||
|
||||
// Definitions.
|
||||
let mut e = PerCpuModeEncodings::new(&recipes.recipes);
|
||||
|
||||
// Basic arithmetic binary instructions are encoded in an R-type instruction.
|
||||
for &(inst, inst_imm, f3, f7) in &[
|
||||
(iadd, Some(iadd_imm), 0b000, 0b000_0000),
|
||||
(isub, None, 0b000, 0b010_0000),
|
||||
(bxor, Some(bxor_imm), 0b100, 0b000_0000),
|
||||
(bor, Some(bor_imm), 0b110, 0b000_0000),
|
||||
(band, Some(band_imm), 0b111, 0b000_0000),
|
||||
] {
|
||||
e.add32(e.enc(inst.bind(I32), r_r, op_bits(f3, f7)));
|
||||
e.add64(e.enc(inst.bind(I64), r_r, op_bits(f3, f7)));
|
||||
|
||||
// Immediate versions for add/xor/or/and.
|
||||
if let Some(inst_imm) = inst_imm {
|
||||
e.add32(e.enc(inst_imm.bind(I32), r_ii, opimm_bits(f3, 0)));
|
||||
e.add64(e.enc(inst_imm.bind(I64), r_ii, opimm_bits(f3, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// 32-bit ops in RV64.
|
||||
e.add64(e.enc(iadd.bind(I32), r_r, op32_bits(0b000, 0b000_0000)));
|
||||
e.add64(e.enc(isub.bind(I32), r_r, op32_bits(0b000, 0b010_0000)));
|
||||
// There are no andiw/oriw/xoriw variations.
|
||||
e.add64(e.enc(iadd_imm.bind(I32), r_ii, opimm32_bits(0b000, 0)));
|
||||
|
||||
// Use iadd_imm with %x0 to materialize constants.
|
||||
e.add32(e.enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0)));
|
||||
e.add64(e.enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0)));
|
||||
e.add64(e.enc(iconst.bind(I64), r_iz, opimm_bits(0b0, 0)));
|
||||
|
||||
// Dynamic shifts have the same masking semantics as the clif base instructions.
|
||||
for &(inst, inst_imm, f3, f7) in &[
|
||||
(ishl, ishl_imm, 0b1, 0b0),
|
||||
(ushr, ushr_imm, 0b101, 0b0),
|
||||
(sshr, sshr_imm, 0b101, 0b10_0000),
|
||||
] {
|
||||
e.add32(e.enc(inst.bind(I32).bind(I32), r_r, op_bits(f3, f7)));
|
||||
e.add64(e.enc(inst.bind(I64).bind(I64), r_r, op_bits(f3, f7)));
|
||||
e.add64(e.enc(inst.bind(I32).bind(I32), r_r, op32_bits(f3, f7)));
|
||||
// Allow i32 shift amounts in 64-bit shifts.
|
||||
e.add64(e.enc(inst.bind(I64).bind(I32), r_r, op_bits(f3, f7)));
|
||||
e.add64(e.enc(inst.bind(I32).bind(I64), r_r, op32_bits(f3, f7)));
|
||||
|
||||
// Immediate shifts.
|
||||
e.add32(e.enc(inst_imm.bind(I32), r_rshamt, opimm_bits(f3, f7)));
|
||||
e.add64(e.enc(inst_imm.bind(I64), r_rshamt, opimm_bits(f3, f7)));
|
||||
e.add64(e.enc(inst_imm.bind(I32), r_rshamt, opimm32_bits(f3, f7)));
|
||||
}
|
||||
|
||||
// Signed and unsigned integer 'less than'. There are no 'w' variants for comparing 32-bit
|
||||
// numbers in RV64.
|
||||
{
|
||||
let mut var_pool = VarPool::new();
|
||||
|
||||
// Helper that creates an instruction predicate for an instruction in the icmp family.
|
||||
let mut icmp_instp = |bound_inst: &BoundInstruction,
|
||||
intcc_field: &'static str|
|
||||
-> InstructionPredicateNode {
|
||||
let x = var_pool.create("x");
|
||||
let y = var_pool.create("y");
|
||||
let cc = Literal::enumerator_for(&shared_defs.imm.intcc, intcc_field);
|
||||
Apply::new(
|
||||
bound_inst.clone().into(),
|
||||
vec![Expr::Literal(cc), Expr::Var(x), Expr::Var(y)],
|
||||
)
|
||||
.inst_predicate(&var_pool)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let icmp_i32 = icmp.bind(I32);
|
||||
let icmp_i64 = icmp.bind(I64);
|
||||
e.add32(
|
||||
e.enc(icmp_i32.clone(), r_ricmp, op_bits(0b010, 0b000_0000))
|
||||
.inst_predicate(icmp_instp(&icmp_i32, "slt")),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(icmp_i64.clone(), r_ricmp, op_bits(0b010, 0b000_0000))
|
||||
.inst_predicate(icmp_instp(&icmp_i64, "slt")),
|
||||
);
|
||||
|
||||
e.add32(
|
||||
e.enc(icmp_i32.clone(), r_ricmp, op_bits(0b011, 0b000_0000))
|
||||
.inst_predicate(icmp_instp(&icmp_i32, "ult")),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(icmp_i64.clone(), r_ricmp, op_bits(0b011, 0b000_0000))
|
||||
.inst_predicate(icmp_instp(&icmp_i64, "ult")),
|
||||
);
|
||||
|
||||
// Immediate variants.
|
||||
let icmp_i32 = icmp_imm.bind(I32);
|
||||
let icmp_i64 = icmp_imm.bind(I64);
|
||||
e.add32(
|
||||
e.enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b010, 0))
|
||||
.inst_predicate(icmp_instp(&icmp_i32, "slt")),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b010, 0))
|
||||
.inst_predicate(icmp_instp(&icmp_i64, "slt")),
|
||||
);
|
||||
|
||||
e.add32(
|
||||
e.enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b011, 0))
|
||||
.inst_predicate(icmp_instp(&icmp_i32, "ult")),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b011, 0))
|
||||
.inst_predicate(icmp_instp(&icmp_i64, "ult")),
|
||||
);
|
||||
}
|
||||
|
||||
// Integer constants with the low 12 bits clear are materialized by lui.
|
||||
e.add32(e.enc(iconst.bind(I32), r_u, lui_bits()));
|
||||
e.add64(e.enc(iconst.bind(I32), r_u, lui_bits()));
|
||||
e.add64(e.enc(iconst.bind(I64), r_u, lui_bits()));
|
||||
|
||||
// "M" Standard Extension for Integer Multiplication and Division.
|
||||
// Gated by the `use_m` flag.
|
||||
e.add32(
|
||||
e.enc(imul.bind(I32), r_r, op_bits(0b000, 0b0000_0001))
|
||||
.isa_predicate(use_m),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(imul.bind(I64), r_r, op_bits(0b000, 0b0000_0001))
|
||||
.isa_predicate(use_m),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(imul.bind(I32), r_r, op32_bits(0b000, 0b0000_0001))
|
||||
.isa_predicate(use_m),
|
||||
);
|
||||
|
||||
// Control flow.
|
||||
|
||||
// Unconditional branches.
|
||||
e.add32(e.enc(jump, r_uj, jal_bits()));
|
||||
e.add64(e.enc(jump, r_uj, jal_bits()));
|
||||
e.add32(e.enc(call, r_uj_call, jal_bits()));
|
||||
e.add64(e.enc(call, r_uj_call, jal_bits()));
|
||||
|
||||
// Conditional branches.
|
||||
{
|
||||
let mut var_pool = VarPool::new();
|
||||
|
||||
// Helper that creates an instruction predicate for an instruction in the icmp family.
|
||||
let mut br_icmp_instp = |bound_inst: &BoundInstruction,
|
||||
intcc_field: &'static str|
|
||||
-> InstructionPredicateNode {
|
||||
let x = var_pool.create("x");
|
||||
let y = var_pool.create("y");
|
||||
let dest = var_pool.create("dest");
|
||||
let args = var_pool.create("args");
|
||||
let cc = Literal::enumerator_for(&shared_defs.imm.intcc, intcc_field);
|
||||
Apply::new(
|
||||
bound_inst.clone().into(),
|
||||
vec![
|
||||
Expr::Literal(cc),
|
||||
Expr::Var(x),
|
||||
Expr::Var(y),
|
||||
Expr::Var(dest),
|
||||
Expr::Var(args),
|
||||
],
|
||||
)
|
||||
.inst_predicate(&var_pool)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let br_icmp_i32 = br_icmp.bind(I32);
|
||||
let br_icmp_i64 = br_icmp.bind(I64);
|
||||
for &(cond, f3) in &[
|
||||
("eq", 0b000),
|
||||
("ne", 0b001),
|
||||
("slt", 0b100),
|
||||
("sge", 0b101),
|
||||
("ult", 0b110),
|
||||
("uge", 0b111),
|
||||
] {
|
||||
e.add32(
|
||||
e.enc(br_icmp_i32.clone(), r_sb, branch_bits(f3))
|
||||
.inst_predicate(br_icmp_instp(&br_icmp_i32, cond)),
|
||||
);
|
||||
e.add64(
|
||||
e.enc(br_icmp_i64.clone(), r_sb, branch_bits(f3))
|
||||
.inst_predicate(br_icmp_instp(&br_icmp_i64, cond)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for &(inst, f3) in &[(brz, 0b000), (brnz, 0b001)] {
|
||||
e.add32(e.enc(inst.bind(I32), r_sb_zero, branch_bits(f3)));
|
||||
e.add64(e.enc(inst.bind(I64), r_sb_zero, branch_bits(f3)));
|
||||
e.add32(e.enc(inst.bind(B1), r_sb_zero, branch_bits(f3)));
|
||||
e.add64(e.enc(inst.bind(B1), r_sb_zero, branch_bits(f3)));
|
||||
}
|
||||
|
||||
// Returns are a special case of jalr_bits using %x1 to hold the return address.
|
||||
// The return address is provided by a special-purpose `link` return value that
|
||||
// is added by legalize_signature().
|
||||
e.add32(e.enc(return_, r_iret, jalr_bits()));
|
||||
e.add64(e.enc(return_, r_iret, jalr_bits()));
|
||||
e.add32(e.enc(call_indirect.bind(I32), r_icall, jalr_bits()));
|
||||
e.add64(e.enc(call_indirect.bind(I64), r_icall, jalr_bits()));
|
||||
|
||||
// Spill and fill.
|
||||
e.add32(e.enc(spill.bind(I32), r_gp_sp, store_bits(0b010)));
|
||||
e.add64(e.enc(spill.bind(I32), r_gp_sp, store_bits(0b010)));
|
||||
e.add64(e.enc(spill.bind(I64), r_gp_sp, store_bits(0b011)));
|
||||
e.add32(e.enc(fill.bind(I32), r_gp_fi, load_bits(0b010)));
|
||||
e.add64(e.enc(fill.bind(I32), r_gp_fi, load_bits(0b010)));
|
||||
e.add64(e.enc(fill.bind(I64), r_gp_fi, load_bits(0b011)));
|
||||
|
||||
// No-op fills, created by late-stage redundant-fill removal.
|
||||
for &ty in &[I64, I32] {
|
||||
e.add64(e.enc(fill_nop.bind(ty), r_fillnull, 0));
|
||||
e.add32(e.enc(fill_nop.bind(ty), r_fillnull, 0));
|
||||
}
|
||||
e.add64(e.enc(fill_nop.bind(B1), r_fillnull, 0));
|
||||
e.add32(e.enc(fill_nop.bind(B1), r_fillnull, 0));
|
||||
|
||||
// Register copies.
|
||||
e.add32(e.enc(copy.bind(I32), r_icopy, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy.bind(I64), r_icopy, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy.bind(I32), r_icopy, opimm32_bits(0b000, 0)));
|
||||
|
||||
e.add32(e.enc(regmove.bind(I32), r_irmov, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(regmove.bind(I64), r_irmov, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(regmove.bind(I32), r_irmov, opimm32_bits(0b000, 0)));
|
||||
|
||||
e.add32(e.enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0)));
|
||||
e.add32(e.enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0)));
|
||||
|
||||
// Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn
|
||||
// into a no-op.
|
||||
// The same encoding is generated for both the 64- and 32-bit architectures.
|
||||
for &ty in &[I64, I32, I16, I8] {
|
||||
e.add32(e.enc(copy_nop.bind(ty), r_stacknull, 0));
|
||||
e.add64(e.enc(copy_nop.bind(ty), r_stacknull, 0));
|
||||
}
|
||||
for &ty in &[F64, F32] {
|
||||
e.add32(e.enc(copy_nop.bind(ty), r_stacknull, 0));
|
||||
e.add64(e.enc(copy_nop.bind(ty), r_stacknull, 0));
|
||||
}
|
||||
|
||||
// Copy-to-SSA
|
||||
e.add32(e.enc(copy_to_ssa.bind(I32), r_copytossa, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy_to_ssa.bind(I64), r_copytossa, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy_to_ssa.bind(I32), r_copytossa, opimm32_bits(0b000, 0)));
|
||||
e.add32(e.enc(copy_to_ssa.bind(B1), r_copytossa, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy_to_ssa.bind(B1), r_copytossa, opimm_bits(0b000, 0)));
|
||||
e.add32(e.enc(copy_to_ssa.bind(R32), r_copytossa, opimm_bits(0b000, 0)));
|
||||
e.add64(e.enc(copy_to_ssa.bind(R64), r_copytossa, opimm_bits(0b000, 0)));
|
||||
|
||||
e
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
use crate::cdsl::cpu_modes::CpuMode;
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
|
||||
use crate::cdsl::settings::{PredicateNode, SettingGroup, SettingGroupBuilder};
|
||||
|
||||
use crate::shared::types::Float::{F32, F64};
|
||||
use crate::shared::types::Int::{I32, I64};
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
|
||||
mod encodings;
|
||||
mod recipes;
|
||||
|
||||
fn define_settings(shared: &SettingGroup) -> SettingGroup {
|
||||
let mut setting = SettingGroupBuilder::new("riscv");
|
||||
|
||||
let supports_m = setting.add_bool(
|
||||
"supports_m",
|
||||
"CPU supports the 'M' extension (mul/div)",
|
||||
"",
|
||||
false,
|
||||
);
|
||||
let supports_a = setting.add_bool(
|
||||
"supports_a",
|
||||
"CPU supports the 'A' extension (atomics)",
|
||||
"",
|
||||
false,
|
||||
);
|
||||
let supports_f = setting.add_bool(
|
||||
"supports_f",
|
||||
"CPU supports the 'F' extension (float)",
|
||||
"",
|
||||
false,
|
||||
);
|
||||
let supports_d = setting.add_bool(
|
||||
"supports_d",
|
||||
"CPU supports the 'D' extension (double)",
|
||||
"",
|
||||
false,
|
||||
);
|
||||
|
||||
let enable_m = setting.add_bool(
|
||||
"enable_m",
|
||||
"Enable the use of 'M' instructions if available",
|
||||
"",
|
||||
true,
|
||||
);
|
||||
|
||||
setting.add_bool(
|
||||
"enable_e",
|
||||
"Enable the 'RV32E' instruction set with only 16 registers",
|
||||
"",
|
||||
false,
|
||||
);
|
||||
|
||||
let shared_enable_atomics = shared.get_bool("enable_atomics");
|
||||
let shared_enable_float = shared.get_bool("enable_float");
|
||||
let shared_enable_simd = shared.get_bool("enable_simd");
|
||||
|
||||
setting.add_predicate("use_m", predicate!(supports_m && enable_m));
|
||||
setting.add_predicate("use_a", predicate!(supports_a && shared_enable_atomics));
|
||||
setting.add_predicate("use_f", predicate!(supports_f && shared_enable_float));
|
||||
setting.add_predicate("use_d", predicate!(supports_d && shared_enable_float));
|
||||
setting.add_predicate(
|
||||
"full_float",
|
||||
predicate!(shared_enable_simd && supports_f && supports_d),
|
||||
);
|
||||
|
||||
setting.build()
|
||||
}
|
||||
|
||||
fn define_registers() -> IsaRegs {
|
||||
let mut regs = IsaRegsBuilder::new();
|
||||
|
||||
let builder = RegBankBuilder::new("IntRegs", "x")
|
||||
.units(32)
|
||||
.track_pressure(true);
|
||||
let int_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("FloatRegs", "f")
|
||||
.units(32)
|
||||
.track_pressure(true);
|
||||
let float_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
|
||||
regs.add_class(builder);
|
||||
|
||||
regs.build()
|
||||
}
|
||||
|
||||
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
|
||||
let settings = define_settings(&shared_defs.settings);
|
||||
let regs = define_registers();
|
||||
|
||||
// CPU modes for 32-bit and 64-bit operation.
|
||||
let mut rv_32 = CpuMode::new("RV32");
|
||||
let mut rv_64 = CpuMode::new("RV64");
|
||||
|
||||
let expand = shared_defs.transform_groups.by_name("expand");
|
||||
let narrow_no_flags = shared_defs.transform_groups.by_name("narrow_no_flags");
|
||||
|
||||
rv_32.legalize_monomorphic(expand);
|
||||
rv_32.legalize_default(narrow_no_flags);
|
||||
rv_32.legalize_type(I32, expand);
|
||||
rv_32.legalize_type(F32, expand);
|
||||
rv_32.legalize_type(F64, expand);
|
||||
|
||||
rv_64.legalize_monomorphic(expand);
|
||||
rv_64.legalize_default(narrow_no_flags);
|
||||
rv_64.legalize_type(I32, expand);
|
||||
rv_64.legalize_type(I64, expand);
|
||||
rv_64.legalize_type(F32, expand);
|
||||
rv_64.legalize_type(F64, expand);
|
||||
|
||||
let recipes = recipes::define(shared_defs, ®s);
|
||||
|
||||
let encodings = encodings::define(shared_defs, &settings, &recipes);
|
||||
rv_32.set_encodings(encodings.enc32);
|
||||
rv_64.set_encodings(encodings.enc64);
|
||||
let encodings_predicates = encodings.inst_pred_reg.extract();
|
||||
|
||||
let recipes = recipes.collect();
|
||||
|
||||
let cpu_modes = vec![rv_32, rv_64];
|
||||
|
||||
TargetIsa::new(
|
||||
"riscv",
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
)
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::cdsl::instructions::InstructionPredicate;
|
||||
use crate::cdsl::recipes::{EncodingRecipeBuilder, EncodingRecipeNumber, Recipes, Stack};
|
||||
use crate::cdsl::regs::IsaRegs;
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
|
||||
/// An helper to create recipes and use them when defining the RISCV encodings.
|
||||
pub(crate) struct RecipeGroup {
|
||||
/// The actualy list of recipes explicitly created in this file.
|
||||
pub recipes: Recipes,
|
||||
|
||||
/// Provides fast lookup from a name to an encoding recipe.
|
||||
name_to_recipe: HashMap<String, EncodingRecipeNumber>,
|
||||
}
|
||||
|
||||
impl RecipeGroup {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
recipes: Recipes::new(),
|
||||
name_to_recipe: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, builder: EncodingRecipeBuilder) {
|
||||
assert!(
|
||||
self.name_to_recipe.get(&builder.name).is_none(),
|
||||
"riscv recipe '{}' created twice",
|
||||
builder.name
|
||||
);
|
||||
let name = builder.name.clone();
|
||||
let number = self.recipes.push(builder.build());
|
||||
self.name_to_recipe.insert(name, number);
|
||||
}
|
||||
|
||||
pub fn by_name(&self, name: &str) -> EncodingRecipeNumber {
|
||||
*self
|
||||
.name_to_recipe
|
||||
.get(name)
|
||||
.unwrap_or_else(|| panic!("unknown riscv recipe name {}", name))
|
||||
}
|
||||
|
||||
pub fn collect(self) -> Recipes {
|
||||
self.recipes
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn define(shared_defs: &SharedDefinitions, regs: &IsaRegs) -> RecipeGroup {
|
||||
let formats = &shared_defs.formats;
|
||||
|
||||
// Register classes shorthands.
|
||||
let gpr = regs.class_by_name("GPR");
|
||||
|
||||
// Definitions.
|
||||
let mut recipes = RecipeGroup::new();
|
||||
|
||||
// R-type 32-bit instructions: These are mostly binary arithmetic instructions.
|
||||
// The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8)
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("R", &formats.binary, 4)
|
||||
.operands_in(vec![gpr, gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// R-type with an immediate shift amount instead of rs2.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Rshamt", &formats.binary_imm64, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.emit("put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// R-type encoding of an integer comparison.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Ricmp", &formats.int_compare, 4)
|
||||
.operands_in(vec![gpr, gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
|
||||
);
|
||||
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Ii", &formats.binary_imm64, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.inst_predicate(InstructionPredicate::new_is_signed_int(
|
||||
&*formats.binary_imm64,
|
||||
"imm",
|
||||
12,
|
||||
0,
|
||||
))
|
||||
.emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// I-type instruction with a hardcoded %x0 rs1.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Iz", &formats.unary_imm, 4)
|
||||
.operands_out(vec![gpr])
|
||||
.inst_predicate(InstructionPredicate::new_is_signed_int(
|
||||
&formats.unary_imm,
|
||||
"imm",
|
||||
12,
|
||||
0,
|
||||
))
|
||||
.emit("put_i(bits, 0, imm.into(), out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// I-type encoding of an integer comparison.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Iicmp", &formats.int_compare_imm, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.inst_predicate(InstructionPredicate::new_is_signed_int(
|
||||
&formats.int_compare_imm,
|
||||
"imm",
|
||||
12,
|
||||
0,
|
||||
))
|
||||
.emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// I-type encoding for `jalr` as a return instruction. We won't use the immediate offset. The
|
||||
// variable return values are not encoded.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Iret", &formats.multiary, 4).emit(
|
||||
r#"
|
||||
// Return instructions are always a jalr to %x1.
|
||||
// The return address is provided as a special-purpose link argument.
|
||||
put_i(
|
||||
bits,
|
||||
1, // rs1 = %x1
|
||||
0, // no offset.
|
||||
0, // rd = %x0: no address written.
|
||||
sink,
|
||||
);
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// I-type encoding for `jalr` as a call_indirect.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Icall", &formats.call_indirect, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.emit(
|
||||
r#"
|
||||
// call_indirect instructions are jalr with rd=%x1.
|
||||
put_i(
|
||||
bits,
|
||||
in_reg0,
|
||||
0, // no offset.
|
||||
1, // rd = %x1: link register.
|
||||
sink,
|
||||
);
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Copy of a GPR is implemented as addi x, 0.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Icopy", &formats.unary, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.operands_out(vec![gpr])
|
||||
.emit("put_i(bits, in_reg0, 0, out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// Same for a GPR regmove.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("Irmov", &formats.reg_move, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.emit("put_i(bits, src, 0, dst, sink);"),
|
||||
);
|
||||
|
||||
// Same for copy-to-SSA -- GPR regmove.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("copytossa", &formats.copy_to_ssa, 4)
|
||||
// No operands_in to mention, because a source register is specified directly.
|
||||
.operands_out(vec![gpr])
|
||||
.emit("put_i(bits, src, 0, out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// U-type instructions have a 20-bit immediate that targets bits 12-31.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("U", &formats.unary_imm, 4)
|
||||
.operands_out(vec![gpr])
|
||||
.inst_predicate(InstructionPredicate::new_is_signed_int(
|
||||
&formats.unary_imm,
|
||||
"imm",
|
||||
32,
|
||||
12,
|
||||
))
|
||||
.emit("put_u(bits, imm.into(), out_reg0, sink);"),
|
||||
);
|
||||
|
||||
// UJ-type unconditional branch instructions.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("UJ", &formats.jump, 4)
|
||||
.branch_range((0, 21))
|
||||
.emit(
|
||||
r#"
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_uj(bits, disp, 0, sink);
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
recipes.push(EncodingRecipeBuilder::new("UJcall", &formats.call, 4).emit(
|
||||
r#"
|
||||
sink.reloc_external(func.srclocs[inst],
|
||||
Reloc::RiscvCall,
|
||||
&func.dfg.ext_funcs[func_ref].name,
|
||||
0);
|
||||
// rd=%x1 is the standard link register.
|
||||
put_uj(bits, 0, 1, sink);
|
||||
"#,
|
||||
));
|
||||
|
||||
// SB-type branch instructions.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("SB", &formats.branch_icmp, 4)
|
||||
.operands_in(vec![gpr, gpr])
|
||||
.branch_range((0, 13))
|
||||
.emit(
|
||||
r#"
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_sb(bits, disp, in_reg0, in_reg1, sink);
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// SB-type branch instruction with rs2 fixed to zero.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("SBzero", &formats.branch, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.branch_range((0, 13))
|
||||
.emit(
|
||||
r#"
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_sb(bits, disp, in_reg0, 0, sink);
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Spill of a GPR.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("GPsp", &formats.unary, 4)
|
||||
.operands_in(vec![gpr])
|
||||
.operands_out(vec![Stack::new(gpr)])
|
||||
.emit("unimplemented!();"),
|
||||
);
|
||||
|
||||
// Fill of a GPR.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("GPfi", &formats.unary, 4)
|
||||
.operands_in(vec![Stack::new(gpr)])
|
||||
.operands_out(vec![gpr])
|
||||
.emit("unimplemented!();"),
|
||||
);
|
||||
|
||||
// Stack-slot to same stack-slot copy, which is guaranteed to turn into a no-op.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("stacknull", &formats.unary, 0)
|
||||
.operands_in(vec![Stack::new(gpr)])
|
||||
.operands_out(vec![Stack::new(gpr)])
|
||||
.emit(""),
|
||||
);
|
||||
|
||||
// No-op fills, created by late-stage redundant-fill removal.
|
||||
recipes.push(
|
||||
EncodingRecipeBuilder::new("fillnull", &formats.unary, 0)
|
||||
.operands_in(vec![Stack::new(gpr)])
|
||||
.operands_out(vec![gpr])
|
||||
.clobbers_flags(false)
|
||||
.emit(""),
|
||||
);
|
||||
|
||||
recipes
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::cdsl::instructions::InstructionPredicateMap;
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::recipes::Recipes;
|
||||
use crate::cdsl::regs::IsaRegsBuilder;
|
||||
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
|
||||
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
@@ -45,18 +42,6 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup {
|
||||
|
||||
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
|
||||
let settings = define_settings(&shared_defs.settings);
|
||||
let regs = IsaRegsBuilder::new().build();
|
||||
let recipes = Recipes::new();
|
||||
let encodings_predicates = InstructionPredicateMap::new();
|
||||
|
||||
let cpu_modes = vec![];
|
||||
|
||||
TargetIsa::new(
|
||||
"s390x",
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
)
|
||||
TargetIsa::new("s390x", settings)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,723 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::cdsl::instructions::{
|
||||
AllInstructions, InstructionBuilder as Inst, InstructionGroup, InstructionGroupBuilder,
|
||||
};
|
||||
use crate::cdsl::operands::Operand;
|
||||
use crate::cdsl::types::ValueType;
|
||||
use crate::cdsl::typevar::{Interval, TypeSetBuilder, TypeVar};
|
||||
use crate::shared::entities::EntityRefs;
|
||||
use crate::shared::formats::Formats;
|
||||
use crate::shared::immediates::Immediates;
|
||||
use crate::shared::types;
|
||||
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub(crate) fn define(
|
||||
mut all_instructions: &mut AllInstructions,
|
||||
formats: &Formats,
|
||||
immediates: &Immediates,
|
||||
entities: &EntityRefs,
|
||||
) -> InstructionGroup {
|
||||
let mut ig = InstructionGroupBuilder::new(&mut all_instructions);
|
||||
|
||||
let iflags: &TypeVar = &ValueType::Special(types::Flag::IFlags.into()).into();
|
||||
|
||||
let iWord = &TypeVar::new(
|
||||
"iWord",
|
||||
"A scalar integer machine word",
|
||||
TypeSetBuilder::new().ints(32..64).build(),
|
||||
);
|
||||
let nlo = &Operand::new("nlo", iWord).with_doc("Low part of numerator");
|
||||
let nhi = &Operand::new("nhi", iWord).with_doc("High part of numerator");
|
||||
let d = &Operand::new("d", iWord).with_doc("Denominator");
|
||||
let q = &Operand::new("q", iWord).with_doc("Quotient");
|
||||
let r = &Operand::new("r", iWord).with_doc("Remainder");
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_udivmodx",
|
||||
r#"
|
||||
Extended unsigned division.
|
||||
|
||||
Concatenate the bits in `nhi` and `nlo` to form the numerator.
|
||||
Interpret the bits as an unsigned number and divide by the unsigned
|
||||
denominator `d`. Trap when `d` is zero or if the quotient is larger
|
||||
than the range of the output.
|
||||
|
||||
Return both quotient and remainder.
|
||||
"#,
|
||||
&formats.ternary,
|
||||
)
|
||||
.operands_in(vec![nlo, nhi, d])
|
||||
.operands_out(vec![q, r])
|
||||
.can_trap(true),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_sdivmodx",
|
||||
r#"
|
||||
Extended signed division.
|
||||
|
||||
Concatenate the bits in `nhi` and `nlo` to form the numerator.
|
||||
Interpret the bits as a signed number and divide by the signed
|
||||
denominator `d`. Trap when `d` is zero or if the quotient is outside
|
||||
the range of the output.
|
||||
|
||||
Return both quotient and remainder.
|
||||
"#,
|
||||
&formats.ternary,
|
||||
)
|
||||
.operands_in(vec![nlo, nhi, d])
|
||||
.operands_out(vec![q, r])
|
||||
.can_trap(true),
|
||||
);
|
||||
|
||||
let argL = &Operand::new("argL", iWord);
|
||||
let argR = &Operand::new("argR", iWord);
|
||||
let resLo = &Operand::new("resLo", iWord);
|
||||
let resHi = &Operand::new("resHi", iWord);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_umulx",
|
||||
r#"
|
||||
Unsigned integer multiplication, producing a double-length result.
|
||||
|
||||
Polymorphic over all scalar integer types, but does not support vector
|
||||
types.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![argL, argR])
|
||||
.operands_out(vec![resLo, resHi]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_smulx",
|
||||
r#"
|
||||
Signed integer multiplication, producing a double-length result.
|
||||
|
||||
Polymorphic over all scalar integer types, but does not support vector
|
||||
types.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![argL, argR])
|
||||
.operands_out(vec![resLo, resHi]),
|
||||
);
|
||||
|
||||
let Float = &TypeVar::new(
|
||||
"Float",
|
||||
"A scalar or vector floating point number",
|
||||
TypeSetBuilder::new()
|
||||
.floats(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.build(),
|
||||
);
|
||||
let IntTo = &TypeVar::new(
|
||||
"IntTo",
|
||||
"An integer type with the same number of lanes",
|
||||
TypeSetBuilder::new()
|
||||
.ints(32..64)
|
||||
.simd_lanes(Interval::All)
|
||||
.build(),
|
||||
);
|
||||
let x = &Operand::new("x", Float);
|
||||
let a = &Operand::new("a", IntTo);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_cvtt2si",
|
||||
r#"
|
||||
Convert with truncation floating point to signed integer.
|
||||
|
||||
The source floating point operand is converted to a signed integer by
|
||||
rounding towards zero. If the result can't be represented in the output
|
||||
type, returns the smallest signed value the output type can represent.
|
||||
|
||||
This instruction does not trap.
|
||||
"#,
|
||||
&formats.unary,
|
||||
)
|
||||
.operands_in(vec![x])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let f32x4 = &TypeVar::new(
|
||||
"f32x4",
|
||||
"A floating point number",
|
||||
TypeSetBuilder::new()
|
||||
.floats(32..32)
|
||||
.simd_lanes(4..4)
|
||||
.build(),
|
||||
);
|
||||
let i32x4 = &TypeVar::new(
|
||||
"i32x4",
|
||||
"An integer type with the same number of lanes",
|
||||
TypeSetBuilder::new().ints(32..32).simd_lanes(4..4).build(),
|
||||
);
|
||||
let x = &Operand::new("x", i32x4);
|
||||
let a = &Operand::new("a", f32x4);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_vcvtudq2ps",
|
||||
r#"
|
||||
Convert unsigned integer to floating point.
|
||||
|
||||
Convert packed doubleword unsigned integers to packed single-precision floating-point
|
||||
values. This instruction does not trap.
|
||||
"#,
|
||||
&formats.unary,
|
||||
)
|
||||
.operands_in(vec![x])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", Float);
|
||||
let a = &Operand::new("a", Float);
|
||||
let y = &Operand::new("y", Float);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_fmin",
|
||||
r#"
|
||||
Floating point minimum with x86 semantics.
|
||||
|
||||
This is equivalent to the C ternary operator `x < y ? x : y` which
|
||||
differs from `fmin` when either operand is NaN or when comparing
|
||||
+0.0 to -0.0.
|
||||
|
||||
When the two operands don't compare as LT, `y` is returned unchanged,
|
||||
even if it is a signalling NaN.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_fmax",
|
||||
r#"
|
||||
Floating point maximum with x86 semantics.
|
||||
|
||||
This is equivalent to the C ternary operator `x > y ? x : y` which
|
||||
differs from `fmax` when either operand is NaN or when comparing
|
||||
+0.0 to -0.0.
|
||||
|
||||
When the two operands don't compare as GT, `y` is returned unchanged,
|
||||
even if it is a signalling NaN.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", iWord);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_push",
|
||||
r#"
|
||||
Pushes a value onto the stack.
|
||||
|
||||
Decrements the stack pointer and stores the specified value on to the top.
|
||||
|
||||
This is polymorphic in i32 and i64. However, it is only implemented for i64
|
||||
in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
"#,
|
||||
&formats.unary,
|
||||
)
|
||||
.operands_in(vec![x])
|
||||
.other_side_effects(true)
|
||||
.can_store(true),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pop",
|
||||
r#"
|
||||
Pops a value from the stack.
|
||||
|
||||
Loads a value from the top of the stack and then increments the stack
|
||||
pointer.
|
||||
|
||||
This is polymorphic in i32 and i64. However, it is only implemented for i64
|
||||
in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
"#,
|
||||
&formats.nullary,
|
||||
)
|
||||
.operands_out(vec![x])
|
||||
.other_side_effects(true)
|
||||
.can_load(true),
|
||||
);
|
||||
|
||||
let y = &Operand::new("y", iWord);
|
||||
let rflags = &Operand::new("rflags", iflags);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_bsr",
|
||||
r#"
|
||||
Bit Scan Reverse -- returns the bit-index of the most significant 1
|
||||
in the word. Result is undefined if the argument is zero. However, it
|
||||
sets the Z flag depending on the argument, so it is at least easy to
|
||||
detect and handle that case.
|
||||
|
||||
This is polymorphic in i32 and i64. It is implemented for both i64 and
|
||||
i32 in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
"#,
|
||||
&formats.unary,
|
||||
)
|
||||
.operands_in(vec![x])
|
||||
.operands_out(vec![y, rflags]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_bsf",
|
||||
r#"
|
||||
Bit Scan Forwards -- returns the bit-index of the least significant 1
|
||||
in the word. Is otherwise identical to 'bsr', just above.
|
||||
"#,
|
||||
&formats.unary,
|
||||
)
|
||||
.operands_in(vec![x])
|
||||
.operands_out(vec![y, rflags]),
|
||||
);
|
||||
|
||||
let uimm8 = &immediates.uimm8;
|
||||
let TxN = &TypeVar::new(
|
||||
"TxN",
|
||||
"A SIMD vector type",
|
||||
TypeSetBuilder::new()
|
||||
.ints(Interval::All)
|
||||
.floats(Interval::All)
|
||||
.bools(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
let a = &Operand::new("a", TxN).with_doc("A vector value (i.e. held in an XMM register)");
|
||||
let b = &Operand::new("b", TxN).with_doc("A vector value (i.e. held in an XMM register)");
|
||||
let i = &Operand::new("i", uimm8).with_doc("An ordering operand controlling the copying of data from the source to the destination; see PSHUFD in Intel manual for details");
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pshufd",
|
||||
r#"
|
||||
Packed Shuffle Doublewords -- copies data from either memory or lanes in an extended
|
||||
register and re-orders the data according to the passed immediate byte.
|
||||
"#,
|
||||
&formats.binary_imm8,
|
||||
)
|
||||
.operands_in(vec![a, i]) // TODO allow copying from memory here (need more permissive type than TxN)
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pshufb",
|
||||
r#"
|
||||
Packed Shuffle Bytes -- re-orders data in an extended register using a shuffle
|
||||
mask from either memory or another extended register
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![a, b]) // TODO allow re-ordering from memory here (need more permissive type than TxN)
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let mask = &Operand::new("mask", uimm8).with_doc("mask to select lanes from b");
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pblendw",
|
||||
r#"
|
||||
Blend packed words using an immediate mask. Each bit of the 8-bit immediate corresponds to a
|
||||
lane in ``b``: if the bit is set, the lane is copied into ``a``.
|
||||
"#,
|
||||
&formats.ternary_imm8,
|
||||
)
|
||||
.operands_in(vec![a, b, mask])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let Idx = &Operand::new("Idx", uimm8).with_doc("Lane index");
|
||||
let x = &Operand::new("x", TxN);
|
||||
let a = &Operand::new("a", &TxN.lane_of());
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pextr",
|
||||
r#"
|
||||
Extract lane ``Idx`` from ``x``.
|
||||
The lane index, ``Idx``, is an immediate value, not an SSA value. It
|
||||
must indicate a valid lane index for the type of ``x``.
|
||||
"#,
|
||||
&formats.binary_imm8,
|
||||
)
|
||||
.operands_in(vec![x, Idx])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let IBxN = &TypeVar::new(
|
||||
"IBxN",
|
||||
"A SIMD vector type containing only booleans and integers",
|
||||
TypeSetBuilder::new()
|
||||
.ints(Interval::All)
|
||||
.bools(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
let x = &Operand::new("x", IBxN);
|
||||
let y = &Operand::new("y", &IBxN.lane_of()).with_doc("New lane value");
|
||||
let a = &Operand::new("a", IBxN);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pinsr",
|
||||
r#"
|
||||
Insert ``y`` into ``x`` at lane ``Idx``.
|
||||
The lane index, ``Idx``, is an immediate value, not an SSA value. It
|
||||
must indicate a valid lane index for the type of ``x``.
|
||||
"#,
|
||||
&formats.ternary_imm8,
|
||||
)
|
||||
.operands_in(vec![x, y, Idx])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let FxN = &TypeVar::new(
|
||||
"FxN",
|
||||
"A SIMD vector type containing floats",
|
||||
TypeSetBuilder::new()
|
||||
.floats(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
let x = &Operand::new("x", FxN);
|
||||
let y = &Operand::new("y", &FxN.lane_of()).with_doc("New lane value");
|
||||
let a = &Operand::new("a", FxN);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_insertps",
|
||||
r#"
|
||||
Insert a lane of ``y`` into ``x`` at using ``Idx`` to encode both which lane the value is
|
||||
extracted from and which it is inserted to. This is similar to x86_pinsr but inserts
|
||||
floats, which are already stored in an XMM register.
|
||||
"#,
|
||||
&formats.ternary_imm8,
|
||||
)
|
||||
.operands_in(vec![x, y, Idx])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", TxN);
|
||||
let y = &Operand::new("y", TxN);
|
||||
let a = &Operand::new("a", TxN);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_punpckh",
|
||||
r#"
|
||||
Unpack the high-order lanes of ``x`` and ``y`` and interleave into ``a``. With notional
|
||||
i8x4 vectors, where ``x = [x3, x2, x1, x0]`` and ``y = [y3, y2, y1, y0]``, this operation
|
||||
would result in ``a = [y3, x3, y2, x2]`` (using the Intel manual's right-to-left lane
|
||||
ordering).
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_punpckl",
|
||||
r#"
|
||||
Unpack the low-order lanes of ``x`` and ``y`` and interleave into ``a``. With notional
|
||||
i8x4 vectors, where ``x = [x3, x2, x1, x0]`` and ``y = [y3, y2, y1, y0]``, this operation
|
||||
would result in ``a = [y1, x1, y0, x0]`` (using the Intel manual's right-to-left lane
|
||||
ordering).
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", FxN);
|
||||
let y = &Operand::new("y", FxN);
|
||||
let a = &Operand::new("a", FxN);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_movsd",
|
||||
r#"
|
||||
Move the low 64 bits of the float vector ``y`` to the low 64 bits of float vector ``x``
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_movlhps",
|
||||
r#"
|
||||
Move the low 64 bits of the float vector ``y`` to the high 64 bits of float vector ``x``
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let IxN = &TypeVar::new(
|
||||
"IxN",
|
||||
"A SIMD vector type containing integers",
|
||||
TypeSetBuilder::new()
|
||||
.ints(Interval::All)
|
||||
.simd_lanes(Interval::All)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
let I128 = &TypeVar::new(
|
||||
"I128",
|
||||
"A SIMD vector type containing one large integer (due to Cranelift type constraints, \
|
||||
this uses the Cranelift I64X2 type but should be understood as one large value, i.e., the \
|
||||
upper lane is concatenated with the lower lane to form the integer)",
|
||||
TypeSetBuilder::new()
|
||||
.ints(64..64)
|
||||
.simd_lanes(2..2)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", IxN).with_doc("Vector value to shift");
|
||||
let y = &Operand::new("y", I128).with_doc("Number of bits to shift");
|
||||
let a = &Operand::new("a", IxN);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_psll",
|
||||
r#"
|
||||
Shift Packed Data Left Logical -- This implements the behavior of the shared instruction
|
||||
``ishl`` but alters the shift operand to live in an XMM register as expected by the PSLL*
|
||||
family of instructions.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_psrl",
|
||||
r#"
|
||||
Shift Packed Data Right Logical -- This implements the behavior of the shared instruction
|
||||
``ushr`` but alters the shift operand to live in an XMM register as expected by the PSRL*
|
||||
family of instructions.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_psra",
|
||||
r#"
|
||||
Shift Packed Data Right Arithmetic -- This implements the behavior of the shared
|
||||
instruction ``sshr`` but alters the shift operand to live in an XMM register as expected by
|
||||
the PSRA* family of instructions.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let I64x2 = &TypeVar::new(
|
||||
"I64x2",
|
||||
"A SIMD vector type containing two 64-bit integers",
|
||||
TypeSetBuilder::new()
|
||||
.ints(64..64)
|
||||
.simd_lanes(2..2)
|
||||
.includes_scalars(false)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", I64x2);
|
||||
let y = &Operand::new("y", I64x2);
|
||||
let a = &Operand::new("a", I64x2);
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pmullq",
|
||||
r#"
|
||||
Multiply Packed Integers -- Multiply two 64x2 integers and receive a 64x2 result with
|
||||
lane-wise wrapping if the result overflows. This instruction is necessary to add distinct
|
||||
encodings for CPUs with newer vector features.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pmuludq",
|
||||
r#"
|
||||
Multiply Packed Integers -- Using only the bottom 32 bits in each lane, multiply two 64x2
|
||||
unsigned integers and receive a 64x2 result. This instruction avoids the need for handling
|
||||
overflow as in `x86_pmullq`.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", TxN);
|
||||
let y = &Operand::new("y", TxN);
|
||||
let f = &Operand::new("f", iflags);
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_ptest",
|
||||
r#"
|
||||
Logical Compare -- PTEST will set the ZF flag if all bits in the result are 0 of the
|
||||
bitwise AND of the first source operand (first operand) and the second source operand
|
||||
(second operand). PTEST sets the CF flag if all bits in the result are 0 of the bitwise
|
||||
AND of the second source operand (second operand) and the logical NOT of the destination
|
||||
operand (first operand).
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![f]),
|
||||
);
|
||||
|
||||
let x = &Operand::new("x", IxN);
|
||||
let y = &Operand::new("y", IxN);
|
||||
let a = &Operand::new("a", IxN);
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pmaxs",
|
||||
r#"
|
||||
Maximum of Packed Signed Integers -- Compare signed integers in the first and second
|
||||
operand and return the maximum values.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pmaxu",
|
||||
r#"
|
||||
Maximum of Packed Unsigned Integers -- Compare unsigned integers in the first and second
|
||||
operand and return the maximum values.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pmins",
|
||||
r#"
|
||||
Minimum of Packed Signed Integers -- Compare signed integers in the first and second
|
||||
operand and return the minimum values.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_pminu",
|
||||
r#"
|
||||
Minimum of Packed Unsigned Integers -- Compare unsigned integers in the first and second
|
||||
operand and return the minimum values.
|
||||
"#,
|
||||
&formats.binary,
|
||||
)
|
||||
.operands_in(vec![x, y])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let c = &Operand::new("c", uimm8)
|
||||
.with_doc("The number of bytes to shift right; see PALIGNR in Intel manual for details");
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_palignr",
|
||||
r#"
|
||||
Concatenate destination and source operands, extracting a byte-aligned result shifted to
|
||||
the right by `c`.
|
||||
"#,
|
||||
&formats.ternary_imm8,
|
||||
)
|
||||
.operands_in(vec![x, y, c])
|
||||
.operands_out(vec![a]),
|
||||
);
|
||||
|
||||
let i64_t = &TypeVar::new(
|
||||
"i64_t",
|
||||
"A scalar 64bit integer",
|
||||
TypeSetBuilder::new().ints(64..64).build(),
|
||||
);
|
||||
|
||||
let GV = &Operand::new("GV", &entities.global_value);
|
||||
let addr = &Operand::new("addr", i64_t);
|
||||
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_elf_tls_get_addr",
|
||||
r#"
|
||||
Elf tls get addr -- This implements the GD TLS model for ELF. The clobber output should
|
||||
not be used.
|
||||
"#,
|
||||
&formats.unary_global_value,
|
||||
)
|
||||
// This is a bit overly broad to mark as clobbering *all* the registers, because it should
|
||||
// only preserve caller-saved registers. There's no way to indicate this to register
|
||||
// allocation yet, though, so mark as clobbering all registers instead.
|
||||
.clobbers_all_regs(true)
|
||||
.operands_in(vec![GV])
|
||||
.operands_out(vec![addr]),
|
||||
);
|
||||
ig.push(
|
||||
Inst::new(
|
||||
"x86_macho_tls_get_addr",
|
||||
r#"
|
||||
Mach-O tls get addr -- This implements TLS access for Mach-O. The clobber output should
|
||||
not be used.
|
||||
"#,
|
||||
&formats.unary_global_value,
|
||||
)
|
||||
// See above comment for x86_elf_tls_get_addr.
|
||||
.clobbers_all_regs(true)
|
||||
.operands_in(vec![GV])
|
||||
.operands_out(vec![addr]),
|
||||
);
|
||||
|
||||
ig.build()
|
||||
}
|
||||
@@ -1,827 +0,0 @@
|
||||
use crate::cdsl::ast::{constant, var, ExprBuilder, Literal};
|
||||
use crate::cdsl::instructions::{vector, Bindable, InstructionGroup};
|
||||
use crate::cdsl::types::{LaneType, ValueType};
|
||||
use crate::cdsl::xform::TransformGroupBuilder;
|
||||
use crate::shared::types::Float::{F32, F64};
|
||||
use crate::shared::types::Int::{I16, I32, I64, I8};
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
pub(crate) fn define(shared: &mut SharedDefinitions, x86_instructions: &InstructionGroup) {
|
||||
let mut expand = TransformGroupBuilder::new(
|
||||
"x86_expand",
|
||||
r#"
|
||||
Legalize instructions by expansion.
|
||||
|
||||
Use x86-specific instructions if needed."#,
|
||||
)
|
||||
.isa("x86")
|
||||
.chain_with(shared.transform_groups.by_name("expand_flags").id);
|
||||
|
||||
let mut narrow = TransformGroupBuilder::new(
|
||||
"x86_narrow",
|
||||
r#"
|
||||
Legalize instructions by narrowing.
|
||||
|
||||
Use x86-specific instructions if needed."#,
|
||||
)
|
||||
.isa("x86")
|
||||
.chain_with(shared.transform_groups.by_name("narrow_flags").id);
|
||||
|
||||
let mut narrow_avx = TransformGroupBuilder::new(
|
||||
"x86_narrow_avx",
|
||||
r#"
|
||||
Legalize instructions by narrowing with CPU feature checks.
|
||||
|
||||
This special case converts using x86 AVX instructions where available."#,
|
||||
)
|
||||
.isa("x86");
|
||||
// We cannot chain with the x86_narrow group until this group is built, see bottom of this
|
||||
// function for where this is chained.
|
||||
|
||||
let mut widen = TransformGroupBuilder::new(
|
||||
"x86_widen",
|
||||
r#"
|
||||
Legalize instructions by widening.
|
||||
|
||||
Use x86-specific instructions if needed."#,
|
||||
)
|
||||
.isa("x86")
|
||||
.chain_with(shared.transform_groups.by_name("widen").id);
|
||||
|
||||
// List of instructions.
|
||||
let insts = &shared.instructions;
|
||||
let band = insts.by_name("band");
|
||||
let bor = insts.by_name("bor");
|
||||
let clz = insts.by_name("clz");
|
||||
let ctz = insts.by_name("ctz");
|
||||
let fcmp = insts.by_name("fcmp");
|
||||
let fcvt_from_uint = insts.by_name("fcvt_from_uint");
|
||||
let fcvt_to_sint = insts.by_name("fcvt_to_sint");
|
||||
let fcvt_to_uint = insts.by_name("fcvt_to_uint");
|
||||
let fcvt_to_sint_sat = insts.by_name("fcvt_to_sint_sat");
|
||||
let fcvt_to_uint_sat = insts.by_name("fcvt_to_uint_sat");
|
||||
let fmax = insts.by_name("fmax");
|
||||
let fmin = insts.by_name("fmin");
|
||||
let iadd = insts.by_name("iadd");
|
||||
let iconst = insts.by_name("iconst");
|
||||
let imul = insts.by_name("imul");
|
||||
let ineg = insts.by_name("ineg");
|
||||
let isub = insts.by_name("isub");
|
||||
let ishl = insts.by_name("ishl");
|
||||
let ireduce = insts.by_name("ireduce");
|
||||
let popcnt = insts.by_name("popcnt");
|
||||
let sdiv = insts.by_name("sdiv");
|
||||
let selectif = insts.by_name("selectif");
|
||||
let smulhi = insts.by_name("smulhi");
|
||||
let srem = insts.by_name("srem");
|
||||
let tls_value = insts.by_name("tls_value");
|
||||
let udiv = insts.by_name("udiv");
|
||||
let umulhi = insts.by_name("umulhi");
|
||||
let ushr = insts.by_name("ushr");
|
||||
let ushr_imm = insts.by_name("ushr_imm");
|
||||
let urem = insts.by_name("urem");
|
||||
|
||||
let x86_bsf = x86_instructions.by_name("x86_bsf");
|
||||
let x86_bsr = x86_instructions.by_name("x86_bsr");
|
||||
let x86_umulx = x86_instructions.by_name("x86_umulx");
|
||||
let x86_smulx = x86_instructions.by_name("x86_smulx");
|
||||
|
||||
let imm = &shared.imm;
|
||||
|
||||
// Shift by a 64-bit amount is equivalent to a shift by that amount mod 32, so we can reduce
|
||||
// the size of the shift amount. This is useful for x86_32, where an I64 shift amount is
|
||||
// not encodable.
|
||||
let a = var("a");
|
||||
let x = var("x");
|
||||
let y = var("y");
|
||||
let z = var("z");
|
||||
|
||||
for &ty in &[I8, I16, I32] {
|
||||
let ishl_by_i64 = ishl.bind(ty).bind(I64);
|
||||
let ireduce = ireduce.bind(I32);
|
||||
expand.legalize(
|
||||
def!(a = ishl_by_i64(x, y)),
|
||||
vec![def!(z = ireduce(y)), def!(a = ishl(x, z))],
|
||||
);
|
||||
}
|
||||
|
||||
for &ty in &[I8, I16, I32] {
|
||||
let ushr_by_i64 = ushr.bind(ty).bind(I64);
|
||||
let ireduce = ireduce.bind(I32);
|
||||
expand.legalize(
|
||||
def!(a = ushr_by_i64(x, y)),
|
||||
vec![def!(z = ireduce(y)), def!(a = ishl(x, z))],
|
||||
);
|
||||
}
|
||||
|
||||
// Division and remainder.
|
||||
//
|
||||
// The srem expansion requires custom code because srem INT_MIN, -1 is not
|
||||
// allowed to trap. The other ops need to check avoid_div_traps.
|
||||
expand.custom_legalize(sdiv, "expand_sdivrem");
|
||||
expand.custom_legalize(srem, "expand_sdivrem");
|
||||
expand.custom_legalize(udiv, "expand_udivrem");
|
||||
expand.custom_legalize(urem, "expand_udivrem");
|
||||
|
||||
// Double length (widening) multiplication.
|
||||
let a = var("a");
|
||||
let x = var("x");
|
||||
let y = var("y");
|
||||
let a1 = var("a1");
|
||||
let a2 = var("a2");
|
||||
let res_lo = var("res_lo");
|
||||
let res_hi = var("res_hi");
|
||||
|
||||
expand.legalize(
|
||||
def!(res_hi = umulhi(x, y)),
|
||||
vec![def!((res_lo, res_hi) = x86_umulx(x, y))],
|
||||
);
|
||||
|
||||
expand.legalize(
|
||||
def!(res_hi = smulhi(x, y)),
|
||||
vec![def!((res_lo, res_hi) = x86_smulx(x, y))],
|
||||
);
|
||||
|
||||
// Floating point condition codes.
|
||||
//
|
||||
// The 8 condition codes in `supported_floatccs` are directly supported by a
|
||||
// `ucomiss` or `ucomisd` instruction. The remaining codes need legalization
|
||||
// patterns.
|
||||
|
||||
let floatcc_eq = Literal::enumerator_for(&imm.floatcc, "eq");
|
||||
let floatcc_ord = Literal::enumerator_for(&imm.floatcc, "ord");
|
||||
let floatcc_ueq = Literal::enumerator_for(&imm.floatcc, "ueq");
|
||||
let floatcc_ne = Literal::enumerator_for(&imm.floatcc, "ne");
|
||||
let floatcc_uno = Literal::enumerator_for(&imm.floatcc, "uno");
|
||||
let floatcc_one = Literal::enumerator_for(&imm.floatcc, "one");
|
||||
|
||||
// Equality needs an explicit `ord` test which checks the parity bit.
|
||||
expand.legalize(
|
||||
def!(a = fcmp(floatcc_eq, x, y)),
|
||||
vec![
|
||||
def!(a1 = fcmp(floatcc_ord, x, y)),
|
||||
def!(a2 = fcmp(floatcc_ueq, x, y)),
|
||||
def!(a = band(a1, a2)),
|
||||
],
|
||||
);
|
||||
expand.legalize(
|
||||
def!(a = fcmp(floatcc_ne, x, y)),
|
||||
vec![
|
||||
def!(a1 = fcmp(floatcc_uno, x, y)),
|
||||
def!(a2 = fcmp(floatcc_one, x, y)),
|
||||
def!(a = bor(a1, a2)),
|
||||
],
|
||||
);
|
||||
|
||||
let floatcc_lt = &Literal::enumerator_for(&imm.floatcc, "lt");
|
||||
let floatcc_gt = &Literal::enumerator_for(&imm.floatcc, "gt");
|
||||
let floatcc_le = &Literal::enumerator_for(&imm.floatcc, "le");
|
||||
let floatcc_ge = &Literal::enumerator_for(&imm.floatcc, "ge");
|
||||
let floatcc_ugt = &Literal::enumerator_for(&imm.floatcc, "ugt");
|
||||
let floatcc_ult = &Literal::enumerator_for(&imm.floatcc, "ult");
|
||||
let floatcc_uge = &Literal::enumerator_for(&imm.floatcc, "uge");
|
||||
let floatcc_ule = &Literal::enumerator_for(&imm.floatcc, "ule");
|
||||
|
||||
// Inequalities that need to be reversed.
|
||||
for &(cc, rev_cc) in &[
|
||||
(floatcc_lt, floatcc_gt),
|
||||
(floatcc_le, floatcc_ge),
|
||||
(floatcc_ugt, floatcc_ult),
|
||||
(floatcc_uge, floatcc_ule),
|
||||
] {
|
||||
expand.legalize(def!(a = fcmp(cc, x, y)), vec![def!(a = fcmp(rev_cc, y, x))]);
|
||||
}
|
||||
|
||||
// We need to modify the CFG for min/max legalization.
|
||||
expand.custom_legalize(fmin, "expand_minmax");
|
||||
expand.custom_legalize(fmax, "expand_minmax");
|
||||
|
||||
// Conversions from unsigned need special handling.
|
||||
expand.custom_legalize(fcvt_from_uint, "expand_fcvt_from_uint");
|
||||
// Conversions from float to int can trap and modify the control flow graph.
|
||||
expand.custom_legalize(fcvt_to_sint, "expand_fcvt_to_sint");
|
||||
expand.custom_legalize(fcvt_to_uint, "expand_fcvt_to_uint");
|
||||
expand.custom_legalize(fcvt_to_sint_sat, "expand_fcvt_to_sint_sat");
|
||||
expand.custom_legalize(fcvt_to_uint_sat, "expand_fcvt_to_uint_sat");
|
||||
|
||||
// Count leading and trailing zeroes, for baseline x86_64
|
||||
let c_minus_one = var("c_minus_one");
|
||||
let c_thirty_one = var("c_thirty_one");
|
||||
let c_thirty_two = var("c_thirty_two");
|
||||
let c_sixty_three = var("c_sixty_three");
|
||||
let c_sixty_four = var("c_sixty_four");
|
||||
let index1 = var("index1");
|
||||
let r2flags = var("r2flags");
|
||||
let index2 = var("index2");
|
||||
|
||||
let intcc_eq = Literal::enumerator_for(&imm.intcc, "eq");
|
||||
let imm64_minus_one = Literal::constant(&imm.imm64, -1);
|
||||
let imm64_63 = Literal::constant(&imm.imm64, 63);
|
||||
expand.legalize(
|
||||
def!(a = clz.I64(x)),
|
||||
vec![
|
||||
def!(c_minus_one = iconst(imm64_minus_one)),
|
||||
def!(c_sixty_three = iconst(imm64_63)),
|
||||
def!((index1, r2flags) = x86_bsr(x)),
|
||||
def!(index2 = selectif(intcc_eq, r2flags, c_minus_one, index1)),
|
||||
def!(a = isub(c_sixty_three, index2)),
|
||||
],
|
||||
);
|
||||
|
||||
let imm64_31 = Literal::constant(&imm.imm64, 31);
|
||||
expand.legalize(
|
||||
def!(a = clz.I32(x)),
|
||||
vec![
|
||||
def!(c_minus_one = iconst(imm64_minus_one)),
|
||||
def!(c_thirty_one = iconst(imm64_31)),
|
||||
def!((index1, r2flags) = x86_bsr(x)),
|
||||
def!(index2 = selectif(intcc_eq, r2flags, c_minus_one, index1)),
|
||||
def!(a = isub(c_thirty_one, index2)),
|
||||
],
|
||||
);
|
||||
|
||||
let imm64_64 = Literal::constant(&imm.imm64, 64);
|
||||
expand.legalize(
|
||||
def!(a = ctz.I64(x)),
|
||||
vec![
|
||||
def!(c_sixty_four = iconst(imm64_64)),
|
||||
def!((index1, r2flags) = x86_bsf(x)),
|
||||
def!(a = selectif(intcc_eq, r2flags, c_sixty_four, index1)),
|
||||
],
|
||||
);
|
||||
|
||||
let imm64_32 = Literal::constant(&imm.imm64, 32);
|
||||
expand.legalize(
|
||||
def!(a = ctz.I32(x)),
|
||||
vec![
|
||||
def!(c_thirty_two = iconst(imm64_32)),
|
||||
def!((index1, r2flags) = x86_bsf(x)),
|
||||
def!(a = selectif(intcc_eq, r2flags, c_thirty_two, index1)),
|
||||
],
|
||||
);
|
||||
|
||||
// Population count for baseline x86_64
|
||||
let x = var("x");
|
||||
let r = var("r");
|
||||
|
||||
let qv3 = var("qv3");
|
||||
let qv4 = var("qv4");
|
||||
let qv5 = var("qv5");
|
||||
let qv6 = var("qv6");
|
||||
let qv7 = var("qv7");
|
||||
let qv8 = var("qv8");
|
||||
let qv9 = var("qv9");
|
||||
let qv10 = var("qv10");
|
||||
let qv11 = var("qv11");
|
||||
let qv12 = var("qv12");
|
||||
let qv13 = var("qv13");
|
||||
let qv14 = var("qv14");
|
||||
let qv15 = var("qv15");
|
||||
let qc77 = var("qc77");
|
||||
#[allow(non_snake_case)]
|
||||
let qc0F = var("qc0F");
|
||||
let qc01 = var("qc01");
|
||||
|
||||
let imm64_1 = Literal::constant(&imm.imm64, 1);
|
||||
let imm64_4 = Literal::constant(&imm.imm64, 4);
|
||||
expand.legalize(
|
||||
def!(r = popcnt.I64(x)),
|
||||
vec![
|
||||
def!(qv3 = ushr_imm(x, imm64_1)),
|
||||
def!(qc77 = iconst(Literal::constant(&imm.imm64, 0x7777_7777_7777_7777))),
|
||||
def!(qv4 = band(qv3, qc77)),
|
||||
def!(qv5 = isub(x, qv4)),
|
||||
def!(qv6 = ushr_imm(qv4, imm64_1)),
|
||||
def!(qv7 = band(qv6, qc77)),
|
||||
def!(qv8 = isub(qv5, qv7)),
|
||||
def!(qv9 = ushr_imm(qv7, imm64_1)),
|
||||
def!(qv10 = band(qv9, qc77)),
|
||||
def!(qv11 = isub(qv8, qv10)),
|
||||
def!(qv12 = ushr_imm(qv11, imm64_4)),
|
||||
def!(qv13 = iadd(qv11, qv12)),
|
||||
def!(qc0F = iconst(Literal::constant(&imm.imm64, 0x0F0F_0F0F_0F0F_0F0F))),
|
||||
def!(qv14 = band(qv13, qc0F)),
|
||||
def!(qc01 = iconst(Literal::constant(&imm.imm64, 0x0101_0101_0101_0101))),
|
||||
def!(qv15 = imul(qv14, qc01)),
|
||||
def!(r = ushr_imm(qv15, Literal::constant(&imm.imm64, 56))),
|
||||
],
|
||||
);
|
||||
|
||||
let lv3 = var("lv3");
|
||||
let lv4 = var("lv4");
|
||||
let lv5 = var("lv5");
|
||||
let lv6 = var("lv6");
|
||||
let lv7 = var("lv7");
|
||||
let lv8 = var("lv8");
|
||||
let lv9 = var("lv9");
|
||||
let lv10 = var("lv10");
|
||||
let lv11 = var("lv11");
|
||||
let lv12 = var("lv12");
|
||||
let lv13 = var("lv13");
|
||||
let lv14 = var("lv14");
|
||||
let lv15 = var("lv15");
|
||||
let lc77 = var("lc77");
|
||||
#[allow(non_snake_case)]
|
||||
let lc0F = var("lc0F");
|
||||
let lc01 = var("lc01");
|
||||
|
||||
expand.legalize(
|
||||
def!(r = popcnt.I32(x)),
|
||||
vec![
|
||||
def!(lv3 = ushr_imm(x, imm64_1)),
|
||||
def!(lc77 = iconst(Literal::constant(&imm.imm64, 0x7777_7777))),
|
||||
def!(lv4 = band(lv3, lc77)),
|
||||
def!(lv5 = isub(x, lv4)),
|
||||
def!(lv6 = ushr_imm(lv4, imm64_1)),
|
||||
def!(lv7 = band(lv6, lc77)),
|
||||
def!(lv8 = isub(lv5, lv7)),
|
||||
def!(lv9 = ushr_imm(lv7, imm64_1)),
|
||||
def!(lv10 = band(lv9, lc77)),
|
||||
def!(lv11 = isub(lv8, lv10)),
|
||||
def!(lv12 = ushr_imm(lv11, imm64_4)),
|
||||
def!(lv13 = iadd(lv11, lv12)),
|
||||
def!(lc0F = iconst(Literal::constant(&imm.imm64, 0x0F0F_0F0F))),
|
||||
def!(lv14 = band(lv13, lc0F)),
|
||||
def!(lc01 = iconst(Literal::constant(&imm.imm64, 0x0101_0101))),
|
||||
def!(lv15 = imul(lv14, lc01)),
|
||||
def!(r = ushr_imm(lv15, Literal::constant(&imm.imm64, 24))),
|
||||
],
|
||||
);
|
||||
|
||||
expand.custom_legalize(ineg, "convert_ineg");
|
||||
expand.custom_legalize(tls_value, "expand_tls_value");
|
||||
widen.custom_legalize(ineg, "convert_ineg");
|
||||
|
||||
// To reduce compilation times, separate out large blocks of legalizations by theme.
|
||||
define_simd(shared, x86_instructions, &mut narrow, &mut narrow_avx);
|
||||
|
||||
expand.build_and_add_to(&mut shared.transform_groups);
|
||||
let narrow_id = narrow.build_and_add_to(&mut shared.transform_groups);
|
||||
narrow_avx
|
||||
.chain_with(narrow_id)
|
||||
.build_and_add_to(&mut shared.transform_groups);
|
||||
widen.build_and_add_to(&mut shared.transform_groups);
|
||||
}
|
||||
|
||||
fn define_simd(
|
||||
shared: &mut SharedDefinitions,
|
||||
x86_instructions: &InstructionGroup,
|
||||
narrow: &mut TransformGroupBuilder,
|
||||
narrow_avx: &mut TransformGroupBuilder,
|
||||
) {
|
||||
let insts = &shared.instructions;
|
||||
let band = insts.by_name("band");
|
||||
let band_not = insts.by_name("band_not");
|
||||
let bitcast = insts.by_name("bitcast");
|
||||
let bitselect = insts.by_name("bitselect");
|
||||
let bor = insts.by_name("bor");
|
||||
let bnot = insts.by_name("bnot");
|
||||
let bxor = insts.by_name("bxor");
|
||||
let extractlane = insts.by_name("extractlane");
|
||||
let fabs = insts.by_name("fabs");
|
||||
let fcmp = insts.by_name("fcmp");
|
||||
let fcvt_from_uint = insts.by_name("fcvt_from_uint");
|
||||
let fcvt_to_sint_sat = insts.by_name("fcvt_to_sint_sat");
|
||||
let fcvt_to_uint_sat = insts.by_name("fcvt_to_uint_sat");
|
||||
let fmax = insts.by_name("fmax");
|
||||
let fmin = insts.by_name("fmin");
|
||||
let fneg = insts.by_name("fneg");
|
||||
let iadd_imm = insts.by_name("iadd_imm");
|
||||
let icmp = insts.by_name("icmp");
|
||||
let imax = insts.by_name("imax");
|
||||
let imin = insts.by_name("imin");
|
||||
let imul = insts.by_name("imul");
|
||||
let ineg = insts.by_name("ineg");
|
||||
let insertlane = insts.by_name("insertlane");
|
||||
let ishl = insts.by_name("ishl");
|
||||
let ishl_imm = insts.by_name("ishl_imm");
|
||||
let raw_bitcast = insts.by_name("raw_bitcast");
|
||||
let scalar_to_vector = insts.by_name("scalar_to_vector");
|
||||
let splat = insts.by_name("splat");
|
||||
let shuffle = insts.by_name("shuffle");
|
||||
let sshr = insts.by_name("sshr");
|
||||
let swizzle = insts.by_name("swizzle");
|
||||
let trueif = insts.by_name("trueif");
|
||||
let uadd_sat = insts.by_name("uadd_sat");
|
||||
let umax = insts.by_name("umax");
|
||||
let umin = insts.by_name("umin");
|
||||
let snarrow = insts.by_name("snarrow");
|
||||
let swiden_high = insts.by_name("swiden_high");
|
||||
let swiden_low = insts.by_name("swiden_low");
|
||||
let ushr_imm = insts.by_name("ushr_imm");
|
||||
let ushr = insts.by_name("ushr");
|
||||
let uwiden_high = insts.by_name("uwiden_high");
|
||||
let uwiden_low = insts.by_name("uwiden_low");
|
||||
let vconst = insts.by_name("vconst");
|
||||
let vall_true = insts.by_name("vall_true");
|
||||
let vany_true = insts.by_name("vany_true");
|
||||
let vselect = insts.by_name("vselect");
|
||||
|
||||
let x86_palignr = x86_instructions.by_name("x86_palignr");
|
||||
let x86_pmaxs = x86_instructions.by_name("x86_pmaxs");
|
||||
let x86_pmaxu = x86_instructions.by_name("x86_pmaxu");
|
||||
let x86_pmins = x86_instructions.by_name("x86_pmins");
|
||||
let x86_pminu = x86_instructions.by_name("x86_pminu");
|
||||
let x86_pshufb = x86_instructions.by_name("x86_pshufb");
|
||||
let x86_pshufd = x86_instructions.by_name("x86_pshufd");
|
||||
let x86_psra = x86_instructions.by_name("x86_psra");
|
||||
let x86_ptest = x86_instructions.by_name("x86_ptest");
|
||||
let x86_punpckh = x86_instructions.by_name("x86_punpckh");
|
||||
let x86_punpckl = x86_instructions.by_name("x86_punpckl");
|
||||
|
||||
let imm = &shared.imm;
|
||||
|
||||
// Set up variables and immediates.
|
||||
let uimm8_zero = Literal::constant(&imm.uimm8, 0x00);
|
||||
let uimm8_one = Literal::constant(&imm.uimm8, 0x01);
|
||||
let uimm8_eight = Literal::constant(&imm.uimm8, 8);
|
||||
let u128_zeroes = constant(vec![0x00; 16]);
|
||||
let u128_ones = constant(vec![0xff; 16]);
|
||||
let u128_seventies = constant(vec![0x70; 16]);
|
||||
let a = var("a");
|
||||
let b = var("b");
|
||||
let c = var("c");
|
||||
let d = var("d");
|
||||
let e = var("e");
|
||||
let f = var("f");
|
||||
let g = var("g");
|
||||
let h = var("h");
|
||||
let x = var("x");
|
||||
let y = var("y");
|
||||
let z = var("z");
|
||||
|
||||
// Limit the SIMD vector size: eventually multiple vector sizes may be supported
|
||||
// but for now only SSE-sized vectors are available.
|
||||
let sse_vector_size: u64 = 128;
|
||||
let allowed_simd_type = |t: &LaneType| t.lane_bits() >= 8 && t.lane_bits() < 128;
|
||||
|
||||
// SIMD splat: 8-bits
|
||||
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 8) {
|
||||
let splat_any8x16 = splat.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = splat_any8x16(x)),
|
||||
vec![
|
||||
// Move into the lowest 8 bits of an XMM register.
|
||||
def!(a = scalar_to_vector(x)),
|
||||
// Zero out a different XMM register; the shuffle mask for moving the lowest byte
|
||||
// to all other byte lanes is 0x0.
|
||||
def!(b = vconst(u128_zeroes)),
|
||||
// PSHUFB takes two XMM operands, one of which is a shuffle mask (i.e. b).
|
||||
def!(y = x86_pshufb(a, b)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD splat: 16-bits
|
||||
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 16) {
|
||||
let splat_x16x8 = splat.bind(vector(ty, sse_vector_size));
|
||||
let raw_bitcast_any16x8_to_i32x4 = raw_bitcast
|
||||
.bind(vector(I32, sse_vector_size))
|
||||
.bind(vector(ty, sse_vector_size));
|
||||
let raw_bitcast_i32x4_to_any16x8 = raw_bitcast
|
||||
.bind(vector(ty, sse_vector_size))
|
||||
.bind(vector(I32, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = splat_x16x8(x)),
|
||||
vec![
|
||||
// Move into the lowest 16 bits of an XMM register.
|
||||
def!(a = scalar_to_vector(x)),
|
||||
// Insert the value again but in the next lowest 16 bits.
|
||||
def!(b = insertlane(a, x, uimm8_one)),
|
||||
// No instruction emitted; pretend this is an I32x4 so we can use PSHUFD.
|
||||
def!(c = raw_bitcast_any16x8_to_i32x4(b)),
|
||||
// Broadcast the bytes in the XMM register with PSHUFD.
|
||||
def!(d = x86_pshufd(c, uimm8_zero)),
|
||||
// No instruction emitted; pretend this is an X16x8 again.
|
||||
def!(y = raw_bitcast_i32x4_to_any16x8(d)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD splat: 32-bits
|
||||
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 32) {
|
||||
let splat_any32x4 = splat.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = splat_any32x4(x)),
|
||||
vec![
|
||||
// Translate to an x86 MOV to get the value in an XMM register.
|
||||
def!(a = scalar_to_vector(x)),
|
||||
// Broadcast the bytes in the XMM register with PSHUFD.
|
||||
def!(y = x86_pshufd(a, uimm8_zero)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD splat: 64-bits
|
||||
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 64) {
|
||||
let splat_any64x2 = splat.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = splat_any64x2(x)),
|
||||
vec![
|
||||
// Move into the lowest 64 bits of an XMM register.
|
||||
def!(a = scalar_to_vector(x)),
|
||||
// Move into the highest 64 bits of the same XMM register.
|
||||
def!(y = insertlane(a, x, uimm8_one)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD swizzle; the following inefficient implementation is due to the Wasm SIMD spec requiring
|
||||
// mask indexes greater than 15 to have the same semantics as a 0 index. For the spec discussion,
|
||||
// see https://github.com/WebAssembly/simd/issues/93.
|
||||
{
|
||||
let swizzle = swizzle.bind(vector(I8, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(a = swizzle(x, y)),
|
||||
vec![
|
||||
def!(b = vconst(u128_seventies)),
|
||||
def!(c = uadd_sat(y, b)),
|
||||
def!(a = x86_pshufb(x, c)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD bnot
|
||||
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
|
||||
let bnot = bnot.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = bnot(x)),
|
||||
vec![def!(a = vconst(u128_ones)), def!(y = bxor(a, x))],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD shift right (arithmetic, i16x8 and i32x4)
|
||||
for ty in &[I16, I32] {
|
||||
let sshr = sshr.bind(vector(*ty, sse_vector_size));
|
||||
let bitcast_i64x2 = bitcast.bind(vector(I64, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(a = sshr(x, y)),
|
||||
vec![def!(b = bitcast_i64x2(y)), def!(a = x86_psra(x, b))],
|
||||
);
|
||||
}
|
||||
// SIMD shift right (arithmetic, i8x16)
|
||||
{
|
||||
let sshr = sshr.bind(vector(I8, sse_vector_size));
|
||||
let bitcast_i64x2 = bitcast.bind(vector(I64, sse_vector_size));
|
||||
let raw_bitcast_i16x8 = raw_bitcast.bind(vector(I16, sse_vector_size));
|
||||
let raw_bitcast_i16x8_again = raw_bitcast.bind(vector(I16, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(z = sshr(x, y)),
|
||||
vec![
|
||||
// Since we will use the high byte of each 16x8 lane, shift an extra 8 bits.
|
||||
def!(a = iadd_imm(y, uimm8_eight)),
|
||||
def!(b = bitcast_i64x2(a)),
|
||||
// Take the low 8 bytes of x, duplicate them in 16x8 lanes, then shift right.
|
||||
def!(c = x86_punpckl(x, x)),
|
||||
def!(d = raw_bitcast_i16x8(c)),
|
||||
def!(e = x86_psra(d, b)),
|
||||
// Take the high 8 bytes of x, duplicate them in 16x8 lanes, then shift right.
|
||||
def!(f = x86_punpckh(x, x)),
|
||||
def!(g = raw_bitcast_i16x8_again(f)),
|
||||
def!(h = x86_psra(g, b)),
|
||||
// Re-pack the vector.
|
||||
def!(z = snarrow(e, h)),
|
||||
],
|
||||
);
|
||||
}
|
||||
// SIMD shift right (arithmetic, i64x2)
|
||||
{
|
||||
let sshr_vector = sshr.bind(vector(I64, sse_vector_size));
|
||||
let sshr_scalar_lane0 = sshr.bind(I64);
|
||||
let sshr_scalar_lane1 = sshr.bind(I64);
|
||||
narrow.legalize(
|
||||
def!(z = sshr_vector(x, y)),
|
||||
vec![
|
||||
// Use scalar operations to shift the first lane.
|
||||
def!(a = extractlane(x, uimm8_zero)),
|
||||
def!(b = sshr_scalar_lane0(a, y)),
|
||||
def!(c = insertlane(x, b, uimm8_zero)),
|
||||
// Do the same for the second lane.
|
||||
def!(d = extractlane(x, uimm8_one)),
|
||||
def!(e = sshr_scalar_lane1(d, y)),
|
||||
def!(z = insertlane(c, e, uimm8_one)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD select
|
||||
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
|
||||
let bitselect = bitselect.bind(vector(ty, sse_vector_size)); // must bind both x/y and c
|
||||
narrow.legalize(
|
||||
def!(d = bitselect(c, x, y)),
|
||||
vec![
|
||||
def!(a = band(x, c)),
|
||||
def!(b = band_not(y, c)),
|
||||
def!(d = bor(a, b)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD vselect; replace with bitselect if BLEND* instructions are not available.
|
||||
// This works, because each lane of boolean vector is filled with zeroes or ones.
|
||||
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
|
||||
let vselect = vselect.bind(vector(ty, sse_vector_size));
|
||||
let raw_bitcast = raw_bitcast.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(d = vselect(c, x, y)),
|
||||
vec![def!(a = raw_bitcast(c)), def!(d = bitselect(a, x, y))],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD vany_true
|
||||
let ne = Literal::enumerator_for(&imm.intcc, "ne");
|
||||
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
|
||||
let vany_true = vany_true.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = vany_true(x)),
|
||||
vec![def!(a = x86_ptest(x, x)), def!(y = trueif(ne, a))],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD vall_true
|
||||
let eq = Literal::enumerator_for(&imm.intcc, "eq");
|
||||
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
|
||||
let vall_true = vall_true.bind(vector(ty, sse_vector_size));
|
||||
if ty.is_int() {
|
||||
// In the common case (Wasm's integer-only all_true), we do not require a
|
||||
// bitcast.
|
||||
narrow.legalize(
|
||||
def!(y = vall_true(x)),
|
||||
vec![
|
||||
def!(a = vconst(u128_zeroes)),
|
||||
def!(c = icmp(eq, x, a)),
|
||||
def!(d = x86_ptest(c, c)),
|
||||
def!(y = trueif(eq, d)),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// However, to support other types we must bitcast them to an integer vector to
|
||||
// use icmp.
|
||||
let lane_type_as_int = LaneType::int_from_bits(ty.lane_bits() as u16);
|
||||
let raw_bitcast_to_int = raw_bitcast.bind(vector(lane_type_as_int, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(y = vall_true(x)),
|
||||
vec![
|
||||
def!(a = vconst(u128_zeroes)),
|
||||
def!(b = raw_bitcast_to_int(x)),
|
||||
def!(c = icmp(eq, b, a)),
|
||||
def!(d = x86_ptest(c, c)),
|
||||
def!(y = trueif(eq, d)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// SIMD icmp ne
|
||||
let ne = Literal::enumerator_for(&imm.intcc, "ne");
|
||||
for ty in ValueType::all_lane_types().filter(|ty| allowed_simd_type(ty) && ty.is_int()) {
|
||||
let icmp_ = icmp.bind(vector(ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(c = icmp_(ne, a, b)),
|
||||
vec![def!(x = icmp(eq, a, b)), def!(c = bnot(x))],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD icmp greater-/less-than
|
||||
let sgt = Literal::enumerator_for(&imm.intcc, "sgt");
|
||||
let ugt = Literal::enumerator_for(&imm.intcc, "ugt");
|
||||
let sge = Literal::enumerator_for(&imm.intcc, "sge");
|
||||
let uge = Literal::enumerator_for(&imm.intcc, "uge");
|
||||
let slt = Literal::enumerator_for(&imm.intcc, "slt");
|
||||
let ult = Literal::enumerator_for(&imm.intcc, "ult");
|
||||
let sle = Literal::enumerator_for(&imm.intcc, "sle");
|
||||
let ule = Literal::enumerator_for(&imm.intcc, "ule");
|
||||
for ty in &[I8, I16, I32] {
|
||||
// greater-than
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(c = icmp_(ugt, a, b)),
|
||||
vec![
|
||||
def!(x = x86_pmaxu(a, b)),
|
||||
def!(y = icmp(eq, x, b)),
|
||||
def!(c = bnot(y)),
|
||||
],
|
||||
);
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(c = icmp_(sge, a, b)),
|
||||
vec![def!(x = x86_pmins(a, b)), def!(c = icmp(eq, x, b))],
|
||||
);
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(c = icmp_(uge, a, b)),
|
||||
vec![def!(x = x86_pminu(a, b)), def!(c = icmp(eq, x, b))],
|
||||
);
|
||||
|
||||
// less-than
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = icmp_(slt, a, b)), vec![def!(c = icmp(sgt, b, a))]);
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = icmp_(ult, a, b)), vec![def!(c = icmp(ugt, b, a))]);
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = icmp_(sle, a, b)), vec![def!(c = icmp(sge, b, a))]);
|
||||
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = icmp_(ule, a, b)), vec![def!(c = icmp(uge, b, a))]);
|
||||
}
|
||||
|
||||
// SIMD integer min/max
|
||||
for ty in &[I8, I16, I32] {
|
||||
let imin = imin.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = imin(a, b)), vec![def!(c = x86_pmins(a, b))]);
|
||||
let umin = umin.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = umin(a, b)), vec![def!(c = x86_pminu(a, b))]);
|
||||
let imax = imax.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = imax(a, b)), vec![def!(c = x86_pmaxs(a, b))]);
|
||||
let umax = umax.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = umax(a, b)), vec![def!(c = x86_pmaxu(a, b))]);
|
||||
}
|
||||
|
||||
// SIMD fcmp greater-/less-than
|
||||
let gt = Literal::enumerator_for(&imm.floatcc, "gt");
|
||||
let lt = Literal::enumerator_for(&imm.floatcc, "lt");
|
||||
let ge = Literal::enumerator_for(&imm.floatcc, "ge");
|
||||
let le = Literal::enumerator_for(&imm.floatcc, "le");
|
||||
let ugt = Literal::enumerator_for(&imm.floatcc, "ugt");
|
||||
let ult = Literal::enumerator_for(&imm.floatcc, "ult");
|
||||
let uge = Literal::enumerator_for(&imm.floatcc, "uge");
|
||||
let ule = Literal::enumerator_for(&imm.floatcc, "ule");
|
||||
for ty in &[F32, F64] {
|
||||
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = fcmp_(gt, a, b)), vec![def!(c = fcmp(lt, b, a))]);
|
||||
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = fcmp_(ge, a, b)), vec![def!(c = fcmp(le, b, a))]);
|
||||
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = fcmp_(ult, a, b)), vec![def!(c = fcmp(ugt, b, a))]);
|
||||
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(def!(c = fcmp_(ule, a, b)), vec![def!(c = fcmp(uge, b, a))]);
|
||||
}
|
||||
|
||||
for ty in &[F32, F64] {
|
||||
let fneg = fneg.bind(vector(*ty, sse_vector_size));
|
||||
let lane_type_as_int = LaneType::int_from_bits(LaneType::from(*ty).lane_bits() as u16);
|
||||
let uimm8_shift = Literal::constant(&imm.uimm8, lane_type_as_int.lane_bits() as i64 - 1);
|
||||
let vconst = vconst.bind(vector(lane_type_as_int, sse_vector_size));
|
||||
let bitcast_to_float = raw_bitcast.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(b = fneg(a)),
|
||||
vec![
|
||||
def!(c = vconst(u128_ones)),
|
||||
def!(d = ishl_imm(c, uimm8_shift)), // Create a mask of all 0s except the MSB.
|
||||
def!(e = bitcast_to_float(d)), // Cast mask to the floating-point type.
|
||||
def!(b = bxor(a, e)), // Flip the MSB.
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD fabs
|
||||
for ty in &[F32, F64] {
|
||||
let fabs = fabs.bind(vector(*ty, sse_vector_size));
|
||||
let lane_type_as_int = LaneType::int_from_bits(LaneType::from(*ty).lane_bits() as u16);
|
||||
let vconst = vconst.bind(vector(lane_type_as_int, sse_vector_size));
|
||||
let bitcast_to_float = raw_bitcast.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(b = fabs(a)),
|
||||
vec![
|
||||
def!(c = vconst(u128_ones)),
|
||||
def!(d = ushr_imm(c, uimm8_one)), // Create a mask of all 1s except the MSB.
|
||||
def!(e = bitcast_to_float(d)), // Cast mask to the floating-point type.
|
||||
def!(b = band(a, e)), // Unset the MSB.
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// SIMD widen
|
||||
for ty in &[I8, I16] {
|
||||
let swiden_high = swiden_high.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(b = swiden_high(a)),
|
||||
vec![
|
||||
def!(c = x86_palignr(a, a, uimm8_eight)),
|
||||
def!(b = swiden_low(c)),
|
||||
],
|
||||
);
|
||||
let uwiden_high = uwiden_high.bind(vector(*ty, sse_vector_size));
|
||||
narrow.legalize(
|
||||
def!(b = uwiden_high(a)),
|
||||
vec![
|
||||
def!(c = x86_palignr(a, a, uimm8_eight)),
|
||||
def!(b = uwiden_low(c)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
narrow.custom_legalize(shuffle, "convert_shuffle");
|
||||
narrow.custom_legalize(extractlane, "convert_extractlane");
|
||||
narrow.custom_legalize(insertlane, "convert_insertlane");
|
||||
narrow.custom_legalize(ineg, "convert_ineg");
|
||||
narrow.custom_legalize(ushr, "convert_ushr");
|
||||
narrow.custom_legalize(ishl, "convert_ishl");
|
||||
narrow.custom_legalize(fcvt_to_sint_sat, "expand_fcvt_to_sint_sat_vector");
|
||||
narrow.custom_legalize(fmin, "expand_minmax_vector");
|
||||
narrow.custom_legalize(fmax, "expand_minmax_vector");
|
||||
|
||||
narrow_avx.custom_legalize(imul, "convert_i64x2_imul");
|
||||
narrow_avx.custom_legalize(fcvt_from_uint, "expand_fcvt_from_uint_vector");
|
||||
narrow_avx.custom_legalize(fcvt_to_uint_sat, "expand_fcvt_to_uint_sat_vector");
|
||||
}
|
||||
@@ -1,87 +1,11 @@
|
||||
use crate::cdsl::cpu_modes::CpuMode;
|
||||
use crate::cdsl::isa::TargetIsa;
|
||||
use crate::cdsl::types::{ReferenceType, VectorType};
|
||||
|
||||
use crate::shared::types::Bool::B1;
|
||||
use crate::shared::types::Float::{F32, F64};
|
||||
use crate::shared::types::Int::{I16, I32, I64, I8};
|
||||
use crate::shared::types::Reference::{R32, R64};
|
||||
use crate::shared::Definitions as SharedDefinitions;
|
||||
|
||||
mod encodings;
|
||||
mod instructions;
|
||||
mod legalize;
|
||||
mod opcodes;
|
||||
mod recipes;
|
||||
mod registers;
|
||||
pub(crate) mod settings;
|
||||
|
||||
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
|
||||
let settings = settings::define(&shared_defs.settings);
|
||||
let regs = registers::define();
|
||||
|
||||
let inst_group = instructions::define(
|
||||
&mut shared_defs.all_instructions,
|
||||
&shared_defs.formats,
|
||||
&shared_defs.imm,
|
||||
&shared_defs.entities,
|
||||
);
|
||||
legalize::define(shared_defs, &inst_group);
|
||||
|
||||
// CPU modes for 32-bit and 64-bit operations.
|
||||
let mut x86_64 = CpuMode::new("I64");
|
||||
let mut x86_32 = CpuMode::new("I32");
|
||||
|
||||
let expand_flags = shared_defs.transform_groups.by_name("expand_flags");
|
||||
let x86_widen = shared_defs.transform_groups.by_name("x86_widen");
|
||||
let x86_narrow = shared_defs.transform_groups.by_name("x86_narrow");
|
||||
let x86_narrow_avx = shared_defs.transform_groups.by_name("x86_narrow_avx");
|
||||
let x86_expand = shared_defs.transform_groups.by_name("x86_expand");
|
||||
|
||||
x86_32.legalize_monomorphic(expand_flags);
|
||||
x86_32.legalize_default(x86_narrow);
|
||||
x86_32.legalize_type(B1, expand_flags);
|
||||
x86_32.legalize_type(I8, x86_widen);
|
||||
x86_32.legalize_type(I16, x86_widen);
|
||||
x86_32.legalize_type(I32, x86_expand);
|
||||
x86_32.legalize_value_type(ReferenceType(R32), x86_expand);
|
||||
x86_32.legalize_type(F32, x86_expand);
|
||||
x86_32.legalize_type(F64, x86_expand);
|
||||
x86_32.legalize_value_type(VectorType::new(I32.into(), 4), x86_narrow_avx);
|
||||
x86_32.legalize_value_type(VectorType::new(I64.into(), 2), x86_narrow_avx);
|
||||
x86_32.legalize_value_type(VectorType::new(F32.into(), 4), x86_narrow_avx);
|
||||
|
||||
x86_64.legalize_monomorphic(expand_flags);
|
||||
x86_64.legalize_default(x86_narrow);
|
||||
x86_64.legalize_type(B1, expand_flags);
|
||||
x86_64.legalize_type(I8, x86_widen);
|
||||
x86_64.legalize_type(I16, x86_widen);
|
||||
x86_64.legalize_type(I32, x86_expand);
|
||||
x86_64.legalize_type(I64, x86_expand);
|
||||
x86_64.legalize_value_type(ReferenceType(R64), x86_expand);
|
||||
x86_64.legalize_type(F32, x86_expand);
|
||||
x86_64.legalize_type(F64, x86_expand);
|
||||
x86_64.legalize_value_type(VectorType::new(I32.into(), 4), x86_narrow_avx);
|
||||
x86_64.legalize_value_type(VectorType::new(I64.into(), 2), x86_narrow_avx);
|
||||
x86_64.legalize_value_type(VectorType::new(F32.into(), 4), x86_narrow_avx);
|
||||
|
||||
let recipes = recipes::define(shared_defs, &settings, ®s);
|
||||
|
||||
let encodings = encodings::define(shared_defs, &settings, &inst_group, &recipes);
|
||||
x86_32.set_encodings(encodings.enc32);
|
||||
x86_64.set_encodings(encodings.enc64);
|
||||
let encodings_predicates = encodings.inst_pred_reg.extract();
|
||||
|
||||
let recipes = encodings.recipes;
|
||||
|
||||
let cpu_modes = vec![x86_64, x86_32];
|
||||
|
||||
TargetIsa::new(
|
||||
"x86",
|
||||
settings,
|
||||
regs,
|
||||
recipes,
|
||||
cpu_modes,
|
||||
encodings_predicates,
|
||||
)
|
||||
TargetIsa::new("x86", settings)
|
||||
}
|
||||
|
||||
@@ -1,721 +0,0 @@
|
||||
//! Static, named definitions of instruction opcodes.
|
||||
|
||||
/// Empty opcode for use as a default.
|
||||
pub static EMPTY: [u8; 0] = [];
|
||||
|
||||
/// Add with carry flag r{16,32,64} to r/m of the same size.
|
||||
pub static ADC: [u8; 1] = [0x11];
|
||||
|
||||
/// Add r{16,32,64} to r/m of the same size.
|
||||
pub static ADD: [u8; 1] = [0x01];
|
||||
|
||||
/// Add imm{16,32} to r/m{16,32,64}, possibly sign-extended.
|
||||
pub static ADD_IMM: [u8; 1] = [0x81];
|
||||
|
||||
/// Add sign-extended imm8 to r/m{16,32,64}.
|
||||
pub static ADD_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
|
||||
|
||||
/// Add packed double-precision floating-point values from xmm2/mem to xmm1 and store result in
|
||||
/// xmm1 (SSE2).
|
||||
pub static ADDPD: [u8; 3] = [0x66, 0x0f, 0x58];
|
||||
|
||||
/// Add packed single-precision floating-point values from xmm2/mem to xmm1 and store result in
|
||||
/// xmm1 (SSE).
|
||||
pub static ADDPS: [u8; 2] = [0x0f, 0x58];
|
||||
|
||||
/// Add the low double-precision floating-point value from xmm2/mem to xmm1
|
||||
/// and store the result in xmm1.
|
||||
pub static ADDSD: [u8; 3] = [0xf2, 0x0f, 0x58];
|
||||
|
||||
/// Add the low single-precision floating-point value from xmm2/mem to xmm1
|
||||
/// and store the result in xmm1.
|
||||
pub static ADDSS: [u8; 3] = [0xf3, 0x0f, 0x58];
|
||||
|
||||
/// r/m{16,32,64} AND register of the same size (Intel docs have a typo).
|
||||
pub static AND: [u8; 1] = [0x21];
|
||||
|
||||
/// imm{16,32} AND r/m{16,32,64}, possibly sign-extended.
|
||||
pub static AND_IMM: [u8; 1] = [0x81];
|
||||
|
||||
/// r/m{16,32,64} AND sign-extended imm8.
|
||||
pub static AND_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
|
||||
|
||||
/// Return the bitwise logical AND NOT of packed single-precision floating-point
|
||||
/// values in xmm1 and xmm2/mem.
|
||||
pub static ANDNPS: [u8; 2] = [0x0f, 0x55];
|
||||
|
||||
/// Return the bitwise logical AND of packed single-precision floating-point values
|
||||
/// in xmm1 and xmm2/mem.
|
||||
pub static ANDPS: [u8; 2] = [0x0f, 0x54];
|
||||
|
||||
/// Bit scan forward (stores index of first encountered 1 from the front).
|
||||
pub static BIT_SCAN_FORWARD: [u8; 2] = [0x0f, 0xbc];
|
||||
|
||||
/// Bit scan reverse (stores index of first encountered 1 from the back).
|
||||
pub static BIT_SCAN_REVERSE: [u8; 2] = [0x0f, 0xbd];
|
||||
|
||||
/// Select packed single-precision floating-point values from xmm1 and xmm2/m128
|
||||
/// from mask specified in XMM0 and store the values into xmm1 (SSE4.1).
|
||||
pub static BLENDVPS: [u8; 4] = [0x66, 0x0f, 0x38, 0x14];
|
||||
|
||||
/// Select packed double-precision floating-point values from xmm1 and xmm2/m128
|
||||
/// from mask specified in XMM0 and store the values into xmm1 (SSE4.1).
|
||||
pub static BLENDVPD: [u8; 4] = [0x66, 0x0f, 0x38, 0x15];
|
||||
|
||||
/// Call near, relative, displacement relative to next instruction (sign-extended).
|
||||
pub static CALL_RELATIVE: [u8; 1] = [0xe8];
|
||||
|
||||
/// Move r/m{16,32,64} if overflow (OF=1).
|
||||
pub static CMOV_OVERFLOW: [u8; 2] = [0x0f, 0x40];
|
||||
|
||||
/// Compare imm{16,32} with r/m{16,32,64} (sign-extended if 64).
|
||||
pub static CMP_IMM: [u8; 1] = [0x81];
|
||||
|
||||
/// Compare imm8 with r/m{16,32,64}.
|
||||
pub static CMP_IMM8: [u8; 1] = [0x83];
|
||||
|
||||
/// Compare r{16,32,64} with r/m of the same size.
|
||||
pub static CMP_REG: [u8; 1] = [0x39];
|
||||
|
||||
/// Compare packed double-precision floating-point value in xmm2/m32 and xmm1 using bits 2:0 of
|
||||
/// imm8 as comparison predicate (SSE2).
|
||||
pub static CMPPD: [u8; 3] = [0x66, 0x0f, 0xc2];
|
||||
|
||||
/// Compare packed single-precision floating-point value in xmm2/m32 and xmm1 using bits 2:0 of
|
||||
/// imm8 as comparison predicate (SSE).
|
||||
pub static CMPPS: [u8; 2] = [0x0f, 0xc2];
|
||||
|
||||
/// Convert four packed signed doubleword integers from xmm2/mem to four packed single-precision
|
||||
/// floating-point values in xmm1 (SSE2).
|
||||
pub static CVTDQ2PS: [u8; 2] = [0x0f, 0x5b];
|
||||
|
||||
/// Convert scalar double-precision floating-point value to scalar single-precision
|
||||
/// floating-point value.
|
||||
pub static CVTSD2SS: [u8; 3] = [0xf2, 0x0f, 0x5a];
|
||||
|
||||
/// Convert doubleword integer to scalar double-precision floating-point value.
|
||||
pub static CVTSI2SD: [u8; 3] = [0xf2, 0x0f, 0x2a];
|
||||
|
||||
/// Convert doubleword integer to scalar single-precision floating-point value.
|
||||
pub static CVTSI2SS: [u8; 3] = [0xf3, 0x0f, 0x2a];
|
||||
|
||||
/// Convert scalar single-precision floating-point value to scalar double-precision
|
||||
/// float-point value.
|
||||
pub static CVTSS2SD: [u8; 3] = [0xf3, 0x0f, 0x5a];
|
||||
|
||||
/// Convert four packed single-precision floating-point values from xmm2/mem to four packed signed
|
||||
/// doubleword values in xmm1 using truncation (SSE2).
|
||||
pub static CVTTPS2DQ: [u8; 3] = [0xf3, 0x0f, 0x5b];
|
||||
|
||||
/// Convert with truncation scalar double-precision floating-point value to signed
|
||||
/// integer.
|
||||
pub static CVTTSD2SI: [u8; 3] = [0xf2, 0x0f, 0x2c];
|
||||
|
||||
/// Convert with truncation scalar single-precision floating-point value to integer.
|
||||
pub static CVTTSS2SI: [u8; 3] = [0xf3, 0x0f, 0x2c];
|
||||
|
||||
/// Unsigned divide for {16,32,64}-bit.
|
||||
pub static DIV: [u8; 1] = [0xf7];
|
||||
|
||||
/// Divide packed double-precision floating-point values in xmm1 by packed double-precision
|
||||
/// floating-point values in xmm2/mem (SSE2).
|
||||
pub static DIVPD: [u8; 3] = [0x66, 0x0f, 0x5e];
|
||||
|
||||
/// Divide packed single-precision floating-point values in xmm1 by packed single-precision
|
||||
/// floating-point values in xmm2/mem (SSE).
|
||||
pub static DIVPS: [u8; 2] = [0x0f, 0x5e];
|
||||
|
||||
/// Divide low double-precision floating-point value in xmm1 by low double-precision
|
||||
/// floating-point value in xmm2/m64.
|
||||
pub static DIVSD: [u8; 3] = [0xf2, 0x0f, 0x5e];
|
||||
|
||||
/// Divide low single-precision floating-point value in xmm1 by low single-precision
|
||||
/// floating-point value in xmm2/m32.
|
||||
pub static DIVSS: [u8; 3] = [0xf3, 0x0f, 0x5e];
|
||||
|
||||
/// Signed divide for {16,32,64}-bit.
|
||||
pub static IDIV: [u8; 1] = [0xf7];
|
||||
|
||||
/// Signed multiply for {16,32,64}-bit, generic registers.
|
||||
pub static IMUL: [u8; 2] = [0x0f, 0xaf];
|
||||
|
||||
/// Signed multiply for {16,32,64}-bit, storing into RDX:RAX.
|
||||
pub static IMUL_RDX_RAX: [u8; 1] = [0xf7];
|
||||
|
||||
/// Insert scalar single-precision floating-point value.
|
||||
pub static INSERTPS: [u8; 4] = [0x66, 0x0f, 0x3a, 0x21];
|
||||
|
||||
/// Either:
|
||||
/// 1. Jump near, absolute indirect, RIP = 64-bit offset from register or memory.
|
||||
/// 2. Jump far, absolute indirect, address given in m16:64.
|
||||
pub static JUMP_ABSOLUTE: [u8; 1] = [0xff];
|
||||
|
||||
/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64 bits.
|
||||
pub static JUMP_NEAR_RELATIVE: [u8; 1] = [0xe9];
|
||||
|
||||
/// Jump near (rel32) if overflow (OF=1).
|
||||
pub static JUMP_NEAR_IF_OVERFLOW: [u8; 2] = [0x0f, 0x80];
|
||||
|
||||
/// Jump short, relative, RIP = RIP + 8-bit displacement sign extended to 64 bits.
|
||||
pub static JUMP_SHORT: [u8; 1] = [0xeb];
|
||||
|
||||
/// Jump short (rel8) if equal (ZF=1).
|
||||
pub static JUMP_SHORT_IF_EQUAL: [u8; 1] = [0x74];
|
||||
|
||||
/// Jump short (rel8) if not equal (ZF=0).
|
||||
pub static JUMP_SHORT_IF_NOT_EQUAL: [u8; 1] = [0x75];
|
||||
|
||||
/// Jump short (rel8) if overflow (OF=1).
|
||||
pub static JUMP_SHORT_IF_OVERFLOW: [u8; 1] = [0x70];
|
||||
|
||||
/// Store effective address for m in register r{16,32,64}.
|
||||
pub static LEA: [u8; 1] = [0x8d];
|
||||
|
||||
/// Count the number of leading zero bits.
|
||||
pub static LZCNT: [u8; 3] = [0xf3, 0x0f, 0xbd];
|
||||
|
||||
/// Return the maximum packed double-precision floating-point values between xmm1 and xmm2/m128
|
||||
/// (SSE2).
|
||||
pub static MAXPD: [u8; 3] = [0x66, 0x0f, 0x5f];
|
||||
|
||||
/// Return the maximum packed single-precision floating-point values between xmm1 and xmm2/m128
|
||||
/// (SSE).
|
||||
pub static MAXPS: [u8; 2] = [0x0f, 0x5f];
|
||||
|
||||
/// Return the maximum scalar double-precision floating-point value between
|
||||
/// xmm2/m64 and xmm1.
|
||||
pub static MAXSD: [u8; 3] = [0xf2, 0x0f, 0x5f];
|
||||
|
||||
/// Return the maximum scalar single-precision floating-point value between
|
||||
/// xmm2/m32 and xmm1.
|
||||
pub static MAXSS: [u8; 3] = [0xf3, 0x0f, 0x5f];
|
||||
|
||||
/// Return the minimum packed double-precision floating-point values between xmm1 and xmm2/m128
|
||||
/// (SSE2).
|
||||
pub static MINPD: [u8; 3] = [0x66, 0x0f, 0x5d];
|
||||
|
||||
/// Return the minimum packed single-precision floating-point values between xmm1 and xmm2/m128
|
||||
/// (SSE).
|
||||
pub static MINPS: [u8; 2] = [0x0f, 0x5d];
|
||||
|
||||
/// Return the minimum scalar double-precision floating-point value between
|
||||
/// xmm2/m64 and xmm1.
|
||||
pub static MINSD: [u8; 3] = [0xf2, 0x0f, 0x5d];
|
||||
|
||||
/// Return the minimum scalar single-precision floating-point value between
|
||||
/// xmm2/m32 and xmm1.
|
||||
pub static MINSS: [u8; 3] = [0xf3, 0x0f, 0x5d];
|
||||
|
||||
/// Move r8 to r/m8.
|
||||
pub static MOV_BYTE_STORE: [u8; 1] = [0x88];
|
||||
|
||||
/// Move imm{16,32,64} to same-sized register.
|
||||
pub static MOV_IMM: [u8; 1] = [0xb8];
|
||||
|
||||
/// Move imm{16,32} to r{16,32,64}, sign-extended if 64-bit target.
|
||||
pub static MOV_IMM_SIGNEXTEND: [u8; 1] = [0xc7];
|
||||
|
||||
/// Move {r/m16, r/m32, r/m64} to same-sized register.
|
||||
pub static MOV_LOAD: [u8; 1] = [0x8b];
|
||||
|
||||
/// Move r16 to r/m16.
|
||||
pub static MOV_STORE_16: [u8; 2] = [0x66, 0x89];
|
||||
|
||||
/// Move {r16, r32, r64} to same-sized register or memory.
|
||||
pub static MOV_STORE: [u8; 1] = [0x89];
|
||||
|
||||
/// Move aligned packed single-precision floating-point values from x/m to xmm (SSE).
|
||||
pub static MOVAPS_LOAD: [u8; 2] = [0x0f, 0x28];
|
||||
|
||||
/// Move doubleword from r/m32 to xmm (SSE2). Quadword with REX prefix.
|
||||
pub static MOVD_LOAD_XMM: [u8; 3] = [0x66, 0x0f, 0x6e];
|
||||
|
||||
/// Move doubleword from xmm to r/m32 (SSE2). Quadword with REX prefix.
|
||||
pub static MOVD_STORE_XMM: [u8; 3] = [0x66, 0x0f, 0x7e];
|
||||
|
||||
/// Move packed single-precision floating-point values low to high (SSE).
|
||||
pub static MOVLHPS: [u8; 2] = [0x0f, 0x16];
|
||||
|
||||
/// Move scalar double-precision floating-point value (from reg/mem to reg).
|
||||
pub static MOVSD_LOAD: [u8; 3] = [0xf2, 0x0f, 0x10];
|
||||
|
||||
/// Move scalar double-precision floating-point value (from reg to reg/mem).
|
||||
pub static MOVSD_STORE: [u8; 3] = [0xf2, 0x0f, 0x11];
|
||||
|
||||
/// Move scalar single-precision floating-point value (from reg to reg/mem).
|
||||
pub static MOVSS_STORE: [u8; 3] = [0xf3, 0x0f, 0x11];
|
||||
|
||||
/// Move scalar single-precision floating-point-value (from reg/mem to reg).
|
||||
pub static MOVSS_LOAD: [u8; 3] = [0xf3, 0x0f, 0x10];
|
||||
|
||||
/// Move byte to register with sign-extension.
|
||||
pub static MOVSX_BYTE: [u8; 2] = [0x0f, 0xbe];
|
||||
|
||||
/// Move word to register with sign-extension.
|
||||
pub static MOVSX_WORD: [u8; 2] = [0x0f, 0xbf];
|
||||
|
||||
/// Move doubleword to register with sign-extension.
|
||||
pub static MOVSXD: [u8; 1] = [0x63];
|
||||
|
||||
/// Move unaligned packed single-precision floating-point from x/m to xmm (SSE).
|
||||
pub static MOVUPS_LOAD: [u8; 2] = [0x0f, 0x10];
|
||||
|
||||
/// Move unaligned packed single-precision floating-point value from xmm to x/m (SSE).
|
||||
pub static MOVUPS_STORE: [u8; 2] = [0x0f, 0x11];
|
||||
|
||||
/// Move byte to register with zero-extension.
|
||||
pub static MOVZX_BYTE: [u8; 2] = [0x0f, 0xb6];
|
||||
|
||||
/// Move word to register with zero-extension.
|
||||
pub static MOVZX_WORD: [u8; 2] = [0x0f, 0xb7];
|
||||
|
||||
/// Unsigned multiply for {16,32,64}-bit.
|
||||
pub static MUL: [u8; 1] = [0xf7];
|
||||
|
||||
/// Multiply packed double-precision floating-point values from xmm2/mem to xmm1 and store result
|
||||
/// in xmm1 (SSE2).
|
||||
pub static MULPD: [u8; 3] = [0x66, 0x0f, 0x59];
|
||||
|
||||
/// Multiply packed single-precision floating-point values from xmm2/mem to xmm1 and store result
|
||||
/// in xmm1 (SSE).
|
||||
pub static MULPS: [u8; 2] = [0x0f, 0x59];
|
||||
|
||||
/// Multiply the low double-precision floating-point value in xmm2/m64 by the
|
||||
/// low double-precision floating-point value in xmm1.
|
||||
pub static MULSD: [u8; 3] = [0xf2, 0x0f, 0x59];
|
||||
|
||||
/// Multiply the low single-precision floating-point value in xmm2/m32 by the
|
||||
/// low single-precision floating-point value in xmm1.
|
||||
pub static MULSS: [u8; 3] = [0xf3, 0x0f, 0x59];
|
||||
|
||||
/// Reverse each bit of r/m{16,32,64}.
|
||||
pub static NOT: [u8; 1] = [0xf7];
|
||||
|
||||
/// r{16,32,64} OR register of same size.
|
||||
pub static OR: [u8; 1] = [0x09];
|
||||
|
||||
/// imm{16,32} OR r/m{16,32,64}, possibly sign-extended.
|
||||
pub static OR_IMM: [u8; 1] = [0x81];
|
||||
|
||||
/// r/m{16,32,64} OR sign-extended imm8.
|
||||
pub static OR_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
|
||||
|
||||
/// Return the bitwise logical OR of packed single-precision values in xmm and x/m (SSE).
|
||||
pub static ORPS: [u8; 2] = [0x0f, 0x56];
|
||||
|
||||
/// Compute the absolute value of bytes in xmm2/m128 and store the unsigned result in xmm1 (SSSE3).
|
||||
pub static PABSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x1c];
|
||||
|
||||
/// Compute the absolute value of 32-bit integers in xmm2/m128 and store the unsigned result in
|
||||
/// xmm1 (SSSE3).
|
||||
pub static PABSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x1e];
|
||||
|
||||
/// Compute the absolute value of 16-bit integers in xmm2/m128 and store the unsigned result in
|
||||
/// xmm1 (SSSE3).
|
||||
pub static PABSW: [u8; 4] = [0x66, 0x0f, 0x38, 0x1d];
|
||||
|
||||
/// Converts 8 packed signed word integers from xmm1 and from xmm2/m128 into 16 packed signed byte
|
||||
/// integers in xmm1 using signed saturation (SSE2).
|
||||
pub static PACKSSWB: [u8; 3] = [0x66, 0x0f, 0x63];
|
||||
|
||||
/// Converts 4 packed signed doubleword integers from xmm1 and from xmm2/m128 into 8 packed signed
|
||||
/// word integers in xmm1 using signed saturation (SSE2).
|
||||
pub static PACKSSDW: [u8; 3] = [0x66, 0x0f, 0x6b];
|
||||
|
||||
/// Converts 8 packed signed word integers from xmm1 and from xmm2/m128 into 16 packed unsigned byte
|
||||
/// integers in xmm1 using unsigned saturation (SSE2).
|
||||
pub static PACKUSWB: [u8; 3] = [0x66, 0x0f, 0x67];
|
||||
|
||||
/// Converts 4 packed signed doubleword integers from xmm1 and from xmm2/m128 into 8 unpacked signed
|
||||
/// word integers in xmm1 using unsigned saturation (SSE4.1).
|
||||
pub static PACKUSDW: [u8; 4] = [0x66, 0x0f, 0x38, 0x2b];
|
||||
|
||||
/// Add packed byte integers from xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PADDB: [u8; 3] = [0x66, 0x0f, 0xfc];
|
||||
|
||||
/// Add packed doubleword integers from xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PADDD: [u8; 3] = [0x66, 0x0f, 0xfe];
|
||||
|
||||
/// Add packed quadword integers from xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PADDQ: [u8; 3] = [0x66, 0x0f, 0xd4];
|
||||
|
||||
/// Add packed word integers from xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PADDW: [u8; 3] = [0x66, 0x0f, 0xfd];
|
||||
|
||||
/// Add packed signed byte integers from xmm2/m128 and xmm1 saturate the results (SSE).
|
||||
pub static PADDSB: [u8; 3] = [0x66, 0x0f, 0xec];
|
||||
|
||||
/// Add packed signed word integers from xmm2/m128 and xmm1 saturate the results (SSE).
|
||||
pub static PADDSW: [u8; 3] = [0x66, 0x0f, 0xed];
|
||||
|
||||
/// Add packed unsigned byte integers from xmm2/m128 and xmm1 saturate the results (SSE).
|
||||
pub static PADDUSB: [u8; 3] = [0x66, 0x0f, 0xdc];
|
||||
|
||||
/// Add packed unsigned word integers from xmm2/m128 and xmm1 saturate the results (SSE).
|
||||
pub static PADDUSW: [u8; 3] = [0x66, 0x0f, 0xdd];
|
||||
|
||||
/// Concatenate destination and source operands, extract a byte-aligned result into xmm1 that is
|
||||
/// shifted to the right by the constant number of bytes in imm8 (SSSE3).
|
||||
pub static PALIGNR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0f];
|
||||
|
||||
/// Bitwise AND of xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PAND: [u8; 3] = [0x66, 0x0f, 0xdb];
|
||||
|
||||
/// Bitwise AND NOT of xmm2/m128 and xmm1 (SSE2).
|
||||
pub static PANDN: [u8; 3] = [0x66, 0x0f, 0xdf];
|
||||
|
||||
/// Average packed unsigned byte integers from xmm2/m128 and xmm1 with rounding (SSE2).
|
||||
pub static PAVGB: [u8; 3] = [0x66, 0x0f, 0xE0];
|
||||
|
||||
/// Average packed unsigned word integers from xmm2/m128 and xmm1 with rounding (SSE2).
|
||||
pub static PAVGW: [u8; 3] = [0x66, 0x0f, 0xE3];
|
||||
|
||||
/// Select byte values from xmm1 and xmm2/m128 from mask specified in the high bit of each byte
|
||||
/// in XMM0 and store the values into xmm1 (SSE4.1).
|
||||
pub static PBLENDVB: [u8; 4] = [0x66, 0x0f, 0x38, 0x10];
|
||||
|
||||
/// Select words from xmm1 and xmm2/m128 from mask specified in imm8 and store the values into xmm1
|
||||
/// (SSE4.1).
|
||||
pub static PBLENDW: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0e];
|
||||
|
||||
/// Compare packed data for equal (SSE2).
|
||||
pub static PCMPEQB: [u8; 3] = [0x66, 0x0f, 0x74];
|
||||
|
||||
/// Compare packed data for equal (SSE2).
|
||||
pub static PCMPEQD: [u8; 3] = [0x66, 0x0f, 0x76];
|
||||
|
||||
/// Compare packed data for equal (SSE4.1).
|
||||
pub static PCMPEQQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x29];
|
||||
|
||||
/// Compare packed data for equal (SSE2).
|
||||
pub static PCMPEQW: [u8; 3] = [0x66, 0x0f, 0x75];
|
||||
|
||||
/// Compare packed signed byte integers for greater than (SSE2).
|
||||
pub static PCMPGTB: [u8; 3] = [0x66, 0x0f, 0x64];
|
||||
|
||||
/// Compare packed signed doubleword integers for greater than (SSE2).
|
||||
pub static PCMPGTD: [u8; 3] = [0x66, 0x0f, 0x66];
|
||||
|
||||
/// Compare packed signed quadword integers for greater than (SSE4.2).
|
||||
pub static PCMPGTQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x37];
|
||||
|
||||
/// Compare packed signed word integers for greater than (SSE2).
|
||||
pub static PCMPGTW: [u8; 3] = [0x66, 0x0f, 0x65];
|
||||
|
||||
/// Extract doubleword or quadword, depending on REX.W (SSE4.1).
|
||||
pub static PEXTR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x16];
|
||||
|
||||
/// Extract byte (SSE4.1).
|
||||
pub static PEXTRB: [u8; 4] = [0x66, 0x0f, 0x3a, 0x14];
|
||||
|
||||
/// Extract word (SSE4.1). There is a 3-byte SSE2 variant that can also move to m/16.
|
||||
pub static PEXTRW: [u8; 4] = [0x66, 0x0f, 0x3a, 0x15];
|
||||
|
||||
/// Insert doubleword or quadword, depending on REX.W (SSE4.1).
|
||||
pub static PINSR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x22];
|
||||
|
||||
/// Insert byte (SSE4.1).
|
||||
pub static PINSRB: [u8; 4] = [0x66, 0x0f, 0x3a, 0x20];
|
||||
|
||||
/// Insert word (SSE2).
|
||||
pub static PINSRW: [u8; 3] = [0x66, 0x0f, 0xc4];
|
||||
|
||||
/// Compare packed signed byte integers in xmm1 and xmm2/m128 and store packed maximum values in
|
||||
/// xmm1 (SSE4.1).
|
||||
pub static PMAXSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x3c];
|
||||
|
||||
/// Compare packed signed doubleword integers in xmm1 and xmm2/m128 and store packed maximum
|
||||
/// values in xmm1 (SSE4.1).
|
||||
pub static PMAXSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3d];
|
||||
|
||||
/// Compare packed signed word integers in xmm1 and xmm2/m128 and store packed maximum values in
|
||||
/// xmm1 (SSE2).
|
||||
pub static PMAXSW: [u8; 3] = [0x66, 0x0f, 0xee];
|
||||
|
||||
/// Compare packed unsigned byte integers in xmm1 and xmm2/m128 and store packed maximum values in
|
||||
/// xmm1 (SSE2).
|
||||
pub static PMAXUB: [u8; 3] = [0x66, 0x0f, 0xde];
|
||||
|
||||
/// Compare packed unsigned doubleword integers in xmm1 and xmm2/m128 and store packed maximum
|
||||
/// values in xmm1 (SSE4.1).
|
||||
pub static PMAXUD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3f];
|
||||
|
||||
/// Compare packed unsigned word integers in xmm1 and xmm2/m128 and store packed maximum values in
|
||||
/// xmm1 (SSE4.1).
|
||||
pub static PMAXUW: [u8; 4] = [0x66, 0x0f, 0x38, 0x3e];
|
||||
|
||||
/// Compare packed signed byte integers in xmm1 and xmm2/m128 and store packed minimum values in
|
||||
/// xmm1 (SSE4.1).
|
||||
pub static PMINSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x38];
|
||||
|
||||
/// Compare packed signed doubleword integers in xmm1 and xmm2/m128 and store packed minimum
|
||||
/// values in xmm1 (SSE4.1).
|
||||
pub static PMINSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x39];
|
||||
|
||||
/// Compare packed signed word integers in xmm1 and xmm2/m128 and store packed minimum values in
|
||||
/// xmm1 (SSE2).
|
||||
pub static PMINSW: [u8; 3] = [0x66, 0x0f, 0xea];
|
||||
|
||||
/// Compare packed unsigned byte integers in xmm1 and xmm2/m128 and store packed minimum values in
|
||||
/// xmm1 (SSE2).
|
||||
pub static PMINUB: [u8; 3] = [0x66, 0x0f, 0xda];
|
||||
|
||||
/// Compare packed unsigned doubleword integers in xmm1 and xmm2/m128 and store packed minimum
|
||||
/// values in xmm1 (SSE4.1).
|
||||
pub static PMINUD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3b];
|
||||
|
||||
/// Compare packed unsigned word integers in xmm1 and xmm2/m128 and store packed minimum values in
|
||||
/// xmm1 (SSE4.1).
|
||||
pub static PMINUW: [u8; 4] = [0x66, 0x0f, 0x38, 0x3a];
|
||||
|
||||
/// Sign extend 8 packed 8-bit integers in the low 8 bytes of xmm2/m64 to 8 packed 16-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVSXBW: [u8; 4] = [0x66, 0x0f, 0x38, 0x20];
|
||||
|
||||
/// Sign extend 4 packed 16-bit integers in the low 8 bytes of xmm2/m64 to 4 packed 32-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVSXWD: [u8; 4] = [0x66, 0x0f, 0x38, 0x23];
|
||||
|
||||
/// Sign extend 2 packed 32-bit integers in the low 8 bytes of xmm2/m64 to 2 packed 64-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVSXDQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x25];
|
||||
|
||||
/// Zero extend 8 packed 8-bit integers in the low 8 bytes of xmm2/m64 to 8 packed 16-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVZXBW: [u8; 4] = [0x66, 0x0f, 0x38, 0x30];
|
||||
|
||||
/// Zero extend 4 packed 16-bit integers in the low 8 bytes of xmm2/m64 to 4 packed 32-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVZXWD: [u8; 4] = [0x66, 0x0f, 0x38, 0x33];
|
||||
|
||||
/// Zero extend 2 packed 32-bit integers in the low 8 bytes of xmm2/m64 to 2 packed 64-bit
|
||||
/// integers in xmm1 (SSE4.1).
|
||||
pub static PMOVZXDQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x35];
|
||||
|
||||
/// Multiply the packed signed word integers in xmm1 and xmm2/m128, and store the low 16 bits of
|
||||
/// the results in xmm1 (SSE2).
|
||||
pub static PMULLW: [u8; 3] = [0x66, 0x0f, 0xd5];
|
||||
|
||||
/// Multiply the packed doubleword signed integers in xmm1 and xmm2/m128 and store the low 32
|
||||
/// bits of each product in xmm1 (SSE4.1).
|
||||
pub static PMULLD: [u8; 4] = [0x66, 0x0f, 0x38, 0x40];
|
||||
|
||||
/// Multiply the packed quadword signed integers in xmm2 and xmm3/m128 and store the low 64
|
||||
/// bits of each product in xmm1 (AVX512VL/DQ). Requires an EVEX encoding.
|
||||
pub static VPMULLQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x40];
|
||||
|
||||
/// Multiply packed unsigned doubleword integers in xmm1 by packed unsigned doubleword integers
|
||||
/// in xmm2/m128, and store the quadword results in xmm1 (SSE2).
|
||||
pub static PMULUDQ: [u8; 3] = [0x66, 0x0f, 0xf4];
|
||||
|
||||
/// Multiply the packed word integers, add adjacent doubleword results.
|
||||
pub static PMADDWD: [u8; 3] = [0x66, 0x0f, 0xf5];
|
||||
|
||||
/// Pop top of stack into r{16,32,64}; increment stack pointer.
|
||||
pub static POP_REG: [u8; 1] = [0x58];
|
||||
|
||||
/// Returns the count of number of bits set to 1.
|
||||
pub static POPCNT: [u8; 3] = [0xf3, 0x0f, 0xb8];
|
||||
|
||||
/// Bitwise OR of xmm2/m128 and xmm1 (SSE2).
|
||||
pub static POR: [u8; 3] = [0x66, 0x0f, 0xeb];
|
||||
|
||||
/// Shuffle bytes in xmm1 according to contents of xmm2/m128 (SSE3).
|
||||
pub static PSHUFB: [u8; 4] = [0x66, 0x0f, 0x38, 0x00];
|
||||
|
||||
/// Shuffle the doublewords in xmm2/m128 based on the encoding in imm8 and
|
||||
/// store the result in xmm1 (SSE2).
|
||||
pub static PSHUFD: [u8; 3] = [0x66, 0x0f, 0x70];
|
||||
|
||||
/// Shift words in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
|
||||
/// digit used in the ModR/M byte (SSE2).
|
||||
pub static PS_W_IMM: [u8; 3] = [0x66, 0x0f, 0x71];
|
||||
|
||||
/// Shift doublewords in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
|
||||
/// digit used in the ModR/M byte (SSE2).
|
||||
pub static PS_D_IMM: [u8; 3] = [0x66, 0x0f, 0x72];
|
||||
|
||||
/// Shift quadwords in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
|
||||
/// digit used in the ModR/M byte (SSE2).
|
||||
pub static PS_Q_IMM: [u8; 3] = [0x66, 0x0f, 0x73];
|
||||
|
||||
/// Shift words in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSLLW: [u8; 3] = [0x66, 0x0f, 0xf1];
|
||||
|
||||
/// Shift doublewords in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSLLD: [u8; 3] = [0x66, 0x0f, 0xf2];
|
||||
|
||||
/// Shift quadwords in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSLLQ: [u8; 3] = [0x66, 0x0f, 0xf3];
|
||||
|
||||
/// Shift words in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSRLW: [u8; 3] = [0x66, 0x0f, 0xd1];
|
||||
|
||||
/// Shift doublewords in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSRLD: [u8; 3] = [0x66, 0x0f, 0xd2];
|
||||
|
||||
/// Shift quadwords in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
|
||||
pub static PSRLQ: [u8; 3] = [0x66, 0x0f, 0xd3];
|
||||
|
||||
/// Shift words in xmm1 right by xmm2/m128 while shifting in sign bits (SSE2).
|
||||
pub static PSRAW: [u8; 3] = [0x66, 0x0f, 0xe1];
|
||||
|
||||
/// Shift doublewords in xmm1 right by xmm2/m128 while shifting in sign bits (SSE2).
|
||||
pub static PSRAD: [u8; 3] = [0x66, 0x0f, 0xe2];
|
||||
|
||||
/// Subtract packed byte integers in xmm2/m128 from packed byte integers in xmm1 (SSE2).
|
||||
pub static PSUBB: [u8; 3] = [0x66, 0x0f, 0xf8];
|
||||
|
||||
/// Subtract packed word integers in xmm2/m128 from packed word integers in xmm1 (SSE2).
|
||||
pub static PSUBW: [u8; 3] = [0x66, 0x0f, 0xf9];
|
||||
|
||||
/// Subtract packed doubleword integers in xmm2/m128 from doubleword byte integers in xmm1 (SSE2).
|
||||
pub static PSUBD: [u8; 3] = [0x66, 0x0f, 0xfa];
|
||||
|
||||
/// Subtract packed quadword integers in xmm2/m128 from xmm1 (SSE2).
|
||||
pub static PSUBQ: [u8; 3] = [0x66, 0x0f, 0xfb];
|
||||
|
||||
/// Subtract packed signed byte integers in xmm2/m128 from packed signed byte integers in xmm1
|
||||
/// and saturate results (SSE2).
|
||||
pub static PSUBSB: [u8; 3] = [0x66, 0x0f, 0xe8];
|
||||
|
||||
/// Subtract packed signed word integers in xmm2/m128 from packed signed word integers in xmm1
|
||||
/// and saturate results (SSE2).
|
||||
pub static PSUBSW: [u8; 3] = [0x66, 0x0f, 0xe9];
|
||||
|
||||
/// Subtract packed unsigned byte integers in xmm2/m128 from packed unsigned byte integers in xmm1
|
||||
/// and saturate results (SSE2).
|
||||
pub static PSUBUSB: [u8; 3] = [0x66, 0x0f, 0xd8];
|
||||
|
||||
/// Subtract packed unsigned word integers in xmm2/m128 from packed unsigned word integers in xmm1
|
||||
/// and saturate results (SSE2).
|
||||
pub static PSUBUSW: [u8; 3] = [0x66, 0x0f, 0xd9];
|
||||
|
||||
/// Set ZF if xmm2/m128 AND xmm1 result is all 0s; set CF if xmm2/m128 AND NOT xmm1 result is all
|
||||
/// 0s (SSE4.1).
|
||||
pub static PTEST: [u8; 4] = [0x66, 0x0f, 0x38, 0x17];
|
||||
|
||||
/// Unpack and interleave high-order bytes from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKHBW: [u8; 3] = [0x66, 0x0f, 0x68];
|
||||
|
||||
/// Unpack and interleave high-order words from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKHWD: [u8; 3] = [0x66, 0x0f, 0x69];
|
||||
|
||||
/// Unpack and interleave high-order doublewords from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKHDQ: [u8; 3] = [0x66, 0x0f, 0x6A];
|
||||
|
||||
/// Unpack and interleave high-order quadwords from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKHQDQ: [u8; 3] = [0x66, 0x0f, 0x6D];
|
||||
|
||||
/// Unpack and interleave low-order bytes from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKLBW: [u8; 3] = [0x66, 0x0f, 0x60];
|
||||
|
||||
/// Unpack and interleave low-order words from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKLWD: [u8; 3] = [0x66, 0x0f, 0x61];
|
||||
|
||||
/// Unpack and interleave low-order doublewords from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKLDQ: [u8; 3] = [0x66, 0x0f, 0x62];
|
||||
|
||||
/// Unpack and interleave low-order quadwords from xmm1 and xmm2/m128 into xmm1 (SSE2).
|
||||
pub static PUNPCKLQDQ: [u8; 3] = [0x66, 0x0f, 0x6C];
|
||||
|
||||
/// Push r{16,32,64}.
|
||||
pub static PUSH_REG: [u8; 1] = [0x50];
|
||||
|
||||
/// Logical exclusive OR (SSE2).
|
||||
pub static PXOR: [u8; 3] = [0x66, 0x0f, 0xef];
|
||||
|
||||
/// Near return to calling procedure.
|
||||
pub static RET_NEAR: [u8; 1] = [0xc3];
|
||||
|
||||
/// General rotation opcode. Kind of rotation depends on encoding.
|
||||
pub static ROTATE_CL: [u8; 1] = [0xd3];
|
||||
|
||||
/// General rotation opcode. Kind of rotation depends on encoding.
|
||||
pub static ROTATE_IMM8: [u8; 1] = [0xc1];
|
||||
|
||||
/// Round scalar doubl-precision floating-point values.
|
||||
pub static ROUNDSD: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0b];
|
||||
|
||||
/// Round scalar single-precision floating-point values.
|
||||
pub static ROUNDSS: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0a];
|
||||
|
||||
/// Subtract with borrow r{16,32,64} from r/m of the same size.
|
||||
pub static SBB: [u8; 1] = [0x19];
|
||||
|
||||
/// Set byte if overflow (OF=1).
|
||||
pub static SET_BYTE_IF_OVERFLOW: [u8; 2] = [0x0f, 0x90];
|
||||
|
||||
/// Compute the square root of the packed double-precision floating-point values and store the
|
||||
/// result in xmm1 (SSE2).
|
||||
pub static SQRTPD: [u8; 3] = [0x66, 0x0f, 0x51];
|
||||
|
||||
/// Compute the square root of the packed double-precision floating-point values and store the
|
||||
/// result in xmm1 (SSE).
|
||||
pub static SQRTPS: [u8; 2] = [0x0f, 0x51];
|
||||
|
||||
/// Compute square root of scalar double-precision floating-point value.
|
||||
pub static SQRTSD: [u8; 3] = [0xf2, 0x0f, 0x51];
|
||||
|
||||
/// Compute square root of scalar single-precision value.
|
||||
pub static SQRTSS: [u8; 3] = [0xf3, 0x0f, 0x51];
|
||||
|
||||
/// Subtract r{16,32,64} from r/m of same size.
|
||||
pub static SUB: [u8; 1] = [0x29];
|
||||
|
||||
/// Subtract packed double-precision floating-point values in xmm2/mem from xmm1 and store result
|
||||
/// in xmm1 (SSE2).
|
||||
pub static SUBPD: [u8; 3] = [0x66, 0x0f, 0x5c];
|
||||
|
||||
/// Subtract packed single-precision floating-point values in xmm2/mem from xmm1 and store result
|
||||
/// in xmm1 (SSE).
|
||||
pub static SUBPS: [u8; 2] = [0x0f, 0x5c];
|
||||
|
||||
/// Subtract the low double-precision floating-point value in xmm2/m64 from xmm1
|
||||
/// and store the result in xmm1.
|
||||
pub static SUBSD: [u8; 3] = [0xf2, 0x0f, 0x5c];
|
||||
|
||||
/// Subtract the low single-precision floating-point value in xmm2/m32 from xmm1
|
||||
/// and store the result in xmm1.
|
||||
pub static SUBSS: [u8; 3] = [0xf3, 0x0f, 0x5c];
|
||||
|
||||
/// AND r8 with r/m8; set SF, ZF, PF according to result.
|
||||
pub static TEST_BYTE_REG: [u8; 1] = [0x84];
|
||||
|
||||
/// AND {r16, r32, r64} with r/m of the same size; set SF, ZF, PF according to result.
|
||||
pub static TEST_REG: [u8; 1] = [0x85];
|
||||
|
||||
/// Count the number of trailing zero bits.
|
||||
pub static TZCNT: [u8; 3] = [0xf3, 0x0f, 0xbc];
|
||||
|
||||
/// Compare low double-precision floating-point values in xmm1 and xmm2/mem64
|
||||
/// and set the EFLAGS flags accordingly.
|
||||
pub static UCOMISD: [u8; 3] = [0x66, 0x0f, 0x2e];
|
||||
|
||||
/// Compare low single-precision floating-point values in xmm1 and xmm2/mem32
|
||||
/// and set the EFLAGS flags accordingly.
|
||||
pub static UCOMISS: [u8; 2] = [0x0f, 0x2e];
|
||||
|
||||
/// Raise invalid opcode instruction.
|
||||
pub static UNDEFINED2: [u8; 2] = [0x0f, 0x0b];
|
||||
|
||||
/// Convert four packed unsigned doubleword integers from xmm2/m128/m32bcst to packed
|
||||
/// single-precision floating-point values in xmm1 with writemask k1. Rounding behavior
|
||||
/// is controlled by MXCSR but can be overriden by EVEX.L'L in static rounding mode
|
||||
/// (AVX512VL, AVX512F).
|
||||
pub static VCVTUDQ2PS: [u8; 3] = [0xf2, 0x0f, 0x7a];
|
||||
|
||||
/// imm{16,32} XOR r/m{16,32,64}, possibly sign-extended.
|
||||
pub static XOR_IMM: [u8; 1] = [0x81];
|
||||
|
||||
/// r/m{16,32,64} XOR sign-extended imm8.
|
||||
pub static XOR_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
|
||||
|
||||
/// r/m{16,32,64} XOR register of the same size.
|
||||
pub static XOR: [u8; 1] = [0x31];
|
||||
|
||||
/// Bitwise logical XOR of packed double-precision floating-point values.
|
||||
pub static XORPD: [u8; 3] = [0x66, 0x0f, 0x57];
|
||||
|
||||
/// Bitwise logical XOR of packed single-precision floating-point values.
|
||||
pub static XORPS: [u8; 2] = [0x0f, 0x57];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +0,0 @@
|
||||
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
|
||||
|
||||
pub(crate) fn define() -> IsaRegs {
|
||||
let mut regs = IsaRegsBuilder::new();
|
||||
|
||||
let builder = RegBankBuilder::new("FloatRegs", "xmm")
|
||||
.units(16)
|
||||
.track_pressure(true);
|
||||
let float_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("IntRegs", "r")
|
||||
.units(16)
|
||||
.names(vec!["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"])
|
||||
.track_pressure(true)
|
||||
.pinned_reg(15);
|
||||
let int_regs = regs.add_bank(builder);
|
||||
|
||||
let builder = RegBankBuilder::new("FlagRegs", "")
|
||||
.units(1)
|
||||
.names(vec!["rflags"])
|
||||
.track_pressure(false);
|
||||
let flag_reg = regs.add_bank(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
|
||||
let gpr = regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
|
||||
let fpr = regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::subclass_of("GPR8", gpr, 0, 8);
|
||||
let gpr8 = regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::subclass_of("ABCD", gpr8, 0, 4);
|
||||
regs.add_class(builder);
|
||||
|
||||
let builder = RegClassBuilder::subclass_of("FPR8", fpr, 0, 8);
|
||||
regs.add_class(builder);
|
||||
|
||||
regs.build()
|
||||
}
|
||||
@@ -7,11 +7,7 @@ mod srcgen;
|
||||
pub mod error;
|
||||
pub mod isa;
|
||||
|
||||
mod gen_binemit;
|
||||
mod gen_encodings;
|
||||
mod gen_inst;
|
||||
mod gen_legalizer;
|
||||
mod gen_registers;
|
||||
mod gen_settings;
|
||||
mod gen_types;
|
||||
|
||||
@@ -57,44 +53,13 @@ pub fn generate(
|
||||
&out_dir,
|
||||
)?;
|
||||
|
||||
let extra_legalization_groups: &[&'static str] = if !new_backend_isas.is_empty() {
|
||||
// The new backend only requires the "expand" legalization group.
|
||||
&["expand"]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
gen_legalizer::generate(
|
||||
&target_isas,
|
||||
&shared_defs.transform_groups,
|
||||
extra_legalization_groups,
|
||||
"legalize",
|
||||
&out_dir,
|
||||
)?;
|
||||
|
||||
for isa in target_isas {
|
||||
gen_registers::generate(&isa, &format!("registers-{}.rs", isa.name), &out_dir)?;
|
||||
|
||||
gen_settings::generate(
|
||||
&isa.settings,
|
||||
gen_settings::ParentGroup::Shared,
|
||||
&format!("settings-{}.rs", isa.name),
|
||||
&out_dir,
|
||||
)?;
|
||||
|
||||
gen_encodings::generate(
|
||||
&shared_defs,
|
||||
&isa,
|
||||
&format!("encoding-{}.rs", isa.name),
|
||||
&out_dir,
|
||||
)?;
|
||||
|
||||
gen_binemit::generate(
|
||||
&isa.name,
|
||||
&isa.recipes,
|
||||
&format!("binemit-{}.rs", isa.name),
|
||||
&out_dir,
|
||||
)?;
|
||||
}
|
||||
|
||||
for isa in new_backend_isas {
|
||||
@@ -119,7 +84,7 @@ pub fn generate(
|
||||
isa::Isa::S390x => {
|
||||
// s390x doesn't have platform-specific settings.
|
||||
}
|
||||
isa::Isa::Arm32 | isa::Isa::Riscv => todo!(),
|
||||
isa::Isa::Arm32 => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::cdsl::instructions::{
|
||||
AllInstructions, InstructionBuilder as Inst, InstructionGroup, InstructionGroupBuilder,
|
||||
AllInstructions, InstructionBuilder as Inst, InstructionGroupBuilder,
|
||||
};
|
||||
use crate::cdsl::operands::Operand;
|
||||
use crate::cdsl::type_inference::Constraint::WiderOrEq;
|
||||
@@ -767,7 +767,7 @@ pub(crate) fn define(
|
||||
formats: &Formats,
|
||||
imm: &Immediates,
|
||||
entities: &EntityRefs,
|
||||
) -> InstructionGroup {
|
||||
) {
|
||||
let mut ig = InstructionGroupBuilder::new(all_instructions);
|
||||
|
||||
define_control_flow(&mut ig, formats, imm, entities);
|
||||
@@ -4647,6 +4647,4 @@ pub(crate) fn define(
|
||||
)
|
||||
.other_side_effects(true),
|
||||
);
|
||||
|
||||
ig.build()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,12 @@ pub mod entities;
|
||||
pub mod formats;
|
||||
pub mod immediates;
|
||||
pub mod instructions;
|
||||
pub mod legalize;
|
||||
pub mod settings;
|
||||
pub mod types;
|
||||
|
||||
use crate::cdsl::formats::{FormatStructure, InstructionFormat};
|
||||
use crate::cdsl::instructions::{AllInstructions, InstructionGroup};
|
||||
use crate::cdsl::instructions::AllInstructions;
|
||||
use crate::cdsl::settings::SettingGroup;
|
||||
use crate::cdsl::xform::TransformGroups;
|
||||
|
||||
use crate::shared::entities::EntityRefs;
|
||||
use crate::shared::formats::Formats;
|
||||
@@ -24,11 +22,6 @@ use std::rc::Rc;
|
||||
pub(crate) struct Definitions {
|
||||
pub settings: SettingGroup,
|
||||
pub all_instructions: AllInstructions,
|
||||
pub instructions: InstructionGroup,
|
||||
pub imm: Immediates,
|
||||
pub formats: Formats,
|
||||
pub transform_groups: TransformGroups,
|
||||
pub entities: EntityRefs,
|
||||
}
|
||||
|
||||
pub(crate) fn define() -> Definitions {
|
||||
@@ -37,18 +30,11 @@ pub(crate) fn define() -> Definitions {
|
||||
let immediates = Immediates::new();
|
||||
let entities = EntityRefs::new();
|
||||
let formats = Formats::new(&immediates, &entities);
|
||||
let instructions =
|
||||
instructions::define(&mut all_instructions, &formats, &immediates, &entities);
|
||||
let transform_groups = legalize::define(&instructions, &immediates);
|
||||
instructions::define(&mut all_instructions, &formats, &immediates, &entities);
|
||||
|
||||
Definitions {
|
||||
settings: settings::define(),
|
||||
all_instructions,
|
||||
instructions,
|
||||
imm: immediates,
|
||||
formats,
|
||||
transform_groups,
|
||||
entities,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,15 +77,6 @@ impl Formatter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a string containing whitespace outdented one level. Used for
|
||||
/// lines of code that are inside a single indented block.
|
||||
fn get_outdent(&mut self) -> String {
|
||||
self.indent_pop();
|
||||
let s = self.get_indent();
|
||||
self.indent_push();
|
||||
s
|
||||
}
|
||||
|
||||
/// Add an indented line.
|
||||
pub fn line(&mut self, contents: impl AsRef<str>) {
|
||||
let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref());
|
||||
@@ -97,12 +88,6 @@ impl Formatter {
|
||||
self.lines.push("\n".to_string());
|
||||
}
|
||||
|
||||
/// Emit a line outdented one level.
|
||||
pub fn outdented_line(&mut self, s: &str) {
|
||||
let new_line = format!("{}{}\n", self.get_outdent(), s);
|
||||
self.lines.push(new_line);
|
||||
}
|
||||
|
||||
/// Write `self.lines` to a file.
|
||||
pub fn update_file(
|
||||
&self,
|
||||
|
||||
@@ -32,9 +32,6 @@ impl<'entries, T: Eq + Hash> UniqueTable<'entries, T> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.table.len()
|
||||
}
|
||||
pub fn get(&self, index: usize) -> &T {
|
||||
self.table[index]
|
||||
}
|
||||
pub fn iter(&self) -> slice::Iter<&'entries T> {
|
||||
self.table.iter()
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
//! Shared ISA-specific definitions.
|
||||
|
||||
pub mod x86;
|
||||
@@ -1,419 +0,0 @@
|
||||
//! Provides a named interface to the `u16` Encoding bits.
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Named interface to the `u16` Encoding bits, representing an opcode.
|
||||
///
|
||||
/// Cranelift requires each recipe to have a single encoding size in bytes.
|
||||
/// X86 opcodes are variable length, so we use separate recipes for different
|
||||
/// styles of opcodes and prefixes. The opcode format is indicated by the
|
||||
/// recipe name prefix.
|
||||
///
|
||||
/// VEX/XOP and EVEX prefixes are not yet supported.
|
||||
/// Encodings using any of these prefixes are represented by separate recipes.
|
||||
///
|
||||
/// The encoding bits are:
|
||||
///
|
||||
/// 0-7: The opcode byte <op>.
|
||||
/// 8-9: pp, mandatory prefix:
|
||||
/// 00: none (Op*)
|
||||
/// 01: 66 (Mp*)
|
||||
/// 10: F3 (Mp*)
|
||||
/// 11: F2 (Mp*)
|
||||
/// 10-11: mm, opcode map:
|
||||
/// 00: <op> (Op1/Mp1)
|
||||
/// 01: 0F <op> (Op2/Mp2)
|
||||
/// 10: 0F 38 <op> (Op3/Mp3)
|
||||
/// 11: 0F 3A <op> (Op3/Mp3)
|
||||
/// 12-14 rrr, opcode bits for the ModR/M byte for certain opcodes.
|
||||
/// 15: REX.W bit (or VEX.W/E)
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct EncodingBits(u16);
|
||||
const OPCODE: RangeInclusive<u16> = 0..=7;
|
||||
const OPCODE_PREFIX: RangeInclusive<u16> = 8..=11; // Includes pp and mm.
|
||||
const RRR: RangeInclusive<u16> = 12..=14;
|
||||
const REX_W: RangeInclusive<u16> = 15..=15;
|
||||
|
||||
impl From<u16> for EncodingBits {
|
||||
fn from(bits: u16) -> Self {
|
||||
Self(bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodingBits {
|
||||
/// Constructs a new EncodingBits from parts.
|
||||
pub fn new(op_bytes: &[u8], rrr: u16, rex_w: u16) -> Self {
|
||||
assert!(
|
||||
!op_bytes.is_empty(),
|
||||
"op_bytes must include at least one opcode byte"
|
||||
);
|
||||
let mut new = Self::from(0);
|
||||
let last_byte = op_bytes[op_bytes.len() - 1];
|
||||
new.write(OPCODE, last_byte as u16);
|
||||
let prefix: u8 = OpcodePrefix::from_opcode(op_bytes).into();
|
||||
new.write(OPCODE_PREFIX, prefix as u16);
|
||||
new.write(RRR, rrr);
|
||||
new.write(REX_W, rex_w);
|
||||
new
|
||||
}
|
||||
|
||||
/// Returns a copy of the EncodingBits with the RRR bits set.
|
||||
#[inline]
|
||||
pub fn with_rrr(mut self, rrr: u8) -> Self {
|
||||
debug_assert_eq!(self.rrr(), 0);
|
||||
self.write(RRR, rrr.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a copy of the EncodingBits with the REX.W bit set.
|
||||
#[inline]
|
||||
pub fn with_rex_w(mut self) -> Self {
|
||||
debug_assert_eq!(self.rex_w(), 0);
|
||||
self.write(REX_W, 1);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the raw bits.
|
||||
#[inline]
|
||||
pub fn bits(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Convenience method for writing bits to specific range.
|
||||
#[inline]
|
||||
fn write(&mut self, range: RangeInclusive<u16>, value: u16) {
|
||||
assert!(ExactSizeIterator::len(&range) > 0);
|
||||
let size = range.end() - range.start() + 1; // Calculate the number of bits in the range.
|
||||
let mask = (1 << size) - 1; // Generate a bit mask.
|
||||
debug_assert!(
|
||||
value <= mask,
|
||||
"The written value should have fewer than {} bits.",
|
||||
size
|
||||
);
|
||||
let mask_complement = !(mask << *range.start()); // Create the bitwise complement for the clear mask.
|
||||
self.0 &= mask_complement; // Clear the bits in `range`.
|
||||
let value = (value & mask) << *range.start(); // Place the value in the correct location.
|
||||
self.0 |= value; // Modify the bits in `range`.
|
||||
}
|
||||
|
||||
/// Convenience method for reading bits from a specific range.
|
||||
#[inline]
|
||||
fn read(self, range: RangeInclusive<u16>) -> u8 {
|
||||
assert!(ExactSizeIterator::len(&range) > 0);
|
||||
let size = range.end() - range.start() + 1; // Calculate the number of bits in the range.
|
||||
debug_assert!(size <= 8, "This structure expects ranges of at most 8 bits");
|
||||
let mask = (1 << size) - 1; // Generate a bit mask.
|
||||
((self.0 >> *range.start()) & mask) as u8
|
||||
}
|
||||
|
||||
/// Instruction opcode byte, without the prefix.
|
||||
#[inline]
|
||||
pub fn opcode_byte(self) -> u8 {
|
||||
self.read(OPCODE)
|
||||
}
|
||||
|
||||
/// Prefix kind for the instruction, as an enum.
|
||||
#[inline]
|
||||
pub fn prefix(self) -> OpcodePrefix {
|
||||
OpcodePrefix::from(self.read(OPCODE_PREFIX))
|
||||
}
|
||||
|
||||
/// Extracts the PP bits of the OpcodePrefix.
|
||||
#[inline]
|
||||
pub fn pp(self) -> u8 {
|
||||
self.prefix().to_primitive() & 0x3
|
||||
}
|
||||
|
||||
/// Extracts the MM bits of the OpcodePrefix.
|
||||
#[inline]
|
||||
pub fn mm(self) -> u8 {
|
||||
(self.prefix().to_primitive() >> 2) & 0x3
|
||||
}
|
||||
|
||||
/// Bits for the ModR/M byte for certain opcodes.
|
||||
#[inline]
|
||||
pub fn rrr(self) -> u8 {
|
||||
self.read(RRR)
|
||||
}
|
||||
|
||||
/// REX.W bit (or VEX.W/E).
|
||||
#[inline]
|
||||
pub fn rex_w(self) -> u8 {
|
||||
self.read(REX_W)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opcode prefix representation.
|
||||
///
|
||||
/// The prefix type occupies four of the EncodingBits.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum OpcodePrefix {
|
||||
Op1,
|
||||
Mp1_66,
|
||||
Mp1_f3,
|
||||
Mp1_f2,
|
||||
Op2_0f,
|
||||
Mp2_66_0f,
|
||||
Mp2_f3_0f,
|
||||
Mp2_f2_0f,
|
||||
Op3_0f_38,
|
||||
Mp3_66_0f_38,
|
||||
Mp3_f3_0f_38,
|
||||
Mp3_f2_0f_38,
|
||||
Op3_0f_3a,
|
||||
Mp3_66_0f_3a,
|
||||
Mp3_f3_0f_3a,
|
||||
Mp3_f2_0f_3a,
|
||||
}
|
||||
|
||||
impl From<u8> for OpcodePrefix {
|
||||
fn from(n: u8) -> Self {
|
||||
use OpcodePrefix::*;
|
||||
match n {
|
||||
0b0000 => Op1,
|
||||
0b0001 => Mp1_66,
|
||||
0b0010 => Mp1_f3,
|
||||
0b0011 => Mp1_f2,
|
||||
0b0100 => Op2_0f,
|
||||
0b0101 => Mp2_66_0f,
|
||||
0b0110 => Mp2_f3_0f,
|
||||
0b0111 => Mp2_f2_0f,
|
||||
0b1000 => Op3_0f_38,
|
||||
0b1001 => Mp3_66_0f_38,
|
||||
0b1010 => Mp3_f3_0f_38,
|
||||
0b1011 => Mp3_f2_0f_38,
|
||||
0b1100 => Op3_0f_3a,
|
||||
0b1101 => Mp3_66_0f_3a,
|
||||
0b1110 => Mp3_f3_0f_3a,
|
||||
0b1111 => Mp3_f2_0f_3a,
|
||||
_ => panic!("invalid opcode prefix"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for OpcodePrefix {
|
||||
fn into(self) -> u8 {
|
||||
use OpcodePrefix::*;
|
||||
match self {
|
||||
Op1 => 0b0000,
|
||||
Mp1_66 => 0b0001,
|
||||
Mp1_f3 => 0b0010,
|
||||
Mp1_f2 => 0b0011,
|
||||
Op2_0f => 0b0100,
|
||||
Mp2_66_0f => 0b0101,
|
||||
Mp2_f3_0f => 0b0110,
|
||||
Mp2_f2_0f => 0b0111,
|
||||
Op3_0f_38 => 0b1000,
|
||||
Mp3_66_0f_38 => 0b1001,
|
||||
Mp3_f3_0f_38 => 0b1010,
|
||||
Mp3_f2_0f_38 => 0b1011,
|
||||
Op3_0f_3a => 0b1100,
|
||||
Mp3_66_0f_3a => 0b1101,
|
||||
Mp3_f3_0f_3a => 0b1110,
|
||||
Mp3_f2_0f_3a => 0b1111,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpcodePrefix {
|
||||
/// Convert an opcode prefix to a `u8`; this is a convenience proxy for `Into<u8>`.
|
||||
fn to_primitive(self) -> u8 {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Extracts the OpcodePrefix from the opcode.
|
||||
pub fn from_opcode(op_bytes: &[u8]) -> Self {
|
||||
assert!(!op_bytes.is_empty(), "at least one opcode byte");
|
||||
|
||||
let prefix_bytes = &op_bytes[..op_bytes.len() - 1];
|
||||
match prefix_bytes {
|
||||
[] => Self::Op1,
|
||||
[0x66] => Self::Mp1_66,
|
||||
[0xf3] => Self::Mp1_f3,
|
||||
[0xf2] => Self::Mp1_f2,
|
||||
[0x0f] => Self::Op2_0f,
|
||||
[0x66, 0x0f] => Self::Mp2_66_0f,
|
||||
[0xf3, 0x0f] => Self::Mp2_f3_0f,
|
||||
[0xf2, 0x0f] => Self::Mp2_f2_0f,
|
||||
[0x0f, 0x38] => Self::Op3_0f_38,
|
||||
[0x66, 0x0f, 0x38] => Self::Mp3_66_0f_38,
|
||||
[0xf3, 0x0f, 0x38] => Self::Mp3_f3_0f_38,
|
||||
[0xf2, 0x0f, 0x38] => Self::Mp3_f2_0f_38,
|
||||
[0x0f, 0x3a] => Self::Op3_0f_3a,
|
||||
[0x66, 0x0f, 0x3a] => Self::Mp3_66_0f_3a,
|
||||
[0xf3, 0x0f, 0x3a] => Self::Mp3_f3_0f_3a,
|
||||
[0xf2, 0x0f, 0x3a] => Self::Mp3_f2_0f_3a,
|
||||
_ => {
|
||||
panic!("unexpected opcode sequence: {:?}", op_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the recipe name prefix.
|
||||
///
|
||||
/// At the moment, each similar OpcodePrefix group is given its own Recipe.
|
||||
/// In order to distinguish them, this string is prefixed.
|
||||
pub fn recipe_name_prefix(self) -> &'static str {
|
||||
use OpcodePrefix::*;
|
||||
match self {
|
||||
Op1 => "Op1",
|
||||
Op2_0f => "Op2",
|
||||
Op3_0f_38 | Op3_0f_3a => "Op3",
|
||||
Mp1_66 | Mp1_f3 | Mp1_f2 => "Mp1",
|
||||
Mp2_66_0f | Mp2_f3_0f | Mp2_f2_0f => "Mp2",
|
||||
Mp3_66_0f_38 | Mp3_f3_0f_38 | Mp3_f2_0f_38 => "Mp3",
|
||||
Mp3_66_0f_3a | Mp3_f3_0f_3a | Mp3_f2_0f_3a => "Mp3",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Helper function for prefix_roundtrip() to avoid long lines.
|
||||
fn test_roundtrip(p: OpcodePrefix) {
|
||||
assert_eq!(p, OpcodePrefix::from(p.to_primitive()));
|
||||
}
|
||||
|
||||
/// Tests that to/from each opcode matches.
|
||||
#[test]
|
||||
fn prefix_roundtrip() {
|
||||
test_roundtrip(OpcodePrefix::Op1);
|
||||
test_roundtrip(OpcodePrefix::Mp1_66);
|
||||
test_roundtrip(OpcodePrefix::Mp1_f3);
|
||||
test_roundtrip(OpcodePrefix::Mp1_f2);
|
||||
test_roundtrip(OpcodePrefix::Op2_0f);
|
||||
test_roundtrip(OpcodePrefix::Mp2_66_0f);
|
||||
test_roundtrip(OpcodePrefix::Mp2_f3_0f);
|
||||
test_roundtrip(OpcodePrefix::Mp2_f2_0f);
|
||||
test_roundtrip(OpcodePrefix::Op3_0f_38);
|
||||
test_roundtrip(OpcodePrefix::Mp3_66_0f_38);
|
||||
test_roundtrip(OpcodePrefix::Mp3_f3_0f_38);
|
||||
test_roundtrip(OpcodePrefix::Mp3_f2_0f_38);
|
||||
test_roundtrip(OpcodePrefix::Op3_0f_3a);
|
||||
test_roundtrip(OpcodePrefix::Mp3_66_0f_3a);
|
||||
test_roundtrip(OpcodePrefix::Mp3_f3_0f_3a);
|
||||
test_roundtrip(OpcodePrefix::Mp3_f2_0f_3a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_to_name() {
|
||||
assert_eq!(OpcodePrefix::Op1.recipe_name_prefix(), "Op1");
|
||||
assert_eq!(OpcodePrefix::Op2_0f.recipe_name_prefix(), "Op2");
|
||||
assert_eq!(OpcodePrefix::Op3_0f_38.recipe_name_prefix(), "Op3");
|
||||
assert_eq!(OpcodePrefix::Mp1_66.recipe_name_prefix(), "Mp1");
|
||||
assert_eq!(OpcodePrefix::Mp2_66_0f.recipe_name_prefix(), "Mp2");
|
||||
assert_eq!(OpcodePrefix::Mp3_66_0f_3a.recipe_name_prefix(), "Mp3");
|
||||
}
|
||||
|
||||
/// Tests that the opcode_byte is the lower of the EncodingBits.
|
||||
#[test]
|
||||
fn encodingbits_opcode_byte() {
|
||||
let enc = EncodingBits::from(0x00ff);
|
||||
assert_eq!(enc.opcode_byte(), 0xff);
|
||||
assert_eq!(enc.prefix().to_primitive(), 0x0);
|
||||
assert_eq!(enc.rrr(), 0x0);
|
||||
assert_eq!(enc.rex_w(), 0x0);
|
||||
|
||||
let enc = EncodingBits::from(0x00cd);
|
||||
assert_eq!(enc.opcode_byte(), 0xcd);
|
||||
}
|
||||
|
||||
/// Tests that the OpcodePrefix is encoded correctly.
|
||||
#[test]
|
||||
fn encodingbits_prefix() {
|
||||
let enc = EncodingBits::from(0x0c00);
|
||||
assert_eq!(enc.opcode_byte(), 0x00);
|
||||
assert_eq!(enc.prefix().to_primitive(), 0xc);
|
||||
assert_eq!(enc.prefix(), OpcodePrefix::Op3_0f_3a);
|
||||
assert_eq!(enc.rrr(), 0x0);
|
||||
assert_eq!(enc.rex_w(), 0x0);
|
||||
}
|
||||
|
||||
/// Tests that the PP bits are encoded correctly.
|
||||
#[test]
|
||||
fn encodingbits_pp() {
|
||||
let enc = EncodingBits::from(0x0300);
|
||||
assert_eq!(enc.opcode_byte(), 0x0);
|
||||
assert_eq!(enc.pp(), 0x3);
|
||||
assert_eq!(enc.mm(), 0x0);
|
||||
assert_eq!(enc.rrr(), 0x0);
|
||||
assert_eq!(enc.rex_w(), 0x0);
|
||||
}
|
||||
|
||||
/// Tests that the MM bits are encoded correctly.
|
||||
#[test]
|
||||
fn encodingbits_mm() {
|
||||
let enc = EncodingBits::from(0x0c00);
|
||||
assert_eq!(enc.opcode_byte(), 0x0);
|
||||
assert_eq!(enc.pp(), 0x00);
|
||||
assert_eq!(enc.mm(), 0x3);
|
||||
assert_eq!(enc.rrr(), 0x0);
|
||||
assert_eq!(enc.rex_w(), 0x0);
|
||||
}
|
||||
|
||||
/// Tests that the ModR/M bits are encoded correctly.
|
||||
#[test]
|
||||
fn encodingbits_rrr() {
|
||||
let enc = EncodingBits::from(0x5000);
|
||||
assert_eq!(enc.opcode_byte(), 0x0);
|
||||
assert_eq!(enc.prefix().to_primitive(), 0x0);
|
||||
assert_eq!(enc.rrr(), 0x5);
|
||||
assert_eq!(enc.rex_w(), 0x0);
|
||||
}
|
||||
|
||||
/// Tests that the REX.W bit is encoded correctly.
|
||||
#[test]
|
||||
fn encodingbits_rex_w() {
|
||||
let enc = EncodingBits::from(0x8000);
|
||||
assert_eq!(enc.opcode_byte(), 0x00);
|
||||
assert_eq!(enc.prefix().to_primitive(), 0x0);
|
||||
assert_eq!(enc.rrr(), 0x0);
|
||||
assert_eq!(enc.rex_w(), 0x1);
|
||||
}
|
||||
|
||||
/// Tests setting and unsetting a bit using EncodingBits::write.
|
||||
#[test]
|
||||
fn encodingbits_flip() {
|
||||
let mut bits = EncodingBits::from(0);
|
||||
let range = 2..=2;
|
||||
|
||||
bits.write(range.clone(), 1);
|
||||
assert_eq!(bits.bits(), 0b100);
|
||||
|
||||
bits.write(range, 0);
|
||||
assert_eq!(bits.bits(), 0b000);
|
||||
}
|
||||
|
||||
/// Tests a round-trip of EncodingBits from/to a u16 (hardcoded endianness).
|
||||
#[test]
|
||||
fn encodingbits_roundtrip() {
|
||||
let bits: u16 = 0x1234;
|
||||
assert_eq!(EncodingBits::from(bits).bits(), bits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// I purposely want to divide the bits using the ranges defined above.
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
fn encodingbits_construction() {
|
||||
assert_eq!(
|
||||
EncodingBits::new(&[0x66, 0x40], 5, 1).bits(),
|
||||
0b1_101_0001_01000000 // 1 = rex_w, 101 = rrr, 0001 = prefix, 01000000 = opcode
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn encodingbits_panics_at_write_to_invalid_range() {
|
||||
EncodingBits::from(0).write(1..=0, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn encodingbits_panics_at_read_to_invalid_range() {
|
||||
EncodingBits::from(0).read(1..=0);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
//! Shared x86-specific definitions.
|
||||
|
||||
mod encoding_bits;
|
||||
pub use encoding_bits::*;
|
||||
@@ -22,7 +22,6 @@
|
||||
pub mod condcodes;
|
||||
pub mod constant_hash;
|
||||
pub mod constants;
|
||||
pub mod isa;
|
||||
|
||||
/// Version number of this crate.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
//! `TargetIsa::legalize_signature()` method.
|
||||
|
||||
use crate::ir::{AbiParam, ArgumentExtension, ArgumentLoc, Type};
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::Ordering;
|
||||
|
||||
/// Legalization action to perform on a single argument or return value when converting a
|
||||
@@ -18,10 +16,6 @@ pub enum ArgAction {
|
||||
/// Assign the argument to the given location.
|
||||
Assign(ArgumentLoc),
|
||||
|
||||
/// Assign the argument to the given location and change the type to the specified type.
|
||||
/// This is used by [`ArgumentPurpose::StructArgument`].
|
||||
AssignAndChangeType(ArgumentLoc, Type),
|
||||
|
||||
/// Convert the argument, then call again.
|
||||
///
|
||||
/// This action can split an integer type into two smaller integer arguments, or it can split a
|
||||
@@ -63,34 +57,6 @@ pub enum ValueConversion {
|
||||
Pointer(Type),
|
||||
}
|
||||
|
||||
impl ValueConversion {
|
||||
/// Apply this conversion to a type, return the converted type.
|
||||
pub fn apply(self, ty: Type) -> Type {
|
||||
match self {
|
||||
Self::IntSplit => ty.half_width().expect("Integer type too small to split"),
|
||||
Self::VectorSplit => ty.half_vector().expect("Not a vector"),
|
||||
Self::IntBits => Type::int(ty.bits()).expect("Bad integer size"),
|
||||
Self::Sext(nty) | Self::Uext(nty) | Self::Pointer(nty) => nty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a split conversion that results in two arguments?
|
||||
pub fn is_split(self) -> bool {
|
||||
match self {
|
||||
Self::IntSplit | Self::VectorSplit => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a conversion to pointer?
|
||||
pub fn is_pointer(self) -> bool {
|
||||
match self {
|
||||
Self::Pointer(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common trait for assigning arguments to registers or stack locations.
|
||||
///
|
||||
/// This will be implemented by individual ISAs.
|
||||
@@ -99,62 +65,6 @@ pub trait ArgAssigner {
|
||||
fn assign(&mut self, arg: &AbiParam) -> ArgAction;
|
||||
}
|
||||
|
||||
/// Legalize the arguments in `args` using the given argument assigner.
|
||||
///
|
||||
/// This function can be used for both arguments and return values.
|
||||
pub fn legalize_args<AA: ArgAssigner>(args: &[AbiParam], aa: &mut AA) -> Option<Vec<AbiParam>> {
|
||||
let mut args = Cow::Borrowed(args);
|
||||
|
||||
// Iterate over the arguments.
|
||||
// We may need to mutate the vector in place, so don't use a normal iterator, and clone the
|
||||
// argument to avoid holding a reference.
|
||||
let mut argno = 0;
|
||||
while let Some(arg) = args.get(argno).cloned() {
|
||||
// Leave the pre-assigned arguments alone.
|
||||
// We'll assume that they don't interfere with our assignments.
|
||||
if arg.location.is_assigned() {
|
||||
argno += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
match aa.assign(&arg) {
|
||||
// Assign argument to a location and move on to the next one.
|
||||
ArgAction::Assign(loc) => {
|
||||
args.to_mut()[argno].location = loc;
|
||||
argno += 1;
|
||||
}
|
||||
// Assign argument to a location, change type to the requested one and move on to the
|
||||
// next one.
|
||||
ArgAction::AssignAndChangeType(loc, ty) => {
|
||||
let arg = &mut args.to_mut()[argno];
|
||||
arg.location = loc;
|
||||
arg.value_type = ty;
|
||||
argno += 1;
|
||||
}
|
||||
// Split this argument into two smaller ones. Then revisit both.
|
||||
ArgAction::Convert(conv) => {
|
||||
debug_assert!(
|
||||
!arg.legalized_to_pointer,
|
||||
"No more conversions allowed after conversion to pointer"
|
||||
);
|
||||
let value_type = conv.apply(arg.value_type);
|
||||
args.to_mut()[argno].value_type = value_type;
|
||||
if conv.is_pointer() {
|
||||
args.to_mut()[argno].legalized_to_pointer = true;
|
||||
} else if conv.is_split() {
|
||||
let new_arg = AbiParam { value_type, ..arg };
|
||||
args.to_mut().insert(argno + 1, new_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match args {
|
||||
Cow::Borrowed(_) => None,
|
||||
Cow::Owned(a) => Some(a),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the right action to take when passing a `have` value type to a call signature where
|
||||
/// the next argument is `arg` which has a different value type.
|
||||
///
|
||||
|
||||
@@ -58,8 +58,6 @@ pub enum Reloc {
|
||||
/// value is sign-extended, multiplied by 4, and added to the PC of
|
||||
/// the call instruction to form the destination address.
|
||||
Arm64Call,
|
||||
/// RISC-V call target
|
||||
RiscvCall,
|
||||
/// s390x PC-relative 4-byte offset
|
||||
S390xPCRel32Dbl,
|
||||
|
||||
@@ -93,7 +91,7 @@ impl fmt::Display for Reloc {
|
||||
Self::X86CallPCRel4 => write!(f, "CallPCRel4"),
|
||||
Self::X86CallPLTRel4 => write!(f, "CallPLTRel4"),
|
||||
Self::X86GOTPCRel4 => write!(f, "GOTPCRel4"),
|
||||
Self::Arm32Call | Self::Arm64Call | Self::RiscvCall => write!(f, "Call"),
|
||||
Self::Arm32Call | Self::Arm64Call => write!(f, "Call"),
|
||||
|
||||
Self::ElfX86_64TlsGd => write!(f, "ElfX86_64TlsGd"),
|
||||
Self::MachOX86_64Tlv => write!(f, "MachOX86_64Tlv"),
|
||||
|
||||
@@ -3565,45 +3565,6 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
|
||||
panic!("ALU+imm and ALU+carry ops should not appear here!");
|
||||
}
|
||||
|
||||
#[cfg(feature = "x86")]
|
||||
Opcode::X86Udivmodx
|
||||
| Opcode::X86Sdivmodx
|
||||
| Opcode::X86Umulx
|
||||
| Opcode::X86Smulx
|
||||
| Opcode::X86Cvtt2si
|
||||
| Opcode::X86Fmin
|
||||
| Opcode::X86Fmax
|
||||
| Opcode::X86Push
|
||||
| Opcode::X86Pop
|
||||
| Opcode::X86Bsr
|
||||
| Opcode::X86Bsf
|
||||
| Opcode::X86Pblendw
|
||||
| Opcode::X86Pshufd
|
||||
| Opcode::X86Pshufb
|
||||
| Opcode::X86Pextr
|
||||
| Opcode::X86Pinsr
|
||||
| Opcode::X86Insertps
|
||||
| Opcode::X86Movsd
|
||||
| Opcode::X86Movlhps
|
||||
| Opcode::X86Palignr
|
||||
| Opcode::X86Psll
|
||||
| Opcode::X86Psrl
|
||||
| Opcode::X86Psra
|
||||
| Opcode::X86Ptest
|
||||
| Opcode::X86Pmaxs
|
||||
| Opcode::X86Pmaxu
|
||||
| Opcode::X86Pmins
|
||||
| Opcode::X86Pminu
|
||||
| Opcode::X86Pmullq
|
||||
| Opcode::X86Pmuludq
|
||||
| Opcode::X86Punpckh
|
||||
| Opcode::X86Punpckl
|
||||
| Opcode::X86Vcvtudq2ps
|
||||
| Opcode::X86ElfTlsGetAddr
|
||||
| Opcode::X86MachoTlsGetAddr => {
|
||||
panic!("x86-specific opcode in supposedly arch-neutral IR!");
|
||||
}
|
||||
|
||||
Opcode::DummySargT => unreachable!(),
|
||||
|
||||
Opcode::Iabs => {
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
//! This module contains types and functions for working with the encoding tables generated by
|
||||
//! `cranelift-codegen/meta/src/gen_encodings.rs`.
|
||||
|
||||
use crate::constant_hash::{probe, Table};
|
||||
use crate::constant_hash::Table;
|
||||
use crate::ir::{Function, InstructionData, Opcode, Type};
|
||||
use crate::isa::{Encoding, Legalize};
|
||||
use crate::settings::PredicateView;
|
||||
use core::ops::Range;
|
||||
|
||||
/// A recipe predicate.
|
||||
///
|
||||
@@ -49,14 +48,6 @@ pub struct Level1Entry<OffT: Into<u32> + Copy> {
|
||||
pub offset: OffT,
|
||||
}
|
||||
|
||||
impl<OffT: Into<u32> + Copy> Level1Entry<OffT> {
|
||||
/// Get the level 2 table range indicated by this entry.
|
||||
fn range(&self) -> Range<usize> {
|
||||
let b = self.offset.into() as usize;
|
||||
b..b + (1 << self.log2len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<OffT: Into<u32> + Copy> Table<Type> for [Level1Entry<OffT>] {
|
||||
fn len(&self) -> usize {
|
||||
self.len()
|
||||
@@ -97,68 +88,6 @@ impl<OffT: Into<u32> + Copy> Table<Opcode> for [Level2Entry<OffT>] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Two-level hash table lookup and iterator construction.
|
||||
///
|
||||
/// Given the controlling type variable and instruction opcode, find the corresponding encoding
|
||||
/// list.
|
||||
///
|
||||
/// Returns an iterator that produces legal encodings for `inst`.
|
||||
pub fn lookup_enclist<'a, OffT1, OffT2>(
|
||||
ctrl_typevar: Type,
|
||||
inst: &'a InstructionData,
|
||||
func: &'a Function,
|
||||
level1_table: &'static [Level1Entry<OffT1>],
|
||||
level2_table: &'static [Level2Entry<OffT2>],
|
||||
enclist: &'static [EncListEntry],
|
||||
legalize_actions: &'static [Legalize],
|
||||
recipe_preds: &'static [RecipePredicate],
|
||||
inst_preds: &'static [InstPredicate],
|
||||
isa_preds: PredicateView<'a>,
|
||||
) -> Encodings<'a>
|
||||
where
|
||||
OffT1: Into<u32> + Copy,
|
||||
OffT2: Into<u32> + Copy,
|
||||
{
|
||||
let (offset, legalize) = match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) {
|
||||
Err(l1idx) => {
|
||||
// No level 1 entry found for the type.
|
||||
// We have a sentinel entry with the default legalization code.
|
||||
(!0, level1_table[l1idx].legalize)
|
||||
}
|
||||
Ok(l1idx) => {
|
||||
// We have a valid level 1 entry for this type.
|
||||
let l1ent = &level1_table[l1idx];
|
||||
let offset = match level2_table.get(l1ent.range()) {
|
||||
Some(l2tab) => {
|
||||
let opcode = inst.opcode();
|
||||
match probe(l2tab, opcode, opcode as usize) {
|
||||
Ok(l2idx) => l2tab[l2idx].offset.into() as usize,
|
||||
Err(_) => !0,
|
||||
}
|
||||
}
|
||||
// The l1ent range is invalid. This means that we just have a customized
|
||||
// legalization code for this type. The level 2 table is empty.
|
||||
None => !0,
|
||||
};
|
||||
(offset, l1ent.legalize)
|
||||
}
|
||||
};
|
||||
|
||||
// Now we have an offset into `enclist` that is `!0` when no encoding list could be found.
|
||||
// The default legalization code is always valid.
|
||||
Encodings::new(
|
||||
offset,
|
||||
legalize,
|
||||
inst,
|
||||
func,
|
||||
enclist,
|
||||
legalize_actions,
|
||||
recipe_preds,
|
||||
inst_preds,
|
||||
isa_preds,
|
||||
)
|
||||
}
|
||||
|
||||
/// Encoding list entry.
|
||||
///
|
||||
/// Encoding lists are represented as sequences of u16 words.
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//! Legacy ("old-style") backends that will be removed in the future.
|
||||
|
||||
// N.B.: the old x86-64 backend (`x86`) and the new one (`x64`) are both
|
||||
// included whenever building with x86 support. The new backend is the default,
|
||||
// but the old can be requested with `BackendVariant::Legacy`. However, if this
|
||||
// crate is built with the `old-x86-backend` feature, then the old backend is
|
||||
// default instead.
|
||||
#[cfg(feature = "x86")]
|
||||
pub(crate) mod x86;
|
||||
|
||||
#[cfg(feature = "riscv")]
|
||||
pub(crate) mod riscv;
|
||||
@@ -1,149 +0,0 @@
|
||||
//! RISC-V ABI implementation.
|
||||
//!
|
||||
//! This module implements the RISC-V calling convention through the primary `legalize_signature()`
|
||||
//! entry point.
|
||||
//!
|
||||
//! This doesn't support the soft-float ABI at the moment.
|
||||
|
||||
use super::registers::{FPR, GPR};
|
||||
use super::settings;
|
||||
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
|
||||
use crate::ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPurpose, Type};
|
||||
use crate::isa::RegClass;
|
||||
use crate::regalloc::RegisterSet;
|
||||
use alloc::borrow::Cow;
|
||||
use core::i32;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
struct Args {
|
||||
pointer_bits: u8,
|
||||
pointer_bytes: u8,
|
||||
pointer_type: Type,
|
||||
regs: u32,
|
||||
reg_limit: u32,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn new(bits: u8, enable_e: bool) -> Self {
|
||||
Self {
|
||||
pointer_bits: bits,
|
||||
pointer_bytes: bits / 8,
|
||||
pointer_type: Type::int(u16::from(bits)).unwrap(),
|
||||
regs: 0,
|
||||
reg_limit: if enable_e { 6 } else { 8 },
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgAssigner for Args {
|
||||
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
|
||||
fn align(value: u32, to: u32) -> u32 {
|
||||
(value + to - 1) & !(to - 1)
|
||||
}
|
||||
|
||||
let ty = arg.value_type;
|
||||
|
||||
// Check for a legal type.
|
||||
// RISC-V doesn't have SIMD at all, so break all vectors down.
|
||||
if ty.is_vector() {
|
||||
return ValueConversion::VectorSplit.into();
|
||||
}
|
||||
|
||||
// Large integers and booleans are broken down to fit in a register.
|
||||
if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) {
|
||||
// Align registers and stack to a multiple of two pointers.
|
||||
self.regs = align(self.regs, 2);
|
||||
self.offset = align(self.offset, 2 * u32::from(self.pointer_bytes));
|
||||
return ValueConversion::IntSplit.into();
|
||||
}
|
||||
|
||||
// Small integers are extended to the size of a pointer register.
|
||||
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
|
||||
match arg.extension {
|
||||
ArgumentExtension::None => {}
|
||||
ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(),
|
||||
ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(),
|
||||
}
|
||||
}
|
||||
|
||||
if self.regs < self.reg_limit {
|
||||
// Assign to a register.
|
||||
let reg = if ty.is_float() {
|
||||
FPR.unit(10 + self.regs as usize)
|
||||
} else {
|
||||
GPR.unit(10 + self.regs as usize)
|
||||
};
|
||||
self.regs += 1;
|
||||
ArgumentLoc::Reg(reg).into()
|
||||
} else {
|
||||
// Assign a stack location.
|
||||
let loc = ArgumentLoc::Stack(self.offset as i32);
|
||||
self.offset += u32::from(self.pointer_bytes);
|
||||
debug_assert!(self.offset <= i32::MAX as u32);
|
||||
loc.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Legalize `sig` for RISC-V.
|
||||
pub fn legalize_signature(
|
||||
sig: &mut Cow<ir::Signature>,
|
||||
triple: &Triple,
|
||||
isa_flags: &settings::Flags,
|
||||
current: bool,
|
||||
) {
|
||||
let bits = triple.pointer_width().unwrap().bits();
|
||||
|
||||
let mut args = Args::new(bits, isa_flags.enable_e());
|
||||
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
|
||||
sig.to_mut().params = new_params;
|
||||
}
|
||||
|
||||
let mut rets = Args::new(bits, isa_flags.enable_e());
|
||||
if let Some(new_returns) = legalize_args(&sig.returns, &mut rets) {
|
||||
sig.to_mut().returns = new_returns;
|
||||
}
|
||||
|
||||
if current {
|
||||
let ptr = Type::int(u16::from(bits)).unwrap();
|
||||
|
||||
// Add the link register as an argument and return value.
|
||||
//
|
||||
// The `jalr` instruction implementing a return can technically accept the return address
|
||||
// in any register, but a micro-architecture with a return address predictor will only
|
||||
// recognize it as a return if the address is in `x1`.
|
||||
let link = AbiParam::special_reg(ptr, ArgumentPurpose::Link, GPR.unit(1));
|
||||
sig.to_mut().params.push(link);
|
||||
sig.to_mut().returns.push(link);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get register class for a type appearing in a legalized signature.
|
||||
pub fn regclass_for_abi_type(ty: Type) -> RegClass {
|
||||
if ty.is_float() {
|
||||
FPR
|
||||
} else {
|
||||
GPR
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocatable_registers(_func: &ir::Function, isa_flags: &settings::Flags) -> RegisterSet {
|
||||
let mut regs = RegisterSet::new();
|
||||
regs.take(GPR, GPR.unit(0)); // Hard-wired 0.
|
||||
// %x1 is the link register which is available for allocation.
|
||||
regs.take(GPR, GPR.unit(2)); // Stack pointer.
|
||||
regs.take(GPR, GPR.unit(3)); // Global pointer.
|
||||
regs.take(GPR, GPR.unit(4)); // Thread pointer.
|
||||
// TODO: %x8 is the frame pointer. Reserve it?
|
||||
|
||||
// Remove %x16 and up for RV32E.
|
||||
if isa_flags.enable_e() {
|
||||
for u in 16..32 {
|
||||
regs.take(GPR, GPR.unit(u));
|
||||
}
|
||||
}
|
||||
|
||||
regs
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
//! Emitting binary RISC-V machine code.
|
||||
|
||||
use crate::binemit::{bad_encoding, CodeSink, Reloc};
|
||||
use crate::ir::{Function, Inst, InstructionData};
|
||||
use crate::isa::{RegUnit, StackBaseMask, StackRef, TargetIsa};
|
||||
use crate::predicates::is_signed_int;
|
||||
use crate::regalloc::RegDiversions;
|
||||
use core::u32;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs"));
|
||||
|
||||
/// R-type instructions.
|
||||
///
|
||||
/// 31 24 19 14 11 6
|
||||
/// funct7 rs2 rs1 funct3 rd opcode
|
||||
/// 25 20 15 12 7 0
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`.
|
||||
fn put_r<CS: CodeSink + ?Sized>(bits: u16, rs1: RegUnit, rs2: RegUnit, rd: RegUnit, sink: &mut CS) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let funct3 = (bits >> 5) & 0x7;
|
||||
let funct7 = (bits >> 8) & 0x7f;
|
||||
let rs1 = u32::from(rs1) & 0x1f;
|
||||
let rs2 = u32::from(rs2) & 0x1f;
|
||||
let rd = u32::from(rd) & 0x1f;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= rd << 7;
|
||||
i |= funct3 << 12;
|
||||
i |= rs1 << 15;
|
||||
i |= rs2 << 20;
|
||||
i |= funct7 << 25;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
|
||||
/// R-type instructions with a shift amount instead of rs2.
|
||||
///
|
||||
/// 31 25 19 14 11 6
|
||||
/// funct7 shamt rs1 funct3 rd opcode
|
||||
/// 25 20 15 12 7 0
|
||||
///
|
||||
/// Both funct7 and shamt contribute to bit 25. In RV64, shamt uses it for shifts > 31.
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`.
|
||||
fn put_rshamt<CS: CodeSink + ?Sized>(
|
||||
bits: u16,
|
||||
rs1: RegUnit,
|
||||
shamt: i64,
|
||||
rd: RegUnit,
|
||||
sink: &mut CS,
|
||||
) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let funct3 = (bits >> 5) & 0x7;
|
||||
let funct7 = (bits >> 8) & 0x7f;
|
||||
let rs1 = u32::from(rs1) & 0x1f;
|
||||
let shamt = shamt as u32 & 0x3f;
|
||||
let rd = u32::from(rd) & 0x1f;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= rd << 7;
|
||||
i |= funct3 << 12;
|
||||
i |= rs1 << 15;
|
||||
i |= shamt << 20;
|
||||
i |= funct7 << 25;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
|
||||
/// I-type instructions.
|
||||
///
|
||||
/// 31 19 14 11 6
|
||||
/// imm rs1 funct3 rd opcode
|
||||
/// 20 15 12 7 0
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
|
||||
fn put_i<CS: CodeSink + ?Sized>(bits: u16, rs1: RegUnit, imm: i64, rd: RegUnit, sink: &mut CS) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let funct3 = (bits >> 5) & 0x7;
|
||||
let rs1 = u32::from(rs1) & 0x1f;
|
||||
let rd = u32::from(rd) & 0x1f;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= rd << 7;
|
||||
i |= funct3 << 12;
|
||||
i |= rs1 << 15;
|
||||
i |= (imm << 20) as u32;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
|
||||
/// U-type instructions.
|
||||
///
|
||||
/// 31 11 6
|
||||
/// imm rd opcode
|
||||
/// 12 7 0
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
|
||||
fn put_u<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let rd = u32::from(rd) & 0x1f;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= rd << 7;
|
||||
i |= imm as u32 & 0xfffff000;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
|
||||
/// SB-type branch instructions.
|
||||
///
|
||||
/// 31 24 19 14 11 6
|
||||
/// imm rs2 rs1 funct3 imm opcode
|
||||
/// 25 20 15 12 7 0
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
|
||||
fn put_sb<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rs1: RegUnit, rs2: RegUnit, sink: &mut CS) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let funct3 = (bits >> 5) & 0x7;
|
||||
let rs1 = u32::from(rs1) & 0x1f;
|
||||
let rs2 = u32::from(rs2) & 0x1f;
|
||||
|
||||
debug_assert!(is_signed_int(imm, 13, 1), "SB out of range {:#x}", imm);
|
||||
let imm = imm as u32;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= funct3 << 12;
|
||||
i |= rs1 << 15;
|
||||
i |= rs2 << 20;
|
||||
|
||||
// The displacement is completely hashed up.
|
||||
i |= ((imm >> 11) & 0x1) << 7;
|
||||
i |= ((imm >> 1) & 0xf) << 8;
|
||||
i |= ((imm >> 5) & 0x3f) << 25;
|
||||
i |= ((imm >> 12) & 0x1) << 31;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
|
||||
/// UJ-type jump instructions.
|
||||
///
|
||||
/// 31 11 6
|
||||
/// imm rd opcode
|
||||
/// 12 7 0
|
||||
///
|
||||
/// Encoding bits: `opcode[6:2]`
|
||||
fn put_uj<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) {
|
||||
let bits = u32::from(bits);
|
||||
let opcode5 = bits & 0x1f;
|
||||
let rd = u32::from(rd) & 0x1f;
|
||||
|
||||
debug_assert!(is_signed_int(imm, 21, 1), "UJ out of range {:#x}", imm);
|
||||
let imm = imm as u32;
|
||||
|
||||
// 0-6: opcode
|
||||
let mut i = 0x3;
|
||||
i |= opcode5 << 2;
|
||||
i |= rd << 7;
|
||||
|
||||
// The displacement is completely hashed up.
|
||||
i |= imm & 0xff000;
|
||||
i |= ((imm >> 11) & 0x1) << 20;
|
||||
i |= ((imm >> 1) & 0x3ff) << 21;
|
||||
i |= ((imm >> 20) & 0x1) << 31;
|
||||
|
||||
sink.put4(i);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
//! Encoding tables for RISC-V.
|
||||
|
||||
use super::registers::*;
|
||||
use crate::ir;
|
||||
use crate::isa;
|
||||
use crate::isa::constraints::*;
|
||||
use crate::isa::enc_tables::*;
|
||||
use crate::isa::encoding::{base_size, RecipeSizing};
|
||||
use crate::predicates;
|
||||
|
||||
// Include the generated encoding tables:
|
||||
// - `LEVEL1_RV32`
|
||||
// - `LEVEL1_RV64`
|
||||
// - `LEVEL2`
|
||||
// - `ENCLIST`
|
||||
// - `INFO`
|
||||
include!(concat!(env!("OUT_DIR"), "/encoding-riscv.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/legalize-riscv.rs"));
|
||||
@@ -1,304 +0,0 @@
|
||||
//! RISC-V Instruction Set Architecture.
|
||||
|
||||
mod abi;
|
||||
mod binemit;
|
||||
mod enc_tables;
|
||||
mod registers;
|
||||
pub mod settings;
|
||||
|
||||
use super::super::settings as shared_settings;
|
||||
#[cfg(feature = "testing_hooks")]
|
||||
use crate::binemit::CodeSink;
|
||||
use crate::binemit::{emit_function, MemoryCodeSink};
|
||||
use crate::ir;
|
||||
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
|
||||
use crate::isa::Builder as IsaBuilder;
|
||||
use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa};
|
||||
use crate::regalloc;
|
||||
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
||||
use core::any::Any;
|
||||
use core::fmt;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use target_lexicon::{PointerWidth, Triple};
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Isa {
|
||||
triple: Triple,
|
||||
shared_flags: shared_settings::Flags,
|
||||
isa_flags: settings::Flags,
|
||||
cpumode: &'static [shared_enc_tables::Level1Entry<u16>],
|
||||
}
|
||||
|
||||
/// Get an ISA builder for creating RISC-V targets.
|
||||
pub fn isa_builder(triple: Triple) -> IsaBuilder {
|
||||
IsaBuilder {
|
||||
triple,
|
||||
setup: settings::builder(),
|
||||
constructor: isa_constructor,
|
||||
}
|
||||
}
|
||||
|
||||
fn isa_constructor(
|
||||
triple: Triple,
|
||||
shared_flags: shared_settings::Flags,
|
||||
builder: shared_settings::Builder,
|
||||
) -> Box<dyn TargetIsa> {
|
||||
let level1 = match triple.pointer_width().unwrap() {
|
||||
PointerWidth::U16 => panic!("16-bit RISC-V unrecognized"),
|
||||
PointerWidth::U32 => &enc_tables::LEVEL1_RV32[..],
|
||||
PointerWidth::U64 => &enc_tables::LEVEL1_RV64[..],
|
||||
};
|
||||
Box::new(Isa {
|
||||
triple,
|
||||
isa_flags: settings::Flags::new(&shared_flags, builder),
|
||||
shared_flags,
|
||||
cpumode: level1,
|
||||
})
|
||||
}
|
||||
|
||||
impl TargetIsa for Isa {
|
||||
fn name(&self) -> &'static str {
|
||||
"riscv"
|
||||
}
|
||||
|
||||
fn triple(&self) -> &Triple {
|
||||
&self.triple
|
||||
}
|
||||
|
||||
fn flags(&self) -> &shared_settings::Flags {
|
||||
&self.shared_flags
|
||||
}
|
||||
|
||||
fn isa_flags(&self) -> Vec<shared_settings::Value> {
|
||||
self.isa_flags.iter().collect()
|
||||
}
|
||||
|
||||
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
|
||||
self.shared_flags.hash(&mut hasher);
|
||||
self.isa_flags.hash(&mut hasher);
|
||||
}
|
||||
|
||||
fn register_info(&self) -> RegInfo {
|
||||
registers::INFO.clone()
|
||||
}
|
||||
|
||||
fn encoding_info(&self) -> EncInfo {
|
||||
enc_tables::INFO.clone()
|
||||
}
|
||||
|
||||
fn legal_encodings<'a>(
|
||||
&'a self,
|
||||
func: &'a ir::Function,
|
||||
inst: &'a ir::InstructionData,
|
||||
ctrl_typevar: ir::Type,
|
||||
) -> Encodings<'a> {
|
||||
lookup_enclist(
|
||||
ctrl_typevar,
|
||||
inst,
|
||||
func,
|
||||
self.cpumode,
|
||||
&enc_tables::LEVEL2[..],
|
||||
&enc_tables::ENCLISTS[..],
|
||||
&enc_tables::LEGALIZE_ACTIONS[..],
|
||||
&enc_tables::RECIPE_PREDICATES[..],
|
||||
&enc_tables::INST_PREDICATES[..],
|
||||
self.isa_flags.predicate_view(),
|
||||
)
|
||||
}
|
||||
|
||||
fn legalize_signature(&self, sig: &mut Cow<ir::Signature>, current: bool) {
|
||||
abi::legalize_signature(sig, &self.triple, &self.isa_flags, current)
|
||||
}
|
||||
|
||||
fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass {
|
||||
abi::regclass_for_abi_type(ty)
|
||||
}
|
||||
|
||||
fn allocatable_registers(&self, func: &ir::Function) -> regalloc::RegisterSet {
|
||||
abi::allocatable_registers(func, &self.isa_flags)
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing_hooks")]
|
||||
fn emit_inst(
|
||||
&self,
|
||||
func: &ir::Function,
|
||||
inst: ir::Inst,
|
||||
divert: &mut regalloc::RegDiversions,
|
||||
sink: &mut dyn CodeSink,
|
||||
) {
|
||||
binemit::emit_inst(func, inst, divert, sink, self)
|
||||
}
|
||||
|
||||
fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
|
||||
emit_function(func, binemit::emit_inst, sink, self)
|
||||
}
|
||||
|
||||
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unsigned_sub_overflow_condition(&self) -> ir::condcodes::IntCC {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ir::{immediates, types};
|
||||
use crate::ir::{Function, InstructionData, Opcode};
|
||||
use crate::isa;
|
||||
use crate::settings::{self, Configurable};
|
||||
use alloc::string::{String, ToString};
|
||||
use core::str::FromStr;
|
||||
use target_lexicon::triple;
|
||||
|
||||
fn encstr(isa: &dyn isa::TargetIsa, enc: Result<isa::Encoding, isa::Legalize>) -> String {
|
||||
match enc {
|
||||
Ok(e) => isa.encoding_info().display(e).to_string(),
|
||||
Err(_) => "no encoding".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_64bitenc() {
|
||||
let shared_builder = settings::builder();
|
||||
let shared_flags = settings::Flags::new(shared_builder);
|
||||
let isa = isa::lookup(triple!("riscv64"))
|
||||
.unwrap()
|
||||
.finish(shared_flags);
|
||||
|
||||
let mut func = Function::new();
|
||||
let block = func.dfg.make_block();
|
||||
let arg64 = func.dfg.append_block_param(block, types::I64);
|
||||
let arg32 = func.dfg.append_block_param(block, types::I32);
|
||||
|
||||
// Try to encode iadd_imm.i64 v1, -10.
|
||||
let inst64 = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg64,
|
||||
imm: immediates::Imm64::new(-10),
|
||||
};
|
||||
|
||||
// ADDI is I/0b00100
|
||||
assert_eq!(
|
||||
encstr(&*isa, isa.encode(&func, &inst64, types::I64)),
|
||||
"Ii#04"
|
||||
);
|
||||
|
||||
// Try to encode iadd_imm.i64 v1, -10000.
|
||||
let inst64_large = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg64,
|
||||
imm: immediates::Imm64::new(-10000),
|
||||
};
|
||||
|
||||
// Immediate is out of range for ADDI.
|
||||
assert!(isa.encode(&func, &inst64_large, types::I64).is_err());
|
||||
|
||||
// Create an iadd_imm.i32 which is encodable in RV64.
|
||||
let inst32 = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg32,
|
||||
imm: immediates::Imm64::new(10),
|
||||
};
|
||||
|
||||
// ADDIW is I/0b00110
|
||||
assert_eq!(
|
||||
encstr(&*isa, isa.encode(&func, &inst32, types::I32)),
|
||||
"Ii#06"
|
||||
);
|
||||
}
|
||||
|
||||
// Same as above, but for RV32.
|
||||
#[test]
|
||||
fn test_32bitenc() {
|
||||
let shared_builder = settings::builder();
|
||||
let shared_flags = settings::Flags::new(shared_builder);
|
||||
let isa = isa::lookup(triple!("riscv32"))
|
||||
.unwrap()
|
||||
.finish(shared_flags);
|
||||
|
||||
let mut func = Function::new();
|
||||
let block = func.dfg.make_block();
|
||||
let arg64 = func.dfg.append_block_param(block, types::I64);
|
||||
let arg32 = func.dfg.append_block_param(block, types::I32);
|
||||
|
||||
// Try to encode iadd_imm.i64 v1, -10.
|
||||
let inst64 = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg64,
|
||||
imm: immediates::Imm64::new(-10),
|
||||
};
|
||||
|
||||
// In 32-bit mode, an i64 bit add should be narrowed.
|
||||
assert!(isa.encode(&func, &inst64, types::I64).is_err());
|
||||
|
||||
// Try to encode iadd_imm.i64 v1, -10000.
|
||||
let inst64_large = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg64,
|
||||
imm: immediates::Imm64::new(-10000),
|
||||
};
|
||||
|
||||
// In 32-bit mode, an i64 bit add should be narrowed.
|
||||
assert!(isa.encode(&func, &inst64_large, types::I64).is_err());
|
||||
|
||||
// Create an iadd_imm.i32 which is encodable in RV32.
|
||||
let inst32 = InstructionData::BinaryImm64 {
|
||||
opcode: Opcode::IaddImm,
|
||||
arg: arg32,
|
||||
imm: immediates::Imm64::new(10),
|
||||
};
|
||||
|
||||
// ADDI is I/0b00100
|
||||
assert_eq!(
|
||||
encstr(&*isa, isa.encode(&func, &inst32, types::I32)),
|
||||
"Ii#04"
|
||||
);
|
||||
|
||||
// Create an imul.i32 which is encodable in RV32, but only when use_m is true.
|
||||
let mul32 = InstructionData::Binary {
|
||||
opcode: Opcode::Imul,
|
||||
args: [arg32, arg32],
|
||||
};
|
||||
|
||||
assert!(isa.encode(&func, &mul32, types::I32).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rv32m() {
|
||||
let shared_builder = settings::builder();
|
||||
let shared_flags = settings::Flags::new(shared_builder);
|
||||
|
||||
// Set the supports_m stting which in turn enables the use_m predicate that unlocks
|
||||
// encodings for imul.
|
||||
let mut isa_builder = isa::lookup(triple!("riscv32")).unwrap();
|
||||
isa_builder.enable("supports_m").unwrap();
|
||||
|
||||
let isa = isa_builder.finish(shared_flags);
|
||||
|
||||
let mut func = Function::new();
|
||||
let block = func.dfg.make_block();
|
||||
let arg32 = func.dfg.append_block_param(block, types::I32);
|
||||
|
||||
// Create an imul.i32 which is encodable in RV32M.
|
||||
let mul32 = InstructionData::Binary {
|
||||
opcode: Opcode::Imul,
|
||||
args: [arg32, arg32],
|
||||
};
|
||||
assert_eq!(
|
||||
encstr(&*isa, isa.encode(&func, &mul32, types::I32)),
|
||||
"R#10c"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Isa {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}\n{}", self.shared_flags, self.isa_flags)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
//! RISC-V register descriptions.
|
||||
|
||||
use crate::isa::registers::{RegBank, RegClass, RegClassData, RegInfo, RegUnit};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FPR, GPR, INFO};
|
||||
use crate::isa::RegUnit;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
#[test]
|
||||
fn unit_encodings() {
|
||||
assert_eq!(INFO.parse_regunit("x0"), Some(0));
|
||||
assert_eq!(INFO.parse_regunit("x31"), Some(31));
|
||||
assert_eq!(INFO.parse_regunit("f0"), Some(32));
|
||||
assert_eq!(INFO.parse_regunit("f31"), Some(63));
|
||||
|
||||
assert_eq!(INFO.parse_regunit("x32"), None);
|
||||
assert_eq!(INFO.parse_regunit("f32"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_names() {
|
||||
fn uname(ru: RegUnit) -> String {
|
||||
INFO.display_regunit(ru).to_string()
|
||||
}
|
||||
|
||||
assert_eq!(uname(0), "%x0");
|
||||
assert_eq!(uname(1), "%x1");
|
||||
assert_eq!(uname(31), "%x31");
|
||||
assert_eq!(uname(32), "%f0");
|
||||
assert_eq!(uname(33), "%f1");
|
||||
assert_eq!(uname(63), "%f31");
|
||||
assert_eq!(uname(64), "%INVALID64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classes() {
|
||||
assert!(GPR.contains(GPR.unit(0)));
|
||||
assert!(GPR.contains(GPR.unit(31)));
|
||||
assert!(!FPR.contains(GPR.unit(0)));
|
||||
assert!(!FPR.contains(GPR.unit(31)));
|
||||
assert!(!GPR.contains(FPR.unit(0)));
|
||||
assert!(!GPR.contains(FPR.unit(31)));
|
||||
assert!(FPR.contains(FPR.unit(0)));
|
||||
assert!(FPR.contains(FPR.unit(31)));
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
//! RISC-V Settings.
|
||||
|
||||
use crate::settings::{self, detail, Builder, Value};
|
||||
use core::fmt;
|
||||
|
||||
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs`. This file contains a
|
||||
// public `Flags` struct with an impl for all of the settings defined in
|
||||
// `cranelift-codegen/meta/src/isa/riscv/mod.rs`.
|
||||
include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{builder, Flags};
|
||||
use crate::settings::{self, Configurable};
|
||||
use alloc::string::ToString;
|
||||
|
||||
#[test]
|
||||
fn display_default() {
|
||||
let shared = settings::Flags::new(settings::builder());
|
||||
let b = builder();
|
||||
let f = Flags::new(&shared, b);
|
||||
assert_eq!(
|
||||
f.to_string(),
|
||||
"[riscv]\n\
|
||||
supports_m = false\n\
|
||||
supports_a = false\n\
|
||||
supports_f = false\n\
|
||||
supports_d = false\n\
|
||||
enable_m = true\n\
|
||||
enable_e = false\n"
|
||||
);
|
||||
// Predicates are not part of the Display output.
|
||||
assert_eq!(f.full_float(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicates() {
|
||||
let mut sb = settings::builder();
|
||||
sb.set("enable_simd", "true").unwrap();
|
||||
let shared = settings::Flags::new(sb);
|
||||
let mut b = builder();
|
||||
b.enable("supports_f").unwrap();
|
||||
b.enable("supports_d").unwrap();
|
||||
let f = Flags::new(&shared, b);
|
||||
assert_eq!(f.full_float(), true);
|
||||
|
||||
let mut sb = settings::builder();
|
||||
sb.set("enable_simd", "false").unwrap();
|
||||
let shared = settings::Flags::new(sb);
|
||||
let mut b = builder();
|
||||
b.enable("supports_f").unwrap();
|
||||
b.enable("supports_d").unwrap();
|
||||
let f = Flags::new(&shared, b);
|
||||
assert_eq!(f.full_float(), false);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,578 +0,0 @@
|
||||
//! Emitting binary x86 machine code.
|
||||
|
||||
use super::enc_tables::{needs_offset, needs_sib_byte};
|
||||
use super::registers::RU;
|
||||
use crate::binemit::{bad_encoding, CodeSink, Reloc};
|
||||
use crate::ir::condcodes::{CondCode, FloatCC, IntCC};
|
||||
use crate::ir::{
|
||||
Block, Constant, ExternalName, Function, Inst, InstructionData, JumpTable, LibCall, Opcode,
|
||||
TrapCode,
|
||||
};
|
||||
use crate::isa::{RegUnit, StackBase, StackBaseMask, StackRef, TargetIsa};
|
||||
use crate::regalloc::RegDiversions;
|
||||
use cranelift_codegen_shared::isa::x86::EncodingBits;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/binemit-x86.rs"));
|
||||
|
||||
// Convert a stack base to the corresponding register.
|
||||
fn stk_base(base: StackBase) -> RegUnit {
|
||||
let ru = match base {
|
||||
StackBase::SP => RU::rsp,
|
||||
StackBase::FP => RU::rbp,
|
||||
StackBase::Zone => unimplemented!(),
|
||||
};
|
||||
ru as RegUnit
|
||||
}
|
||||
|
||||
// Mandatory prefix bytes for Mp* opcodes.
|
||||
const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2];
|
||||
|
||||
// Second byte for three-byte opcodes for mm=0b10 and mm=0b11.
|
||||
const OP3_BYTE2: [u8; 2] = [0x38, 0x3a];
|
||||
|
||||
// A REX prefix with no bits set: 0b0100WRXB.
|
||||
const BASE_REX: u8 = 0b0100_0000;
|
||||
|
||||
// Create a single-register REX prefix, setting the B bit to bit 3 of the register.
|
||||
// This is used for instructions that encode a register in the low 3 bits of the opcode and for
|
||||
// instructions that use the ModR/M `reg` field for something else.
|
||||
fn rex1(reg_b: RegUnit) -> u8 {
|
||||
let b = ((reg_b >> 3) & 1) as u8;
|
||||
BASE_REX | b
|
||||
}
|
||||
|
||||
// Create a dual-register REX prefix, setting:
|
||||
//
|
||||
// REX.B = bit 3 of r/m register, or SIB base register when a SIB byte is present.
|
||||
// REX.R = bit 3 of reg register.
|
||||
fn rex2(rm: RegUnit, reg: RegUnit) -> u8 {
|
||||
let b = ((rm >> 3) & 1) as u8;
|
||||
let r = ((reg >> 3) & 1) as u8;
|
||||
BASE_REX | b | (r << 2)
|
||||
}
|
||||
|
||||
// Create a three-register REX prefix, setting:
|
||||
//
|
||||
// REX.B = bit 3 of r/m register, or SIB base register when a SIB byte is present.
|
||||
// REX.R = bit 3 of reg register.
|
||||
// REX.X = bit 3 of SIB index register.
|
||||
fn rex3(rm: RegUnit, reg: RegUnit, index: RegUnit) -> u8 {
|
||||
let b = ((rm >> 3) & 1) as u8;
|
||||
let r = ((reg >> 3) & 1) as u8;
|
||||
let x = ((index >> 3) & 1) as u8;
|
||||
BASE_REX | b | (x << 1) | (r << 2)
|
||||
}
|
||||
|
||||
/// Encode the RXBR' bits of the EVEX P0 byte. For an explanation of these bits, see section 2.6.1
|
||||
/// in the Intel Software Development Manual, volume 2A. These bits can be used by different
|
||||
/// addressing modes (see section 2.6.2), requiring different `vex*` functions than this one.
|
||||
fn evex2(rm: RegUnit, reg: RegUnit) -> u8 {
|
||||
let b = (!(rm >> 3) & 1) as u8;
|
||||
let x = (!(rm >> 4) & 1) as u8;
|
||||
let r = (!(reg >> 3) & 1) as u8;
|
||||
let r_ = (!(reg >> 4) & 1) as u8;
|
||||
0x00 | r_ | (b << 1) | (x << 2) | (r << 3)
|
||||
}
|
||||
|
||||
/// Determines whether a REX prefix should be emitted. A REX byte always has 0100 in bits 7:4; bits
|
||||
/// 3:0 correspond to WRXB. W allows certain instructions to declare a 64-bit operand size; because
|
||||
/// [needs_rex] is only used by [infer_rex] and we prevent [infer_rex] from using [w] in
|
||||
/// [Template::build], we do not need to check again whether [w] forces an inferred REX prefix--it
|
||||
/// always does and should be encoded like `.rex().w()`. The RXB are extension of ModR/M or SIB
|
||||
/// fields; see section 2.2.1.2 in the Intel Software Development Manual.
|
||||
#[inline]
|
||||
fn needs_rex(rex: u8) -> bool {
|
||||
rex != BASE_REX
|
||||
}
|
||||
|
||||
// Emit a REX prefix.
|
||||
//
|
||||
// The R, X, and B bits are computed from registers using the functions above. The W bit is
|
||||
// extracted from `bits`.
|
||||
fn rex_prefix<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(rex & 0xf8, BASE_REX);
|
||||
let w = EncodingBits::from(bits).rex_w();
|
||||
sink.put1(rex | (w << 3));
|
||||
}
|
||||
|
||||
// Emit a single-byte opcode with no REX prefix.
|
||||
fn put_op1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x8f00, 0, "Invalid encoding bits for Op1*");
|
||||
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op1 encoding");
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit a single-byte opcode with REX prefix.
|
||||
fn put_rexop1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for RexOp1*");
|
||||
rex_prefix(bits, rex, sink);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit a single-byte opcode with inferred REX prefix.
|
||||
fn put_dynrexop1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for DynRexOp1*");
|
||||
if needs_rex(rex) {
|
||||
rex_prefix(bits, rex, sink);
|
||||
}
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit two-byte opcode: 0F XX
|
||||
fn put_op2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x8f00, 0x0400, "Invalid encoding bits for Op2*");
|
||||
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op2 encoding");
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit two-byte opcode: 0F XX with REX prefix.
|
||||
fn put_rexop2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for RexOp2*");
|
||||
rex_prefix(bits, rex, sink);
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit two-byte opcode: 0F XX with inferred REX prefix.
|
||||
fn put_dynrexop2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(
|
||||
bits & 0x0f00,
|
||||
0x0400,
|
||||
"Invalid encoding bits for DynRexOp2*"
|
||||
);
|
||||
if needs_rex(rex) {
|
||||
rex_prefix(bits, rex, sink);
|
||||
}
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit single-byte opcode with mandatory prefix.
|
||||
fn put_mp1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x8c00, 0, "Invalid encoding bits for Mp1*");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp1 encoding");
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit single-byte opcode with mandatory prefix and REX.
|
||||
fn put_rexmp1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for RexMp1*");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
rex_prefix(bits, rex, sink);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit two-byte opcode (0F XX) with mandatory prefix.
|
||||
fn put_mp2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x8c00, 0x0400, "Invalid encoding bits for Mp2*");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp2 encoding");
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
// Emit two-byte opcode (0F XX) with mandatory prefix and REX.
|
||||
fn put_rexmp2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0c00, 0x0400, "Invalid encoding bits for RexMp2*");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
rex_prefix(bits, rex, sink);
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit two-byte opcode (0F XX) with mandatory prefix and inferred REX.
|
||||
fn put_dynrexmp2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(
|
||||
bits & 0x0c00,
|
||||
0x0400,
|
||||
"Invalid encoding bits for DynRexMp2*"
|
||||
);
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
if needs_rex(rex) {
|
||||
rex_prefix(bits, rex, sink);
|
||||
}
|
||||
sink.put1(0x0f);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix.
|
||||
fn put_mp3<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x8800, 0x0800, "Invalid encoding bits for Mp3*");
|
||||
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp3 encoding");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
sink.put1(0x0f);
|
||||
sink.put1(OP3_BYTE2[(enc.mm() - 2) as usize]);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix and REX
|
||||
fn put_rexmp3<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(bits & 0x0800, 0x0800, "Invalid encoding bits for RexMp3*");
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
rex_prefix(bits, rex, sink);
|
||||
sink.put1(0x0f);
|
||||
sink.put1(OP3_BYTE2[(enc.mm() - 2) as usize]);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix and an inferred REX prefix.
|
||||
fn put_dynrexmp3<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
debug_assert_eq!(
|
||||
bits & 0x0800,
|
||||
0x0800,
|
||||
"Invalid encoding bits for DynRexMp3*"
|
||||
);
|
||||
let enc = EncodingBits::from(bits);
|
||||
sink.put1(PREFIX[(enc.pp() - 1) as usize]);
|
||||
if needs_rex(rex) {
|
||||
rex_prefix(bits, rex, sink);
|
||||
}
|
||||
sink.put1(0x0f);
|
||||
sink.put1(OP3_BYTE2[(enc.mm() - 2) as usize]);
|
||||
sink.put1(bits as u8);
|
||||
}
|
||||
|
||||
/// Defines the EVEX context for the `L'`, `L`, and `b` bits (bits 6:4 of EVEX P2 byte). Table 2-36 in
|
||||
/// section 2.6.10 (Intel Software Development Manual, volume 2A) describes how these bits can be
|
||||
/// used together for certain classes of instructions; i.e., special care should be taken to ensure
|
||||
/// that instructions use an applicable correct `EvexContext`. Table 2-39 contains cases where
|
||||
/// opcodes can result in an #UD.
|
||||
#[allow(dead_code)]
|
||||
enum EvexContext {
|
||||
RoundingRegToRegFP {
|
||||
rc: EvexRoundingControl,
|
||||
},
|
||||
NoRoundingFP {
|
||||
sae: bool,
|
||||
length: EvexVectorLength,
|
||||
},
|
||||
MemoryOp {
|
||||
broadcast: bool,
|
||||
length: EvexVectorLength,
|
||||
},
|
||||
Other {
|
||||
length: EvexVectorLength,
|
||||
},
|
||||
}
|
||||
|
||||
impl EvexContext {
|
||||
/// Encode the `L'`, `L`, and `b` bits (bits 6:4 of EVEX P2 byte) for merging with the P2 byte.
|
||||
fn bits(&self) -> u8 {
|
||||
match self {
|
||||
Self::RoundingRegToRegFP { rc } => 0b001 | rc.bits() << 1,
|
||||
Self::NoRoundingFP { sae, length } => (*sae as u8) | length.bits() << 1,
|
||||
Self::MemoryOp { broadcast, length } => (*broadcast as u8) | length.bits() << 1,
|
||||
Self::Other { length } => length.bits() << 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The EVEX format allows choosing a vector length in the `L'` and `L` bits; see `EvexContext`.
|
||||
#[allow(dead_code)]
|
||||
enum EvexVectorLength {
|
||||
V128,
|
||||
V256,
|
||||
V512,
|
||||
}
|
||||
|
||||
impl EvexVectorLength {
|
||||
/// Encode the `L'` and `L` bits for merging with the P2 byte.
|
||||
fn bits(&self) -> u8 {
|
||||
match self {
|
||||
Self::V128 => 0b00,
|
||||
Self::V256 => 0b01,
|
||||
Self::V512 => 0b10,
|
||||
// 0b11 is reserved (#UD).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The EVEX format allows defining rounding control in the `L'` and `L` bits; see `EvexContext`.
|
||||
#[allow(dead_code)]
|
||||
enum EvexRoundingControl {
|
||||
RNE,
|
||||
RD,
|
||||
RU,
|
||||
RZ,
|
||||
}
|
||||
|
||||
impl EvexRoundingControl {
|
||||
/// Encode the `L'` and `L` bits for merging with the P2 byte.
|
||||
fn bits(&self) -> u8 {
|
||||
match self {
|
||||
Self::RNE => 0b00,
|
||||
Self::RD => 0b01,
|
||||
Self::RU => 0b10,
|
||||
Self::RZ => 0b11,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the EVEX masking behavior; masking support is described in section 2.6.4 of the Intel
|
||||
/// Software Development Manual, volume 2A.
|
||||
#[allow(dead_code)]
|
||||
enum EvexMasking {
|
||||
None,
|
||||
Merging { k: u8 },
|
||||
Zeroing { k: u8 },
|
||||
}
|
||||
|
||||
impl EvexMasking {
|
||||
/// Encode the `z` bit for merging with the P2 byte.
|
||||
fn z_bit(&self) -> u8 {
|
||||
match self {
|
||||
Self::None | Self::Merging { .. } => 0,
|
||||
Self::Zeroing { .. } => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode the `aaa` bits for merging with the P2 byte.
|
||||
fn aaa_bits(&self) -> u8 {
|
||||
match self {
|
||||
Self::None => 0b000,
|
||||
Self::Merging { k } | Self::Zeroing { k } => {
|
||||
debug_assert!(*k <= 7);
|
||||
*k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode an EVEX prefix, including the instruction opcode. To match the current recipe
|
||||
/// convention, the ModR/M byte is written separately in the recipe. This EVEX encoding function
|
||||
/// only encodes the `reg` (operand 1), `vvvv` (operand 2), `rm` (operand 3) form; other forms are
|
||||
/// possible (see section 2.6.2, Intel Software Development Manual, volume 2A), requiring
|
||||
/// refactoring of this function or separate functions for each form (e.g. as for the REX prefix).
|
||||
fn put_evex<CS: CodeSink + ?Sized>(
|
||||
bits: u16,
|
||||
reg: RegUnit,
|
||||
vvvvv: RegUnit,
|
||||
rm: RegUnit,
|
||||
context: EvexContext,
|
||||
masking: EvexMasking,
|
||||
sink: &mut CS,
|
||||
) {
|
||||
let enc = EncodingBits::from(bits);
|
||||
|
||||
// EVEX prefix.
|
||||
sink.put1(0x62);
|
||||
|
||||
debug_assert!(enc.mm() < 0b100);
|
||||
let mut p0 = enc.mm() & 0b11;
|
||||
p0 |= evex2(rm, reg) << 4; // bits 3:2 are always unset
|
||||
sink.put1(p0);
|
||||
|
||||
let mut p1 = enc.pp() | 0b100; // bit 2 is always set
|
||||
p1 |= (!(vvvvv as u8) & 0b1111) << 3;
|
||||
p1 |= (enc.rex_w() & 0b1) << 7;
|
||||
sink.put1(p1);
|
||||
|
||||
let mut p2 = masking.aaa_bits();
|
||||
p2 |= (!(vvvvv as u8 >> 4) & 0b1) << 3;
|
||||
p2 |= context.bits() << 4;
|
||||
p2 |= masking.z_bit() << 7;
|
||||
sink.put1(p2);
|
||||
|
||||
// Opcode
|
||||
sink.put1(enc.opcode_byte());
|
||||
|
||||
// ModR/M byte placed in recipe
|
||||
}
|
||||
|
||||
/// Emit a ModR/M byte for reg-reg operands.
|
||||
fn modrm_rr<CS: CodeSink + ?Sized>(rm: RegUnit, reg: RegUnit, sink: &mut CS) {
|
||||
let reg = reg as u8 & 7;
|
||||
let rm = rm as u8 & 7;
|
||||
let mut b = 0b11000000;
|
||||
b |= reg << 3;
|
||||
b |= rm;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a ModR/M byte where the reg bits are part of the opcode.
|
||||
fn modrm_r_bits<CS: CodeSink + ?Sized>(rm: RegUnit, bits: u16, sink: &mut CS) {
|
||||
let reg = (bits >> 12) as u8 & 7;
|
||||
let rm = rm as u8 & 7;
|
||||
let mut b = 0b11000000;
|
||||
b |= reg << 3;
|
||||
b |= rm;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a mode 00 ModR/M byte. This is a register-indirect addressing mode with no offset.
|
||||
/// Registers %rsp and %rbp are invalid for `rm`, %rsp indicates a SIB byte, and %rbp indicates an
|
||||
/// absolute immediate 32-bit address.
|
||||
fn modrm_rm<CS: CodeSink + ?Sized>(rm: RegUnit, reg: RegUnit, sink: &mut CS) {
|
||||
let reg = reg as u8 & 7;
|
||||
let rm = rm as u8 & 7;
|
||||
let mut b = 0b00000000;
|
||||
b |= reg << 3;
|
||||
b |= rm;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a mode 00 Mod/RM byte, with a rip-relative displacement in 64-bit mode. Effective address
|
||||
/// is calculated by adding displacement to 64-bit rip of next instruction. See intel Sw dev manual
|
||||
/// section 2.2.1.6.
|
||||
fn modrm_riprel<CS: CodeSink + ?Sized>(reg: RegUnit, sink: &mut CS) {
|
||||
modrm_rm(0b101, reg, sink)
|
||||
}
|
||||
|
||||
/// Emit a mode 01 ModR/M byte. This is a register-indirect addressing mode with 8-bit
|
||||
/// displacement.
|
||||
/// Register %rsp is invalid for `rm`. It indicates the presence of a SIB byte.
|
||||
fn modrm_disp8<CS: CodeSink + ?Sized>(rm: RegUnit, reg: RegUnit, sink: &mut CS) {
|
||||
let reg = reg as u8 & 7;
|
||||
let rm = rm as u8 & 7;
|
||||
let mut b = 0b01000000;
|
||||
b |= reg << 3;
|
||||
b |= rm;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a mode 10 ModR/M byte. This is a register-indirect addressing mode with 32-bit
|
||||
/// displacement.
|
||||
/// Register %rsp is invalid for `rm`. It indicates the presence of a SIB byte.
|
||||
fn modrm_disp32<CS: CodeSink + ?Sized>(rm: RegUnit, reg: RegUnit, sink: &mut CS) {
|
||||
let reg = reg as u8 & 7;
|
||||
let rm = rm as u8 & 7;
|
||||
let mut b = 0b10000000;
|
||||
b |= reg << 3;
|
||||
b |= rm;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a mode 00 ModR/M with a 100 RM indicating a SIB byte is present.
|
||||
fn modrm_sib<CS: CodeSink + ?Sized>(reg: RegUnit, sink: &mut CS) {
|
||||
modrm_rm(0b100, reg, sink);
|
||||
}
|
||||
|
||||
/// Emit a mode 01 ModR/M with a 100 RM indicating a SIB byte and 8-bit
|
||||
/// displacement are present.
|
||||
fn modrm_sib_disp8<CS: CodeSink + ?Sized>(reg: RegUnit, sink: &mut CS) {
|
||||
modrm_disp8(0b100, reg, sink);
|
||||
}
|
||||
|
||||
/// Emit a mode 10 ModR/M with a 100 RM indicating a SIB byte and 32-bit
|
||||
/// displacement are present.
|
||||
fn modrm_sib_disp32<CS: CodeSink + ?Sized>(reg: RegUnit, sink: &mut CS) {
|
||||
modrm_disp32(0b100, reg, sink);
|
||||
}
|
||||
|
||||
/// Emit a SIB byte with a base register and no scale+index.
|
||||
fn sib_noindex<CS: CodeSink + ?Sized>(base: RegUnit, sink: &mut CS) {
|
||||
let base = base as u8 & 7;
|
||||
// SIB SS_III_BBB.
|
||||
let mut b = 0b00_100_000;
|
||||
b |= base;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a SIB byte with a scale, base, and index.
|
||||
fn sib<CS: CodeSink + ?Sized>(scale: u8, index: RegUnit, base: RegUnit, sink: &mut CS) {
|
||||
// SIB SS_III_BBB.
|
||||
debug_assert_eq!(scale & !0x03, 0, "Scale out of range");
|
||||
let scale = scale & 3;
|
||||
let index = index as u8 & 7;
|
||||
let base = base as u8 & 7;
|
||||
let b: u8 = (scale << 6) | (index << 3) | base;
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Get the low 4 bits of an opcode for an integer condition code.
|
||||
///
|
||||
/// Add this offset to a base opcode for:
|
||||
///
|
||||
/// ---- 0x70: Short conditional branch.
|
||||
/// 0x0f 0x80: Long conditional branch.
|
||||
/// 0x0f 0x90: SetCC.
|
||||
///
|
||||
fn icc2opc(cond: IntCC) -> u16 {
|
||||
use crate::ir::condcodes::IntCC::*;
|
||||
match cond {
|
||||
Overflow => 0x0,
|
||||
NotOverflow => 0x1,
|
||||
UnsignedLessThan => 0x2,
|
||||
UnsignedGreaterThanOrEqual => 0x3,
|
||||
Equal => 0x4,
|
||||
NotEqual => 0x5,
|
||||
UnsignedLessThanOrEqual => 0x6,
|
||||
UnsignedGreaterThan => 0x7,
|
||||
// 0x8 = Sign.
|
||||
// 0x9 = !Sign.
|
||||
// 0xa = Parity even.
|
||||
// 0xb = Parity odd.
|
||||
SignedLessThan => 0xc,
|
||||
SignedGreaterThanOrEqual => 0xd,
|
||||
SignedLessThanOrEqual => 0xe,
|
||||
SignedGreaterThan => 0xf,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the low 4 bits of an opcode for a floating point condition code.
|
||||
///
|
||||
/// The ucomiss/ucomisd instructions set the FLAGS bits CF/PF/CF like this:
|
||||
///
|
||||
/// ZPC OSA
|
||||
/// UN 111 000
|
||||
/// GT 000 000
|
||||
/// LT 001 000
|
||||
/// EQ 100 000
|
||||
///
|
||||
/// Not all floating point condition codes are supported.
|
||||
fn fcc2opc(cond: FloatCC) -> u16 {
|
||||
use crate::ir::condcodes::FloatCC::*;
|
||||
match cond {
|
||||
Ordered => 0xb, // EQ|LT|GT => *np (P=0)
|
||||
Unordered => 0xa, // UN => *p (P=1)
|
||||
OrderedNotEqual => 0x5, // LT|GT => *ne (Z=0),
|
||||
UnorderedOrEqual => 0x4, // UN|EQ => *e (Z=1)
|
||||
GreaterThan => 0x7, // GT => *a (C=0&Z=0)
|
||||
GreaterThanOrEqual => 0x3, // GT|EQ => *ae (C=0)
|
||||
UnorderedOrLessThan => 0x2, // UN|LT => *b (C=1)
|
||||
UnorderedOrLessThanOrEqual => 0x6, // UN|LT|EQ => *be (Z=1|C=1)
|
||||
Equal | // EQ
|
||||
NotEqual | // UN|LT|GT
|
||||
LessThan | // LT
|
||||
LessThanOrEqual | // LT|EQ
|
||||
UnorderedOrGreaterThan | // UN|GT
|
||||
UnorderedOrGreaterThanOrEqual // UN|GT|EQ
|
||||
=> panic!("{} not supported", cond),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a single-byte branch displacement to `destination`.
|
||||
fn disp1<CS: CodeSink + ?Sized>(destination: Block, func: &Function, sink: &mut CS) {
|
||||
let delta = func.offsets[destination].wrapping_sub(sink.offset() + 1);
|
||||
sink.put1(delta as u8);
|
||||
}
|
||||
|
||||
/// Emit a four-byte branch displacement to `destination`.
|
||||
fn disp4<CS: CodeSink + ?Sized>(destination: Block, func: &Function, sink: &mut CS) {
|
||||
let delta = func.offsets[destination].wrapping_sub(sink.offset() + 4);
|
||||
sink.put4(delta);
|
||||
}
|
||||
|
||||
/// Emit a four-byte displacement to jump table `jt`.
|
||||
fn jt_disp4<CS: CodeSink + ?Sized>(jt: JumpTable, func: &Function, sink: &mut CS) {
|
||||
let delta = func.jt_offsets[jt].wrapping_sub(sink.offset() + 4);
|
||||
sink.put4(delta);
|
||||
sink.reloc_jt(Reloc::X86PCRelRodata4, jt);
|
||||
}
|
||||
|
||||
/// Emit a four-byte displacement to `constant`.
|
||||
fn const_disp4<CS: CodeSink + ?Sized>(constant: Constant, func: &Function, sink: &mut CS) {
|
||||
let offset = func.dfg.constants.get_offset(constant);
|
||||
let delta = offset.wrapping_sub(sink.offset() + 4);
|
||||
sink.put4(delta);
|
||||
sink.reloc_constant(Reloc::X86PCRelRodata4, offset);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,199 +0,0 @@
|
||||
//! x86 Instruction Set Architectures.
|
||||
|
||||
mod abi;
|
||||
mod binemit;
|
||||
mod enc_tables;
|
||||
mod registers;
|
||||
pub mod settings;
|
||||
#[cfg(feature = "unwind")]
|
||||
pub mod unwind;
|
||||
|
||||
use super::super::settings as shared_settings;
|
||||
#[cfg(feature = "testing_hooks")]
|
||||
use crate::binemit::CodeSink;
|
||||
use crate::binemit::{emit_function, MemoryCodeSink};
|
||||
use crate::ir;
|
||||
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
|
||||
use crate::isa::Builder as IsaBuilder;
|
||||
#[cfg(feature = "unwind")]
|
||||
use crate::isa::{unwind::systemv::RegisterMappingError, RegUnit};
|
||||
use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa};
|
||||
use crate::regalloc;
|
||||
use crate::result::CodegenResult;
|
||||
use crate::timing;
|
||||
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
|
||||
use core::any::Any;
|
||||
use core::fmt;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use target_lexicon::{PointerWidth, Triple};
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Isa {
|
||||
triple: Triple,
|
||||
shared_flags: shared_settings::Flags,
|
||||
isa_flags: settings::Flags,
|
||||
cpumode: &'static [shared_enc_tables::Level1Entry<u16>],
|
||||
}
|
||||
|
||||
/// Get an ISA builder for creating x86 targets.
|
||||
pub fn isa_builder(triple: Triple) -> IsaBuilder {
|
||||
IsaBuilder {
|
||||
triple,
|
||||
setup: settings::builder(),
|
||||
constructor: isa_constructor,
|
||||
}
|
||||
}
|
||||
|
||||
fn isa_constructor(
|
||||
triple: Triple,
|
||||
shared_flags: shared_settings::Flags,
|
||||
builder: shared_settings::Builder,
|
||||
) -> Box<dyn TargetIsa> {
|
||||
let level1 = match triple.pointer_width().unwrap() {
|
||||
PointerWidth::U16 => unimplemented!("x86-16"),
|
||||
PointerWidth::U32 => &enc_tables::LEVEL1_I32[..],
|
||||
PointerWidth::U64 => &enc_tables::LEVEL1_I64[..],
|
||||
};
|
||||
|
||||
let isa_flags = settings::Flags::new(&shared_flags, builder);
|
||||
|
||||
Box::new(Isa {
|
||||
triple,
|
||||
isa_flags,
|
||||
shared_flags,
|
||||
cpumode: level1,
|
||||
})
|
||||
}
|
||||
|
||||
impl TargetIsa for Isa {
|
||||
fn name(&self) -> &'static str {
|
||||
"x86"
|
||||
}
|
||||
|
||||
fn triple(&self) -> &Triple {
|
||||
&self.triple
|
||||
}
|
||||
|
||||
fn flags(&self) -> &shared_settings::Flags {
|
||||
&self.shared_flags
|
||||
}
|
||||
|
||||
fn isa_flags(&self) -> Vec<shared_settings::Value> {
|
||||
self.isa_flags.iter().collect()
|
||||
}
|
||||
|
||||
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
|
||||
self.shared_flags.hash(&mut hasher);
|
||||
self.isa_flags.hash(&mut hasher);
|
||||
}
|
||||
|
||||
fn uses_cpu_flags(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn uses_complex_addresses(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn register_info(&self) -> RegInfo {
|
||||
registers::INFO.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn map_dwarf_register(&self, reg: RegUnit) -> Result<u16, RegisterMappingError> {
|
||||
unwind::systemv::map_reg(self, reg).map(|r| r.0)
|
||||
}
|
||||
|
||||
fn encoding_info(&self) -> EncInfo {
|
||||
enc_tables::INFO.clone()
|
||||
}
|
||||
|
||||
fn legal_encodings<'a>(
|
||||
&'a self,
|
||||
func: &'a ir::Function,
|
||||
inst: &'a ir::InstructionData,
|
||||
ctrl_typevar: ir::Type,
|
||||
) -> Encodings<'a> {
|
||||
lookup_enclist(
|
||||
ctrl_typevar,
|
||||
inst,
|
||||
func,
|
||||
self.cpumode,
|
||||
&enc_tables::LEVEL2[..],
|
||||
&enc_tables::ENCLISTS[..],
|
||||
&enc_tables::LEGALIZE_ACTIONS[..],
|
||||
&enc_tables::RECIPE_PREDICATES[..],
|
||||
&enc_tables::INST_PREDICATES[..],
|
||||
self.isa_flags.predicate_view(),
|
||||
)
|
||||
}
|
||||
|
||||
fn legalize_signature(&self, sig: &mut Cow<ir::Signature>, current: bool) {
|
||||
abi::legalize_signature(
|
||||
sig,
|
||||
&self.triple,
|
||||
current,
|
||||
&self.shared_flags,
|
||||
&self.isa_flags,
|
||||
)
|
||||
}
|
||||
|
||||
fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass {
|
||||
abi::regclass_for_abi_type(ty)
|
||||
}
|
||||
|
||||
fn allocatable_registers(&self, _func: &ir::Function) -> regalloc::RegisterSet {
|
||||
abi::allocatable_registers(&self.triple, &self.shared_flags)
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing_hooks")]
|
||||
fn emit_inst(
|
||||
&self,
|
||||
func: &ir::Function,
|
||||
inst: ir::Inst,
|
||||
divert: &mut regalloc::RegDiversions,
|
||||
sink: &mut dyn CodeSink,
|
||||
) {
|
||||
binemit::emit_inst(func, inst, divert, sink, self)
|
||||
}
|
||||
|
||||
fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
|
||||
emit_function(func, binemit::emit_inst, sink, self)
|
||||
}
|
||||
|
||||
fn prologue_epilogue(&self, func: &mut ir::Function) -> CodegenResult<()> {
|
||||
let _tt = timing::prologue_epilogue();
|
||||
abi::prologue_epilogue(func, self)
|
||||
}
|
||||
|
||||
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC {
|
||||
ir::condcodes::IntCC::UnsignedLessThan
|
||||
}
|
||||
|
||||
fn unsigned_sub_overflow_condition(&self) -> ir::condcodes::IntCC {
|
||||
ir::condcodes::IntCC::UnsignedLessThan
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn create_unwind_info(
|
||||
&self,
|
||||
func: &ir::Function,
|
||||
) -> CodegenResult<Option<super::super::unwind::UnwindInfo>> {
|
||||
abi::create_unwind_info(func, self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
|
||||
Some(unwind::systemv::create_cie())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Isa {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}\n{}", self.shared_flags, self.isa_flags)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
//! x86 register descriptions.
|
||||
|
||||
use crate::isa::registers::{RegBank, RegClass, RegClassData, RegInfo, RegUnit};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/registers-x86.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::isa::RegUnit;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
#[test]
|
||||
fn unit_encodings() {
|
||||
fn gpr(unit: usize) -> Option<u16> {
|
||||
Some(GPR.unit(unit))
|
||||
}
|
||||
// The encoding of integer registers is not alphabetical.
|
||||
assert_eq!(INFO.parse_regunit("rax"), gpr(0));
|
||||
assert_eq!(INFO.parse_regunit("rbx"), gpr(3));
|
||||
assert_eq!(INFO.parse_regunit("rcx"), gpr(1));
|
||||
assert_eq!(INFO.parse_regunit("rdx"), gpr(2));
|
||||
assert_eq!(INFO.parse_regunit("rsi"), gpr(6));
|
||||
assert_eq!(INFO.parse_regunit("rdi"), gpr(7));
|
||||
assert_eq!(INFO.parse_regunit("rbp"), gpr(5));
|
||||
assert_eq!(INFO.parse_regunit("rsp"), gpr(4));
|
||||
assert_eq!(INFO.parse_regunit("r8"), gpr(8));
|
||||
assert_eq!(INFO.parse_regunit("r15"), gpr(15));
|
||||
|
||||
fn fpr(unit: usize) -> Option<u16> {
|
||||
Some(FPR.unit(unit))
|
||||
}
|
||||
assert_eq!(INFO.parse_regunit("xmm0"), fpr(0));
|
||||
assert_eq!(INFO.parse_regunit("xmm15"), fpr(15));
|
||||
|
||||
// FIXME(#1306) Add these tests back in when FPR32 is re-added.
|
||||
// fn fpr32(unit: usize) -> Option<u16> {
|
||||
// Some(FPR32.unit(unit))
|
||||
// }
|
||||
// assert_eq!(INFO.parse_regunit("xmm0"), fpr32(0));
|
||||
// assert_eq!(INFO.parse_regunit("xmm31"), fpr32(31));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_names() {
|
||||
fn gpr(ru: RegUnit) -> String {
|
||||
INFO.display_regunit(GPR.first + ru).to_string()
|
||||
}
|
||||
assert_eq!(gpr(0), "%rax");
|
||||
assert_eq!(gpr(3), "%rbx");
|
||||
assert_eq!(gpr(1), "%rcx");
|
||||
assert_eq!(gpr(2), "%rdx");
|
||||
assert_eq!(gpr(6), "%rsi");
|
||||
assert_eq!(gpr(7), "%rdi");
|
||||
assert_eq!(gpr(5), "%rbp");
|
||||
assert_eq!(gpr(4), "%rsp");
|
||||
assert_eq!(gpr(8), "%r8");
|
||||
assert_eq!(gpr(15), "%r15");
|
||||
|
||||
fn fpr(ru: RegUnit) -> String {
|
||||
INFO.display_regunit(FPR.first + ru).to_string()
|
||||
}
|
||||
assert_eq!(fpr(0), "%xmm0");
|
||||
assert_eq!(fpr(15), "%xmm15");
|
||||
|
||||
// FIXME(#1306) Add these tests back in when FPR32 is re-added.
|
||||
// fn fpr32(ru: RegUnit) -> String {
|
||||
// INFO.display_regunit(FPR32.first + ru).to_string()
|
||||
// }
|
||||
// assert_eq!(fpr32(0), "%xmm0");
|
||||
// assert_eq!(fpr32(31), "%xmm31");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regclasses() {
|
||||
assert_eq!(GPR.intersect_index(GPR), Some(GPR.into()));
|
||||
assert_eq!(GPR.intersect_index(ABCD), Some(ABCD.into()));
|
||||
assert_eq!(GPR.intersect_index(FPR), None);
|
||||
assert_eq!(ABCD.intersect_index(GPR), Some(ABCD.into()));
|
||||
assert_eq!(ABCD.intersect_index(ABCD), Some(ABCD.into()));
|
||||
assert_eq!(ABCD.intersect_index(FPR), None);
|
||||
assert_eq!(FPR.intersect_index(FPR), Some(FPR.into()));
|
||||
assert_eq!(FPR.intersect_index(GPR), None);
|
||||
assert_eq!(FPR.intersect_index(ABCD), None);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//! x86 Settings.
|
||||
|
||||
use crate::settings::{self, detail, Builder, Value};
|
||||
use core::fmt;
|
||||
|
||||
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a
|
||||
// public `Flags` struct with an impl for all of the settings defined in
|
||||
// `cranelift-codegen/meta/src/isa/x86/settings.rs`.
|
||||
include!(concat!(env!("OUT_DIR"), "/settings-x86.rs"));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{builder, Flags};
|
||||
use crate::settings::{self, Configurable};
|
||||
|
||||
#[test]
|
||||
fn presets() {
|
||||
let shared = settings::Flags::new(settings::builder());
|
||||
|
||||
// Nehalem has SSE4.1 but not BMI1.
|
||||
let mut b0 = builder();
|
||||
b0.enable("nehalem").unwrap();
|
||||
let f0 = Flags::new(&shared, b0);
|
||||
assert_eq!(f0.has_sse41(), true);
|
||||
assert_eq!(f0.has_bmi1(), false);
|
||||
|
||||
let mut b1 = builder();
|
||||
b1.enable("haswell").unwrap();
|
||||
let f1 = Flags::new(&shared, b1);
|
||||
assert_eq!(f1.has_sse41(), true);
|
||||
assert_eq!(f1.has_bmi1(), true);
|
||||
}
|
||||
#[test]
|
||||
fn display_presets() {
|
||||
// Spot check that the flags Display impl does not cause a panic
|
||||
let shared = settings::Flags::new(settings::builder());
|
||||
|
||||
let b0 = builder();
|
||||
let f0 = Flags::new(&shared, b0);
|
||||
let _ = format!("{}", f0);
|
||||
|
||||
let mut b1 = builder();
|
||||
b1.enable("nehalem").unwrap();
|
||||
let f1 = Flags::new(&shared, b1);
|
||||
let _ = format!("{}", f1);
|
||||
|
||||
let mut b2 = builder();
|
||||
b2.enable("haswell").unwrap();
|
||||
let f2 = Flags::new(&shared, b2);
|
||||
let _ = format!("{}", f2);
|
||||
}
|
||||
}
|
||||
@@ -1,531 +0,0 @@
|
||||
//! Module for x86 unwind generation for supported ABIs.
|
||||
|
||||
pub mod systemv;
|
||||
pub mod winx64;
|
||||
|
||||
use crate::ir::{Function, InstructionData, Opcode, ValueLoc};
|
||||
use crate::isa::x86::registers::{FPR, RU};
|
||||
use crate::isa::{RegUnit, TargetIsa};
|
||||
use crate::result::CodegenResult;
|
||||
use alloc::vec::Vec;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::isa::unwind::input::{UnwindCode, UnwindInfo};
|
||||
|
||||
pub(crate) fn create_unwind_info(
|
||||
func: &Function,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> CodegenResult<Option<UnwindInfo<RegUnit>>> {
|
||||
// Find last block based on max offset.
|
||||
let last_block = func
|
||||
.layout
|
||||
.blocks()
|
||||
.max_by_key(|b| func.offsets[*b])
|
||||
.expect("at least a block");
|
||||
// Find last instruction offset + size, and make it function size.
|
||||
let function_size = func
|
||||
.inst_offsets(last_block, &isa.encoding_info())
|
||||
.fold(0, |_, (offset, _, size)| offset + size);
|
||||
|
||||
let entry_block = func.layout.entry_block().expect("missing entry block");
|
||||
let prologue_end = func.prologue_end.unwrap();
|
||||
let epilogues_start = func
|
||||
.epilogues_start
|
||||
.iter()
|
||||
.map(|(i, b)| (*b, *i))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let word_size = isa.pointer_bytes();
|
||||
|
||||
let mut stack_size = None;
|
||||
let mut prologue_size = 0;
|
||||
let mut prologue_unwind_codes = Vec::new();
|
||||
let mut epilogues_unwind_codes = Vec::new();
|
||||
let mut frame_register: Option<RegUnit> = None;
|
||||
|
||||
// Process only entry block and blocks with epilogues.
|
||||
let mut blocks = func
|
||||
.epilogues_start
|
||||
.iter()
|
||||
.map(|(_, b)| *b)
|
||||
.collect::<Vec<_>>();
|
||||
if !blocks.contains(&entry_block) {
|
||||
blocks.push(entry_block);
|
||||
}
|
||||
blocks.sort_by_key(|b| func.offsets[*b]);
|
||||
|
||||
for block in blocks.iter() {
|
||||
let mut in_prologue = block == &entry_block;
|
||||
let mut in_epilogue = false;
|
||||
let mut epilogue_pop_offsets = Vec::new();
|
||||
|
||||
let epilogue_start = epilogues_start.get(block);
|
||||
let is_last_block = block == &last_block;
|
||||
|
||||
for (offset, inst, size) in func.inst_offsets(*block, &isa.encoding_info()) {
|
||||
let offset = offset + size;
|
||||
|
||||
let unwind_codes;
|
||||
if in_prologue {
|
||||
// Check for prologue end (inclusive)
|
||||
if prologue_end == inst {
|
||||
in_prologue = false;
|
||||
}
|
||||
prologue_size += size;
|
||||
unwind_codes = &mut prologue_unwind_codes;
|
||||
} else if !in_epilogue && epilogue_start == Some(&inst) {
|
||||
// Now in an epilogue, emit a remember state instruction if not last block
|
||||
in_epilogue = true;
|
||||
|
||||
epilogues_unwind_codes.push(Vec::new());
|
||||
unwind_codes = epilogues_unwind_codes.last_mut().unwrap();
|
||||
|
||||
if !is_last_block {
|
||||
unwind_codes.push((offset, UnwindCode::RememberState));
|
||||
}
|
||||
} else if in_epilogue {
|
||||
unwind_codes = epilogues_unwind_codes.last_mut().unwrap();
|
||||
} else {
|
||||
// Ignore normal instructions
|
||||
continue;
|
||||
}
|
||||
|
||||
match func.dfg[inst] {
|
||||
InstructionData::Unary { opcode, arg } => {
|
||||
match opcode {
|
||||
Opcode::X86Push => {
|
||||
let reg = func.locations[arg].unwrap_reg();
|
||||
unwind_codes.push((
|
||||
offset,
|
||||
UnwindCode::StackAlloc {
|
||||
size: word_size.into(),
|
||||
},
|
||||
));
|
||||
unwind_codes.push((
|
||||
offset,
|
||||
UnwindCode::SaveRegister {
|
||||
reg,
|
||||
stack_offset: 0,
|
||||
},
|
||||
));
|
||||
}
|
||||
Opcode::AdjustSpDown => {
|
||||
let stack_size =
|
||||
stack_size.expect("expected a previous stack size instruction");
|
||||
|
||||
// This is used when calling a stack check function
|
||||
// We need to track the assignment to RAX which has the size of the stack
|
||||
unwind_codes
|
||||
.push((offset, UnwindCode::StackAlloc { size: stack_size }));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InstructionData::UnaryImm { opcode, imm } => {
|
||||
match opcode {
|
||||
Opcode::Iconst => {
|
||||
let imm: i64 = imm.into();
|
||||
assert!(imm <= core::u32::MAX as i64);
|
||||
assert!(stack_size.is_none());
|
||||
|
||||
// This instruction should only appear in a prologue to pass an
|
||||
// argument of the stack size to a stack check function.
|
||||
// Record the stack size so we know what it is when we encounter the adjustment
|
||||
// instruction (which will adjust via the register assigned to this instruction).
|
||||
stack_size = Some(imm as u32);
|
||||
}
|
||||
Opcode::AdjustSpDownImm => {
|
||||
let imm: i64 = imm.into();
|
||||
assert!(imm <= core::u32::MAX as i64);
|
||||
|
||||
stack_size = Some(imm as u32);
|
||||
|
||||
unwind_codes
|
||||
.push((offset, UnwindCode::StackAlloc { size: imm as u32 }));
|
||||
}
|
||||
Opcode::AdjustSpUpImm => {
|
||||
let imm: i64 = imm.into();
|
||||
assert!(imm <= core::u32::MAX as i64);
|
||||
|
||||
stack_size = Some(imm as u32);
|
||||
|
||||
unwind_codes
|
||||
.push((offset, UnwindCode::StackDealloc { size: imm as u32 }));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InstructionData::Store {
|
||||
opcode: Opcode::Store,
|
||||
args: [arg1, arg2],
|
||||
offset: stack_offset,
|
||||
..
|
||||
} => {
|
||||
if let (ValueLoc::Reg(src), ValueLoc::Reg(dst)) =
|
||||
(func.locations[arg1], func.locations[arg2])
|
||||
{
|
||||
// If this is a save of an FPR, record an unwind operation
|
||||
// Note: the stack_offset here is relative to an adjusted SP
|
||||
if dst == (RU::rsp as RegUnit) && FPR.contains(src) {
|
||||
let stack_offset: i32 = stack_offset.into();
|
||||
unwind_codes.push((
|
||||
offset,
|
||||
UnwindCode::SaveRegister {
|
||||
reg: src,
|
||||
stack_offset: stack_offset as u32,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
InstructionData::CopySpecial { src, dst, .. } if frame_register.is_none() => {
|
||||
// Check for change in CFA register (RSP is always the starting CFA)
|
||||
if src == (RU::rsp as RegUnit) {
|
||||
unwind_codes.push((offset, UnwindCode::SetFramePointer { reg: dst }));
|
||||
frame_register = Some(dst);
|
||||
}
|
||||
}
|
||||
InstructionData::NullAry { opcode } => match opcode {
|
||||
Opcode::X86Pop => {
|
||||
epilogue_pop_offsets.push(offset);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
InstructionData::MultiAry { opcode, .. } if in_epilogue => match opcode {
|
||||
Opcode::Return => {
|
||||
let args = func.dfg.inst_args(inst);
|
||||
for (i, arg) in args.iter().rev().enumerate() {
|
||||
// Only walk back the args for the pop instructions encountered
|
||||
if i >= epilogue_pop_offsets.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let offset = epilogue_pop_offsets[i];
|
||||
|
||||
let reg = func.locations[*arg].unwrap_reg();
|
||||
unwind_codes.push((offset, UnwindCode::RestoreRegister { reg }));
|
||||
unwind_codes.push((
|
||||
offset,
|
||||
UnwindCode::StackDealloc {
|
||||
size: word_size.into(),
|
||||
},
|
||||
));
|
||||
|
||||
if Some(reg) == frame_register {
|
||||
unwind_codes.push((offset, UnwindCode::RestoreFramePointer));
|
||||
// Keep frame_register assigned for next epilogue.
|
||||
}
|
||||
}
|
||||
epilogue_pop_offsets.clear();
|
||||
|
||||
// TODO ensure unwind codes sorted by offsets ?
|
||||
|
||||
if !is_last_block {
|
||||
unwind_codes.push((offset, UnwindCode::RestoreState));
|
||||
}
|
||||
|
||||
in_epilogue = false;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(UnwindInfo {
|
||||
prologue_size,
|
||||
prologue_unwind_codes,
|
||||
epilogues_unwind_codes,
|
||||
function_size,
|
||||
word_size,
|
||||
initial_sp_offset: word_size,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::ir::{
|
||||
types, AbiParam, ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind,
|
||||
};
|
||||
use crate::isa::{lookup_variant, BackendVariant, CallConv};
|
||||
use crate::settings::{builder, Flags};
|
||||
use crate::Context;
|
||||
use std::str::FromStr;
|
||||
use target_lexicon::triple;
|
||||
|
||||
#[test]
|
||||
fn test_small_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
prologue_size: 9,
|
||||
prologue_unwind_codes: vec![
|
||||
(2, UnwindCode::StackAlloc { size: 8 }),
|
||||
(
|
||||
2,
|
||||
UnwindCode::SaveRegister {
|
||||
reg: RU::rbp.into(),
|
||||
stack_offset: 0,
|
||||
}
|
||||
),
|
||||
(
|
||||
5,
|
||||
UnwindCode::SetFramePointer {
|
||||
reg: RU::rbp.into(),
|
||||
}
|
||||
),
|
||||
(9, UnwindCode::StackAlloc { size: 64 })
|
||||
],
|
||||
epilogues_unwind_codes: vec![vec![
|
||||
(13, UnwindCode::StackDealloc { size: 64 }),
|
||||
(
|
||||
15,
|
||||
UnwindCode::RestoreRegister {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
),
|
||||
(15, UnwindCode::StackDealloc { size: 8 }),
|
||||
(15, UnwindCode::RestoreFramePointer)
|
||||
]],
|
||||
function_size: 16,
|
||||
word_size: 8,
|
||||
initial_sp_offset: 8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_medium_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 10000)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
prologue_size: 27,
|
||||
prologue_unwind_codes: vec![
|
||||
(2, UnwindCode::StackAlloc { size: 8 }),
|
||||
(
|
||||
2,
|
||||
UnwindCode::SaveRegister {
|
||||
reg: RU::rbp.into(),
|
||||
stack_offset: 0,
|
||||
}
|
||||
),
|
||||
(
|
||||
5,
|
||||
UnwindCode::SetFramePointer {
|
||||
reg: RU::rbp.into(),
|
||||
}
|
||||
),
|
||||
(27, UnwindCode::StackAlloc { size: 10000 })
|
||||
],
|
||||
epilogues_unwind_codes: vec![vec![
|
||||
(34, UnwindCode::StackDealloc { size: 10000 }),
|
||||
(
|
||||
36,
|
||||
UnwindCode::RestoreRegister {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
),
|
||||
(36, UnwindCode::StackDealloc { size: 8 }),
|
||||
(36, UnwindCode::RestoreFramePointer)
|
||||
]],
|
||||
function_size: 37,
|
||||
word_size: 8,
|
||||
initial_sp_offset: 8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 1000000)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
prologue_size: 27,
|
||||
prologue_unwind_codes: vec![
|
||||
(2, UnwindCode::StackAlloc { size: 8 }),
|
||||
(
|
||||
2,
|
||||
UnwindCode::SaveRegister {
|
||||
reg: RU::rbp.into(),
|
||||
stack_offset: 0,
|
||||
}
|
||||
),
|
||||
(
|
||||
5,
|
||||
UnwindCode::SetFramePointer {
|
||||
reg: RU::rbp.into(),
|
||||
}
|
||||
),
|
||||
(27, UnwindCode::StackAlloc { size: 1000000 })
|
||||
],
|
||||
epilogues_unwind_codes: vec![vec![
|
||||
(34, UnwindCode::StackDealloc { size: 1000000 }),
|
||||
(
|
||||
36,
|
||||
UnwindCode::RestoreRegister {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
),
|
||||
(36, UnwindCode::StackDealloc { size: 8 }),
|
||||
(36, UnwindCode::RestoreFramePointer)
|
||||
]],
|
||||
function_size: 37,
|
||||
word_size: 8,
|
||||
initial_sp_offset: 8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
|
||||
let mut func =
|
||||
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
if let Some(stack_slot) = stack_slot {
|
||||
func.stack_slots.push(stack_slot);
|
||||
}
|
||||
|
||||
func
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_return_func() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_multi_return_function(CallConv::SystemV));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
prologue_size: 5,
|
||||
prologue_unwind_codes: vec![
|
||||
(2, UnwindCode::StackAlloc { size: 8 }),
|
||||
(
|
||||
2,
|
||||
UnwindCode::SaveRegister {
|
||||
reg: RU::rbp.into(),
|
||||
stack_offset: 0,
|
||||
}
|
||||
),
|
||||
(
|
||||
5,
|
||||
UnwindCode::SetFramePointer {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
)
|
||||
],
|
||||
epilogues_unwind_codes: vec![
|
||||
vec![
|
||||
(12, UnwindCode::RememberState),
|
||||
(
|
||||
12,
|
||||
UnwindCode::RestoreRegister {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
),
|
||||
(12, UnwindCode::StackDealloc { size: 8 }),
|
||||
(12, UnwindCode::RestoreFramePointer),
|
||||
(13, UnwindCode::RestoreState)
|
||||
],
|
||||
vec![
|
||||
(
|
||||
15,
|
||||
UnwindCode::RestoreRegister {
|
||||
reg: RU::rbp.into()
|
||||
}
|
||||
),
|
||||
(15, UnwindCode::StackDealloc { size: 8 }),
|
||||
(15, UnwindCode::RestoreFramePointer)
|
||||
]
|
||||
],
|
||||
function_size: 16,
|
||||
word_size: 8,
|
||||
initial_sp_offset: 8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn create_multi_return_function(call_conv: CallConv) -> Function {
|
||||
let mut sig = Signature::new(call_conv);
|
||||
sig.params.push(AbiParam::new(types::I32));
|
||||
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let v0 = func.dfg.append_block_param(block0, types::I32);
|
||||
let block1 = func.dfg.make_block();
|
||||
let block2 = func.dfg.make_block();
|
||||
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().brnz(v0, block2, &[]);
|
||||
pos.ins().jump(block1, &[]);
|
||||
|
||||
pos.insert_block(block1);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
pos.insert_block(block2);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
func
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
//! Unwind information for System V ABI (x86-64).
|
||||
|
||||
use crate::ir::Function;
|
||||
use crate::isa::{
|
||||
unwind::systemv::{RegisterMappingError, UnwindInfo},
|
||||
RegUnit, TargetIsa,
|
||||
};
|
||||
use crate::result::CodegenResult;
|
||||
use gimli::{write::CommonInformationEntry, Encoding, Format, Register, X86_64};
|
||||
|
||||
/// Creates a new x86-64 common information entry (CIE).
|
||||
pub fn create_cie() -> CommonInformationEntry {
|
||||
use gimli::write::CallFrameInstruction;
|
||||
|
||||
let mut entry = CommonInformationEntry::new(
|
||||
Encoding {
|
||||
address_size: 8,
|
||||
format: Format::Dwarf32,
|
||||
version: 1,
|
||||
},
|
||||
1, // Code alignment factor
|
||||
-8, // Data alignment factor
|
||||
X86_64::RA,
|
||||
);
|
||||
|
||||
// Every frame will start with the call frame address (CFA) at RSP+8
|
||||
// It is +8 to account for the push of the return address by the call instruction
|
||||
entry.add_instruction(CallFrameInstruction::Cfa(X86_64::RSP, 8));
|
||||
|
||||
// Every frame will start with the return address at RSP (CFA-8 = RSP+8-8 = RSP)
|
||||
entry.add_instruction(CallFrameInstruction::Offset(X86_64::RA, -8));
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
/// Map Cranelift registers to their corresponding Gimli registers.
|
||||
pub fn map_reg(isa: &dyn TargetIsa, reg: RegUnit) -> Result<Register, RegisterMappingError> {
|
||||
if isa.name() != "x86" || isa.pointer_bits() != 64 {
|
||||
return Err(RegisterMappingError::UnsupportedArchitecture);
|
||||
}
|
||||
|
||||
// Mapping from https://github.com/bytecodealliance/cranelift/pull/902 by @iximeow
|
||||
const X86_GP_REG_MAP: [gimli::Register; 16] = [
|
||||
X86_64::RAX,
|
||||
X86_64::RCX,
|
||||
X86_64::RDX,
|
||||
X86_64::RBX,
|
||||
X86_64::RSP,
|
||||
X86_64::RBP,
|
||||
X86_64::RSI,
|
||||
X86_64::RDI,
|
||||
X86_64::R8,
|
||||
X86_64::R9,
|
||||
X86_64::R10,
|
||||
X86_64::R11,
|
||||
X86_64::R12,
|
||||
X86_64::R13,
|
||||
X86_64::R14,
|
||||
X86_64::R15,
|
||||
];
|
||||
const X86_XMM_REG_MAP: [gimli::Register; 16] = [
|
||||
X86_64::XMM0,
|
||||
X86_64::XMM1,
|
||||
X86_64::XMM2,
|
||||
X86_64::XMM3,
|
||||
X86_64::XMM4,
|
||||
X86_64::XMM5,
|
||||
X86_64::XMM6,
|
||||
X86_64::XMM7,
|
||||
X86_64::XMM8,
|
||||
X86_64::XMM9,
|
||||
X86_64::XMM10,
|
||||
X86_64::XMM11,
|
||||
X86_64::XMM12,
|
||||
X86_64::XMM13,
|
||||
X86_64::XMM14,
|
||||
X86_64::XMM15,
|
||||
];
|
||||
|
||||
let reg_info = isa.register_info();
|
||||
let bank = reg_info
|
||||
.bank_containing_regunit(reg)
|
||||
.ok_or_else(|| RegisterMappingError::MissingBank)?;
|
||||
match bank.name {
|
||||
"IntRegs" => {
|
||||
// x86 GP registers have a weird mapping to DWARF registers, so we use a
|
||||
// lookup table.
|
||||
Ok(X86_GP_REG_MAP[(reg - bank.first_unit) as usize])
|
||||
}
|
||||
"FloatRegs" => Ok(X86_XMM_REG_MAP[(reg - bank.first_unit) as usize]),
|
||||
_ => Err(RegisterMappingError::UnsupportedRegisterBank(bank.name)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_unwind_info(
|
||||
func: &Function,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> CodegenResult<Option<UnwindInfo>> {
|
||||
// Only System V-like calling conventions are supported
|
||||
match isa.unwind_info_kind() {
|
||||
crate::machinst::UnwindInfoKind::SystemV => {}
|
||||
_ => return Ok(None),
|
||||
}
|
||||
|
||||
if func.prologue_end.is_none() || isa.name() != "x86" || isa.pointer_bits() != 64 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let unwind = match super::create_unwind_info(func, isa)? {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
struct RegisterMapper<'a, 'b>(&'a (dyn TargetIsa + 'b));
|
||||
impl<'a, 'b> crate::isa::unwind::systemv::RegisterMapper<RegUnit> for RegisterMapper<'a, 'b> {
|
||||
fn map(&self, reg: RegUnit) -> Result<u16, RegisterMappingError> {
|
||||
Ok(map_reg(self.0, reg)?.0)
|
||||
}
|
||||
fn sp(&self) -> u16 {
|
||||
X86_64::RSP.0
|
||||
}
|
||||
fn fp(&self) -> Option<u16> {
|
||||
Some(X86_64::RBP.0)
|
||||
}
|
||||
}
|
||||
let map = RegisterMapper(isa);
|
||||
|
||||
Ok(Some(UnwindInfo::build(unwind, &map)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::ir::{
|
||||
types, AbiParam, ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind,
|
||||
};
|
||||
use crate::isa::{lookup_variant, BackendVariant, CallConv};
|
||||
use crate::settings::{builder, Flags};
|
||||
use crate::Context;
|
||||
use gimli::write::Address;
|
||||
use std::str::FromStr;
|
||||
use target_lexicon::triple;
|
||||
|
||||
#[test]
|
||||
fn test_simple_func() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::SystemV,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let fde = match isa
|
||||
.create_unwind_info(&context.func)
|
||||
.expect("can create unwind info")
|
||||
{
|
||||
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
|
||||
info.to_fde(Address::Constant(1234))
|
||||
}
|
||||
_ => panic!("expected unwind information"),
|
||||
};
|
||||
|
||||
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(1234), length: 16, lsda: None, instructions: [(2, CfaOffset(16)), (2, Offset(Register(6), -16)), (5, CfaRegister(Register(6))), (15, SameValue(Register(6))), (15, Cfa(Register(7), 8))] }");
|
||||
}
|
||||
|
||||
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
|
||||
let mut func =
|
||||
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
if let Some(stack_slot) = stack_slot {
|
||||
func.stack_slots.push(stack_slot);
|
||||
}
|
||||
|
||||
func
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_return_func() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_multi_return_function(CallConv::SystemV));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let fde = match isa
|
||||
.create_unwind_info(&context.func)
|
||||
.expect("can create unwind info")
|
||||
{
|
||||
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
|
||||
info.to_fde(Address::Constant(4321))
|
||||
}
|
||||
_ => panic!("expected unwind information"),
|
||||
};
|
||||
|
||||
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(4321), length: 16, lsda: None, instructions: [(2, CfaOffset(16)), (2, Offset(Register(6), -16)), (5, CfaRegister(Register(6))), (12, RememberState), (12, SameValue(Register(6))), (12, Cfa(Register(7), 8)), (13, RestoreState), (15, SameValue(Register(6))), (15, Cfa(Register(7), 8))] }");
|
||||
}
|
||||
|
||||
fn create_multi_return_function(call_conv: CallConv) -> Function {
|
||||
let mut sig = Signature::new(call_conv);
|
||||
sig.params.push(AbiParam::new(types::I32));
|
||||
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let v0 = func.dfg.append_block_param(block0, types::I32);
|
||||
let block1 = func.dfg.make_block();
|
||||
let block2 = func.dfg.make_block();
|
||||
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().brnz(v0, block2, &[]);
|
||||
pos.ins().jump(block1, &[]);
|
||||
|
||||
pos.insert_block(block1);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
pos.insert_block(block2);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
func
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
//! Unwind information for Windows x64 ABI.
|
||||
|
||||
use crate::ir::Function;
|
||||
use crate::isa::x86::registers::{FPR, GPR};
|
||||
use crate::isa::{unwind::winx64::UnwindInfo, RegUnit, TargetIsa};
|
||||
use crate::result::CodegenResult;
|
||||
|
||||
pub(crate) fn create_unwind_info(
|
||||
func: &Function,
|
||||
isa: &dyn TargetIsa,
|
||||
) -> CodegenResult<Option<UnwindInfo>> {
|
||||
// Only Windows fastcall is supported for unwind information
|
||||
if !func.signature.call_conv.extends_windows_fastcall() || func.prologue_end.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let unwind = match super::create_unwind_info(func, isa)? {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(UnwindInfo::build::<RegUnit, RegisterMapper>(unwind)?))
|
||||
}
|
||||
|
||||
struct RegisterMapper;
|
||||
|
||||
impl crate::isa::unwind::winx64::RegisterMapper<RegUnit> for RegisterMapper {
|
||||
fn map(reg: RegUnit) -> crate::isa::unwind::winx64::MappedRegister {
|
||||
use crate::isa::unwind::winx64::MappedRegister;
|
||||
if GPR.contains(reg) {
|
||||
MappedRegister::Int(GPR.index_of(reg) as u8)
|
||||
} else if FPR.contains(reg) {
|
||||
MappedRegister::Xmm(reg as u8)
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind};
|
||||
use crate::isa::unwind::winx64::UnwindCode;
|
||||
use crate::isa::x86::registers::RU;
|
||||
use crate::isa::{lookup_variant, BackendVariant, CallConv};
|
||||
use crate::settings::{builder, Flags};
|
||||
use crate::Context;
|
||||
use std::str::FromStr;
|
||||
use target_lexicon::triple;
|
||||
|
||||
#[test]
|
||||
fn test_wrong_calling_convention() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(CallConv::SystemV, None));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
assert_eq!(
|
||||
create_unwind_info(&context.func, &*isa).expect("can create unwind info"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
flags: 0,
|
||||
prologue_size: 9,
|
||||
frame_register: None,
|
||||
frame_register_offset: 0,
|
||||
unwind_codes: vec![
|
||||
UnwindCode::PushRegister {
|
||||
instruction_offset: 2,
|
||||
reg: GPR.index_of(RU::rbp.into()) as u8
|
||||
},
|
||||
UnwindCode::StackAlloc {
|
||||
instruction_offset: 9,
|
||||
size: 64
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(unwind.emit_size(), 8);
|
||||
|
||||
let mut buf = [0u8; 8];
|
||||
unwind.emit(&mut buf);
|
||||
|
||||
assert_eq!(
|
||||
buf,
|
||||
[
|
||||
0x01, // Version and flags (version 1, no flags)
|
||||
0x09, // Prologue size
|
||||
0x02, // Unwind code count (1 for stack alloc, 1 for push reg)
|
||||
0x00, // Frame register + offset (no frame register)
|
||||
0x09, // Prolog offset
|
||||
0x72, // Operation 2 (small stack alloc), size = 0xB slots (e.g. (0x7 * 8) + 8 = 64 bytes)
|
||||
0x02, // Prolog offset
|
||||
0x50, // Operation 0 (save nonvolatile register), reg = 5 (RBP)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_medium_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 10000)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
flags: 0,
|
||||
prologue_size: 27,
|
||||
frame_register: None,
|
||||
frame_register_offset: 0,
|
||||
unwind_codes: vec![
|
||||
UnwindCode::PushRegister {
|
||||
instruction_offset: 2,
|
||||
reg: GPR.index_of(RU::rbp.into()) as u8
|
||||
},
|
||||
UnwindCode::StackAlloc {
|
||||
instruction_offset: 27,
|
||||
size: 10000
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(unwind.emit_size(), 12);
|
||||
|
||||
let mut buf = [0u8; 12];
|
||||
unwind.emit(&mut buf);
|
||||
|
||||
assert_eq!(
|
||||
buf,
|
||||
[
|
||||
0x01, // Version and flags (version 1, no flags)
|
||||
0x1B, // Prologue size
|
||||
0x03, // Unwind code count (2 for stack alloc, 1 for push reg)
|
||||
0x00, // Frame register + offset (no frame register)
|
||||
0x1B, // Prolog offset
|
||||
0x01, // Operation 1 (large stack alloc), size is scaled 16-bits (info = 0)
|
||||
0xE2, // Low size byte
|
||||
0x04, // High size byte (e.g. 0x04E2 * 8 = 10000 bytes)
|
||||
0x02, // Prolog offset
|
||||
0x50, // Operation 0 (push nonvolatile register), reg = 5 (RBP)
|
||||
0x00, // Padding
|
||||
0x00, // Padding
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_alloc() {
|
||||
let isa = lookup_variant(triple!("x86_64"), BackendVariant::Legacy)
|
||||
.expect("expect x86 ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::WindowsFastcall,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 1000000)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let unwind = create_unwind_info(&context.func, &*isa)
|
||||
.expect("can create unwind info")
|
||||
.expect("expected unwind info");
|
||||
|
||||
assert_eq!(
|
||||
unwind,
|
||||
UnwindInfo {
|
||||
flags: 0,
|
||||
prologue_size: 27,
|
||||
frame_register: None,
|
||||
frame_register_offset: 0,
|
||||
unwind_codes: vec![
|
||||
UnwindCode::PushRegister {
|
||||
instruction_offset: 2,
|
||||
reg: GPR.index_of(RU::rbp.into()) as u8
|
||||
},
|
||||
UnwindCode::StackAlloc {
|
||||
instruction_offset: 27,
|
||||
size: 1000000
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(unwind.emit_size(), 12);
|
||||
|
||||
let mut buf = [0u8; 12];
|
||||
unwind.emit(&mut buf);
|
||||
|
||||
assert_eq!(
|
||||
buf,
|
||||
[
|
||||
0x01, // Version and flags (version 1, no flags)
|
||||
0x1B, // Prologue size
|
||||
0x04, // Unwind code count (3 for stack alloc, 1 for push reg)
|
||||
0x00, // Frame register + offset (no frame register)
|
||||
0x1B, // Prolog offset
|
||||
0x11, // Operation 1 (large stack alloc), size is unscaled 32-bits (info = 1)
|
||||
0x40, // Byte 1 of size
|
||||
0x42, // Byte 2 of size
|
||||
0x0F, // Byte 3 of size
|
||||
0x00, // Byte 4 of size (size is 0xF4240 = 1000000 bytes)
|
||||
0x02, // Prolog offset
|
||||
0x50, // Operation 0 (push nonvolatile register), reg = 5 (RBP)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
|
||||
let mut func =
|
||||
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
if let Some(stack_slot) = stack_slot {
|
||||
func.stack_slots.push(stack_slot);
|
||||
}
|
||||
|
||||
func
|
||||
}
|
||||
}
|
||||
@@ -84,15 +84,6 @@ pub(crate) mod aarch64;
|
||||
#[cfg(feature = "s390x")]
|
||||
mod s390x;
|
||||
|
||||
#[cfg(any(feature = "x86", feature = "riscv"))]
|
||||
mod legacy;
|
||||
|
||||
#[cfg(feature = "x86")]
|
||||
use legacy::x86;
|
||||
|
||||
#[cfg(feature = "riscv")]
|
||||
use legacy::riscv;
|
||||
|
||||
pub mod unwind;
|
||||
|
||||
mod call_conv;
|
||||
@@ -120,57 +111,18 @@ macro_rules! isa_builder {
|
||||
}};
|
||||
}
|
||||
|
||||
/// The "variant" for a given target. On one platform (x86-64), we have two
|
||||
/// backends, the "old" and "new" one; the new one is the default if included
|
||||
/// in the build configuration and not otherwise specified.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BackendVariant {
|
||||
/// Any backend available.
|
||||
Any,
|
||||
/// A "legacy" backend: one that operates using legalizations and encodings.
|
||||
Legacy,
|
||||
/// A backend built on `MachInst`s and the `VCode` framework.
|
||||
MachInst,
|
||||
}
|
||||
|
||||
impl Default for BackendVariant {
|
||||
fn default() -> Self {
|
||||
BackendVariant::Any
|
||||
}
|
||||
}
|
||||
|
||||
/// Look for an ISA for the given `triple`, selecting the backend variant given
|
||||
/// by `variant` if available.
|
||||
pub fn lookup_variant(triple: Triple, variant: BackendVariant) -> Result<Builder, LookupError> {
|
||||
match (triple.architecture, variant) {
|
||||
(Architecture::Riscv32 { .. }, _) | (Architecture::Riscv64 { .. }, _) => {
|
||||
isa_builder!(riscv, (feature = "riscv"), triple)
|
||||
}
|
||||
(Architecture::X86_64, BackendVariant::Legacy) => {
|
||||
isa_builder!(x86, (feature = "x86"), triple)
|
||||
}
|
||||
(Architecture::X86_64, BackendVariant::MachInst) => {
|
||||
isa_builder!(x64, (feature = "x86"), triple)
|
||||
}
|
||||
#[cfg(not(feature = "old-x86-backend"))]
|
||||
(Architecture::X86_64, BackendVariant::Any) => {
|
||||
isa_builder!(x64, (feature = "x86"), triple)
|
||||
}
|
||||
#[cfg(feature = "old-x86-backend")]
|
||||
(Architecture::X86_64, BackendVariant::Any) => {
|
||||
isa_builder!(x86, (feature = "x86"), triple)
|
||||
}
|
||||
(Architecture::Arm { .. }, _) => isa_builder!(arm32, (feature = "arm32"), triple),
|
||||
(Architecture::Aarch64 { .. }, _) => isa_builder!(aarch64, (feature = "arm64"), triple),
|
||||
(Architecture::S390x { .. }, _) => isa_builder!(s390x, (feature = "s390x"), triple),
|
||||
_ => Err(LookupError::Unsupported),
|
||||
}
|
||||
}
|
||||
|
||||
/// Look for an ISA for the given `triple`.
|
||||
/// Return a builder that can create a corresponding `TargetIsa`.
|
||||
pub fn lookup(triple: Triple) -> Result<Builder, LookupError> {
|
||||
lookup_variant(triple, BackendVariant::Any)
|
||||
match triple.architecture {
|
||||
Architecture::X86_64 => {
|
||||
isa_builder!(x64, (feature = "x86"), triple)
|
||||
}
|
||||
Architecture::Arm { .. } => isa_builder!(arm32, (feature = "arm32"), triple),
|
||||
Architecture::Aarch64 { .. } => isa_builder!(aarch64, (feature = "arm64"), triple),
|
||||
Architecture::S390x { .. } => isa_builder!(s390x, (feature = "s390x"), triple),
|
||||
_ => Err(LookupError::Unsupported),
|
||||
}
|
||||
}
|
||||
|
||||
/// Look for a supported ISA with the given `name`.
|
||||
@@ -292,11 +244,6 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
|
||||
/// Get the ISA-dependent flag values that were used to make this trait object.
|
||||
fn isa_flags(&self) -> Vec<settings::Value>;
|
||||
|
||||
/// Get the variant of this ISA (Legacy or MachInst).
|
||||
fn variant(&self) -> BackendVariant {
|
||||
BackendVariant::Legacy
|
||||
}
|
||||
|
||||
/// Hashes all flags, both ISA-independent and ISA-specific, into the
|
||||
/// specified hasher.
|
||||
fn hash_all_flags(&self, hasher: &mut dyn Hasher);
|
||||
|
||||
@@ -2962,45 +2962,6 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
|
||||
| Opcode::IfcmpImm => {
|
||||
panic!("ALU+imm and ALU+carry ops should not appear here!");
|
||||
}
|
||||
|
||||
#[cfg(feature = "x86")]
|
||||
Opcode::X86Udivmodx
|
||||
| Opcode::X86Sdivmodx
|
||||
| Opcode::X86Umulx
|
||||
| Opcode::X86Smulx
|
||||
| Opcode::X86Cvtt2si
|
||||
| Opcode::X86Fmin
|
||||
| Opcode::X86Fmax
|
||||
| Opcode::X86Push
|
||||
| Opcode::X86Pop
|
||||
| Opcode::X86Bsr
|
||||
| Opcode::X86Bsf
|
||||
| Opcode::X86Pblendw
|
||||
| Opcode::X86Pshufd
|
||||
| Opcode::X86Pshufb
|
||||
| Opcode::X86Pextr
|
||||
| Opcode::X86Pinsr
|
||||
| Opcode::X86Insertps
|
||||
| Opcode::X86Movsd
|
||||
| Opcode::X86Movlhps
|
||||
| Opcode::X86Psll
|
||||
| Opcode::X86Psrl
|
||||
| Opcode::X86Psra
|
||||
| Opcode::X86Ptest
|
||||
| Opcode::X86Pmaxs
|
||||
| Opcode::X86Pmaxu
|
||||
| Opcode::X86Pmins
|
||||
| Opcode::X86Pminu
|
||||
| Opcode::X86Pmullq
|
||||
| Opcode::X86Pmuludq
|
||||
| Opcode::X86Punpckh
|
||||
| Opcode::X86Punpckl
|
||||
| Opcode::X86Vcvtudq2ps
|
||||
| Opcode::X86Palignr
|
||||
| Opcode::X86ElfTlsGetAddr
|
||||
| Opcode::X86MachoTlsGetAddr => {
|
||||
panic!("x86-specific opcode in supposedly arch-neutral IR!");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -24,77 +24,6 @@ pub enum UnwindInfo {
|
||||
SystemV(systemv::UnwindInfo),
|
||||
}
|
||||
|
||||
/// Intermediate representation for the unwind information
|
||||
/// generated by a backend.
|
||||
pub mod input {
|
||||
use crate::binemit::CodeOffset;
|
||||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "enable-serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Elementary operation in the unwind operations.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
||||
pub enum UnwindCode<Reg> {
|
||||
/// Defines that a register is saved at the specified offset.
|
||||
SaveRegister {
|
||||
/// The saved register.
|
||||
reg: Reg,
|
||||
/// The specified offset relative to the stack pointer.
|
||||
stack_offset: u32,
|
||||
},
|
||||
/// Defines that a register is as defined before call.
|
||||
RestoreRegister {
|
||||
/// The restored register.
|
||||
reg: Reg,
|
||||
},
|
||||
/// The stack pointer was adjusted to allocate the stack.
|
||||
StackAlloc {
|
||||
/// Size to allocate.
|
||||
size: u32,
|
||||
},
|
||||
/// The stack pointer was adjusted to free the stack.
|
||||
StackDealloc {
|
||||
/// Size to deallocate.
|
||||
size: u32,
|
||||
},
|
||||
/// The alternative register was assigned as frame pointer base.
|
||||
SetFramePointer {
|
||||
/// The specified register.
|
||||
reg: Reg,
|
||||
},
|
||||
/// Restores a frame pointer base to default register.
|
||||
RestoreFramePointer,
|
||||
/// Saves the state.
|
||||
RememberState,
|
||||
/// Restores the state.
|
||||
RestoreState,
|
||||
/// On aarch64 ARMv8.3+ devices, enables or disables pointer authentication.
|
||||
Aarch64SetPointerAuth {
|
||||
/// Whether return addresses (hold in LR) contain a pointer-authentication code.
|
||||
return_addresses: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Unwind information as generated by a backend.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
||||
pub struct UnwindInfo<Reg> {
|
||||
/// Size of the prologue.
|
||||
pub prologue_size: CodeOffset,
|
||||
/// Unwind codes for prologue.
|
||||
pub prologue_unwind_codes: Vec<(CodeOffset, UnwindCode<Reg>)>,
|
||||
/// Unwind codes for epilogues.
|
||||
pub epilogues_unwind_codes: Vec<Vec<(CodeOffset, UnwindCode<Reg>)>>,
|
||||
/// Entire function size.
|
||||
pub function_size: CodeOffset,
|
||||
/// Platform word size in bytes.
|
||||
pub word_size: u8,
|
||||
/// Initial stack pointer offset.
|
||||
pub initial_sp_offset: u8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwind pseudoinstruction used in VCode backends: represents that
|
||||
/// at the present location, an action has just been taken.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! System V ABI unwind information.
|
||||
|
||||
use crate::binemit::CodeOffset;
|
||||
use crate::isa::unwind::input;
|
||||
use crate::isa::unwind::UnwindInst;
|
||||
use crate::result::{CodegenError, CodegenResult};
|
||||
use alloc::vec::Vec;
|
||||
@@ -259,66 +258,6 @@ pub(crate) fn create_unwind_info_from_insts<MR: RegisterMapper<regalloc::Reg>>(
|
||||
}
|
||||
|
||||
impl UnwindInfo {
|
||||
// TODO: remove `build()` below when old backend is removed. The new backend uses a simpler
|
||||
// approach in `create_unwind_info_from_insts()` above.
|
||||
|
||||
pub(crate) fn build<'b, Reg: PartialEq + Copy>(
|
||||
unwind: input::UnwindInfo<Reg>,
|
||||
map_reg: &'b dyn RegisterMapper<Reg>,
|
||||
) -> CodegenResult<Self> {
|
||||
use input::UnwindCode;
|
||||
let mut builder = InstructionBuilder::new(unwind.initial_sp_offset, map_reg);
|
||||
|
||||
for (offset, c) in unwind.prologue_unwind_codes.iter().chain(
|
||||
unwind
|
||||
.epilogues_unwind_codes
|
||||
.iter()
|
||||
.map(|c| c.iter())
|
||||
.flatten(),
|
||||
) {
|
||||
match c {
|
||||
UnwindCode::SaveRegister { reg, stack_offset } => {
|
||||
builder
|
||||
.save_reg(*offset, *reg, *stack_offset)
|
||||
.map_err(CodegenError::RegisterMappingError)?;
|
||||
}
|
||||
UnwindCode::StackAlloc { size } => {
|
||||
builder.adjust_sp_down_imm(*offset, *size as i64);
|
||||
}
|
||||
UnwindCode::StackDealloc { size } => {
|
||||
builder.adjust_sp_up_imm(*offset, *size as i64);
|
||||
}
|
||||
UnwindCode::RestoreRegister { reg } => {
|
||||
builder
|
||||
.restore_reg(*offset, *reg)
|
||||
.map_err(CodegenError::RegisterMappingError)?;
|
||||
}
|
||||
UnwindCode::SetFramePointer { reg } => {
|
||||
builder
|
||||
.set_cfa_reg(*offset, *reg)
|
||||
.map_err(CodegenError::RegisterMappingError)?;
|
||||
}
|
||||
UnwindCode::RestoreFramePointer => {
|
||||
builder.restore_cfa(*offset);
|
||||
}
|
||||
UnwindCode::RememberState => {
|
||||
builder.remember_state(*offset);
|
||||
}
|
||||
UnwindCode::RestoreState => {
|
||||
builder.restore_state(*offset);
|
||||
}
|
||||
UnwindCode::Aarch64SetPointerAuth { return_addresses } => {
|
||||
builder.set_aarch64_pauth(*offset, *return_addresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instructions = builder.instructions;
|
||||
let len = unwind.function_size;
|
||||
|
||||
Ok(Self { instructions, len })
|
||||
}
|
||||
|
||||
/// Converts the unwind information into a `FrameDescriptionEntry`.
|
||||
pub fn to_fde(&self, address: Address) -> gimli::write::FrameDescriptionEntry {
|
||||
let mut fde = FrameDescriptionEntry::new(address, self.len);
|
||||
@@ -330,145 +269,3 @@ impl UnwindInfo {
|
||||
fde
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: delete the builder below when the old backend is removed.
|
||||
|
||||
struct InstructionBuilder<'a, Reg: PartialEq + Copy> {
|
||||
sp_offset: i32,
|
||||
frame_register: Option<Reg>,
|
||||
saved_state: Option<(i32, Option<Reg>)>,
|
||||
map_reg: &'a dyn RegisterMapper<Reg>,
|
||||
instructions: Vec<(u32, CallFrameInstruction)>,
|
||||
}
|
||||
|
||||
impl<'a, Reg: PartialEq + Copy> InstructionBuilder<'a, Reg> {
|
||||
fn new(sp_offset: u8, map_reg: &'a (dyn RegisterMapper<Reg> + 'a)) -> Self {
|
||||
Self {
|
||||
sp_offset: sp_offset as i32, // CFA offset starts at the specified offset to account for the return address on stack
|
||||
saved_state: None,
|
||||
frame_register: None,
|
||||
map_reg,
|
||||
instructions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn save_reg(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
reg: Reg,
|
||||
stack_offset: u32,
|
||||
) -> Result<(), RegisterMappingError> {
|
||||
// Pushes in the prologue are register saves, so record an offset of the save
|
||||
self.instructions.push((
|
||||
offset,
|
||||
CallFrameInstruction::Offset(
|
||||
self.map_reg.map(reg)?,
|
||||
stack_offset as i32 - self.sp_offset,
|
||||
),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adjust_sp_down_imm(&mut self, offset: u32, imm: i64) {
|
||||
assert!(imm <= core::u32::MAX as i64);
|
||||
|
||||
self.sp_offset += imm as i32;
|
||||
|
||||
// Don't adjust the CFA if we're using a frame pointer
|
||||
if self.frame_register.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.instructions
|
||||
.push((offset, CallFrameInstruction::CfaOffset(self.sp_offset)));
|
||||
}
|
||||
|
||||
fn adjust_sp_up_imm(&mut self, offset: u32, imm: i64) {
|
||||
assert!(imm <= core::u32::MAX as i64);
|
||||
|
||||
self.sp_offset -= imm as i32;
|
||||
|
||||
// Don't adjust the CFA if we're using a frame pointer
|
||||
if self.frame_register.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let cfa_inst_ofs = {
|
||||
// Scan to find and merge with CFA instruction with the same offset.
|
||||
let mut it = self.instructions.iter_mut();
|
||||
loop {
|
||||
match it.next_back() {
|
||||
Some((i_offset, i)) if *i_offset == offset => {
|
||||
if let CallFrameInstruction::Cfa(_, o) = i {
|
||||
break Some(o);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break None;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(o) = cfa_inst_ofs {
|
||||
// Update previous CFA instruction.
|
||||
*o = self.sp_offset;
|
||||
} else {
|
||||
// Add just CFA offset instruction.
|
||||
self.instructions
|
||||
.push((offset, CallFrameInstruction::CfaOffset(self.sp_offset)));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cfa_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> {
|
||||
self.instructions.push((
|
||||
offset,
|
||||
CallFrameInstruction::CfaRegister(self.map_reg.map(reg)?),
|
||||
));
|
||||
self.frame_register = Some(reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore_cfa(&mut self, offset: u32) {
|
||||
// Restore SP and its offset.
|
||||
self.instructions.push((
|
||||
offset,
|
||||
CallFrameInstruction::Cfa(self.map_reg.sp(), self.sp_offset),
|
||||
));
|
||||
self.frame_register = None;
|
||||
}
|
||||
|
||||
fn restore_reg(&mut self, offset: u32, reg: Reg) -> Result<(), RegisterMappingError> {
|
||||
// Pops in the epilogue are register restores, so record a "same value" for the register
|
||||
self.instructions.push((
|
||||
offset,
|
||||
CallFrameInstruction::SameValue(self.map_reg.map(reg)?),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remember_state(&mut self, offset: u32) {
|
||||
self.saved_state = Some((self.sp_offset, self.frame_register));
|
||||
|
||||
self.instructions
|
||||
.push((offset, CallFrameInstruction::RememberState));
|
||||
}
|
||||
|
||||
fn restore_state(&mut self, offset: u32) {
|
||||
let (sp_offset, frame_register) = self.saved_state.take().unwrap();
|
||||
self.sp_offset = sp_offset;
|
||||
self.frame_register = frame_register;
|
||||
|
||||
self.instructions
|
||||
.push((offset, CallFrameInstruction::RestoreState));
|
||||
}
|
||||
|
||||
fn set_aarch64_pauth(&mut self, offset: u32, return_addresses: bool) {
|
||||
self.instructions.push((
|
||||
offset,
|
||||
CallFrameInstruction::Aarch64SetPointerAuth { return_addresses },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Windows x64 ABI unwind information.
|
||||
|
||||
use crate::isa::unwind::input;
|
||||
use crate::result::{CodegenError, CodegenResult};
|
||||
use alloc::vec::Vec;
|
||||
use log::warn;
|
||||
@@ -259,76 +258,6 @@ impl UnwindInfo {
|
||||
.iter()
|
||||
.fold(0, |nodes, c| nodes + c.node_count())
|
||||
}
|
||||
|
||||
// TODO: remove `build()` below when old backend is removed. The new backend uses
|
||||
// a simpler approach in `create_unwind_info_from_insts()` below.
|
||||
|
||||
pub(crate) fn build<Reg: PartialEq + Copy + std::fmt::Debug, MR: RegisterMapper<Reg>>(
|
||||
unwind: input::UnwindInfo<Reg>,
|
||||
) -> CodegenResult<Self> {
|
||||
use crate::isa::unwind::input::UnwindCode as InputUnwindCode;
|
||||
|
||||
let word_size: u32 = unwind.word_size.into();
|
||||
let mut unwind_codes = Vec::new();
|
||||
for (offset, c) in unwind.prologue_unwind_codes.iter() {
|
||||
match c {
|
||||
InputUnwindCode::SaveRegister { reg, stack_offset } => {
|
||||
let reg = MR::map(*reg);
|
||||
let offset = ensure_unwind_offset(*offset)?;
|
||||
match reg {
|
||||
MappedRegister::Int(reg) => {
|
||||
// Attempt to convert sequence of the `InputUnwindCode`:
|
||||
// `StackAlloc { size = word_size }`, `SaveRegister { stack_offset: 0 }`
|
||||
// to the shorter `UnwindCode::PushRegister`.
|
||||
let push_reg_sequence = if let Some(UnwindCode::StackAlloc {
|
||||
instruction_offset: alloc_offset,
|
||||
size,
|
||||
}) = unwind_codes.last()
|
||||
{
|
||||
*size == word_size && offset == *alloc_offset && *stack_offset == 0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if push_reg_sequence {
|
||||
*unwind_codes.last_mut().unwrap() = UnwindCode::PushRegister {
|
||||
instruction_offset: offset,
|
||||
reg,
|
||||
};
|
||||
} else {
|
||||
unwind_codes.push(UnwindCode::SaveReg {
|
||||
instruction_offset: offset,
|
||||
reg,
|
||||
stack_offset: *stack_offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
MappedRegister::Xmm(reg) => {
|
||||
unwind_codes.push(UnwindCode::SaveXmm {
|
||||
instruction_offset: offset,
|
||||
reg,
|
||||
stack_offset: *stack_offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
InputUnwindCode::StackAlloc { size } => {
|
||||
unwind_codes.push(UnwindCode::StackAlloc {
|
||||
instruction_offset: ensure_unwind_offset(*offset)?,
|
||||
size: *size,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
flags: 0, // this assumes cranelift functions have no SEH handlers
|
||||
prologue_size: ensure_unwind_offset(unwind.prologue_size)?,
|
||||
frame_register: None,
|
||||
frame_register_offset: 0,
|
||||
unwind_codes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const UNWIND_RBP_REG: u8 = 5;
|
||||
|
||||
@@ -109,7 +109,6 @@ mod tests {
|
||||
use target_lexicon::triple;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(feature = "old-x86-backend", ignore)]
|
||||
fn test_simple_func() {
|
||||
let isa = lookup(triple!("x86_64"))
|
||||
.expect("expect x86 ISA")
|
||||
@@ -152,7 +151,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(feature = "old-x86-backend", ignore)]
|
||||
fn test_multi_return_func() {
|
||||
let isa = lookup(triple!("x86_64"))
|
||||
.expect("expect x86 ISA")
|
||||
|
||||
@@ -6900,44 +6900,6 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
|
||||
panic!("Branch opcode reached non-branch lowering logic!");
|
||||
}
|
||||
|
||||
Opcode::X86Udivmodx
|
||||
| Opcode::X86Sdivmodx
|
||||
| Opcode::X86Umulx
|
||||
| Opcode::X86Smulx
|
||||
| Opcode::X86Cvtt2si
|
||||
| Opcode::X86Fmin
|
||||
| Opcode::X86Fmax
|
||||
| Opcode::X86Push
|
||||
| Opcode::X86Pop
|
||||
| Opcode::X86Bsr
|
||||
| Opcode::X86Bsf
|
||||
| Opcode::X86Pblendw
|
||||
| Opcode::X86Pshufd
|
||||
| Opcode::X86Pshufb
|
||||
| Opcode::X86Pextr
|
||||
| Opcode::X86Pinsr
|
||||
| Opcode::X86Insertps
|
||||
| Opcode::X86Movsd
|
||||
| Opcode::X86Movlhps
|
||||
| Opcode::X86Palignr
|
||||
| Opcode::X86Psll
|
||||
| Opcode::X86Psrl
|
||||
| Opcode::X86Psra
|
||||
| Opcode::X86Ptest
|
||||
| Opcode::X86Pmaxs
|
||||
| Opcode::X86Pmaxu
|
||||
| Opcode::X86Pmins
|
||||
| Opcode::X86Pminu
|
||||
| Opcode::X86Pmullq
|
||||
| Opcode::X86Pmuludq
|
||||
| Opcode::X86Punpckh
|
||||
| Opcode::X86Punpckl
|
||||
| Opcode::X86Vcvtudq2ps
|
||||
| Opcode::X86ElfTlsGetAddr
|
||||
| Opcode::X86MachoTlsGetAddr => {
|
||||
panic!("x86-specific opcode in supposedly arch-neutral IR!");
|
||||
}
|
||||
|
||||
Opcode::Nop => {
|
||||
// Nothing.
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
//! Legalization of calls.
|
||||
//!
|
||||
//! This module exports the `expand_call` function which transforms a `call`
|
||||
//! instruction into `func_addr` and `call_indirect` instructions.
|
||||
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::flowgraph::ControlFlowGraph;
|
||||
use crate::ir::{self, InstBuilder};
|
||||
use crate::isa::TargetIsa;
|
||||
|
||||
/// Expand a `call` instruction. This lowers it to a `call_indirect`, which
|
||||
/// is only done if the ABI doesn't support direct calls.
|
||||
pub fn expand_call(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
// Unpack the instruction.
|
||||
let (func_ref, old_args) = match func.dfg[inst] {
|
||||
ir::InstructionData::Call {
|
||||
opcode,
|
||||
ref args,
|
||||
func_ref,
|
||||
} => {
|
||||
debug_assert_eq!(opcode, ir::Opcode::Call);
|
||||
(func_ref, args.clone())
|
||||
}
|
||||
_ => panic!("Wanted call: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
let ptr_ty = isa.pointer_type();
|
||||
|
||||
let sig = func.dfg.ext_funcs[func_ref].signature;
|
||||
|
||||
let callee = {
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
pos.ins().func_addr(ptr_ty, func_ref)
|
||||
};
|
||||
|
||||
let mut new_args = ir::ValueList::default();
|
||||
new_args.push(callee, &mut func.dfg.value_lists);
|
||||
for i in 0..old_args.len(&func.dfg.value_lists) {
|
||||
new_args.push(
|
||||
old_args.as_slice(&func.dfg.value_lists)[i],
|
||||
&mut func.dfg.value_lists,
|
||||
);
|
||||
}
|
||||
|
||||
func.dfg
|
||||
.replace(inst)
|
||||
.CallIndirect(ir::Opcode::CallIndirect, ptr_ty, sig, new_args);
|
||||
}
|
||||
@@ -13,32 +13,22 @@
|
||||
//! The legalizer does not deal with register allocation constraints. These constraints are derived
|
||||
//! from the encoding recipes, and solved later by the register allocator.
|
||||
|
||||
#[cfg(any(feature = "x86", feature = "riscv"))]
|
||||
use crate::bitset::BitSet;
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::flowgraph::ControlFlowGraph;
|
||||
use crate::ir::types::{I32, I64};
|
||||
use crate::ir::types::I32;
|
||||
use crate::ir::{self, InstBuilder, MemFlags};
|
||||
use crate::isa::TargetIsa;
|
||||
|
||||
#[cfg(any(feature = "x86", feature = "riscv"))]
|
||||
use crate::predicates;
|
||||
#[cfg(any(feature = "x86", feature = "riscv"))]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::timing;
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
mod boundary;
|
||||
mod call;
|
||||
mod globalvalue;
|
||||
mod heap;
|
||||
mod libcall;
|
||||
mod split;
|
||||
mod table;
|
||||
|
||||
#[cfg(any(feature = "x86", feature = "riscv"))]
|
||||
use self::call::expand_call;
|
||||
use self::globalvalue::expand_global_value;
|
||||
use self::heap::expand_heap_addr;
|
||||
pub(crate) use self::libcall::expand_as_libcall;
|
||||
@@ -329,12 +319,6 @@ pub fn simple_legalize(func: &mut ir::Function, cfg: &mut ControlFlowGraph, isa:
|
||||
}
|
||||
}
|
||||
|
||||
// Include legalization patterns that were generated by `gen_legalizer.rs` from the
|
||||
// `TransformGroup` in `cranelift-codegen/meta/shared/legalize.rs`.
|
||||
//
|
||||
// Concretely, this defines private functions `narrow()`, and `expand()`.
|
||||
include!(concat!(env!("OUT_DIR"), "/legalizer.rs"));
|
||||
|
||||
/// Custom expansion for conditional trap instructions.
|
||||
/// TODO: Add CFG support to the Rust DSL patterns so we won't have to do this.
|
||||
fn expand_cond_trap(
|
||||
@@ -412,189 +396,6 @@ fn expand_cond_trap(
|
||||
cfg.recompute_block(pos.func, new_block_trap);
|
||||
}
|
||||
|
||||
/// Jump tables.
|
||||
fn expand_br_table(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
if isa.flags().enable_jump_tables() {
|
||||
expand_br_table_jt(inst, func, cfg, isa);
|
||||
} else {
|
||||
expand_br_table_conds(inst, func, cfg, isa);
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand br_table to jump table.
|
||||
fn expand_br_table_jt(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
use crate::ir::condcodes::IntCC;
|
||||
|
||||
let (arg, default_block, table) = match func.dfg[inst] {
|
||||
ir::InstructionData::BranchTable {
|
||||
opcode: ir::Opcode::BrTable,
|
||||
arg,
|
||||
destination,
|
||||
table,
|
||||
} => (arg, destination, table),
|
||||
_ => panic!("Expected br_table: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
// Rewrite:
|
||||
//
|
||||
// br_table $idx, default_block, $jt
|
||||
//
|
||||
// To:
|
||||
//
|
||||
// $oob = ifcmp_imm $idx, len($jt)
|
||||
// brif uge $oob, default_block
|
||||
// jump fallthrough_block
|
||||
//
|
||||
// fallthrough_block:
|
||||
// $base = jump_table_base.i64 $jt
|
||||
// $rel_addr = jump_table_entry.i64 $idx, $base, 4, $jt
|
||||
// $addr = iadd $base, $rel_addr
|
||||
// indirect_jump_table_br $addr, $jt
|
||||
|
||||
let block = func.layout.pp_block(inst);
|
||||
let jump_table_block = func.dfg.make_block();
|
||||
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
// Bounds check.
|
||||
let table_size = pos.func.jump_tables[table].len() as i64;
|
||||
let oob = pos
|
||||
.ins()
|
||||
.icmp_imm(IntCC::UnsignedGreaterThanOrEqual, arg, table_size);
|
||||
|
||||
pos.ins().brnz(oob, default_block, &[]);
|
||||
pos.ins().jump(jump_table_block, &[]);
|
||||
pos.insert_block(jump_table_block);
|
||||
|
||||
let addr_ty = isa.pointer_type();
|
||||
|
||||
let arg = if pos.func.dfg.value_type(arg) == addr_ty {
|
||||
arg
|
||||
} else {
|
||||
pos.ins().uextend(addr_ty, arg)
|
||||
};
|
||||
|
||||
let base_addr = pos.ins().jump_table_base(addr_ty, table);
|
||||
let entry = pos
|
||||
.ins()
|
||||
.jump_table_entry(arg, base_addr, I32.bytes() as u8, table);
|
||||
|
||||
let addr = pos.ins().iadd(base_addr, entry);
|
||||
pos.ins().indirect_jump_table_br(addr, table);
|
||||
|
||||
pos.remove_inst();
|
||||
cfg.recompute_block(pos.func, block);
|
||||
cfg.recompute_block(pos.func, jump_table_block);
|
||||
}
|
||||
|
||||
/// Expand br_table to series of conditionals.
|
||||
fn expand_br_table_conds(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
_isa: &dyn TargetIsa,
|
||||
) {
|
||||
use crate::ir::condcodes::IntCC;
|
||||
|
||||
let (arg, default_block, table) = match func.dfg[inst] {
|
||||
ir::InstructionData::BranchTable {
|
||||
opcode: ir::Opcode::BrTable,
|
||||
arg,
|
||||
destination,
|
||||
table,
|
||||
} => (arg, destination, table),
|
||||
_ => panic!("Expected br_table: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
let block = func.layout.pp_block(inst);
|
||||
|
||||
// This is a poor man's jump table using just a sequence of conditional branches.
|
||||
let table_size = func.jump_tables[table].len();
|
||||
let mut cond_failed_block = vec![];
|
||||
if table_size >= 1 {
|
||||
cond_failed_block = alloc::vec::Vec::with_capacity(table_size - 1);
|
||||
for _ in 0..table_size - 1 {
|
||||
cond_failed_block.push(func.dfg.make_block());
|
||||
}
|
||||
}
|
||||
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
// Ignore the lint for this loop as the range needs to be 0 to table_size
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..table_size {
|
||||
let dest = pos.func.jump_tables[table].as_slice()[i];
|
||||
let t = pos.ins().icmp_imm(IntCC::Equal, arg, i as i64);
|
||||
pos.ins().brnz(t, dest, &[]);
|
||||
// Jump to the next case.
|
||||
if i < table_size - 1 {
|
||||
let block = cond_failed_block[i];
|
||||
pos.ins().jump(block, &[]);
|
||||
pos.insert_block(block);
|
||||
}
|
||||
}
|
||||
|
||||
// `br_table` jumps to the default destination if nothing matches
|
||||
pos.ins().jump(default_block, &[]);
|
||||
|
||||
pos.remove_inst();
|
||||
cfg.recompute_block(pos.func, block);
|
||||
for failed_block in cond_failed_block.into_iter() {
|
||||
cfg.recompute_block(pos.func, failed_block);
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand the select instruction.
|
||||
///
|
||||
/// Conditional moves are available in some ISAs for some register classes. The remaining selects
|
||||
/// are handled by a branch.
|
||||
fn expand_select(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
_isa: &dyn TargetIsa,
|
||||
) {
|
||||
let (ctrl, tval, fval) = match func.dfg[inst] {
|
||||
ir::InstructionData::Ternary {
|
||||
opcode: ir::Opcode::Select,
|
||||
args,
|
||||
} => (args[0], args[1], args[2]),
|
||||
_ => panic!("Expected select: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
// Replace `result = select ctrl, tval, fval` with:
|
||||
//
|
||||
// brnz ctrl, new_block(tval)
|
||||
// jump new_block(fval)
|
||||
// new_block(result):
|
||||
let old_block = func.layout.pp_block(inst);
|
||||
let result = func.dfg.first_result(inst);
|
||||
func.dfg.clear_results(inst);
|
||||
let new_block = func.dfg.make_block();
|
||||
func.dfg.attach_block_param(new_block, result);
|
||||
|
||||
func.dfg.replace(inst).brnz(ctrl, new_block, &[tval]);
|
||||
let mut pos = FuncCursor::new(func).after_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
pos.ins().jump(new_block, &[fval]);
|
||||
pos.insert_block(new_block);
|
||||
|
||||
cfg.recompute_block(pos.func, new_block);
|
||||
cfg.recompute_block(pos.func, old_block);
|
||||
}
|
||||
|
||||
fn expand_br_icmp(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
@@ -629,34 +430,6 @@ fn expand_br_icmp(
|
||||
cfg.recompute_block(pos.func, old_block);
|
||||
}
|
||||
|
||||
/// Expand illegal `f32const` and `f64const` instructions.
|
||||
fn expand_fconst(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
_isa: &dyn TargetIsa,
|
||||
) {
|
||||
let ty = func.dfg.value_type(func.dfg.first_result(inst));
|
||||
debug_assert!(!ty.is_vector(), "Only scalar fconst supported: {}", ty);
|
||||
|
||||
// In the future, we may want to generate constant pool entries for these constants, but for
|
||||
// now use an `iconst` and a bit cast.
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
let ival = match pos.func.dfg[inst] {
|
||||
ir::InstructionData::UnaryIeee32 {
|
||||
opcode: ir::Opcode::F32const,
|
||||
imm,
|
||||
} => pos.ins().iconst(ir::types::I32, i64::from(imm.bits())),
|
||||
ir::InstructionData::UnaryIeee64 {
|
||||
opcode: ir::Opcode::F64const,
|
||||
imm,
|
||||
} => pos.ins().iconst(ir::types::I64, imm.bits() as i64),
|
||||
_ => panic!("Expected fconst: {}", pos.func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
pos.func.dfg.replace(inst).bitcast(ty, ival);
|
||||
}
|
||||
|
||||
/// Expand illegal `stack_load` instructions.
|
||||
fn expand_stack_load(
|
||||
inst: ir::Inst,
|
||||
@@ -722,171 +495,3 @@ fn expand_stack_store(
|
||||
mflags.set_aligned();
|
||||
pos.func.dfg.replace(inst).store(mflags, val, addr, 0);
|
||||
}
|
||||
|
||||
/// Split a load into two parts before `iconcat`ing the result together.
|
||||
fn narrow_load(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
let (ptr, offset, flags) = match pos.func.dfg[inst] {
|
||||
ir::InstructionData::Load {
|
||||
opcode: ir::Opcode::Load,
|
||||
arg,
|
||||
offset,
|
||||
flags,
|
||||
} => (arg, offset, flags),
|
||||
_ => panic!("Expected load: {}", pos.func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
let res_ty = pos.func.dfg.ctrl_typevar(inst);
|
||||
let small_ty = res_ty.half_width().expect("Can't narrow load");
|
||||
|
||||
let al = pos.ins().load(small_ty, flags, ptr, offset);
|
||||
let ah = pos.ins().load(
|
||||
small_ty,
|
||||
flags,
|
||||
ptr,
|
||||
offset.try_add_i64(8).expect("load offset overflow"),
|
||||
);
|
||||
let (al, ah) = match flags.endianness(isa.endianness()) {
|
||||
ir::Endianness::Little => (al, ah),
|
||||
ir::Endianness::Big => (ah, al),
|
||||
};
|
||||
pos.func.dfg.replace(inst).iconcat(al, ah);
|
||||
}
|
||||
|
||||
/// Split a store into two parts after `isplit`ing the value.
|
||||
fn narrow_store(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
let (val, ptr, offset, flags) = match pos.func.dfg[inst] {
|
||||
ir::InstructionData::Store {
|
||||
opcode: ir::Opcode::Store,
|
||||
args,
|
||||
offset,
|
||||
flags,
|
||||
} => (args[0], args[1], offset, flags),
|
||||
_ => panic!("Expected store: {}", pos.func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
let (al, ah) = pos.ins().isplit(val);
|
||||
let (al, ah) = match flags.endianness(isa.endianness()) {
|
||||
ir::Endianness::Little => (al, ah),
|
||||
ir::Endianness::Big => (ah, al),
|
||||
};
|
||||
pos.ins().store(flags, al, ptr, offset);
|
||||
pos.ins().store(
|
||||
flags,
|
||||
ah,
|
||||
ptr,
|
||||
offset.try_add_i64(8).expect("store offset overflow"),
|
||||
);
|
||||
pos.remove_inst();
|
||||
}
|
||||
|
||||
/// Expands an illegal iconst value by splitting it into two.
|
||||
fn narrow_iconst(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
isa: &dyn TargetIsa,
|
||||
) {
|
||||
let imm: i64 = if let ir::InstructionData::UnaryImm {
|
||||
opcode: ir::Opcode::Iconst,
|
||||
imm,
|
||||
} = &func.dfg[inst]
|
||||
{
|
||||
(*imm).into()
|
||||
} else {
|
||||
panic!("unexpected instruction in narrow_iconst");
|
||||
};
|
||||
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
let ty = pos.func.dfg.ctrl_typevar(inst);
|
||||
if isa.pointer_bits() == 32 && ty == I64 {
|
||||
let low = pos.ins().iconst(I32, imm & 0xffffffff);
|
||||
let high = pos.ins().iconst(I32, imm >> 32);
|
||||
// The instruction has as many results as iconcat, so no need to replace them.
|
||||
pos.func.dfg.replace(inst).iconcat(low, high);
|
||||
return;
|
||||
}
|
||||
|
||||
unimplemented!("missing encoding or legalization for iconst.{:?}", ty);
|
||||
}
|
||||
|
||||
fn narrow_icmp_imm(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
_cfg: &mut ControlFlowGraph,
|
||||
_isa: &dyn TargetIsa,
|
||||
) {
|
||||
use crate::ir::condcodes::{CondCode, IntCC};
|
||||
|
||||
let (arg, cond, imm): (ir::Value, IntCC, i64) = match func.dfg[inst] {
|
||||
ir::InstructionData::IntCompareImm {
|
||||
opcode: ir::Opcode::IcmpImm,
|
||||
arg,
|
||||
cond,
|
||||
imm,
|
||||
} => (arg, cond, imm.into()),
|
||||
_ => panic!("unexpected instruction in narrow_icmp_imm"),
|
||||
};
|
||||
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
let ty = pos.func.dfg.ctrl_typevar(inst);
|
||||
let ty_half = ty.half_width().unwrap();
|
||||
|
||||
let mask = ((1u128 << ty_half.bits()) - 1) as i64;
|
||||
let imm_low = pos.ins().iconst(ty_half, imm & mask);
|
||||
let imm_high = pos.ins().iconst(
|
||||
ty_half,
|
||||
imm.checked_shr(ty_half.bits().into()).unwrap_or(0) & mask,
|
||||
);
|
||||
let (arg_low, arg_high) = pos.ins().isplit(arg);
|
||||
|
||||
match cond {
|
||||
IntCC::Equal => {
|
||||
let res_low = pos.ins().icmp(cond, arg_low, imm_low);
|
||||
let res_high = pos.ins().icmp(cond, arg_high, imm_high);
|
||||
pos.func.dfg.replace(inst).band(res_low, res_high);
|
||||
}
|
||||
IntCC::NotEqual => {
|
||||
let res_low = pos.ins().icmp(cond, arg_low, imm_low);
|
||||
let res_high = pos.ins().icmp(cond, arg_high, imm_high);
|
||||
pos.func.dfg.replace(inst).bor(res_low, res_high);
|
||||
}
|
||||
IntCC::SignedGreaterThan
|
||||
| IntCC::SignedGreaterThanOrEqual
|
||||
| IntCC::SignedLessThan
|
||||
| IntCC::SignedLessThanOrEqual
|
||||
| IntCC::UnsignedGreaterThan
|
||||
| IntCC::UnsignedGreaterThanOrEqual
|
||||
| IntCC::UnsignedLessThan
|
||||
| IntCC::UnsignedLessThanOrEqual => {
|
||||
let b1 = pos.ins().icmp(cond.without_equal(), arg_high, imm_high);
|
||||
let b2 = pos
|
||||
.ins()
|
||||
.icmp(cond.inverse().without_equal(), arg_high, imm_high);
|
||||
let b3 = pos.ins().icmp(cond.unsigned(), arg_low, imm_low);
|
||||
let c1 = pos.ins().bnot(b2);
|
||||
let c2 = pos.ins().band(c1, b3);
|
||||
pos.func.dfg.replace(inst).bor(b1, c2);
|
||||
}
|
||||
_ => unimplemented!("missing legalization for condition {:?}", cond),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
use crate::binemit;
|
||||
use crate::ir;
|
||||
use crate::isa::{
|
||||
BackendVariant, EncInfo, Encoding, Encodings, Legalize, RegClass, RegInfo, TargetIsa,
|
||||
};
|
||||
use crate::isa::{EncInfo, Encoding, Encodings, Legalize, RegClass, RegInfo, TargetIsa};
|
||||
use crate::machinst::*;
|
||||
use crate::regalloc::RegisterSet;
|
||||
use crate::settings::{self, Flags};
|
||||
@@ -64,10 +62,6 @@ impl TargetIsa for TargetIsaAdapter {
|
||||
self.backend.isa_flags()
|
||||
}
|
||||
|
||||
fn variant(&self) -> BackendVariant {
|
||||
BackendVariant::MachInst
|
||||
}
|
||||
|
||||
fn hash_all_flags(&self, hasher: &mut dyn Hasher) {
|
||||
self.backend.hash_all_flags(hasher);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
; Test the legalization of function signatures for RV32E.
|
||||
test legalizer
|
||||
target riscv32 enable_e
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %f() {
|
||||
; Spilling into the stack args after %x15 since %16 and up are not
|
||||
; available in RV32E.
|
||||
sig0 = (i64, i64, i64, i64) -> i64 system_v
|
||||
; check: sig0 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [0], i32 [4]) -> i32 [%x10], i32 [%x11] system_v
|
||||
block0:
|
||||
return
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
; Test the legalization of function signatures.
|
||||
test legalizer
|
||||
target riscv32
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %f() {
|
||||
sig0 = (i32) -> i32 system_v
|
||||
; check: sig0 = (i32 [%x10]) -> i32 [%x10] system_v
|
||||
|
||||
sig1 = (i64) -> b1 system_v
|
||||
; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v
|
||||
|
||||
; The i64 argument must go in an even-odd register pair.
|
||||
sig2 = (f32, i64) -> f64 system_v
|
||||
; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v
|
||||
|
||||
; Spilling into the stack args.
|
||||
sig3 = (f64, f64, f64, f64, f64, f64, f64, i64) -> f64 system_v
|
||||
; check: sig3 = (f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] system_v
|
||||
|
||||
; Splitting vectors.
|
||||
sig4 = (i32x4) system_v
|
||||
; check: sig4 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) system_v
|
||||
|
||||
; Splitting vectors, then splitting ints.
|
||||
sig5 = (i64x4) system_v
|
||||
; check: sig5 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) system_v
|
||||
|
||||
block0:
|
||||
return
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
; Binary emission of 32-bit code.
|
||||
test binemit
|
||||
target riscv32
|
||||
|
||||
function %RV32I(i32 link [%x1]) -> i32 link [%x1] {
|
||||
sig0 = ()
|
||||
fn0 = %foo()
|
||||
|
||||
block0(v9999: i32):
|
||||
[-,%x10] v1 = iconst.i32 1
|
||||
[-,%x21] v2 = iconst.i32 2
|
||||
|
||||
; Integer Register-Register Operations.
|
||||
; add
|
||||
[-,%x7] v10 = iadd v1, v2 ; bin: 015503b3
|
||||
[-,%x16] v11 = iadd v2, v1 ; bin: 00aa8833
|
||||
; sub
|
||||
[-,%x7] v12 = isub v1, v2 ; bin: 415503b3
|
||||
[-,%x16] v13 = isub v2, v1 ; bin: 40aa8833
|
||||
; and
|
||||
[-,%x7] v20 = band v1, v2 ; bin: 015573b3
|
||||
[-,%x16] v21 = band v2, v1 ; bin: 00aaf833
|
||||
; or
|
||||
[-,%x7] v22 = bor v1, v2 ; bin: 015563b3
|
||||
[-,%x16] v23 = bor v2, v1 ; bin: 00aae833
|
||||
; xor
|
||||
[-,%x7] v24 = bxor v1, v2 ; bin: 015543b3
|
||||
[-,%x16] v25 = bxor v2, v1 ; bin: 00aac833
|
||||
; sll
|
||||
[-,%x7] v30 = ishl v1, v2 ; bin: 015513b3
|
||||
[-,%x16] v31 = ishl v2, v1 ; bin: 00aa9833
|
||||
; srl
|
||||
[-,%x7] v32 = ushr v1, v2 ; bin: 015553b3
|
||||
[-,%x16] v33 = ushr v2, v1 ; bin: 00aad833
|
||||
; sra
|
||||
[-,%x7] v34 = sshr v1, v2 ; bin: 415553b3
|
||||
[-,%x16] v35 = sshr v2, v1 ; bin: 40aad833
|
||||
; slt
|
||||
[-,%x7] v42 = icmp slt v1, v2 ; bin: 015523b3
|
||||
[-,%x16] v43 = icmp slt v2, v1 ; bin: 00aaa833
|
||||
; sltu
|
||||
[-,%x7] v44 = icmp ult v1, v2 ; bin: 015533b3
|
||||
[-,%x16] v45 = icmp ult v2, v1 ; bin: 00aab833
|
||||
|
||||
; Integer Register-Immediate Instructions
|
||||
|
||||
; addi
|
||||
[-,%x7] v100 = iadd_imm v1, 1000 ; bin: 3e850393
|
||||
[-,%x16] v101 = iadd_imm v2, -905 ; bin: c77a8813
|
||||
; andi
|
||||
[-,%x7] v110 = band_imm v1, 1000 ; bin: 3e857393
|
||||
[-,%x16] v111 = band_imm v2, -905 ; bin: c77af813
|
||||
; ori
|
||||
[-,%x7] v112 = bor_imm v1, 1000 ; bin: 3e856393
|
||||
[-,%x16] v113 = bor_imm v2, -905 ; bin: c77ae813
|
||||
; xori
|
||||
[-,%x7] v114 = bxor_imm v1, 1000 ; bin: 3e854393
|
||||
[-,%x16] v115 = bxor_imm v2, -905 ; bin: c77ac813
|
||||
|
||||
; slli
|
||||
[-,%x7] v120 = ishl_imm v1, 31 ; bin: 01f51393
|
||||
[-,%x16] v121 = ishl_imm v2, 8 ; bin: 008a9813
|
||||
; srli
|
||||
[-,%x7] v122 = ushr_imm v1, 31 ; bin: 01f55393
|
||||
[-,%x16] v123 = ushr_imm v2, 8 ; bin: 008ad813
|
||||
; srai
|
||||
[-,%x7] v124 = sshr_imm v1, 31 ; bin: 41f55393
|
||||
[-,%x16] v125 = sshr_imm v2, 8 ; bin: 408ad813
|
||||
|
||||
; slti
|
||||
[-,%x7] v130 = icmp_imm slt v1, 1000 ; bin: 3e852393
|
||||
[-,%x16] v131 = icmp_imm slt v2, -905 ; bin: c77aa813
|
||||
; sltiu
|
||||
[-,%x7] v132 = icmp_imm ult v1, 1000 ; bin: 3e853393
|
||||
[-,%x16] v133 = icmp_imm ult v2, -905 ; bin: c77ab813
|
||||
|
||||
; lui
|
||||
[-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7
|
||||
[-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837
|
||||
; addi
|
||||
[-,%x7] v142 = iconst.i32 1000 ; bin: 3e800393
|
||||
[-,%x16] v143 = iconst.i32 -905 ; bin: c7700813
|
||||
|
||||
; Copies alias to iadd_imm.
|
||||
[-,%x7] v150 = copy v1 ; bin: 00050393
|
||||
[-,%x16] v151 = copy v2 ; bin: 000a8813
|
||||
|
||||
; Control Transfer Instructions
|
||||
|
||||
; jal %x1, fn0
|
||||
call fn0() ; bin: Call(%foo) 000000ef
|
||||
|
||||
; jalr %x1, %x10
|
||||
call_indirect sig0, v1() ; bin: 000500e7
|
||||
call_indirect sig0, v2() ; bin: 000a80e7
|
||||
|
||||
brz v1, block3
|
||||
fallthrough block4
|
||||
|
||||
block4:
|
||||
brnz v1, block1
|
||||
fallthrough block5
|
||||
|
||||
block5:
|
||||
; jalr %x0, %x1, 0
|
||||
return v9999 ; bin: 00008067
|
||||
|
||||
block1:
|
||||
; beq 0x000
|
||||
br_icmp eq v1, v2, block1 ; bin: 01550063
|
||||
fallthrough block100
|
||||
|
||||
block100:
|
||||
; bne 0xffc
|
||||
br_icmp ne v1, v2, block1 ; bin: ff551ee3
|
||||
fallthrough block101
|
||||
|
||||
block101:
|
||||
; blt 0xff8
|
||||
br_icmp slt v1, v2, block1 ; bin: ff554ce3
|
||||
fallthrough block102
|
||||
|
||||
block102:
|
||||
; bge 0xff4
|
||||
br_icmp sge v1, v2, block1 ; bin: ff555ae3
|
||||
fallthrough block103
|
||||
|
||||
block103:
|
||||
; bltu 0xff0
|
||||
br_icmp ult v1, v2, block1 ; bin: ff5568e3
|
||||
fallthrough block104
|
||||
|
||||
block104:
|
||||
; bgeu 0xfec
|
||||
br_icmp uge v1, v2, block1 ; bin: ff5576e3
|
||||
fallthrough block105
|
||||
|
||||
block105:
|
||||
|
||||
; Forward branches.
|
||||
fallthrough block106
|
||||
|
||||
block106:
|
||||
; beq 0x018
|
||||
br_icmp eq v2, v1, block2 ; bin: 00aa8c63
|
||||
fallthrough block107
|
||||
|
||||
block107:
|
||||
; bne 0x014
|
||||
br_icmp ne v2, v1, block2 ; bin: 00aa9a63
|
||||
fallthrough block108
|
||||
|
||||
block108:
|
||||
; blt 0x010
|
||||
br_icmp slt v2, v1, block2 ; bin: 00aac863
|
||||
fallthrough block109
|
||||
|
||||
block109:
|
||||
; bge 0x00c
|
||||
br_icmp sge v2, v1, block2 ; bin: 00aad663
|
||||
fallthrough block110
|
||||
|
||||
block110:
|
||||
; bltu 0x008
|
||||
br_icmp ult v2, v1, block2 ; bin: 00aae463
|
||||
fallthrough block111
|
||||
|
||||
block111:
|
||||
; bgeu 0x004
|
||||
br_icmp uge v2, v1, block2 ; bin: 00aaf263
|
||||
|
||||
fallthrough block2
|
||||
|
||||
block2:
|
||||
; jal %x0, 0x00000
|
||||
jump block2 ; bin: 0000006f
|
||||
|
||||
block3:
|
||||
; beq x, %x0
|
||||
brz v1, block3 ; bin: 00050063
|
||||
fallthrough block6
|
||||
|
||||
block6:
|
||||
; bne x, %x0
|
||||
brnz v1, block3 ; bin: fe051ee3
|
||||
|
||||
; jal %x0, 0x1ffff4
|
||||
jump block2 ; bin: ff5ff06f
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
test legalizer
|
||||
target riscv32 supports_m=1
|
||||
|
||||
function %int32(i32, i32) {
|
||||
block0(v1: i32, v2: i32):
|
||||
v10 = iadd v1, v2
|
||||
; check: [R#0c]
|
||||
; sameln: v10 = iadd
|
||||
|
||||
v11 = isub v1, v2
|
||||
; check: [R#200c]
|
||||
; sameln: v11 = isub
|
||||
|
||||
v12 = imul v1, v2
|
||||
; check: [R#10c]
|
||||
; sameln: v12 = imul
|
||||
|
||||
return
|
||||
; check: [Iret#19]
|
||||
; sameln: return
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
; Test the legalization of i32 instructions that don't have RISC-V versions.
|
||||
test legalizer
|
||||
|
||||
target riscv32 supports_m=1
|
||||
|
||||
target riscv64 supports_m=1
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %carry_out(i32, i32) -> i32, b1 {
|
||||
block0(v1: i32, v2: i32):
|
||||
v3, v4 = iadd_cout v1, v2
|
||||
return v3, v4
|
||||
}
|
||||
; check: v3 = iadd v1, v2
|
||||
; check: v4 = icmp ult v3, v1
|
||||
; check: return v3, v4
|
||||
|
||||
; Expanding illegal immediate constants.
|
||||
; Note that at some point we'll probably expand the iconst as well.
|
||||
function %large_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iadd_imm v0, 1000000000
|
||||
return v1
|
||||
}
|
||||
; check: $(cst=$V) = iconst.i32 0x3b9a_ca00
|
||||
; check: v1 = iadd v0, $cst
|
||||
; check: return v1
|
||||
|
||||
function %bitclear(i32, i32) -> i32 {
|
||||
block0(v0: i32, v1: i32):
|
||||
v2 = band_not v0, v1
|
||||
; check: iconst.i32 -1
|
||||
; check: bxor
|
||||
; check: band
|
||||
return v2
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
; Test legalizer's handling of ABI boundaries.
|
||||
test legalizer
|
||||
target riscv32
|
||||
|
||||
; regex: V=v\d+
|
||||
; regex: SS=ss\d+
|
||||
; regex: WS=\s+
|
||||
|
||||
function %int_split_args(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
; check: block0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32):
|
||||
; check: v0 = iconcat $v0l, $v0h
|
||||
v1 = iadd_imm v0, 1
|
||||
; check: $(v1l=$V), $(v1h=$V) = isplit v1
|
||||
; check: return $v1l, $v1h, $link
|
||||
return v1
|
||||
}
|
||||
|
||||
function %split_call_arg(i32) {
|
||||
fn1 = %foo(i64)
|
||||
fn2 = %foo(i32, i64)
|
||||
block0(v0: i32):
|
||||
v1 = uextend.i64 v0
|
||||
call fn1(v1)
|
||||
; check: $(v1h=$V) = iconst.i32 0
|
||||
; check: call fn1(v0, $v1h)
|
||||
call fn2(v0, v1)
|
||||
; check: call fn2(v0, $V, $V)
|
||||
return
|
||||
}
|
||||
|
||||
function %split_ret_val() {
|
||||
fn1 = %foo() -> i64
|
||||
block0:
|
||||
v1 = call fn1()
|
||||
; check: block0($(link=$V): i32):
|
||||
; nextln: $(v1l=$V), $(v1h=$V) = call fn1()
|
||||
; check: v1 = iconcat $v1l, $v1h
|
||||
jump block1(v1)
|
||||
; check: jump block1(v1)
|
||||
|
||||
block1(v10: i64):
|
||||
jump block1(v10)
|
||||
}
|
||||
|
||||
; First return value is fine, second one is expanded.
|
||||
function %split_ret_val2() {
|
||||
fn1 = %foo() -> i32, i64
|
||||
block0:
|
||||
v1, v2 = call fn1()
|
||||
; check: block0($(link=$V): i32):
|
||||
; nextln: v1, $(v2l=$V), $(v2h=$V) = call fn1()
|
||||
; check: v2 = iconcat $v2l, $v2h
|
||||
jump block1(v1, v2)
|
||||
; check: jump block1(v1, v2)
|
||||
|
||||
block1(v9: i32, v10: i64):
|
||||
jump block1(v9, v10)
|
||||
}
|
||||
|
||||
function %int_ext(i8, i8 sext, i8 uext) -> i8 uext {
|
||||
block0(v1: i8, v2: i8, v3: i8):
|
||||
; check: block0(v1: i8, $(v2x=$V): i32, $(v3x=$V): i32, $(link=$V): i32):
|
||||
; check: v2 = ireduce.i8 $v2x
|
||||
; check: v3 = ireduce.i8 $v3x
|
||||
; check: $(v1x=$V) = uextend.i32 v1
|
||||
; check: return $v1x, $link
|
||||
return v1
|
||||
}
|
||||
|
||||
; Function produces single return value, still need to copy.
|
||||
function %ext_ret_val() {
|
||||
fn1 = %foo() -> i8 sext
|
||||
block0:
|
||||
v1 = call fn1()
|
||||
; check: block0($V: i32):
|
||||
; nextln: $(rv=$V) = call fn1()
|
||||
; check: v1 = ireduce.i8 $rv
|
||||
jump block1(v1)
|
||||
; check: jump block1(v1)
|
||||
|
||||
block1(v10: i8):
|
||||
jump block1(v10)
|
||||
}
|
||||
|
||||
function %vector_split_args(i64x4) -> i64x4 {
|
||||
block0(v0: i64x4):
|
||||
; check: block0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32, $(link=$V): i32):
|
||||
; check: $(v0a=$V) = iconcat $v0al, $v0ah
|
||||
; check: $(v0b=$V) = iconcat $v0bl, $v0bh
|
||||
; check: $(v0ab=$V) = vconcat $v0a, $v0b
|
||||
; check: $(v0c=$V) = iconcat $v0cl, $v0ch
|
||||
; check: $(v0d=$V) = iconcat $v0dl, $v0dh
|
||||
; check: $(v0cd=$V) = vconcat $v0c, $v0d
|
||||
; check: v0 = vconcat $v0ab, $v0cd
|
||||
v1 = bxor v0, v0
|
||||
; check: $(v1ab=$V), $(v1cd=$V) = vsplit v1
|
||||
; check: $(v1a=$V), $(v1b=$V) = vsplit $v1ab
|
||||
; check: $(v1al=$V), $(v1ah=$V) = isplit $v1a
|
||||
; check: $(v1bl=$V), $(v1bh=$V) = isplit $v1b
|
||||
; check: $(v1c=$V), $(v1d=$V) = vsplit $v1cd
|
||||
; check: $(v1cl=$V), $(v1ch=$V) = isplit $v1c
|
||||
; check: $(v1dl=$V), $(v1dh=$V) = isplit $v1d
|
||||
; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh, $link
|
||||
return v1
|
||||
}
|
||||
|
||||
function %indirect(i32) {
|
||||
sig1 = () system_v
|
||||
block0(v0: i32):
|
||||
call_indirect sig1, v0()
|
||||
return
|
||||
}
|
||||
|
||||
; The first argument to call_indirect doesn't get altered.
|
||||
function %indirect_arg(i32, f32x2) {
|
||||
sig1 = (f32x2) system_v
|
||||
block0(v0: i32, v1: f32x2):
|
||||
call_indirect sig1, v0(v1)
|
||||
; check: call_indirect sig1, v0($V, $V)
|
||||
return
|
||||
}
|
||||
|
||||
; Call a function that takes arguments on the stack.
|
||||
function %stack_args(i32) {
|
||||
; check: $(ss0=$SS) = outgoing_arg 4
|
||||
fn1 = %foo(i64, i64, i64, i64, i32)
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i64 1
|
||||
call fn1(v1, v1, v1, v1, v0)
|
||||
; check: [GPsp#48,$ss0]$WS $(v0s=$V) = spill v0
|
||||
; check: call fn1($(=.*), $v0s)
|
||||
return
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
; Test the legalization of i64 arithmetic instructions.
|
||||
test legalizer
|
||||
target riscv32 supports_m=1
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %bitwise_and(i64, i64) -> i64 {
|
||||
block0(v1: i64, v2: i64):
|
||||
v3 = band v1, v2
|
||||
return v3
|
||||
}
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
; check: [R#ec
|
||||
; sameln: $(v3l=$V) = band $v1l, $v2l
|
||||
; check: [R#ec
|
||||
; sameln: $(v3h=$V) = band $v1h, $v2h
|
||||
; check: v3 = iconcat $v3l, $v3h
|
||||
; check: return $v3l, $v3h, $link
|
||||
|
||||
function %bitwise_or(i64, i64) -> i64 {
|
||||
block0(v1: i64, v2: i64):
|
||||
v3 = bor v1, v2
|
||||
return v3
|
||||
}
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
; check: [R#cc
|
||||
; sameln: $(v3l=$V) = bor $v1l, $v2l
|
||||
; check: [R#cc
|
||||
; sameln: $(v3h=$V) = bor $v1h, $v2h
|
||||
; check: v3 = iconcat $v3l, $v3h
|
||||
; check: return $v3l, $v3h, $link
|
||||
|
||||
function %bitwise_xor(i64, i64) -> i64 {
|
||||
block0(v1: i64, v2: i64):
|
||||
v3 = bxor v1, v2
|
||||
return v3
|
||||
}
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
; check: [R#8c
|
||||
; sameln: $(v3l=$V) = bxor $v1l, $v2l
|
||||
; check: [R#8c
|
||||
; sameln: $(v3h=$V) = bxor $v1h, $v2h
|
||||
; check: v3 = iconcat $v3l, $v3h
|
||||
; check: return $v3l, $v3h, $link
|
||||
|
||||
function %arith_add(i64, i64) -> i64 {
|
||||
; Legalizing iadd.i64 requires two steps:
|
||||
; 1. Narrow to iadd_cout.i32, then
|
||||
; 2. Expand iadd_cout.i32 since RISC-V has no carry flag.
|
||||
block0(v1: i64, v2: i64):
|
||||
v3 = iadd v1, v2
|
||||
return v3
|
||||
}
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
; check: [R#0c
|
||||
; sameln: $(v3l=$V) = iadd $v1l, $v2l
|
||||
; check: $(c=$V) = icmp ult $v3l, $v1l
|
||||
; check: [R#0c
|
||||
; sameln: $(v3h1=$V) = iadd $v1h, $v2h
|
||||
; check: $(c_int=$V) = bint.i32 $c
|
||||
; check: [R#0c
|
||||
; sameln: $(v3h=$V) = iadd $v3h1, $c_int
|
||||
; check: v3 = iconcat $v3l, $v3h
|
||||
; check: return $v3l, $v3h, $link
|
||||
@@ -1,55 +0,0 @@
|
||||
test legalizer
|
||||
target riscv32
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %icmp_imm_eq(i64) -> b1 {
|
||||
block0(v0: i64):
|
||||
v1 = icmp_imm eq v0, 0x20202020_10101010
|
||||
return v1
|
||||
}
|
||||
; check: block0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32):
|
||||
; nextln: $(v2l=$V) -> $(v0l)
|
||||
; nextln: $(v2h=$V) -> $(v0h)
|
||||
; nextln: v0 = iconcat $(v0l), $(v0h)
|
||||
; nextln: $(imm_low=$V) = iconst.i32 0x1010_1010
|
||||
; nextln: $(imm_high=$V) = iconst.i32 0x2020_2020
|
||||
; nextln: $(v3=$V) = icmp eq $(v2l), $(imm_low)
|
||||
; nextln: $(v4=$V) = icmp eq $(v2h), $(imm_high)
|
||||
; nextln: v1 = band $(v3), $(v4)
|
||||
; nextln: return v1, $(link)
|
||||
|
||||
function %icmp_imm_ne(i64) -> b1 {
|
||||
block0(v0: i64):
|
||||
v1 = icmp_imm ne v0, 0x33333333_44444444
|
||||
return v1
|
||||
}
|
||||
; check: block0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32):
|
||||
; nextln: $(v2l=$V) -> $(v0l)
|
||||
; nextln: $(v2h=$V) -> $(v0h)
|
||||
; nextln: v0 = iconcat $(v0l), $(v0h)
|
||||
; nextln: $(imm_low=$V) = iconst.i32 0x4444_4444
|
||||
; nextln: $(imm_high=$V) = iconst.i32 0x3333_3333
|
||||
; nextln: $(v3=$V) = icmp ne $(v2l), $(imm_low)
|
||||
; nextln: $(v4=$V) = icmp ne $(v2h), $(imm_high)
|
||||
; nextln: v1 = bor $(v3), $(v4)
|
||||
; nextln: return v1, $(link)
|
||||
|
||||
function %icmp_imm_sge(i64) -> b1 {
|
||||
block0(v0: i64):
|
||||
v1 = icmp_imm sge v0, 0x01020304_05060708
|
||||
return v1
|
||||
}
|
||||
; check: block0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32):
|
||||
; nextln: $(v2l=$V) -> $(v0l)
|
||||
; nextln: $(v2h=$V) -> $(v0h)
|
||||
; nextln: v0 = iconcat $(v0l), $(v0h)
|
||||
; nextln: $(imm_low=$V) = iconst.i32 0x0506_0708
|
||||
; nextln: $(imm_high=$V) = iconst.i32 0x0102_0304
|
||||
; nextln: $(v3=$V) = icmp sgt $(v2h), $(imm_high)
|
||||
; nextln: $(v4=$V) = icmp slt $(v2h), $(imm_high)
|
||||
; nextln: $(v5=$V) = icmp uge $(v2l), $(imm_low)
|
||||
; nextln: $(v6=$V) = bnot $v4
|
||||
; nextln: $(v7=$V) = band $v6, $v5
|
||||
; nextln: v1 = bor $(v3), $(v7)
|
||||
; nextln: return v1, $(link)
|
||||
@@ -1,36 +0,0 @@
|
||||
; Test the parser's support for encoding annotations.
|
||||
test legalizer
|
||||
target riscv32
|
||||
|
||||
function %parse_encoding(i32 [%x5]) -> i32 [%x10] {
|
||||
; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] fast {
|
||||
|
||||
sig0 = (i32 [%x10]) -> i32 [%x10] system_v
|
||||
; check: sig0 = (i32 [%x10]) -> i32 [%x10] system_v
|
||||
|
||||
sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v
|
||||
; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v
|
||||
|
||||
sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v
|
||||
; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v
|
||||
|
||||
; Arguments on stack where not necessary
|
||||
sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] system_v
|
||||
; check: sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] system_v
|
||||
|
||||
; Stack argument before register argument
|
||||
sig4 = (f32 [72], i32 [%x10]) system_v
|
||||
; check: sig4 = (f32 [72], i32 [%x10]) system_v
|
||||
|
||||
; Return value on stack
|
||||
sig5 = () -> f32 [0] system_v
|
||||
; check: sig5 = () -> f32 [0] system_v
|
||||
|
||||
; function + signature
|
||||
fn0 = %bar(i32 [%x10]) -> b1 [%x10] system_v
|
||||
; check: sig6 = (i32 [%x10]) -> b1 [%x10] system_v
|
||||
; nextln: fn0 = %bar sig6
|
||||
|
||||
block0(v0: i32):
|
||||
return v0
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
; Test tracking of register moves.
|
||||
test binemit
|
||||
target riscv32
|
||||
|
||||
function %regmoves(i32 link [%x1]) -> i32 link [%x1] {
|
||||
block0(v9999: i32):
|
||||
[-,%x10] v1 = iconst.i32 1
|
||||
[-,%x7] v2 = iadd_imm v1, 1000 ; bin: 3e850393
|
||||
regmove v1, %x10 -> %x11 ; bin: 00050593
|
||||
[-,%x7] v3 = iadd_imm v1, 1000 ; bin: 3e858393
|
||||
regmove v1, %x11 -> %x10 ; bin: 00058513
|
||||
[-,%x7] v4 = iadd_imm v1, 1000 ; bin: 3e850393
|
||||
|
||||
return v9999
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
; Test the legalization of block arguments that are split.
|
||||
test legalizer
|
||||
target riscv32
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %simple(i64, i64) -> i64 {
|
||||
block0(v1: i64, v2: i64):
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
jump block1(v1)
|
||||
; check: jump block1($v1l, $v1h)
|
||||
|
||||
block1(v3: i64):
|
||||
; check: block1($(v3l=$V): i32, $(v3h=$V): i32):
|
||||
v4 = band v3, v2
|
||||
; check: $(v4l=$V) = band $v3l, $v2l
|
||||
; check: $(v4h=$V) = band $v3h, $v2h
|
||||
return v4
|
||||
; check: return $v4l, $v4h, $link
|
||||
}
|
||||
|
||||
function %multi(i64) -> i64 {
|
||||
block1(v1: i64):
|
||||
; check: block1($(v1l=$V): i32, $(v1h=$V): i32, $(link=$V): i32):
|
||||
jump block2(v1, v1)
|
||||
; check: jump block2($v1l, $v1l, $v1h, $v1h)
|
||||
|
||||
block2(v2: i64, v3: i64):
|
||||
; check: block2($(v2l=$V): i32, $(v3l=$V): i32, $(v2h=$V): i32, $(v3h=$V): i32):
|
||||
jump block3(v2)
|
||||
; check: jump block3($v2l, $v2h)
|
||||
|
||||
block3(v4: i64):
|
||||
; check: block3($(v4l=$V): i32, $(v4h=$V): i32):
|
||||
v5 = band v4, v3
|
||||
; check: $(v5l=$V) = band $v4l, $v3l
|
||||
; check: $(v5h=$V) = band $v4h, $v3h
|
||||
return v5
|
||||
; check: return $v5l, $v5h, $link
|
||||
}
|
||||
|
||||
function %loop(i64, i64) -> i64 {
|
||||
block0(v1: i64, v2: i64):
|
||||
; check: block0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32):
|
||||
jump block1(v1)
|
||||
; check: jump block1($v1l, $v1h)
|
||||
|
||||
block1(v3: i64):
|
||||
; check: block1($(v3l=$V): i32, $(v3h=$V): i32):
|
||||
v4 = band v3, v2
|
||||
; check: $(v4l=$V) = band $v3l, $v2l
|
||||
; check: $(v4h=$V) = band $v3h, $v2h
|
||||
jump block1(v4)
|
||||
; check: jump block1($v4l, $v4h)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
test verifier
|
||||
target riscv32
|
||||
|
||||
function %RV32I(i32 link [%x1]) -> i32 link [%x1] {
|
||||
fn0 = %foo()
|
||||
|
||||
block0(v9999: i32):
|
||||
; iconst.i32 needs legalizing, so it should throw a
|
||||
[R#0,-] v1 = iconst.i32 0xf0f0f0f0f0 ; error: Instruction failed to re-encode
|
||||
[Iret#19] return v9999
|
||||
}
|
||||
|
||||
function %RV32I(i32 link [%x1]) -> i32 link [%x1] {
|
||||
fn0 = %foo()
|
||||
|
||||
block0(v9999: i32):
|
||||
v1 = iconst.i32 1
|
||||
v2 = iconst.i32 2
|
||||
[R#0,-] v3 = iadd v1, v2 ; error: encoding R#00 should be R#0c
|
||||
[Iret#19] return v9999
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %amode_add(i64, i64) -> i64 {
|
||||
block0(v0: i64, v1: i64):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(b1, i32, i32) -> i32 {
|
||||
; check: pushq %rbp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f(i32, i32) -> i32 {
|
||||
block0(v0: i32, v1: i32):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(b8) -> b64 {
|
||||
block0(v0: b8):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(i32, i32) -> i32 {
|
||||
block0(v0: i32, v1: i32):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
;; system_v has first param in %rdi, fascall in %rcx
|
||||
function %one_arg(i32) system_v {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst has_lzcnt
|
||||
target x86_64 has_lzcnt
|
||||
|
||||
function %clz(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(i64, i64) -> i64, i64 {
|
||||
block0(v0: i64, v1: i64):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst has_bmi1
|
||||
target x86_64 has_bmi1
|
||||
|
||||
function %ctz(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
test compile
|
||||
set avoid_div_traps=false
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
;; We should get the checked-div/rem sequence (`srem` pseudoinst below) even
|
||||
;; when `avoid_div_traps` above is false (i.e. even when the host is normally
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
test compile
|
||||
set enable_llvm_abi_extensions=true
|
||||
set unwind_info=true
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(i64, i64, i64, i64) -> i64 windows_fastcall {
|
||||
block0(v0: i64, v1: i64, v2: i64, v3: i64):
|
||||
@@ -206,7 +206,7 @@ block0(v0: i64):
|
||||
v18 = load.f64 v0+136
|
||||
v19 = load.f64 v0+144
|
||||
v20 = load.f64 v0+152
|
||||
|
||||
|
||||
v21 = fadd.f64 v1, v2
|
||||
v22 = fadd.f64 v3, v4
|
||||
v23 = fadd.f64 v5, v6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f(f64) -> f64 {
|
||||
block0(v0: f64):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f(i32, i64 vmctx) -> i64 {
|
||||
gv0 = vmctx
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
test compile
|
||||
set enable_llvm_abi_extensions=true
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %f0(i128, i128) -> i128 {
|
||||
; check: pushq %rbp
|
||||
@@ -190,7 +190,7 @@ block0(v0: i128, v1: i128):
|
||||
; nextln: orq %rax, %r8
|
||||
; nextln: andq $$1, %r8
|
||||
; nextln: setnz %r8b
|
||||
|
||||
|
||||
v4 = icmp slt v0, v1
|
||||
; check: cmpq %rcx, %rsi
|
||||
; nextln: setl %r9b
|
||||
@@ -201,7 +201,7 @@ block0(v0: i128, v1: i128):
|
||||
; nextln: orq %r9, %r10
|
||||
; nextln: andq $$1, %r10
|
||||
; nextln: setnz %r9b
|
||||
|
||||
|
||||
v5 = icmp sle v0, v1
|
||||
; check: cmpq %rcx, %rsi
|
||||
; nextln: setl %r10b
|
||||
@@ -212,7 +212,7 @@ block0(v0: i128, v1: i128):
|
||||
; nextln: orq %r10, %r11
|
||||
; nextln: andq $$1, %r11
|
||||
; nextln: setnz %r10b
|
||||
|
||||
|
||||
v6 = icmp sgt v0, v1
|
||||
; check: cmpq %rcx, %rsi
|
||||
; nextln: setnle %r11b
|
||||
@@ -307,7 +307,7 @@ block0(v0: i128):
|
||||
; nextln: setz %sil
|
||||
; nextln: andb %dil, %sil
|
||||
; nextln: jnz label1; j label2
|
||||
|
||||
|
||||
jump block2
|
||||
|
||||
block1:
|
||||
@@ -725,7 +725,7 @@ block2(v6: i128):
|
||||
; nextln: movq %rbp, %rsp
|
||||
; nextln: popq %rbp
|
||||
; nextln: ret
|
||||
|
||||
|
||||
}
|
||||
|
||||
function %f24(i128, i128, i64, i128, i128, i128) -> i128 {
|
||||
@@ -1106,4 +1106,4 @@ block0(v0: i128, v1: i128):
|
||||
; nextln: movq %rcx, %rdx
|
||||
; nextln: movq %rbp, %rsp
|
||||
; nextln: popq %rbp
|
||||
; nextln: ret
|
||||
; nextln: ret
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test compile
|
||||
target x86_64 machinst
|
||||
target x86_64
|
||||
|
||||
function %add_from_mem_u32_1(i64, i32) -> i32 {
|
||||
block0(v0: i64, v1: i32):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user