Skip to content

JIT: Support containing compares in GT_SELECT for xarch (i.e. start emitting cmov instructions) - #81267

Merged
jakobbotsch merged 24 commits into
dotnet:mainfrom
jakobbotsch:xarch-select-contain-conditions
Feb 9, 2023
Merged

JIT: Support containing compares in GT_SELECT for xarch (i.e. start emitting cmov instructions)#81267
jakobbotsch merged 24 commits into
dotnet:mainfrom
jakobbotsch:xarch-select-contain-conditions

Conversation

@jakobbotsch

@jakobbotschjakobbotsch commented Jan 27, 2023

Copy link
Copy Markdown
Member

This adds support for contained compares in GT_SELECT nodes for xarch. As part of this also enables if-conversion on xarch.

This fixes#6749. For:

[MethodImpl(MethodImplOptions.NoInlining)]staticlongsete_or_mov(boolcond){returncond?0:4;}

Before:

G_M14693_IG01: ;; offset=0000H ;; size=0 bbWeight=1 PerfScore 0.00G_M14693_IG02: ;; offset=0000H 84C9 testcl,cl7507jne SHORT G_M14693_IG04 ;; size=4 bbWeight=1 PerfScore 1.25G_M14693_IG03: ;; offset=0004H B804000000 moveax,4 EB02 jmp SHORT G_M14693_IG05 ;; size=7 bbWeight=0.50 PerfScore 1.12G_M14693_IG04: ;; offset=000BH 33C0 xoreax,eax ;; size=2 bbWeight=0.50 PerfScore 0.12G_M14693_IG05: ;; offset=000DH4898cdqe ;; size=2 bbWeight=1 PerfScore 0.25G_M14693_IG06: ;; offset=000FH C3 ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code 16, prolog size 0, PerfScore 5.35, instruction count 7, allocated bytes for code 16 (MethodHash=bc7ac69a) for method Program:sete_or_mov(bool):long

After:

G_M14693_IG01: ;; size=0 bbWeight=1 PerfScore 0.00G_M14693_IG02:xoreax,eaxmovedx,4testcl,cl cmove eax,edxcdqe ;; size=14 bbWeight=1 PerfScore 1.25G_M14693_IG03:ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code 15, prolog size 0, PerfScore 3.75, instruction count 6, allocated bytes for code 15 (MethodHash=bc7ac69a) for method Program:sete_or_mov(bool):long

