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
Optimize Math.Max/Min to a single instruction on x64 when one of the arguments is a constant (not NaN and whoever will be implementing it has to be careful around -/+0.0)
floatTest(floata)=>Math.Max(a,10);
Currently emits:
; Method Tests4:Test(float):float:thisG_M55200_IG01:vzeroupperG_M55200_IG02:vmovssxmm0, dword ptr [reloc @RWD00] vucomiss xmm1,xmm0jp SHORT G_M55200_IG03je SHORT G_M55200_IG06G_M55200_IG03: vucomiss xmm1,xmm1jp SHORT G_M55200_IG05 vucomiss xmm1,xmm0ja SHORT G_M55200_IG04jmp SHORT G_M55200_IG08G_M55200_IG04: vmovaps xmm0,xmm1jmp SHORT G_M55200_IG08G_M55200_IG05: vmovaps xmm0,xmm1jmp SHORT G_M55200_IG08G_M55200_IG06: vmovaps xmm1,xmm0 vmovd eax,xmm1testeax,eaxjl SHORT G_M55200_IG07jmp SHORT G_M55200_IG08G_M55200_IG07:vmovssxmm0, dword ptr [reloc @RWD00]G_M55200_IG08:retRWD00 dd 41200000h ; 10; Total bytes of code: 68
Expected codegen:
; Method Tests4:Test(float):float:thisvzerouppervmaxssxmm0,xmm1, dword ptr [reloc @RWD00]retRWD00 dd 41200000h ; 10.0; Total bytes of code: 12
#65584 did it for ARM where we could do it even for both non-constants
In particular, Math.Max and Math.Min for floating-point have a requirement, from the IEEE 754 spec, that -0.0 be the minimum of it and +0.0. Likewise, they have a requirement that NaN is propagated (if both inputs are NaN, then the exact NaN returned doesn't matter).
x86/x64 provide maxss and minss. These instructions will return the second operand if both operands are 0 (same or differing signs). Likewise, if either input is NaN, the second operand is returned. Otherwise, the correct max/min is returned.
What this functionally means is that if both inputs are unknown, we can't really "optimize" and instead have to use maxss/minss and then some follow up computations. However, if either input is constant then we actually can optimize it down to a single instruction in all cases:
In the case both are constant, we simply constant fold and it doesn't matter.
If either input is known to be NaN, it is used as the second argument (ensuring NaN is propagated since either input being NaN means the second argument is returned)
For maxss, if either input is +0.0 it takes the second argument (ensuring +0.0 is returned if both inputs are zero since both inputs being zero, of either sign, means the second argument is returned)
For minss, if either input is -0.0 it takes the second argument (ensuring -0.0 is returned if both inputs are zero since both inputs being zero, of either sign, means the second argument is returned)
Otherwise, for any other known constant input it is the first argument
If the unknown is NaN, this ensures it is returned since either input being NaN means the second argument is returned
If the unknown is +0.0 for maxss and the known was -0.0, it would ensure +0.0 is returned since both inputs being zero, of either sign, means the second argument is returned
If the unknown is -0.0 for minss and the known was +0.0, it would ensure -0.0 is returned since both inputs being zero, of either sign, means the second argument is returned
For any other inputs known or unknown; the default handling is already correct
This ensures that Max returns NaN and +0.0 and likewise that Min returns NaN and -0.0 for their respective special cases.
Reacted by Egor Bogatov, Adeel Mujahid and HighPerfDotNet
@SkiFoD see #65584, it ifdefed special import for Max for x86/64, you need to enable it, most likely you will also have to modify lsraxarch and the xarch emitter
@EgorBo Seems like I figured out how to make the changes, but I got a few questions.
Is it all right that the call transforms to the intrinsic use only under a pressure (like if the Math.Max/Min is called in a huge cycle) or if there are envs "COMPlus_TieredCompilation=0" and "COMPlus_JITMinOpts=0" set up?
p.s. For some reason if I used the envs along with "COMPlus_JitDump/JitDisasm" then there are not output in the console :( Have you ever come across with such a behaviour?
Activity
tannergooding commentedon Feb 19, 2022
In particular,
Math.MaxandMath.Minfor floating-point have a requirement, from the IEEE 754 spec, that-0.0be the minimum of it and+0.0. Likewise, they have a requirement thatNaNis propagated (if both inputs are NaN, then the exact NaN returned doesn't matter).x86/x64 provide
maxssandminss. These instructions will return the second operand if both operands are0(same or differing signs). Likewise, if either input isNaN, the second operand is returned. Otherwise, the correct max/min is returned.What this functionally means is that if both inputs are unknown, we can't really "optimize" and instead have to use
maxss/minssand then some follow up computations. However, if either input is constant then we actually can optimize it down to a single instruction in all cases:NaN, it is used as the second argument (ensuring NaN is propagated since either input being NaN means the second argument is returned)maxss, if either input is+0.0it takes the second argument (ensuring +0.0 is returned if both inputs are zero since both inputs being zero, of either sign, means the second argument is returned)minss, if either input is-0.0it takes the second argument (ensuring -0.0 is returned if both inputs are zero since both inputs being zero, of either sign, means the second argument is returned)maxssand the known was-0.0, it would ensure +0.0 is returned since both inputs being zero, of either sign, means the second argument is returnedminssand the known was+0.0, it would ensure -0.0 is returned since both inputs being zero, of either sign, means the second argument is returnedThis ensures that
MaxreturnsNaNand+0.0and likewise thatMinreturnsNaNand-0.0for their respective special cases.SkiFoD commentedon Feb 21, 2022
Hey, everyone. I would like to take a look at the issue. @EgorBo Could you please tell me how you made "Math.Max" call to get inlined?
EgorBo commentedon Feb 21, 2022
Hey, sure! What do you mean inlined? Intrinsic?
SkiFoD commentedon Feb 21, 2022
I'm trying to get the same emit, but I always get something like this:

So I can't look inside the Math.Max call to invistigate it.
EgorBo commentedon Feb 21, 2022
@SkiFoD see #65584, it ifdefed special import for Max for x86/64, you need to enable it, most likely you will also have to modify lsraxarch and the xarch emitter
SkiFoD commentedon Feb 21, 2022
@EgorBo Thank you, so basically you are changing the call to instrinsic use.
SkiFoD commentedon Feb 21, 2022
@EgorBo Seems like I figured out how to make the changes, but I got a few questions.
Is it all right that the call transforms to the intrinsic use only under a pressure (like if the Math.Max/Min is called in a huge cycle) or if there are envs "COMPlus_TieredCompilation=0" and "COMPlus_JITMinOpts=0" set up?
p.s. For some reason if I used the envs along with "COMPlus_JitDump/JitDisasm" then there are not output in the console :( Have you ever come across with such a behaviour?
10 remaining items
Add xarch optimization for min/max (dotnet#65625)
Changes according to the requirements (dotnet#65625)
Draft for Math.Max/Math.Min optimization (dotnet#65625)
Draft for optimizing Math.Max/Math.Min with a const (dotnet#65625)
Fix tests (dotnet#65625)
Refactoring of the conditions (dotnet#65625)
Fix of the summary (dotnet#65625)
Refactoring due to the PR comments (dotnet#65625)
Add spilling side effect + Fix of formats (dotnet#65625)
Optimization. Use Min/Max intrinsics if one of arguments is constant. (…