-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JIT: optimize away overflow check in some span non portable casts #9426
Comments
It should also use unsigned arithmetic, division by const (especially power of 2) is cheaper. And checked cast from I'll give it a try, anyway I don't think the JIT will learn to optimize this code properly anytime soon... |
Hmm, something like this: if (Unsafe.SizeOf<TFrom>() == Unsafe.SizeOf<TTo>())
{
newLength = source.Length;
}
else if (Unsafe.SizeOf<TFrom>() == 1)
{
newLength = (int)((uint)source.Length / (uint)Unsafe.SizeOf<TTo>());
}
else
{
ulong newLongLength = (ulong)(uint)source.Length * (ulong)Unsafe.SizeOf<TFrom>() / (ulong)Unsafe.SizeOf<TTo>();
if (Unsafe.SizeOf<TFrom>() < Unsafe.SizeOf<TTo>())
{
newLength = (int)newLongLength;
}
else
{
newLength = checked((int)newLongLength);
}
} A bit large but fortunately it is still inlined. The entire 8BC9 mov ecx, ecx
48C1E103 shl rcx, 3
48F7C100000080 test rcx, 0xFFFFFFFF80000000
7517 jne SHORT G_M13600_IG07 |
@mikedn Looks like you don't need the second |
Could be, need to check how well those long operations are handled on 32 bit. It may also be possible to improve the JIT certain areas to reduce the number of special cases. One significant CQ issues is related to signed arithmetic and that may be difficult to deal with in the JIT. But other issues this code exposes might be more manageable. |
Yeah, it looks like JIT's handling of 64 bit Without special cases for byte sized to/from G_M61089_IG01:
55 push ebp
8BEC mov ebp, esp
57 push edi
56 push esi
83EC10 sub esp, 16
8BF1 mov esi, ecx
G_M61089_IG02:
8D4508 lea eax, bword ptr [ebp+08H]
8B38 mov edi, dword ptr [eax]
8B4004 mov eax, dword ptr [eax+4]
33D2 xor edx, edx
52 push edx
50 push eax
6A00 push 0
6A04 push 4
E8F93D5A0A call CORINFO_HELP_LMUL
8945F0 mov dword ptr [ebp-10H], eax
8955F4 mov dword ptr [ebp-0CH], edx
8B45F0 mov eax, dword ptr [ebp-10H]
8B55F4 mov edx, dword ptr [ebp-0CH]
52 push edx
50 push eax
6A00 push 0
6A01 push 1
E8E268390A call CORINFO_HELP_ULDIV
8945E8 mov dword ptr [ebp-18H], eax
8955EC mov dword ptr [ebp-14H], edx
8B45E8 mov eax, dword ptr [ebp-18H]
8B55EC mov edx, dword ptr [ebp-14H]
85C0 test eax, eax
7812 js SHORT G_M61089_IG04
85D2 test edx, edx
750E jne SHORT G_M61089_IG04
893E mov dword ptr [esi], edi
894604 mov dword ptr [esi+4], eax
G_M61089_IG03:
8D65F8 lea esp, [ebp-08H]
5E pop esi
5F pop edi
5D pop ebp
C20800 ret 8
G_M61089_IG04:
E82BA7390A call CORINFO_HELP_OVERFLOW
CC int3 |
Attempting to navigate around all the problems the JIT has with this code (and boy, does it have a few problems...) risks making the code too complicated. I think that the best option is to handle only those cases that are likely to be useful.
So, we can do something like: int newLength;
if (Unsafe.SizeOf<TFrom>() == Unsafe.SizeOf<TTo>())
{
newLength = source.Length;
}
else if (Unsafe.SizeOf<TFrom>() == 1)
{
newLength = (int)((uint)source.Length / (uint)Unsafe.SizeOf<TTo>());
}
else
{
ulong newUInt64Length = UInt64MulDiv((uint)source.Length, (uint)Unsafe.SizeOf<TFrom>(), (uint)Unsafe.SizeOf<TTo>());
newLength = checked((int)newUInt64Length);
}
...
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static ulong UInt64MulDiv(uint x, uint y, uint z) => (ulong)x * (ulong)y / (ulong)z; The main purpose of
@jkotas Opinions? |
A list of the various problems the JIT has with this (original and proposed) code could be useful:
8BD2 mov edx, edx
48C1E202 shl rdx, 2
48C1EA02 shr rdx, 2
48F7C200000080 test rdx, 0xFFFFFFFF80000000
750E jne SHORT G_M30356_IG04
8BD2 mov edx, edx ; zero extend
48C1EA02 shr rdx, 2
48F7C200000080 test rdx, 0xFFFFFFFF80000000
750E jne SHORT G_M49564_IG04
|
Sounds good to me.
Is there no way to do this without a helper method? |
Anything that ensures that the calls happen before the int->long casts will do. For example, something like: uint length = (uint)source.Length;
uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
uint toSize = ...
ulong newUInt64Length = (ulong)length * (ulong)fromSize ... The problem is that calls tends tend to spill the stack to local variables and then you end up multiplying 2 |
The API |
Per dotnet/coreclr#15941 this is now called |
When casting a span to a larger type via
NonPortableCast
, the result should not be able to overflow and the jit should be able to eliminate the check and throw.This came up in some span conversion work over in CoreFx.
It might be hard to catch all cases of this since the current code for
NonPortableCast<FromType, ToType>
first multiplies the length bysizeof<FromType>
and then divides bysizeof<ToType>
but when the from type ischar
the resulting length will not overflow anint
.For instance:
Currently this does a fairly expensive and overflow check;
We could probably also fix this in the source code by comparing sizes and only using checked math for the cases where size increases.
category:cq
theme:basic-cq
skill-level:expert
cost:small
The text was updated successfully, but these errors were encountered: