1 //! A NaN-canonicalizing rewriting pass. Patch floating point arithmetic
2 //! instructions that may return a NaN result with a sequence of operations
3 //! that will replace nondeterministic NaN's with a single canonical NaN value.
4 
5 use crate::cursor::{Cursor, FuncCursor};
6 use crate::ir::condcodes::FloatCC;
7 use crate::ir::immediates::{Ieee32, Ieee64};
8 use crate::ir::types;
9 use crate::ir::types::Type;
10 use crate::ir::{Function, Inst, InstBuilder, InstructionData, Opcode, Value};
11 use crate::timing;
12 
13 // Canonical 32-bit and 64-bit NaN values.
14 static CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
15 static CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
16 
17 /// Perform the NaN canonicalization pass.
do_nan_canonicalization(func: &mut Function)18 pub fn do_nan_canonicalization(func: &mut Function) {
19     let _tt = timing::canonicalize_nans();
20     let mut pos = FuncCursor::new(func);
21     while let Some(_block) = pos.next_block() {
22         while let Some(inst) = pos.next_inst() {
23             if is_fp_arith(&mut pos, inst) {
24                 add_nan_canon_seq(&mut pos, inst);
25             }
26         }
27     }
28 }
29 
30 /// Returns true/false based on whether the instruction is a floating-point
31 /// arithmetic operation. This ignores operations like `fneg`, `fabs`, or
32 /// `fcopysign` that only operate on the sign bit of a floating point value.
is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool33 fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
34     match pos.func.dfg[inst] {
35         InstructionData::Unary { opcode, .. } => {
36             opcode == Opcode::Ceil
37                 || opcode == Opcode::Floor
38                 || opcode == Opcode::Nearest
39                 || opcode == Opcode::Sqrt
40                 || opcode == Opcode::Trunc
41         }
42         InstructionData::Binary { opcode, .. } => {
43             opcode == Opcode::Fadd
44                 || opcode == Opcode::Fdiv
45                 || opcode == Opcode::Fmax
46                 || opcode == Opcode::Fmin
47                 || opcode == Opcode::Fmul
48                 || opcode == Opcode::Fsub
49         }
50         InstructionData::Ternary { opcode, .. } => opcode == Opcode::Fma,
51         _ => false,
52     }
53 }
54 
55 /// Append a sequence of canonicalizing instructions after the given instruction.
add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst)56 fn add_nan_canon_seq(pos: &mut FuncCursor, inst: Inst) {
57     // Select the instruction result, result type. Replace the instruction
58     // result and step forward before inserting the canonicalization sequence.
59     let val = pos.func.dfg.first_result(inst);
60     let val_type = pos.func.dfg.value_type(val);
61     let new_res = pos.func.dfg.replace_result(val, val_type);
62     let _next_inst = pos.next_inst().expect("block missing terminator!");
63 
64     // Insert a comparison instruction, to check if `inst_res` is NaN. Select
65     // the canonical NaN value if `val` is NaN, assign the result to `inst`.
66     let is_nan = pos.ins().fcmp(FloatCC::NotEqual, new_res, new_res);
67     let canon_nan = insert_nan_const(pos, val_type);
68     pos.ins()
69         .with_result(val)
70         .select(is_nan, canon_nan, new_res);
71 
72     pos.prev_inst(); // Step backwards so the pass does not skip instructions.
73 }
74 
75 /// Insert a canonical 32-bit or 64-bit NaN constant at the current position.
insert_nan_const(pos: &mut FuncCursor, nan_type: Type) -> Value76 fn insert_nan_const(pos: &mut FuncCursor, nan_type: Type) -> Value {
77     match nan_type {
78         types::F32 => pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN)),
79         types::F64 => pos.ins().f64const(Ieee64::with_bits(CANON_64BIT_NAN)),
80         _ => {
81             // Panic if the type given was not an IEEE floating point type.
82             panic!("Could not canonicalize NaN: Unexpected result type found.");
83         }
84     }
85 }
86