Conversation
A binary select of the form sel(p, [x, xor(x, c)]) is rewritten into xor(x, sign_ext(p) & c) when c is fully known, with the selector complemented when the xor arm is the on-false case. The rewrite also applies when an earlier pass (e.g. concat_simp) already split the xor limb into per-slice pieces, as long as the two arms provably differ by a fully known constant (TryFoldXorDelta). Runs at optimization levels where split-based rewrites are enabled (SplitsEnabled); it is the strength-reduced form of the per-step select in an unrolled CRC/shift loop.
|
Hi, @ericastor, please, take a look at this PR. My optimization seems to be close to your |
|
@ericastor , ping |
|
@mag-mga please address comments before asking for further review. Edit: sorry bad github ui. Comments sent |
allight
left a comment
There was a problem hiding this comment.
Oh whoops sorry hadn't actually sent these
| m::SignExt(m::Eq(m::Param("p"), m::Literal(0b00))))); | ||
| } | ||
|
|
||
| TEST_P(SelectSimplificationPassTest, SelectWithConstantXorCaseCrcShift) { |
There was a problem hiding this comment.
Please use FunctionBuilder to create tests.
There was a problem hiding this comment.
Rewrote to FunctionBuilder
| // Flattens `node` into its non-concat pieces, most-significant first: the same | ||
| // order in which `concat` lists its operands and `bits_ops::Concat` | ||
| // reassembles them. | ||
| void FlattenConcats(Node* node, std::vector<Node*>& pieces) { |
There was a problem hiding this comment.
Because both concat_simp and select_simp are in the fixedpoint_simp compound pass this seems like it could cause the optimization to never finish if it triggers (because concat_simp will continuously undo the change).
There was a problem hiding this comment.
IIUC, this doesn't actually flatten the concats - it reads through them so the operation can see what it's working on. It still only applies if this optimization actually takes an action, which I don't think is something concat_simp can undo.
There was a problem hiding this comment.
Make it as independent select_to_bitwise_ops pass in the second commit
| // potentially more efficient components, and exposing further optimization | ||
| // opportunities. | ||
| // | ||
| // 16. **Converting Binary `Select` to Masked XOR**: |
There was a problem hiding this comment.
This seems very unlike the rest of the transforms in this pass in that it really depends on the values in the select cases.
This transform should either be in strength_red or more likely in a totally separate pass itself IMO.
(There's also some questions we've been having internally about changing the way that we organize optimization phases which would probably want this to be late in compilation, but that's only peripherally related to this.)
There was a problem hiding this comment.
It really is closely related to transform (14) above. I would agree that maybe both of these should be split out into another pass, though.
|
Apologies - I was at a conference all last week, and then today was a US holiday. I'll pick this up for a proper review in the morning. |
|
The code seems solid, and this is an interesting proposal - though I think I agree with @allight that it's starting to look like it'd be worth splitting out the "select -> bitwise operations" transforms into their own pass. Are you able to share a concrete example of something where this makes a difference to optimized IR quality or to PPA? |
7d5b733 to
02f1493
Compare
We had a hot CRC function inside many loops (something like that below) Previously, it generates Now it leads to For our target it is especially better for FPGA LUT and sometimes also better in synthesis (although some synthesis tools can finally synthesize both functions in the same way). I'm not confident I have enough permission to provide PPA :( |
Oh, no worries there! I meant the motivating example, rather than PPA specifics, so I appreciate your sharing it. I'll review this again, and thanks for reworking it as requested. |
ericastor
left a comment
There was a problem hiding this comment.
This is great, thank you!
| # This is mostly to clean up extraneous bit-slice/concats etc. | ||
| "fixedpoint_simp", | ||
| "dce", | ||
| # Convert binary selects whose arms differ by a fully-known xor term into |
There was a problem hiding this comment.
As I mentioned, I'm not actually sure this is true - so we'll check it, and decide where it should live overall. Thanks!
| // Returns the (on_false, on_true) arm pair of a single-bit binary mux: either | ||
| // two explicit cases, or one explicit case plus a default value. Returns | ||
| // std::nullopt for any other select shape. | ||
| std::optional<std::pair<Node*, Node*>> GetBinaryMuxArms(Select* sel) { |
There was a problem hiding this comment.
As a followup, we might want to switch this to use our GenericSelect framework! But for now, this is sensible.
|
Oh darn, our CI checks are aborting on the lack of a newline at the end of your new files. Are you able to fix that? I might just do it on import, though. |
@ericastor , |
PiperOrigin-RevId: 980085178
|
I think we should close this MR because it is already merged to main branch |
A binary select of the form
sel(p, [x, xor(x, c)])is rewritten intoxor(x, sign_ext(p) & c)when c is fully known, with the selector complemented when the xor arm is the on-false case. The rewrite also applies when an earlier pass (e.g. concat_simp) already split the xor limb into per-slice pieces, as long as the two arms provably differ by a fully known constant (TryFoldXorDelta).Runs at optimization levels where split-based rewrites are enabled (SplitsEnabled); it is the strength-reduced form of the per-step select in an unrolled CRC/shift loop.