Skip to content

Commit

Permalink
Merge pull request #43241 from fwyzard/test_user_defined_ROOT_streamer
Browse files Browse the repository at this point in the history
Implement ROOT read rules for PortableHostCollection [13.3.x]
  • Loading branch information
cmsbuild authored Nov 13, 2023
2 parents 550ebb4 + ceab451 commit 3463fd1
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 15 deletions.
2 changes: 2 additions & 0 deletions DataFormats/Portable/BuildFile.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
<use name="alpaka"/>
<use name="rootcling"/>
<use name="FWCore/Utilities/" source_only="1"/>
<use name="HeterogeneousCore/AlpakaInterface" source_only="1"/>
33 changes: 31 additions & 2 deletions DataFormats/Portable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,37 @@

`PortableHostCollection<T>` is a class template that wraps a SoA type `T` and an alpaka host buffer, which owns the
memory where the SoA is allocated. The content of the SoA is persistent, while the buffer itself is transient.
Specialisations of this template can be persisted, and can be read back also in "bare ROOT" mode, without any
dictionaries.
Specialisations of this template can be persisted, but requre specil ROOT read rules to read the data into a single
memory buffer.

The original way to declare these rules, now deprecated, is to add them to the `classes_def.xml` file. For example,
to declare the read rules for `portabletest::TestHostCollection` based on the `portabletest::TestSoA` SoA, one
would add to the same `classes_def.xml` file where `portabletest::TestHostCollection` is declared:
```xml
<read
sourceClass="portabletest::TestHostCollection"
targetClass="portabletest::TestHostCollection"
version="[1-]"
source="portabletest::TestSoA layout_;"
target="buffer_,layout_,view_"
embed="false">
<![CDATA[
portabletest::TestHostCollection::ROOTReadStreamer(newObj, onfile.layout_);
]]>
```

