Skip to content

Commit 179ba2f

Browse files
committed
Drop generic type constraints from Unsafe.BitCast
1 parent f54c778 commit 179ba2f

File tree

8 files changed

+217
-190
lines changed

8 files changed

+217
-190
lines changed

src/coreclr/jit/importercalls.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4717,6 +4717,13 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
47174717
ClassLayout* toLayout = nullptr;
47184718
var_types toType = TypeHandleToVarType(toTypeHnd, &toLayout);
47194719

4720+
if (fromType == TYP_REF || info.compCompHnd->getBoxHelper(fromTypeHnd) == CORINFO_HELP_BOX_NULLABLE ||
4721+
toType == TYP_REF || info.compCompHnd->getBoxHelper(toTypeHnd) == CORINFO_HELP_BOX_NULLABLE)
4722+
{
4723+
// Fallback to the software implementation to throw when the types don't fit "where T : struct"
4724+
return nullptr;
4725+
}
4726+
47204727
unsigned fromSize = fromLayout != nullptr ? fromLayout->GetSize() : genTypeSize(fromType);
47214728
unsigned toSize = toLayout != nullptr ? toLayout->GetSize() : genTypeSize(toType);
47224729

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

4732-
assert((fromType != TYP_REF) && (toType != TYP_REF));
4733-
47344739
GenTree* op1 = impPopStack().val;
47354740

47364741
op1 = impImplicitR4orR8Cast(op1, fromType);

src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs

Lines changed: 160 additions & 160 deletions
Large diffs are not rendered by default.

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ internal static bool IsPrimitiveType(this CorElementType et)
129129

130130
[Intrinsic]
131131
internal static bool IsKnownConstant(char t) => false;
132+
133+
[Intrinsic]
134+
internal static bool IsKnownConstant<T>(T t) where T : struct => false;
132135
#pragma warning restore IDE0060
133136
}
134137
}

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,14 @@ public static bool AreSame<T>([AllowNull] ref readonly T left, [AllowNull] ref r
242242
/// <summary>
243243
/// Reinterprets the given value of type <typeparamref name="TFrom" /> as a value of type <typeparamref name="TTo" />.
244244
/// </summary>
245-
/// <exception cref="NotSupportedException">The size of <typeparamref name="TFrom" /> and <typeparamref name="TTo" /> are not the same.</exception>
245+
/// <exception cref="NotSupportedException">The sizes of <typeparamref name="TFrom" /> and <typeparamref name="TTo" /> are not the same
246+
/// or the type parameters are not <see langword="struct"/>s.</exception>
246247
[Intrinsic]
247248
[NonVersionable]
248249
[MethodImpl(MethodImplOptions.AggressiveInlining)]
249250
public static TTo BitCast<TFrom, TTo>(TFrom source)
250-
where TFrom : struct
251-
where TTo : struct
252251
{
253-
if (sizeof(TFrom) != sizeof(TTo))
252+
if (sizeof(TFrom) != sizeof(TTo) || default(TFrom) is null || default(TTo) is null)
254253
{
255254
ThrowHelper.ThrowNotSupportedException();
256255
}

src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Packed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static unsafe bool CanUsePackedIndexOf<T>(T value)
2727
Debug.Assert(RuntimeHelpers.IsBitwiseEquatable<T>());
2828
Debug.Assert(sizeof(T) == sizeof(ushort));
2929

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

3333
[MethodImpl(MethodImplOptions.AggressiveInlining)]

src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Diagnostics;
66
using System.Numerics;
77
using System.Runtime.CompilerServices;
8-
using System.Runtime.InteropServices;
98
using System.Runtime.Intrinsics;
109

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

227226
// Adapted from IndexOf(...)
228-
public static unsafe bool Contains<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
227+
public static bool Contains<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
229228
{
230229
Debug.Assert(length >= 0);
231230

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

300-
public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
299+
public static int IndexOf<T>(ref T searchSpace, T value, int length) where T : IEquatable<T>?
301300
{
302301
Debug.Assert(length >= 0);
303302

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

13061305
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1307-
internal static unsafe bool ContainsValueType<T>(ref T searchSpace, T value, int length) where T : struct, INumber<T>
1306+
internal static bool ContainsValueType<T>(ref T searchSpace, T value, int length) where T : struct, INumber<T>
13081307
{
13091308
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value))
13101309
{
1311-
return PackedSpanHelpers.Contains(ref Unsafe.As<T, short>(ref searchSpace), *(short*)&value, length);
1310+
return PackedSpanHelpers.Contains(ref Unsafe.As<T, short>(ref searchSpace), Unsafe.BitCast<T, short>(value), length);
13121311
}
13131312

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

14801479
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1481-
private static unsafe int IndexOfValueType<TValue, TNegator>(ref TValue searchSpace, TValue value, int length)
1480+
private static int IndexOfValueType<TValue, TNegator>(ref TValue searchSpace, TValue value, int length)
14821481
where TValue : struct, INumber<TValue>
14831482
where TNegator : struct, INegator<TValue>
14841483
{
14851484
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value))
14861485
{
14871486
return typeof(TNegator) == typeof(DontNegate<short>)
1488-
? PackedSpanHelpers.IndexOf(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value, length)
1489-
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value, length);
1487+
? PackedSpanHelpers.IndexOf(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value), length)
1488+
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), Unsafe.BitCast<TValue, char>(value), length);
14901489
}
14911490

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

