diff --git a/io/io/inc/TStreamerInfoActions.h b/io/io/inc/TStreamerInfoActions.h index 6cc37943ac8f8..0c8aac6721dd0 100644 --- a/io/io/inc/TStreamerInfoActions.h +++ b/io/io/inc/TStreamerInfoActions.h @@ -213,6 +213,13 @@ namespace TStreamerInfoActions { void AddToOffset(Int_t delta); void SetMissing(); + /// Replace each action with `UseCacheVectorLoop` wrapping the original + /// action. Used by TBranchElement when a sub-branch must read its data + /// into the parent's on-file staging area (e.g. a split branch supplying + /// the source of a schema-evolution rule whose source is a nested + /// struct member). + void WrapAllActionsWithUseCacheVectorLoop(TVirtualStreamerInfo *info); + TActionSequence *CreateCopy(); static TActionSequence *CreateReadMemberWiseActions(TVirtualStreamerInfo *info, TVirtualCollectionProxy &proxy); static TActionSequence *CreateReadMemberWiseActions(TVirtualStreamerInfo &info, std::unique_ptr loopConfig); diff --git a/io/io/src/TStreamerInfoActions.cxx b/io/io/src/TStreamerInfoActions.cxx index bebd784eacd7d..84f3f9d7bbc82 100644 --- a/io/io/src/TStreamerInfoActions.cxx +++ b/io/io/src/TStreamerInfoActions.cxx @@ -5416,6 +5416,21 @@ void TStreamerInfoActions::TActionSequence::AddToOffset(Int_t delta) } } +void TStreamerInfoActions::TActionSequence::WrapAllActionsWithUseCacheVectorLoop(TVirtualStreamerInfo *info) +{ + // Replace each action in the sequence with a UseCacheVectorLoop wrapping + // the original action. After this call, the actions iterate over the + // staging area pushed onto the buffer's data cache stack rather than the + // original (user) iteration range. The element offsets stored on the + // inner actions therefore must be relative to the staging element layout. + + for (auto &configured : fActions) { + TConfiguredAction inner(configured); + configured.fLoopAction = UseCacheVectorLoop; + configured.fConfiguration = new TConfigurationUseCache(info, inner, /*repeat*/ kFALSE); + } +} + void TStreamerInfoActions::TActionSequence::SetMissing() { // Add the (potentially negative) delta to all the configuration's offset. This is used by diff --git a/tree/tree/inc/TBranchElement.h b/tree/tree/inc/TBranchElement.h index 39c762041c414..0d00ffd1879f7 100644 --- a/tree/tree/inc/TBranchElement.h +++ b/tree/tree/inc/TBranchElement.h @@ -51,7 +51,9 @@ class TBranchElement : public TBranch { kOwnOnfileObj = BIT(19), ///< We are the owner of fOnfileObject. kAddressSet = BIT(20), ///< The addressing set have been called for this branch kMakeClass = BIT(21), ///< This branch has been switched to using the MakeClass Mode - kDecomposedObj = BIT(21) ///< More explicit alias for kMakeClass. + kDecomposedObj = BIT(21), ///< More explicit alias for kMakeClass. + kReadFromStagingArray = BIT(23) ///< This split sub-branch must read its data into the parent's on-file staging + ///< area (e.g. for a schema-evolution rule with a nested split source). }; diff --git a/tree/tree/src/TBranchElement.cxx b/tree/tree/src/TBranchElement.cxx index a8227be2e1bac..d02b0d3c16baa 100644 --- a/tree/tree/src/TBranchElement.cxx +++ b/tree/tree/src/TBranchElement.cxx @@ -3695,6 +3695,18 @@ void TBranchElement::InitializeOffsets() dataName.Replace(dotpos,endpos-dotpos,subBranchElement->GetFullName()); } TRealData* rd = pClass->GetRealData(dataName); + TRealData *stagingRd = nullptr; + if (!rd && fOnfileObject && fOnfileObject->fClass) { + // The data member does not exist in the user (target) class. + // If the parent owns an on-file staging area (e.g. for an I/O + // rule whose source is a struct member that has itself been + // split on disk), try the staging class. When found, the + // sub-branch will be redirected to read its bytes into the + // staging area rather than be skipped. + stagingRd = fOnfileObject->fClass->GetRealData(dataName); + if (stagingRd && stagingRd->TestBit(TRealData::kTransient)) + stagingRd = nullptr; + } if (rd && (!rd->TestBit(TRealData::kTransient) || alternateElement)) { // -- Data member exists in the dictionary meta info, get the offset. // If we are using an alternateElement, it is the target of a rule @@ -3704,6 +3716,17 @@ void TBranchElement::InitializeOffsets() // We are a rule with no specific target, it applies to the whole // object, let's set the offset to zero offset = 0; + } else if (stagingRd) { + // -- Staging redirect: read into the parent's on-file object. + offset = stagingRd->GetThisOffset(); + subBranch->fOnfileObject = fOnfileObject; + subBranch->SetBit(kReadFromStagingArray); + // The sub-branch's read-action sequence may have been built + // earlier (before we had a chance to set the bit and the + // on-file object), so re-build it now to pick up the staging + // redirect. We defer this until after the per-sub-branch + // SetOffset call below to make sure the action offsets are + // computed against the staging element layout. } else { // -- No dictionary meta info for this data member, it must no // longer exist @@ -3767,6 +3790,13 @@ void TBranchElement::InitializeOffsets() // 'localOffset', we need to remove it explicitly. subBranch->SetOffset(offset - localOffset); } + if (subBranch->TestBit(kReadFromStagingArray) && subBranch->fReadActionSequence) { + // The sub-branch's read action sequence may have been + // built before the staging-redirect bit was set above, + // so re-build it now to install the UseCacheVectorLoop + // wrappers (see TBranchElement::SetReadActionSequence). + subBranch->SetReadActionSequence(); + } } } else { // -- Set fBranchOffset for sub-branch. @@ -5764,6 +5794,19 @@ void TBranchElement::SetReadActionSequence() if (create) { SetActionSequence(originalClass, localInfo, create, fReadActionSequence); } + + if (TestBit(kReadFromStagingArray) && fReadActionSequence && fOnfileObject) { + // The on-disk data for this split sub-branch needs to land in the + // parent's on-file staging area (e.g. it is the source of an I/O rule + // whose source member is a nested struct that has been split on disk). + // Replace each action with a UseCacheVectorLoop wrapping the original, + // so the action iterates over the staging area rather than the user + // collection. The element offsets configured on the inner actions are + // already expressed relative to the staging element layout. + TVirtualStreamerInfo *stagingInfo = fOnfileObject->fClass ? fOnfileObject->fClass->GetStreamerInfo() : nullptr; + if (stagingInfo) + fReadActionSequence->WrapAllActionsWithUseCacheVectorLoop(stagingInfo); + } } ////////////////////////////////////////////////////////////////////////////////