(Not completely optimal, no sign extension is necessary, but that's a separate issue.)

It does not get the cmov case from the issue, but that's because if-conversion does not handle it today (cc @a74nh -- seems like a nice opportunity).

TODO:

  • Interference checks
  • Only require delayed free for one operand
  • Double check delay free logic

This adds support for contained compares in GT_SELECT nodes for xarch.
As part of this, also enables if-conversion on xarch.
Fixdotnet#6749
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 27, 2023
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch, @kunalspathak
See info in area-owners.md if you want to be subscribed.

Issue Details

This adds support for contained compares in GT_SELECT nodes for xarch. As part of this also enables if-conversion on xarch.

This partially fixes #6749. For:

[MethodImpl(MethodImplOptions.NoInlining)]staticlongcmov(longlongValue){longtmp1=longValue&0x00000000ffffffff;returntmp1==0?longValue:tmp1;}

Before:

; Assembly listing for method Program:sete_or_mov(bool):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; optimized code; rsp based frame; partially interruptible; No PGO data; Final local variable assignments;; V00 arg0 [V00,T00] ( 3, 3 ) bool -> rcx single-def;# V01 OutArgs [V01 ] ( 1, 1 ) lclBlk ( 0) [rsp+00H] "OutgoingArgSpace"; V02 tmp1 [V02,T01] ( 3, 2 ) int -> rax;; Lcl frame size = 0G_M14693_IG01: ;; offset=0000H ;; size=0 bbWeight=1 PerfScore 0.00G_M14693_IG02: ;; offset=0000H 84C9 testcl,cl7507jne SHORT G_M14693_IG04 ;; size=4 bbWeight=1 PerfScore 1.25G_M14693_IG03: ;; offset=0004H B804000000 moveax,4 EB02 jmp SHORT G_M14693_IG05 ;; size=7 bbWeight=0.50 PerfScore 1.12G_M14693_IG04: ;; offset=000BH 33C0 xoreax,eax ;; size=2 bbWeight=0.50 PerfScore 0.12G_M14693_IG05: ;; offset=000DH4898cdqe ;; size=2 bbWeight=1 PerfScore 0.25G_M14693_IG06: ;; offset=000FH C3 ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code 16, prolog size 0, PerfScore 5.35, instruction count 7, allocated bytes for code 16 (MethodHash=bc7ac69a) for method Program:sete_or_mov(bool):long; ===========================================================

After:

; Assembly listing for method Program:sete_or_mov(bool):long; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; optimized code; rsp based frame; partially interruptible; No PGO data; Final local variable assignments;; V00 arg0 [V00,T00] ( 3, 3 ) bool -> rcx single-def;# V01 OutArgs [V01 ] ( 1, 1 ) lclBlk ( 0) [rsp+00H] "OutgoingArgSpace"; V02 tmp1 [V02,T01] ( 2, 2 ) int -> rdx;; Lcl frame size = 0G_M14693_IG01: ;; offset=0000H ;; size=0 bbWeight=1 PerfScore 0.00G_M14693_IG02: ;; offset=0000H B804000000 moveax,4 33D2 xoredx,edx 84C9 testcl,cl 0F44D0 cmove edx,eax 4863C2 movsxdrax,edx ;; size=15 bbWeight=1 PerfScore 1.25G_M14693_IG03: ;; offset=000FH C3 ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code 16, prolog size 0, PerfScore 3.85, instruction count 6, allocated bytes for code 16 (MethodHash=bc7ac69a) for method Program:sete_or_mov(bool):long; ============================================================

It does not get the cmov case from the issue, but that's because if-conversion does not handle it today (cc @a74nh -- seems like a nice opportunity).

Author:jakobbotsch
Assignees:-
Labels:

area-CodeGen-coreclr

Milestone:-

@jakobbotsch

jakobbotsch commented Jan 28, 2023

Copy link
Copy Markdown
MemberAuthor

It does not get the cmov case from the issue, but that's because if-conversion does not handle it today (cc @a74nh -- seems like a nice opportunity).

Disregard this -- if-conversion works fine, I just had a flipped ifdef in my code. The code produced for cmov is:

[MethodImpl(MethodImplOptions.NoInlining)]staticlongcmov(longlongValue){longtmp1=longValue&0x00000000ffffffff;returntmp1==0?longValue:tmp1;}

Before:

G_M64344_IG01: ;; size=0 bbWeight=1 PerfScore 0.00G_M64344_IG02:moveax,ecxtestrax,raxje SHORT G_M64344_IG04 ;; size=7 bbWeight=1 PerfScore 1.50G_M64344_IG03:ret ;; size=1 bbWeight=0.50 PerfScore 0.50G_M64344_IG04:movrax,rcx ;; size=3 bbWeight=0.50 PerfScore 0.12G_M64344_IG05:ret ;; size=1 bbWeight=0.50 PerfScore 0.50; Total bytes of code 12, prolog size 0, PerfScore 3.83, instruction count 6, allocated bytes for code 12 (MethodHash=16c004a7) for method Program:cmov(long):long

After:

G_M64344_IG01: ;; size=0 bbWeight=1 PerfScore 0.00G_M64344_IG02:moveax,ecxtestrax,rax cmove rax,rcx ;; size=9 bbWeight=1 PerfScore 0.75G_M64344_IG03:ret ;; size=1 bbWeight=1 PerfScore 1.00; Total bytes of code 10, prolog size 0, PerfScore 2.75, instruction count 4, allocated bytes for code 10 (MethodHash=16c004a7) for method Program:cmov(long):long

@jakobbotsch

jakobbotsch commented Jan 28, 2023

Copy link
Copy Markdown
MemberAuthor

Definitely still some things to improve:

CBoolTest:AreZero2(int,int):

@@ -12,26 +12,23 @@
;
; Lcl frame size = 0
-G_M65508_IG01: ; bbWeight=1, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref, nogc <-- Prolog IG
;
; Lcl frame size = 0
-G_M65508_IG01: ; bbWeight=1, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref, nogc <-- Prolog IG+G_M65508_IG01: ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, nogc <-- Prolog IG
;; size=0 bbWeight=1 PerfScore 0.00
-G_M65508_IG02: ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref, isz+G_M65508_IG02: ; bbWeight=1, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref
or edx, ecx
- jne SHORT G_M65508_IG05- ;; size=4 bbWeight=1 PerfScore 1.25-G_M65508_IG03: ; bbWeight=0.50, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref- mov eax, 1- ;; size=5 bbWeight=0.50 PerfScore 0.12-G_M65508_IG04: ; bbWeight=0.50, epilog, nogc, extend+ setne al+ movzx rax, al+ xor edx, edx+ mov ecx, 1+ test eax, eax+ mov eax, ecx+ cmovne eax, edx+ ;; size=22 bbWeight=1 PerfScore 2.75+G_M65508_IG03: ; bbWeight=1, epilog, nogc, extend
ret
- ;; size=1 bbWeight=0.50 PerfScore 0.50-G_M65508_IG05: ; bbWeight=0.50, gcVars=0000000000000000 {}, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, gcvars, byref- xor eax, eax- ;; size=2 bbWeight=0.50 PerfScore 0.12-G_M65508_IG06: ; bbWeight=0.50, epilog, nogc, extend- ret

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

Definitely still some things to improve:

CBoolTest:AreZero2(int,int):

I turned off if-conversion for the problematic scenarios (these cases turn out to be very rare, actually).
I first tried adding the support for if-conversion handling these kinds of cases, but that was pretty clunky and hacky. I left some notes at #74867 (comment) on why that is.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr outerloop, runtime-coreclr jitstress, runtime-coreclr libraries-jitstress, Fuzzlyn

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 4 pipeline(s).

}

