diff --git a/src/Common/src/CoreLib/System/Runtime/InteropServices/MemoryMarshal.Fast.cs b/src/Common/src/CoreLib/System/Runtime/InteropServices/MemoryMarshal.Fast.cs index 967459e4b743..8a5bb753949b 100644 --- a/src/Common/src/CoreLib/System/Runtime/InteropServices/MemoryMarshal.Fast.cs +++ b/src/Common/src/CoreLib/System/Runtime/InteropServices/MemoryMarshal.Fast.cs @@ -59,9 +59,38 @@ public static Span Cast(Span source) if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); + // Use unsigned integers - unsigned division by constant (especially by power of 2) + // and checked casts are faster and smaller. + uint fromSize = (uint)Unsafe.SizeOf(); + uint toSize = (uint)Unsafe.SizeOf(); + uint fromLength = (uint)source.Length; + int toLength; + if (fromSize == toSize) + { + // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // should be optimized to just `length` but the JIT doesn't do that today. + toLength = (int)fromLength; + } + else if (fromSize == 1) + { + // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int` + // and can't eliminate the checked cast. This also avoids a 32 bit specific issue, + // the JIT can't eliminate long multiply by 1. + toLength = (int)(fromLength / toSize); + } + else + { + // Ensure that casts are done in such a way that the JIT is able to "see" + // the uint->ulong casts and the multiply together so that on 32 bit targets + // 32x32to64 multiplication is used. + ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize; + toLength = checked((int)toLengthUInt64); + } + return new Span( ref Unsafe.As(ref source._pointer.Value), - checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); + toLength); } /// @@ -84,9 +113,38 @@ public static ReadOnlySpan Cast(ReadOnlySpan source) if (RuntimeHelpers.IsReferenceOrContainsReferences()) ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); + // Use unsigned integers - unsigned division by constant (especially by power of 2) + // and checked casts are faster and smaller. + uint fromSize = (uint)Unsafe.SizeOf(); + uint toSize = (uint)Unsafe.SizeOf(); + uint fromLength = (uint)source.Length; + int toLength; + if (fromSize == toSize) + { + // Special case for same size types - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // should be optimized to just `length` but the JIT doesn't do that today. + toLength = (int)fromLength; + } + else if (fromSize == 1) + { + // Special case for byte sized TFrom - `(ulong)fromLength * (ulong)fromSize / (ulong)toSize` + // becomes `(ulong)fromLength / (ulong)toSize` but the JIT can't narrow it down to `int` + // and can't eliminate the checked cast. This also avoids a 32 bit specific issue, + // the JIT can't eliminate long multiply by 1. + toLength = (int)(fromLength / toSize); + } + else + { + // Ensure that casts are done in such a way that the JIT is able to "see" + // the uint->ulong casts and the multiply together so that on 32 bit targets + // 32x32to64 multiplication is used. + ulong toLengthUInt64 = (ulong)fromLength * (ulong)fromSize / (ulong)toSize; + toLength = checked((int)toLengthUInt64); + } + return new ReadOnlySpan( ref Unsafe.As(ref MemoryMarshal.GetReference(source)), - checked((int)((long)source.Length * Unsafe.SizeOf() / Unsafe.SizeOf()))); + toLength); } ///