Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ set_property(TARGET Core APPEND PROPERTY DICT_HEADERS
TClassEdit.h
TError.h
ThreadLocalStorage.h
ROOT/RAlignmentUtils.hxx
ROOT/BitUtils.hxx
ROOT/RError.hxx
ROOT/RLogger.hxx
ROOT/RNotFn.hxx
Expand Down
154 changes: 154 additions & 0 deletions core/foundation/inc/ROOT/BitUtils.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// @(#)root/foundation:
// Author: Philippe Canal, April 2026

/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#ifndef ROOT_BitUtils
#define ROOT_BitUtils

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <type_traits>

#ifdef _MSC_VER
#include <intrin.h> // for _BitScan*
#endif

namespace ROOT {
namespace Internal {

/// Return true if \p align is a valid C++ alignment value: strictly positive
/// and a power of two. This is the set of values accepted by
/// `::operator new[](n, std::align_val_t(align))`.
inline constexpr bool IsValidAlignment(std::size_t align) noexcept
{
return align > 0 && (align & (align - 1)) == 0;
}

/// Round \p value up to the next multiple of \p align.
/// \p align must be a power of two (asserted at runtime in debug builds).
template <typename T>
inline constexpr T AlignUp(T value, T align) noexcept
{
assert(IsValidAlignment(static_cast<std::size_t>(align))); // must be a power of two
return (value + align - 1) & ~(align - 1);
}

/// Given an integer `x`, returns the number of leading 0-bits starting at the most significant bit position.
/// If `x` is 0, it returns the size of `x` in bits.
///
/// Example:
///
/// if x is a std::uint32_t with value 42 (0b0...0101010), then LeadingZeroes(x) == 26
template <typename T>
inline std::size_t LeadingZeroes(T x)
{
constexpr std::size_t maxBits = sizeof(T) * 8;
static_assert(std::is_integral_v<T> && (maxBits == 32 || maxBits == 64));

if (x == 0)
return maxBits;

#ifdef _MSC_VER
unsigned long idx = 0;
[[maybe_unused]] unsigned char nonZero;
if constexpr (maxBits == 32) {
nonZero = _BitScanReverse(&idx, x);
} else {
#ifdef _WIN64
// 64-bit machine
nonZero = _BitScanReverse64(&idx, x);
#else
// 32-bit machine
std::uint32_t low = (x & 0xFFFF'FFFF);
std::uint32_t high = (x >> 32) & 0xFFFF'FFFF;
unsigned long lowIdx, highIdx;
unsigned char lowNonZero = _BitScanReverse(&lowIdx, low);
unsigned char highNonZero = _BitScanReverse(&highIdx, high);
assert(lowNonZero | highNonZero);
if (high == 0)
idx = 63 - lowIdx;
else
idx = 31 - highIdx;
return static_cast<std::size_t>(idx);

#endif // _WIN64
}

assert(nonZero);
// NOTE: _BitScanReverse return the 0-based index of the leftmost non-zero bit.
// To convert it to the number of zeroes we need to "flip" it from [0, maxBits) to [maxBits, 0)
// (e.g. _BitScanReverse == 0 <=> LeadingZeroes == maxBits)
return static_cast<std::size_t>(maxBits - 1 - idx);
#else
if constexpr (maxBits == 32) {
return static_cast<std::size_t>(__builtin_clz(x));
} else {
return static_cast<std::size_t>(__builtin_clzl(x));
}
#endif // _MSC_VER
}

/// Given an integer `x`, returns the number of trailing 0-bits starting at the least significant bit position.
/// If `x` is 0, it returns the size of `x` in bits.
///
/// Example:
///
/// if x is a std::uint32_t with value 42 (0b0...0101010), then TrailingZeroes(x) == 1
template <typename T>
inline std::size_t TrailingZeroes(T x)
{
constexpr std::size_t maxBits = sizeof(T) * 8;
static_assert(std::is_integral_v<T> && (maxBits == 32 || maxBits == 64));

if (x == 0)
return maxBits;

#ifdef _MSC_VER
unsigned long idx = 0;
[[maybe_unused]] unsigned char nonZero;
if constexpr (maxBits == 32) {
nonZero = _BitScanForward(&idx, x);
} else {
#ifdef _WIN64
// 64-bit machine
nonZero = _BitScanForward64(&idx, x);
#else
// 32-bit machine
std::uint32_t low = (x & 0xFFFF'FFFF);
std::uint32_t high = (x >> 32) & 0xFFFF'FFFF;
unsigned long lowIdx, highIdx;
unsigned char lowNonZero = _BitScanForward(&lowIdx, low);
unsigned char highNonZero = _BitScanForward(&highIdx, high);
nonZero = lowNonZero | highNonZero;
if (low == 0)
idx = highIdx + 32;
else
idx = lowIdx;

#endif // _WIN64
}
assert(nonZero);
// Differently from LeadingZeroes, in this case the bit index returned by _BitScanForward is
// already equivalent to the number of trailing zeroes, so we don't need any transformation.
return static_cast<std::size_t>(idx);
#else
if constexpr (maxBits == 32) {
return static_cast<std::size_t>(__builtin_ctz(x));
} else {
return static_cast<std::size_t>(__builtin_ctzl(x));
}
#endif // _MSC_VER
}

} // namespace Internal
} // namespace ROOT

#endif // ROOT_BitUtils
41 changes: 0 additions & 41 deletions core/foundation/inc/ROOT/RAlignmentUtils.hxx

This file was deleted.

1 change: 1 addition & 0 deletions core/foundation/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ ROOT_ADD_GTEST(testException testException.cxx LIBRARIES Core GTest::gmock)
ROOT_ADD_GTEST(testLogger testLogger.cxx LIBRARIES Core)
ROOT_ADD_GTEST(testRRangeCast testRRangeCast.cxx LIBRARIES Core)
ROOT_ADD_GTEST(testStringUtils testStringUtils.cxx LIBRARIES Core)
ROOT_ADD_GTEST(testBitUtils testBitUtils.cxx LIBRARIES Core)
ROOT_ADD_GTEST(FoundationUtilsTests FoundationUtilsTests.cxx LIBRARIES Core INCLUDE_DIRS ../res)
45 changes: 45 additions & 0 deletions core/foundation/test/testBitUtils.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "ROOT/BitUtils.hxx"

#include "gtest/gtest.h"

using namespace ROOT::Internal;

TEST(BitUtils, LeadingZeroes32)
{
EXPECT_EQ(LeadingZeroes(0), 32);
EXPECT_EQ(LeadingZeroes(~0), 0);
EXPECT_EQ(LeadingZeroes(0xF000'0000), 0);
EXPECT_EQ(LeadingZeroes(0x0000'F040), 16);
EXPECT_EQ(LeadingZeroes(0x0000'0003), 30);
EXPECT_EQ(LeadingZeroes(0x000F'F000), 12);
}

TEST(BitUtils, LeadingZeroes64)
{
EXPECT_EQ(LeadingZeroes(0ull), 64);
EXPECT_EQ(LeadingZeroes(~0ull), 0);
EXPECT_EQ(LeadingZeroes(0xF000'0000'0000'0000ull), 0);
EXPECT_EQ(LeadingZeroes(0x0000'F000'1000'1000ull), 16);
EXPECT_EQ(LeadingZeroes(0x0000'0000'0000'0003ull), 62);
EXPECT_EQ(LeadingZeroes(0x0000'000F'F000'0000ull), 28);
}

TEST(BitUtils, TrailingZeroes32)
{
EXPECT_EQ(TrailingZeroes(0), 32);
EXPECT_EQ(TrailingZeroes(~0), 0);
EXPECT_EQ(TrailingZeroes(0xF000'0000), 28);
EXPECT_EQ(TrailingZeroes(0x0000'F040), 6);
EXPECT_EQ(TrailingZeroes(0x0000'0003), 0);
EXPECT_EQ(TrailingZeroes(0x000F'F000), 12);
}

TEST(BitUtils, TrailingZeroes64)
{
EXPECT_EQ(TrailingZeroes(0ull), 64);
EXPECT_EQ(TrailingZeroes(~0ull), 0);
EXPECT_EQ(TrailingZeroes(0xF000'0000'0000'0000ull), 60);
EXPECT_EQ(TrailingZeroes(0x0000'F000'1000'1000ull), 12);
EXPECT_EQ(TrailingZeroes(0x0000'0000'0000'0003ull), 0);
EXPECT_EQ(TrailingZeroes(0x0000'000F'F000'0000ull), 28);
}
2 changes: 1 addition & 1 deletion core/meta/src/TClass.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ In order to access the name of a class within the ROOT type system, the method T
#include "TClonesArray.h"
#include "TRef.h"
#include "TRefArray.h"
#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"

using std::multimap, std::make_pair, std::string;

Expand Down
2 changes: 1 addition & 1 deletion core/meta/src/TStreamerElement.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "TBaseClass.h"
#include "TDataMember.h"
#include "TDataType.h"
#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"
#include "TEnum.h"
#include "TRealData.h"
#include "ThreadLocalStorage.h"
Expand Down
2 changes: 1 addition & 1 deletion core/metacling/src/TClingClassInfo.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ but the class metadata comes from the Clang C++ compiler, not CINT.
#include "llvm/Support/Casting.h"
#include "llvm/Support/raw_ostream.h"

#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"

#include <sstream>
#include <string>
Expand Down
2 changes: 1 addition & 1 deletion io/io/inc/TEmulatedCollectionProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#define ROOT_TEmulatedCollectionProxy

#include "TGenCollectionProxy.h"
#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"

#include <type_traits>
#include <vector>
Expand Down
2 changes: 1 addition & 1 deletion io/io/inc/TGenCollectionProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#include "TCollectionProxyInfo.h"

#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"

#include <atomic>
#include <string>
Expand Down
2 changes: 1 addition & 1 deletion io/io/src/TStreamerInfo.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ element type.
#include "TVirtualMutex.h"

#include "TStreamerInfoActions.h"
#include "ROOT/RAlignmentUtils.hxx"
#include "ROOT/BitUtils.hxx"

#include <memory>
#include <algorithm>
Expand Down
15 changes: 14 additions & 1 deletion tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,18 @@ private:
RNTupleDescriptor CloneSchema() const;

public:
static constexpr unsigned int kFeatureFlagTest = 137; // Bit reserved for forward-compatibility testing
/// All known feature flags.
/// Note that the flag values represent the bit _index_, not the already-bitshifted integer.
enum EFeatureFlags {
// Insert new feature flags here, with contiguous values. If at any point a "hole" appears in the valid feature
// flags values, the check in RNTupleSerialize must be updated.

// End of regular feature flags
kFeatureFlag_COUNT,

/// Reserved for forward-compatibility testing
kFeatureFlag_Test = 137
};

class RColumnDescriptorIterable;
class RFieldDescriptorIterable;
Expand Down Expand Up @@ -1736,6 +1747,8 @@ public:
void SetVersionForWriting();

void SetNTuple(const std::string_view name, const std::string_view description);
/// Sets the `flag`-th bit of the feature flag to 1.
/// Note that `flag` itself is not a bitmask, just the bit index of the flag to enable.
void SetFeature(unsigned int flag);

void SetOnDiskHeaderXxHash3(std::uint64_t xxhash3) { fDescriptor.fOnDiskHeaderXxHash3 = xxhash3; }
Expand Down
Loading
Loading