#ifdef FEATURE_HW_INTRINSICS
if (OperIs(GT_HWINTRINSIC) && emitter::DoesWriteZeroFlag(HWIntrinsicInfo::lookupIns(AsHWIntrinsic())))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that this isn't necessarily "correct".

lookupIns catches the most common cases, but certainly not all cases particularly when various intrinsics represent complex helpers that are lowered or when the table tracked instruction is INS_invalid due to it requiring additional lookups/handling.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I added another check to the heuristic in if-conversion for the rest of the hw intrinsic cases that this PR doesn't handle yet, so that we don't regress those.

bool GenTree::CanSetZeroFlag()
{
#if defined(TARGET_XARCH)
if (OperIs(GT_AND, GT_OR, GT_XOR, GT_ADD, GT_SUB, GT_NEG))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing many different cases, particularly those that leave ZF in an undefined state such as imul

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just factoring the code that was already there. I can add a TODO if you want.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It was factored out of Lowering::OptimizeConstCompare)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. I wonder if its going to cause any problematic behavior if we're depending on this to be accurate.

I could easily see someone checking !CanSetZeroFlag() and then assuming the zero flag can't be overrwritten. Inversely I could see someone checking CanSetZeroFlag() and assuming that it will write some 0/1 value and that it is strictly tied to the computed result.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can rename this. The specific meaning I was intending was that this is a node that supports setting the zero flag if GTF_SET_FLAGS is set. Maybe SupportsSettingZeroFlag() is better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure Supports fixes the main concern around people thinking !Supports means "does not support" (and therefore will not modify zf).

I don't have a good suggestion for a different name, however.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might just be something we'll have to cover with docs and code review.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to SupportsSettingZeroFlag and added some more docs on it

}
#endif
#elif defined(TARGET_ARM64)
if (OperIs(GT_AND, GT_ADD, GT_SUB))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be problematic on Arm64 since we have two different instructions for each of these?

That is one that sets the flags (adds) and one that does not (add)

@jakobbotschjakobbotschJan 30, 2023

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think codegen just handles this via gtSetFlags(). Anyway there shouldn't be any behavior differences for arm64 here, this is just factoring this.

@TIHanTIHan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, the LSRA changes seem about right based on my limited experience with it.

