Skip to content

Commit

Permalink
Drop generic type constraints from Unsafe.BitCast
Browse files Browse the repository at this point in the history
  • Loading branch information
MihaZupan committed Apr 9, 2024
1 parent f54c778 commit 179ba2f
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 190 deletions.
9 changes: 7 additions & 2 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4717,6 +4717,13 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
ClassLayout* toLayout = nullptr;
var_types toType = TypeHandleToVarType(toTypeHnd, &toLayout);

if (fromType == TYP_REF || info.compCompHnd->getBoxHelper(fromTypeHnd) == CORINFO_HELP_BOX_NULLABLE ||
toType == TYP_REF || info.compCompHnd->getBoxHelper(toTypeHnd) == CORINFO_HELP_BOX_NULLABLE)
{
// Fallback to the software implementation to throw when the types don't fit "where T : struct"
return nullptr;
}

unsigned fromSize = fromLayout != nullptr ? fromLayout->GetSize() : genTypeSize(fromType);
unsigned toSize = toLayout != nullptr ? toLayout->GetSize() : genTypeSize(toType);

Expand All @@ -4729,8 +4736,6 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
return nullptr;
}

assert((fromType != TYP_REF) && (toType != TYP_REF));

GenTree* op1 = impPopStack().val;

op1 = impImplicitR4orR8Cast(op1, fromType);
Expand Down
320 changes: 160 additions & 160 deletions src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ internal static bool IsPrimitiveType(this CorElementType et)

[Intrinsic]
internal static bool IsKnownConstant(char t) => false;

[Intrinsic]
internal static bool IsKnownConstant<T>(T t) where T : struct => false;
#pragma warning restore IDE0060
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,14 @@ public static bool AreSame<T>([AllowNull] ref readonly T left, [AllowNull] ref r
/// <summary>
/// Reinterprets the given value of type <typeparamref name="TFrom" /> as a value of type <typeparamref name="TTo" />.
/// </summary>
/// <exception cref="NotSupportedException">The size of <typeparamref name="TFrom" /> and <typeparamref name="TTo" /> are not the same.</exception>
/// <exception cref="NotSupportedException">The sizes of <typeparamref name="TFrom" /> and <typeparamref name="TTo" /> are not the same
/// or the type parameters are not <see langword="struct"/>s.</exception>
[Intrinsic]
[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TTo BitCast<TFrom, TTo>(TFrom source)
where TFrom : struct
where TTo : struct
{
if (sizeof(TFrom) != sizeof(TTo))
if (sizeof(TFrom) != sizeof(TTo) || default(TFrom) is null || default(TTo) is null)
{
ThrowHelper.ThrowNotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static unsafe bool CanUsePackedIndexOf<T>(T value)
Debug.Assert(RuntimeHelpers.IsBitwiseEquatable<T>());
Debug.Assert(sizeof(T) == sizeof(ushort));

return *(ushort*)&value - 1u < 254u;
return Unsafe.BitCast<T, ushort>(value) - 1u < 254u;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
52 changes: 30 additions & 22 deletions src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;

#pragma warning disable 8500 // sizeof of managed types
Expand Down Expand Up @@ -225,7 +224,7 @@ public static int IndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T val
}

// Adapted from IndexOf(...)
public static unsafe bool Contains<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
public static bool Contains<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
{
Debug.Assert(length >= 0);

Expand Down Expand Up @@ -297,7 +296,7 @@ public static unsafe bool Contains<T>(ref T searchSpace, T value, int length) wh
return true;
}

public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
public static int IndexOf<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
{
Debug.Assert(length >= 0);

Expand Down Expand Up @@ -1304,11 +1303,11 @@ public static int SequenceCompareTo<T>(ref T first, int firstLength, ref T secon
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool ContainsValueType<T>(ref T searchSpace, T value, int length) where T : struct, INumber<T>
internal static bool ContainsValueType<T>(ref T searchSpace, T value, int length) where T : struct, INumber<T>
{
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value))
{
return PackedSpanHelpers.Contains(ref Unsafe.As<T, short>(ref searchSpace), *(short*)&value, length);
return PackedSpanHelpers.Contains(ref Unsafe.As<T, short>(ref searchSpace), Unsafe.BitCast<T, short>(value), length);
}

return NonPackedContainsValueType(ref searchSpace, value, length);
Expand Down Expand Up @@ -1478,15 +1477,15 @@ internal static int IndexOfAnyExceptValueType<T>(ref T searchSpace, T value, int
=> IndexOfValueType<T, Negate<T>>(ref searchSpace, value, length);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe int IndexOfValueType<TValue, TNegator>(ref TValue searchSpace, TValue value, int length)
private static int IndexOfValueType<TValue, TNegator>(ref TValue searchSpace, TValue value, int length)
where TValue : struct, INumber<TValue>
where TNegator : struct, INegator<TValue>
{
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value))
{
return typeof(TNegator) == typeof(DontNegate<short>)
? PackedSpanHelpers.IndexOf(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value, length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value, length);
? PackedSpanHelpers.IndexOf(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value), length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value), length);
}

return NonPackedIndexOfValueType<TValue, TNegator>(ref searchSpace, value, length);
Expand Down Expand Up @@ -1665,24 +1664,33 @@ internal static int IndexOfAnyExceptValueType<T>(ref T searchSpace, T value0, T
=> IndexOfAnyValueType<T, Negate<T>>(ref searchSpace, value0, value1, length);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, int length)
private static int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, int length)
where TValue : struct, INumber<TValue>
where TNegator : struct, INegator<TValue>
{
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1))
{
if ((*(char*)&value0 ^ *(char*)&value1) == 0x20)
char char0 = Unsafe.BitCast<TValue, char>(value0);
char char1 = Unsafe.BitCast<TValue, char>(value1);

if (RuntimeHelpers.IsKnownConstant(value0) && RuntimeHelpers.IsKnownConstant(value1))
{
char lowerCase = (char)Math.Max(*(char*)&value0, *(char*)&value1);
// If the values differ only in the 0x20 bit, we can optimize the search by reducing the number of comparisons.
// This optimization only applies to a small subset of values and the throughput difference is not too significant.
// We avoid introducing per-call overhead for non-constant values by guarding this optimization behind RuntimeHelpers.IsKnownConstant.
if ((char0 ^ char1) == 0x20)
{
char lowerCase = (char)Math.Max(char0, char1);

return typeof(TNegator) == typeof(DontNegate<short>)
? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length)
: PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length);
return typeof(TNegator) == typeof(DontNegate<short>)
? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length)
: PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length);
}
}

