Skip to content

Commit 362d86f

Browse files
committed
[ntuple] Fix RArrayAsRVecField for use outside RDF
This field used to expect a pre-contructed RVec of a certain size, which we can ensure in RDF but not elsewhere. Factor out the logic to resize RVecs in a type-erased way and use it in both this field and the RRVecField.
1 parent 01664eb commit 362d86f

File tree

2 files changed

+60
-87
lines changed

2 files changed

+60
-87
lines changed

tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,13 @@ public:
111111

112112
/// The type-erased field for a RVec<Type>
113113
class RRVecField : public RFieldBase {
114+
friend class RArrayAsRVecField; // to call ResizeRVec()
115+
116+
// Ensures that the RVec pointed to by rvec has at least nItems valid elements
117+
// Returns the possibly new "begin pointer" of the RVec, i.e. the pointer to the data area.
118+
static unsigned char *
119+
ResizeRVec(void *rvec, std::size_t nItems, std::size_t itemSize, const RFieldBase *itemField, RDeleter *itemDeleter);
120+
114121
public:
115122
/// the RRVecDeleter is also used by RArrayAsRVecField and therefore declared public
116123
class RRVecDeleter : public RDeleter {

tree/ntuple/src/RFieldSequenceContainer.cxx

Lines changed: 53 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <ROOT/RFieldUtils.hxx>
1010

1111
#include <cstdlib> // for malloc, free
12+
#include <limits>
1213
#include <memory>
1314
#include <new> // hardware_destructive_interference_size
1415

@@ -134,19 +135,20 @@ namespace {
134135

135136
/// Retrieve the addresses of the data members of a generic RVec from a pointer to the beginning of the RVec object.
136137
/// Returns pointers to fBegin, fSize and fCapacity in a std::tuple.
137-
std::tuple<void **, std::int32_t *, std::int32_t *> GetRVecDataMembers(void *rvecPtr)
138+
std::tuple<unsigned char **, std::int32_t *, std::int32_t *> GetRVecDataMembers(void *rvecPtr)
138139
{
139-
void **begin = reinterpret_cast<void **>(rvecPtr);
140+
unsigned char **beginPtr = reinterpret_cast<unsigned char **>(rvecPtr);
140141
// int32_t fSize is the second data member (after 1 void*)
141-
std::int32_t *size = reinterpret_cast<std::int32_t *>(begin + 1);
142+
std::int32_t *size = reinterpret_cast<std::int32_t *>(beginPtr + 1);
142143
R__ASSERT(*size >= 0);
143144
// int32_t fCapacity is the third data member (1 int32_t after fSize)
144145
std::int32_t *capacity = size + 1;
145146
R__ASSERT(*capacity >= -1);
146-
return {begin, size, capacity};
147+
return {beginPtr, size, capacity};
147148
}
148149

149-
std::tuple<const void *const *, const std::int32_t *, const std::int32_t *> GetRVecDataMembers(const void *rvecPtr)
150+
std::tuple<const unsigned char *const *, const std::int32_t *, const std::int32_t *>
151+
GetRVecDataMembers(const void *rvecPtr)
150152
{
151153
return {GetRVecDataMembers(const_cast<void *>(rvecPtr))};
152154
}
@@ -198,19 +200,19 @@ std::size_t EvalRVecAlignment(std::size_t alignOfSubfield)
198200
return std::max({alignof(void *), alignof(std::int32_t), alignOfSubfield});
199201
}
200202

201-
void DestroyRVecWithChecks(std::size_t alignOfT, void **beginPtr, char *begin, std::int32_t *capacityPtr)
203+
void DestroyRVecWithChecks(std::size_t alignOfT, unsigned char **beginPtr, std::int32_t *capacityPtr)
202204
{
203205
// figure out if we are in the small state, i.e. begin == &inlineBuffer
204206
// there might be padding between fCapacity and the inline buffer, so we compute it here
205207
constexpr auto dataMemberSz = sizeof(void *) + 2 * sizeof(std::int32_t);
206208
auto paddingMiddle = dataMemberSz % alignOfT;
207209
if (paddingMiddle != 0)
208210
paddingMiddle = alignOfT - paddingMiddle;
209-
const bool isSmall = (begin == (reinterpret_cast<char *>(beginPtr) + dataMemberSz + paddingMiddle));
211+
const bool isSmall = (*beginPtr == (reinterpret_cast<unsigned char *>(beginPtr) + dataMemberSz + paddingMiddle));
210212

211213
const bool owns = (*capacityPtr != -1);
212214
if (!isSmall && owns)
213-
free(begin);
215+
free(*beginPtr);
214216
}
215217

216218
} // anonymous namespace
@@ -242,9 +244,8 @@ std::size_t ROOT::RRVecField::AppendImpl(const void *from)
242244
GetPrincipalColumnOf(*fSubfields[0])->AppendV(*beginPtr, *sizePtr);
243245
nbytes += *sizePtr * GetPrincipalColumnOf(*fSubfields[0])->GetElement()->GetPackedSize();
244246
} else {
245-
auto begin = reinterpret_cast<const char *>(*beginPtr); // for pointer arithmetics
246247
for (std::int32_t i = 0; i < *sizePtr; ++i) {
247-
nbytes += CallAppendOn(*fSubfields[0], begin + i * fItemSize);
248+
nbytes += CallAppendOn(*fSubfields[0], *beginPtr + i * fItemSize);
248249
}
249250
}
250251