The diffs do look good, though there are a lot of regressions; wondering if they are actual regressions or not.

@jakobbotsch

jakobbotsch commented Feb 1, 2023

Copy link
Copy Markdown
MemberAuthor

This does not improve many of the IfStatements micro benchmarks, here is the full table of results:

MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioAllocatedAlloc Ratio
SingleJob-MLIHKJ\Core_Root_base\corerun.exe23.93 us0.048 us0.040 us23.93 us23.85 us23.99 us1.00-NA
SingleJob-FCEXGF\Core_Root_diff\corerun.exe19.26 us0.144 us0.135 us19.29 us19.07 us19.48 us0.80-NA
AndJob-MLIHKJ\Core_Root_base\corerun.exe25.06 us0.199 us0.186 us25.11 us24.81 us25.32 us1.00-NA
AndJob-FCEXGF\Core_Root_diff\corerun.exe25.03 us0.155 us0.145 us25.07 us24.68 us25.21 us1.00-NA
AndAndJob-MLIHKJ\Core_Root_base\corerun.exe27.33 us0.243 us0.227 us27.35 us27.01 us27.64 us1.00-NA
AndAndJob-FCEXGF\Core_Root_diff\corerun.exe27.24 us0.106 us0.094 us27.26 us27.09 us27.35 us1.00-NA
AndAndAndJob-MLIHKJ\Core_Root_base\corerun.exe27.42 us0.220 us0.206 us27.33 us27.20 us27.75 us1.00-NA
AndAndAndJob-FCEXGF\Core_Root_diff\corerun.exe27.48 us0.175 us0.164 us27.50 us27.22 us27.75 us1.00-NA
OrJob-MLIHKJ\Core_Root_base\corerun.exe25.93 us0.029 us0.026 us25.93 us25.90 us25.99 us1.00-NA
OrJob-FCEXGF\Core_Root_diff\corerun.exe26.31 us0.150 us0.140 us26.33 us25.98 us26.50 us1.01-NA
OrOrJob-MLIHKJ\Core_Root_base\corerun.exe26.39 us0.040 us0.038 us26.40 us26.32 us26.44 us1.00-NA
OrOrJob-FCEXGF\Core_Root_diff\corerun.exe26.43 us0.104 us0.086 us26.40 us26.35 us26.66 us1.00-NA
AndOrJob-MLIHKJ\Core_Root_base\corerun.exe28.69 us0.319 us0.283 us28.76 us28.32 us29.24 us1.00-NA
AndOrJob-FCEXGF\Core_Root_diff\corerun.exe28.35 us0.035 us0.029 us28.35 us28.31 us28.40 us0.99-NA
SingleArrayJob-MLIHKJ\Core_Root_base\corerun.exe25.70 us0.268 us0.251 us25.75 us25.37 us26.04 us1.00-NA
SingleArrayJob-FCEXGF\Core_Root_diff\corerun.exe21.17 us0.061 us0.057 us21.16 us21.10 us21.27 us0.82-NA
AndArrayJob-MLIHKJ\Core_Root_base\corerun.exe26.07 us0.093 us0.083 us26.05 us25.97 us26.22 us1.00-NA
AndArrayJob-FCEXGF\Core_Root_diff\corerun.exe26.00 us0.130 us0.122 us25.96 us25.87 us26.24 us1.00-NA
OrArrayJob-MLIHKJ\Core_Root_base\corerun.exe26.00 us0.085 us0.075 us25.97 us25.93 us26.18 us1.00-NA
OrArrayJob-FCEXGF\Core_Root_diff\corerun.exe25.98 us0.069 us0.065 us25.98 us25.90 us26.10 us1.00-NA
  1. Single is converted ok and shows good benefit
  2. And/AndAnd/AndAndAnd are filtered out during if-conversion for xarch because they end up testing the result of a GT_OR against zero, which the xarch backend does not handle efficiently. I.e.:
