From 25a9f9b8990224a8890587602f59a5223110fbc9 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Tue, 13 Oct 2026 11:30:54 +0200 Subject: [PATCH] [ntuple] fix emulatation of SoA fields Emulated SoA fields should have the SoA trait set so that during merging the output field preserves the flag. --- .../ROOT/RField/RFieldSequenceContainer.hxx | 23 +++++++------ tree/ntuple/src/RField.cxx | 10 +++--- tree/ntuple/src/RFieldBase.cxx | 4 +-- tree/ntuple/src/RFieldSequenceContainer.cxx | 32 ++++++++++++++----- tree/ntuple/test/ntuple_emulated.cxx | 1 + 5 files changed, 47 insertions(+), 23 deletions(-) diff --git a/tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx b/tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx index 82f685bde14e7..64244b0614403 100644 --- a/tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx +++ b/tree/ntuple/inc/ROOT/RField/RFieldSequenceContainer.hxx @@ -37,7 +37,7 @@ class RSoAField; namespace Internal { std::unique_ptr CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr itemField, - std::string_view emulatedFromType); + std::string_view emulatedFromType, bool isSoA); } //////////////////////////////////////////////////////////////////////////////// @@ -212,9 +212,9 @@ public: /// The field can be constructed as untyped collection through CreateUntyped(). class RVectorField : public RFieldBase { friend class RArrayAsVectorField; // to get access to the RVectorDeleter - friend std::unique_ptr Internal::CreateEmulatedVectorField(std::string_view fieldName, - std::unique_ptr itemField, - std::string_view emulatedFromType); + friend std::unique_ptr + Internal::CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr itemField, + std::string_view emulatedFromType, bool isSoA); class RVectorDeleter : public RDeleter { private: @@ -237,11 +237,16 @@ class RVectorField : public RFieldBase { RDeleter *itemDeleter); protected: - /// Creates a possibly-untyped VectorField. - /// If `emulatedFromType` is not nullopt, the field is untyped. If the string is empty, it is a "regular" - /// untyped vector field; otherwise, it was created as an emulated field from the given type name. - RVectorField(std::string_view fieldName, std::unique_ptr itemField, - std::optional emulatedFromType); + enum class ECollectionType { + kRegular, + kUntyped, + kEmulatedProxy, + kEmulatedSoA + }; + /// Creates a possibly untyped or emulated VectorField. + /// The emulatedFromType information is only needed for the non-regular vectors. + RVectorField(std::string_view fieldName, std::unique_ptr itemField, ECollectionType collectionType, + std::string_view enforcedTypeName); std::unique_ptr CloneImpl(std::string_view newName) const final; diff --git a/tree/ntuple/src/RField.cxx b/tree/ntuple/src/RField.cxx index 05dcd1aef1723..b5ab83f69e63d 100644 --- a/tree/ntuple/src/RField.cxx +++ b/tree/ntuple/src/RField.cxx @@ -595,12 +595,14 @@ ROOT::Internal::CreateEmulatedRecordField(std::string_view fieldName, return std::unique_ptr(new RRecordField(fieldName, std::move(itemFields), emulatedFromType)); } -std::unique_ptr ROOT::Internal::CreateEmulatedVectorField(std::string_view fieldName, - std::unique_ptr itemField, - std::string_view emulatedFromType) +std::unique_ptr +ROOT::Internal::CreateEmulatedVectorField(std::string_view fieldName, std::unique_ptr itemField, + std::string_view emulatedFromType, bool isSoA) { R__ASSERT(!emulatedFromType.empty()); - return std::unique_ptr(new RVectorField(fieldName, std::move(itemField), emulatedFromType)); + const RVectorField::ECollectionType type = + isSoA ? RVectorField::ECollectionType::kEmulatedSoA : RVectorField::ECollectionType::kEmulatedProxy; + return std::unique_ptr(new RVectorField(fieldName, std::move(itemField), type, emulatedFromType)); } ROOT::RRecordField::RRecordField(std::string_view fieldName, std::vector> itemFields, diff --git a/tree/ntuple/src/RFieldBase.cxx b/tree/ntuple/src/RFieldBase.cxx index 05a5254c7d8d5..a69c24b6ff6d0 100644 --- a/tree/ntuple/src/RFieldBase.cxx +++ b/tree/ntuple/src/RFieldBase.cxx @@ -553,8 +553,8 @@ ROOT::RFieldBase::Create(const std::string &fieldName, const std::string &typeNa const auto &itemFieldDesc = desc->GetFieldDescriptor(itemFieldId); auto itemField = Create(itemFieldDesc.GetFieldName(), itemFieldDesc.GetTypeName(), options, desc, itemFieldId).Unwrap(); - auto vecField = - ROOT::Internal::CreateEmulatedVectorField(fieldName, std::move(itemField), fieldDesc.GetTypeName()); + auto vecField = ROOT::Internal::CreateEmulatedVectorField( + fieldName, std::move(itemField), fieldDesc.GetTypeName(), fieldDesc.IsSoACollection()); vecField->fTypeAlias = fieldDesc.GetTypeAlias(); return vecField; } else if (ROOT::Internal::IsCustomEnumFieldDesc(*desc, fieldDesc)) { diff --git a/tree/ntuple/src/RFieldSequenceContainer.cxx b/tree/ntuple/src/RFieldSequenceContainer.cxx index 7505249e14b86..b4e6c2288a40f 100644 --- a/tree/ntuple/src/RFieldSequenceContainer.cxx +++ b/tree/ntuple/src/RFieldSequenceContainer.cxx @@ -507,19 +507,26 @@ void ROOT::RRVecField::AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const //------------------------------------------------------------------------------ ROOT::RVectorField::RVectorField(std::string_view fieldName, std::unique_ptr itemField, - std::optional emulatedFromType) - : ROOT::RFieldBase(fieldName, emulatedFromType ? *emulatedFromType : "std::vector<" + itemField->GetTypeName() + ">", + ECollectionType collectionType, std::string_view enforcedTypeName) + : ROOT::RFieldBase(fieldName, + (collectionType != ECollectionType::kRegular) ? enforcedTypeName + : "std::vector<" + itemField->GetTypeName() + ">", ROOT::ENTupleStructure::kCollection, false /* isSimple */), fItemSize(itemField->GetValueSize()), fNWritten(0) { + // enforcedTypeName must be empty if and only if the collection type is regular or untyped + assert(enforcedTypeName.empty() != (collectionType >= ECollectionType::kEmulatedProxy)); + if (itemField->GetAlignment() > kMaxItemAlignment) { throw RException( R__FAIL(std::string("Unsupported vector item alignment: ") + std::to_string(itemField->GetAlignment()))); } - if (emulatedFromType && !emulatedFromType->empty()) + if (collectionType >= ECollectionType::kEmulatedProxy) fTraits |= kTraitEmulatedField; + if (collectionType == ECollectionType::kEmulatedSoA) + fTraits |= kTraitSoACollection; if (!itemField->GetTypeAlias().empty()) fTypeAlias = "std::vector<" + itemField->GetTypeAlias() + ">"; @@ -530,22 +537,31 @@ ROOT::RVectorField::RVectorField(std::string_view fieldName, std::unique_ptr itemField) - : RVectorField(fieldName, std::move(itemField), {}) + : RVectorField(fieldName, std::move(itemField), ECollectionType::kRegular, "") { } std::unique_ptr ROOT::RVectorField::CreateUntyped(std::string_view fieldName, std::unique_ptr itemField) { - return std::unique_ptr(new RVectorField(fieldName, itemField->Clone("_0"), "")); + return std::unique_ptr( + new RVectorField(fieldName, itemField->Clone("_0"), ECollectionType::kUntyped, "")); } std::unique_ptr ROOT::RVectorField::CloneImpl(std::string_view newName) const { auto newItemField = fSubfields[0]->Clone(fSubfields[0]->GetFieldName()); - auto isUntyped = GetTypeName().empty() || ((fTraits & kTraitEmulatedField) != 0); - auto emulatedFromType = isUntyped ? std::make_optional(GetTypeName()) : std::nullopt; - return std::unique_ptr(new RVectorField(newName, std::move(newItemField), emulatedFromType)); + ECollectionType type = ECollectionType::kRegular; + if (GetTypeName().empty()) { + type = ECollectionType::kUntyped; + } else if (fTraits & kTraitEmulatedField) { + type = (fTraits & kTraitSoACollection) ? ECollectionType::kEmulatedSoA : ECollectionType::kEmulatedProxy; + } + std::string_view enforcedTypeName; + if (type != ECollectionType::kRegular) + enforcedTypeName = GetTypeName(); + return std::unique_ptr( + new RVectorField(newName, std::move(newItemField), type, enforcedTypeName)); } std::size_t ROOT::RVectorField::AppendImpl(const void *from) diff --git a/tree/ntuple/test/ntuple_emulated.cxx b/tree/ntuple/test/ntuple_emulated.cxx index 8d4b1a1f1d019..71b36b658eb96 100644 --- a/tree/ntuple/test/ntuple_emulated.cxx +++ b/tree/ntuple/test/ntuple_emulated.cxx @@ -984,6 +984,7 @@ TEST(RNTupleEmulated, EmulatedFields_SoA) const auto &soa = model->GetConstField("f"); EXPECT_EQ(soa.GetTypeName(), "EmulatedSoA"); EXPECT_TRUE(soa.GetTraits() & ROOT::RFieldBase::kTraitEmulatedField); + EXPECT_TRUE(soa.GetTraits() & ROOT::RFieldBase::kTraitSoACollection); EXPECT_EQ(soa.GetStructure(), ROOT::ENTupleStructure::kCollection); const auto children = soa.GetConstSubfields();