Skip to content

Commit 95a074d

Browse files
committed
[ntuple] Move LeadingZeroes/TrailingZeroes to BitUtils
Also make them usable for both 32-bit and 64-bit integers.
1 parent d2ce2e2 commit 95a074d

4 files changed

Lines changed: 147 additions & 32 deletions

File tree

core/foundation/inc/ROOT/BitUtils.hxx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@
1212
#ifndef ROOT_BitUtils
1313
#define ROOT_BitUtils
1414

15+
#include <algorithm>
1516
#include <cassert>
1617
#include <cstddef>
18+
#include <type_traits>
19+
20+
#ifdef _MSC_VER
21+
#include <intrin.h> // for _BitScan*
22+
#endif
1723

1824
namespace ROOT {
1925
namespace Internal {
@@ -35,6 +41,98 @@ inline constexpr T AlignUp(T value, T align) noexcept
3541
return (value + align - 1) & ~(align - 1);
3642
}
3743

44+
/// Given an integer `x`, returns the number of leading 0-bits starting at the most significant bit position.
45+
/// If `x` is 0, it returns the size of `x` in bits.
46+
///
47+
/// Example:
48+
///
49+
/// if x is a std::uint32_t with value 42 (0b0...0101010), then LeadingZeroes(x) == 26
50+
template <typename T>
51+
inline std::size_t LeadingZeroes(T x)
52+
{
53+
constexpr std::size_t maxBits = sizeof(T) * 8;
54+
static_assert(std::is_integral_v<T> && (maxBits == 32 || maxBits == 64));
55+
56+
if (x == 0)
57+
return maxBits;
58+
59+
#ifdef _MSC_VER
60+
unsigned long idx = 0;
61+
[[maybe_unused]] unsigned char nonZero;
62+
if constexpr (maxBits == 32) {
63+
nonZero = _BitScanReverse(&idx, x);
64+
} else {
65+
#ifdef R__B64
66+
// 64-bit machine
67+
nonZero = _BitScanReverse64(&idx, x);
68+
#else
69+
// 32-bit machine
70+
std::uint32_t low = (x & 0xFFFF'FFFF);
71+
std::uint32_t high = (x >> 32) & 0xFFFF'FFFF;
72+
unsigned long lowIdx, highIdx;
73+
unsigned char lowNonZero = _BitScanReverse(&lowIdx, low);
74+
unsigned char highNonZero = _BitScanReverse(&highIdx, high);
75+
nonZero = lowNonZero | highNonZero;
76+
idx = std::max(highIdx * (highIdx + 32), lowIdx);
77+
#endif // R__B64
78+
}
79+
assert(nonZero);
80+
return static_cast<std::size_t>(maxBits - 1 - idx);
81+
#else
82+
if constexpr (maxBits == 32) {
83+
return static_cast<std::size_t>(__builtin_clz(x));
84+
} else {
85+
return static_cast<std::size_t>(__builtin_clzl(x));
86+
}
87+
#endif // _MSC_VER
88+
}
89+
90+
/// Given an integer `x`, returns the number of trailing 0-bits starting at the least significant bit position.
91+
/// If `x` is 0, it returns the size of `x` in bits.
92+
///
93+
/// Example:
94+
///
95+
/// if x is a std::uint32_t with value 42 (0b0...0101010), then TrailingZeroes(x) == 1
96+
template <typename T>
97+
inline std::size_t TrailingZeroes(T x)
98+
{
99+
constexpr std::size_t maxBits = sizeof(T) * 8;
100+
static_assert(std::is_integral_v<T> && (maxBits == 32 || maxBits == 64));
101+
102+
if (x == 0)
103+
return maxBits;
104+
105+
#ifdef _MSC_VER
106+
unsigned long idx = 0;
107+
[[maybe_unused]] unsigned char nonZero;
108+
if constexpr (maxBits == 32) {
109+
nonZero = _BitScanForward(&idx, x);
110+
} else {
111+
#ifdef R__B64
112+
// 64-bit machine
113+
nonZero = _BitScanForward64(&idx, x);
114+
#else
115+
// 32-bit machine
116+
std::uint32_t low = (x & 0xFFFF'FFFF);
117+
std::uint32_t high = (x >> 32) & 0xFFFF'FFFF;
118+
unsigned long lowIdx, highIdx;
119+
unsigned char lowNonZero = _BitScanForward(&lowIdx, low);
120+
unsigned char highNonZero = _BitScanForward(&highIdx, high);
121+
nonZero = lowNonZero | highNonZero;
122+
idx = std::min(highIdx * (highIdx + 32), lowIdx);
123+
#endif // R__B64
124+
}
125+
assert(nonZero);
126+
return static_cast<std::size_t>(idx);
127+
#else
128+
if constexpr (maxBits == 32) {
129+
return static_cast<std::size_t>(__builtin_ctz(x));
130+
} else {
131+
return static_cast<std::size_t>(__builtin_ctzl(x));
132+
}
133+
#endif // _MSC_VER
134+
}
135+
38136
} // namespace Internal
39137
} // namespace ROOT
40138

core/foundation/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ ROOT_ADD_GTEST(testException testException.cxx LIBRARIES Core GTest::gmock)
1212
ROOT_ADD_GTEST(testLogger testLogger.cxx LIBRARIES Core)
1313
ROOT_ADD_GTEST(testRRangeCast testRRangeCast.cxx LIBRARIES Core)
1414
ROOT_ADD_GTEST(testStringUtils testStringUtils.cxx LIBRARIES Core)
15+
ROOT_ADD_GTEST(testBitUtils testBitUtils.cxx LIBRARIES Core)
1516
ROOT_ADD_GTEST(FoundationUtilsTests FoundationUtilsTests.cxx LIBRARIES Core INCLUDE_DIRS ../res)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "ROOT/BitUtils.hxx"
2+
3+
#include "gtest/gtest.h"
4+
5+
using namespace ROOT::Internal;
6+
7+
TEST(BitUtils, LeadingZeroes32)
8+
{
9+
EXPECT_EQ(LeadingZeroes(0), 32);
10+
EXPECT_EQ(LeadingZeroes(~0), 0);
11+
EXPECT_EQ(LeadingZeroes(0xF000'0000), 0);
12+
EXPECT_EQ(LeadingZeroes(0x0000'F040), 16);
13+
EXPECT_EQ(LeadingZeroes(0x0000'0003), 30);
14+
EXPECT_EQ(LeadingZeroes(0x000F'F000), 12);
15+
}
16+
17+
TEST(BitUtils, LeadingZeroes64)
18+
{
19+
EXPECT_EQ(LeadingZeroes(0ull), 64);
20+
EXPECT_EQ(LeadingZeroes(~0ull), 0);
21+
EXPECT_EQ(LeadingZeroes(0xF000'0000'0000'0000ull), 0);
22+
EXPECT_EQ(LeadingZeroes(0x0000'F000'1000'1000ull), 16);
23+
EXPECT_EQ(LeadingZeroes(0x0000'0000'0000'0003ull), 62);
24+
EXPECT_EQ(LeadingZeroes(0x0000'000F'F000'0000ull), 28);
25+
}
26+
27+
TEST(BitUtils, TrailingZeroes32)
28+
{
29+
EXPECT_EQ(TrailingZeroes(0), 32);
30+
EXPECT_EQ(TrailingZeroes(~0), 0);
31+
EXPECT_EQ(TrailingZeroes(0xF000'0000), 28);
32+
EXPECT_EQ(TrailingZeroes(0x0000'F040), 6);
33+
EXPECT_EQ(TrailingZeroes(0x0000'0003), 0);
34+
EXPECT_EQ(TrailingZeroes(0x000F'F000), 12);
35+
}
36+
37+
TEST(BitUtils, TrailingZeroes64)
38+
{
39+
EXPECT_EQ(TrailingZeroes(0ull), 64);
40+
EXPECT_EQ(TrailingZeroes(~0ull), 0);
41+
EXPECT_EQ(TrailingZeroes(0xF000'0000'0000'0000ull), 60);
42+
EXPECT_EQ(TrailingZeroes(0x0000'F000'1000'1000ull), 12);
43+
EXPECT_EQ(TrailingZeroes(0x0000'0000'0000'0003ull), 0);
44+
EXPECT_EQ(TrailingZeroes(0x0000'000F'F000'0000ull), 28);
45+
}

tree/ntuple/src/RColumnElement.hxx

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// definitions are implementation details and should not be exposed to a public interface.
88

99
#include <ROOT/RColumnElementBase.hxx>
10+
#include <ROOT/BitUtils.hxx>
1011
#include <ROOT/RNTupleTypes.hxx>
1112
#include <ROOT/RNTupleUtils.hxx>
1213
#include <ROOT/RConfig.hxx>
@@ -1000,36 +1001,6 @@ namespace Quantize {
10001001

10011002
using Quantized_t = std::uint32_t;
10021003

1003-
[[maybe_unused]] inline std::size_t LeadingZeroes(std::uint32_t x)
1004-
{
1005-
if (x == 0)
1006-
return 32;
1007-
1008-
#ifdef _MSC_VER
1009-
unsigned long idx = 0;
1010-
if (_BitScanReverse(&idx, x))
1011-
return static_cast<std::size_t>(31 - idx);
1012-
return 32;
1013-
#else
1014-
return static_cast<std::size_t>(__builtin_clz(x));
1015-
#endif
1016-
}
1017-
1018-
[[maybe_unused]] inline std::size_t TrailingZeroes(std::uint32_t x)
1019-
{
1020-
if (x == 0)
1021-
return 32;
1022-
1023-
#ifdef _MSC_VER
1024-
unsigned long idx = 0;
1025-
if (_BitScanForward(&idx, x))
1026-
return static_cast<std::size_t>(idx);
1027-
return 32;
1028-
#else
1029-
return static_cast<std::size_t>(__builtin_ctz(x));
1030-
#endif
1031-
}
1032-
10331004
/// Converts the array `src` of `count` floating point numbers into an array of their quantized representations.
10341005
/// Each element of `src` is assumed to be in the inclusive range [min, max].
10351006
/// The quantized representation will consist of unsigned integers of at most `nQuantBits` (with `nQuantBits <= 8 *
@@ -1065,7 +1036,7 @@ int QuantizeReals(Quantized_t *dst, const T *src, std::size_t count, double min,
10651036
ByteSwapIfNecessary(q);
10661037

10671038
// double-check we actually used at most `nQuantBits`
1068-
assert(outOfRange || LeadingZeroes(q) >= unusedBits);
1039+
assert(outOfRange || ROOT::Internal::LeadingZeroes(q) >= unusedBits);
10691040

10701041
// we want to leave zeroes in the LSB, not the MSB, because we'll then drop the LSB
10711042
// when bit packing.
@@ -1095,7 +1066,7 @@ int UnquantizeReals(T *dst, const Quantized_t *src, std::size_t count, double mi
10951066
for (std::size_t i = 0; i < count; ++i) {
10961067
Quantized_t elem = src[i];
10971068
// Undo the LSB-preserving shift performed by QuantizeReals
1098-
assert(TrailingZeroes(elem) >= unusedBits);
1069+
assert(ROOT::Internal::TrailingZeroes(elem) >= unusedBits);
10991070
elem >>= unusedBits;
11001071
ByteSwapIfNecessary(elem);
11011072

0 commit comments

Comments
 (0)