You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider devirtualizing & inlining Comparer<T>.Default.CompareTo as well. #39873
Starting in .NET Core 2.1, calling EqualityComparer<T>.Default.Equals is specially optimized by the JIT by devirtualizing and inlining the equality check.
However, as seen in SharpLab, the similar Comparer<T>.Default.CompareTo did not have the same fate. As seen in the generated assembly code, comparing integers with via IComparable<int> is optimized, but calling Comparer<int>.CompareTo(x1, x2) results in a function call. Making the two examples generate identical code would be a good optimization opportunity.
For Compare2 I see more complex codegen than sharplab...suspect their ambient has done some class init?
; Assembly listing for method C:Compare2(int,int):int
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; fully interruptible
; Final local variable assignments
;
; V00 arg0 [V00,T01] ( 3, 3 ) int -> rsi
; V01 arg1 [V01,T02] ( 3, 3 ) int -> rdi
; V02 OutArgs [V02 ] ( 1, 1 ) lclBlk (32) [rsp+0x00] "OutgoingArgSpace"
; V03 tmp1 [V03,T00] ( 3, 6 ) ref -> rcx "argument with side effect"
;
; Lcl frame size = 40
G_M274_IG01:
push rdi
push rsi
sub rsp, 40
mov esi, ecx
mov edi, edx
;; bbWeight=1 PerfScore 2.75
G_M274_IG02:
mov rcx, 0xD1FFAB1E
mov edx, 37
call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS
mov rcx, 0xD1FFAB1E
mov rcx, gword ptr [rcx]
mov edx, esi
mov r8d, edi
mov rax, qword ptr [rcx]
mov rax, qword ptr [rax+64]
mov rax, qword ptr [rax+32]
;; bbWeight=1 PerfScore 10.25
G_M274_IG03:
add rsp, 40
pop rsi
pop rdi
rex.jmp rax
;; bbWeight=1 PerfScore 3.25
Late devirt here requires class init, so doesn't happen either.
Querying runtime about current class of field Comparer`1.<Default>k__BackingField (declared as Comparer`1)
Field's current class not available
impDevirtualizeCall: Trying to devirtualize virtual call:
class for 'this' is Comparer`1 (attrib 20000400)
base method is Comparer`1::Compare
devirt to Comparer`1::Compare -- inexact or not final
[000004] --C-G------- * CALLV ind int Comparer`1.Compare
[000011] --CXG------- this in rcx +--* COMMA ref
[000010] H-CXG------- | +--* CALL help long HELPER.CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS
[000008] ------------ arg0 | | +--* CNS_INT long 0x7ffa70c30020
[000009] ------------ arg1 | | \--* CNS_INT int 37
[000006] ----G------- | \--* FIELD ref <Default>k__BackingField
[000002] ------------ arg1 +--* LCL_VAR int V00 arg0
[000003] ------------ arg2 \--* LCL_VAR int V01 arg1
Class not final or exact, and method not final
When the Comparer class has been initialized I get the sharplab code, and indeed here we've devirtualized late:
; Assembly listing for method C:Compare2(int,int):int; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; optimized code; rsp based frame; fully interruptible; Final local variable assignments;; V00 arg0 [V00,T00] ( 3, 3 ) int -> r8 ; V01 arg1 [V01,T01] ( 3, 3 ) int -> rax ;# V02 OutArgs [V02 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] "OutgoingArgSpace";; Lcl frame size = 0G_M274_IG01:movr8d,ecxmoveax,edx ;; bbWeight=1 PerfScore 0.50G_M274_IG02:movrcx,0xD1FFAB1Emovrcx, gword ptr [rcx]movedx,r8dmovr8d,eaxmovrax,0xD1FFAB1Emovrax, qword ptr [rax] ;; bbWeight=1 PerfScore 5.00G_M274_IG03: rex.jmprax ;; bbWeight=1 PerfScore 2.00
as seen in the jit dump
Querying runtime about current class of field Comparer`1.<Default>k__BackingField (declared as Comparer`1)
Runtime reports field is init-only and initialized and has class GenericComparer`1
impDevirtualizeCall: Trying to devirtualize virtual call:
class for 'this' is GenericComparer`1 [exact] (attrib 20000010)
base method is Comparer`1::Compare
devirt to GenericComparer`1::Compare -- exact
[000004] --C-G------- * CALLV ind int Comparer`1.Compare
[000006] ----G------- this in rcx +--* FIELD ref <Default>k__BackingField
[000002] ------------ arg1 +--* LCL_VAR int V00 arg0
[000003] ------------ arg2 \--* LCL_VAR int V01 arg1
exact; can devirtualize
... after devirt...
[000004] --C-G------- * CALL int GenericComparer`1.Compare
[000006] ----G------- this in rcx +--* FIELD ref <Default>k__BackingField
[000002] ------------ arg1 +--* LCL_VAR int V00 arg0
[000003] ------------ arg2 \--* LCL_VAR int V01 arg1
Codegen for some other types is interesting too. For short types we use subtract which is good but somehow also need to spill and reload one arg:
; Assembly listing for method C:Compare(short,short):int; Emitting BLENDED_CODE for X64 CPU with AVX - Windows; optimized code; rsp based frame; partially interruptible; Final local variable assignments;; V00 arg0 [V00,T00] ( 3, 3 ) short -> [rsp+0x08] do-not-enreg[F] ld-addr-op; V01 arg1 [V01,T01] ( 3, 3 ) short -> rdx ;# V02 OutArgs [V02 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] "OutgoingArgSpace";; Lcl frame size = 0G_M29376_IG01:mov dword ptr [rsp+08H],ecx ;; bbWeight=1 PerfScore 1.00G_M29376_IG02:movsxrax, word ptr [rsp+08H]movsxrdx,dxsubeax,edx ;; bbWeight=1 PerfScore 1.50G_M29376_IG03:ret ;; bbWeight=1 PerfScore 1.00
For the short case of Compare, the method is defined on a struct, so we indirect the this arg. The call gets inlined, showing that the address is immediately dereferenced, but we're unable to clean this up:
Trees after Morph - Inlining
STMT00001 (IL ???... ???)
[000005] --C--------- * RETURN int
[000009] ---XG------- \--* SUB int
[000008] *--XG------- +--* IND short
[000006] ------------ | \--* ADDR long
[000007] -------N---- | \--* LCL_VAR int V00 arg0
[000002] ------------ \--* LCL_VAR short V01 arg1
Wonder if the mixture of types here gets in the way. Seems a bit odd that we widen the type in LCL_VAR int V00 arg0 as we're pushing the address of the local, not its value.
Activity
AndyAyersMS commentedon Jul 24, 2020
Thanks, will take a look. Suspect this is similar to #39519, the jit devirtualizes late and so can't inline.
AndyAyersMS commentedon Jul 24, 2020
For
Compare2I see more complex codegen than sharplab...suspect their ambient has done some class init?Late devirt here requires class init, so doesn't happen either.
When the Comparer class has been initialized I get the sharplab code, and indeed here we've devirtualized late:
as seen in the jit dump
Codegen for some other types is interesting too. For short types we use subtract which is good but somehow also need to spill and reload one arg:
AndyAyersMS commentedon Jul 24, 2020
For the
shortcase ofCompare, the method is defined on a struct, so we indirect thethisarg. The call gets inlined, showing that the address is immediately dereferenced, but we're unable to clean this up:Wonder if the mixture of types here gets in the way. Seems a bit odd that we widen the type in
LCL_VAR int V00 arg0as we're pushing the address of the local, not its value.cc @dotnet/jit-contrib
AndyAyersMS commentedon Jul 24, 2020
Seems to be the case, with
we now generate
Quick gloss of the above importer change over SPC looks promising:
AndyAyersMS commentedon Jan 22, 2021
Overall support should parallel what we did for the generic comparer, something like the following:
[Intrinsic]to theDefaultmethod for CoreCLRimpGetSpecialIntrinsicExactReturnTypeto handle this case (deferring computation of the type to the runtime)getDefaultEqualityComparerClassfor comparersfgGetStaticsCCtorHelperso things get properly cleaned upWill consider this for .Net 6.
stephentoub commentedon Feb 16, 2021
This was fixed by #48160, correct?