From d0c80d489f533b0a9bb872ec8cf949314d73e74d Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 27 Apr 2026 17:24:38 +0200 Subject: [PATCH 1/2] [ntuple] remove unused RSoAField::fRecordMemberIndexes --- tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx | 1 - tree/ntuple/src/RFieldMeta.cxx | 2 -- 2 files changed, 3 deletions(-) diff --git a/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx b/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx index a0eee042b89c0..875ef7059e345 100644 --- a/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx +++ b/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx @@ -64,7 +64,6 @@ class RSoAField : public RFieldBase { TClass *fSoAClass = nullptr; std::vector fSoAMemberOffsets; ///< The offset of the RVec members in the SoA type - std::vector fRecordMemberIndexes; ///< Maps the SoA members to the members of the underlying record std::vector fRecordMemberFields; ///< Direct access to the member fields of the underlying record std::size_t fMaxAlignment = 1; ROOT::Internal::RColumnIndex fNWritten; diff --git a/tree/ntuple/src/RFieldMeta.cxx b/tree/ntuple/src/RFieldMeta.cxx index c8e331f30a3f5..e6f8cb4b7208e 100644 --- a/tree/ntuple/src/RFieldMeta.cxx +++ b/tree/ntuple/src/RFieldMeta.cxx @@ -658,7 +658,6 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, const RSoAF : ROOT::RFieldBase(fieldName, source.GetTypeName(), ROOT::ENTupleStructure::kCollection, false /* isSimple */), fSoAClass(source.fSoAClass), fSoAMemberOffsets(source.fSoAMemberOffsets), - fRecordMemberIndexes(source.fRecordMemberIndexes), fMaxAlignment(source.fMaxAlignment) { fTraits = source.GetTraits(); @@ -753,7 +752,6 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS fMaxAlignment = std::max(fMaxAlignment, vecField->GetAlignment()); fSoAMemberOffsets.emplace_back(dataMember->GetOffset()); - fRecordMemberIndexes.emplace_back(itr->second); } if (recordFieldNameToIdx.size() != fSoAMemberOffsets.size()) { throw RException(R__FAIL("missing SoA members")); From 2d1606c120a89d56040ff96948d39a1de381bd6e Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 27 Apr 2026 21:18:59 +0200 Subject: [PATCH 2/2] [ntuple] fix writing of unaligned SoA class Fix writing of SoA classes that has the members in a different order than the underlying record type. --- tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx | 4 +++- tree/ntuple/src/RFieldMeta.cxx | 9 ++++++--- tree/ntuple/test/SoAField.hxx | 6 ++++++ tree/ntuple/test/SoAFieldLinkDef.h | 1 + tree/ntuple/test/ntuple_soa.cxx | 22 ++++++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx b/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx index 875ef7059e345..07c5e87906dfb 100644 --- a/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx +++ b/tree/ntuple/inc/ROOT/RField/RFieldSoA.hxx @@ -63,8 +63,10 @@ class RSoAField : public RFieldBase { }; TClass *fSoAClass = nullptr; - std::vector fSoAMemberOffsets; ///< The offset of the RVec members in the SoA type std::vector fRecordMemberFields; ///< Direct access to the member fields of the underlying record + /// The offset of the RVec members in the SoA type in the order of subfields of the underlying record type. + /// In particular, the order is not necessarily the same then the order of RVec members in the SoA class. + std::vector fSoAMemberOffsets; std::size_t fMaxAlignment = 1; ROOT::Internal::RColumnIndex fNWritten; diff --git a/tree/ntuple/src/RFieldMeta.cxx b/tree/ntuple/src/RFieldMeta.cxx index e6f8cb4b7208e..0364db32becdb 100644 --- a/tree/ntuple/src/RFieldMeta.cxx +++ b/tree/ntuple/src/RFieldMeta.cxx @@ -715,6 +715,8 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS throw RException(R__FAIL("SoA fields with inheritance are currently unsupported")); } + fSoAMemberOffsets.resize(fRecordMemberFields.size()); + unsigned int nMembers = 0; for (auto dataMember : ROOT::Detail::TRangeStaticCast(*fSoAClass->GetListOfDataMembers())) { if ((dataMember->Property() & kIsStatic) || !dataMember->IsPersistent()) continue; @@ -751,12 +753,13 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, TClass *clS fMaxAlignment = std::max(fMaxAlignment, vecField->GetAlignment()); - fSoAMemberOffsets.emplace_back(dataMember->GetOffset()); + assert(itr->second < fSoAMemberOffsets.size()); + fSoAMemberOffsets[itr->second] = dataMember->GetOffset(); + nMembers++; } - if (recordFieldNameToIdx.size() != fSoAMemberOffsets.size()) { + if (recordFieldNameToIdx.size() != nMembers) { throw RException(R__FAIL("missing SoA members")); } - assert(fRecordMemberFields.size() == fSoAMemberOffsets.size()); std::string renormalizedAlias; if (ROOT::Internal::NeedsMetaNameAsAlias(fSoAClass->GetName(), renormalizedAlias)) diff --git a/tree/ntuple/test/SoAField.hxx b/tree/ntuple/test/SoAField.hxx index d31e5f04241ba..51eae69e38967 100644 --- a/tree/ntuple/test/SoAField.hxx +++ b/tree/ntuple/test/SoAField.hxx @@ -51,6 +51,12 @@ struct SoASimple { ClassDefNV(SoASimple, 2); }; +struct SoASimpleSwapped { + ROOT::RVec fY; + ROOT::RVec fX; + ClassDefNV(SoASimpleSwapped, 2); +}; + struct SoASimpleBadArray { ROOT::RVec fX; float fY[3]; diff --git a/tree/ntuple/test/SoAFieldLinkDef.h b/tree/ntuple/test/SoAFieldLinkDef.h index 79041223dbacc..5ea63c02b9043 100644 --- a/tree/ntuple/test/SoAFieldLinkDef.h +++ b/tree/ntuple/test/SoAFieldLinkDef.h @@ -13,6 +13,7 @@ #pragma link C++ class RecordSimple+; #pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimple+; +#pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleSwapped+; #pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleBadArray+; #pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleBadType+; #pragma link C++ options=rntupleSoARecord(RecordSimple) class SoASimpleUnexpectedMember+; diff --git a/tree/ntuple/test/ntuple_soa.cxx b/tree/ntuple/test/ntuple_soa.cxx index f32c17c6d4a31..edbcda63ac523 100644 --- a/tree/ntuple/test/ntuple_soa.cxx +++ b/tree/ntuple/test/ntuple_soa.cxx @@ -204,3 +204,25 @@ TEST(RNTuple, SoASimple) EXPECT_FLOAT_EQ(5.0, v(2).at(1).fX); EXPECT_FLOAT_EQ(6.0, v(2).at(1).fY); } + +TEST(RNTuple, SoASimpleSwapped) +{ + ROOT::TestSupport::FileRaii fileGuard("test_rntuple_soa_simple_swapped.root"); + + { + auto model = ROOT::RNTupleModel::Create(); + model->AddField(std::make_unique("f", "SoASimpleSwapped")); + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + auto soa = writer->GetModel().GetDefaultEntry().GetPtr("f"); + soa->fX.push_back(1.0); + soa->fY.push_back(2.0); + writer->Fill(); + } + + auto reader = ROOT::RNTupleReader::Open("ntpl", fileGuard.GetPath()); + EXPECT_EQ(1u, reader->GetNEntries()); + auto v = reader->GetView>("f"); + EXPECT_EQ(1u, v(0).size()); + EXPECT_FLOAT_EQ(1.0, v(0).at(0).fX); + EXPECT_FLOAT_EQ(2.0, v(0).at(0).fY); +}