diff --git a/DataFormats/BeamSpot/plugins/BuildFile.xml b/DataFormats/BeamSpot/plugins/BuildFile.xml new file mode 100644 index 0000000000000..fb0d04138c776 --- /dev/null +++ b/DataFormats/BeamSpot/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/BeamSpot/plugins/TrivialSerialisation.cc b/DataFormats/BeamSpot/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..46ba35e9926e6 --- /dev/null +++ b/DataFormats/BeamSpot/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/BeamSpot/interface/BeamSpotHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(BeamSpotHost); diff --git a/DataFormats/EcalDigi/plugins/BuildFile.xml b/DataFormats/EcalDigi/plugins/BuildFile.xml new file mode 100644 index 0000000000000..500770828ac90 --- /dev/null +++ b/DataFormats/EcalDigi/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/EcalDigi/plugins/TrivialSerialisation.cc b/DataFormats/EcalDigi/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..b189c5ec3a5fe --- /dev/null +++ b/DataFormats/EcalDigi/plugins/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/EcalDigi/interface/EcalDigiHostCollection.h" +#include "DataFormats/EcalDigi/interface/EcalDigiPhase2HostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(EcalDigiHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(EcalDigiPhase2HostCollection); diff --git a/DataFormats/EcalRecHit/plugins/BuildFile.xml b/DataFormats/EcalRecHit/plugins/BuildFile.xml new file mode 100644 index 0000000000000..ee7c656ef4f71 --- /dev/null +++ b/DataFormats/EcalRecHit/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/EcalRecHit/plugins/TrivialSerialisation.cc b/DataFormats/EcalRecHit/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..adc2c817de023 --- /dev/null +++ b/DataFormats/EcalRecHit/plugins/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/EcalRecHit/interface/EcalRecHitHostCollection.h" +#include "DataFormats/EcalRecHit/interface/EcalUncalibratedRecHitHostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(EcalRecHitHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(EcalUncalibratedRecHitHostCollection); diff --git a/DataFormats/HGCalDigi/plugins/BuildFile.xml b/DataFormats/HGCalDigi/plugins/BuildFile.xml new file mode 100644 index 0000000000000..f22493a26579e --- /dev/null +++ b/DataFormats/HGCalDigi/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/HGCalDigi/plugins/TrivialSerialisation.cc b/DataFormats/HGCalDigi/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..7b9d0eeb8d342 --- /dev/null +++ b/DataFormats/HGCalDigi/plugins/TrivialSerialisation.cc @@ -0,0 +1,8 @@ +#include "DataFormats/HGCalDigi/interface/HGCalDigiHost.h" +#include "DataFormats/HGCalDigi/interface/HGCalECONDPacketInfoHost.h" +#include "DataFormats/HGCalDigi/interface/HGCalFEDPacketInfoHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hgcaldigi::HGCalDigiHost); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hgcaldigi::HGCalECONDPacketInfoHost); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hgcaldigi::HGCalFEDPacketInfoHost); diff --git a/DataFormats/HGCalReco/plugins/BuildFile.xml b/DataFormats/HGCalReco/plugins/BuildFile.xml new file mode 100644 index 0000000000000..1171aee28adb6 --- /dev/null +++ b/DataFormats/HGCalReco/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/HGCalReco/plugins/TrivialSerialisation.cc b/DataFormats/HGCalReco/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..c3bd30fcba8e0 --- /dev/null +++ b/DataFormats/HGCalReco/plugins/TrivialSerialisation.cc @@ -0,0 +1,10 @@ +#include "DataFormats/HGCalReco/interface/HGCalSoAClustersHostCollection.h" +#include "DataFormats/HGCalReco/interface/HGCalSoARecHitsExtraHostCollection.h" +#include "DataFormats/HGCalReco/interface/HGCalSoARecHitsHostCollection.h" +#include "DataFormats/HGCalReco/interface/MtdHostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(HGCalSoAClustersHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(HGCalSoARecHitsExtraHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(HGCalSoARecHitsHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(MtdHostCollection); diff --git a/DataFormats/HcalDigi/plugins/BuildFile.xml b/DataFormats/HcalDigi/plugins/BuildFile.xml new file mode 100644 index 0000000000000..a627d6e7e1a0a --- /dev/null +++ b/DataFormats/HcalDigi/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/HcalDigi/plugins/TrivialSerialisation.cc b/DataFormats/HcalDigi/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..22c6683ef6c24 --- /dev/null +++ b/DataFormats/HcalDigi/plugins/TrivialSerialisation.cc @@ -0,0 +1,5 @@ +#include "DataFormats/HcalDigi/interface/HcalDigiHostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hcal::Phase0DigiHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hcal::Phase1DigiHostCollection); diff --git a/DataFormats/HcalRecHit/plugins/BuildFile.xml b/DataFormats/HcalRecHit/plugins/BuildFile.xml new file mode 100644 index 0000000000000..07725848fdb30 --- /dev/null +++ b/DataFormats/HcalRecHit/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/HcalRecHit/plugins/TrivialSerialisation.cc b/DataFormats/HcalRecHit/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..b4ff719665537 --- /dev/null +++ b/DataFormats/HcalRecHit/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/HcalRecHit/interface/HcalRecHitHostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(hcal::RecHitHostCollection); diff --git a/DataFormats/ParticleFlowReco/plugins/BuildFile.xml b/DataFormats/ParticleFlowReco/plugins/BuildFile.xml new file mode 100644 index 0000000000000..e20859b77da70 --- /dev/null +++ b/DataFormats/ParticleFlowReco/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/ParticleFlowReco/plugins/TrivialSerialisation.cc b/DataFormats/ParticleFlowReco/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..b212bcc08db16 --- /dev/null +++ b/DataFormats/ParticleFlowReco/plugins/TrivialSerialisation.cc @@ -0,0 +1,13 @@ +// Include the Eigen core library before including the SoA definitions +#include + +#include "DataFormats/ParticleFlowReco/interface/CaloRecHitHostCollection.h" +#include "DataFormats/ParticleFlowReco/interface/PFClusterHostCollection.h" +#include "DataFormats/ParticleFlowReco/interface/PFRecHitFractionHostCollection.h" +#include "DataFormats/ParticleFlowReco/interface/PFRecHitHostCollection.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::CaloRecHitHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::PFClusterHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::PFRecHitFractionHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::PFRecHitHostCollection); diff --git a/DataFormats/ParticleFlowReco/src/alpaka/classes_cuda.h b/DataFormats/ParticleFlowReco/src/alpaka/classes_cuda.h index 806574361dbf1..75dc8a747a97a 100644 --- a/DataFormats/ParticleFlowReco/src/alpaka/classes_cuda.h +++ b/DataFormats/ParticleFlowReco/src/alpaka/classes_cuda.h @@ -1,4 +1,4 @@ -// Include Eigen core library before include the SoA definitions +// Include the Eigen core library before including the SoA definitions #include #include "DataFormats/Common/interface/DeviceProduct.h" diff --git a/DataFormats/ParticleFlowReco/src/alpaka/classes_rocm.h b/DataFormats/ParticleFlowReco/src/alpaka/classes_rocm.h index 806574361dbf1..75dc8a747a97a 100644 --- a/DataFormats/ParticleFlowReco/src/alpaka/classes_rocm.h +++ b/DataFormats/ParticleFlowReco/src/alpaka/classes_rocm.h @@ -1,4 +1,4 @@ -// Include Eigen core library before include the SoA definitions +// Include the Eigen core library before including the SoA definitions #include #include "DataFormats/Common/interface/DeviceProduct.h" diff --git a/DataFormats/ParticleFlowReco/src/classes_serial.cc b/DataFormats/ParticleFlowReco/src/classes_serial.cc index 18a4bc88acf51..82a8cab3cd150 100644 --- a/DataFormats/ParticleFlowReco/src/classes_serial.cc +++ b/DataFormats/ParticleFlowReco/src/classes_serial.cc @@ -1,4 +1,4 @@ -// Include Eigen core library before include the SoA definitions +// Include the Eigen core library before including the SoA definitions #include #include "DataFormats/ParticleFlowReco/interface/CaloRecHitHostCollection.h" diff --git a/DataFormats/ParticleFlowReco/src/classes_serial.h b/DataFormats/ParticleFlowReco/src/classes_serial.h index d1b0d6d93a0c1..1ab95b775a7f3 100644 --- a/DataFormats/ParticleFlowReco/src/classes_serial.h +++ b/DataFormats/ParticleFlowReco/src/classes_serial.h @@ -1,4 +1,4 @@ -// Include Eigen core library before include the SoA definitions +// Include the Eigen core library before including the SoA definitions #include #include "DataFormats/Common/interface/Wrapper.h" diff --git a/DataFormats/Portable/BuildFile.xml b/DataFormats/Portable/BuildFile.xml index ff3cadd88a5d7..462238bf544f0 100644 --- a/DataFormats/Portable/BuildFile.xml +++ b/DataFormats/Portable/BuildFile.xml @@ -1,4 +1,6 @@ - + + + diff --git a/DataFormats/Portable/interface/PortableCollection.h b/DataFormats/Portable/interface/PortableCollection.h index 8cedd9eabc7a0..182d2726c2cbb 100644 --- a/DataFormats/Portable/interface/PortableCollection.h +++ b/DataFormats/Portable/interface/PortableCollection.h @@ -1,13 +1,15 @@ #ifndef DataFormats_Portable_interface_PortableCollection_h #define DataFormats_Portable_interface_PortableCollection_h +#include + #include -#include "DataFormats/Portable/interface/PortableHostCollection.h" #include "DataFormats/Portable/interface/PortableDeviceCollection.h" -#include "HeterogeneousCore/AlpakaInterface/interface/concepts.h" +#include "DataFormats/Portable/interface/PortableHostCollection.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToDevice.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToHost.h" +#include "HeterogeneousCore/AlpakaInterface/interface/concepts.h" namespace traits { diff --git a/DataFormats/Portable/interface/PortableCollectionCommon.h b/DataFormats/Portable/interface/PortableCollectionCommon.h index 0c0e0b9fd3d13..65b7c11baeda3 100644 --- a/DataFormats/Portable/interface/PortableCollectionCommon.h +++ b/DataFormats/Portable/interface/PortableCollectionCommon.h @@ -1,9 +1,9 @@ #ifndef DataFormats_Portable_interface_PortableCollectionCommon_h #define DataFormats_Portable_interface_PortableCollectionCommon_h +#include #include #include -#include namespace portablecollection { diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index 5301b9c5022a8..0ccbb648c748d 100644 --- a/DataFormats/Portable/interface/PortableDeviceCollection.h +++ b/DataFormats/Portable/interface/PortableDeviceCollection.h @@ -1,8 +1,10 @@ #ifndef DataFormats_Portable_interface_PortableDeviceCollection_h #define DataFormats_Portable_interface_PortableDeviceCollection_h +#include #include #include +#include #include #include @@ -217,7 +219,7 @@ class PortableDeviceMultiCollection { public: PortableDeviceMultiCollection() = delete; - explicit PortableDeviceMultiCollection(edm::Uninitialized) noexcept {}; + explicit PortableDeviceMultiCollection(edm::Uninitialized) noexcept {} PortableDeviceMultiCollection(int32_t elements, TDev const& device) : buffer_{cms::alpakatools::make_device_buffer(device, Layout<>::computeDataSize(elements))}, diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index 33f908a42e11a..869598e880c59 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -1,14 +1,20 @@ #ifndef DataFormats_Portable_interface_PortableHostCollection_h #define DataFormats_Portable_interface_PortableHostCollection_h +#include #include #include +#include +#include #include +#include +#include #include #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableCollectionCommon.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" #include "HeterogeneousCore/AlpakaInterface/interface/host.h" #include "HeterogeneousCore/AlpakaInterface/interface/memory.h" @@ -27,7 +33,7 @@ class PortableHostCollection { PortableHostCollection() = delete; - explicit PortableHostCollection(edm::Uninitialized) noexcept {}; + explicit PortableHostCollection(edm::Uninitialized) noexcept {} PortableHostCollection(int32_t elements, alpaka_common::DevHost const& host) requires(!portablecollection::hasBlocksNumber) @@ -424,4 +430,65 @@ using PortableHostCollection4 = ::PortableHostMultiCollection; template using PortableHostCollection5 = ::PortableHostMultiCollection; +namespace ngt { + + // Specialize the MemoryCopyTraits for PortableHostColletion + template + struct MemoryCopyTraits> { + using value_type = PortableHostCollection; + using Properties = int32_t; + + // The properties needed to initialize a new PrortableHostCollection are just its size. + static Properties properties(value_type const& object) { return object->metadata().size(); } + + static void initialize(value_type& object, Properties const& size) { + // Replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory. + object = value_type(size, cms::alpakatools::host()); + } + + static std::vector> regions(value_type& object) { + // The whole PortableHostCollection is stored in a single contiguous memory region. + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + // The whole PortableHostCollection is stored in a single contiguous memory region. + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + + // Specialize the MemoryCopyTraits for PortableHostMultiCollection + template + struct MemoryCopyTraits> { + using value_type = PortableHostMultiCollection; + using Properties = typename PortableHostMultiCollection::SizesArray; + + // The properties needed to initialize a new PrortableHostMultiCollection are the sizes of all underlying PortableHostCollections. + static Properties properties(PortableHostMultiCollection const& object) { return object.sizes(); } + + static void initialize(PortableHostMultiCollection& object, Properties const& sizes) { + // Replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory. + object = PortableHostMultiCollection(sizes, cms::alpakatools::host()); + } + + static std::vector> regions(PortableHostMultiCollection& object) { + // The whole PortableHostMultiCollection is stored in a single contiguous memory region. + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(PortableHostMultiCollection const& object) { + // The whole PortableHostMultiCollection is stored in a single contiguous memory region. + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; +} // namespace ngt + #endif // DataFormats_Portable_interface_PortableHostCollection_h diff --git a/DataFormats/Portable/interface/PortableHostObject.h b/DataFormats/Portable/interface/PortableHostObject.h index 30cca9458c2c5..a4f5851c8815d 100644 --- a/DataFormats/Portable/interface/PortableHostObject.h +++ b/DataFormats/Portable/interface/PortableHostObject.h @@ -8,6 +8,7 @@ #include #include "DataFormats/Common/interface/Uninitialized.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" #include "HeterogeneousCore/AlpakaInterface/interface/host.h" #include "HeterogeneousCore/AlpakaInterface/interface/memory.h" @@ -115,4 +116,28 @@ class PortableHostObject { Product* product_ = nullptr; }; +// Specialize the MemoryCopyTraits for PortableHostObject +namespace ngt { + + template + struct MemoryCopyTraits> { + // This specialisation requires a initialize() method, but does not need to pass any parameters to it. + using Properties = void; + + static void initialize(PortableHostObject& object) { + // Replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory. + object = PortableHostObject(cms::alpakatools::host()); + } + + static std::vector> regions(PortableHostObject& object) { + return {{reinterpret_cast(object.data()), sizeof(T)}}; + } + + static std::vector> regions(PortableHostObject const& object) { + return {{reinterpret_cast(object.data()), sizeof(T)}}; + } + }; + +} // namespace ngt + #endif // DataFormats_Portable_interface_PortableHostObject_h diff --git a/DataFormats/Portable/interface/PortableObject.h b/DataFormats/Portable/interface/PortableObject.h index 81e82db700333..31a55e48d1d2c 100644 --- a/DataFormats/Portable/interface/PortableObject.h +++ b/DataFormats/Portable/interface/PortableObject.h @@ -5,11 +5,11 @@ #include -#include "DataFormats/Portable/interface/PortableHostObject.h" #include "DataFormats/Portable/interface/PortableDeviceObject.h" -#include "HeterogeneousCore/AlpakaInterface/interface/concepts.h" +#include "DataFormats/Portable/interface/PortableHostObject.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToDevice.h" #include "HeterogeneousCore/AlpakaInterface/interface/CopyToHost.h" +#include "HeterogeneousCore/AlpakaInterface/interface/concepts.h" namespace traits { diff --git a/DataFormats/PortableTestObjects/plugins/BuildFile.xml b/DataFormats/PortableTestObjects/plugins/BuildFile.xml new file mode 100644 index 0000000000000..bac81f07c93fc --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/PortableTestObjects/plugins/TrivialSerialisation.cc b/DataFormats/PortableTestObjects/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..874b21eb8e1a7 --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/TrivialSerialisation.cc @@ -0,0 +1,20 @@ +#include "DataFormats/PortableTestObjects/interface/ImageHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/LogitsHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/MaskHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/MultiHeadNetHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/ParticleHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/SimpleNetHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/TestHostCollection.h" +#include "DataFormats/PortableTestObjects/interface/TestHostObject.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::ImageHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::LogitsHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::MaskHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::MultiHeadNetHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::ParticleHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::SimpleNetHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::TestHostCollection); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::TestHostMultiCollection2); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::TestHostMultiCollection3); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(portabletest::TestHostObject); diff --git a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h index 287c4b3d5319e..28f09a4e60373 100644 --- a/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h +++ b/DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h @@ -6,6 +6,7 @@ #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableHostCollection.h" #include "DataFormats/SiPixelClusterSoA/interface/SiPixelClustersSoA.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" // TODO: The class is created via inheritance of the PortableCollection. @@ -15,6 +16,7 @@ class SiPixelClustersHost : public PortableHostCollection { public: SiPixelClustersHost(edm::Uninitialized) : PortableHostCollection{edm::kUninitialized} {} + // FIXME add an explicit overload for the host case template explicit SiPixelClustersHost(size_t maxModules, TQueue queue) : PortableHostCollection(maxModules + 1, queue) {} @@ -32,4 +34,41 @@ class SiPixelClustersHost : public PortableHostCollection { int32_t offsetBPIX2_h = 0; }; +namespace ngt { + + template <> + struct MemoryCopyTraits { + using value_type = SiPixelClustersHost; + + struct Properties { + int size; + uint32_t nClusters; + int32_t offsetBPIX2; + }; + + static Properties properties(value_type const& object) { + return {object->metadata().size() - 1, object.nClusters(), object.offsetBPIX2()}; + } + + static void initialize(value_type& object, Properties const& prop) { + // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + object = value_type(prop.size, cms::alpakatools::host()); + object.setNClusters(prop.nClusters, prop.offsetBPIX2); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelClusterSoA_interface_SiPixelClustersHost_h diff --git a/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml b/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml new file mode 100644 index 0000000000000..373ad21b5af1b --- /dev/null +++ b/DataFormats/SiPixelClusterSoA/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/SiPixelClusterSoA/plugins/TrivialSerialisation.cc b/DataFormats/SiPixelClusterSoA/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..6cfaa73c5ea53 --- /dev/null +++ b/DataFormats/SiPixelClusterSoA/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(SiPixelClustersHost); diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h index 1150a82046428..c7f55a885e8a3 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h @@ -9,6 +9,7 @@ #include "DataFormats/Portable/interface/PortableHostCollection.h" #include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsSoA.h" #include "DataFormats/SiPixelRawData/interface/SiPixelErrorCompact.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/SimpleVector.h" #include "HeterogeneousCore/AlpakaInterface/interface/memory.h" @@ -26,4 +27,36 @@ class SiPixelDigiErrorsHost : public PortableHostCollection + struct MemoryCopyTraits { + using value_type = SiPixelDigiErrorsHost; + struct Properties { + int maxFedWords; + }; + + static Properties properties(value_type const& object) { return {object.maxFedWords()}; } + + static void initialize(value_type& object, Properties const& props) { + // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + object = value_type(props.maxFedWords, cms::alpakatools::host()); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelDigiSoA_interface_SiPixelDigiErrorsHost_h diff --git a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h index 99b498fc5fe14..0ba4a7ba0416f 100644 --- a/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h +++ b/DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h @@ -4,6 +4,7 @@ #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableHostCollection.h" #include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigisSoA.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" // TODO: The class is created via inheritance of the PortableDeviceCollection. // This is generally discouraged, and should be done via composition. @@ -25,4 +26,38 @@ class SiPixelDigisHost : public PortableHostCollection { uint32_t nModules_h = 0; }; +// Specialize the MemoryCopyTraits for SiPixelDigisHost +namespace ngt { + + template <> + struct MemoryCopyTraits { + using value_type = SiPixelDigisHost; + struct Properties { + uint32_t nDigis; + uint32_t nModules; + }; + + static Properties properties(value_type const& object) { return {object.nDigis(), object.nModules()}; } + + static void initialize(value_type& object, Properties const& props) { + // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + object = value_type(props.nDigis, cms::alpakatools::host()); + object.setNModules(props.nModules); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_SiPixelDigiSoA_interface_SiPixelDigisHost_h diff --git a/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml b/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml new file mode 100644 index 0000000000000..30b51a75552e9 --- /dev/null +++ b/DataFormats/SiPixelDigiSoA/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/SiPixelDigiSoA/plugins/TrivialSerialisation.cc b/DataFormats/SiPixelDigiSoA/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..fd71a296b1a34 --- /dev/null +++ b/DataFormats/SiPixelDigiSoA/plugins/TrivialSerialisation.cc @@ -0,0 +1,6 @@ +#include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigiErrorsHost.h" +#include "DataFormats/SiPixelDigiSoA/interface/SiPixelDigisHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(SiPixelDigiErrorsHost); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(SiPixelDigisHost); diff --git a/DataFormats/TrackSoA/plugins/BuildFile.xml b/DataFormats/TrackSoA/plugins/BuildFile.xml new file mode 100644 index 0000000000000..e2d5a430d6ae4 --- /dev/null +++ b/DataFormats/TrackSoA/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/TrackSoA/plugins/TrivialSerialisation.cc b/DataFormats/TrackSoA/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..79e7d0f9c4c0e --- /dev/null +++ b/DataFormats/TrackSoA/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/TrackSoA/interface/TracksHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::TracksHost); diff --git a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h index 528f1a2205689..cd9998a975a55 100644 --- a/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h +++ b/DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h @@ -7,8 +7,9 @@ #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableHostCollection.h" -#include "DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsSoA.h" #include "DataFormats/SiPixelClusterSoA/interface/SiPixelClustersHost.h" +#include "DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsSoA.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" // TODO: The class is created via inheritance of the PortableCollection. @@ -25,6 +26,7 @@ namespace reco { : PortableHostMultiCollection{edm::kUninitialized} {} // Constructor which specifies only the SoA size, to be used when copying the results from the device to the host + // FIXME add an explicit overload for the host case template explicit TrackingRecHitHost(TQueue queue, uint32_t nHits, uint32_t nModules) : HitPortableCollectionHost({{int(nHits), int(nModules + 1)}}, queue) {} @@ -32,7 +34,7 @@ namespace reco { // Constructor from clusters template - explicit TrackingRecHitHost(TQueue queue, SiPixelClustersHost const &clusters) + explicit TrackingRecHitHost(TQueue queue, SiPixelClustersHost const& clusters) : HitPortableCollectionHost({{int(clusters.nClusters()), clusters.view().metadata().size()}}, queue) { auto hitsView = this->template view(); auto modsView = this->template view(); @@ -59,4 +61,37 @@ namespace reco { } // namespace reco +namespace ngt { + + template <> + struct MemoryCopyTraits { + using value_type = reco::TrackingRecHitHost; + + struct Properties { + uint32_t nHits; + uint32_t nModules; + }; + + static Properties properties(value_type const& object) { return {object.nHits(), object.nModules()}; } + + static void initialize(value_type& object, Properties const& prop) { + // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + object = value_type(cms::alpakatools::host(), prop.nHits, prop.nModules); + } + + static std::vector> regions(value_type& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(value_type const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace ngt + #endif // DataFormats_TrackingRecHitSoA_interface_TrackingRecHitsHost_h diff --git a/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml b/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml new file mode 100644 index 0000000000000..d1a16495ee94e --- /dev/null +++ b/DataFormats/TrackingRecHitSoA/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/TrackingRecHitSoA/plugins/TrivialSerialisation.cc b/DataFormats/TrackingRecHitSoA/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..85530825e35c6 --- /dev/null +++ b/DataFormats/TrackingRecHitSoA/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/TrackingRecHitSoA/interface/TrackingRecHitsHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(reco::TrackingRecHitHost); diff --git a/DataFormats/TrivialSerialisation/BuildFile.xml b/DataFormats/TrivialSerialisation/BuildFile.xml new file mode 100644 index 0000000000000..f6d0fd47836d2 --- /dev/null +++ b/DataFormats/TrivialSerialisation/BuildFile.xml @@ -0,0 +1 @@ + diff --git a/DataFormats/TrivialSerialisation/README.md b/DataFormats/TrivialSerialisation/README.md new file mode 100644 index 0000000000000..66e5d60da5f42 --- /dev/null +++ b/DataFormats/TrivialSerialisation/README.md @@ -0,0 +1,46 @@ +# `ngt::MemoryCopyTraits` + +`struct ngt::MemoryCopyTraits` should be specialised for each type that can +be safely `memcpy`ed. + +The specialisation shall have two static methods + + static std::vector> regions(T& object); + static std::vector> regions(T const& object); + +that return a `vector` of `span` describing address, size pairs. A type that +supports this interface can be copied by doing a `memcpy` of all the memory +areas from a source object to a destination object. + +A specialisation may implement the `initialize()` method, to initialize a newly +allocated object: + + static void initialize(T& object); + + +A specialisation may implement the method `properties()`, which should return +the properties (_e.g._ number of elements, or size in bytes, _etc_.) of an +existing object: + + using Properties = ...; + static Properties properties(T const& object); + +If `properties()` is defined, the value it returns should be passed as the +second argument to `initialize()`: + + static void initialize(T& object, Properties const& args);); + + +A specialisation may provide the method `finalize()`: + + static void finalize(T& object); + +If present, it should be called to restore the object invariants after a +`memcpy` operation. + + +## Specialisations of `ngt::MemoryCopyTraits` + +This package provides the specialisation of `ngt::MemoryCopyTraits` for all +arithmetic types, all `std::vector`s of arithmetic types (except +`std::vector`), and `std::string`. diff --git a/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h b/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h new file mode 100644 index 0000000000000..57f2511b72d7a --- /dev/null +++ b/DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h @@ -0,0 +1,145 @@ +#ifndef DataFormats_TrivialSerialisation_interface_MemoryCopyTraits_h +#define DataFormats_TrivialSerialisation_interface_MemoryCopyTraits_h + +#include +#include +#include +#include +#include +#include +#include + +namespace ngt { + + // This struct should be specialised for each type that can be safely memcpy'ed. + // + // The specialisation shall have two static methods + // + // static std::vector> regions(T& object); + // static std::vector> regions(T const& object); + // + // that return a vector of address, size pairs. A type that supports this + // interface can be copied by doing a memcpy of all the address, size pairs from a + // source object to a destination object. + // + // + // A specialisation may implement the method properties(), which returns the + // properties of an existing object, which can be used to initialize a newly + // allocated copy of the object via the initialize() method. + // + // using Properties = ...; + // static Properties properties(T const& object); + // + // If properties() is not implemented, the initialize() method takes a single + // argument: + // + // static void initialize(T& object); + // + // If properties() is implemented, the initialize() method should take as a + // second parameter a const reference to a Properties object: + // + // static void initialize(T& object, Properties const& args); + // + // + // A specialisation can optionally provide a static method + // + // static void finalize(T& object); + // + // If present, it should be called to restore the object invariants after a + // memcpy operation. + // + + template + struct MemoryCopyTraits; + + // Checks if the properties method is defined + template + concept HasTrivialCopyProperties = requires(T const& object) { MemoryCopyTraits::properties(object); }; + + // Get the return type of properties(...), if it exists. + template + requires HasTrivialCopyProperties + using TrivialCopyProperties = decltype(MemoryCopyTraits::properties(std::declval())); + + // Checks if the declaration of initialize(...) is consistent with the presence or absence of properties. + template + concept HasValidInitialize = + // does not have properties(...) and initialize(object) takes a single argument, or + (not HasTrivialCopyProperties and requires(T& object) { MemoryCopyTraits::initialize(object); }) or + // does have properties(...) and initialize(object, props) takes two arguments + (HasTrivialCopyProperties and + requires(T& object, TrivialCopyProperties props) { MemoryCopyTraits::initialize(object, props); }); + + // Checks for const and non const memory regions + template + concept HasRegions = requires(T& object, T const& const_object) { + MemoryCopyTraits::regions(object); + MemoryCopyTraits::regions(const_object); + }; + + // Checks if there is a valid specialisation of MemoryCopyTraits for a type T + template + concept HasMemoryCopyTraits = + // Has memory regions declared and + (HasRegions) and + // has either no initialize(...) or a valid one + (not requires { &MemoryCopyTraits::initialize; } or HasValidInitialize); + + // Checks if finalize(...) is defined + template + concept HasTrivialCopyFinalize = requires(T& object) { ngt::MemoryCopyTraits::finalize(object); }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Specialisations for various types + + // Specialisation for arithmetic types + template + requires std::is_arithmetic_v + struct MemoryCopyTraits { + static std::vector> regions(T& object) { + return {{reinterpret_cast(&object), sizeof(T)}}; + } // namespace edm + + static std::vector> regions(T const& object) { + return {{reinterpret_cast(&object), sizeof(T)}}; + } + }; + + // Specialisation for std::string + template <> + struct MemoryCopyTraits { + using Properties = std::string::size_type; + + static Properties properties(std::string const& object) { return object.size(); } + static void initialize(std::string& object, Properties const& size) { object.resize(size); } + + static std::vector> regions(std::string& object) { + return {{reinterpret_cast(object.data()), object.size() * sizeof(char)}}; + } + + static std::vector> regions(std::string const& object) { + return {{reinterpret_cast(object.data()), object.size() * sizeof(char)}}; + } + }; + + // Specialisation for vectors of arithmetic types + template + requires(std::is_arithmetic_v and not std::is_same_v) + struct MemoryCopyTraits> { + using Properties = std::vector::size_type; + + static Properties properties(std::vector const& object) { return object.size(); } + static void initialize(std::vector& object, Properties const& size) { object.resize(size); } + + static std::vector> regions(std::vector& object) { + return {{reinterpret_cast(object.data()), object.size() * sizeof(T)}}; + } + + static std::vector> regions(std::vector const& object) { + return {{reinterpret_cast(object.data()), object.size() * sizeof(T)}}; + } + }; + +} // namespace ngt + +#endif // DataFormats_TrivialSerialisation_interface_MemoryCopyTraits_h diff --git a/DataFormats/TrivialSerialisation/plugins/BuildFile.xml b/DataFormats/TrivialSerialisation/plugins/BuildFile.xml new file mode 100644 index 0000000000000..accdcdbae171f --- /dev/null +++ b/DataFormats/TrivialSerialisation/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/TrivialSerialisation/plugins/TrivialSerialisation.cc b/DataFormats/TrivialSerialisation/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..fb8f9fed24320 --- /dev/null +++ b/DataFormats/TrivialSerialisation/plugins/TrivialSerialisation.cc @@ -0,0 +1,41 @@ +#include +#include + +// Including MemoryCopyTraits.h is not necessary, but it is included explicitly +// because it contains the specialisations used below. +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +// Arithmetic types: +DEFINE_TRIVIAL_SERIALISER_PLUGIN(bool); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(char); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(signed char); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned char); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(short); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned short); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(int); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned int); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(long); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned long); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(long long); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned long long); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(float); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(double); + +// std::vector of arithmetic types, except for std::vector: +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::vector); + +// std::string: +DEFINE_TRIVIAL_SERIALISER_PLUGIN(std::string); diff --git a/DataFormats/TrivialSerialisation/test/BuildFile.xml b/DataFormats/TrivialSerialisation/test/BuildFile.xml new file mode 100644 index 0000000000000..5da9ef00be32e --- /dev/null +++ b/DataFormats/TrivialSerialisation/test/BuildFile.xml @@ -0,0 +1,4 @@ + + + + diff --git a/DataFormats/TrivialSerialisation/test/test_catch2_MemoryCopyTraits.cc b/DataFormats/TrivialSerialisation/test/test_catch2_MemoryCopyTraits.cc new file mode 100644 index 0000000000000..76efd00a4a4fc --- /dev/null +++ b/DataFormats/TrivialSerialisation/test/test_catch2_MemoryCopyTraits.cc @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" + +// Catch2 tests for MemoryCopyTraits +// +// This test file defines various types, and specializes MemoryCopyTraits for them. +// +// The following tests are performed: +// +// - MemoryCopyTraits specializations for int and double are correct. In +// particular, their memory regions are correct. +// - MemoryCopyTraits specialization for std::vector is correct. In +// particular, one can successfully initialize a vector from the properties of +// another. +// - A memcpy-able struct "S" can be copied correctly using its MemoryCopyTraits +// specialization, and the method ngt::MemoryCopyTraits::finalize() works +// correctly. +// - It can be checked that there is no MemoryCopyTraits specialization for +// std::map. +// - A type "S2" whose specialization requires initialization but has no +// properties can be copied correctly through the MemoryCopyTraits interface. +// - Several bad MemoryCopyTraits specializations can be detected correctly at +// compile time. + +// -------------------------------------------------------------------------- +// Definitions of various types, and their MemoryCopyTraits specializations + +// A trivially copyable type and its MemoryCopyTraits specialization +struct S { + S() = default; + S(std::string m, std::vector v) : msg{std::move(m)}, vec{std::move(v)} { setVecSum(); } + + std::string msg; + std::vector vec; + float vec_sum = 0.0f; // something that needs to be calculated after the copy is finished + void setVecSum() { vec_sum = std::accumulate(vec.begin(), vec.end(), 0.0f); } +}; + +template <> +struct ngt::MemoryCopyTraits { + using Properties = std::array; // {vec.size(), s.size()} + + static Properties properties(S const& object) { + return std::array{{object.vec.size(), object.msg.size()}}; + } + + static void initialize(S& object, Properties const& sizes) { + object.vec.resize(sizes.at(0)); + object.msg.resize(sizes.at(1)); + } + + static void finalize(S& object) { object.setVecSum(); } + + static std::vector> regions(S& object) { + return {{reinterpret_cast(object.msg.data()), object.msg.size()}, + {reinterpret_cast(object.vec.data()), object.vec.size() * sizeof(float)}}; + } + + static std::vector> regions(S const& object) { + return {{reinterpret_cast(object.msg.data()), object.msg.size()}, + {reinterpret_cast(object.vec.data()), object.vec.size() * sizeof(float)}}; + } +}; + +// A type that does not have properties but requires initialization, and its valid +// MemoryCopyTraits specialization +struct S2 { + S2() { vec.resize(size); } + + // vec requires initialization, but doesn't need properties because its size is + // fixed + const size_t size = 10; + std::vector vec; +}; + +template <> +struct ngt::MemoryCopyTraits { + static void initialize(S2& object) { object.vec.resize(object.size); } + + static std::vector> regions(S2& object) { + return {{reinterpret_cast(object.vec.data()), object.vec.size() * sizeof(int)}}; + } + + static std::vector> regions(S2 const& object) { + return {{reinterpret_cast(object.vec.data()), object.vec.size() * sizeof(int)}}; + } +}; + +// A bad MemoryCopyTraits specialization, that is missing regions() +struct S3 { + std::vector vec; +}; + +template <> +struct ngt::MemoryCopyTraits { + static void initialize(S3& object) { object.vec.resize(10); } +}; + +// A bad MemoryCopyTraits specialization, with a non-valid initialize() +struct S4 { + std::vector data; +}; + +template <> +struct ngt::MemoryCopyTraits { + using Properties = size_t; + + static Properties properties(S4 const& object) { return object.data.size(); } + + // initialize should take two arguments; the object and its properties (size) + static void initialize(S4& object) { object.data.resize(5); } + + static std::vector> regions(S4& object) { + return {{reinterpret_cast(object.data.data()), object.data.size() * sizeof(int)}}; + } + + static std::vector> regions(S4 const& object) { + return {{reinterpret_cast(object.data.data()), object.data.size() * sizeof(int)}}; + } +}; + +// A bad MemoryCopyTraits specialization, missing only the const regions +struct S5 { + int value; +}; + +template <> +struct ngt::MemoryCopyTraits { + static std::vector> regions(S5& object) { + return {{reinterpret_cast(&object.value), sizeof(int)}}; + } +}; + +// -------------------------------------------------------------------------- +// The tests + +TEST_CASE("test MemoryCopyTraits", "[MemoryCopyTraits]") { + SECTION("int") { + REQUIRE(ngt::HasMemoryCopyTraits); + + auto checkInt = [](int v) { + // test non-const regions + auto regions = ngt::MemoryCopyTraits::regions(v); + REQUIRE(regions.size() == 1); + REQUIRE(regions[0].size() == sizeof(int)); + REQUIRE(regions[0].data() == reinterpret_cast(&v)); + + // test const regions + const int const_v = v; + auto const_regions = ngt::MemoryCopyTraits::regions(const_v); + REQUIRE(const_regions.size() == 1); + REQUIRE(const_regions[0].size() == sizeof(int)); + REQUIRE(const_regions[0].data() == reinterpret_cast(&const_v)); + }; + + checkInt(-1); + checkInt(42); + checkInt(std::numeric_limits::max()); + checkInt(std::numeric_limits::min()); + } + + SECTION("double") { + REQUIRE(ngt::HasMemoryCopyTraits); + + auto checkDouble = [](double v) { + // test non-const regions + auto regions = ngt::MemoryCopyTraits::regions(v); + REQUIRE(regions.size() == 1); + REQUIRE(regions[0].size() == sizeof(double)); + REQUIRE(regions[0].data() == reinterpret_cast(&v)); + + // test const regions + const double const_v = v; + auto const_regions = ngt::MemoryCopyTraits::regions(const_v); + REQUIRE(const_regions.size() == 1); + REQUIRE(const_regions[0].size() == sizeof(double)); + REQUIRE(const_regions[0].data() == reinterpret_cast(&const_v)); + }; + + checkDouble(-1.0); + checkDouble(std::sqrt(2.)); + checkDouble(std::numeric_limits::max()); + checkDouble(std::numeric_limits::min()); + checkDouble(std::numeric_limits::epsilon()); + } + + SECTION("std::vector") { + using VectorType = std::vector; + + REQUIRE(ngt::HasMemoryCopyTraits); + REQUIRE(ngt::HasTrivialCopyProperties); + REQUIRE(ngt::HasValidInitialize); + + VectorType vec = {-5.5f, -3.3f, -1.1f, 4.4f, 8.8f}; + + // Test properties + auto vec_size = ngt::MemoryCopyTraits::properties(vec); + REQUIRE(vec_size == 5); + + // Test initialize + VectorType new_vec; + ngt::MemoryCopyTraits::initialize(new_vec, vec_size); + REQUIRE(new_vec.size() == 5); + + // Test non-const regions + auto regions = ngt::MemoryCopyTraits::regions(vec); + REQUIRE(regions.size() == 1); + REQUIRE(regions[0].size() == vec.size() * sizeof(float)); + REQUIRE(regions[0].data() == reinterpret_cast(vec.data())); + + // Test const regions + const VectorType const_vec = {1.1f, 2.2f, 3.3f, 4.4f, 5.5f}; + auto const_regions = ngt::MemoryCopyTraits::regions(const_vec); + REQUIRE(const_regions.size() == 1); + REQUIRE(const_regions[0].size() == const_vec.size() * sizeof(float)); + REQUIRE(const_regions[0].data() == reinterpret_cast(const_vec.data())); + } + + SECTION("memcpy-able struct") { + std::string test_msg = "hello!"; + std::vector test_vec = {-1.0f, 4.0f, 42.0f}; + float test_vec_sum = std::accumulate(test_vec.begin(), test_vec.end(), 0.0f); + + // initialize a memcpy-able struct s + REQUIRE(ngt::HasMemoryCopyTraits); + REQUIRE(ngt::HasTrivialCopyProperties); + REQUIRE(ngt::HasValidInitialize); + S s{test_msg, test_vec}; + + // initialize a clone of s + S s_clone; + ngt::MemoryCopyTraits::initialize(s_clone, ngt::MemoryCopyTraits::properties(s)); + + // Get memory regions + auto const s_regions = ngt::MemoryCopyTraits::regions(s); + auto s_clone_regions = ngt::MemoryCopyTraits::regions(s_clone); + + REQUIRE(s_regions.size() == s_clone_regions.size()); + REQUIRE(s_clone.msg.size() == s.msg.size()); + REQUIRE(s_clone.vec.size() == s.vec.size()); + + for (size_t i = 0; i < s_regions.size(); ++i) { + // check that initialize worked, i.e. enough memory in s_clone has been made available to copy s into it + REQUIRE(s_regions.at(i).size_bytes() == s_clone_regions.at(i).size_bytes()); + + // do the copy + std::memcpy(s_clone_regions.at(i).data(), s_regions.at(i).data(), s_regions.at(i).size_bytes()); + } + + //s_clone.vec_sum has not been touched yet + REQUIRE(s_clone.vec_sum == 0.0f); + + // finalize the clone, which should calculate vec_sum + ngt::MemoryCopyTraits::finalize(s_clone); + + // check that the copy worked + REQUIRE(s_clone.vec == test_vec); + REQUIRE(s_clone.msg == test_msg); + + // check that finalize worked + REQUIRE(s_clone.vec_sum == test_vec_sum); + } + + SECTION("std::map") { + using MapType = std::map; + + // there is no MemoryCopyTraits specialization for std::map (and there shouldn't be, since std::map is not trivially copyable) + static_assert(not ngt::HasMemoryCopyTraits); + } + + SECTION("A valid specialization with initialize() but without properties()") { + REQUIRE(ngt::HasMemoryCopyTraits); + REQUIRE(not ngt::HasTrivialCopyProperties); + REQUIRE(ngt::HasValidInitialize); + + S2 s2; + // fill its member vector with some data + for (size_t i = 0; i < s2.vec.size(); ++i) { + s2.vec[i] = static_cast(i * 10); + } + + S2 s2_clone; + // initialize the clone (no properties required) + ngt::MemoryCopyTraits::initialize(s2_clone); + + // get memory regions + auto const s2_regions = ngt::MemoryCopyTraits::regions(s2); + auto s2_clone_regions = ngt::MemoryCopyTraits::regions(s2_clone); + + // Only one memory region (the vector) + REQUIRE(s2_regions.size() == 1); + REQUIRE(s2_clone_regions.size() == 1); + REQUIRE(s2_regions[0].size_bytes() == s2_clone_regions[0].size_bytes()); + + // before the copy: + REQUIRE(s2_clone.vec != s2.vec); + + // do the copy + std::memcpy(s2_clone_regions[0].data(), s2_regions[0].data(), s2_regions[0].size_bytes()); + + // and now, + REQUIRE(s2_clone.vec == s2.vec); + } + + SECTION("Invalid specializations") { + // S3: Missing regions() method + static_assert(not ngt::HasMemoryCopyTraits); + static_assert(not ngt::HasRegions); + + // S4: Has properties, but the initialize() declaration takes only one + // argument. + // static_assert(not ngt::HasMemoryCopyTraits); //FIXME + static_assert(ngt::HasTrivialCopyProperties); + // static_assert(not ngt::HasValidInitialize); //FIXME + + // S5: Missing just the const regions() overload + static_assert(not ngt::HasMemoryCopyTraits); + static_assert(not ngt::HasRegions); + } +} diff --git a/DataFormats/TrivialSerialisation/test/test_catch2_main.cc b/DataFormats/TrivialSerialisation/test/test_catch2_main.cc new file mode 100644 index 0000000000000..2c344d5b8aab1 --- /dev/null +++ b/DataFormats/TrivialSerialisation/test/test_catch2_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/DataFormats/VertexSoA/plugins/BuildFile.xml b/DataFormats/VertexSoA/plugins/BuildFile.xml new file mode 100644 index 0000000000000..9967a9fbb7ed1 --- /dev/null +++ b/DataFormats/VertexSoA/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/VertexSoA/plugins/TrivialSerialisation.cc b/DataFormats/VertexSoA/plugins/TrivialSerialisation.cc new file mode 100644 index 0000000000000..92fee377f783d --- /dev/null +++ b/DataFormats/VertexSoA/plugins/TrivialSerialisation.cc @@ -0,0 +1,4 @@ +#include "DataFormats/VertexSoA/interface/ZVertexHost.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(ZVertexHost); diff --git a/HeterogeneousCore/TrivialSerialisation/BuildFile.xml b/HeterogeneousCore/TrivialSerialisation/BuildFile.xml new file mode 100644 index 0000000000000..6eeacf9dd884c --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/BuildFile.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/HeterogeneousCore/TrivialSerialisation/README.md b/HeterogeneousCore/TrivialSerialisation/README.md new file mode 100644 index 0000000000000..39b8578038dce --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/README.md @@ -0,0 +1,39 @@ +# `ngt::AnyBuffer` + +`ngt::AnyBuffer` behaves like `std::any`, with two differences: it can only be +used with trivially copyable types, and provides access to the underlying memory +buffer to allow `memcpy`ing its content. + + +# `ngt::GenericCloner` + +This `EDProducer` will clone all the event products declared in its +configuration, using either the plugin-based NGT trivial serialisation, or the +products' ROOT dictionaries. + +The products can be specified either as module labels (_e.g._ `""`) +or as branch names (_e.g._ `"___"`). + +If a module label is used, no underscore (`_`) must be present; this module will +clone all the products produced by that module, including those produced by the +Transformer functionality (such as the implicitly copied-to-host products in +case of Alpaka-based modules). +If a branch name is used, all four fields must be present, separated by +underscores; this module will clone only on the matching product(s). + +Glob expressions (`?` and `*`) are supported in module labels and within the +individual fields of branch names, similar to an `OutputModule`s "keep" +statements. +Use `"*"` to clone all products. + +For example, in the case of Alpaka-based modules running on a device, using + + eventProducts = cms.untracked.vstring("module") + +will cause "module" to run, along with automatic copy of its device products to +the host, and will attempt to clone all device and host products. +To clone only the host product, the branch can be specified explicitly with + + eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" ) + +. diff --git a/HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h b/HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h new file mode 100644 index 0000000000000..0cadcede2fc9a --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h @@ -0,0 +1,74 @@ +#ifndef HeterogeneousCore_TrivialSerialisation_interface_AnyBuffer_h +#define HeterogeneousCore_TrivialSerialisation_interface_AnyBuffer_h + +#include +#include +#include + +#include + +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" + +namespace ngt { + + class AnyBuffer { + public: + AnyBuffer() = default; + + template + AnyBuffer(T const& t) + requires(std::is_trivially_copyable_v) + : storage_(reinterpret_cast(&t), reinterpret_cast(&t) + sizeof(T)), + typeid_(&typeid(std::remove_cv_t)) {} + + template + T& cast_to() + requires(std::is_trivially_copyable_v) + { + if (empty()) { + throw edm::Exception(edm::errors::LogicError) + << "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name()) + << " from an empty AnyBuffer"; + } + if (typeid(std::remove_cv_t) != *typeid_) { + throw edm::Exception(edm::errors::LogicError) + << "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name()) + << " from an AnyBuffer holding an object of type " << edm::typeDemangle(typeid_->name()); + } + return *reinterpret_cast(storage_.data()); + } + + template + T const& cast_to() const + requires(std::is_trivially_copyable_v) + { + if (empty()) { + throw edm::Exception(edm::errors::LogicError) + << "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name()) + << " from an empty AnyBuffer"; + } + if (typeid(std::remove_cv_t) != *typeid_) { + throw edm::Exception(edm::errors::LogicError) + << "Attempt to read an object of type " << edm::typeDemangle(typeid(T).name()) + << " from an AnyBuffer holding an object of type " << edm::typeDemangle(typeid_->name()); + } + return *reinterpret_cast(storage_.data()); + } + + bool empty() const { return typeid_ == nullptr; } + + std::byte* data() { return storage_.data(); } + + std::byte const* data() const { return storage_.data(); } + + size_t size_bytes() const { return storage_.size(); } + + private: + boost::container::small_vector storage_; // small_vector size chosen to fit AnyBuffer in 64 bytes + std::type_info const* typeid_ = nullptr; + }; + +} // namespace ngt + +#endif // HeterogeneousCore_TrivialSerialisation_interface_AnyBuffer_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Reader.h b/HeterogeneousCore/TrivialSerialisation/interface/Reader.h new file mode 100644 index 0000000000000..d358dd9f560bf --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/Reader.h @@ -0,0 +1,52 @@ +#ifndef TrivialSerialisation_Common_interface_Reader_h +#define TrivialSerialisation_Common_interface_Reader_h + +#include +#include +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" + +namespace ngt { + + template + class Reader : public ReaderBase { + static_assert(ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); + + public: + using WrapperType = edm::Wrapper; + + Reader(WrapperType const& wrapper) : ReaderBase(&wrapper) {} + + ngt::AnyBuffer parameters() const override { + if constexpr (not ngt::HasTrivialCopyProperties) { + // if ngt::MemoryCopyTraits::properties(...) is not declared, do not call it. + return {}; + } else { + // if ngt::MemoryCopyTraits::properties(...) is declared, call it and wrap the result in an ngt::AnyBuffer + return ngt::AnyBuffer(ngt::MemoryCopyTraits::properties(object())); + } + } + + std::vector> regions() const override { + static_assert(ngt::HasRegions); + return ngt::MemoryCopyTraits::regions(object()); + } + + private: + const T& object() const { + const WrapperType& w = static_cast(*ptr_); + if (not w.isPresent()) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + return w.bareProduct(); + } + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_Reader_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h b/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h new file mode 100644 index 0000000000000..f3c871bdbd969 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h @@ -0,0 +1,28 @@ +#ifndef TrivialSerialisation_Common_interface_ReaderBase_h +#define TrivialSerialisation_Common_interface_ReaderBase_h + +#include +#include +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" + +namespace ngt { + + class ReaderBase { + public: + ReaderBase(const edm::WrapperBase* ptr) : ptr_(ptr) {} + + virtual ngt::AnyBuffer parameters() const = 0; + virtual std::vector> regions() const = 0; + + virtual ~ReaderBase() = default; + + protected: + const edm::WrapperBase* ptr_; + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_ReaderBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h b/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h new file mode 100644 index 0000000000000..06049244ec292 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h @@ -0,0 +1,29 @@ +#ifndef TrivialSerialisation_Common_interface_Serialiser_h +#define TrivialSerialisation_Common_interface_Serialiser_h + +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Reader.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Writer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" + +namespace ngt { + + template + class Serialiser : public SerialiserBase { + public: + std::unique_ptr writer() override { return std::make_unique>(); } + + std::unique_ptr reader(edm::WrapperBase const& wrapper) override { + edm::Wrapper const& w = dynamic_cast const&>(wrapper); + return std::make_unique>(w); + } + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_Serialiser_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h new file mode 100644 index 0000000000000..35fb99273e8a5 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h @@ -0,0 +1,20 @@ +#ifndef TrivialSerialisation_Common_interface_SerialiserBase_h +#define TrivialSerialisation_Common_interface_SerialiserBase_h + +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" + +namespace ngt { + class SerialiserBase { + public: + virtual std::unique_ptr writer() = 0; + virtual std::unique_ptr reader(const edm::WrapperBase& wrapper) = 0; + + virtual ~SerialiserBase() = default; + }; +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_SerialiserBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h new file mode 100644 index 0000000000000..e927bfe643c94 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h @@ -0,0 +1,16 @@ +#ifndef TrivialSerialisation_src_SerialiserFactory_h +#define TrivialSerialisation_src_SerialiserFactory_h + +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/Serialiser.h" +#include "FWCore/PluginManager/interface/PluginFactory.h" + +namespace ngt { + using SerialiserFactory = edmplugin::PluginFactory; +} + +// Helper macro to define Serialiser plugins +#define DEFINE_TRIVIAL_SERIALISER_PLUGIN(TYPE) \ + DEFINE_EDM_PLUGIN(ngt::SerialiserFactory, ngt::Serialiser, typeid(TYPE).name()) + +#endif // TrivialSerialisation_src_SerialiserFactory_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/Writer.h b/HeterogeneousCore/TrivialSerialisation/interface/Writer.h new file mode 100644 index 0000000000000..d4bfc4b7cad22 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/Writer.h @@ -0,0 +1,73 @@ +#ifndef TrivialSerialisation_Common_interface_Writer_h +#define TrivialSerialisation_Common_interface_Writer_h + +#include +#include +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" + +namespace ngt { + + template + class Writer : public WriterBase { + static_assert(ngt::HasMemoryCopyTraits, "No specialization of MemoryCopyTraits found for type T"); + + public: + using WrapperType = edm::Wrapper; + + Writer() : WriterBase() { + // See edm::Wrapper::construct_(). + if constexpr (requires { T(); }) { + ptr_ = std::make_unique>(edm::WrapperBase::Emplace{}); + } else { + ptr_ = std::make_unique>(edm::WrapperBase::Emplace{}, edm::kUninitialized); + } + } + + ngt::AnyBuffer uninitialized_parameters() const override { + if constexpr (not ngt::HasTrivialCopyProperties) { + // if ngt::MemoryCopyTraits::properties(...) is not declared, do not call it. + return {}; + } else { + // if ngt::MemoryCopyTraits::properties(...) is declared, call it and wrap the result in an ngt::AnyBuffer + return ngt::AnyBuffer(ngt::MemoryCopyTraits::properties(object())); + } + } + + void initialize(ngt::AnyBuffer const& args) override { + if constexpr (not ngt::HasValidInitialize) { + // If there is no valid initialize(), this shouldn't be present. + static_assert(not ngt::HasTrivialCopyProperties); + } else if constexpr (not ngt::HasTrivialCopyProperties) { + // If T has no TrivialCopyProperties, call initialize() without any additional arguments. + ngt::MemoryCopyTraits::initialize(object()); + } else { + // If T has TrivialCopyProperties, cast args to Properties and pass it as an additional argument to initialize(). + ngt::MemoryCopyTraits::initialize(object(), args.cast_to>()); + } + } + + std::vector> regions() override { + static_assert(ngt::HasRegions); + return ngt::MemoryCopyTraits::regions(object()); + } + + void finalize() override { + if constexpr (ngt::HasTrivialCopyFinalize) { + ngt::MemoryCopyTraits::finalize(object()); + } + } + + private: + const T& object() const { return static_cast(ptr_.get())->bareProduct(); } + + T& object() { return static_cast(ptr_.get())->bareProduct(); } + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_Writer_h diff --git a/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h b/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h new file mode 100644 index 0000000000000..6fc1f5f12c865 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h @@ -0,0 +1,31 @@ +#ifndef TrivialSerialisation_Common_interface_WriterBase_h +#define TrivialSerialisation_Common_interface_WriterBase_h + +#include +#include +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/AnyBuffer.h" + +namespace ngt { + + class WriterBase { + public: + WriterBase() = default; + virtual ~WriterBase() = default; + + virtual void initialize(ngt::AnyBuffer const& args) = 0; + virtual ngt::AnyBuffer uninitialized_parameters() const = 0; + virtual std::vector> regions() = 0; + virtual void finalize() = 0; + + std::unique_ptr get() { return std::move(ptr_); } + + protected: + std::unique_ptr ptr_; + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_WriterBase_h diff --git a/HeterogeneousCore/TrivialSerialisation/plugins/BuildFile.xml b/HeterogeneousCore/TrivialSerialisation/plugins/BuildFile.xml new file mode 100644 index 0000000000000..014e295e3082f --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/plugins/BuildFile.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/HeterogeneousCore/TrivialSerialisation/plugins/GenericCloner.cc b/HeterogeneousCore/TrivialSerialisation/plugins/GenericCloner.cc new file mode 100644 index 0000000000000..2d880a0f1c676 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/plugins/GenericCloner.cc @@ -0,0 +1,236 @@ +/* + * This EDProducer will clone all the event products declared in its + * configuration, using either the plugin-based NGT trivial serialisation, or + * the products' ROOT dictionaries. + * + * The products can be specified either as module labels (e.g. "") + * or as branch names (e.g. "___"). + * + * If a module label is used, no underscore (_) must be present; this module + * will clone all the products produced by that module, including those produced + * by the Transformer functionality (such as the implicitly copied-to-host + * products in case of Alpaka-based modules). + * If a branch name is used, all four fields must be present, separated by + * underscores; this module will clone only on the matching product(s). + * + * Glob expressions (? and *) are supported in module labels and within the + * individual fields of branch names, similar to an OutputModule's "keep" + * statements. + * Use "*" to clone all products. + * + * For example, in the case of Alpaka-based modules running on a device, using + * + * eventProducts = cms.untracked.vstring("module") + * + * will cause "module" to run, along with automatic copy of its device products + * to the host, and will attempt to clone all device and host products. + * To clone only the host product, the branch can be specified explicitly with + * + * eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" ) + * + * . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "DataFormats/Provenance/interface/ProductDescription.h" +#include "DataFormats/Provenance/interface/ProductNamePattern.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/WrapperBaseHandle.h" +#include "FWCore/Framework/interface/WrapperBaseOrphanHandle.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/MessageLogger/interface/MessageLogger.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterDescriptionNode.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Reflection/interface/ObjectWithDict.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/ReaderBase.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/WriterBase.h" + +namespace ngt { + + class GenericCloner : public edm::global::EDProducer<> { + public: + explicit GenericCloner(edm::ParameterSet const&); + ~GenericCloner() override = default; + + void produce(edm::StreamID, edm::Event&, edm::EventSetup const&) const override; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + + private: + struct Entry { + edm::TypeWithDict objectType_; + edm::TypeWithDict wrappedType_; + edm::EDGetToken getToken_; + edm::EDPutToken putToken_; + }; + + std::vector eventPatterns_; + std::vector eventProducts_; + std::string label_; + bool verbose_; + }; + + GenericCloner::GenericCloner(edm::ParameterSet const& config) + : eventPatterns_(edm::productPatterns(config.getParameter>("eventProducts"))), + label_(config.getParameter("@module_label")), + verbose_(config.getUntrackedParameter("verbose")) { + eventProducts_.reserve(eventPatterns_.size()); + + callWhenNewProductsRegistered([this](edm::ProductDescription const& product) { + static const std::string_view kPathStatus("edm::PathStatus"); + static const std::string_view kEndPathStatus("edm::EndPathStatus"); + + switch (product.branchType()) { + case edm::InEvent: + // Do not clone edm::PathStatus and edm::EndPathStatus objects. + if (product.className() == kPathStatus or product.className() == kEndPathStatus) { + return; + } + for (auto& pattern : eventPatterns_) { + if (pattern.match(product)) { + // Check that the product is not transient + if (product.transient()) { + edm::LogWarning("GenericCloner") << "Event product " << product.branchName() << " of type '" + << product.unwrappedType() << "' is transient, will not be cloned."; + break; + } + if (verbose_) { + edm::LogInfo("GenericCloner") << "will clone Event product " << product.branchName() << " of type '" + << product.unwrappedType() << "'"; + } + Entry entry; + entry.objectType_ = product.unwrappedType(); + entry.wrappedType_ = product.wrappedType(); + // Move this to EDConsumerBase::consumes() ? + entry.getToken_ = this->consumes( + edm::TypeToGet{product.unwrappedTypeID(), edm::PRODUCT_TYPE}, + edm::InputTag{product.moduleLabel(), product.productInstanceName(), product.processName()}); + entry.putToken_ = this->produces(product.unwrappedTypeID(), product.productInstanceName()); + eventProducts_.emplace_back(std::move(entry)); + break; + } + } + break; + + case edm::InLumi: + case edm::InRun: + case edm::InProcess: + // Lumi, run and process products are not supported. + break; + + default: + throw edm::Exception(edm::errors::LogicError) + << "Unexpected product type " << product.branchType() << "\nPlease contact a Framework developer."; + } + }); + } + + void GenericCloner::produce(edm::StreamID /*unused*/, edm::Event& event, edm::EventSetup const& /*unused*/) const { + for (auto& product : eventProducts_) { + // Get the product from the Event, as a const WrapperBase pointer. + edm::Handle handle(product.objectType_.typeInfo()); + event.getByToken(product.getToken_, handle); + const edm::WrapperBase* wrapper = handle.product(); + + // Get for a Serialiser for this object from the plugin factory, if it is available for this type. + std::unique_ptr serialiser{ + ngt::SerialiserFactory::get()->tryToCreate(product.objectType_.typeInfo().name())}; + + if (serialiser) { + edm::LogInfo("GenericCloner") << "Found a Serialiser for type '" + << edm::typeDemangle(product.objectType_.typeInfo().name()) << "'."; + + // If the type has a Serialiser, initialise the corresponding Reader and Writer. + auto reader = serialiser->reader(*wrapper); + auto writer = serialiser->writer(); + + // Initialise the clone, if the type requires it. + writer->initialize(reader->parameters()); + + // Copy the source regions to the target. + auto targets = writer->regions(); + auto sources = reader->regions(); + + assert(sources.size() == targets.size()); + for (size_t i = 0; i < sources.size(); ++i) { + assert(sources[i].data() != nullptr); + assert(targets[i].data() != nullptr); + assert(targets[i].size_bytes() == sources[i].size_bytes()); + std::memcpy(targets[i].data(), sources[i].data(), sources[i].size_bytes()); + } + + // Finalize the clone after the trivialCopy, if the type requires it. + writer->finalize(); + + // Move the clone into the Event + event.put(product.putToken_, writer->get()); + } else { + edm::LogInfo("GenericCloner") << "No Serialiser for type '" + << edm::typeDemangle(product.objectType_.typeInfo().name()) + << "', falling back to the ROOT serialization."; + + // Use ROOT-based serialisation and deserialisation to clone the wrapped object. + std::unique_ptr clone(static_cast(product.wrappedType_.getClass()->New())); + + // Write the wrapper into a TBuffer. + TBufferFile buffer(TBuffer::kWrite); + product.wrappedType_.getClass()->Streamer(const_cast(wrapper), buffer); + + // Read back a copy of the product from the TBuffer. + buffer.SetReadMode(); + buffer.SetBufferOffset(0); + product.wrappedType_.getClass()->Streamer(clone.get(), buffer); + + // Move the wrapped clone into the Event. + event.put(product.putToken_, std::move(clone)); + } + } + } + + void GenericCloner::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + descriptions.setComment( + R"(This EDProducer will clone all the event products declared by its configuration, using their ROOT dictionaries. + +The products can be specified either as module labels (e.g. "") or as branch names (e.g. "___"). +If a module label is used, no underscore ("_") must be present; this module will clone all the products produced by that module, including those produced by the Transformer functionality (such as the implicitly copied-to-host products in case of Alpaka-based modules). +If a branch name is used, all four fields must be present, separated by underscores; this module will clone only on the matching product(s). + +Glob expressions ("?" and "*") are supported in module labels and within the individual fields of branch names, similar to an OutputModule's "keep" statements. +Use "*" to clone all products. + +For example, in the case of Alpaka-based modules running on a device, using + + eventProducts = cms.untracked.vstring( "module" ) + +will cause "module" to run, along with automatic copy of its device products to the host, and will attempt to clone all device and host products. +To clone only the host product, the branch can be specified explicitly with + + eventProducts = cms.untracked.vstring( "HostProductType_module_*_*" ) + +.)"); + + edm::ParameterSetDescription desc; + desc.add>("eventProducts", {}) + ->setComment("List of modules or branches whose event products will be cloned."); + desc.addUntracked("verbose", false) + ->setComment("Print the branch names of the products that will be cloned."); + descriptions.addWithDefaultLabel(desc); + } + +} // namespace ngt + +#include "FWCore/Framework/interface/MakerMacros.h" +DEFINE_FWK_MODULE(ngt::GenericCloner); diff --git a/HeterogeneousCore/TrivialSerialisation/src/SerialiserFactory.cc b/HeterogeneousCore/TrivialSerialisation/src/SerialiserFactory.cc new file mode 100644 index 0000000000000..1e7d6e9f623d0 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/src/SerialiserFactory.cc @@ -0,0 +1,3 @@ +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +EDM_REGISTER_PLUGINFACTORY(ngt::SerialiserFactory, "SerialiserFactory"); diff --git a/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml b/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml new file mode 100644 index 0000000000000..fb89e209b4425 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/BuildFile.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/HeterogeneousCore/TrivialSerialisation/test/testGenericClonerHost_cfg.py b/HeterogeneousCore/TrivialSerialisation/test/testGenericClonerHost_cfg.py new file mode 100644 index 0000000000000..752dc882ab5c4 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/testGenericClonerHost_cfg.py @@ -0,0 +1,50 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("TEST") + +process.load("FWCore.MessageService.MessageLogger_cfi") +process.MessageLogger.cerr.INFO.limit = 10000000 + +process.options.numberOfThreads = 1 +process.options.numberOfStreams = 1 + +process.source = cms.Source("EmptySource") +process.maxEvents.input = 10 + +#produce, clone and validate a portable object, a portable collection, and some portable multicollections +process.load("Configuration.StandardSequences.Accelerators_cff") +process.load("HeterogeneousCore.AlpakaCore.ProcessAcceleratorAlpaka_cfi") + +process.producePortableObjects = cms.EDProducer("TestAlpakaProducer@alpaka", + size = cms.int32(42), + size2 = cms.int32(33), + size3 = cms.int32(61), + alpaka = cms.untracked.PSet( + # "serial_sync", "cuda_async", or "rocm_async" + backend = cms.untracked.string("") + ) +) + +process.clonePortableObjects = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("producePortableObjects"), + verbose = cms.untracked.bool(True) +) + +process.validatePortableCollections = cms.EDAnalyzer("TestAlpakaAnalyzer", + source = cms.InputTag("clonePortableObjects") +) + +process.validatePortableObject = cms.EDAnalyzer("TestAlpakaObjectAnalyzer", + source = cms.InputTag("clonePortableObjects") +) + +process.taskSoA = cms.Task( + process.producePortableObjects, + process.clonePortableObjects +) + +process.pathSoA = cms.Path( + process.validatePortableCollections + + process.validatePortableObject, + process.taskSoA +) diff --git a/HeterogeneousCore/TrivialSerialisation/test/testGenericCloner_cfg.py b/HeterogeneousCore/TrivialSerialisation/test/testGenericCloner_cfg.py new file mode 100644 index 0000000000000..fe77249cbbc69 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/testGenericCloner_cfg.py @@ -0,0 +1,110 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("TEST") + +process.load("FWCore.MessageService.MessageLogger_cfi") +process.MessageLogger.cerr.INFO.limit = 10000000 + +process.options.numberOfThreads = 1 +process.options.numberOfStreams = 1 + +process.source = cms.Source("EmptySource") +process.maxEvents.input = 10 + +# produce, clone and validate products of type int +process.produceInt = cms.EDProducer("edmtest::GlobalIntProducer", + value = cms.int32(42) +) + +process.cloneInt = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("produceInt"), + verbose = cms.untracked.bool(True) +) + +process.validateInt = cms.EDAnalyzer("edmtest::GlobalIntAnalyzer", + source = cms.InputTag("cloneInt"), + expected = cms.int32(42) +) + +process.taskInt = cms.Task( + process.produceInt, + process.cloneInt +) + +process.pathInt = cms.Path( + process.validateInt, + process.taskInt +) + +# produce, clone and validate products of type std::string +process.produceString = cms.EDProducer("edmtest::GlobalStringProducer", + value = cms.string("Hello world") +) + +process.cloneString = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("produceString"), + verbose = cms.untracked.bool(True) +) + +process.validateString = cms.EDAnalyzer("edmtest::GlobalStringAnalyzer", + source = cms.InputTag("cloneString"), + expected = cms.string("Hello world") +) + +process.taskString = cms.Task( + process.produceString, + process.cloneString +) + +process.pathString = cms.Path( + process.validateString, + process.taskString +) + +# produce, clone and validate products of type edm::EventID +process.eventIds = cms.EDProducer("edmtest::EventIDProducer") + +process.cloneIdsByLabel = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("eventIds"), + verbose = cms.untracked.bool(True) +) + +process.cloneIdsByBranch = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("*_eventIds__TEST"), + verbose = cms.untracked.bool(True) +) + +process.validateIdsByLabel = cms.EDAnalyzer("edmtest::EventIDValidator", + source = cms.untracked.InputTag('cloneIdsByLabel') +) + +process.validateIdsByBranch = cms.EDAnalyzer("edmtest::EventIDValidator", + source = cms.untracked.InputTag('cloneIdsByBranch') +) + +process.taskIds = cms.Task( + process.eventIds, + process.cloneIdsByLabel, + process.cloneIdsByBranch +) + +process.pathIds = cms.Path( + process.validateIdsByLabel + + process.validateIdsByBranch, + process.taskIds +) + +# will not clone a transient product +process.produceTransient = cms.EDProducer("TransientIntProducer", + ivalue = cms.int32(22) +) + +process.cloneTransient = cms.EDProducer("ngt::GenericCloner", + eventProducts = cms.vstring("produceTransient"), + verbose = cms.untracked.bool(True) +) + +process.pathTransient = cms.Path( + process.produceTransient + + process.cloneTransient +) diff --git a/HeterogeneousCore/TrivialSerialisation/test/test_catch2_SerialisationPluginFactory.cc b/HeterogeneousCore/TrivialSerialisation/test/test_catch2_SerialisationPluginFactory.cc new file mode 100644 index 0000000000000..246db1dc1b3fd --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/test_catch2_SerialisationPluginFactory.cc @@ -0,0 +1,80 @@ +#include +#include + +#include + +#include "DataFormats/Common/interface/Wrapper.h" +#include "DataFormats/TrivialSerialisation/interface/MemoryCopyTraits.h" +#include "FWCore/PluginManager/interface/PluginManager.h" +#include "FWCore/PluginManager/interface/standard.h" +#include "HeterogeneousCore/TrivialSerialisation/interface/SerialiserFactory.h" + +/* +* This test demonstrates how TrivialSerialiser can be utilized to copy supported types. +*/ + +TEST_CASE("Test MemoryCopyTraits", "[MemoryCopyTraits]") { + // initialize the edmplugin::PluginManager + edmplugin::PluginManager::configure(edmplugin::standard::config()); + + SECTION("std::vector") { + const int size = 10; + + // Create a vector. + std::vector vec(size); + + // Initialize the vector with some data. + for (int i = 0; i < size; i++) { + vec[i] = static_cast(i); + } + + // Wrap the vector. + edm::Wrapper> wrapper(std::make_unique>(std::move(vec))); + + // Now cast the wrapper to edm::WrapperBase, hiding the underlying collection type. + edm::WrapperBase const& wrapperbase = wrapper; + + // Check that the MemoryCopyTraits are specialised for the type "std::vector". + static_assert(ngt::HasMemoryCopyTraits>); + + // Get the Serialiser plugin for the type "std::vector". + std::string typeName = typeid(std::vector).name(); + std::unique_ptr serialiser = ngt::SerialiserFactory::get()->tryToCreate(typeName); + if (serialiser) { + INFO("A serialiser plugin has been found for type " << typeName << "."); + } else { + INFO("No serialiser plugin found for type " << typeName << "."); + } + REQUIRE(serialiser); + + // Initialize the reader and writer. + auto reader = serialiser->reader(wrapperbase); + auto writer = serialiser->writer(); + + // Initialize the clone with properties from the original. + writer->initialize(reader->parameters()); + + // Get the memory regions. + auto targets = writer->regions(); + auto sources = reader->regions(); + + // Check that reader and writer have the same number of memory regions. In the case of a PortableCollection this should be equal to one. + REQUIRE(sources.size() == targets.size()); + + // Copy each region from the source to the clone. + for (size_t i = 0; i < sources.size(); ++i) { + REQUIRE(sources[i].data() != nullptr); + REQUIRE(targets[i].data() != nullptr); + REQUIRE(targets[i].size_bytes() == sources[i].size_bytes()); + std::memcpy(targets[i].data(), sources[i].data(), sources[i].size_bytes()); + } + + // Check that the copy succeeded. + std::unique_ptr clonebase = writer->get(); + edm::Wrapper>* cloneptr = dynamic_cast>*>(clonebase.get()); + const std::vector& clone = cloneptr->bareProduct(); + for (int i = 0; i < size; i++) { + REQUIRE(clone[i] == static_cast(i)); + } + } +} diff --git a/HeterogeneousCore/TrivialSerialisation/test/test_catch2_main.cc b/HeterogeneousCore/TrivialSerialisation/test/test_catch2_main.cc new file mode 100644 index 0000000000000..2c344d5b8aab1 --- /dev/null +++ b/HeterogeneousCore/TrivialSerialisation/test/test_catch2_main.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include