|
8 | 8 |
|
9 | 9 | using Internal.Runtime.CompilerServices; |
10 | 10 |
|
| 11 | +// Some routines inspired by the Stanford Bit Twiddling Hacks by Sean Eron Anderson: |
| 12 | +// http://graphics.stanford.edu/~seander/bithacks.html |
| 13 | + |
11 | 14 | namespace System |
12 | 15 | { |
13 | 16 | internal static class BitOps |
14 | 17 | { |
| 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> |
15 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
16 | | - public static int TrailingZeroCount(int matches) |
| 44 | + public static int TrailingZeroCount(uint value) |
17 | 45 | { |
18 | 46 | if (Bmi1.IsSupported) |
19 | 47 | { |
20 | | - return (int)Bmi1.TrailingZeroCount((uint)matches); |
| 48 | + // Note that TZCNT contract specifies 0->32 |
| 49 | + return (int)Bmi1.TrailingZeroCount(value); |
21 | 50 | } |
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 | | - } |
31 | 51 |
|
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 | + } |
37 | 62 | } |
38 | 63 | } |
0 commit comments