return typeof(TNegator) == typeof(DontNegate<short>)
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, length);
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), char0, char1, length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), char0, char1, length);
}

return NonPackedIndexOfAnyValueType<TValue, TNegator>(ref searchSpace, value0, value1, length);
Expand Down Expand Up @@ -1882,15 +1890,15 @@ internal static int IndexOfAnyExceptValueType<T>(ref T searchSpace, T value0, T
=> IndexOfAnyValueType<T, Negate<T>>(ref searchSpace, value0, value1, value2, length);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length)
private static int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length)
where TValue : struct, INumber<TValue>
where TNegator : struct, INegator<TValue>
{
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1) && PackedSpanHelpers.CanUsePackedIndexOf(value2))
{
return typeof(TNegator) == typeof(DontNegate<short>)
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length);
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value0), Unsafe.BitCast<TValue, char>(value1), Unsafe.BitCast<TValue, char>(value2), length)
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value0), Unsafe.BitCast<TValue, char>(value1), Unsafe.BitCast<TValue, char>(value2), length);
}

return NonPackedIndexOfAnyValueType<TValue, TNegator>(ref searchSpace, value0, value1, value2, length);
Expand Down Expand Up @@ -3466,15 +3474,15 @@ internal static int IndexOfAnyExceptInRangeUnsignedNumber<T>(ref T searchSpace,
IndexOfAnyInRangeUnsignedNumber<T, Negate<T>>(ref searchSpace, lowInclusive, highInclusive, length);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe int IndexOfAnyInRangeUnsignedNumber<T, TNegator>(ref T searchSpace, T lowInclusive, T highInclusive, int length)
private static int IndexOfAnyInRangeUnsignedNumber<T, TNegator>(ref T searchSpace, T lowInclusive, T highInclusive, int length)
where T : struct, IUnsignedNumber<T>, IComparisonOperators<T, T, bool>
where TNegator : struct, INegator<T>
{
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(ushort) && PackedSpanHelpers.CanUsePackedIndexOf(lowInclusive) && PackedSpanHelpers.CanUsePackedIndexOf(highInclusive) && highInclusive >= lowInclusive)
{
ref char charSearchSpace = ref Unsafe.As<T, char>(ref searchSpace);
char charLowInclusive = *(char*)&lowInclusive;
char charRange = (char)(*(char*)&highInclusive - charLowInclusive);
char charLowInclusive = Unsafe.BitCast<T, char>(lowInclusive);
char charRange = (char)(Unsafe.BitCast<T, char>(highInclusive) - charLowInclusive);

return typeof(TNegator) == typeof(DontNegate<ushort>)
? PackedSpanHelpers.IndexOfAnyInRange(ref charSearchSpace, charLowInclusive, charRange, length)
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13300,7 +13300,7 @@ public static partial class Unsafe
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")]
public static T? As<T>(object? o) where T : class? { throw null; }
public static ref TTo As<TFrom, TTo>(ref TFrom source) { throw null; }
public static TTo BitCast<TFrom, TTo>(TFrom source) where TFrom : struct where TTo : struct { throw null; }
public static TTo BitCast<TFrom, TTo>(TFrom source) { throw null; }
public static System.IntPtr ByteOffset<T>([System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T target) { throw null; }
[System.CLSCompliantAttribute(false)]
public static void CopyBlock(ref byte destination, ref readonly byte source, uint byteCount) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,18 @@ public static unsafe void BitCast()
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int, long>(5));
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, int>(5));

// Conversion to/from a class should fail

Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<string, long>(string.Empty));
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, string>(42));
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<string, string>(string.Empty));

// Conversion to/from nullable value types should fail

Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int?, long>(42));
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, int?>(42));
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int?, int?>(42));

// Conversion between floating-point and same sized integral should succeed

Assert.Equal(0x8000_0000u, Unsafe.BitCast<float, uint>(-0.0f));
Expand Down

0 comments on commit 179ba2f

Please sign in to comment.