Issue 311 - Add a pass to make NaN bits deterministic.#322
Conversation
|
I can take care of the linting issues, pardon that. If there are any issues you see regarding the code overall though, let me know! |
05a2594 to
964640a
Compare
lib/codegen/src/context.rs
Outdated
| if isa.flags().opt_level() != OptLevel::Fastest { | ||
| self.preopt(isa)?; | ||
| } | ||
| self.canonicalize_nans(isa)?; // FIXUP: Should this be wrapped in a conditional? |
There was a problem hiding this comment.
Yes, this should be guarded by if isa.flags().enable_nan_canonicalization, so that we don't do the canonicalization unless we're asked to.
| // Canonical 32-bit and 64-bit NaN values. | ||
| static CANON_32BIT_NAN: u32 = 0b01111111100000000000000000000001; | ||
| static CANON_64BIT_NAN: u64 = | ||
| 0b0111111111110000000000000000000000000000000000000000000000000001; |
There was a problem hiding this comment.
We need to set the most significant bit of the significand field to 1, so that these are quiet NaNs rather than signaling NaNs. (For completeness, I'm ignoring pre-NAN2008 MIPS here.) Even though IEEE 754 doesn't say much here, it does say that a NaN returned from arithmetic is a quiet NaN.
It would also be good to set the least significant bit to 0, so that we're consistent with common hardware implementations such as ARM's "default NaN" mode.
| /// `fcopysign` that only operate on the sign bit of a floating point value. | ||
| fn is_fp_arith(pos: &mut FuncCursor, inst: Inst) -> bool { | ||
| // FIXUP: Should `ceil`, `floor`, `trunc`, `nearest`, and | ||
| // immediate constants be considered as well? |
There was a problem hiding this comment.
Yes for ceil, floor, trunc, and nearest, and no for immediate constants.
lib/codegen/meta/base/settings.py
Outdated
| """ | ||
| Enable NaN canonicalization | ||
| """, | ||
| default=True) |
There was a problem hiding this comment.
I think the default should be False, since wasm doesn't require this behavior.
|
|
||
| enable_nan_canonicalization = BoolSetting( | ||
| """ | ||
| Enable NaN canonicalization |
There was a problem hiding this comment.
It'd be good to briefly explain what canonicalization means here, and mention the word "deterministic", to help people who need that find it.
|
Thanks for the review by the way! I'll add those changes, the commit that was just pushed is fixing the formatting problems. I'll take care of the other points you mentioned tonight. |
| /// identify and replace NaN's with a single canonical NaN value. | ||
| fn add_nan_canon_instrs(pos: &mut FuncCursor, inst: Inst) { | ||
| // FIXUP: Are these unwraps safe? An arithmetic operation should never be | ||
| // found at the end of an EBB, if I understand this correctly. |
There was a problem hiding this comment.
Yeah, the unwraps are safe, though this would be a place where using .expect("...") would be a little friendlier than .unwrap() because forgetting to add a terminator to an ebb is a bug that does sometimes happen.
lib/codegen/src/timing.rs
Outdated
| binemit: "Binary machine code emission", | ||
| layout_renumber: "Layout full renumbering", | ||
|
|
||
| canonicalize_nans: "Canonicalization of NaN's", |
There was a problem hiding this comment.
Style nit: no apostrophe. "NaNs" is the plural of "NaN".
|
|
||
| /// Given some instruction that could potentially return a nondeterministic | ||
| /// NaN value, determine if the operation is using 32-bit or 64-bit floating | ||
| /// point numbers, and return the corresponding NaN value. |
There was a problem hiding this comment.
You can get this type from the instruction's result type, rather than digging into the operands. func.dfg.value_type(func.dfg.first_result(inst)) or so. Which is simpler, but also, that's the value that you'll be canonicalizing.
| pos.goto_inst(next_inst); | ||
|
|
||
| // Insert a select instruction to canonicalize the NaN value. | ||
| pos.ins().select(is_nan, canon_nan_res, inst_res); |
There was a problem hiding this comment.
Ok, so now let's talk about wiring in the new result value :-). The tricky thing is that we don't want to rewire all uses of the old value, because we need the fcmp and select to continue to use the old value.
As I mentioned earlier, there are a few ways to do it, but here's what I think is best: You can use dfg's replace_result to replace the result value of the arithmetic instruction with a new Value. Then you can use pos.ins()'s with_result to when inserting the select using the original Value.
|
Thanks! Wrapping up edits for the previous review notes right now, I will take a look into wiring in the new result once I am done with that :) if I have any questions I’ll follow up with you here or on irc |
80c5d5b to
86a7b28
Compare
|
Took a swing at this, having a little bit of trouble figuring out why the CI build is failing. When I run the I also wanted to check that I had wired up the Update: Just saw #323, disregard the first paragraph regarding the flake8/mypy questions. |
| // the canonical NaN value if `val` is NaN, assign the result to `inst`. | ||
| let is_nan = pos.ins().fcmp(FloatCC::NotEqual, val, val); | ||
| let canon_nan = insert_nan_const(pos, val_type); | ||
| pos.ins().with_result(new_res).select( |
There was a problem hiding this comment.
This looks right, except that replace_result goes the other way. new_res is the new result value of inst, and val remains the value that inst's users are using.
There was a problem hiding this comment.
Ah, thanks. So, fcmp and select should use new_res as a parameter, and I should pass val to with_result? I can do that right now.
86a7b28 to
7513d87
Compare
sunfishcode
left a comment
There was a problem hiding this comment.
This PR looks almost ready to merge! Just one detail below.
|
|
||
| // Canonical 32-bit and 64-bit NaN values. | ||
| static CANON_32BIT_NAN: u32 = 0b01111111100000000000000000000001; | ||
| static CANON_64BIT_NAN: u64 = 0b0111111111110000000000000000000000000000000000000000000000000001; |
There was a problem hiding this comment.
In case github swallowed my comment here, we need to use quiet NaN bit-patterns here, so the most significant bit of the significand field needs to be 1. And then, we should make the last significant bit 0, because that matches what several other systems consider to be the canonical NaN bit pattern.
|
Neat! I can apply that change today, thank you for the help :) |
7513d87 to
2bc6622
Compare
|
Looks good! |
This PR intends to fix #311. I have marked some lines that I was unsure about with 'FIXUP' comments, which I would not mind fixing if you have opinions about them. Otherwise, I can simply remove the comments and squash the commits down :)
This passes all of the existing test cases in the
codegencrate, as well as the top-level crate. I can add more tests if you think that is warranted, but might need some guidance on that front.Thanks again to @sunfishcode for the guidance, I had a lot of fun doing this!