Issue 311 - Add a pass to make NaN bits deterministic. (#322)
This commit is contained in:
@@ -99,6 +99,16 @@ enable_float = BoolSetting(
|
||||
""",
|
||||
default=True)
|
||||
|
||||
enable_nan_canonicalization = BoolSetting(
|
||||
"""
|
||||
Enable NaN canonicalization
|
||||
|
||||
This replaces NaNs with a single canonical value, for users requiring
|
||||
entirely deterministic WebAssembly computation. This is not required
|
||||
by the WebAssembly spec, so it is not enabled by default.
|
||||
""",
|
||||
default=False)
|
||||
|
||||
enable_simd = BoolSetting(
|
||||
"""Enable the use of SIMD instructions.""",
|
||||
default=True)
|
||||
|
||||
@@ -18,6 +18,7 @@ use isa::TargetIsa;
|
||||
use legalize_function;
|
||||
use licm::do_licm;
|
||||
use loop_analysis::LoopAnalysis;
|
||||
use nan_canonicalization::do_nan_canonicalization;
|
||||
use postopt::do_postopt;
|
||||
use preopt::do_preopt;
|
||||
use regalloc;
|
||||
@@ -124,6 +125,9 @@ impl Context {
|
||||
if isa.flags().opt_level() != OptLevel::Fastest {
|
||||
self.preopt(isa)?;
|
||||
}
|
||||
if isa.flags().enable_nan_canonicalization() {
|
||||
self.canonicalize_nans(isa)?;
|
||||
}
|
||||
self.legalize(isa)?;
|
||||
if isa.flags().opt_level() != OptLevel::Fastest {
|
||||
self.postopt(isa)?;
|
||||
@@ -212,6 +216,12 @@ impl Context {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform NaN canonicalizing rewrites on the function.
|
||||
pub fn canonicalize_nans(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||
do_nan_canonicalization(&mut self.func);
|
||||
self.verify_if(isa)
|
||||
}
|
||||
|
||||
/// Run the legalizer for `isa` on the function.
|
||||
pub fn legalize(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||
// Legalization invalidates the domtree and loop_analysis by mutating the CFG.
|
||||
|
||||
@@ -90,6 +90,7 @@ pub use entity::packed_option;
|
||||
|
||||
mod abi;
|
||||
mod bitset;
|
||||
mod nan_canonicalization;
|
||||
mod constant_hash;
|
||||
mod context;
|
||||
mod dce;
|
||||
|
||||
81
lib/codegen/src/nan_canonicalization.rs
Normal file
81
lib/codegen/src/nan_canonicalization.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
//! A NaN-canonicalizing rewriting pass. Patch floating point arithmetic
|
||||
//! instructions that may return a NaN result with a sequence of operations
|
||||
//! that will replace nondeterministic NaN's with a single canonical NaN value.
|
||||
|
||||
use cursor::{Cursor, FuncCursor};
|
||||
use ir::{Function, Inst, InstBuilder, InstructionData, Opcode, Value};
|
||||
use ir::condcodes::FloatCC;
|
||||
use ir::immediates::{Ieee32, Ieee64};
|
||||
use ir::types;
|
||||
use ir::types::Type;
|
||||
use timing;
|
||||
|
||||
// Canonical 32-bit and 64-bit NaN values.
|
||||
static CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
|
||||
static CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
|
||||
|
||||
/// Perform the NaN canonicalization pass.
|
||||
pub fn do_nan_canonicalization(func: &mut Function) {
|
||||
let _tt = timing::canonicalize_nans();
|
||||
let mut pos = FuncCursor::new(func);
|
||||
while let Some(_ebb) = pos.next_ebb() {
|
||||
while let Some(inst) = pos.next_inst() {
|
||||
if is_fp_arith(&mut pos, inst) {
|
||||
add_nan_canon_seq(&mut pos, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true/false based on whether the instruction is a floating-point
|
||||
/// arithmetic operation. This ignores operations like `fneg`, `fabs`, or
|
||||
/// `fcopysign` that only operate on the sign bit of a floating point value.
|
||||
fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
|
||||
match pos.func.dfg[inst] {
|
||||
InstructionData::Unary { opcode, .. } => {
|
||||
opcode == Opcode::Ceil || opcode == Opcode::Floor || opcode == Opcode::Nearest ||
|
||||
opcode == Opcode::Sqrt || opcode == Opcode::Trunc
|
||||
}
|
||||
InstructionData::Binary { opcode, .. } => {
|
||||
opcode == Opcode::Fadd || opcode == Opcode::Fdiv || opcode == Opcode::Fmax ||
|
||||
opcode == Opcode::Fmin || opcode == Opcode::Fmul ||
|
||||
opcode == Opcode::Fsub
|
||||
}
|
||||
InstructionData::Ternary { opcode, .. } => opcode == Opcode::Fma,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a sequence of canonicalizing instructions after the given instruction.
|
||||
fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst) {
|
||||
// Select the instruction result, result type. Replace the instruction
|
||||
// result and step forward before inserting the canonicalization sequence.
|
||||
let val = pos.func.dfg.first_result(inst);
|
||||
let val_type = pos.func.dfg.value_type(val);
|
||||
let new_res = pos.func.dfg.replace_result(val, val_type);
|
||||
let _next_inst = pos.next_inst().expect("EBB missing terminator!");
|
||||
|
||||
// Insert a comparison instruction, to check if `inst_res` is NaN. Select
|
||||
// the canonical NaN value if `val` is NaN, assign the result to `inst`.
|
||||
let is_nan = pos.ins().fcmp(FloatCC::NotEqual, new_res, new_res);
|
||||
let canon_nan = insert_nan_const(pos, val_type);
|
||||
pos.ins().with_result(val).select(
|
||||
is_nan,
|
||||
canon_nan,
|
||||
new_res,
|
||||
);
|
||||
|
||||
pos.prev_inst(); // Step backwards so the pass does not skip instructions.
|
||||
}
|
||||
|
||||
/// Insert a canonical 32-bit or 64-bit NaN constant at the current position.
|
||||
fn insert_nan_const(pos: &mut FuncCursor, nan_type: Type) -> Value {
|
||||
match nan_type {
|
||||
types::F32 => pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN)),
|
||||
types::F64 => pos.ins().f64const(Ieee64::with_bits(CANON_64BIT_NAN)),
|
||||
_ => {
|
||||
// Panic if the type given was not an IEEE floating point type.
|
||||
panic!("Could not canonicalize NaN: Unexpected result type found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,6 +369,7 @@ mod tests {
|
||||
avoid_div_traps = false\n\
|
||||
is_compressed = false\n\
|
||||
enable_float = true\n\
|
||||
enable_nan_canonicalization = false\n\
|
||||
enable_simd = true\n\
|
||||
enable_atomics = true\n\
|
||||
baldrdash_prologue_words = 0\n\
|
||||
|
||||
@@ -73,6 +73,8 @@ define_passes!{
|
||||
prologue_epilogue: "Prologue/epilogue insertion",
|
||||
binemit: "Binary machine code emission",
|
||||
layout_renumber: "Layout full renumbering",
|
||||
|
||||
canonicalize_nans: "Canonicalization of NaNs",
|
||||
}
|
||||
|
||||
impl Pass {
|
||||
|
||||
Reference in New Issue
Block a user