diff --git a/cranelift/docs/langref.rst b/cranelift/docs/langref.rst index 3ef460de06..8acb5e793a 100644 --- a/cranelift/docs/langref.rst +++ b/cranelift/docs/langref.rst @@ -212,6 +212,9 @@ indicate the different kinds of immediate operands on an instruction. signed two's complement integer. Instruction encodings may limit the valid range. + In the textual format, :type:`imm64` immediates appear as decimal or + hexadecimal literals using the same syntax as C. + .. type:: ieee32 A 32-bit immediate floating point number in the IEEE 754-2008 binary32 @@ -229,6 +232,38 @@ indicate the different kinds of immediate operands on an instruction. bits of the operand are interpreted as if the SIMD vector was loaded from memory containing the immediate. +The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64` +are displayed as hexadecimal floating point literals in the textual IL format. +Decimal floating point literals are not allowed because some computer systems +can round differently when converting to binary. The hexadecimal floating point +format is mostly the same as the one used by C99, but extended to represent all +NaN bit patterns: + +Normal numbers + Compatible with C99: ``-0x1.Tpe`` where ``T`` are the trailing + significand bits encoded as hexadecimal, and ``e`` is the unbiased exponent + as a decimal number. :type:`ieee32` has 23 trailing significand bits. They + are padded with an extra LSB to produce 6 hexadecimal digits. This is not + necessary for :type:`ieee64` which has 52 trailing significand bits + forming 13 hexadecimal digits with no padding. + +Subnormal numbers + Compatible with C99: ``-0x0.Tpemin`` where ``T`` are the trailing + significand bits encoded as hexadecimal, and ``emin`` is the minimum exponent + as a decimal number. + +Infinities + Either ``-Inf`` or ``Inf``. + +Quiet NaNs + Quiet NaNs have the MSB of the trailing significand set. If the remaining + bits of the trailing significand are all zero, the value is displayed as + ``-qNaN`` or ``qNaN``. Otherwise, ``-qNaN:0xT`` where ``T`` are the + trailing significand bits encoded as hexadecimal. + +Signaling NaNs + Displayed as ``-sNaN:0xT``. + Control flow ============ diff --git a/cranelift/src/libcretonne/immediates.rs b/cranelift/src/libcretonne/immediates.rs index a60291b164..0e1e94ba07 100644 --- a/cranelift/src/libcretonne/immediates.rs +++ b/cranelift/src/libcretonne/immediates.rs @@ -6,6 +6,7 @@ //! module in the meta language. use std::fmt::{self, Display, Formatter}; +use std::mem; /// 64-bit immediate integer operand. /// @@ -36,9 +37,123 @@ impl Display for Imm64 { } } + +/// An IEEE binary32 immediate floating point value. +/// +/// All bit patterns are allowed. +pub struct Ieee32(f32); + +/// An IEEE binary64 immediate floating point value. +/// +/// All bit patterns are allowed. +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, "qNaN:0x{:x}", payload) + } else { + write!(f, "qNaN") + } + } 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 format_imm64() { @@ -50,4 +165,73 @@ mod tests { 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)), "qNaN"); + assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-qNaN"); + // Construct some qNaNs with payloads. + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "qNaN:0x1"); + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)), + "qNaN: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)), "qNaN"); + assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-qNaN"); + // Construct some qNaNs with payloads. + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)), + "qNaN:0x1"); + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)), + "qNaN:0x4000000000001"); + // Signaling NaNs. + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)), + "sNaN:0x1"); + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff4000000000001)), + "sNaN:0x4000000000001"); + } }