Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 8e79024

Browse files
grant-djkotas
authored andcommitted
BitOps.TrailingZeroCount has inconsistent software fallback (#22333)
Fix #22326
1 parent 4e5df11 commit 8e79024

File tree

1 file changed

+41
-16
lines changed
  • src/System.Private.CoreLib/shared/System

1 file changed

+41
-16
lines changed

src/System.Private.CoreLib/shared/System/BitOps.cs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,56 @@
88

99
using Internal.Runtime.CompilerServices;
1010

11+
// Some routines inspired by the Stanford Bit Twiddling Hacks by Sean Eron Anderson:
12+
// http://graphics.stanford.edu/~seander/bithacks.html
13+
1114
namespace System
1215
{
1316
internal static class BitOps
1417
{
18+
// C# no-alloc optimization that directly wraps the data section of the dll (similar to string constants)
19+
// https://github.com/dotnet/roslyn/pull/24621
20+
21+
private static ReadOnlySpan<byte> s_TrailingZeroCountDeBruijn => new byte[32]
22+
{
23+
00, 01, 28, 02, 29, 14, 24, 03,
24+
30, 22, 20, 15, 25, 17, 04, 08,
25+
31, 27, 13, 23, 21, 19, 16, 07,
26+
26, 12, 18, 06, 11, 05, 10, 09
27+
};
28+
29+
/// <summary>
30+
/// Count the number of trailing zero bits in an integer value.
31+
/// Similar in behavior to the x86 instruction TZCNT.
32+
/// </summary>
33+
/// <param name="value">The value.</param>
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
public static int TrailingZeroCount(int value)
36+
=> TrailingZeroCount((uint)value);
37+
38+
/// <summary>
39+
/// Count the number of trailing zero bits in an integer value.
40+
/// Similar in behavior to the x86 instruction TZCNT.
41+
/// </summary>
42+
/// <param name="value">The value.</param>
1543
[MethodImpl(MethodImplOptions.AggressiveInlining)]
16-
public static int TrailingZeroCount(int matches)
44+
public static int TrailingZeroCount(uint value)
1745
{
1846
if (Bmi1.IsSupported)
1947
{
20-
return (int)Bmi1.TrailingZeroCount((uint)matches);
48+
// Note that TZCNT contract specifies 0->32
49+
return (int)Bmi1.TrailingZeroCount(value);
2150
}
22-
else // Software fallback
23-
{
24-
// https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
25-
// uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
26-
return Unsafe.AddByteOffset(
27-
ref MemoryMarshal.GetReference(TrailingCountMultiplyDeBruijn),
28-
((uint)((matches & -matches) * 0x077CB531U)) >> 27);
29-
}
30-
}
3151

32-
private static ReadOnlySpan<byte> TrailingCountMultiplyDeBruijn => new byte[32]
33-
{
34-
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
35-
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
36-
};
52+
// Main code has behavior 0->0, so special-case in order to match intrinsic path 0->32
53+
if (value == 0u)
54+
return 32;
55+
56+
// uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
57+
return Unsafe.AddByteOffset(
58+
ref MemoryMarshal.GetReference(s_TrailingZeroCountDeBruijn),
59+
// Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_0111_1100_1011_0101_0011_0001u
60+
((uint)((value & -value) * 0x077CB531u)) >> 27);
61+
}
3762
}
3863
}

0 commit comments

Comments
 (0)