cranelift_codegen/
nan_canonicalization.rs

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
5use crate::cursor::{Cursor, FuncCursor};
6use crate::ir::condcodes::FloatCC;
7use crate::ir::immediates::{Ieee32, Ieee64};
8use crate::ir::types;
9use crate::ir::{Function, Inst, InstBuilder, InstructionData, Opcode, Value};
10use crate::opts::MemFlags;
11use crate::timing;
12
13// Canonical 32-bit and 64-bit NaN values.
14static CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
15static CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
16
17/// Perform the NaN canonicalization pass.
18pub 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.
33fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool {
34    match pos.func.dfg.insts[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.
56fn 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
68    let scalar_select = |pos: &mut FuncCursor, canon_nan: Value| {
69        pos.ins()
70            .with_result(val)
71            .select(is_nan, canon_nan, new_res);
72    };
73    let vector_select = |pos: &mut FuncCursor, canon_nan: Value| {
74        let is_nan = pos.ins().bitcast(val_type, MemFlags::new(), is_nan);
75        pos.ins()
76            .with_result(val)
77            .bitselect(is_nan, canon_nan, new_res);
78    };
79
80    match val_type {
81        types::F32 => {
82            let canon_nan = pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN));
83            scalar_select(pos, canon_nan);
84        }
85        types::F64 => {
86            let canon_nan = pos.ins().f64const(Ieee64::with_bits(CANON_64BIT_NAN));
87            scalar_select(pos, canon_nan);
88        }
89        types::F32X4 => {
90            let canon_nan = pos.ins().f32const(Ieee32::with_bits(CANON_32BIT_NAN));
91            let canon_nan = pos.ins().splat(types::F32X4, canon_nan);
92            vector_select(pos, canon_nan);
93        }
94        types::F64X2 => {
95            let canon_nan = pos.ins().f64const(Ieee64::with_bits(CANON_64BIT_NAN));
96            let canon_nan = pos.ins().splat(types::F64X2, canon_nan);
97            vector_select(pos, canon_nan);
98        }
99        _ => {
100            // Panic if the type given was not an IEEE floating point type.
101            panic!("Could not canonicalize NaN: Unexpected result type found.");
102        }
103    }
104
105    pos.prev_inst(); // Step backwards so the pass does not skip instructions.
106}