From b36fc6b75f1ee60b4d0c8b4d58c758389f1cb8e9 Mon Sep 17 00:00:00 2001 From: pup Date: Wed, 9 May 2018 16:11:58 -0400 Subject: [PATCH] Issue 311 - Add a pass to make NaN bits deterministic. (#322) --- lib/codegen/meta/base/settings.py | 10 +++ lib/codegen/src/context.rs | 10 +++ lib/codegen/src/lib.rs | 1 + lib/codegen/src/nan_canonicalization.rs | 81 +++++++++++++++++++++++++ lib/codegen/src/settings.rs | 1 + lib/codegen/src/timing.rs | 2 + 6 files changed, 105 insertions(+) create mode 100644 lib/codegen/src/nan_canonicalization.rs diff --git a/lib/codegen/meta/base/settings.py b/lib/codegen/meta/base/settings.py index a5107e5b4c..5753c35273 100644 --- a/lib/codegen/meta/base/settings.py +++ b/lib/codegen/meta/base/settings.py @@ -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) diff --git a/lib/codegen/src/context.rs b/lib/codegen/src/context.rs index c2aab76bad..b383160703 100644 --- a/lib/codegen/src/context.rs +++ b/lib/codegen/src/context.rs @@ -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. diff --git a/lib/codegen/src/lib.rs b/lib/codegen/src/lib.rs index faa21d24d7..7944c852a0 100644 --- a/lib/codegen/src/lib.rs +++ b/lib/codegen/src/lib.rs @@ -90,6 +90,7 @@ pub use entity::packed_option; mod abi; mod bitset; +mod nan_canonicalization; mod constant_hash; mod context; mod dce; diff --git a/lib/codegen/src/nan_canonicalization.rs b/lib/codegen/src/nan_canonicalization.rs new file mode 100644 index 0000000000..20877c3e18 --- /dev/null +++ b/lib/codegen/src/nan_canonicalization.rs @@ -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."); + } + } +} diff --git a/lib/codegen/src/settings.rs b/lib/codegen/src/settings.rs index 39416a298f..422eedc342 100644 --- a/lib/codegen/src/settings.rs +++ b/lib/codegen/src/settings.rs @@ -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\ diff --git a/lib/codegen/src/timing.rs b/lib/codegen/src/timing.rs index 017f3a466e..556ca56de1 100644 --- a/lib/codegen/src/timing.rs +++ b/lib/codegen/src/timing.rs @@ -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 {