; Assembly listing for method IfStatements.IfStatements:AndAndInner(int,int,int); Emitting BLENDED_CODE for X64 CPU with AVX - Windows; optimized code; rsp based frame; partially interruptible; No PGO data; Final local variable assignments;; V00 arg0 [V00,T00] ( 5, 4.50) int -> rcx ; V01 arg1 [V01,T01] ( 4, 4 ) int -> rdx single-def; V02 arg2 [V02,T02] ( 4, 4 ) int -> r8 single-def; V03 OutArgs [V03 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace";; Lcl frame size = 40G_M12418_IG01:subrsp,40 ;; size=4 bbWeight=1 PerfScore 0.25G_M12418_IG02:movr9d,ecxandr9d,1moveax,edxandeax,1orr9d,eaxmoveax,r8dandeax,1orr9d,eaxjne SHORT G_M12418_IG04 ;; size=26 bbWeight=1 PerfScore 3.00G_M12418_IG03:movecx,5 ;; size=5 bbWeight=0.50 PerfScore 0.12G_M12418_IG04:xorr9d,r9dcall[IfStatements.IfStatements:Consume(int,int,int,int)]nop ;; size=10 bbWeight=1 PerfScore 3.50G_M12418_IG05:addrsp,40ret ;; size=5 bbWeight=1 PerfScore 1.25; Total bytes of code 50, prolog size 4, PerfScore 13.13, instruction count 16, allocated bytes for code 50 (MethodHash=2b41cf7d) for method IfStatements.IfStatements:AndAndInner(int,int,int); ============================================================

Hopefully we can get these cases in the future.
3. Or/OrOr/AndOr cannot be if-converted, so there are no diffs
4. SingleArray is converted just fine and shows good benefit
5. AndArray is converted too, but oddly shows no benefit. The diff is:

@@ -1,55 +1,55 @@
; Assembly listing for method IfStatements.IfStatements:AndArrayInner(int,int)
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; Final local variable assignments
;
-; V00 arg0 [V00,T00] ( 6, 5.50) int -> rcx +; V00 arg0 [V00,T00] ( 7, 6 ) int -> rcx 
; V01 arg1 [V01,T01] ( 5, 4 ) int -> rdx single-def
; V02 OutArgs [V02 ] ( 1, 1 ) lclBlk (32) [rsp+00H] "OutgoingArgSpace"
; V03 tmp1 [V03,T02] ( 3, 6 ) ref -> r9 single-def "arr expr"
; V04 tmp2 [V04,T05] ( 2, 2 ) ref -> r8 single-def "arr expr"
; V05 cse0 [V05,T03] ( 3, 2.50) ref -> r8 "CSE - aggressive"
; V06 cse1 [V06,T04] ( 3, 2.50) int -> rax "CSE - aggressive"
;
; Lcl frame size = 40
G_M49807_IG01:
sub rsp, 40
;; size=4 bbWeight=1 PerfScore 0.25
G_M49807_IG02:
mov r8, 0xD1FFAB1E ; data for IfStatements.IfStatements:inputs
mov r8, gword ptr [r8]
mov r9, r8
mov eax, dword ptr [r9+08H]
cmp ecx, eax
jae SHORT G_M49807_IG06
mov r10d, ecx
test byte ptr [r9+4*r10+10H], 1
jne SHORT G_M49807_IG04
;; size=35 bbWeight=1 PerfScore 10.00
G_M49807_IG03:
cmp edx, eax
jae SHORT G_M49807_IG06
mov r9d, edx
+ mov eax, 5
test byte ptr [r8+4*r9+10H], 1
- jne SHORT G_M49807_IG04- mov ecx, 5- ;; size=20 bbWeight=0.50 PerfScore 2.88+ cmove ecx, eax+ ;; size=21 bbWeight=0.50 PerfScore 2.50
G_M49807_IG04:
xor r8d, r8d
xor r9d, r9d
call [IfStatements.IfStatements:Consume(int,int,int,int)]
nop ;; size=13 bbWeight=1 PerfScore 3.75
G_M49807_IG05:
add rsp, 40
ret ;; size=5 bbWeight=1 PerfScore 1.25
G_M49807_IG06:
call CORINFO_HELP_RNGCHKFAIL
int3 ;; size=6 bbWeight=0 PerfScore 0.00

This one is pretty strange to me considering that the input values should be random.
7. OrArray is not converted by if-conversion, so no diffs.

@jakobbotsch

jakobbotsch commented Feb 1, 2023

Copy link
Copy Markdown
MemberAuthor

This one is pretty strange to me considering that the input values should be random.

Interestingly, if I change the implementation of AndArray from

[Benchmark]publicvoidAndArray(){for(inti=0;i<Iterations;i++){AndArrayInner(i,i+1);}}

to

[Benchmark]publicvoidAndArray(){intother=Iterations-1;for(inti=0;i<Iterations;i++){AndArrayInner(i,other);other--;}}

then I do start to see a benefit from cmov. Note that the code is:

[MethodImpl(MethodImplOptions.NoInlining)]publicstaticvoidAndArrayInner(intop1,intop2){if(inputs[op1]%2==0&&inputs[op2]%2==0){op1=5;}Consume(op1,op2,0,0);}

In the former case, the inputs[op1] comparison is going to have the same result as the inputs[op2] comparison from the previous loop iteration, seems like the branch predictor is smart enough to realize that.

@jakobbotsch
jakobbotsch marked this pull request as ready for review February 1, 2023 17:33
@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-coreclr jitstressregs, runtime-coreclr libraries-jitstressregs

@dotnetdotnet deleted a comment from azure-pipelinesBotFeb 1, 2023
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

@jakobbotsch

jakobbotsch commented Feb 1, 2023

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib PTAL @kunalspathak@BruceForstall (if you are available)

Diffs.
Practically all the regressions I have spot checked have been related to some more register moves fundamentally being required due to the removal of the control flow (e.g. see most examples posted above).

ARM64 diffs are due to the removal of some conditional reversing in if-conversion. Removing this benefits LSRA significantly on xarch and has an insignificant impact on arm64. Some more details in #81267 (comment)

// guarantee that the dstReg is used only in one of falseVal/trueVal, then
// we are good.
//
// To ensure the above we have some bespoke interference logic here on

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was an easy way to handle this particular scenario.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too, but luckily this check didn't turn out to be too ugly.
I do wonder if we could use a similar trick in the interval building for RMW opers to avoid delay free in most cases, perhaps something to look at in a follow up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we could but it would defeat the "linear" nature of the linear scan provided that it doesn't hurt much in common scenarios.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to understand a bit more, BuildDelayFreeUses() doesn't achieve what you want? It doesn't delay free unconditionally, but does a check about matching interval, etc.?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would defeat the "linear" nature of the linear scan

This does not affect the complexity since the number of ref positions created for op1/op2 is bounded by a constant (at most 2, for an indir with a contained LEA).

Just to understand a bit more, BuildDelayFreeUses() doesn't achieve what you want? It doesn't delay free unconditionally, but does a check about matching interval, etc.?

I did start out with BuildDelayFreeUses, but the extra flexibility we have in codegen (by swapping the trueVal/falseVal and reversing the condition) means that we don't need to mark something delay-free in practically all situations. BuildDelayFreeUses handles only situations where the other operand is a local, but for this particular node it could also be another contained indirection.

I looked at the RMW logic and I don't think it benefits from doing this since for the RMW opers we don't have the capability to contain two indirections anyway.

public static void Eq_byte_consume(byte a1, byte a2) {
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, {{w[0-9]+}}
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, eq
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{eq|ne}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does the x64 validation works for these examples?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't do any disasm checks on them for x64. Added that now.

@jakobbotsch

Copy link
Copy Markdown
MemberAuthor

Ping @kunalspathak@BruceForstall

@BruceForstallBruceForstall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
Co-authored-by: Bruce Forstall <brucefo@microsoft.com>
@jakobbotsch
jakobbotsch merged commit 430d6a4 into dotnet:mainFeb 9, 2023
@jakobbotsch
jakobbotsch deleted the xarch-select-contain-conditions branch February 9, 2023 10:16
@ghostghost locked as resolved and limited conversation to collaborators Mar 11, 2023
@EgorBo

EgorBo commented Mar 28, 2023

Copy link
Copy Markdown
Member

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RyuJit: avoid conditional jumps using cmov and similar instructions

6 participants

@jakobbotsch@EgorBo@TIHan@tannergooding@BruceForstall@kunalspathak