Move IR modules under repr/.
Use the cretonne::repr module as a common namespace for sub-modules defining the in-memory representation of Cretonn IL.
This commit is contained in:
325
cranelift/src/libcretonne/repr/condcodes.rs
Normal file
325
cranelift/src/libcretonne/repr/condcodes.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
//! Condition codes for the Cretonne code generator.
|
||||
//!
|
||||
//! A condition code here is an enumerated type that determined how to compare two numbers. There
|
||||
//! are different rules for comparing integers and floating point numbers, so they use different
|
||||
//! condition codes.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Common traits of condition codes.
|
||||
pub trait CondCode: Copy {
|
||||
/// Get the inverse condition code of `self`.
|
||||
///
|
||||
/// The inverse condition code produces the opposite result for all comparisons.
|
||||
/// That is, `cmp CC, x, y` is true if and only if `cmp CC.inverse(), x, y` is false.
|
||||
fn inverse(self) -> Self;
|
||||
|
||||
/// Get the reversed condition code for `self`.
|
||||
///
|
||||
/// The reversed condition code produces the same result as swapping `x` and `y` in the
|
||||
/// comparison. That is, `cmp CC, x, y` is the same as `cmp CC.reverse(), y, x`.
|
||||
fn reverse(self) -> Self;
|
||||
}
|
||||
|
||||
/// Condition code for comparing integers.
|
||||
///
|
||||
/// This condition code is used by the `icmp` instruction to compare integer values. There are
|
||||
/// separate codes for comparing the integers as signed or unsigned numbers where it makes a
|
||||
/// difference.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum IntCC {
|
||||
Equal,
|
||||
NotEqual,
|
||||
SignedLessThan,
|
||||
SignedGreaterThanOrEqual,
|
||||
SignedGreaterThan,
|
||||
SignedLessThanOrEqual,
|
||||
UnsignedLessThan,
|
||||
UnsignedGreaterThanOrEqual,
|
||||
UnsignedGreaterThan,
|
||||
UnsignedLessThanOrEqual,
|
||||
}
|
||||
|
||||
impl CondCode for IntCC {
|
||||
fn inverse(self) -> Self {
|
||||
use self::IntCC::*;
|
||||
match self {
|
||||
Equal => NotEqual,
|
||||
NotEqual => Equal,
|
||||
SignedLessThan => SignedGreaterThanOrEqual,
|
||||
SignedGreaterThanOrEqual => SignedLessThan,
|
||||
SignedGreaterThan => SignedLessThanOrEqual,
|
||||
SignedLessThanOrEqual => SignedGreaterThan,
|
||||
UnsignedLessThan => UnsignedGreaterThanOrEqual,
|
||||
UnsignedGreaterThanOrEqual => UnsignedLessThan,
|
||||
UnsignedGreaterThan => UnsignedLessThanOrEqual,
|
||||
UnsignedLessThanOrEqual => UnsignedGreaterThan,
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse(self) -> Self {
|
||||
use self::IntCC::*;
|
||||
match self {
|
||||
Equal => Equal,
|
||||
NotEqual => NotEqual,
|
||||
SignedGreaterThan => SignedLessThan,
|
||||
SignedGreaterThanOrEqual => SignedLessThanOrEqual,
|
||||
SignedLessThan => SignedGreaterThan,
|
||||
SignedLessThanOrEqual => SignedGreaterThanOrEqual,
|
||||
UnsignedGreaterThan => UnsignedLessThan,
|
||||
UnsignedGreaterThanOrEqual => UnsignedLessThanOrEqual,
|
||||
UnsignedLessThan => UnsignedGreaterThan,
|
||||
UnsignedLessThanOrEqual => UnsignedGreaterThanOrEqual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IntCC {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use self::IntCC::*;
|
||||
f.write_str(match self {
|
||||
&Equal => "eq",
|
||||
&NotEqual => "ne",
|
||||
&SignedGreaterThan => "sgt",
|
||||
&SignedGreaterThanOrEqual => "sge",
|
||||
&SignedLessThan => "slt",
|
||||
&SignedLessThanOrEqual => "sle",
|
||||
&UnsignedGreaterThan => "ugt",
|
||||
&UnsignedGreaterThanOrEqual => "uge",
|
||||
&UnsignedLessThan => "ult",
|
||||
&UnsignedLessThanOrEqual => "ule",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IntCC {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::IntCC::*;
|
||||
match s {
|
||||
"eq" => Ok(Equal),
|
||||
"ne" => Ok(NotEqual),
|
||||
"sge" => Ok(SignedGreaterThanOrEqual),
|
||||
"sgt" => Ok(SignedGreaterThan),
|
||||
"sle" => Ok(SignedLessThanOrEqual),
|
||||
"slt" => Ok(SignedLessThan),
|
||||
"uge" => Ok(UnsignedGreaterThanOrEqual),
|
||||
"ugt" => Ok(UnsignedGreaterThan),
|
||||
"ule" => Ok(UnsignedLessThanOrEqual),
|
||||
"ult" => Ok(UnsignedLessThan),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Condition code for comparing floating point numbers.
|
||||
///
|
||||
/// This condition code is used by the `fcmp` instruction to compare floating point values. Two
|
||||
/// IEEE floating point values relate in exactly one of four ways:
|
||||
///
|
||||
/// 1. `UN` - unordered when either value is NaN.
|
||||
/// 2. `EQ` - equal numerical value.
|
||||
/// 3. `LT` - `x` is less than `y`.
|
||||
/// 4. `GT` - `x` is greater than `y`.
|
||||
///
|
||||
/// Note that `0.0` and `-0.0` relate as `EQ` because they both represent the number 0.
|
||||
///
|
||||
/// The condition codes described here are used to produce a single boolean value from the
|
||||
/// comparison. The 14 condition codes here cover every possible combination of the relation above
|
||||
/// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum FloatCC {
|
||||
Ordered, // EQ | LT | GT
|
||||
Unordered, // UN
|
||||
|
||||
Equal, // EQ
|
||||
// The C '!=' operator is the inverse of '==': NotEqual.
|
||||
NotEqual, // UN | LT | GT
|
||||
OrderedNotEqual, // LT | GT
|
||||
UnorderedOrEqual, // UN | EQ
|
||||
|
||||
LessThan, // LT
|
||||
LessThanOrEqual, // LT | EQ
|
||||
GreaterThan, // GT
|
||||
GreaterThanOrEqual, // GT | EQ
|
||||
|
||||
UnorderedOrLessThan, // UN | LT
|
||||
UnorderedOrLessThanOrEqual, // UN | LT | EQ
|
||||
UnorderedOrGreaterThan, // UN | GT
|
||||
UnorderedOrGreaterThanOrEqual, // UN | GT | EQ
|
||||
}
|
||||
|
||||
impl CondCode for FloatCC {
|
||||
fn inverse(self) -> Self {
|
||||
use self::FloatCC::*;
|
||||
match self {
|
||||
Ordered => Unordered,
|
||||
Unordered => Ordered,
|
||||
Equal => NotEqual,
|
||||
NotEqual => Equal,
|
||||
OrderedNotEqual => UnorderedOrEqual,
|
||||
UnorderedOrEqual => OrderedNotEqual,
|
||||
LessThan => UnorderedOrGreaterThanOrEqual,
|
||||
LessThanOrEqual => UnorderedOrGreaterThan,
|
||||
GreaterThan => UnorderedOrLessThanOrEqual,
|
||||
GreaterThanOrEqual => UnorderedOrLessThan,
|
||||
UnorderedOrLessThan => GreaterThanOrEqual,
|
||||
UnorderedOrLessThanOrEqual => GreaterThan,
|
||||
UnorderedOrGreaterThan => LessThanOrEqual,
|
||||
UnorderedOrGreaterThanOrEqual => LessThan,
|
||||
}
|
||||
}
|
||||
fn reverse(self) -> Self {
|
||||
use self::FloatCC::*;
|
||||
match self {
|
||||
Ordered => Ordered,
|
||||
Unordered => Unordered,
|
||||
Equal => Equal,
|
||||
NotEqual => NotEqual,
|
||||
OrderedNotEqual => OrderedNotEqual,
|
||||
UnorderedOrEqual => UnorderedOrEqual,
|
||||
LessThan => GreaterThan,
|
||||
LessThanOrEqual => GreaterThanOrEqual,
|
||||
GreaterThan => LessThan,
|
||||
GreaterThanOrEqual => LessThanOrEqual,
|
||||
UnorderedOrLessThan => UnorderedOrGreaterThan,
|
||||
UnorderedOrLessThanOrEqual => UnorderedOrGreaterThanOrEqual,
|
||||
UnorderedOrGreaterThan => UnorderedOrLessThan,
|
||||
UnorderedOrGreaterThanOrEqual => UnorderedOrLessThanOrEqual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FloatCC {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use self::FloatCC::*;
|
||||
f.write_str(match self {
|
||||
&Ordered => "ord",
|
||||
&Unordered => "uno",
|
||||
&Equal => "eq",
|
||||
&NotEqual => "ne",
|
||||
&OrderedNotEqual => "one",
|
||||
&UnorderedOrEqual => "ueq",
|
||||
&LessThan => "lt",
|
||||
&LessThanOrEqual => "le",
|
||||
&GreaterThan => "gt",
|
||||
&GreaterThanOrEqual => "ge",
|
||||
&UnorderedOrLessThan => "ult",
|
||||
&UnorderedOrLessThanOrEqual => "ule",
|
||||
&UnorderedOrGreaterThan => "ugt",
|
||||
&UnorderedOrGreaterThanOrEqual => "uge",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FloatCC {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::FloatCC::*;
|
||||
match s {
|
||||
"ord" => Ok(Ordered),
|
||||
"uno" => Ok(Unordered),
|
||||
"eq" => Ok(Equal),
|
||||
"ne" => Ok(NotEqual),
|
||||
"one" => Ok(OrderedNotEqual),
|
||||
"ueq" => Ok(UnorderedOrEqual),
|
||||
"lt" => Ok(LessThan),
|
||||
"le" => Ok(LessThanOrEqual),
|
||||
"gt" => Ok(GreaterThan),
|
||||
"ge" => Ok(GreaterThanOrEqual),
|
||||
"ult" => Ok(UnorderedOrLessThan),
|
||||
"ule" => Ok(UnorderedOrLessThanOrEqual),
|
||||
"ugt" => Ok(UnorderedOrGreaterThan),
|
||||
"uge" => Ok(UnorderedOrGreaterThanOrEqual),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static INT_ALL: [IntCC; 10] = [IntCC::Equal,
|
||||
IntCC::NotEqual,
|
||||
IntCC::SignedLessThan,
|
||||
IntCC::SignedGreaterThanOrEqual,
|
||||
IntCC::SignedGreaterThan,
|
||||
IntCC::SignedLessThanOrEqual,
|
||||
IntCC::UnsignedLessThan,
|
||||
IntCC::UnsignedGreaterThanOrEqual,
|
||||
IntCC::UnsignedGreaterThan,
|
||||
IntCC::UnsignedLessThanOrEqual];
|
||||
|
||||
#[test]
|
||||
fn int_inverse() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
let inv = cc.inverse();
|
||||
assert!(cc != inv);
|
||||
assert_eq!(inv.inverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_reverse() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
let rev = cc.reverse();
|
||||
assert_eq!(rev.reverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_display() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
assert_eq!(cc.to_string().parse(), Ok(cc));
|
||||
}
|
||||
}
|
||||
|
||||
static FLOAT_ALL: [FloatCC; 14] = [FloatCC::Ordered,
|
||||
FloatCC::Unordered,
|
||||
FloatCC::Equal,
|
||||
FloatCC::NotEqual,
|
||||
FloatCC::OrderedNotEqual,
|
||||
FloatCC::UnorderedOrEqual,
|
||||
FloatCC::LessThan,
|
||||
FloatCC::LessThanOrEqual,
|
||||
FloatCC::GreaterThan,
|
||||
FloatCC::GreaterThanOrEqual,
|
||||
FloatCC::UnorderedOrLessThan,
|
||||
FloatCC::UnorderedOrLessThanOrEqual,
|
||||
FloatCC::UnorderedOrGreaterThan,
|
||||
FloatCC::UnorderedOrGreaterThanOrEqual];
|
||||
|
||||
#[test]
|
||||
fn float_inverse() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
let inv = cc.inverse();
|
||||
assert!(cc != inv);
|
||||
assert_eq!(inv.inverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_reverse() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
let rev = cc.reverse();
|
||||
assert_eq!(rev.reverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_display() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
assert_eq!(cc.to_string().parse(), Ok(cc));
|
||||
}
|
||||
}
|
||||
}
|
||||
420
cranelift/src/libcretonne/repr/dfg.rs
Normal file
420
cranelift/src/libcretonne/repr/dfg.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
//! Data flow graph tracking Instructions, Values, and EBBs.
|
||||
|
||||
use entity_map::EntityMap;
|
||||
use repr::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue};
|
||||
use repr::instructions::InstructionData;
|
||||
use repr::types::Type;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::u16;
|
||||
|
||||
/// A data flow graph defines all instuctions and extended basic blocks in a function as well as
|
||||
/// the data flow dependencies between them. The DFG also tracks values which can be either
|
||||
/// instruction results or EBB arguments.
|
||||
///
|
||||
/// The layout of EBBs in the function and of instructions in each EBB is recorded by the
|
||||
/// `FunctionLayout` data structure which form the other half of the function representation.
|
||||
///
|
||||
pub struct DataFlowGraph {
|
||||
/// Data about all of the instructions in the function, including opcodes and operands.
|
||||
/// The instructions in this map are not in program order. That is tracked by `Layout`, along
|
||||
/// with the EBB containing each instruction.
|
||||
insts: EntityMap<Inst, InstructionData>,
|
||||
|
||||
/// Extended basic blocks in the function and their arguments.
|
||||
/// This map is not in program order. That is handled by `Layout`, and so is the sequence of
|
||||
/// instructions contained in each EBB.
|
||||
ebbs: EntityMap<Ebb, EbbData>,
|
||||
|
||||
/// Extended value table. Most `Value` references refer directly to their defining instruction.
|
||||
/// Others index into this table.
|
||||
///
|
||||
/// This is implemented directly with a `Vec` rather than an `EntityMap<Value, ...>` because
|
||||
/// the Value entity references can refer to two things -- an instruction or an extended value.
|
||||
extended_values: Vec<ValueData>,
|
||||
}
|
||||
|
||||
impl DataFlowGraph {
|
||||
/// Create a new empty `DataFlowGraph`.
|
||||
pub fn new() -> DataFlowGraph {
|
||||
DataFlowGraph {
|
||||
insts: EntityMap::new(),
|
||||
ebbs: EntityMap::new(),
|
||||
extended_values: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handling values.
|
||||
///
|
||||
/// Values are either EBB arguments or instruction results.
|
||||
impl DataFlowGraph {
|
||||
// Allocate an extended value entry.
|
||||
fn make_value(&mut self, data: ValueData) -> Value {
|
||||
let vref = Value::new_table(self.extended_values.len());
|
||||
self.extended_values.push(data);
|
||||
vref
|
||||
}
|
||||
|
||||
/// Get the type of a value.
|
||||
pub fn value_type(&self, v: Value) -> Type {
|
||||
use repr::entities::ExpandedValue::*;
|
||||
match v.expand() {
|
||||
Direct(i) => self.insts[i].first_type(),
|
||||
Table(i) => {
|
||||
match self.extended_values[i] {
|
||||
ValueData::Inst { ty, .. } => ty,
|
||||
ValueData::Arg { ty, .. } => ty,
|
||||
}
|
||||
}
|
||||
None => panic!("NO_VALUE has no type"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the definition of a value.
|
||||
///
|
||||
/// This is either the instruction that defined it or the Ebb that has the value as an
|
||||
/// argument.
|
||||
pub fn value_def(&self, v: Value) -> ValueDef {
|
||||
use repr::entities::ExpandedValue::*;
|
||||
match v.expand() {
|
||||
Direct(inst) => ValueDef::Res(inst, 0),
|
||||
Table(idx) => {
|
||||
match self.extended_values[idx] {
|
||||
ValueData::Inst { inst, num, .. } => ValueDef::Res(inst, num as usize),
|
||||
ValueData::Arg { ebb, num, .. } => ValueDef::Arg(ebb, num as usize),
|
||||
}
|
||||
}
|
||||
None => panic!("NO_VALUE has no def"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where did a value come from?
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ValueDef {
|
||||
/// Value is the n'th result of an instruction.
|
||||
Res(Inst, usize),
|
||||
/// Value is the n'th argument to an EBB.
|
||||
Arg(Ebb, usize),
|
||||
}
|
||||
|
||||
// Internal table storage for extended values.
|
||||
enum ValueData {
|
||||
// Value is defined by an instruction, but it is not the first result.
|
||||
Inst {
|
||||
ty: Type,
|
||||
num: u16, // Result number starting from 0.
|
||||
inst: Inst,
|
||||
next: Value, // Next result defined by `def`.
|
||||
},
|
||||
|
||||
// Value is an EBB argument.
|
||||
Arg {
|
||||
ty: Type,
|
||||
num: u16, // Argument number, starting from 0.
|
||||
ebb: Ebb,
|
||||
next: Value, // Next argument to `ebb`.
|
||||
},
|
||||
}
|
||||
|
||||
/// Iterate through a list of related value references, such as:
|
||||
///
|
||||
/// - All results defined by an instruction. See `DataFlowGraph::inst_results`.
|
||||
/// - All arguments to an EBB. See `DataFlowGraph::ebb_args`.
|
||||
///
|
||||
/// A value iterator borrows a `DataFlowGraph` reference.
|
||||
pub struct Values<'a> {
|
||||
dfg: &'a DataFlowGraph,
|
||||
cur: Value,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Values<'a> {
|
||||
type Item = Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let prev = self.cur;
|
||||
|
||||
// Advance self.cur to the next value, or NO_VALUE.
|
||||
self.cur = match prev.expand() {
|
||||
ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result().unwrap_or_default(),
|
||||
ExpandedValue::Table(index) => {
|
||||
match self.dfg.extended_values[index] {
|
||||
ValueData::Inst { next, .. } => next,
|
||||
ValueData::Arg { next, .. } => next,
|
||||
}
|
||||
}
|
||||
ExpandedValue::None => return None,
|
||||
};
|
||||
|
||||
Some(prev)
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructions.
|
||||
///
|
||||
impl DataFlowGraph {
|
||||
/// Create a new instruction.
|
||||
///
|
||||
/// The type of the first result is indicated by `data.ty`. If the instruction produces
|
||||
/// multiple results, also call `make_inst_results` to allocate value table entries.
|
||||
pub fn make_inst(&mut self, data: InstructionData) -> Inst {
|
||||
self.insts.push(data)
|
||||
}
|
||||
|
||||
/// Create result values for an instruction that produces multiple results.
|
||||
///
|
||||
/// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If
|
||||
/// the instruction may produce more than 1 result, call `make_inst_results` to allocate
|
||||
/// value table entries for the additional results.
|
||||
///
|
||||
/// The result value types are determined from the instruction's value type constraints and the
|
||||
/// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic
|
||||
/// instructions, `ctrl_typevar` is ignored, and `VOID` can be used.
|
||||
///
|
||||
/// The type of the first result value is also set, even if it was already set in the
|
||||
/// `InstructionData` passed to `make_inst`. If this function is called with a single-result
|
||||
/// instruction, that is the only effect.
|
||||
///
|
||||
/// Returns the number of results produced by the instruction.
|
||||
pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize {
|
||||
let constraints = self.insts[inst].opcode().constraints();
|
||||
let fixed_results = constraints.fixed_results();
|
||||
|
||||
// Additional values form a linked list starting from the second result value. Generate
|
||||
// the list backwards so we don't have to modify value table entries in place. (This
|
||||
// causes additional result values to be numbered backwards which is not the aestetic
|
||||
// choice, but since it is only visible in extremely rare instructions with 3+ results,
|
||||
// we don't care).
|
||||
let mut head = NO_VALUE;
|
||||
let mut first_type = Type::default();
|
||||
|
||||
// TBD: Function call return values for direct and indirect function calls.
|
||||
|
||||
if fixed_results > 0 {
|
||||
for res_idx in (1..fixed_results).rev() {
|
||||
head = self.make_value(ValueData::Inst {
|
||||
ty: constraints.result_type(res_idx, ctrl_typevar),
|
||||
num: res_idx as u16,
|
||||
inst: inst,
|
||||
next: head,
|
||||
});
|
||||
}
|
||||
first_type = constraints.result_type(0, ctrl_typevar);
|
||||
}
|
||||
|
||||
// Update the second_result pointer in `inst`.
|
||||
if head != NO_VALUE {
|
||||
*self.insts[inst]
|
||||
.second_result_mut()
|
||||
.expect("instruction format doesn't allow multiple results") = head;
|
||||
}
|
||||
*self.insts[inst].first_type_mut() = first_type;
|
||||
|
||||
fixed_results
|
||||
}
|
||||
|
||||
/// Get the first result of an instruction.
|
||||
///
|
||||
/// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type.
|
||||
pub fn first_result(&self, inst: Inst) -> Value {
|
||||
Value::new_direct(inst)
|
||||
}
|
||||
|
||||
/// Iterate through all the results of an instruction.
|
||||
pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> {
|
||||
Values {
|
||||
dfg: self,
|
||||
cur: if self.insts[inst].first_type().is_void() {
|
||||
NO_VALUE
|
||||
} else {
|
||||
Value::new_direct(inst)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow immutable access to instructions via indexing.
|
||||
impl Index<Inst> for DataFlowGraph {
|
||||
type Output = InstructionData;
|
||||
|
||||
fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData {
|
||||
&self.insts[inst]
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow mutable access to instructions via indexing.
|
||||
impl IndexMut<Inst> for DataFlowGraph {
|
||||
fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData {
|
||||
&mut self.insts[inst]
|
||||
}
|
||||
}
|
||||
|
||||
/// Extended basic blocks.
|
||||
impl DataFlowGraph {
|
||||
/// Create a new basic block.
|
||||
pub fn make_ebb(&mut self) -> Ebb {
|
||||
self.ebbs.push(EbbData::new())
|
||||
}
|
||||
|
||||
/// Get the number of arguments on `ebb`.
|
||||
pub fn num_ebb_args(&self, ebb: Ebb) -> usize {
|
||||
let last_arg = self.ebbs[ebb].last_arg;
|
||||
match last_arg.expand() {
|
||||
ExpandedValue::None => 0,
|
||||
ExpandedValue::Table(idx) => {
|
||||
if let ValueData::Arg { num, .. } = self.extended_values[idx] {
|
||||
num as usize + 1
|
||||
} else {
|
||||
panic!("inconsistent value table entry for EBB arg");
|
||||
}
|
||||
}
|
||||
ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append an argument with type `ty` to `ebb`.
|
||||
pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value {
|
||||
let num_args = self.num_ebb_args(ebb);
|
||||
assert!(num_args <= u16::MAX as usize, "Too many arguments to EBB");
|
||||
let val = self.make_value(ValueData::Arg {
|
||||
ty: ty,
|
||||
ebb: ebb,
|
||||
num: num_args as u16,
|
||||
next: NO_VALUE,
|
||||
});
|
||||
let last_arg = self.ebbs[ebb].last_arg;
|
||||
match last_arg.expand() {
|
||||
// If last_arg is NO_VALUE, we're adding the first EBB argument.
|
||||
ExpandedValue::None => {
|
||||
self.ebbs[ebb].first_arg = val;
|
||||
}
|
||||
// Append to linked list of arguments.
|
||||
ExpandedValue::Table(idx) => {
|
||||
if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] {
|
||||
*next = val;
|
||||
} else {
|
||||
panic!("inconsistent value table entry for EBB arg");
|
||||
}
|
||||
}
|
||||
ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"),
|
||||
};
|
||||
self.ebbs[ebb].last_arg = val;
|
||||
val
|
||||
}
|
||||
|
||||
/// Iterate through the arguments to an EBB.
|
||||
pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> {
|
||||
Values {
|
||||
dfg: self,
|
||||
cur: self.ebbs[ebb].first_arg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contents of an extended basic block.
|
||||
//
|
||||
// Arguments for an extended basic block are values that dominate everything in the EBB. All
|
||||
// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must
|
||||
// match the function arguments.
|
||||
struct EbbData {
|
||||
// First argument to this EBB, or `NO_VALUE` if the block has no arguments.
|
||||
//
|
||||
// The arguments are all ValueData::Argument entries that form a linked list from `first_arg`
|
||||
// to `last_arg`.
|
||||
first_arg: Value,
|
||||
|
||||
// Last argument to this EBB, or `NO_VALUE` if the block has no arguments.
|
||||
last_arg: Value,
|
||||
}
|
||||
|
||||
impl EbbData {
|
||||
fn new() -> EbbData {
|
||||
EbbData {
|
||||
first_arg: NO_VALUE,
|
||||
last_arg: NO_VALUE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use repr::types;
|
||||
use repr::instructions::{Opcode, InstructionData};
|
||||
|
||||
#[test]
|
||||
fn make_inst() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let idata = InstructionData::Nullary {
|
||||
opcode: Opcode::Iconst,
|
||||
ty: types::I32,
|
||||
};
|
||||
let inst = dfg.make_inst(idata);
|
||||
assert_eq!(inst.to_string(), "inst0");
|
||||
|
||||
// Immutable reference resolution.
|
||||
{
|
||||
let immdfg = &dfg;
|
||||
let ins = &immdfg[inst];
|
||||
assert_eq!(ins.opcode(), Opcode::Iconst);
|
||||
assert_eq!(ins.first_type(), types::I32);
|
||||
}
|
||||
|
||||
// Result iterator.
|
||||
let mut res = dfg.inst_results(inst);
|
||||
let val = res.next().unwrap();
|
||||
assert!(res.next().is_none());
|
||||
|
||||
assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0));
|
||||
assert_eq!(dfg.value_type(val), types::I32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_results() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let idata = InstructionData::Nullary {
|
||||
opcode: Opcode::Trap,
|
||||
ty: types::VOID,
|
||||
};
|
||||
let inst = dfg.make_inst(idata);
|
||||
|
||||
// Result iterator should be empty.
|
||||
let mut res = dfg.inst_results(inst);
|
||||
assert_eq!(res.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ebb() {
|
||||
let mut dfg = DataFlowGraph::new();
|
||||
|
||||
let ebb = dfg.make_ebb();
|
||||
assert_eq!(ebb.to_string(), "ebb0");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 0);
|
||||
assert_eq!(dfg.ebb_args(ebb).next(), None);
|
||||
|
||||
let arg1 = dfg.append_ebb_arg(ebb, types::F32);
|
||||
assert_eq!(arg1.to_string(), "vx0");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 1);
|
||||
{
|
||||
let mut args1 = dfg.ebb_args(ebb);
|
||||
assert_eq!(args1.next(), Some(arg1));
|
||||
assert_eq!(args1.next(), None);
|
||||
}
|
||||
let arg2 = dfg.append_ebb_arg(ebb, types::I16);
|
||||
assert_eq!(arg2.to_string(), "vx1");
|
||||
assert_eq!(dfg.num_ebb_args(ebb), 2);
|
||||
{
|
||||
let mut args2 = dfg.ebb_args(ebb);
|
||||
assert_eq!(args2.next(), Some(arg1));
|
||||
assert_eq!(args2.next(), Some(arg2));
|
||||
assert_eq!(args2.next(), None);
|
||||
}
|
||||
|
||||
assert_eq!(dfg.value_def(arg1), ValueDef::Arg(ebb, 0));
|
||||
assert_eq!(dfg.value_def(arg2), ValueDef::Arg(ebb, 1));
|
||||
assert_eq!(dfg.value_type(arg1), types::F32);
|
||||
assert_eq!(dfg.value_type(arg2), types::I16);
|
||||
}
|
||||
}
|
||||
279
cranelift/src/libcretonne/repr/entities.rs
Normal file
279
cranelift/src/libcretonne/repr/entities.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
//! IL entity references.
|
||||
//!
|
||||
//! Instructions in Cretonne IL need to reference other entities in the function. This can be other
|
||||
//! parts of the function like extended basic blocks or stack slots, or it can be external entities
|
||||
//! that are declared in the function preamble in the text format.
|
||||
//!
|
||||
//! These entity references in instruction operands are not implemented as Rust references both
|
||||
//! because Rust's ownership and mutability rules make it difficult, and because 64-bit pointers
|
||||
//! take up a lot of space, and we want a compact in-memory representation. Instead, entity
|
||||
//! references are structs wrapping a `u32` index into a table in the `Function` main data
|
||||
//! structure. There is a separate index type for each entity type, so we don't lose type safety.
|
||||
//!
|
||||
//! The `entities` module defines public types for the entity references along with constants
|
||||
//! representing an invalid reference. We prefer to use `Option<EntityRef>` whenever possible, but
|
||||
//! unfortunately that type is twice as large as the 32-bit index type on its own. Thus, compact
|
||||
//! data structures use the sentinen constant, while function arguments and return values prefer
|
||||
//! the more Rust-like `Option<EntityRef>` variant.
|
||||
//!
|
||||
//! The entity references all implement the `Display` trait in a way that matches the textual IL
|
||||
//! format.
|
||||
|
||||
use entity_map::EntityRef;
|
||||
use std::default::Default;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::u32;
|
||||
|
||||
/// An opaque reference to an extended basic block in a function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Ebb(u32);
|
||||
|
||||
impl EntityRef for Ebb {
|
||||
fn new(index: usize) -> Self {
|
||||
assert!(index < (u32::MAX as usize));
|
||||
Ebb(index as u32)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Ebb {
|
||||
/// Create a new EBB reference from its number. This corresponds to the ebbNN representation.
|
||||
pub fn with_number(n: u32) -> Option<Ebb> {
|
||||
if n < u32::MAX {
|
||||
Some(Ebb(n))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Display an `Ebb` reference as "ebb12".
|
||||
impl Display for Ebb {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "ebb{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A guaranteed invalid EBB reference.
|
||||
pub const NO_EBB: Ebb = Ebb(u32::MAX);
|
||||
|
||||
impl Default for Ebb {
|
||||
fn default() -> Ebb {
|
||||
NO_EBB
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque reference to an instruction in a function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Inst(u32);
|
||||
|
||||
impl EntityRef for Inst {
|
||||
fn new(index: usize) -> Self {
|
||||
assert!(index < (u32::MAX as usize));
|
||||
Inst(index as u32)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Display an `Inst` reference as "inst7".
|
||||
impl Display for Inst {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "inst{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A guaranteed invalid instruction reference.
|
||||
pub const NO_INST: Inst = Inst(u32::MAX);
|
||||
|
||||
impl Default for Inst {
|
||||
fn default() -> Inst {
|
||||
NO_INST
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// An opaque reference to an SSA value.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Value(u32);
|
||||
|
||||
// Value references can either reference an instruction directly, or they can refer to the extended
|
||||
// value table.
|
||||
pub enum ExpandedValue {
|
||||
// This is the first value produced by the referenced instruction.
|
||||
Direct(Inst),
|
||||
|
||||
// This value is described in the extended value table.
|
||||
Table(usize),
|
||||
|
||||
// This is NO_VALUE.
|
||||
None,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Create a `Direct` value from its number representation.
|
||||
/// This is the number in the vNN notation.
|
||||
pub fn direct_with_number(n: u32) -> Option<Value> {
|
||||
if n < u32::MAX / 2 {
|
||||
let encoding = n * 2;
|
||||
assert!(encoding < u32::MAX);
|
||||
Some(Value(encoding))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Table` value from its number representation.
|
||||
/// This is the number in the vxNN notation.
|
||||
pub fn table_with_number(n: u32) -> Option<Value> {
|
||||
if n < u32::MAX / 2 {
|
||||
let encoding = n * 2 + 1;
|
||||
assert!(encoding < u32::MAX);
|
||||
Some(Value(encoding))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn new_direct(i: Inst) -> Value {
|
||||
let encoding = i.index() * 2;
|
||||
assert!(encoding < u32::MAX as usize);
|
||||
Value(encoding as u32)
|
||||
}
|
||||
|
||||
pub fn new_table(index: usize) -> Value {
|
||||
let encoding = index * 2 + 1;
|
||||
assert!(encoding < u32::MAX as usize);
|
||||
Value(encoding as u32)
|
||||
}
|
||||
|
||||
// Expand the internal representation into something useful.
|
||||
pub fn expand(&self) -> ExpandedValue {
|
||||
use self::ExpandedValue::*;
|
||||
if *self == NO_VALUE {
|
||||
return None;
|
||||
}
|
||||
let index = (self.0 / 2) as usize;
|
||||
if self.0 % 2 == 0 {
|
||||
Direct(Inst::new(index))
|
||||
} else {
|
||||
Table(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a `Value` reference as "v7" or "v2x".
|
||||
impl Display for Value {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
use self::ExpandedValue::*;
|
||||
match self.expand() {
|
||||
Direct(i) => write!(fmt, "v{}", i.0),
|
||||
Table(i) => write!(fmt, "vx{}", i),
|
||||
None => write!(fmt, "NO_VALUE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A guaranteed invalid value reference.
|
||||
pub const NO_VALUE: Value = Value(u32::MAX);
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Value {
|
||||
NO_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque reference to a stack slot.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct StackSlot(u32);
|
||||
|
||||
impl EntityRef for StackSlot {
|
||||
fn new(index: usize) -> StackSlot {
|
||||
assert!(index < (u32::MAX as usize));
|
||||
StackSlot(index as u32)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a `StackSlot` reference as "ss12".
|
||||
impl Display for StackSlot {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "ss{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A guaranteed invalid stack slot reference.
|
||||
pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX);
|
||||
|
||||
impl Default for StackSlot {
|
||||
fn default() -> StackSlot {
|
||||
NO_STACK_SLOT
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque reference to a jump table.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct JumpTable(u32);
|
||||
|
||||
impl EntityRef for JumpTable {
|
||||
fn new(index: usize) -> JumpTable {
|
||||
assert!(index < (u32::MAX as usize));
|
||||
JumpTable(index as u32)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a `JumpTable` reference as "jt12".
|
||||
impl Display for JumpTable {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
write!(fmt, "jt{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A guaranteed invalid jump table reference.
|
||||
pub const NO_JUMP_TABLE: JumpTable = JumpTable(u32::MAX);
|
||||
|
||||
impl Default for JumpTable {
|
||||
fn default() -> JumpTable {
|
||||
NO_JUMP_TABLE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::u32;
|
||||
use entity_map::EntityRef;
|
||||
|
||||
#[test]
|
||||
fn value_with_number() {
|
||||
assert_eq!(Value::direct_with_number(0).unwrap().to_string(), "v0");
|
||||
assert_eq!(Value::direct_with_number(1).unwrap().to_string(), "v1");
|
||||
assert_eq!(Value::table_with_number(0).unwrap().to_string(), "vx0");
|
||||
assert_eq!(Value::table_with_number(1).unwrap().to_string(), "vx1");
|
||||
|
||||
assert_eq!(Value::direct_with_number(u32::MAX / 2), None);
|
||||
assert_eq!(match Value::direct_with_number(u32::MAX / 2 - 1).unwrap().expand() {
|
||||
ExpandedValue::Direct(i) => i.index() as u32,
|
||||
_ => u32::MAX,
|
||||
},
|
||||
u32::MAX / 2 - 1);
|
||||
|
||||
assert_eq!(Value::table_with_number(u32::MAX / 2), None);
|
||||
assert_eq!(match Value::table_with_number(u32::MAX / 2 - 1).unwrap().expand() {
|
||||
ExpandedValue::Table(i) => i as u32,
|
||||
_ => u32::MAX,
|
||||
},
|
||||
u32::MAX / 2 - 1);
|
||||
}
|
||||
}
|
||||
706
cranelift/src/libcretonne/repr/immediates.rs
Normal file
706
cranelift/src/libcretonne/repr/immediates.rs
Normal file
@@ -0,0 +1,706 @@
|
||||
|
||||
//! Immediate operands for Cretonne instructions
|
||||
//!
|
||||
//! This module defines the types of immediate operands that can appear on Cretonne instructions.
|
||||
//! Each type here should have a corresponding definition in the `cretonne.immediates` Python
|
||||
//! module in the meta language.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::mem;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// 64-bit immediate integer operand.
|
||||
///
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Imm64(i64);
|
||||
|
||||
impl Imm64 {
|
||||
pub fn from_bits(x: u64) -> Imm64 {
|
||||
Imm64(x as i64)
|
||||
}
|
||||
|
||||
pub fn to_bits(&self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Imm64 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let x = self.0;
|
||||
if -10_000 < x && x < 10_000 {
|
||||
// Use decimal for small numbers.
|
||||
write!(f, "{}", x)
|
||||
} else {
|
||||
// Hexadecimal with a multiple of 4 digits and group separators:
|
||||
//
|
||||
// 0xfff0
|
||||
// 0x0001_ffff
|
||||
// 0xffff_ffff_fff8_4400
|
||||
//
|
||||
let mut pos = (64 - x.leading_zeros() - 1) & 0xf0;
|
||||
try!(write!(f, "0x{:04x}", (x >> pos) & 0xffff));
|
||||
while pos > 0 {
|
||||
pos -= 16;
|
||||
try!(write!(f, "_{:04x}", (x >> pos) & 0xffff));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Imm64 {
|
||||
type Err = &'static str;
|
||||
|
||||
// Parse a decimal or hexadecimal Imm64, formatted as above.
|
||||
fn from_str(s: &str) -> Result<Imm64, &'static str> {
|
||||
let mut value: u64 = 0;
|
||||
let mut digits = 0;
|
||||
let negative = s.starts_with('-');
|
||||
let s2 = if negative {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
if s2.starts_with("0x") {
|
||||
// Hexadecimal.
|
||||
for ch in s2[2..].chars() {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
if digits > 16 {
|
||||
return Err("Too many hexadecimal digits in Imm64");
|
||||
}
|
||||
// This can't overflow given the digit limit.
|
||||
value = (value << 4) | digit as u64;
|
||||
}
|
||||
None => {
|
||||
// Allow embedded underscores, but fail on anything else.
|
||||
if ch != '_' {
|
||||
return Err("Invalid character in hexadecimal Imm64");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Decimal number, possibly negative.
|
||||
for ch in s2.chars() {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
match value.checked_mul(10) {
|
||||
None => return Err("Too large decimal Imm64"),
|
||||
Some(v) => value = v,
|
||||
}
|
||||
match value.checked_add(digit as u64) {
|
||||
None => return Err("Too large decimal Imm64"),
|
||||
Some(v) => value = v,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Allow embedded underscores, but fail on anything else.
|
||||
if ch != '_' {
|
||||
return Err("Invalid character in decimal Imm64");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if digits == 0 {
|
||||
return Err("No digits in Imm64");
|
||||
}
|
||||
|
||||
// We support the range-and-a-half from -2^63 .. 2^64-1.
|
||||
if negative {
|
||||
value = value.wrapping_neg();
|
||||
// Don't allow large negative values to wrap around and become positive.
|
||||
if value as i64 > 0 {
|
||||
return Err("Negative number too small for Imm64");
|
||||
}
|
||||
}
|
||||
Ok(Imm64::from_bits(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// An IEEE binary32 immediate floating point value.
|
||||
///
|
||||
/// All bit patterns are allowed.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Ieee32(f32);
|
||||
|
||||
/// An IEEE binary64 immediate floating point value.
|
||||
///
|
||||
/// All bit patterns are allowed.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Ieee64(f64);
|
||||
|
||||
// Format a floating point number in a way that is reasonably human-readable, and that can be
|
||||
// converted back to binary without any rounding issues. The hexadecimal formatting of normal and
|
||||
// subnormal numbers is compatible with C99 and the printf "%a" format specifier. The NaN and Inf
|
||||
// formats are not supported by C99.
|
||||
//
|
||||
// The encoding parameters are:
|
||||
//
|
||||
// w - exponent field width in bits
|
||||
// t - trailing significand field width in bits
|
||||
//
|
||||
fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
|
||||
debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
|
||||
debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
|
||||
debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
|
||||
|
||||
let max_e_bits = (1u64 << w) - 1;
|
||||
let t_bits = bits & ((1u64 << t) - 1); // Trailing significand.
|
||||
let e_bits = (bits >> t) & max_e_bits; // Biased exponent.
|
||||
let sign_bit = (bits >> w + t) & 1;
|
||||
|
||||
let bias: i32 = (1 << (w - 1)) - 1;
|
||||
let e = e_bits as i32 - bias; // Unbiased exponent.
|
||||
let emin = 1 - bias; // Minimum exponent.
|
||||
|
||||
// How many hexadecimal digits are needed for the trailing significand?
|
||||
let digits = (t + 3) / 4;
|
||||
// Trailing significand left-aligned in `digits` hexadecimal digits.
|
||||
let left_t_bits = t_bits << (4 * digits - t);
|
||||
|
||||
// All formats share the leading sign.
|
||||
if sign_bit != 0 {
|
||||
try!(write!(f, "-"));
|
||||
}
|
||||
|
||||
if e_bits == 0 {
|
||||
if t_bits == 0 {
|
||||
// Zero.
|
||||
write!(f, "0.0")
|
||||
} else {
|
||||
// Subnormal.
|
||||
write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin)
|
||||
}
|
||||
} else if e_bits == max_e_bits {
|
||||
if t_bits == 0 {
|
||||
// Infinity.
|
||||
write!(f, "Inf")
|
||||
} else {
|
||||
// NaN.
|
||||
let payload = t_bits & ((1 << (t - 1)) - 1);
|
||||
if t_bits & (1 << (t - 1)) != 0 {
|
||||
// Quiet NaN.
|
||||
if payload != 0 {
|
||||
write!(f, "NaN:0x{:x}", payload)
|
||||
} else {
|
||||
write!(f, "NaN")
|
||||
}
|
||||
} else {
|
||||
// Signaling NaN.
|
||||
write!(f, "sNaN:0x{:x}", payload)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal number.
|
||||
write!(f, "0x1.{0:01$x}p{2}", left_t_bits, digits as usize, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a float using the same format as `format_float` above.
|
||||
//
|
||||
// The encoding parameters are:
|
||||
//
|
||||
// w - exponent field width in bits
|
||||
// t - trailing significand field width in bits
|
||||
//
|
||||
fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
|
||||
debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
|
||||
debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
|
||||
debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
|
||||
|
||||
let (sign_bit, s2) = if s.starts_with('-') {
|
||||
(1u64 << t + w, &s[1..])
|
||||
} else {
|
||||
(0, s)
|
||||
};
|
||||
|
||||
if !s2.starts_with("0x") {
|
||||
let max_e_bits = ((1u64 << w) - 1) << t;
|
||||
let quiet_bit = 1u64 << (t - 1);
|
||||
|
||||
// The only decimal encoding allowed is 0.
|
||||
if s2 == "0.0" {
|
||||
return Ok(sign_bit);
|
||||
}
|
||||
|
||||
if s2 == "Inf" {
|
||||
// +/- infinity: e = max, t = 0.
|
||||
return Ok(sign_bit | max_e_bits);
|
||||
}
|
||||
if s2 == "NaN" {
|
||||
// Canonical quiet NaN: e = max, t = quiet.
|
||||
return Ok(sign_bit | max_e_bits | quiet_bit);
|
||||
}
|
||||
if s2.starts_with("NaN:0x") {
|
||||
// Quiet NaN with payload.
|
||||
return match u64::from_str_radix(&s2[6..], 16) {
|
||||
Ok(payload) if payload < quiet_bit => {
|
||||
Ok(sign_bit | max_e_bits | quiet_bit | payload)
|
||||
}
|
||||
_ => Err("Invalid NaN payload"),
|
||||
};
|
||||
}
|
||||
if s2.starts_with("sNaN:0x") {
|
||||
// Signaling NaN with payload.
|
||||
return match u64::from_str_radix(&s2[7..], 16) {
|
||||
Ok(payload) if 0 < payload && payload < quiet_bit => {
|
||||
Ok(sign_bit | max_e_bits | payload)
|
||||
}
|
||||
_ => Err("Invalid sNaN payload"),
|
||||
};
|
||||
}
|
||||
|
||||
return Err("Float must be hexadecimal");
|
||||
}
|
||||
let s3 = &s2[2..];
|
||||
|
||||
let mut digits = 0u8;
|
||||
let mut digits_before_period: Option<u8> = None;
|
||||
let mut significand = 0u64;
|
||||
let mut exponent = 0i32;
|
||||
|
||||
for (idx, ch) in s3.char_indices() {
|
||||
match ch {
|
||||
'.' => {
|
||||
// This is the radix point. There can only be one.
|
||||
if digits_before_period != None {
|
||||
return Err("Multiple radix points");
|
||||
} else {
|
||||
digits_before_period = Some(digits);
|
||||
}
|
||||
}
|
||||
'p' => {
|
||||
// The following exponent is a decimal number.
|
||||
let exp_str = &s3[1 + idx..];
|
||||
match exp_str.parse::<i16>() {
|
||||
Ok(e) => {
|
||||
exponent = e as i32;
|
||||
break;
|
||||
}
|
||||
Err(_) => return Err("Bad exponent"),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
if digits > 16 {
|
||||
return Err("Too many digits");
|
||||
}
|
||||
significand = (significand << 4) | digit as u64;
|
||||
}
|
||||
None => return Err("Invalid character"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if digits == 0 {
|
||||
return Err("No digits");
|
||||
}
|
||||
|
||||
if significand == 0 {
|
||||
// This is +/- 0.0.
|
||||
return Ok(sign_bit);
|
||||
}
|
||||
|
||||
// Number of bits appearing after the radix point.
|
||||
match digits_before_period {
|
||||
None => {} // No radix point present.
|
||||
Some(d) => exponent -= 4 * (digits - d) as i32,
|
||||
};
|
||||
|
||||
// Normalize the significand and exponent.
|
||||
let significant_bits = (64 - significand.leading_zeros()) as u8;
|
||||
if significant_bits > t + 1 {
|
||||
let adjust = significant_bits - (t + 1);
|
||||
if significand & ((1u64 << adjust) - 1) != 0 {
|
||||
return Err("Too many significant bits");
|
||||
}
|
||||
// Adjust significand down.
|
||||
significand >>= adjust;
|
||||
exponent += adjust as i32;
|
||||
} else {
|
||||
let adjust = t + 1 - significant_bits;
|
||||
significand <<= adjust;
|
||||
exponent -= adjust as i32;
|
||||
}
|
||||
assert_eq!(significand >> t, 1);
|
||||
|
||||
// Trailing significand excludes the high bit.
|
||||
let t_bits = significand & ((1 << t) - 1);
|
||||
|
||||
let max_exp = (1i32 << w) - 2;
|
||||
let bias: i32 = (1 << (w - 1)) - 1;
|
||||
exponent += bias + t as i32;
|
||||
|
||||
if exponent > max_exp {
|
||||
Err("Magnitude too large")
|
||||
} else if exponent > 0 {
|
||||
// This is a normal number.
|
||||
let e_bits = (exponent as u64) << t;
|
||||
Ok(sign_bit | e_bits | t_bits)
|
||||
} else if 1 - exponent <= t as i32 {
|
||||
// This is a subnormal number: e = 0, t = significand bits.
|
||||
// Renormalize significand for exponent = 1.
|
||||
let adjust = 1 - exponent;
|
||||
if significand & ((1u64 << adjust) - 1) != 0 {
|
||||
Err("Subnormal underflow")
|
||||
} else {
|
||||
significand >>= adjust;
|
||||
Ok(sign_bit | significand)
|
||||
}
|
||||
} else {
|
||||
Err("Magnitude too small")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ieee32 {
|
||||
pub fn new(x: f32) -> Ieee32 {
|
||||
Ieee32(x)
|
||||
}
|
||||
|
||||
/// Construct Ieee32 immediate from raw bits.
|
||||
pub fn from_bits(x: u32) -> Ieee32 {
|
||||
Ieee32(unsafe { mem::transmute(x) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ieee32 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let bits: u32 = unsafe { mem::transmute(self.0) };
|
||||
format_float(bits as u64, 8, 23, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ieee32 {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Ieee32, &'static str> {
|
||||
match parse_float(s, 8, 23) {
|
||||
Ok(b) => Ok(Ieee32::from_bits(b as u32)),
|
||||
Err(s) => Err(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ieee64 {
|
||||
pub fn new(x: f64) -> Ieee64 {
|
||||
Ieee64(x)
|
||||
}
|
||||
|
||||
/// Construct Ieee64 immediate from raw bits.
|
||||
pub fn from_bits(x: u64) -> Ieee64 {
|
||||
Ieee64(unsafe { mem::transmute(x) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ieee64 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let bits: u64 = unsafe { mem::transmute(self.0) };
|
||||
format_float(bits, 11, 52, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ieee64 {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Ieee64, &'static str> {
|
||||
match parse_float(s, 11, 52) {
|
||||
Ok(b) => Ok(Ieee64::from_bits(b)),
|
||||
Err(s) => Err(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{f32, f64};
|
||||
use std::str::FromStr;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[test]
|
||||
fn format_imm64() {
|
||||
assert_eq!(Imm64(0).to_string(), "0");
|
||||
assert_eq!(Imm64(9999).to_string(), "9999");
|
||||
assert_eq!(Imm64(10000).to_string(), "0x2710");
|
||||
assert_eq!(Imm64(-9999).to_string(), "-9999");
|
||||
assert_eq!(Imm64(-10000).to_string(), "0xffff_ffff_ffff_d8f0");
|
||||
assert_eq!(Imm64(0xffff).to_string(), "0xffff");
|
||||
assert_eq!(Imm64(0x10000).to_string(), "0x0001_0000");
|
||||
}
|
||||
|
||||
// Verify that `text` can be parsed as a `T` into a value that displays as `want`.
|
||||
fn parse_ok<T: FromStr + Display>(text: &str, want: &str)
|
||||
where <T as FromStr>::Err: Display
|
||||
{
|
||||
match text.parse::<T>() {
|
||||
Err(s) => panic!("\"{}\".parse() error: {}", text, s),
|
||||
Ok(x) => assert_eq!(x.to_string(), want),
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that `text` fails to parse as `T` with the error `msg`.
|
||||
fn parse_err<T: FromStr + Display>(text: &str, msg: &str)
|
||||
where <T as FromStr>::Err: Display
|
||||
{
|
||||
match text.parse::<T>() {
|
||||
Err(s) => assert_eq!(s.to_string(), msg),
|
||||
Ok(x) => panic!("Wanted Err({}), but got {}", msg, x),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_imm64() {
|
||||
parse_ok::<Imm64>("0", "0");
|
||||
parse_ok::<Imm64>("1", "1");
|
||||
parse_ok::<Imm64>("-0", "0");
|
||||
parse_ok::<Imm64>("-1", "-1");
|
||||
parse_ok::<Imm64>("0x0", "0");
|
||||
parse_ok::<Imm64>("0xf", "15");
|
||||
parse_ok::<Imm64>("-0x9", "-9");
|
||||
|
||||
// Probe limits.
|
||||
parse_ok::<Imm64>("0xffffffff_ffffffff", "-1");
|
||||
parse_ok::<Imm64>("0x80000000_00000000", "0x8000_0000_0000_0000");
|
||||
parse_ok::<Imm64>("-0x80000000_00000000", "0x8000_0000_0000_0000");
|
||||
parse_err::<Imm64>("-0x80000000_00000001",
|
||||
"Negative number too small for Imm64");
|
||||
parse_ok::<Imm64>("18446744073709551615", "-1");
|
||||
parse_ok::<Imm64>("-9223372036854775808", "0x8000_0000_0000_0000");
|
||||
// Overflow both the checked_add and checked_mul.
|
||||
parse_err::<Imm64>("18446744073709551616", "Too large decimal Imm64");
|
||||
parse_err::<Imm64>("184467440737095516100", "Too large decimal Imm64");
|
||||
parse_err::<Imm64>("-9223372036854775809",
|
||||
"Negative number too small for Imm64");
|
||||
|
||||
// Underscores are allowed where digits go.
|
||||
parse_ok::<Imm64>("0_0", "0");
|
||||
parse_ok::<Imm64>("-_10_0", "-100");
|
||||
parse_ok::<Imm64>("_10_", "10");
|
||||
parse_ok::<Imm64>("0x97_88_bb", "0x0097_88bb");
|
||||
parse_ok::<Imm64>("0x_97_", "151");
|
||||
|
||||
parse_err::<Imm64>("", "No digits in Imm64");
|
||||
parse_err::<Imm64>("-", "No digits in Imm64");
|
||||
parse_err::<Imm64>("_", "No digits in Imm64");
|
||||
parse_err::<Imm64>("0x", "No digits in Imm64");
|
||||
parse_err::<Imm64>("0x_", "No digits in Imm64");
|
||||
parse_err::<Imm64>("-0x", "No digits in Imm64");
|
||||
parse_err::<Imm64>(" ", "Invalid character in decimal Imm64");
|
||||
parse_err::<Imm64>("0 ", "Invalid character in decimal Imm64");
|
||||
parse_err::<Imm64>(" 0", "Invalid character in decimal Imm64");
|
||||
parse_err::<Imm64>("--", "Invalid character in decimal Imm64");
|
||||
parse_err::<Imm64>("-0x-", "Invalid character in hexadecimal Imm64");
|
||||
|
||||
// Hex count overflow.
|
||||
parse_err::<Imm64>("0x0_0000_0000_0000_0000",
|
||||
"Too many hexadecimal digits in Imm64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_ieee32() {
|
||||
assert_eq!(Ieee32::new(0.0).to_string(), "0.0");
|
||||
assert_eq!(Ieee32::new(-0.0).to_string(), "-0.0");
|
||||
assert_eq!(Ieee32::new(1.0).to_string(), "0x1.000000p0");
|
||||
assert_eq!(Ieee32::new(1.5).to_string(), "0x1.800000p0");
|
||||
assert_eq!(Ieee32::new(0.5).to_string(), "0x1.000000p-1");
|
||||
assert_eq!(Ieee32::new(f32::EPSILON).to_string(), "0x1.000000p-23");
|
||||
assert_eq!(Ieee32::new(f32::MIN).to_string(), "-0x1.fffffep127");
|
||||
assert_eq!(Ieee32::new(f32::MAX).to_string(), "0x1.fffffep127");
|
||||
// Smallest positive normal number.
|
||||
assert_eq!(Ieee32::new(f32::MIN_POSITIVE).to_string(),
|
||||
"0x1.000000p-126");
|
||||
// Subnormals.
|
||||
assert_eq!(Ieee32::new(f32::MIN_POSITIVE / 2.0).to_string(),
|
||||
"0x0.800000p-126");
|
||||
assert_eq!(Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON).to_string(),
|
||||
"0x0.000002p-126");
|
||||
assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "Inf");
|
||||
assert_eq!(Ieee32::new(f32::NEG_INFINITY).to_string(), "-Inf");
|
||||
assert_eq!(Ieee32::new(f32::NAN).to_string(), "NaN");
|
||||
assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN");
|
||||
// Construct some qNaNs with payloads.
|
||||
assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "NaN:0x1");
|
||||
assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "NaN:0x300001");
|
||||
// Signaling NaNs.
|
||||
assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "sNaN:0x1");
|
||||
assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "sNaN:0x200001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ieee32() {
|
||||
parse_ok::<Ieee32>("0.0", "0.0");
|
||||
parse_ok::<Ieee32>("-0.0", "-0.0");
|
||||
parse_ok::<Ieee32>("0x0", "0.0");
|
||||
parse_ok::<Ieee32>("0x0.0", "0.0");
|
||||
parse_ok::<Ieee32>("0x.0", "0.0");
|
||||
parse_ok::<Ieee32>("0x0.", "0.0");
|
||||
parse_ok::<Ieee32>("0x1", "0x1.000000p0");
|
||||
parse_ok::<Ieee32>("-0x1", "-0x1.000000p0");
|
||||
parse_ok::<Ieee32>("0x10", "0x1.000000p4");
|
||||
parse_ok::<Ieee32>("0x10.0", "0x1.000000p4");
|
||||
parse_err::<Ieee32>("0.", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>(".0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("-0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>(".", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("-", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("0x", "No digits");
|
||||
parse_err::<Ieee32>("0x..", "Multiple radix points");
|
||||
|
||||
// Check significant bits.
|
||||
parse_ok::<Ieee32>("0x0.ffffff", "0x1.fffffep-1");
|
||||
parse_ok::<Ieee32>("0x1.fffffe", "0x1.fffffep0");
|
||||
parse_ok::<Ieee32>("0x3.fffffc", "0x1.fffffep1");
|
||||
parse_ok::<Ieee32>("0x7.fffff8", "0x1.fffffep2");
|
||||
parse_ok::<Ieee32>("0xf.fffff0", "0x1.fffffep3");
|
||||
parse_err::<Ieee32>("0x1.ffffff", "Too many significant bits");
|
||||
parse_err::<Ieee32>("0x1.fffffe0000000000", "Too many digits");
|
||||
|
||||
// Exponents.
|
||||
parse_ok::<Ieee32>("0x1p3", "0x1.000000p3");
|
||||
parse_ok::<Ieee32>("0x1p-3", "0x1.000000p-3");
|
||||
parse_ok::<Ieee32>("0x1.0p3", "0x1.000000p3");
|
||||
parse_ok::<Ieee32>("0x2.0p3", "0x1.000000p4");
|
||||
parse_ok::<Ieee32>("0x1.0p127", "0x1.000000p127");
|
||||
parse_ok::<Ieee32>("0x1.0p-126", "0x1.000000p-126");
|
||||
parse_ok::<Ieee32>("0x0.1p-122", "0x1.000000p-126");
|
||||
parse_err::<Ieee32>("0x2.0p127", "Magnitude too large");
|
||||
|
||||
// Subnormals.
|
||||
parse_ok::<Ieee32>("0x1.0p-127", "0x0.800000p-126");
|
||||
parse_ok::<Ieee32>("0x1.0p-149", "0x0.000002p-126");
|
||||
parse_ok::<Ieee32>("0x0.000002p-126", "0x0.000002p-126");
|
||||
parse_err::<Ieee32>("0x0.100001p-126", "Subnormal underflow");
|
||||
parse_err::<Ieee32>("0x1.8p-149", "Subnormal underflow");
|
||||
parse_err::<Ieee32>("0x1.0p-150", "Magnitude too small");
|
||||
|
||||
// NaNs and Infs.
|
||||
parse_ok::<Ieee32>("Inf", "Inf");
|
||||
parse_ok::<Ieee32>("-Inf", "-Inf");
|
||||
parse_ok::<Ieee32>("NaN", "NaN");
|
||||
parse_ok::<Ieee32>("-NaN", "-NaN");
|
||||
parse_ok::<Ieee32>("NaN:0x0", "NaN");
|
||||
parse_err::<Ieee32>("NaN:", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("NaN:0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("NaN:0x", "Invalid NaN payload");
|
||||
parse_ok::<Ieee32>("NaN:0x000001", "NaN:0x1");
|
||||
parse_ok::<Ieee32>("NaN:0x300001", "NaN:0x300001");
|
||||
parse_err::<Ieee32>("NaN:0x400001", "Invalid NaN payload");
|
||||
parse_ok::<Ieee32>("sNaN:0x1", "sNaN:0x1");
|
||||
parse_err::<Ieee32>("sNaN:0x0", "Invalid sNaN payload");
|
||||
parse_ok::<Ieee32>("sNaN:0x200001", "sNaN:0x200001");
|
||||
parse_err::<Ieee32>("sNaN:0x400001", "Invalid sNaN payload");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_ieee64() {
|
||||
assert_eq!(Ieee64::new(0.0).to_string(), "0.0");
|
||||
assert_eq!(Ieee64::new(-0.0).to_string(), "-0.0");
|
||||
assert_eq!(Ieee64::new(1.0).to_string(), "0x1.0000000000000p0");
|
||||
assert_eq!(Ieee64::new(1.5).to_string(), "0x1.8000000000000p0");
|
||||
assert_eq!(Ieee64::new(0.5).to_string(), "0x1.0000000000000p-1");
|
||||
assert_eq!(Ieee64::new(f64::EPSILON).to_string(),
|
||||
"0x1.0000000000000p-52");
|
||||
assert_eq!(Ieee64::new(f64::MIN).to_string(), "-0x1.fffffffffffffp1023");
|
||||
assert_eq!(Ieee64::new(f64::MAX).to_string(), "0x1.fffffffffffffp1023");
|
||||
// Smallest positive normal number.
|
||||
assert_eq!(Ieee64::new(f64::MIN_POSITIVE).to_string(),
|
||||
"0x1.0000000000000p-1022");
|
||||
// Subnormals.
|
||||
assert_eq!(Ieee64::new(f64::MIN_POSITIVE / 2.0).to_string(),
|
||||
"0x0.8000000000000p-1022");
|
||||
assert_eq!(Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON).to_string(),
|
||||
"0x0.0000000000001p-1022");
|
||||
assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "Inf");
|
||||
assert_eq!(Ieee64::new(f64::NEG_INFINITY).to_string(), "-Inf");
|
||||
assert_eq!(Ieee64::new(f64::NAN).to_string(), "NaN");
|
||||
assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN");
|
||||
// Construct some qNaNs with payloads.
|
||||
assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), "NaN:0x1");
|
||||
assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(),
|
||||
"NaN:0x4000000000001");
|
||||
// Signaling NaNs.
|
||||
assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(),
|
||||
"sNaN:0x1");
|
||||
assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(),
|
||||
"sNaN:0x4000000000001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ieee64() {
|
||||
parse_ok::<Ieee64>("0.0", "0.0");
|
||||
parse_ok::<Ieee64>("-0.0", "-0.0");
|
||||
parse_ok::<Ieee64>("0x0", "0.0");
|
||||
parse_ok::<Ieee64>("0x0.0", "0.0");
|
||||
parse_ok::<Ieee64>("0x.0", "0.0");
|
||||
parse_ok::<Ieee64>("0x0.", "0.0");
|
||||
parse_ok::<Ieee64>("0x1", "0x1.0000000000000p0");
|
||||
parse_ok::<Ieee64>("-0x1", "-0x1.0000000000000p0");
|
||||
parse_ok::<Ieee64>("0x10", "0x1.0000000000000p4");
|
||||
parse_ok::<Ieee64>("0x10.0", "0x1.0000000000000p4");
|
||||
parse_err::<Ieee64>("0.", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>(".0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("-0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>(".", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("-", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("0x", "No digits");
|
||||
parse_err::<Ieee64>("0x..", "Multiple radix points");
|
||||
|
||||
// Check significant bits.
|
||||
parse_ok::<Ieee64>("0x0.fffffffffffff8", "0x1.fffffffffffffp-1");
|
||||
parse_ok::<Ieee64>("0x1.fffffffffffff", "0x1.fffffffffffffp0");
|
||||
parse_ok::<Ieee64>("0x3.ffffffffffffe", "0x1.fffffffffffffp1");
|
||||
parse_ok::<Ieee64>("0x7.ffffffffffffc", "0x1.fffffffffffffp2");
|
||||
parse_ok::<Ieee64>("0xf.ffffffffffff8", "0x1.fffffffffffffp3");
|
||||
parse_err::<Ieee64>("0x3.fffffffffffff", "Too many significant bits");
|
||||
parse_err::<Ieee64>("0x001.fffffe00000000", "Too many digits");
|
||||
|
||||
// Exponents.
|
||||
parse_ok::<Ieee64>("0x1p3", "0x1.0000000000000p3");
|
||||
parse_ok::<Ieee64>("0x1p-3", "0x1.0000000000000p-3");
|
||||
parse_ok::<Ieee64>("0x1.0p3", "0x1.0000000000000p3");
|
||||
parse_ok::<Ieee64>("0x2.0p3", "0x1.0000000000000p4");
|
||||
parse_ok::<Ieee64>("0x1.0p1023", "0x1.0000000000000p1023");
|
||||
parse_ok::<Ieee64>("0x1.0p-1022", "0x1.0000000000000p-1022");
|
||||
parse_ok::<Ieee64>("0x0.1p-1018", "0x1.0000000000000p-1022");
|
||||
parse_err::<Ieee64>("0x2.0p1023", "Magnitude too large");
|
||||
|
||||
// Subnormals.
|
||||
parse_ok::<Ieee64>("0x1.0p-1023", "0x0.8000000000000p-1022");
|
||||
parse_ok::<Ieee64>("0x1.0p-1074", "0x0.0000000000001p-1022");
|
||||
parse_ok::<Ieee64>("0x0.0000000000001p-1022", "0x0.0000000000001p-1022");
|
||||
parse_err::<Ieee64>("0x0.10000000000008p-1022", "Subnormal underflow");
|
||||
parse_err::<Ieee64>("0x1.8p-1074", "Subnormal underflow");
|
||||
parse_err::<Ieee64>("0x1.0p-1075", "Magnitude too small");
|
||||
|
||||
// NaNs and Infs.
|
||||
parse_ok::<Ieee64>("Inf", "Inf");
|
||||
parse_ok::<Ieee64>("-Inf", "-Inf");
|
||||
parse_ok::<Ieee64>("NaN", "NaN");
|
||||
parse_ok::<Ieee64>("-NaN", "-NaN");
|
||||
parse_ok::<Ieee64>("NaN:0x0", "NaN");
|
||||
parse_err::<Ieee64>("NaN:", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("NaN:0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("NaN:0x", "Invalid NaN payload");
|
||||
parse_ok::<Ieee64>("NaN:0x000001", "NaN:0x1");
|
||||
parse_ok::<Ieee64>("NaN:0x4000000000001", "NaN:0x4000000000001");
|
||||
parse_err::<Ieee64>("NaN:0x8000000000001", "Invalid NaN payload");
|
||||
parse_ok::<Ieee64>("sNaN:0x1", "sNaN:0x1");
|
||||
parse_err::<Ieee64>("sNaN:0x0", "Invalid sNaN payload");
|
||||
parse_ok::<Ieee64>("sNaN:0x4000000000001", "sNaN:0x4000000000001");
|
||||
parse_err::<Ieee64>("sNaN:0x8000000000001", "Invalid sNaN payload");
|
||||
}
|
||||
}
|
||||
605
cranelift/src/libcretonne/repr/instructions.rs
Normal file
605
cranelift/src/libcretonne/repr/instructions.rs
Normal file
@@ -0,0 +1,605 @@
|
||||
//! Instruction formats and opcodes.
|
||||
//!
|
||||
//! The `instructions` module contains definitions for instruction formats, opcodes, and the
|
||||
//! in-memory representation of IL instructions.
|
||||
//!
|
||||
//! A large part of this module is auto-generated from the instruction descriptions in the meta
|
||||
//! directory.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use repr::entities::*;
|
||||
use repr::immediates::*;
|
||||
use repr::condcodes::*;
|
||||
use repr::types::{self, Type};
|
||||
|
||||
// Include code generated by `meta/gen_instr.py`. This file contains:
|
||||
//
|
||||
// - The `pub enum InstructionFormat` enum with all the instruction formats.
|
||||
// - The `pub enum Opcode` definition with all known opcodes,
|
||||
// - The `const OPCODE_FORMAT: [InstructionFormat; N]` table.
|
||||
// - The private `fn opcode_name(Opcode) -> &'static str` function, and
|
||||
// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`.
|
||||
//
|
||||
// For value type constraints:
|
||||
//
|
||||
// - The `const OPCODE_CONSTRAINTS : [OpcodeConstraints; N]` table.
|
||||
// - The `const TYPE_SETS : [ValueTypeSet; N]` table.
|
||||
// - The `const OPERAND_CONSTRAINTS : [OperandConstraint; N]` table.
|
||||
//
|
||||
include!(concat!(env!("OUT_DIR"), "/opcodes.rs"));
|
||||
|
||||
impl Display for Opcode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", opcode_name(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
/// Get the instruction format for this opcode.
|
||||
pub fn format(self) -> Option<InstructionFormat> {
|
||||
if self == Opcode::NotAnOpcode {
|
||||
None
|
||||
} else {
|
||||
Some(OPCODE_FORMAT[self as usize - 1])
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the constraint descriptor for this opcode.
|
||||
/// Panic if this is called on `NotAnOpcode`.
|
||||
pub fn constraints(self) -> OpcodeConstraints {
|
||||
OPCODE_CONSTRAINTS[self as usize - 1]
|
||||
}
|
||||
}
|
||||
|
||||
// A primitive hash function for matching opcodes.
|
||||
// Must match `meta/constant_hash.py`.
|
||||
fn simple_hash(s: &str) -> u32 {
|
||||
let mut h: u32 = 5381;
|
||||
for c in s.chars() {
|
||||
h = (h ^ c as u32).wrapping_add(h.rotate_right(6));
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
// This trait really belongs in libreader where it is used by the .cton file parser, but since it
|
||||
// critically depends on the `opcode_name()` function which is needed here anyway, it lives in this
|
||||
// module. This also saves us from runing the build script twice to generate code for the two
|
||||
// separate crates.
|
||||
impl FromStr for Opcode {
|
||||
type Err = &'static str;
|
||||
|
||||
/// Parse an Opcode name from a string.
|
||||
fn from_str(s: &str) -> Result<Opcode, &'static str> {
|
||||
let tlen = OPCODE_HASH_TABLE.len();
|
||||
assert!(tlen.is_power_of_two());
|
||||
let mut idx = simple_hash(s) as usize;
|
||||
let mut step: usize = 0;
|
||||
loop {
|
||||
idx = idx % tlen;
|
||||
let entry = OPCODE_HASH_TABLE[idx];
|
||||
|
||||
if entry == Opcode::NotAnOpcode {
|
||||
return Err("Unknown opcode");
|
||||
}
|
||||
|
||||
if *opcode_name(entry) == *s {
|
||||
return Ok(entry);
|
||||
}
|
||||
|
||||
// Quadratic probing.
|
||||
step += 1;
|
||||
// When `tlen` is a power of two, it can be proven that idx will visit all entries.
|
||||
// This means that this loop will always terminate if the hash table has even one
|
||||
// unused entry.
|
||||
assert!(step < tlen);
|
||||
idx += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contents on an instruction.
|
||||
///
|
||||
/// Every variant must contain `opcode` and `ty` fields. An instruction that doesn't produce a
|
||||
/// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at
|
||||
/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a
|
||||
/// `Box<AuxData>` to store the additional information out of line.
|
||||
#[derive(Debug)]
|
||||
pub enum InstructionData {
|
||||
Nullary {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
},
|
||||
Unary {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
arg: Value,
|
||||
},
|
||||
UnaryImm {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
imm: Imm64,
|
||||
},
|
||||
UnaryIeee32 {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
imm: Ieee32,
|
||||
},
|
||||
UnaryIeee64 {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
imm: Ieee64,
|
||||
},
|
||||
UnaryImmVector {
|
||||
opcode: Opcode,
|
||||
ty: Type, // TBD: imm: Box<ImmVectorData>
|
||||
},
|
||||
Binary {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
args: [Value; 2],
|
||||
},
|
||||
BinaryImm {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
arg: Value,
|
||||
imm: Imm64,
|
||||
},
|
||||
// Same as BinaryImm, but the immediate is the lhs operand.
|
||||
BinaryImmRev {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
arg: Value,
|
||||
imm: Imm64,
|
||||
},
|
||||
BinaryOverflow {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
second_result: Value,
|
||||
args: [Value; 2],
|
||||
},
|
||||
Ternary {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
args: [Value; 3],
|
||||
},
|
||||
InsertLane {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
lane: u8,
|
||||
args: [Value; 2],
|
||||
},
|
||||
ExtractLane {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
lane: u8,
|
||||
arg: Value,
|
||||
},
|
||||
IntCompare {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
cond: IntCC,
|
||||
args: [Value; 2],
|
||||
},
|
||||
FloatCompare {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
cond: FloatCC,
|
||||
args: [Value; 2],
|
||||
},
|
||||
Jump {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
data: Box<JumpData>,
|
||||
},
|
||||
Branch {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
data: Box<BranchData>,
|
||||
},
|
||||
BranchTable {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
arg: Value,
|
||||
table: JumpTable,
|
||||
},
|
||||
Call {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
data: Box<CallData>,
|
||||
},
|
||||
Return {
|
||||
opcode: Opcode,
|
||||
ty: Type,
|
||||
data: Box<ReturnData>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A variable list of `Value` operands used for function call arguments and passing arguments to
|
||||
/// basic blocks.
|
||||
#[derive(Debug)]
|
||||
pub struct VariableArgs(Vec<Value>);
|
||||
|
||||
impl VariableArgs {
|
||||
pub fn new() -> VariableArgs {
|
||||
VariableArgs(Vec::new())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, v: Value) {
|
||||
self.0.push(v)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Coerce VariableArgs into a &[Value] slice.
|
||||
impl Deref for VariableArgs {
|
||||
type Target = [Value];
|
||||
|
||||
fn deref<'a>(&'a self) -> &'a [Value] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for VariableArgs {
|
||||
fn deref_mut<'a>(&'a mut self) -> &'a mut [Value] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VariableArgs {
|
||||
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
|
||||
for (i, val) in self.0.iter().enumerate() {
|
||||
if i == 0 {
|
||||
try!(write!(fmt, "{}", val));
|
||||
} else {
|
||||
try!(write!(fmt, ", {}", val));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VariableArgs {
|
||||
fn default() -> VariableArgs {
|
||||
VariableArgs::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit
|
||||
/// in the allowed InstructionData size.
|
||||
#[derive(Debug)]
|
||||
pub struct JumpData {
|
||||
pub destination: Ebb,
|
||||
pub arguments: VariableArgs,
|
||||
}
|
||||
|
||||
impl Display for JumpData {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if self.arguments.is_empty() {
|
||||
write!(f, "{}", self.destination)
|
||||
} else {
|
||||
write!(f, "{}({})", self.destination, self.arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit
|
||||
/// in the allowed InstructionData size.
|
||||
#[derive(Debug)]
|
||||
pub struct BranchData {
|
||||
pub arg: Value,
|
||||
pub destination: Ebb,
|
||||
pub arguments: VariableArgs,
|
||||
}
|
||||
|
||||
impl Display for BranchData {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
try!(write!(f, "{}, {}", self.arg, self.destination));
|
||||
if !self.arguments.is_empty() {
|
||||
try!(write!(f, "({})", self.arguments));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload of a call instruction.
|
||||
#[derive(Debug)]
|
||||
pub struct CallData {
|
||||
/// Second result value for a call producing multiple return values.
|
||||
second_result: Value,
|
||||
|
||||
// Dynamically sized array containing call argument values.
|
||||
pub args: VariableArgs,
|
||||
}
|
||||
|
||||
impl Display for CallData {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "TBD({})", self.args)
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload of a return instruction.
|
||||
#[derive(Debug)]
|
||||
pub struct ReturnData {
|
||||
// Dynamically sized array containing return values.
|
||||
pub args: VariableArgs,
|
||||
}
|
||||
|
||||
impl InstructionData {
|
||||
/// Create data for a call instruction.
|
||||
pub fn call(opc: Opcode, return_type: Type) -> InstructionData {
|
||||
InstructionData::Call {
|
||||
opcode: opc,
|
||||
ty: return_type,
|
||||
data: Box::new(CallData {
|
||||
second_result: NO_VALUE,
|
||||
args: VariableArgs::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Value type constraints for a given opcode.
|
||||
///
|
||||
/// The `InstructionFormat` determines the constraints on most operands, but `Value` operands and
|
||||
/// results are not determined by the format. Every `Opcode` has an associated
|
||||
/// `OpcodeConstraints` object that provides the missing details.
|
||||
///
|
||||
/// Since there can be a lot of opcodes, the `OpcodeConstraints` object is encoded as a bit field
|
||||
/// by the `meta/gen_instr.py` script.
|
||||
///
|
||||
/// The bit field bits are:
|
||||
///
|
||||
/// Bits 0-2:
|
||||
/// Number of fixed result values. This does not include `variable_args` results as are
|
||||
/// produced by call instructions.
|
||||
///
|
||||
/// Bit 3:
|
||||
/// This opcode is polymorphic and the controlling type variable can be inferred from the
|
||||
/// designated input operand. This is the `typevar_operand` index given to the
|
||||
/// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type
|
||||
/// variable must be the first output value instead.
|
||||
///
|
||||
/// Bits 4-7:
|
||||
/// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`.
|
||||
///
|
||||
/// Bits 8-15:
|
||||
/// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first
|
||||
/// `fixed_results()` entries describe the result constraints, then follows constraints for the
|
||||
/// fixed `Value` input operands. The number of `Value` inputs isdetermined by the instruction
|
||||
/// format.
|
||||
///
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct OpcodeConstraints(u16);
|
||||
|
||||
impl OpcodeConstraints {
|
||||
/// Can the controlling type variable for this opcode be inferred from the designated value
|
||||
/// input operand?
|
||||
/// This also implies that this opcode is polymorphic.
|
||||
pub fn use_typevar_operand(self) -> bool {
|
||||
(self.0 & 0x8) != 0
|
||||
}
|
||||
|
||||
/// Get the number of *fixed* result values produced by this opcode.
|
||||
/// This does not include `variable_args` produced by calls.
|
||||
pub fn fixed_results(self) -> usize {
|
||||
(self.0 & 0x7) as usize
|
||||
}
|
||||
|
||||
/// Get the offset into `TYPE_SETS` for the controlling type variable.
|
||||
/// Returns `None` if the instruction is not polymorphic.
|
||||
fn typeset_offset(self) -> Option<usize> {
|
||||
let offset = ((self.0 & 0xff) >> 4) as usize;
|
||||
if offset < TYPE_SETS.len() {
|
||||
Some(offset)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the offset into OPERAND_CONSTRAINTS where the descriptors for this opcode begin.
|
||||
fn constraint_offset(self) -> usize {
|
||||
(self.0 >> 8) as usize
|
||||
}
|
||||
|
||||
/// Get the value type of result number `n`, having resolved the controlling type variable to
|
||||
/// `ctrl_type`.
|
||||
pub fn result_type(self, n: usize, ctrl_type: Type) -> Type {
|
||||
assert!(n < self.fixed_results(), "Invalid result index");
|
||||
OPERAND_CONSTRAINTS[self.constraint_offset() + n]
|
||||
.resolve(ctrl_type)
|
||||
.expect("Result constraints can't be free")
|
||||
}
|
||||
|
||||
/// Get the typeset of allowed types for the controlling type variable in a polymorphic
|
||||
/// instruction.
|
||||
pub fn ctrl_typeset(self) -> Option<ValueTypeSet> {
|
||||
self.typeset_offset().map(|offset| TYPE_SETS[offset])
|
||||
}
|
||||
|
||||
/// Is this instruction polymorphic?
|
||||
pub fn is_polymorphic(self) -> bool {
|
||||
self.ctrl_typeset().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type set describes the permitted set of types for a type variable.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ValueTypeSet {
|
||||
allow_scalars: bool,
|
||||
allow_simd: bool,
|
||||
|
||||
base: Type,
|
||||
all_ints: bool,
|
||||
all_floats: bool,
|
||||
all_bools: bool,
|
||||
}
|
||||
|
||||
impl ValueTypeSet {
|
||||
/// Is `scalar` part of the base type set?
|
||||
///
|
||||
/// Note that the base type set does not have to be included in the type set proper.
|
||||
fn is_base_type(&self, scalar: Type) -> bool {
|
||||
scalar == self.base || (self.all_ints && scalar.is_int()) ||
|
||||
(self.all_floats && scalar.is_float()) || (self.all_bools && scalar.is_bool())
|
||||
}
|
||||
|
||||
/// Does `typ` belong to this set?
|
||||
pub fn contains(&self, typ: Type) -> bool {
|
||||
let allowed = if typ.is_scalar() {
|
||||
self.allow_scalars
|
||||
} else {
|
||||
self.allow_simd
|
||||
};
|
||||
allowed && self.is_base_type(typ.lane_type())
|
||||
}
|
||||
|
||||
/// Get an example member of this type set.
|
||||
///
|
||||
/// This is used for error messages to avoid suggesting invalid types.
|
||||
pub fn example(&self) -> Type {
|
||||
if self.base != types::VOID {
|
||||
return self.base;
|
||||
}
|
||||
let t = if self.all_ints {
|
||||
types::I32
|
||||
} else if self.all_floats {
|
||||
types::F32
|
||||
} else if self.allow_scalars {
|
||||
types::B1
|
||||
} else {
|
||||
types::B32
|
||||
};
|
||||
|
||||
if self.allow_scalars {
|
||||
t
|
||||
} else {
|
||||
t.by(4).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operand constraints. This describes the value type constraints on a single `Value` operand.
|
||||
enum OperandConstraint {
|
||||
/// This operand has a concrete value type.
|
||||
Concrete(Type),
|
||||
|
||||
/// This operand can vary freely within the given type set.
|
||||
/// The type set is identified by its index into the TYPE_SETS constant table.
|
||||
Free(u8),
|
||||
|
||||
/// This operand is the same type as the controlling type variable.
|
||||
Same,
|
||||
|
||||
/// This operand is `ctrlType.lane_type()`.
|
||||
LaneOf,
|
||||
|
||||
/// This operand is `ctrlType.as_bool()`.
|
||||
AsBool,
|
||||
}
|
||||
|
||||
impl OperandConstraint {
|
||||
/// Resolve this operand constraint into a concrete value type, given the value of the
|
||||
/// controlling type variable.
|
||||
/// Returns `None` if this is a free operand which is independent of the controlling type
|
||||
/// variable.
|
||||
pub fn resolve(&self, ctrl_type: Type) -> Option<Type> {
|
||||
use self::OperandConstraint::*;
|
||||
match *self {
|
||||
Concrete(t) => Some(t),
|
||||
Free(_) => None,
|
||||
Same => Some(ctrl_type),
|
||||
LaneOf => Some(ctrl_type.lane_type()),
|
||||
AsBool => Some(ctrl_type.as_bool()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn opcodes() {
|
||||
let x = Opcode::Iadd;
|
||||
let mut y = Opcode::Isub;
|
||||
|
||||
assert!(x != y);
|
||||
y = Opcode::Iadd;
|
||||
assert_eq!(x, y);
|
||||
assert_eq!(x.format(), Some(InstructionFormat::Binary));
|
||||
|
||||
assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm");
|
||||
assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm");
|
||||
|
||||
// Check the matcher.
|
||||
assert_eq!("iadd".parse::<Opcode>(), Ok(Opcode::Iadd));
|
||||
assert_eq!("iadd_imm".parse::<Opcode>(), Ok(Opcode::IaddImm));
|
||||
assert_eq!("iadd\0".parse::<Opcode>(), Err("Unknown opcode"));
|
||||
assert_eq!("".parse::<Opcode>(), Err("Unknown opcode"));
|
||||
assert_eq!("\0".parse::<Opcode>(), Err("Unknown opcode"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_data() {
|
||||
use std::mem;
|
||||
// The size of the InstructionData enum is important for performance. It should not exceed
|
||||
// 16 bytes. Use `Box<FooData>` out-of-line payloads for instruction formats that require
|
||||
// more space than that.
|
||||
// It would be fine with a data structure smaller than 16 bytes, but what are the odds of
|
||||
// that?
|
||||
assert_eq!(mem::size_of::<InstructionData>(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_set() {
|
||||
use repr::types::*;
|
||||
|
||||
let vts = ValueTypeSet {
|
||||
allow_scalars: true,
|
||||
allow_simd: true,
|
||||
base: VOID,
|
||||
all_ints: true,
|
||||
all_floats: false,
|
||||
all_bools: true,
|
||||
};
|
||||
assert_eq!(vts.example().to_string(), "i32");
|
||||
|
||||
let vts = ValueTypeSet {
|
||||
allow_scalars: true,
|
||||
allow_simd: true,
|
||||
base: VOID,
|
||||
all_ints: false,
|
||||
all_floats: true,
|
||||
all_bools: true,
|
||||
};
|
||||
assert_eq!(vts.example().to_string(), "f32");
|
||||
|
||||
let vts = ValueTypeSet {
|
||||
allow_scalars: false,
|
||||
allow_simd: true,
|
||||
base: VOID,
|
||||
all_ints: false,
|
||||
all_floats: true,
|
||||
all_bools: true,
|
||||
};
|
||||
assert_eq!(vts.example().to_string(), "f32x4");
|
||||
|
||||
let vts = ValueTypeSet {
|
||||
allow_scalars: false,
|
||||
allow_simd: true,
|
||||
base: VOID,
|
||||
all_ints: false,
|
||||
all_floats: false,
|
||||
all_bools: true,
|
||||
};
|
||||
assert_eq!(vts.example().to_string(), "b32x4");
|
||||
}
|
||||
}
|
||||
435
cranelift/src/libcretonne/repr/layout.rs
Normal file
435
cranelift/src/libcretonne/repr/layout.rs
Normal file
@@ -0,0 +1,435 @@
|
||||
//! Function layout.
|
||||
//!
|
||||
//! The order of extended basic blocks in a function and the order of instructions in an EBB is
|
||||
//! determined by the `Layout` data structure defined in this module.
|
||||
|
||||
use std::iter::{Iterator, IntoIterator};
|
||||
use entity_map::{EntityMap, EntityRef};
|
||||
use repr::entities::{Ebb, NO_EBB, Inst, NO_INST};
|
||||
|
||||
/// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not
|
||||
/// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references
|
||||
/// being defined elsewhere.
|
||||
///
|
||||
/// This data structure determines:
|
||||
///
|
||||
/// - The order of EBBs in the function.
|
||||
/// - Which EBB contains a given instruction.
|
||||
/// - The order of instructions with an EBB.
|
||||
///
|
||||
/// While data dependencies are not recorded, instruction ordering does affect control
|
||||
/// dependencies, so part of the semantics of the program are determined by the layout.
|
||||
///
|
||||
pub struct Layout {
|
||||
// Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in
|
||||
// both ends by NO_EBB.
|
||||
ebbs: EntityMap<Ebb, EbbNode>,
|
||||
|
||||
// Linked list nodes for the layout order of instructions. Forms a double linked list per EBB,
|
||||
// terminated in both ends by NO_INST.
|
||||
insts: EntityMap<Inst, InstNode>,
|
||||
|
||||
// First EBB in the layout order, or `None` when no EBBs have been laid out.
|
||||
first_ebb: Option<Ebb>,
|
||||
|
||||
// Last EBB in the layout order, or `None` when no EBBs have been laid out.
|
||||
last_ebb: Option<Ebb>,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
/// Create a new empty `Layout`.
|
||||
pub fn new() -> Layout {
|
||||
Layout {
|
||||
ebbs: EntityMap::new(),
|
||||
insts: EntityMap::new(),
|
||||
first_ebb: None,
|
||||
last_ebb: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods for laying out EBBs.
|
||||
///
|
||||
/// An unknown EBB starts out as *not inserted* in the EBB layout. The layout is a linear order of
|
||||
/// inserted EBBs. Once an EBB has been inserted in the layout, instructions can be added. An EBB
|
||||
/// can only be removed from the layout when it is empty.
|
||||
///
|
||||
/// Since every EBB must end with a terminator instruction which cannot fall through, the layout of
|
||||
/// EBBs does not affect the semantics of the program.
|
||||
///
|
||||
impl Layout {
|
||||
/// Is `ebb` currently part of the layout?
|
||||
pub fn is_ebb_inserted(&self, ebb: Ebb) -> bool {
|
||||
Some(ebb) == self.first_ebb || (self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev != NO_EBB)
|
||||
}
|
||||
|
||||
/// Insert `ebb` as the last EBB in the layout.
|
||||
pub fn append_ebb(&mut self, ebb: Ebb) {
|
||||
assert!(!self.is_ebb_inserted(ebb),
|
||||
"Cannot append EBB that is already in the layout");
|
||||
{
|
||||
let node = self.ebbs.ensure(ebb);
|
||||
assert!(node.first_inst == NO_INST && node.last_inst == NO_INST);
|
||||
node.prev = self.last_ebb.unwrap_or_default();
|
||||
node.next = NO_EBB;
|
||||
}
|
||||
if let Some(last) = self.last_ebb {
|
||||
self.ebbs[last].next = ebb;
|
||||
} else {
|
||||
self.first_ebb = Some(ebb);
|
||||
}
|
||||
self.last_ebb = Some(ebb);
|
||||
}
|
||||
|
||||
/// Insert `ebb` in the layout before the existing EBB `before`.
|
||||
pub fn insert_ebb(&mut self, ebb: Ebb, before: Ebb) {
|
||||
assert!(!self.is_ebb_inserted(ebb),
|
||||
"Cannot insert EBB that is already in the layout");
|
||||
assert!(self.is_ebb_inserted(before),
|
||||
"EBB Insertion point not in the layout");
|
||||
let after = self.ebbs[before].prev;
|
||||
{
|
||||
let node = self.ebbs.ensure(ebb);
|
||||
node.next = before;
|
||||
node.prev = after;
|
||||
}
|
||||
self.ebbs[before].prev = ebb;
|
||||
if after == NO_EBB {
|
||||
self.first_ebb = Some(ebb);
|
||||
} else {
|
||||
self.ebbs[after].next = ebb;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over all EBBs in layout order.
|
||||
pub fn ebbs<'a>(&'a self) -> Ebbs<'a> {
|
||||
Ebbs {
|
||||
layout: self,
|
||||
next: self.first_ebb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct EbbNode {
|
||||
prev: Ebb,
|
||||
next: Ebb,
|
||||
first_inst: Inst,
|
||||
last_inst: Inst,
|
||||
}
|
||||
|
||||
/// Iterate over EBBs in layout order. See `Layout::ebbs()`.
|
||||
pub struct Ebbs<'a> {
|
||||
layout: &'a Layout,
|
||||
next: Option<Ebb>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Ebbs<'a> {
|
||||
type Item = Ebb;
|
||||
|
||||
fn next(&mut self) -> Option<Ebb> {
|
||||
match self.next {
|
||||
Some(ebb) => {
|
||||
self.next = self.layout.ebbs[ebb].next.wrap();
|
||||
Some(ebb)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use a layout reference in a for loop.
|
||||
impl<'a> IntoIterator for &'a Layout {
|
||||
type Item = Ebb;
|
||||
type IntoIter = Ebbs<'a>;
|
||||
|
||||
fn into_iter(self) -> Ebbs<'a> {
|
||||
self.ebbs()
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods for arranging instructions.
|
||||
///
|
||||
/// An instruction starts out as *not inserted* in the layout. An instruction can be inserted into
|
||||
/// an EBB at a given position.
|
||||
impl Layout {
|
||||
/// Get the EBB containing `inst`, or `None` if `inst` is not inserted in the layout.
|
||||
pub fn inst_ebb(&self, inst: Inst) -> Option<Ebb> {
|
||||
if self.insts.is_valid(inst) {
|
||||
self.insts[inst].ebb.wrap()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Append `inst` to the end of `ebb`.
|
||||
pub fn append_inst(&mut self, inst: Inst, ebb: Ebb) {
|
||||
assert_eq!(self.inst_ebb(inst), None);
|
||||
assert!(self.is_ebb_inserted(ebb),
|
||||
"Cannot append instructions to EBB not in layout");
|
||||
let ebb_node = &mut self.ebbs[ebb];
|
||||
{
|
||||
let inst_node = self.insts.ensure(inst);
|
||||
inst_node.ebb = ebb;
|
||||
inst_node.prev = ebb_node.last_inst;
|
||||
assert_eq!(inst_node.next, NO_INST);
|
||||
}
|
||||
if ebb_node.first_inst == NO_INST {
|
||||
ebb_node.first_inst = inst;
|
||||
} else {
|
||||
self.insts[ebb_node.last_inst].next = inst;
|
||||
}
|
||||
ebb_node.last_inst = inst;
|
||||
}
|
||||
|
||||
/// Insert `inst` before the instruction `before` in the same EBB.
|
||||
pub fn insert_inst(&mut self, inst: Inst, before: Inst) {
|
||||
assert_eq!(self.inst_ebb(inst), None);
|
||||
let ebb = self.inst_ebb(before)
|
||||
.expect("Instruction before insertion point not in the layout");
|
||||
let after = self.insts[before].prev;
|
||||
{
|
||||
let inst_node = self.insts.ensure(inst);
|
||||
inst_node.ebb = ebb;
|
||||
inst_node.next = before;
|
||||
inst_node.prev = after;
|
||||
}
|
||||
self.insts[before].prev = inst;
|
||||
if after == NO_INST {
|
||||
self.ebbs[ebb].first_inst = inst;
|
||||
} else {
|
||||
self.insts[after].next = inst;
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the instructions in `ebb` in layout order.
|
||||
pub fn ebb_insts<'a>(&'a self, ebb: Ebb) -> Insts<'a> {
|
||||
Insts {
|
||||
layout: self,
|
||||
next: self.ebbs[ebb].first_inst.wrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct InstNode {
|
||||
ebb: Ebb,
|
||||
prev: Inst,
|
||||
next: Inst,
|
||||
}
|
||||
|
||||
/// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`.
|
||||
pub struct Insts<'a> {
|
||||
layout: &'a Layout,
|
||||
next: Option<Inst>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Insts<'a> {
|
||||
type Item = Inst;
|
||||
|
||||
fn next(&mut self) -> Option<Inst> {
|
||||
match self.next {
|
||||
Some(inst) => {
|
||||
self.next = self.layout.insts[inst].next.wrap();
|
||||
Some(inst)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Layout;
|
||||
use entity_map::EntityRef;
|
||||
use repr::entities::{Ebb, Inst};
|
||||
|
||||
#[test]
|
||||
fn append_ebb() {
|
||||
let mut layout = Layout::new();
|
||||
let e0 = Ebb::new(0);
|
||||
let e1 = Ebb::new(1);
|
||||
let e2 = Ebb::new(2);
|
||||
|
||||
{
|
||||
let imm = &layout;
|
||||
assert!(!imm.is_ebb_inserted(e0));
|
||||
assert!(!imm.is_ebb_inserted(e1));
|
||||
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, []);
|
||||
}
|
||||
|
||||
layout.append_ebb(e1);
|
||||
assert!(!layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(!layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e1]);
|
||||
|
||||
layout.append_ebb(e2);
|
||||
assert!(!layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e1, e2]);
|
||||
|
||||
layout.append_ebb(e0);
|
||||
assert!(layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e1, e2, e0]);
|
||||
|
||||
{
|
||||
let imm = &layout;
|
||||
let mut v = Vec::new();
|
||||
for e in imm {
|
||||
v.push(e);
|
||||
}
|
||||
assert_eq!(v, [e1, e2, e0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_ebb() {
|
||||
let mut layout = Layout::new();
|
||||
let e0 = Ebb::new(0);
|
||||
let e1 = Ebb::new(1);
|
||||
let e2 = Ebb::new(2);
|
||||
|
||||
{
|
||||
let imm = &layout;
|
||||
assert!(!imm.is_ebb_inserted(e0));
|
||||
assert!(!imm.is_ebb_inserted(e1));
|
||||
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, []);
|
||||
}
|
||||
|
||||
layout.append_ebb(e1);
|
||||
assert!(!layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(!layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e1]);
|
||||
|
||||
layout.insert_ebb(e2, e1);
|
||||
assert!(!layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e2, e1]);
|
||||
|
||||
layout.insert_ebb(e0, e1);
|
||||
assert!(layout.is_ebb_inserted(e0));
|
||||
assert!(layout.is_ebb_inserted(e1));
|
||||
assert!(layout.is_ebb_inserted(e2));
|
||||
let v: Vec<Ebb> = layout.ebbs().collect();
|
||||
assert_eq!(v, [e2, e0, e1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_inst() {
|
||||
let mut layout = Layout::new();
|
||||
let e1 = Ebb::new(1);
|
||||
|
||||
layout.append_ebb(e1);
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, []);
|
||||
|
||||
let i0 = Inst::new(0);
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), None);
|
||||
assert_eq!(layout.inst_ebb(i2), None);
|
||||
|
||||
layout.append_inst(i1, e1);
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), None);
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i1]);
|
||||
|
||||
layout.append_inst(i2, e1);
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), Some(e1));
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i1, i2]);
|
||||
|
||||
layout.append_inst(i0, e1);
|
||||
assert_eq!(layout.inst_ebb(i0), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), Some(e1));
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i1, i2, i0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_inst() {
|
||||
let mut layout = Layout::new();
|
||||
let e1 = Ebb::new(1);
|
||||
|
||||
layout.append_ebb(e1);
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, []);
|
||||
|
||||
let i0 = Inst::new(0);
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), None);
|
||||
assert_eq!(layout.inst_ebb(i2), None);
|
||||
|
||||
layout.append_inst(i1, e1);
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), None);
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i1]);
|
||||
|
||||
layout.insert_inst(i2, i1);
|
||||
assert_eq!(layout.inst_ebb(i0), None);
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), Some(e1));
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i2, i1]);
|
||||
|
||||
layout.insert_inst(i0, i1);
|
||||
assert_eq!(layout.inst_ebb(i0), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i1), Some(e1));
|
||||
assert_eq!(layout.inst_ebb(i2), Some(e1));
|
||||
let v: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v, [i2, i0, i1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_ebbs() {
|
||||
let mut layout = Layout::new();
|
||||
|
||||
let e0 = Ebb::new(0);
|
||||
let e1 = Ebb::new(1);
|
||||
|
||||
layout.append_ebb(e0);
|
||||
layout.append_ebb(e1);
|
||||
|
||||
let i0 = Inst::new(0);
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
let i3 = Inst::new(3);
|
||||
|
||||
layout.append_inst(i0, e0);
|
||||
layout.append_inst(i1, e0);
|
||||
layout.append_inst(i2, e1);
|
||||
layout.append_inst(i3, e1);
|
||||
|
||||
let v0: Vec<Inst> = layout.ebb_insts(e0).collect();
|
||||
let v1: Vec<Inst> = layout.ebb_insts(e1).collect();
|
||||
assert_eq!(v0, [i0, i1]);
|
||||
assert_eq!(v1, [i2, i3]);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
//! Representation of Cretonne IL functions.
|
||||
|
||||
use types::{FunctionName, Signature};
|
||||
pub mod entities;
|
||||
pub mod types;
|
||||
pub mod condcodes;
|
||||
pub mod immediates;
|
||||
pub mod instructions;
|
||||
pub mod dfg;
|
||||
pub mod layout;
|
||||
|
||||
use repr::types::{FunctionName, Signature};
|
||||
use entity_map::EntityRef;
|
||||
use entities::{Ebb, NO_EBB, StackSlot};
|
||||
use dfg::DataFlowGraph;
|
||||
use layout::Layout;
|
||||
use repr::entities::{Ebb, NO_EBB, StackSlot};
|
||||
use repr::dfg::DataFlowGraph;
|
||||
use repr::layout::Layout;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::Index;
|
||||
|
||||
|
||||
420
cranelift/src/libcretonne/repr/types.rs
Normal file
420
cranelift/src/libcretonne/repr/types.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
|
||||
//! Common types for the Cretonne code generator.
|
||||
|
||||
use std::default::Default;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
// ====--------------------------------------------------------------------------------------====//
|
||||
//
|
||||
// Value types
|
||||
//
|
||||
// ====--------------------------------------------------------------------------------------====//
|
||||
|
||||
/// The type of an SSA value.
|
||||
///
|
||||
/// The `VOID` type is only used for instructions that produce no value. It can't be part of a SIMD
|
||||
/// vector.
|
||||
///
|
||||
/// Basic integer types: `I8`, `I16`, `I32`, and `I64`. These types are sign-agnostic.
|
||||
///
|
||||
/// Basic floating point types: `F32` and `F64`. IEEE single and double precision.
|
||||
///
|
||||
/// Boolean types: `B1`, `B8`, `B16`, `B32`, and `B64`. These all encode 'true' or 'false'. The
|
||||
/// larger types use redundant bits.
|
||||
///
|
||||
/// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float/bool type.
|
||||
///
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Type(u8);
|
||||
|
||||
/// No type. Used for functions without a return value. Can't be loaded or stored. Can't be part of
|
||||
/// a SIMD vector.
|
||||
pub const VOID: Type = Type(0);
|
||||
|
||||
/// Integer type with 8 bits.
|
||||
pub const I8: Type = Type(1);
|
||||
|
||||
/// Integer type with 16 bits.
|
||||
pub const I16: Type = Type(2);
|
||||
|
||||
/// Integer type with 32 bits.
|
||||
pub const I32: Type = Type(3);
|
||||
|
||||
/// Integer type with 64 bits.
|
||||
pub const I64: Type = Type(4);
|
||||
|
||||
/// IEEE single precision floating point type.
|
||||
pub const F32: Type = Type(5);
|
||||
|
||||
/// IEEE double precision floating point type.
|
||||
pub const F64: Type = Type(6);
|
||||
|
||||
/// Boolean type. Can't be loaded or stored, but can be used to form SIMD vectors.
|
||||
pub const B1: Type = Type(7);
|
||||
|
||||
/// Boolean type using 8 bits to represent true/false.
|
||||
pub const B8: Type = Type(8);
|
||||
|
||||
/// Boolean type using 16 bits to represent true/false.
|
||||
pub const B16: Type = Type(9);
|
||||
|
||||
/// Boolean type using 32 bits to represent true/false.
|
||||
pub const B32: Type = Type(10);
|
||||
|
||||
/// Boolean type using 64 bits to represent true/false.
|
||||
pub const B64: Type = Type(11);
|
||||
|
||||
impl Type {
|
||||
/// Get the lane type of this SIMD vector type.
|
||||
///
|
||||
/// A scalar type is the same as a SIMD vector type with one lane, so it returns itself.
|
||||
pub fn lane_type(self) -> Type {
|
||||
Type(self.0 & 0x0f)
|
||||
}
|
||||
|
||||
/// Get the number of bits in a lane.
|
||||
pub fn lane_bits(self) -> u8 {
|
||||
match self.lane_type() {
|
||||
B1 => 1,
|
||||
B8 | I8 => 8,
|
||||
B16 | I16 => 16,
|
||||
B32 | I32 | F32 => 32,
|
||||
B64 | I64 | F64 => 64,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a type with the same number of lanes as this type, but with the lanes replaces by
|
||||
/// booleans of the same size.
|
||||
pub fn as_bool(self) -> Type {
|
||||
// Replace the low 4 bits with the boolean version, preserve the high 4 bits.
|
||||
let lane = match self.lane_type() {
|
||||
B8 | I8 => B8,
|
||||
B16 | I16 => B16,
|
||||
B32 | I32 | F32 => B32,
|
||||
B64 | I64 | F64 => B64,
|
||||
_ => B1,
|
||||
};
|
||||
Type(lane.0 | (self.0 & 0xf0))
|
||||
}
|
||||
|
||||
/// Is this the VOID type?
|
||||
pub fn is_void(self) -> bool {
|
||||
self == VOID
|
||||
}
|
||||
|
||||
/// Is this a scalar boolean type?
|
||||
pub fn is_bool(self) -> bool {
|
||||
match self {
|
||||
B1 | B8 | B16 | B32 | B64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a scalar integer type?
|
||||
pub fn is_int(self) -> bool {
|
||||
match self {
|
||||
I8 | I16 | I32 | I64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a scalar floating point type?
|
||||
pub fn is_float(self) -> bool {
|
||||
match self {
|
||||
F32 | F64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get log2 of the number of lanes in this SIMD vector type.
|
||||
///
|
||||
/// All SIMD types have a lane count that is a power of two and no larger than 256, so this
|
||||
/// will be a number in the range 0-8.
|
||||
///
|
||||
/// A scalar type is the same as a SIMD vector type with one lane, so it return 0.
|
||||
pub fn log2_lane_count(self) -> u8 {
|
||||
self.0 >> 4
|
||||
}
|
||||
|
||||
/// Is this a scalar type? (That is, not a SIMD vector type).
|
||||
///
|
||||
/// A scalar type is the same as a SIMD vector type with one lane.
|
||||
pub fn is_scalar(self) -> bool {
|
||||
self.log2_lane_count() == 0
|
||||
}
|
||||
|
||||
/// Get the number of lanes in this SIMD vector type.
|
||||
///
|
||||
/// A scalar type is the same as a SIMD vector type with one lane, so it returns 1.
|
||||
pub fn lane_count(self) -> u16 {
|
||||
1 << self.log2_lane_count()
|
||||
}
|
||||
|
||||
/// Get the total number of bits used to represent this type.
|
||||
pub fn bits(self) -> u16 {
|
||||
self.lane_bits() as u16 * self.lane_count()
|
||||
}
|
||||
|
||||
/// Get a SIMD vector type with `n` times more lanes than this one.
|
||||
///
|
||||
/// If this is a scalar type, this produces a SIMD type with this as a lane type and `n` lanes.
|
||||
///
|
||||
/// If this is already a SIMD vector type, this produces a SIMD vector type with `n *
|
||||
/// self.lane_count()` lanes.
|
||||
pub fn by(self, n: u16) -> Option<Type> {
|
||||
if self.lane_bits() == 0 || !n.is_power_of_two() {
|
||||
return None;
|
||||
}
|
||||
let log2_lanes: u32 = n.trailing_zeros();
|
||||
let new_type = self.0 as u32 + (log2_lanes << 4);
|
||||
if new_type < 0x90 {
|
||||
Some(Type(new_type as u8))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a SIMD vector with half the number of lanes.
|
||||
pub fn half_vector(self) -> Option<Type> {
|
||||
if self.is_scalar() {
|
||||
None
|
||||
} else {
|
||||
Some(Type(self.0 - 0x10))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if self.is_void() {
|
||||
write!(f, "void")
|
||||
} else if self.is_bool() {
|
||||
write!(f, "b{}", self.lane_bits())
|
||||
} else if self.is_int() {
|
||||
write!(f, "i{}", self.lane_bits())
|
||||
} else if self.is_float() {
|
||||
write!(f, "f{}", self.lane_bits())
|
||||
} else if !self.is_scalar() {
|
||||
write!(f, "{}x{}", self.lane_type(), self.lane_count())
|
||||
} else {
|
||||
panic!("Invalid Type(0x{:x})", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Type {
|
||||
fn default() -> Type {
|
||||
VOID
|
||||
}
|
||||
}
|
||||
|
||||
// ====--------------------------------------------------------------------------------------====//
|
||||
//
|
||||
// Function signatures
|
||||
//
|
||||
// ====--------------------------------------------------------------------------------------====//
|
||||
|
||||
/// The name of a function can be any UTF-8 string.
|
||||
///
|
||||
/// Function names are mostly a testing and debugging tool. In partucular, `.cton` files use
|
||||
/// function names to identify functions.
|
||||
pub type FunctionName = String;
|
||||
|
||||
/// Function argument extension options.
|
||||
///
|
||||
/// On some architectures, small integer function arguments are extended to the width of a
|
||||
/// general-purpose register.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ArgumentExtension {
|
||||
/// No extension, high bits are indeterminate.
|
||||
None,
|
||||
/// Unsigned extension: high bits in register are 0.
|
||||
Uext,
|
||||
/// Signed extension: high bits in register replicate sign bit.
|
||||
Sext,
|
||||
}
|
||||
|
||||
/// Function argument or return value type.
|
||||
///
|
||||
/// This describes the value type being passed to or from a function along with flags that affect
|
||||
/// how the argument is passed.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ArgumentType {
|
||||
pub value_type: Type,
|
||||
pub extension: ArgumentExtension,
|
||||
/// Place this argument in a register if possible.
|
||||
pub inreg: bool,
|
||||
}
|
||||
|
||||
/// Function signature.
|
||||
///
|
||||
/// The function signature describes the types of arguments and return values along with other
|
||||
/// details that are needed to call a function correctly.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Signature {
|
||||
pub argument_types: Vec<ArgumentType>,
|
||||
pub return_types: Vec<ArgumentType>,
|
||||
}
|
||||
|
||||
impl ArgumentType {
|
||||
pub fn new(vt: Type) -> ArgumentType {
|
||||
ArgumentType {
|
||||
value_type: vt,
|
||||
extension: ArgumentExtension::None,
|
||||
inreg: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArgumentType {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
try!(write!(f, "{}", self.value_type));
|
||||
match self.extension {
|
||||
ArgumentExtension::None => {}
|
||||
ArgumentExtension::Uext => try!(write!(f, " uext")),
|
||||
ArgumentExtension::Sext => try!(write!(f, " sext")),
|
||||
}
|
||||
if self.inreg {
|
||||
try!(write!(f, " inreg"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub fn new() -> Signature {
|
||||
Signature {
|
||||
argument_types: Vec::new(),
|
||||
return_types: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_list(f: &mut Formatter, args: &Vec<ArgumentType>) -> fmt::Result {
|
||||
match args.split_first() {
|
||||
None => {}
|
||||
Some((first, rest)) => {
|
||||
try!(write!(f, "{}", first));
|
||||
for arg in rest {
|
||||
try!(write!(f, ", {}", arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Display for Signature {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
try!(write!(f, "("));
|
||||
try!(write_list(f, &self.argument_types));
|
||||
try!(write!(f, ")"));
|
||||
if !self.return_types.is_empty() {
|
||||
try!(write!(f, " -> "));
|
||||
try!(write_list(f, &self.return_types));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic_scalars() {
|
||||
assert_eq!(VOID, VOID.lane_type());
|
||||
assert_eq!(0, VOID.bits());
|
||||
assert_eq!(B1, B1.lane_type());
|
||||
assert_eq!(B8, B8.lane_type());
|
||||
assert_eq!(B16, B16.lane_type());
|
||||
assert_eq!(B32, B32.lane_type());
|
||||
assert_eq!(B64, B64.lane_type());
|
||||
assert_eq!(I8, I8.lane_type());
|
||||
assert_eq!(I16, I16.lane_type());
|
||||
assert_eq!(I32, I32.lane_type());
|
||||
assert_eq!(I64, I64.lane_type());
|
||||
assert_eq!(F32, F32.lane_type());
|
||||
assert_eq!(F64, F64.lane_type());
|
||||
|
||||
assert_eq!(VOID.lane_bits(), 0);
|
||||
assert_eq!(B1.lane_bits(), 1);
|
||||
assert_eq!(B8.lane_bits(), 8);
|
||||
assert_eq!(B16.lane_bits(), 16);
|
||||
assert_eq!(B32.lane_bits(), 32);
|
||||
assert_eq!(B64.lane_bits(), 64);
|
||||
assert_eq!(I8.lane_bits(), 8);
|
||||
assert_eq!(I16.lane_bits(), 16);
|
||||
assert_eq!(I32.lane_bits(), 32);
|
||||
assert_eq!(I64.lane_bits(), 64);
|
||||
assert_eq!(F32.lane_bits(), 32);
|
||||
assert_eq!(F64.lane_bits(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vectors() {
|
||||
let big = F64.by(256).unwrap();
|
||||
assert_eq!(big.lane_bits(), 64);
|
||||
assert_eq!(big.lane_count(), 256);
|
||||
assert_eq!(big.bits(), 64 * 256);
|
||||
|
||||
assert_eq!(big.half_vector().unwrap().to_string(), "f64x128");
|
||||
assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1");
|
||||
assert_eq!(I32.half_vector(), None);
|
||||
assert_eq!(VOID.half_vector(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_scalars() {
|
||||
assert_eq!(VOID.to_string(), "void");
|
||||
assert_eq!(B1.to_string(), "b1");
|
||||
assert_eq!(B8.to_string(), "b8");
|
||||
assert_eq!(B16.to_string(), "b16");
|
||||
assert_eq!(B32.to_string(), "b32");
|
||||
assert_eq!(B64.to_string(), "b64");
|
||||
assert_eq!(I8.to_string(), "i8");
|
||||
assert_eq!(I16.to_string(), "i16");
|
||||
assert_eq!(I32.to_string(), "i32");
|
||||
assert_eq!(I64.to_string(), "i64");
|
||||
assert_eq!(F32.to_string(), "f32");
|
||||
assert_eq!(F64.to_string(), "f64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_vectors() {
|
||||
assert_eq!(B1.by(8).unwrap().to_string(), "b1x8");
|
||||
assert_eq!(B8.by(1).unwrap().to_string(), "b8");
|
||||
assert_eq!(B16.by(256).unwrap().to_string(), "b16x256");
|
||||
assert_eq!(B32.by(4).unwrap().by(2).unwrap().to_string(), "b32x8");
|
||||
assert_eq!(B64.by(8).unwrap().to_string(), "b64x8");
|
||||
assert_eq!(I8.by(64).unwrap().to_string(), "i8x64");
|
||||
assert_eq!(F64.by(2).unwrap().to_string(), "f64x2");
|
||||
assert_eq!(I8.by(3), None);
|
||||
assert_eq!(I8.by(512), None);
|
||||
assert_eq!(VOID.by(4), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn argument_type() {
|
||||
let mut t = ArgumentType::new(I32);
|
||||
assert_eq!(t.to_string(), "i32");
|
||||
t.extension = ArgumentExtension::Uext;
|
||||
assert_eq!(t.to_string(), "i32 uext");
|
||||
t.inreg = true;
|
||||
assert_eq!(t.to_string(), "i32 uext inreg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signatures() {
|
||||
let mut sig = Signature::new();
|
||||
assert_eq!(sig.to_string(), "()");
|
||||
sig.argument_types.push(ArgumentType::new(I32));
|
||||
assert_eq!(sig.to_string(), "(i32)");
|
||||
sig.return_types.push(ArgumentType::new(F32));
|
||||
assert_eq!(sig.to_string(), "(i32) -> f32");
|
||||
sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap()));
|
||||
assert_eq!(sig.to_string(), "(i32, i32x4) -> f32");
|
||||
sig.return_types.push(ArgumentType::new(B8));
|
||||
assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user