@@ -253,30 +254,27 @@ std::size_t ROOT::RRVecField::AppendImpl(const void *from)
253254
return nbytes + fPrincipalColumn->GetElement()->GetPackedSize();
254255
}
255256

256-
void ROOT::RRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
257-
{
258-
// TODO as a performance optimization, we could assign values to elements of the inline buffer:
259-
// if size < inline buffer size: we save one allocation here and usage of the RVec skips a pointer indirection
257+
unsigned char *ROOT::RRVecField::ResizeRVec(void *rvec, std::size_t nItems, std::size_t itemSize,
258+
const RFieldBase *itemField, RDeleter *itemDeleter)
260259

261-
auto [beginPtr, sizePtr, capacityPtr] = GetRVecDataMembers(to);
260+
{
261+
if (nItems > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) {
262+
throw RException(R__FAIL("RVec too large: " + std::to_string(nItems)));
263+
}
262264

263-
// Read collection info for this entry
264-
ROOT::NTupleSize_t nItems;
265-
RNTupleLocalIndex collectionStart;
266-
fPrincipalColumn->GetCollectionInfo(globalIndex, &collectionStart, &nItems);
267-
char *begin = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
265+
auto [beginPtr, sizePtr, capacityPtr] = GetRVecDataMembers(rvec);
268266
const std::size_t oldSize = *sizePtr;
269267

270268
// See "semantics of reading non-trivial objects" in RNTuple's Architecture.md for details
271269
// on the element construction/destrution.
272270
const bool owns = (*capacityPtr != -1);
273-
const bool needsConstruct = !(fSubfields[0]->GetTraits() & kTraitTriviallyConstructible);
274-
const bool needsDestruct = owns && fItemDeleter;
271+
const bool needsConstruct = !(itemField->GetTraits() & kTraitTriviallyConstructible);
272+
const bool needsDestruct = owns && itemDeleter;
275273

276274
// Destroy excess elements, if any
277275
if (needsDestruct) {
278276
for (std::size_t i = nItems; i < oldSize; ++i) {
279-
fItemDeleter->operator()(begin + (i * fItemSize), true /* dtorOnly */);
277+
itemDeleter->operator()(*beginPtr + (i * itemSize), true /* dtorOnly */);
280278
}
281279
}
282280

@@ -286,7 +284,7 @@ void ROOT::RRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
286284
// allocates memory we need to release it here to avoid memleaks (e.g. if this is an RVec<RVec<int>>)
287285
if (needsDestruct) {
288286
for (std::size_t i = 0u; i < oldSize; ++i) {
289-
fItemDeleter->operator()(begin + (i * fItemSize), true /* dtorOnly */);
287+
itemDeleter->operator()(*beginPtr + (i * itemSize), true /* dtorOnly */);
290288
}
291289
}
292290

@@ -297,25 +295,39 @@ void ROOT::RRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
297295
}
298296
// We trust that malloc returns a buffer with large enough alignment.
299297
// This might not be the case if T in RVec<T> is over-aligned.
300-
*beginPtr = malloc(nItems * fItemSize);
298+
*beginPtr = static_cast<unsigned char *>(malloc(nItems * itemSize));
301299
R__ASSERT(*beginPtr != nullptr);
302-
begin = reinterpret_cast<char *>(*beginPtr);
303300
*capacityPtr = nItems;
304301

305302
// Placement new for elements that were already there before the resize
306303
if (needsConstruct) {
307304
for (std::size_t i = 0u; i < oldSize; ++i)
308-
CallConstructValueOn(*fSubfields[0], begin + (i * fItemSize));
305+
CallConstructValueOn(*itemField, *beginPtr + (i * itemSize));
309306
}
310307
}
311308
*sizePtr = nItems;
312309

