Skip to content

Commit 0d8ca56

Browse files
committed
[ntuple] Add RNTupleLocatorMulti class for kTypeMulti locator
1 parent a776301 commit 0d8ca56

5 files changed

Lines changed: 249 additions & 8 deletions

File tree

tree/ntuple/inc/ROOT/RNTupleTypes.hxx

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,67 @@ public:
201201
std::uint64_t GetLocation() const { return fLocation; }
202202
};
203203

204+
/// RNTupleLocator payload for the kTypeMulti locator (type 0x03). Used by storage
205+
/// backends that pack multiple pages into shared objects (e.g., S3 Mode A).
206+
///
207+
/// The class stores the object identifier and byte offset as two explicit 32-bit
208+
/// integers plus a separate 4-bit reserved field. When packed into a single 64-bit
209+
/// value via GetLocation() (parallel to RNTupleLocatorObject64::GetLocation()), the
210+
/// layout is:
211+
/// bits 63..60: 4 reserved bits (for future per-locator flags)
212+
/// bits 59..30: 30-bit object identifier (max value 2^30 - 1, i.e. ~1 Billion objects)
213+
/// bits 29..0: 30-bit byte offset within the object (max value 2^30 - 1, i.e. 1 GiB - 1)
214+
class RNTupleLocatorMulti {
215+
public:
216+
static constexpr std::uint32_t kMaxObjectId = (1U << 30) - 1;
217+
static constexpr std::uint32_t kMaxOffset = (1U << 30) - 1;
218+
static constexpr std::uint8_t kMaxReserved = 0xF;
219+
220+
private:
221+
static constexpr std::uint64_t kMaskReserved = 0xFULL << 60;
222+
static constexpr std::uint64_t kMaskObjectId = 0x3FFFFFFFULL << 30;
223+
static constexpr std::uint64_t kMaskOffset = 0x3FFFFFFFULL;
224+
225+
std::uint32_t fObjectId = 0;
226+
std::uint32_t fOffset = 0;
227+
std::uint8_t fReserved = 0;
228+
229+
public:
230+
RNTupleLocatorMulti() = default;
231+
/// Construct from logical object identifier and byte offset; throws if either value
232+
/// exceeds the 30-bit range.
233+
RNTupleLocatorMulti(std::uint32_t objectId, std::uint32_t offset);
234+
/// Construct from a raw 64-bit packed location value, extracting the fields per
235+
/// the layout documented above. Intended for the deserializer; user code should
236+
/// prefer the (objectId, offset) constructor.
237+
explicit RNTupleLocatorMulti(std::uint64_t location)
238+
: fObjectId(static_cast<std::uint32_t>((location & kMaskObjectId) >> 30)),
239+
fOffset(static_cast<std::uint32_t>(location & kMaskOffset)),
240+
fReserved(static_cast<std::uint8_t>((location & kMaskReserved) >> 60))
241+
{
242+
}
243+
244+
bool operator==(const RNTupleLocatorMulti &other) const
245+
{
246+
return fObjectId == other.fObjectId && fOffset == other.fOffset && fReserved == other.fReserved;
247+
}
248+
249+
/// Returns the raw 64-bit packed location derived from the three fields, suitable
250+
/// for storage in RNTupleLocator::fPosition or for direct serialization. Parallel
251+
/// to RNTupleLocatorObject64::GetLocation().
252+
std::uint64_t GetLocation() const
253+
{
254+
return (static_cast<std::uint64_t>(fReserved) << 60) | (static_cast<std::uint64_t>(fObjectId) << 30) | fOffset;
255+
}
256+
257+
std::uint32_t GetObjectId() const { return fObjectId; }
258+
std::uint32_t GetOffset() const { return fOffset; }
259+
std::uint8_t GetReserved() const { return fReserved; }
260+
261+
/// Sets the 4-bit reserved field; throws if the value exceeds the 4-bit range.
262+
void SetReserved(std::uint8_t reserved);
263+
};
264+
204265
// Workaround missing return type overloading
205266
class RNTupleLocator;
206267
namespace Internal {
@@ -216,6 +277,11 @@ template <>
216277
struct RNTupleLocatorHelper<RNTupleLocatorObject64> {
217278
static RNTupleLocatorObject64 Get(const RNTupleLocator &loc);
218279
};
280+
281+
template <>
282+
struct RNTupleLocatorHelper<RNTupleLocatorMulti> {
283+
static RNTupleLocatorMulti Get(const RNTupleLocator &loc);
284+
};
219285
} // namespace Internal
220286

