Add simple Else conditions to If Conversion#77728
Conversation
For example:
if (x < 7) { a = 5; } else { a = 9; }
a = (cond) ? b : c;
The else condition must write to the same variable as the then
statement.
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsFor example: The else condition must write to the same variable as the then statement. This patch builds on top of #73472 It refactors a lot of the code in the If Conversion pass. Various parts are split into new functions. Following the style of other passes, I put everything inside it's own OptIfConversionDsc class too. Detection is slightly reworked - It now finds a valid flow and then checks all the statements inside that flow are valid. I think this is slightly better for performance. The existing if conversion logic should be unchanged - all the checks and modifications are the same. This does not catch GT_RETURN cases, for example:
|
|
Note, due to #77723, after rebasing on top of the latest head, I was no longer able to build anything. All changes are contained within optIfConvert, so I'm not expecting any issues due to the rebase. |
|
The way I've done the SSA feels a little like a fudge, but I wasn't sure how else to do it. Before optimisation we have two assignments and one jtrue. This gives us two SSA definitions After the optimisation, we now just have a single assignment: But, we need two SSA definitions. Especially so given that the next block will probably be a PHI. This feels wrong, but the second assignment is eventually optimised away to nothing. The alternative would be to find all uses of SSA DEF 2 across all blocks and update them to use SSA DEF 1 ? Then somehow remove SSA DEF 2 ? |
Since these are currently not possible - move the phase to run after range check and declare it to invalidate SSA? |
|
@dotnet/jit-contrib |
I tried moving optIfConversion to be after optOptimizeBools and removing all my SSA fix ups (included the ones in HEAD). With that I get no test failures. Does the SSA state not matter after a certain point? Is there something specific I should be calling to state that the SSA is invalid? |
Yes, after the last phase that uses it. Currently this is range check.
No, a comment in |
|
I've been having a quick look at GT_RETURN. It's going to require a little reworking of the code in the PR, but otherwise looks fairly simple to add in. To avoid people re-reviewing another rewrite of the same bits of code, I was thinking it's probably best to get it ready then merge it with this PR. |
For now you can use |
|
Some spmidiff results on Arm64 Linux: Regressions look like just the usual issues around 0s. That should improve once we starting using XZR register for csel. |
|
I'm happy with this patch now. It catches return cases, eg: One test failure, but that looks like just a spurious build failure. Not happy with the IsPreferredRelop() function, ideally it would use the GenConditionDesc map, but that's in Codegen, and I wasn't sure if was ok be calling into codegen from a front end pass. Essentially, just need to make sure the GenConditionDesc created in genCodeForSelect only requires a single condition (which is now guarded with an assert). |
Sounds great! I'll try to get to this soon. In the meantime I will run some testing.
I assume this is just heuristically, right? The backend should be able to handle |
|
/azp run runtime-coreclr jitstress, runtime-coreclr libraries-jitstress, Fuzzlyn |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Change-Id: I61c8419d32d10d005c746cc9ad6ee4e89320d015 CustomizedGitHooks: yes
Change-Id: If61e778cc97e57dd20fd36ed623a1b0870c2b2bd CustomizedGitHooks: yes
What's happening here is there is a Later during If Conversion, it hoists the I don't think we can or would want to recalculate boundary checks during if conversion, so the best I can do with the flags available is: Incidentally, I think boundary checks always have an else statement, which is why it's only coming up in this patch. |
I think the fix should be to make sure we set |
Yes, I think that'd work. Not sure if a failing test case on main is possible - will give it a go. |
|
Ok, so.... Patch has been rebased after #78698, all review comments should be covered and all the tests are passing. |
|
/azp run runtime-coreclr jitstress, runtime-coreclr libraries-jitstress |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
I looked at some of the diffs:
-G_M14134_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, isz
- tbnz w0, #0, G_M14134_IG04
- ;; size=4 bbWeight=1 PerfScore 1.00
-G_M14134_IG03: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
- mov w2, #5
- tst w1, #1
- csel w0, w2, w0, eq
- ;; size=12 bbWeight=0.50 PerfScore 0.75
-G_M14134_IG04: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
+G_M14134_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
+ and w2, w0, #1
+ and w3, w1, #1
+ orr w2, w2, w3
+ mov w3, #5
+ cmp w2, #0
+ csel w0, w3, w0, eqLet's keep an eye on this one in the benchmarks.
- ldr x0, [fp, #0x28] // [V20 tmp18]
- ldr w1, [fp, #0x24] // [V19 tmp17]
+ ldr x0, [fp, #0x30] // [V20 tmp18]
+ ldr w1, [fp, #0x2C] // [V19 tmp17]
orr x0, x0, x1
- cbnz x0, G_M58282_IG04
- mov w2, wzr
- b G_M58282_IG08
- ;; size=84 bbWeight=0.50 PerfScore 13.50
-G_M58282_IG04: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
- ldr w2, [fp, #0x20] // [V18 tmp16]
- asr w0, w2, #31
- orr w2, w0, #1
- b G_M58282_IG08
+ ldr w1, [fp, #0x28] // [V18 tmp16]
+ asr w1, w1, #31
+ orr w1, w1, #1
+ cmp x0, #0
+ csel w19, w1, wzr, ne
+ b G_M58282_IG07
+ ;; size=96 bbWeight=0.50 PerfScore 15.00Looks pretty good, the regression comes from different (worse) register allocation. On the opposite end: -24 (-37.50 % of base) : 3407.dasm - System.Numerics.INumber`1[ushort]:Min(ushort,ushort):ushort
-16 (-36.36 % of base) : 13042.dasm - System.Xml.XmlElement:get_LastNode():System.Xml.XmlLinkedNode:this
-12 (-33.33 % of base) : 2804.dasm - System.Math:Max(int,int):int
-12 (-33.33 % of base) : 2806.dasm - System.Math:Max(uint,uint):uint
-12 (-33.33 % of base) : 2579.dasm - System.Math:Min(int,int):int
-12 (-33.33 % of base) : 2805.dasm - System.Math:Min(uint,uint):uintWe should keep an eye on benchmarks that may be impacted by these (cc @dotnet/jit-contrib), but overall it seems great to me to use conditional moves for these primitive methods. The diffs look like: ;; size=8 bbWeight=1 PerfScore 1.50
-G_M1277_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, isz
+G_M1277_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
uxth w0, w0
uxth w1, w1
cmp w0, w1
- beq G_M1277_IG06
- ;; size=16 bbWeight=1 PerfScore 2.50
-G_M1277_IG03: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, isz
+ csel w2, w0, w1, lt
cmp w0, w1
- blt G_M1277_IG05
- mov w0, w1
- ;; size=12 bbWeight=0.50 PerfScore 1.00
-G_M1277_IG04: ; , epilog, nogc, extend
+ csel w0, w1, w2, eq
+ ;; size=24 bbWeight=1 PerfScore 3.00
+G_M1277_IG03: ; , epilog, nogc, extend
ldp fp, lr, [sp], #0x10
ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
-G_M1277_IG05: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, epilog, nogc
- ldp fp, lr, [sp], #0x10
- ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
-G_M1277_IG06: ; gcVars=0000000000000000 {}, gcrefRegs=0000 {}, byrefRegs=0000 {}, gcvars, byref
- mov w0, w1
- ;; size=4 bbWeight=0.50 PerfScore 0.25
-G_M1277_IG07: ; , epilog, nogc, extend
- ldp fp, lr, [sp], #0x10
- ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
+ ;; size=8 bbWeight=1 PerfScore 2.00The double compare looks like an instance of #78385.
-G_M56171_IG01: ; gcVars=0000000000000000 {}, gcrefRegs=0000 {}, byrefRegs=0000 {}, gcvars, byref, nogc <-- Prolog IG
+G_M56171_IG01: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IG
stp fp, lr, [sp, #-0x10]!
mov fp, sp
;; size=8 bbWeight=1 PerfScore 1.50
-G_M56171_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, isz
+G_M56171_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
cmp w0, w1
- bge G_M56171_IG05
- ;; size=8 bbWeight=1 PerfScore 1.50
-G_M56171_IG03: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
- mov w0, w1
- ;; size=4 bbWeight=0.50 PerfScore 0.25
-G_M56171_IG04: ; , epilog, nogc, extend
+ csel w0, w0, w1, ge
+ ;; size=8 bbWeight=1 PerfScore 1.00
+G_M56171_IG03: ; , epilog, nogc, extend
ldp fp, lr, [sp], #0x10
ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
-G_M56171_IG05: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, epilog, nogc
- ldp fp, lr, [sp], #0x10
- ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
+ ;; size=8 bbWeight=1 PerfScore 2.00Another one: -G_M6195_IG01: ; gcVars=0000000000000000 {}, gcrefRegs=0000 {}, byrefRegs=0000 {}, gcvars, byref, nogc <-- Prolog IG
+G_M6195_IG01: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IG
stp fp, lr, [sp, #-0x10]!
mov fp, sp
;; size=8 bbWeight=1 PerfScore 1.50
-G_M6195_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, isz
+G_M6195_IG02: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
+ mov w1, #1
tst w0, #0xD1FFAB1E
- beq G_M6195_IG05
- ;; size=8 bbWeight=1 PerfScore 1.50
-G_M6195_IG03: ; gcrefRegs=0000 {}, byrefRegs=0000 {}, byref
- mov w0, wzr
- ;; size=4 bbWeight=0.50 PerfScore 0.25
-G_M6195_IG04: ; , epilog, nogc, extend
+ csel w0, w1, wzr, eq
+ ;; size=12 bbWeight=1 PerfScore 1.50
+G_M6195_IG03: ; , epilog, nogc, extend
ldp fp, lr, [sp], #0x10
ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
-G_M6195_IG05: ; gcVars=0000000000000000 {}, gcrefRegs=0000 {}, byrefRegs=0000 {}, gcvars, byref
- mov w0, #1
- ;; size=4 bbWeight=0.50 PerfScore 0.25
-G_M6195_IG06: ; , epilog, nogc, extend
- ldp fp, lr, [sp], #0x10
- ret lr
- ;; size=8 bbWeight=0.50 PerfScore 1.00
+ ;; size=8 bbWeight=1 PerfScore 2.00Given the heuristics on if-conversion around loops there's probably not much to worry about here, and overall the diffs look great. |
|
Thanks again! |
For example:
if (x < 7) { a = 5; } else { a = 9; }
a = (cond) ? b : c;
The else condition must write to the same variable as the then statement.
This patch builds on top of #73472
It refactors a lot of the code in the If Conversion pass. Various parts are split into new functions. Following the style of other passes, I put everything inside it's own OptIfConversionDsc class too.
Detection is slightly reworked - It now finds a valid flow and then checks all the statements inside that flow are valid. I think this is slightly better for performance.
The existing if conversion logic should be unchanged - all the checks and modifications are the same.
This does not catch GT_RETURN cases, for example:
return (a==b) ? 1 : 0
It should be possible to add that on top of this.