Files
wasmtime/cranelift/src/libcretonne/immediates.rs
Jakob Stoklund Olesen 1b7d5d849f Generate a constant hash table for recognizing opcodes.
Use a simple quadratically probed, open addressed hash table. We could use a
parfect hash function, but it would take longer to compute in Python, and this
is not in the critical path performancewise.
2016-04-07 20:24:21 -07:00

314 lines
11 KiB
Rust

//! 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;
// Include code generated by `meta/gen_instr.py`. This file contains:
//
// - The `pub enum Opcode` definition with all known opcodes,
// - The private `fn opcode_name(Opcode) -> &'static str` function, and
// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`.
//
include!(concat!(env!("OUT_DIR"), "/opcodes.rs"));
impl Display for Opcode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", opcode_name(*self))
}
}
// 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
}
impl Opcode {
/// Parse an Opcode name from a string.
pub fn from_str(s: &str) -> Option<Opcode> {
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 None;
}
if *opcode_name(entry) == *s {
return Some(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;
}
}
}
/// 64-bit immediate integer operand.
///
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Imm64(i64);
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(())
}
}
}
/// 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 {
assert!(w > 0 && w <= 16, "Invalid exponent range");
assert!(1 + w + t <= 64, "Too large IEEE format for u64");
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)
}
}
impl Ieee32 {
pub fn new(x: f32) -> Ieee32 {
Ieee32(x)
}
/// Construct Ieee32 immediate from raw bits.
pub fn new_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 Ieee64 {
pub fn new(x: f64) -> Ieee64 {
Ieee64(x)
}
/// Construct Ieee64 immediate from raw bits.
pub fn new_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)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{f32, f64};
#[test]
fn opcodes() {
let x = Opcode::Iadd;
let mut y = Opcode::Isub;
assert!(x != y);
y = Opcode::Iadd;
assert_eq!(x, y);
assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm");
assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm");
// Check the matcher.
assert_eq!(Opcode::from_str("iadd"), Some(Opcode::Iadd));
assert_eq!(Opcode::from_str("iadd_imm"), Some(Opcode::IaddImm));
assert_eq!(Opcode::from_str("iadd\0"), None);
assert_eq!(Opcode::from_str(""), None);
assert_eq!(Opcode::from_str("\0"), None);
}
#[test]
fn format_imm64() {
assert_eq!(format!("{}", Imm64(0)), "0");
assert_eq!(format!("{}", Imm64(9999)), "9999");
assert_eq!(format!("{}", Imm64(10000)), "0x2710");
assert_eq!(format!("{}", Imm64(-9999)), "-9999");
assert_eq!(format!("{}", Imm64(-10000)), "0xffff_ffff_ffff_d8f0");
assert_eq!(format!("{}", Imm64(0xffff)), "0xffff");
assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000");
}
#[test]
fn format_ieee32() {
assert_eq!(format!("{}", Ieee32::new(0.0)), "0.0");
assert_eq!(format!("{}", Ieee32::new(-0.0)), "-0.0");
assert_eq!(format!("{}", Ieee32::new(1.0)), "0x1.000000p0");
assert_eq!(format!("{}", Ieee32::new(1.5)), "0x1.800000p0");
assert_eq!(format!("{}", Ieee32::new(0.5)), "0x1.000000p-1");
assert_eq!(format!("{}", Ieee32::new(f32::EPSILON)), "0x1.000000p-23");
assert_eq!(format!("{}", Ieee32::new(f32::MIN)), "-0x1.fffffep127");
assert_eq!(format!("{}", Ieee32::new(f32::MAX)), "0x1.fffffep127");
// Smallest positive normal number.
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE)),
"0x1.000000p-126");
// Subnormals.
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE / 2.0)),
"0x0.800000p-126");
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON)),
"0x0.000002p-126");
assert_eq!(format!("{}", Ieee32::new(f32::INFINITY)), "Inf");
assert_eq!(format!("{}", Ieee32::new(f32::NEG_INFINITY)), "-Inf");
assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "NaN");
assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-NaN");
// Construct some qNaNs with payloads.
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "NaN:0x1");
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)),
"NaN:0x300001");
// Signaling NaNs.
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7f800001)), "sNaN:0x1");
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fa00001)),
"sNaN:0x200001");
}
#[test]
fn format_ieee64() {
assert_eq!(format!("{}", Ieee64::new(0.0)), "0.0");
assert_eq!(format!("{}", Ieee64::new(-0.0)), "-0.0");
assert_eq!(format!("{}", Ieee64::new(1.0)), "0x1.0000000000000p0");
assert_eq!(format!("{}", Ieee64::new(1.5)), "0x1.8000000000000p0");
assert_eq!(format!("{}", Ieee64::new(0.5)), "0x1.0000000000000p-1");
assert_eq!(format!("{}", Ieee64::new(f64::EPSILON)),
"0x1.0000000000000p-52");
assert_eq!(format!("{}", Ieee64::new(f64::MIN)),
"-0x1.fffffffffffffp1023");
assert_eq!(format!("{}", Ieee64::new(f64::MAX)),
"0x1.fffffffffffffp1023");
// Smallest positive normal number.
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE)),
"0x1.0000000000000p-1022");
// Subnormals.
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE / 2.0)),
"0x0.8000000000000p-1022");
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON)),
"0x0.0000000000001p-1022");
assert_eq!(format!("{}", Ieee64::new(f64::INFINITY)), "Inf");
assert_eq!(format!("{}", Ieee64::new(f64::NEG_INFINITY)), "-Inf");
assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "NaN");
assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-NaN");
// Construct some qNaNs with payloads.
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)),
"NaN:0x1");
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)),
"NaN:0x4000000000001");
// Signaling NaNs.
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)),
"sNaN:0x1");
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff4000000000001)),
"sNaN:0x4000000000001");
}
}