// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; #if NETCOREAPP3_1_OR_GREATER using System.Runtime.Intrinsics.X86; #endif namespace CommunityToolkit.HighPerformance.Helpers; /// <summary> /// Helpers to perform bit operations on numeric types. /// </summary> public static class BitHelper { /// <summary> /// Checks whether or not a given bit is set. /// </summary> /// <param name="value">The input <see cref="uint"/> value.</param> /// <param name="n">The position of the bit to check (in [0, 31] range).</param> /// <returns>Whether or not the n-th bit is set.</returns> /// <remarks> /// This method doesn't validate <paramref name="n"/> against the valid range. /// If the parameter is not valid, the result will just be inconsistent. /// Additionally, no conditional branches are used to retrieve the flag. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasFlag(uint value, int n) { // Read the n-th bit, downcast to byte byte flag = (byte)((value >> n) & 1); // Reinterpret the byte to avoid the test, setnz and // movzx instructions (asm x64). This is because the JIT // compiler is able to optimize this reinterpret-cast as // a single "and eax, 0x1" instruction, whereas if we had // compared the previous computed flag against 0, the assembly // would have had to perform the test, set the non-zero // flag and then extend the (byte) result to eax. return *(bool*)&flag; } /// <summary> /// Checks whether or not a given bit is set in a given bitwise lookup table. /// This method provides a branchless, register-based (with no memory accesses) way to /// check whether a given value is valid, according to a precomputed lookup table. /// It is similar in behavior to <see cref="HasFlag(uint,int)"/>, with the main difference /// being that this method will also validate the input <paramref name="x"/> parameter, and /// will always return <see langword="false"/> if it falls outside of the expected interval. /// Additionally, this method accepts a <paramref name="min"/> parameter, which is used to /// decrement the input parameter <paramref name="x"/> to ensure that the range of accepted /// values fits within the available 32 bits of the lookup table in use. /// For more info on this optimization technique, see <see href="https://egorbo.com/llvm-range-checks.html"/>. /// Here is how the code from the link above would be implemented using this method: /// <code> /// bool IsReservedCharacter(char c) /// { /// return BitHelper.HasLookupFlag(314575237u, c, 36); /// } /// </code> /// The resulted assembly is virtually identical, with the added optimization that the one /// produced by <see cref="HasLookupFlag(uint,int,int)"/> has no conditional branches at all. /// </summary> /// <param name="table">The input lookup table to use.</param> /// <param name="x">The input value to check.</param> /// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param> /// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns> /// <remarks> /// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/> /// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasLookupFlag(uint table, int x, int min = 0) { // First, the input value is scaled down by the given minimum. // This step will be skipped entirely if min is just the default of 0. // The valid range is given by 32, which is the number of bits in the // lookup table. The input value is first cast to uint so that if it was // negative, the check will fail as well. Then, the result of this // operation is used to compute a bitwise flag of either 0xFFFFFFFF if the // input is accepted, or all 0 otherwise. The target bit is then extracted, // and this value is combined with the previous mask. This is done so that // if the shift was performed with a value that was too high, which has an // undefined behavior and could produce a non-0 value, the mask will reset // the final value anyway. This result is then unchecked-cast to a byte (as // it is guaranteed to always be either 1 or 0), and then reinterpreted // as a bool just like in the HasFlag method above, and then returned. int i = x - min; bool isInRange = (uint)i < 32u; byte byteFlag = *(byte*)&isInRange; int negativeFlag = byteFlag - 1; int mask = ~negativeFlag; int shift = unchecked((int)((table >> i) & 1)); int and = shift & mask; byte result = unchecked((byte)and); bool valid = *(bool*)&result; return valid; } /// <summary> /// Checks whether the given value has any bytes that are set to 0. /// That is, given a <see cref="uint"/> value, which has a total of 4 bytes, /// it checks whether any of those have all the bits set to 0. /// </summary> /// <param name="value">The input value to check.</param> /// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns> /// <remarks> /// This method contains no branches. /// For more background on this subject, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasZeroByte(uint value) { return ((value - 0x0101_0101u) & ~value & 0x8080_8080u) != 0; } /// <summary> /// Checks whether the given value has any bytes that are set to 0. /// This method mirrors <see cref="HasZeroByte(uint)"/>, but with <see cref="ulong"/> values. /// </summary> /// <param name="value">The input value to check.</param> /// <returns>Whether <paramref name="value"/> has any bytes set to 0.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasZeroByte(ulong value) { return ((value - 0x0101_0101_0101_0101ul) & ~value & 0x8080_8080_8080_8080ul) != 0; } /// <summary> /// Checks whether a byte in the input <see cref="uint"/> value matches a target value. /// </summary> /// <param name="value">The input value to check.</param> /// <param name="target">The target byte to look for.</param> /// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns> /// <remarks> /// This method contains no branches. /// For more info, see <see href="https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord"/>. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasByteEqualTo(uint value, byte target) { return HasZeroByte(value ^ (0x0101_0101u * target)); } /// <summary> /// Checks whether a byte in the input <see cref="uint"/> value matches a target value. /// This method mirrors <see cref="HasByteEqualTo(uint,byte)"/>, but with <see cref="ulong"/> values. /// </summary> /// <param name="value">The input value to check.</param> /// <param name="target">The target byte to look for.</param> /// <returns>Whether <paramref name="value"/> has any bytes set to <paramref name="target"/>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasByteEqualTo(ulong value, byte target) { return HasZeroByte(value ^ (0x0101_0101_0101_0101u * target)); } /// <summary> /// Sets a bit to a specified value. /// </summary> /// <param name="value">The target <see cref="uint"/> value.</param> /// <param name="n">The position of the bit to set or clear (in [0, 31] range).</param> /// <param name="flag">The value to assign to the target bit.</param> /// <remarks> /// Just like <see cref="HasFlag(uint,int)"/>, this method doesn't validate <paramref name="n"/> /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetFlag(ref uint value, int n, bool flag) { value = SetFlag(value, n, flag); } /// <summary> /// Sets a bit to a specified value. /// </summary> /// <param name="value">The input <see cref="uint"/> value.</param> /// <param name="n">The position of the bit to set or clear (in [0, 31] range).</param> /// <param name="flag">The value to assign to the target bit.</param> /// <returns>An <see cref="uint"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns> /// <remarks> /// Just like <see cref="HasFlag(uint,int)"/>, this method doesn't validate <paramref name="n"/> /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe uint SetFlag(uint value, int n, bool flag) { // Shift a bit left to the n-th position, negate the // resulting value and perform an AND with the input value. // This effectively clears the n-th bit of our input. uint bit = 1u << n; uint not = ~bit; uint and = value & not; // Reinterpret the flag as 1 or 0, and cast to uint, // then we left shift the uint flag to the right position // and perform an OR with the resulting value of the previous // operation. This will always guaranteed to work, thanks to the // initial code clearing that bit before setting it again. bool copy = flag; uint flag32 = *(byte*)© uint shift = flag32 << n; uint or = and | shift; return or; } /// <summary> /// Extracts a bit field range from a given value. /// </summary> /// <param name="value">The input <see cref="uint"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 31] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <returns>The value of the extracted range within <paramref name="value"/>.</returns> /// <remarks> /// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>. /// If either parameter is not valid, the result will just be inconsistent. The method /// should not be used to set all the bits at once, and it is not guaranteed to work in /// that case, which would just be equivalent to assigning the <see cref="uint"/> value. /// Additionally, no conditional branches are used to retrieve the range. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ExtractRange(uint value, byte start, byte length) { #if NETCOREAPP3_1_OR_GREATER if (Bmi1.IsSupported) { return Bmi1.BitFieldExtract(value, start, length); } #endif return (value >> start) & ((1u << length) - 1u); } /// <summary> /// Sets a bit field range within a target value. /// </summary> /// <param name="value">The target <see cref="uint"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 31] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <param name="flags">The input flags to insert in the target range.</param> /// <remarks> /// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetRange(ref uint value, byte start, byte length, uint flags) { value = SetRange(value, start, length, flags); } /// <summary> /// Sets a bit field range within a target value. /// </summary> /// <param name="value">The initial <see cref="uint"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 31] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <param name="flags">The input flags to insert in the target range.</param> /// <returns>The updated bit field value after setting the specified range.</returns> /// <remarks> /// Just like <see cref="ExtractRange(uint,byte,byte)"/>, this method doesn't validate the parameters /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint SetRange(uint value, byte start, byte length, uint flags) { uint highBits = (1u << length) - 1u; uint loadMask = highBits << start; uint storeMask = (flags & highBits) << start; #if NETCOREAPP3_1_OR_GREATER if (Bmi1.IsSupported) { return Bmi1.AndNot(loadMask, value) | storeMask; } #endif return (~loadMask & value) | storeMask; } /// <summary> /// Checks whether or not a given bit is set. /// </summary> /// <param name="value">The input <see cref="ulong"/> value.</param> /// <param name="n">The position of the bit to check (in [0, 63] range).</param> /// <returns>Whether or not the n-th bit is set.</returns> /// <remarks> /// This method doesn't validate <paramref name="n"/> against the valid range. /// If the parameter is not valid, the result will just be inconsistent. /// Additionally, no conditional branches are used to retrieve the flag. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasFlag(ulong value, int n) { // Same logic as the uint version, see that for more info byte flag = (byte)((value >> n) & 1); return *(bool*)&flag; } /// <summary> /// Checks whether or not a given bit is set in a given bitwise lookup table. /// For more info, check the XML docs of the <see cref="HasLookupFlag(uint,int,int)"/> overload. /// </summary> /// <param name="table">The input lookup table to use.</param> /// <param name="x">The input value to check.</param> /// <param name="min">The minimum accepted value for <paramref name="x"/> (defaults to 0).</param> /// <returns>Whether or not the corresponding flag for <paramref name="x"/> is set in <paramref name="table"/>.</returns> /// <remarks> /// For best results, as shown in the sample code, both <paramref name="table"/> and <paramref name="min"/> /// should be compile-time constants, so that the JIT compiler will be able to produce more efficient code. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool HasLookupFlag(ulong table, int x, int min = 0) { int i = x - min; bool isInRange = (uint)i < 64u; byte byteFlag = *(byte*)&isInRange; int negativeFlag = byteFlag - 1; int mask = ~negativeFlag; int shift = unchecked((int)((table >> i) & 1)); int and = shift & mask; byte result = unchecked((byte)and); bool valid = *(bool*)&result; return valid; } /// <summary> /// Sets a bit to a specified value. /// </summary> /// <param name="value">The target <see cref="ulong"/> value.</param> /// <param name="n">The position of the bit to set or clear (in [0, 63] range).</param> /// <param name="flag">The value to assign to the target bit.</param> /// <remarks> /// Just like <see cref="HasFlag(ulong,int)"/>, this method doesn't validate <paramref name="n"/> /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetFlag(ref ulong value, int n, bool flag) { value = SetFlag(value, n, flag); } /// <summary> /// Sets a bit to a specified value. /// </summary> /// <param name="value">The input <see cref="ulong"/> value.</param> /// <param name="n">The position of the bit to set or clear (in [0, 63] range).</param> /// <param name="flag">The value to assign to the target bit.</param> /// <returns>An <see cref="ulong"/> value equal to <paramref name="value"/> except for the <paramref name="n"/>-th bit.</returns> /// <remarks> /// Just like <see cref="HasFlag(ulong,int)"/>, this method doesn't validate <paramref name="n"/> /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ulong SetFlag(ulong value, int n, bool flag) { ulong bit = 1ul << n; ulong not = ~bit; ulong and = value & not; bool copy = flag; ulong flag64 = *(byte*)© ulong shift = flag64 << n; ulong or = and | shift; return or; } /// <summary> /// Extracts a bit field range from a given value. /// </summary> /// <param name="value">The input <see cref="ulong"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 63] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <returns>The value of the extracted range within <paramref name="value"/>.</returns> /// <remarks> /// This method doesn't validate <paramref name="start"/> and <paramref name="length"/>. /// If either parameter is not valid, the result will just be inconsistent. The method /// should not be used to set all the bits at once, and it is not guaranteed to work in /// that case, which would just be equivalent to assigning the <see cref="ulong"/> value. /// Additionally, no conditional branches are used to retrieve the range. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ExtractRange(ulong value, byte start, byte length) { #if NETCOREAPP3_1_OR_GREATER if (Bmi1.X64.IsSupported) { return Bmi1.X64.BitFieldExtract(value, start, length); } #endif return (value >> start) & ((1ul << length) - 1ul); } /// <summary> /// Sets a bit field range within a target value. /// </summary> /// <param name="value">The target <see cref="ulong"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 63] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <param name="flags">The input flags to insert in the target range.</param> /// <remarks> /// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetRange(ref ulong value, byte start, byte length, ulong flags) { value = SetRange(value, start, length, flags); } /// <summary> /// Sets a bit field range within a target value. /// </summary> /// <param name="value">The initial <see cref="ulong"/> value.</param> /// <param name="start">The initial index of the range to extract (in [0, 63] range).</param> /// <param name="length">The length of the range to extract (depends on <paramref name="start"/>).</param> /// <param name="flags">The input flags to insert in the target range.</param> /// <returns>The updated bit field value after setting the specified range.</returns> /// <remarks> /// Just like <see cref="ExtractRange(ulong,byte,byte)"/>, this method doesn't validate the parameters /// and does not contain branching instructions, so it's well suited for use in tight loops as well. /// </remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong SetRange(ulong value, byte start, byte length, ulong flags) { ulong highBits = (1ul << length) - 1ul; ulong loadMask = highBits << start; ulong storeMask = (flags & highBits) << start; #if NETCOREAPP3_1_OR_GREATER if (Bmi1.X64.IsSupported) { return Bmi1.X64.AndNot(loadMask, value) | storeMask; } #endif return (~loadMask & value) | storeMask; } }