diff --git a/DataFormats/Portable/BuildFile.xml b/DataFormats/Portable/BuildFile.xml
index 5255e95fcaf10..f5ec5afe436d6 100644
--- a/DataFormats/Portable/BuildFile.xml
+++ b/DataFormats/Portable/BuildFile.xml
@@ -1,2 +1,4 @@
+
+
diff --git a/DataFormats/Portable/README.md b/DataFormats/Portable/README.md
index 167039a8fdebe..3c92bdf04bd91 100644
--- a/DataFormats/Portable/README.md
+++ b/DataFormats/Portable/README.md
@@ -4,8 +4,37 @@
`PortableHostCollection` 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
+
+
+```
+
+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` 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.
diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h
index 0565bc9550a5b..8b098688455e8 100644
--- a/DataFormats/Portable/interface/PortableHostCollection.h
+++ b/DataFormats/Portable/interface/PortableHostCollection.h
@@ -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();
}
diff --git a/DataFormats/Portable/interface/PortableHostCollectionReadRules.h b/DataFormats/Portable/interface/PortableHostCollectionReadRules.h
new file mode 100644
index 0000000000000..e207665b757e2
--- /dev/null
+++ b/DataFormats/Portable/interface/PortableHostCollectionReadRules.h
@@ -0,0 +1,76 @@
+#ifndef DataFormats_Portable_interface_PortableHostCollectionReadRules_h
+#define DataFormats_Portable_interface_PortableHostCollectionReadRules_h
+
+#include
+#include
+
+#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
+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
+ static_assert(std::is_same_v>);
+
+ // 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(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;
+ // this is called only once, when the dictionary is loaded.
+ template
+ static bool set_PortableHostCollection_read_rules(std::string const &type) {
+ // forward declaration
+ TGenericClassInfo *GenerateInitInstance(T const *);
+
+ // build the read rules
+ std::vector 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(::readPortableHostCollection_v1);
+ 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(EDM_STRINGIZE(COLLECTION))
+
+#endif // DataFormats_Portable_interface_PortableHostCollectionReadRules_h
diff --git a/DataFormats/PortableTestObjects/src/classes.cc b/DataFormats/PortableTestObjects/src/classes.cc
new file mode 100644
index 0000000000000..16c2d6fc6a022
--- /dev/null
+++ b/DataFormats/PortableTestObjects/src/classes.cc
@@ -0,0 +1,4 @@
+#include "DataFormats/Portable/interface/PortableHostCollectionReadRules.h"
+#include "DataFormats/PortableTestObjects/interface/TestHostCollection.h"
+
+SET_PORTABLEHOSTCOLLECTION_READ_RULES(portabletest::TestHostCollection);
diff --git a/DataFormats/PortableTestObjects/src/classes_def.xml b/DataFormats/PortableTestObjects/src/classes_def.xml
index 3470c65a70c23..fa32d49ab8556 100644
--- a/DataFormats/PortableTestObjects/src/classes_def.xml
+++ b/DataFormats/PortableTestObjects/src/classes_def.xml
@@ -1,18 +1,6 @@
-
-
-
-