The new, recommended way to declare these rules is to create a `classes.cc` file along with `classes.h`, and use the
`SET_PORTABLEHOSTCOLLECTION_READ_RULES`. For example, to declare the rules for `portabletest::TestHostCollection` one
would create the file `classes.cc` with the content:
```c++
#include "DataFormats/Portable/interface/PortableHostCollectionReadRules.h"
#include "DataFormats/PortableTestObjects/interface/TestHostCollection.h"

SET_PORTABLEHOSTCOLLECTION_READ_RULES(portabletest::TestHostCollection);
```
`PortableHostCollection<T>` can also be read back in "bare ROOT" mode, without any dictionaries.
They have no implicit or explicit references to alpaka (neither as part of the class signature nor as part of its name).
This could make it possible to read them back with different portability solutions in the future.
Expand Down
5 changes: 4 additions & 1 deletion DataFormats/Portable/interface/PortableHostCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ class PortableHostCollection {

// part of the ROOT read streamer
static void ROOTReadStreamer(PortableHostCollection* newObj, Layout& layout) {
// destroy the default-constructed collection
newObj->~PortableHostCollection();
// use the global "host" object returned by cms::alpakatools::host()
// construct in-place a new collection, with the known size, using the global "host" object returned by cms::alpakatools::host()
new (newObj) PortableHostCollection(layout.metadata().size(), cms::alpakatools::host());
// copy the data from the on-file layout to the new collection
newObj->layout_.ROOTReadStreamer(layout);
// free the memory allocated by ROOT
layout.ROOTStreamerCleaner();
}

Expand Down
76 changes: 76 additions & 0 deletions DataFormats/Portable/interface/PortableHostCollectionReadRules.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef DataFormats_Portable_interface_PortableHostCollectionReadRules_h
#define DataFormats_Portable_interface_PortableHostCollectionReadRules_h

#include <TGenericClassInfo.h>
#include <TVirtualObject.h>

#include "DataFormats/Portable/interface/PortableHostCollection.h"
#include "FWCore/Utilities/interface/concatenate.h"
#include "FWCore/Utilities/interface/stringize.h"

// read function for PortableHostCollection, called for every event
template <typename T>
static void readPortableHostCollection_v1(char *target, TVirtualObject *from_buffer) {
// extract the actual types
using Collection = T;
using Layout = typename Collection::Layout;

// valid only for PortableHostCollection<T>
static_assert(std::is_same_v<Collection, PortableHostCollection<Layout>>);

// proxy for the object being read from file
struct OnFile {
Layout &layout_;
};

// address in memory of the buffer containing the object being read from file
char *address = static_cast<char *>(from_buffer->GetObject());
// offset of the "layout_" data member
static ptrdiff_t layout_offset = from_buffer->GetClass()->GetDataMemberOffset("layout_");
// reference to the Layout object being read from file
OnFile onfile = {*(Layout *)(address + layout_offset)};

// pointer to the Collection object being constructed in memory
Collection *newObj = (Collection *)target;

// move the data from the on-file layout to the newly constructed object
Collection::ROOTReadStreamer(newObj, onfile.layout_);
}

// put set_PortableHostCollection_read_rules in the ROOT namespace to let it forward declare GenerateInitInstance
namespace ROOT {

// set the read rules for PortableHostCollection<T>;
// this is called only once, when the dictionary is loaded.
template <typename T>
static bool set_PortableHostCollection_read_rules(std::string const &type) {
// forward declaration
TGenericClassInfo *GenerateInitInstance(T const *);

// build the read rules
std::vector<ROOT::Internal::TSchemaHelper> readrules(1);
ROOT::Internal::TSchemaHelper &rule = readrules[0];
rule.fTarget = "buffer_,layout_,view_";
rule.fSourceClass = type;
rule.fSource = type + "::Layout layout_;";
rule.fCode = type + "::ROOTReadStreamer(newObj, onfile.layout_)";
rule.fVersion = "[1-]";
rule.fChecksum = "";
rule.fInclude = "";
rule.fEmbed = false;
rule.fFunctionPtr = reinterpret_cast<void *>(::readPortableHostCollection_v1<T>);
rule.fAttributes = "";

// set the read rules
TGenericClassInfo *instance = GenerateInitInstance((T const *)nullptr);
instance->SetReadRules(readrules);

return true;
}
} // namespace ROOT

#define SET_PORTABLEHOSTCOLLECTION_READ_RULES(COLLECTION) \
static bool EDM_CONCATENATE(set_PortableHostCollection_read_rules_done_at_, __LINE__) [[maybe_unused]] = \
ROOT::set_PortableHostCollection_read_rules<COLLECTION>(EDM_STRINGIZE(COLLECTION))

#endif // DataFormats_Portable_interface_PortableHostCollectionReadRules_h
4 changes: 4 additions & 0 deletions DataFormats/PortableTestObjects/src/classes.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "DataFormats/Portable/interface/PortableHostCollectionReadRules.h"
#include "DataFormats/PortableTestObjects/interface/TestHostCollection.h"

SET_PORTABLEHOSTCOLLECTION_READ_RULES(portabletest::TestHostCollection);
12 changes: 0 additions & 12 deletions DataFormats/PortableTestObjects/src/classes_def.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
<lcgdict>
<class name="portabletest::TestSoA"/>
<class name="portabletest::TestSoA::View"/>

<class name="portabletest::TestHostCollection"/>
<read
sourceClass="portabletest::TestHostCollection"
targetClass="portabletest::TestHostCollection"
version="[1-]"
source="portabletest::TestSoA layout_;"
target="buffer_,layout_,view_"
embed="false">
<![CDATA[
portabletest::TestHostCollection::ROOTReadStreamer(newObj, onfile.layout_);
]]>
</read>
<class name="edm::Wrapper<portabletest::TestHostCollection>" splitLevel="0"/>
</lcgdict>

0 comments on commit 3463fd1

Please sign in to comment.