313310
// Placement new for new elements, if any
314311
if (needsConstruct) {
315312
for (std::size_t i = oldSize; i < nItems; ++i)
316-
CallConstructValueOn(*fSubfields[0], begin + (i * fItemSize));
313+
CallConstructValueOn(*itemField, *beginPtr + (i * itemSize));
317314
}
318315

316+
return *beginPtr;
317+
}
318+
319+
void ROOT::RRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
320+
{
321+
// TODO as a performance optimization, we could assign values to elements of the inline buffer:
322+
// if size < inline buffer size: we save one allocation here and usage of the RVec skips a pointer indirection
323+
324+
// Read collection info for this entry
325+
ROOT::NTupleSize_t nItems;
326+
RNTupleLocalIndex collectionStart;
327+
fPrincipalColumn->GetCollectionInfo(globalIndex, &collectionStart, &nItems);
328+
329+
auto begin = ResizeRVec(to, nItems, fItemSize, fSubfields[0].get(), fItemDeleter.get());
330+
319331
if (fSubfields[0]->IsSimple() && nItems) {
320332
GetPrincipalColumnOf(*fSubfields[0])->ReadV(collectionStart, nItems, begin);
321333
return;
@@ -430,14 +442,13 @@ void ROOT::RRVecField::RRVecDeleter::operator()(void *objPtr, bool dtorOnly)
430442
{
431443
auto [beginPtr, sizePtr, capacityPtr] = GetRVecDataMembers(objPtr);
432444

433-
char *begin = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
434445
if (fItemDeleter) {
435446
for (std::int32_t i = 0; i < *sizePtr; ++i) {
436-
fItemDeleter->operator()(begin + i * fItemSize, true /* dtorOnly */);
447+
fItemDeleter->operator()(*beginPtr + i * fItemSize, true /* dtorOnly */);
437448
}
438449
}
439450

440-
DestroyRVecWithChecks(fItemAlignment, beginPtr, begin, capacityPtr);
451+
DestroyRVecWithChecks(fItemAlignment, beginPtr, capacityPtr);
441452
RDeleter::operator()(objPtr, dtorOnly);
442453
}
443454

@@ -453,10 +464,10 @@ std::vector<ROOT::RFieldBase::RValue> ROOT::RRVecField::SplitValue(const RValue
453464
auto [beginPtr, sizePtr, _] = GetRVecDataMembers(value.GetPtr<void>().get());
454465

455466
std::vector<RValue> result;
456-
char *begin = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
457467
result.reserve(*sizePtr);
458468
for (std::int32_t i = 0; i < *sizePtr; ++i) {
459-
result.emplace_back(fSubfields[0]->BindValue(std::shared_ptr<void>(value.GetPtr<void>(), begin + i * fItemSize)));
469+
result.emplace_back(
470+
fSubfields[0]->BindValue(std::shared_ptr<void>(value.GetPtr<void>(), *beginPtr + i * fItemSize)));
460471
}
461472
return result;
462473
}
@@ -748,52 +759,10 @@ std::unique_ptr<ROOT::RFieldBase> ROOT::RArrayAsRVecField::CloneImpl(std::string
748759
void ROOT::RArrayAsRVecField::ConstructValue(void *where) const
749760
{
750761
// initialize data members fBegin, fSize, fCapacity
762+
// currently the inline buffer is left uninitialized
751763
void **beginPtr = new (where)(void *)(nullptr);
752-
std::int32_t *sizePtr = new (reinterpret_cast<void *>(beginPtr + 1)) std::int32_t(0);
753-
std::int32_t *capacityPtr = new (sizePtr + 1) std::int32_t(0);
754-
755-
// Create the RVec with the known fixed size, do it once here instead of
756-
// every time the value is read in `Read*Impl` functions
757-
char *begin = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
758-
759-
// Early return if the RVec has already been allocated.
760-
if (*sizePtr == std::int32_t(fArrayLength))
761-
return;
762-
763-
// Need to allocate the RVec if it is the first time the value is being created.
764-
// See "semantics of reading non-trivial objects" in RNTuple's Architecture.md for details
765-
// on the element construction.
766-
const bool owns = (*capacityPtr != -1); // RVec is adopting the memory
767-
const bool needsConstruct = !(fSubfields[0]->GetTraits() & kTraitTriviallyConstructible);
768-
const bool needsDestruct = owns && fItemDeleter;
769-
770-
// Destroy old elements: useless work for trivial types, but in case the element type's constructor
771-
// allocates memory we need to release it here to avoid memleaks (e.g. if this is an RVec<RVec<int>>)
772-
if (needsDestruct) {
773-
for (std::int32_t i = 0; i < *sizePtr; ++i) {
774-
fItemDeleter->operator()(begin + (i * fItemSize), true /* dtorOnly */);
775-
}
776-
}
777-
778-
// TODO: Isn't the RVec always owning in this case?
779-
if (owns) {
780-
// *beginPtr points to the array of item values (allocated in an earlier call by the following malloc())
781-
free(*beginPtr);
782-
}
783-
784-
*beginPtr = malloc(fArrayLength * fItemSize);
785-
R__ASSERT(*beginPtr != nullptr);
786-
// Re-assign begin pointer after allocation
787-
begin = reinterpret_cast<char *>(*beginPtr);
788-
// Size and capacity are equal since the field data type is std::array
789-
*sizePtr = fArrayLength;
790-
*capacityPtr = fArrayLength;
791-
792-
// Placement new for the array elements
793-
if (needsConstruct) {
794-
for (std::size_t i = 0; i < fArrayLength; ++i)
795-
CallConstructValueOn(*fSubfields[0], begin + (i * fItemSize));
796-
}
764+
std::int32_t *sizePtr = new (static_cast<void *>(beginPtr + 1)) std::int32_t(0);
765+
new (sizePtr + 1) std::int32_t(-1);
797766
}
798767

799768
std::unique_ptr<ROOT::RFieldBase::RDeleter> ROOT::RArrayAsRVecField::GetDeleter() const
@@ -807,39 +776,36 @@ std::unique_ptr<ROOT::RFieldBase::RDeleter> ROOT::RArrayAsRVecField::GetDeleter(
807776

808777
void ROOT::RArrayAsRVecField::ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to)
809778
{
810-
811-
auto [beginPtr, _, __] = GetRVecDataMembers(to);
812-
auto rvecBeginPtr = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
779+
auto begin = RRVecField::ResizeRVec(to, fArrayLength, fItemSize, fSubfields[0].get(), fItemDeleter.get());
813780

814781
if (fSubfields[0]->IsSimple()) {
815-
GetPrincipalColumnOf(*fSubfields[0])->ReadV(globalIndex * fArrayLength, fArrayLength, rvecBeginPtr);
782+
GetPrincipalColumnOf(*fSubfields[0])->ReadV(globalIndex * fArrayLength, fArrayLength, begin);
816783
return;
817784
}
818785

819786
// Read the new values into the collection elements
820787
for (std::size_t i = 0; i < fArrayLength; ++i) {
821-
CallReadOn(*fSubfields[0], globalIndex * fArrayLength + i, rvecBeginPtr + (i * fItemSize));
788+
CallReadOn(*fSubfields[0], globalIndex * fArrayLength + i, begin + (i * fItemSize));
822789
}
823790
}
824791

825792
void ROOT::RArrayAsRVecField::ReadInClusterImpl(RNTupleLocalIndex localIndex, void *to)
826793
{
827-
auto [beginPtr, _, __] = GetRVecDataMembers(to);
828-
auto rvecBeginPtr = reinterpret_cast<char *>(*beginPtr); // for pointer arithmetics
794+
auto begin = RRVecField::ResizeRVec(to, fArrayLength, fItemSize, fSubfields[0].get(), fItemDeleter.get());
829795

830796
const auto &clusterId = localIndex.GetClusterId();
831797
const auto &indexInCluster = localIndex.GetIndexInCluster();
832798

833799
if (fSubfields[0]->IsSimple()) {
834800
GetPrincipalColumnOf(*fSubfields[0])
835-
->ReadV(RNTupleLocalIndex(clusterId, indexInCluster * fArrayLength), fArrayLength, rvecBeginPtr);
801+
->ReadV(RNTupleLocalIndex(clusterId, indexInCluster * fArrayLength), fArrayLength, begin);
836802
return;
837803
}
838804

839805
// Read the new values into the collection elements
840806
for (std::size_t i = 0; i < fArrayLength; ++i) {
841807
CallReadOn(*fSubfields[0], RNTupleLocalIndex(clusterId, indexInCluster * fArrayLength + i),
842-
rvecBeginPtr + (i * fItemSize));
808+
begin + (i * fItemSize));
843809
}
844810
}
845811

0 commit comments

Comments
 (0)