16671666
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1668-
private static unsafe int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, int length)
1667+
private static int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, int length)
16691668
where TValue : struct, INumber<TValue>
16701669
where TNegator : struct, INegator<TValue>
16711670
{
16721671
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1))
16731672
{
1674-
if ((*(char*)&value0 ^ *(char*)&value1) == 0x20)
1673+
char char0 = Unsafe.BitCast<TValue, char>(value0);
1674+
char char1 = Unsafe.BitCast<TValue, char>(value1);
1675+
1676+
if (RuntimeHelpers.IsKnownConstant(value0) && RuntimeHelpers.IsKnownConstant(value1))
16751677
{
1676-
char lowerCase = (char)Math.Max(*(char*)&value0, *(char*)&value1);
1678+
// If the values differ only in the 0x20 bit, we can optimize the search by reducing the number of comparisons.
1679+
// This optimization only applies to a small subset of values and the throughput difference is not too significant.
1680+
// We avoid introducing per-call overhead for non-constant values by guarding this optimization behind RuntimeHelpers.IsKnownConstant.
1681+
if ((char0 ^ char1) == 0x20)
1682+
{
1683+
char lowerCase = (char)Math.Max(char0, char1);
16771684

1678-
return typeof(TNegator) == typeof(DontNegate<short>)
1679-
? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length)
1680-
: PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length);
1685+
return typeof(TNegator) == typeof(DontNegate<short>)
1686+
? PackedSpanHelpers.IndexOfAnyIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length)
1687+
: PackedSpanHelpers.IndexOfAnyExceptIgnoreCase(ref Unsafe.As<TValue, char>(ref searchSpace), lowerCase, length);
1688+
}
16811689
}
16821690

16831691
return typeof(TNegator) == typeof(DontNegate<short>)
1684-
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, length)
1685-
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, length);
1692+
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), char0, char1, length)
1693+
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), char0, char1, length);
16861694
}
16871695

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

18841892
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1885-
private static unsafe int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length)
1893+
private static int IndexOfAnyValueType<TValue, TNegator>(ref TValue searchSpace, TValue value0, TValue value1, TValue value2, int length)
18861894
where TValue : struct, INumber<TValue>
18871895
where TNegator : struct, INegator<TValue>
18881896
{
18891897
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(TValue) == typeof(short) && PackedSpanHelpers.CanUsePackedIndexOf(value0) && PackedSpanHelpers.CanUsePackedIndexOf(value1) && PackedSpanHelpers.CanUsePackedIndexOf(value2))
18901898
{
18911899
return typeof(TNegator) == typeof(DontNegate<short>)
1892-
? PackedSpanHelpers.IndexOfAny(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length)
1893-
: PackedSpanHelpers.IndexOfAnyExcept(ref Unsafe.As<TValue, char>(ref searchSpace), *(char*)&value0, *(char*)&value1, *(char*)&value2, length);
1900+
? 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)
1901+
: 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);
18941902
}
18951903

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

34683476
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3469-
private static unsafe int IndexOfAnyInRangeUnsignedNumber<T, TNegator>(ref T searchSpace, T lowInclusive, T highInclusive, int length)
3477+
private static int IndexOfAnyInRangeUnsignedNumber<T, TNegator>(ref T searchSpace, T lowInclusive, T highInclusive, int length)
34703478
where T : struct, IUnsignedNumber<T>, IComparisonOperators<T, T, bool>
34713479
where TNegator : struct, INegator<T>
34723480
{
34733481
if (PackedSpanHelpers.PackedIndexOfIsSupported && typeof(T) == typeof(ushort) && PackedSpanHelpers.CanUsePackedIndexOf(lowInclusive) && PackedSpanHelpers.CanUsePackedIndexOf(highInclusive) && highInclusive >= lowInclusive)
34743482
{
34753483
ref char charSearchSpace = ref Unsafe.As<T, char>(ref searchSpace);
3476-
char charLowInclusive = *(char*)&lowInclusive;
3477-
char charRange = (char)(*(char*)&highInclusive - charLowInclusive);
3484+
char charLowInclusive = Unsafe.BitCast<T, char>(lowInclusive);
3485+
char charRange = (char)(Unsafe.BitCast<T, char>(highInclusive) - charLowInclusive);
34783486

34793487
return typeof(TNegator) == typeof(DontNegate<ushort>)
34803488
? PackedSpanHelpers.IndexOfAnyInRange(ref charSearchSpace, charLowInclusive, charRange, length)

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13300,7 +13300,7 @@ public static partial class Unsafe
1330013300
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")]
1330113301
public static T? As<T>(object? o) where T : class? { throw null; }
1330213302
public static ref TTo As<TFrom, TTo>(ref TFrom source) { throw null; }
13303-
public static TTo BitCast<TFrom, TTo>(TFrom source) where TFrom : struct where TTo : struct { throw null; }
13303+
public static TTo BitCast<TFrom, TTo>(TFrom source) { throw null; }
1330413304
public static System.IntPtr ByteOffset<T>([System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T target) { throw null; }
1330513305
[System.CLSCompliantAttribute(false)]
1330613306
public static void CopyBlock(ref byte destination, ref readonly byte source, uint byteCount) { }

src/libraries/System.Runtime/tests/System.Runtime.CompilerServices.Unsafe.Tests/UnsafeTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,18 @@ public static unsafe void BitCast()
11661166
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int, long>(5));
11671167
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, int>(5));
11681168

1169+
// Conversion to/from a class should fail
1170+
1171+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<string, long>(string.Empty));
1172+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, string>(42));
1173+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<string, string>(string.Empty));
1174+
1175+
// Conversion to/from nullable value types should fail
1176+
1177+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int?, long>(42));
1178+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<long, int?>(42));
1179+
Assert.Throws<NotSupportedException>(() => Unsafe.BitCast<int?, int?>(42));
1180+
11691181
// Conversion between floating-point and same sized integral should succeed
11701182

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

0 commit comments

Comments
 (0)