From ed805356c91079f6c4cbe26fa2a31b0f3af2e51f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 17:27:29 +0800 Subject: [PATCH 1/7] 32bit --- .../src/System/Numerics/BitOperations.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs index 826a129be7f854..fac37ef9264f31 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs @@ -71,18 +71,27 @@ public static class BitOperations [CLSCompliant(false)] public static bool IsPow2(ulong value) => (value & (value - 1)) == 0 && value != 0; - /// Round the given integral value up to a power of 2. + /// + /// Round the given integral value up to a power of 2. + /// /// The value. - internal static uint RoundUpToPowerOf2(uint value) + /// + /// The smallest power of 2 which is greater than or equals to . + /// If is 0 or the result overflows, return 0. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [CLSCompliant(false)] + public static uint RoundUpToPowerOf2(uint value) { - // TODO: https://github.com/dotnet/runtime/issues/43135 - // When this is exposed publicly, decide on the behavior for the boundary cases... - // the accelerated and fallback paths differ. - Debug.Assert(value > 0 && value <= (uint.MaxValue / 2) + 1); - if (Lzcnt.IsSupported || ArmBase.IsSupported || X86Base.IsSupported) { - return 1u << (32 - LeadingZeroCount(value - 1)); + // LeadingZeroCount is intrinsic +#if TARGET_64BIT + return (uint)(0x1_0000_0000ul >> LeadingZeroCount(value - 1)); +#else + int shift = 32 - LeadingZeroCount(value - 1); + return (1u ^ (uint)(shift >> 5)) << shift; +#endif } // Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 From 8983b8c90fb712e49a5d9e127bf42a067e334a28 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 17:39:28 +0800 Subject: [PATCH 2/7] 64bit --- .../src/System/Numerics/BitOperations.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs index fac37ef9264f31..2d64c6d3283a26 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs @@ -85,7 +85,6 @@ public static uint RoundUpToPowerOf2(uint value) { if (Lzcnt.IsSupported || ArmBase.IsSupported || X86Base.IsSupported) { - // LeadingZeroCount is intrinsic #if TARGET_64BIT return (uint)(0x1_0000_0000ul >> LeadingZeroCount(value - 1)); #else @@ -104,6 +103,36 @@ public static uint RoundUpToPowerOf2(uint value) return value + 1; } + + /// + /// Round the given integral value up to a power of 2. + /// + /// The value. + /// + /// The smallest power of 2 which is greater than or equals to . + /// If is 0 or the result overflows, return 0. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [CLSCompliant(false)] + public static ulong RoundUpToPowerOf2(ulong value) + { + if (Lzcnt.X64.IsSupported || ArmBase.Arm64.IsSupported) + { + int shift = 64 - LeadingZeroCount(value - 1); + return (1ul ^ (ulong)(shift >> 6)) << shift; + } + + // Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --value; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + return value + 1; + } + /// /// Count the number of leading zero bits in a mask. /// Similar in behavior to the x86 instruction LZCNT. From 37e32b2f0b5f10fc4fdffd77d4c6dcb236f07f00 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 17:44:06 +0800 Subject: [PATCH 3/7] Expose in public api --- src/libraries/System.Runtime/ref/System.Runtime.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 5c77a9476c8bb9..da83529a5da361 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -8196,6 +8196,10 @@ public static partial class BitOperations public static uint RotateRight(uint value, int offset) { throw null; } [System.CLSCompliantAttribute(false)] public static ulong RotateRight(ulong value, int offset) { throw null; } + [System.CLSCompliantAttribute(false)] + public static uint RoundUpToPowerOf2(uint value) { throw null; } + [System.CLSCompliantAttribute(false)] + public static ulong RoundUpToPowerOf2(ulong value) { throw null; } public static int TrailingZeroCount(int value) { throw null; } public static int TrailingZeroCount(long value) { throw null; } [System.CLSCompliantAttribute(false)] From 459fe278598b8d015309a427842d25f9e9aed7c8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 17:52:04 +0800 Subject: [PATCH 4/7] Unit test --- .../System/Numerics/BitOperationsTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs index f59d90b7c51450..adc9f6ca6d7e7d 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs @@ -388,5 +388,32 @@ public static void BitOps_RotateRight_ulong() Assert.Equal(value, BitOperations.RotateRight(value, int.MinValue)); // % 64 = 0 Assert.Equal(BitOperations.RotateLeft(value, 63), BitOperations.RotateRight(value, int.MaxValue)); // % 64 = 63 } + + [Theory] + [InlineData(0u, 0u)] + [InlineData(1u, 1u)] + [InlineData(2u, 2u)] + [InlineData(0x7FFF_FFFFu, 0x8000_0000u)] + [InlineData(0x8000_0000u, 0x8000_0000u)] + [InlineData(0xFFFF_FFFFu, 0)] + public static void BitOps_RoundUpToPow2_uint(uint value, uint expected) + { + Assert.Equal(expected, BitOperations.RoundUpToPowerOf2(value)); + } + + [Theory] + [InlineData(0ul, 0ul)] + [InlineData(1ul, 1ul)] + [InlineData(2ul, 2ul)] + [InlineData(0x7FFF_FFFFul, 0x8000_0000ul)] + [InlineData(0x8000_0000ul, 0x8000_0000ul)] + [InlineData(0xFFFF_FFFFul, 0x1_0000_0000ul)] + [InlineData(0x7FFF_FFFF_FFFF_FFFFul, 0x8000_0000_0000_0000ul)] + [InlineData(0x8000_0000_0000_0000ul, 0x8000_0000_0000_0000ul)] + [InlineData(0xFFFF_FFFF_FFFF_FFFFul, 0)] + public static void BitOps_RoundUpToPow2_ulong(ulong value, ulong expected) + { + Assert.Equal(expected, BitOperations.RoundUpToPowerOf2(value)); + } } } From 53199cf7607318d46e427a6a1cff9338703cadec Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 23:33:34 +0800 Subject: [PATCH 5/7] Add more tests --- .../System/Numerics/BitOperationsTests.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs index adc9f6ca6d7e7d..7836538dbe7480 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Numerics/BitOperationsTests.cs @@ -393,9 +393,17 @@ public static void BitOps_RotateRight_ulong() [InlineData(0u, 0u)] [InlineData(1u, 1u)] [InlineData(2u, 2u)] + [InlineData(0x0096u, 0x0100u)] + [InlineData(0x05CDu, 0x0800u)] + [InlineData(0x0932u, 0x1000u)] + [InlineData(0x0004_C911u, 0x0008_0000u)] + [InlineData(0x00E0_A2E2u, 0x0100_0000u)] + [InlineData(0x0988_0713u, 0x1000_0000u)] + [InlineData(0x30A4_9649u, 0x4000_0000u)] [InlineData(0x7FFF_FFFFu, 0x8000_0000u)] [InlineData(0x8000_0000u, 0x8000_0000u)] - [InlineData(0xFFFF_FFFFu, 0)] + [InlineData(0x8000_0001u, 0ul)] + [InlineData(0xFFFF_FFFFu, 0ul)] public static void BitOps_RoundUpToPow2_uint(uint value, uint expected) { Assert.Equal(expected, BitOperations.RoundUpToPowerOf2(value)); @@ -405,12 +413,25 @@ public static void BitOps_RoundUpToPow2_uint(uint value, uint expected) [InlineData(0ul, 0ul)] [InlineData(1ul, 1ul)] [InlineData(2ul, 2ul)] + [InlineData(0x0096ul, 0x0100ul)] + [InlineData(0x05cdul, 0x0800ul)] + [InlineData(0x0932ul, 0x1000ul)] + [InlineData(0x0004_c911ul, 0x0008_0000ul)] + [InlineData(0x00e0_a2b2ul, 0x0100_0000ul)] + [InlineData(0x0988_0713ul, 0x1000_0000ul)] + [InlineData(0x30a4_9649ul, 0x4000_0000ul)] [InlineData(0x7FFF_FFFFul, 0x8000_0000ul)] [InlineData(0x8000_0000ul, 0x8000_0000ul)] + [InlineData(0x8000_0001ul, 0x1_0000_0000ul)] [InlineData(0xFFFF_FFFFul, 0x1_0000_0000ul)] + [InlineData(0x0000_0003_343B_0D81ul, 0x0000_0004_0000_0000ul)] + [InlineData(0x0000_0D87_5EE2_8F19ul, 0x0000_1000_0000_0000ul)] + [InlineData(0x0006_2A08_4A7A_3A2Dul, 0x0008_0000_0000_0000ul)] + [InlineData(0x0101_BF76_4398_F791ul, 0x0200_0000_0000_0000ul)] [InlineData(0x7FFF_FFFF_FFFF_FFFFul, 0x8000_0000_0000_0000ul)] [InlineData(0x8000_0000_0000_0000ul, 0x8000_0000_0000_0000ul)] - [InlineData(0xFFFF_FFFF_FFFF_FFFFul, 0)] + [InlineData(0x8000_0000_0000_0001ul, 0ul)] + [InlineData(0xFFFF_FFFF_FFFF_FFFFul, 0ul)] public static void BitOps_RoundUpToPow2_ulong(ulong value, ulong expected) { Assert.Equal(expected, BitOperations.RoundUpToPowerOf2(value)); From 5e9f22e802d1ffa7ac2b5ef7a9e6036f1ad09c00 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 10 Jun 2021 23:51:45 +0800 Subject: [PATCH 6/7] Use in test --- .../Collections/Generic/ArrayBuilderTests.cs | 25 ++----------------- .../tests/ArrayPool/UnitTests.cs | 15 ++--------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs index 5a6dbcd32d1ff1..14a80e17d6af8a 100644 --- a/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs +++ b/src/libraries/Common/tests/Tests/System/Collections/Generic/ArrayBuilderTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; +using System.Numerics; using Xunit; namespace System.Collections.Generic.Tests @@ -205,29 +206,7 @@ private static int CalculateExpectedCapacity(int count) // Then allocate arrays of size 4, 8, 16, etc. count = Math.Max(count, 4); - return NextPowerOfTwo(count); - } - - private static int NextPowerOfTwo(int value) - { - // Taken from https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - - Debug.Assert(value >= 0); - - // If the number is already a power of 2, we want to round to itself. - value--; - - // Propogate 1-bits right: if the highest bit set is @ position n, - // then all of the bits to the right of position n will become set. - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - - // This yields a number of the form 2^N - 1. - // Add 1 to get a power of 2 with the bit set @ position n + 1. - return value + 1; + return (int)BitOperations.RoundUpToPowerOf2((uint)count); } } diff --git a/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs b/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs index a917fd3af41c7a..7456e1b46fe68e 100644 --- a/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs +++ b/src/libraries/System.Buffers/tests/ArrayPool/UnitTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.Tracing; using System.Linq; +using System.Numerics; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -278,23 +279,11 @@ public static void CanRentManySizedBuffers(ArrayPool pool) for (int i = 1; i < 10000; i++) { byte[] buffer = pool.Rent(i); - Assert.Equal(i <= 16 ? 16 : RoundUpToPowerOf2(i), buffer.Length); + Assert.Equal(i <= 16 ? 16 : (int)BitOperations.RoundUpToPowerOf2((uint)i), buffer.Length); pool.Return(buffer); } } - private static int RoundUpToPowerOf2(int i) - { - // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - --i; - i |= i >> 1; - i |= i >> 2; - i |= i >> 4; - i |= i >> 8; - i |= i >> 16; - return i + 1; - } - [Theory] [InlineData(1, 16)] [InlineData(15, 16)] From 24af03cedb35c90f5771b2b568d71c9a3c0e92f5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 11 Jun 2021 14:50:27 +0800 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../src/System/Numerics/BitOperations.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs index 2d64c6d3283a26..49fbb26ff4ca48 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs @@ -76,8 +76,8 @@ public static class BitOperations /// /// The value. /// - /// The smallest power of 2 which is greater than or equals to . - /// If is 0 or the result overflows, return 0. + /// The smallest power of 2 which is greater than or equal to . + /// If is 0 or the result overflows, returns 0. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [CLSCompliant(false)] @@ -109,8 +109,8 @@ public static uint RoundUpToPowerOf2(uint value) /// /// The value. /// - /// The smallest power of 2 which is greater than or equals to . - /// If is 0 or the result overflows, return 0. + /// The smallest power of 2 which is greater than or equal to . + /// If is 0 or the result overflows, returns 0. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [CLSCompliant(false)]