221287
/// Generic information about the physical location of data. Values depend on the concrete storage type. E.g.,
@@ -226,6 +292,7 @@ struct RNTupleLocatorHelper<RNTupleLocatorObject64> {
226292
class RNTupleLocator {
227293
friend struct Internal::RNTupleLocatorHelper<std::uint64_t>;
228294
friend struct Internal::RNTupleLocatorHelper<RNTupleLocatorObject64>;
295+
friend struct Internal::RNTupleLocatorHelper<RNTupleLocatorMulti>;
229296

230297
public:
231298
/// Values for the _Type_ field in non-disk locators. Serializable types must have the MSb == 0; see
@@ -277,7 +344,8 @@ public:
277344
void SetType(ELocatorType type);
278345
void SetReserved(std::uint8_t reserved);
279346

280-
/// Note that for GetPosition() / SetPosition(), the locator type must correspond (kTypeFile, kTypeObject64).
347+
/// Note that for GetPosition() / SetPosition(), the locator type must correspond
348+
/// (kTypeFile, kTypeObject64, kTypeMulti).
281349

282350
template <typename T>
283351
T GetPosition() const
@@ -287,6 +355,7 @@ public:
287355

288356
void SetPosition(std::uint64_t position);
289357
void SetPosition(RNTupleLocatorObject64 position);
358+
void SetPosition(RNTupleLocatorMulti position);
290359
};
291360

292361
namespace Internal {

tree/ntuple/src/RNTupleSerialize.cxx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,44 @@ ROOT::RResult<void> DeserializeLocatorPayloadObject64(const unsigned char *buffe
511511
return ROOT::RResult<void>::Success();
512512
}
513513

514+
std::uint32_t SerializeLocatorPayloadMulti(const ROOT::RNTupleLocator &locator, unsigned char *buffer)
515+
{
516+
const auto &data = locator.GetPosition<ROOT::RNTupleLocatorMulti>();
517+
const uint32_t sizeofNBytesOnStorage = (locator.GetNBytesOnStorage() > std::numeric_limits<std::uint32_t>::max())
518+
? sizeof(std::uint64_t)
519+
: sizeof(std::uint32_t);
520+
if (buffer) {
521+
if (sizeofNBytesOnStorage == sizeof(std::uint32_t)) {
522+
RNTupleSerializer::SerializeUInt32(locator.GetNBytesOnStorage(), buffer);
523+
} else {
524+
RNTupleSerializer::SerializeUInt64(locator.GetNBytesOnStorage(), buffer);
525+
}
526+
RNTupleSerializer::SerializeUInt64(data.GetLocation(), buffer + sizeofNBytesOnStorage);
527+
}
528+
return sizeofNBytesOnStorage + sizeof(std::uint64_t);
529+
}
530+
531+
ROOT::RResult<void> DeserializeLocatorPayloadMulti(const unsigned char *buffer, std::uint32_t sizeofLocatorPayload,
532+
ROOT::RNTupleLocator &locator)
533+
{
534+
std::uint64_t packed;
535+
if (sizeofLocatorPayload == 12) {
536+
std::uint32_t nBytesOnStorage;
537+
RNTupleSerializer::DeserializeUInt32(buffer, nBytesOnStorage);
538+
locator.SetNBytesOnStorage(nBytesOnStorage);
539+
RNTupleSerializer::DeserializeUInt64(buffer + sizeof(std::uint32_t), packed);
540+
} else if (sizeofLocatorPayload == 16) {
541+
std::uint64_t nBytesOnStorage;
542+
RNTupleSerializer::DeserializeUInt64(buffer, nBytesOnStorage);
543+
locator.SetNBytesOnStorage(nBytesOnStorage);
544+
RNTupleSerializer::DeserializeUInt64(buffer + sizeof(std::uint64_t), packed);
545+
} else {
546+
return R__FAIL("invalid Multi locator payload size: " + std::to_string(sizeofLocatorPayload));
547+
}
548+
locator.SetPosition(ROOT::RNTupleLocatorMulti{packed});
549+
return ROOT::RResult<void>::Success();
550+
}
551+
514552
std::uint32_t SerializeAliasColumn(const ROOT::RColumnDescriptor &columnDesc,
515553
const ROOT::Internal::RNTupleSerializer::RContext &context, void *buffer)
516554
{
@@ -1091,7 +1129,7 @@ ROOT::Internal::RNTupleSerializer::SerializeLocator(const RNTupleLocator &locato
10911129
locatorType = 0x02;
10921130
break;
10931131
case RNTupleLocator::kTypeMulti:
1094-
size += SerializeLocatorPayloadObject64(locator, payloadp);
1132+
size += SerializeLocatorPayloadMulti(locator, payloadp);
10951133
locatorType = 0x03;
10961134
break;
10971135
default:
@@ -1144,7 +1182,7 @@ ROOT::RResult<std::uint32_t> ROOT::Internal::RNTupleSerializer::DeserializeLocat
11441182
break;
11451183
case 0x03:
11461184
locator.SetType(RNTupleLocator::kTypeMulti);
1147-
DeserializeLocatorPayloadObject64(bytes, payloadSize, locator);
1185+
DeserializeLocatorPayloadMulti(bytes, payloadSize, locator);
11481186
break;
11491187
default: locator.SetType(RNTupleLocator::kTypeUnknown);
11501188
}

tree/ntuple/src/RNTupleTypes.cxx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,18 @@ void ROOT::RNTupleLocator::SetPosition(std::uint64_t position)
8080

8181
void ROOT::RNTupleLocator::SetPosition(RNTupleLocatorObject64 position)
8282
{
83-
if (GetType() != kTypeObject64 && GetType() != kTypeMulti)
83+
if (GetType() != kTypeObject64)
8484
throw RException(R__FAIL("cannot set position as 64bit object for type " + std::to_string(GetType())));
8585
fPosition = position.GetLocation();
8686
}
8787

88+
void ROOT::RNTupleLocator::SetPosition(RNTupleLocatorMulti position)
89+
{
90+
if (GetType() != kTypeMulti)
91+
throw RException(R__FAIL("cannot set position as Multi locator for type " + std::to_string(GetType())));
92+
fPosition = position.GetLocation();
93+
}
94+
8895
std::uint64_t ROOT::Internal::RNTupleLocatorHelper<std::uint64_t>::Get(const RNTupleLocator &loc)
8996
{
9097
if (loc.GetType() != ROOT::RNTupleLocator::kTypeFile)
@@ -95,7 +102,34 @@ std::uint64_t ROOT::Internal::RNTupleLocatorHelper<std::uint64_t>::Get(const RNT
95102
ROOT::RNTupleLocatorObject64
96103
ROOT::Internal::RNTupleLocatorHelper<ROOT::RNTupleLocatorObject64>::Get(const RNTupleLocator &loc)
97104
{
98-
if (loc.GetType() != ROOT::RNTupleLocator::kTypeObject64 && loc.GetType() != ROOT::RNTupleLocator::kTypeMulti)
105+
if (loc.GetType() != ROOT::RNTupleLocator::kTypeObject64)
99106
throw RException(R__FAIL("cannot retrieve position as 64bit object for type " + std::to_string(loc.GetType())));
100107
return RNTupleLocatorObject64{loc.fPosition};
101108
}
109+
110+
ROOT::RNTupleLocatorMulti
111+
ROOT::Internal::RNTupleLocatorHelper<ROOT::RNTupleLocatorMulti>::Get(const RNTupleLocator &loc)
112+
{
113+
if (loc.GetType() != ROOT::RNTupleLocator::kTypeMulti)
114+
throw RException(
115+
R__FAIL("cannot retrieve position as Multi locator for type " + std::to_string(loc.GetType())));
116+
return RNTupleLocatorMulti{loc.fPosition};
117+
}
118+
119+
ROOT::RNTupleLocatorMulti::RNTupleLocatorMulti(std::uint32_t objectId, std::uint32_t offset)
120+
{
121+
if (objectId > kMaxObjectId)
122+
throw RException(R__FAIL("RNTupleLocatorMulti object id exceeds 30-bit range: " + std::to_string(objectId)));
123+
if (offset > kMaxOffset)
124+
throw RException(R__FAIL("RNTupleLocatorMulti offset exceeds 30-bit range: " + std::to_string(offset)));
125+
fObjectId = objectId;
126+
fOffset = offset;
127+
}
128+
129+
void ROOT::RNTupleLocatorMulti::SetReserved(std::uint8_t reserved)
130+
{
131+
if (reserved > kMaxReserved)
132+
throw RException(
133+
R__FAIL("RNTupleLocatorMulti reserved value exceeds 4-bit range: " + std::to_string(reserved)));
134+
fReserved = reserved;
135+
}

tree/ntuple/test/ntuple_serialize.cxx

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ TEST(RNTuple, SerializeLocator)
402402
// Multi locator round-trip with 32-bit nBytesOnStorage
403403
locator = RNTupleLocator{};
404404
locator.SetType(RNTupleLocator::kTypeMulti);
405-
locator.SetPosition(RNTupleLocatorObject64{42U});
405+
locator.SetPosition(RNTupleLocatorMulti{7, 1024});
406406
locator.SetNBytesOnStorage(1024U);
407407
locator.SetReserved(0);
408408
EXPECT_EQ(16u, RNTupleSerializer::SerializeLocator(locator, buffer).Unwrap());
@@ -411,7 +411,9 @@ TEST(RNTuple, SerializeLocator)
411411
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeMulti);
412412
EXPECT_EQ(locator.GetNBytesOnStorage(), 1024U);
413413
EXPECT_EQ(locator.GetReserved(), 0);
414-
EXPECT_EQ(42U, locator.GetPosition<RNTupleLocatorObject64>().GetLocation());
414+
auto multi = locator.GetPosition<RNTupleLocatorMulti>();
415+
EXPECT_EQ(7U, multi.GetObjectId());
416+
EXPECT_EQ(1024U, multi.GetOffset());
415417

416418
// Multi locator round-trip with 64-bit nBytesOnStorage and reserved bit
417419
locator.SetNBytesOnStorage(static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1);
@@ -422,7 +424,9 @@ TEST(RNTuple, SerializeLocator)
422424
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeMulti);
423425
EXPECT_EQ(locator.GetNBytesOnStorage(), static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1);
424426
EXPECT_EQ(locator.GetReserved(), 1);
425-
EXPECT_EQ(42U, locator.GetPosition<RNTupleLocatorObject64>().GetLocation());
427+
multi = locator.GetPosition<RNTupleLocatorMulti>();
428+
EXPECT_EQ(7U, multi.GetObjectId());
429+
EXPECT_EQ(1024U, multi.GetOffset());
426430

427431
std::int32_t *head = reinterpret_cast<std::int32_t *>(buffer);
428432
#ifndef R__BYTESWAP
@@ -435,6 +439,101 @@ TEST(RNTuple, SerializeLocator)
435439
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeUnknown);
436440
}
437441

442+
TEST(RNTuple, RNTupleLocatorMultiPackUnpack)
443+
{
444+
// Default-constructed: all zero
445+
RNTupleLocatorMulti zero;
446+
EXPECT_EQ(0U, zero.GetObjectId());
447+
EXPECT_EQ(0U, zero.GetOffset());
448+
EXPECT_EQ(0U, zero.GetReserved());
449+
EXPECT_EQ(0ULL, zero.GetLocation());
450+
451+
// Non-trivial values within the 30-bit range
452+
RNTupleLocatorMulti m(0x12345, 0xABCDE);
453+
EXPECT_EQ(0x12345U, m.GetObjectId());
454+
EXPECT_EQ(0xABCDEU, m.GetOffset());
455+
EXPECT_EQ(0U, m.GetReserved());
456+
// GetLocation() packs the three fields per the documented layout:
457+
// bits [63..60] reserved, [59..30] object id, [29..0] offset
458+
EXPECT_EQ((static_cast<std::uint64_t>(0x12345) << 30) | 0xABCDE, m.GetLocation());
459+
460+
// Round-trip via the raw uint64 constructor
461+
RNTupleLocatorMulti round{m.GetLocation()};
462+
EXPECT_EQ(m, round);
463+
EXPECT_EQ(m.GetObjectId(), round.GetObjectId());
464+
EXPECT_EQ(m.GetOffset(), round.GetOffset());
465+
466+
// Max 30-bit values
467+
RNTupleLocatorMulti maxVals(RNTupleLocatorMulti::kMaxObjectId, RNTupleLocatorMulti::kMaxOffset);
468+
EXPECT_EQ(0x3FFFFFFFU, maxVals.GetObjectId());
469+
EXPECT_EQ(0x3FFFFFFFU, maxVals.GetOffset());
470+
EXPECT_EQ(0U, maxVals.GetReserved());
471+
472+
// Object id only: no bit leak into offset or reserved
473+
RNTupleLocatorMulti idOnly(RNTupleLocatorMulti::kMaxObjectId, 0U);
474+
EXPECT_EQ(RNTupleLocatorMulti::kMaxObjectId, idOnly.GetObjectId());
475+
EXPECT_EQ(0U, idOnly.GetOffset());
476+
EXPECT_EQ(0U, idOnly.GetReserved());
477+
478+
// Offset only: no bit leak into object id or reserved
479+
RNTupleLocatorMulti offsetOnly(0U, RNTupleLocatorMulti::kMaxOffset);
480+
EXPECT_EQ(0U, offsetOnly.GetObjectId());
481+
EXPECT_EQ(RNTupleLocatorMulti::kMaxOffset, offsetOnly.GetOffset());
482+
EXPECT_EQ(0U, offsetOnly.GetReserved());
483+
484+
// Constructor enforces the 30-bit range on both fields
485+
EXPECT_THROW(RNTupleLocatorMulti(1U << 30, 0), ROOT::RException);
486+
EXPECT_THROW(RNTupleLocatorMulti(0, 1U << 30), ROOT::RException);
487+
488+
// Reserved bits round-trip without corrupting the other fields
489+
RNTupleLocatorMulti withReserved(7, 42);
490+
withReserved.SetReserved(0xF);
491+
EXPECT_EQ(0xF, withReserved.GetReserved());
492+
EXPECT_EQ(7U, withReserved.GetObjectId());
493+
EXPECT_EQ(42U, withReserved.GetOffset());
494+
495+
// SetReserved enforces the 4-bit range
496+
EXPECT_THROW(withReserved.SetReserved(0x10), ROOT::RException);
497+
498+
// Raw uint64 construction preserves bits without validation; the masks
499+
// correctly carve out each field from a fully populated location.
500+
RNTupleLocatorMulti raw{0xFFFFFFFFFFFFFFFFULL};
501+
EXPECT_EQ(RNTupleLocatorMulti::kMaxObjectId, raw.GetObjectId());
502+
EXPECT_EQ(RNTupleLocatorMulti::kMaxOffset, raw.GetOffset());
503+
EXPECT_EQ(RNTupleLocatorMulti::kMaxReserved, raw.GetReserved());
504+
}
505+
506+
TEST(RNTuple, RNTupleLocatorMultiTypeEnforcement)
507+
{
508+
RNTupleLocator locator;
509+
510+
// SetPosition(Multi) requires kTypeMulti.
511+
locator.SetType(RNTupleLocator::kTypeObject64);
512+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorMulti(1, 2)), ROOT::RException);
513+
locator.SetType(RNTupleLocator::kTypeFile);
514+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorMulti(1, 2)), ROOT::RException);
515+
516+
// SetPosition(Object64) no longer accepts kTypeMulti after the split.
517+
locator = RNTupleLocator{};
518+
locator.SetType(RNTupleLocator::kTypeMulti);
519+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorObject64{1}), ROOT::RException);
520+
521+
// Correct usage round-trip.
522+
locator.SetPosition(RNTupleLocatorMulti(1, 2));
523+
auto m = locator.GetPosition<RNTupleLocatorMulti>();
524+
EXPECT_EQ(1U, m.GetObjectId());
525+
EXPECT_EQ(2U, m.GetOffset());
526+
527+
// GetPosition<Object64>() no longer accepts kTypeMulti after the split.
528+
EXPECT_THROW(locator.GetPosition<RNTupleLocatorObject64>(), ROOT::RException);
529+
530+
// GetPosition<Multi>() rejects non-Multi types.
531+
locator = RNTupleLocator{};
532+
locator.SetType(RNTupleLocator::kTypeObject64);
533+
locator.SetPosition(RNTupleLocatorObject64{1});
534+
EXPECT_THROW(locator.GetPosition<RNTupleLocatorMulti>(), ROOT::RException);
535+
}
536+
438537
TEST(RNTuple, SerializeEnvelopeLink)
439538
{
440539
RNTupleSerializer::REnvelopeLink link;

tree/ntuple/test/ntuple_test.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
using ROOT::EExtraTypeInfoIds;
5858
using ROOT::RNTupleLocalIndex;
5959
using ROOT::RNTupleLocator;
60+
using ROOT::RNTupleLocatorMulti;
6061
using ROOT::RNTupleLocatorObject64;
6162
using ROOT::Internal::RColumnIndex;
6263
using RClusterDescriptor = ROOT::RClusterDescriptor;

0 commit comments

Comments
 (0)