diff --git a/DataFormats/Common/interface/AnyBuffer.h b/DataFormats/Common/interface/AnyBuffer.h new file mode 100644 index 0000000000000..a1c8c26016eb2 --- /dev/null +++ b/DataFormats/Common/interface/AnyBuffer.h @@ -0,0 +1,74 @@ +#ifndef DataFormats_Common_interface_AnyBuffer_h +#define DataFormats_Common_interface_AnyBuffer_h + +#include +#include +#include + +#include + +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" + +namespace edm { + + 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_; // arbitrary small vector size to fit AnyBuffer in 64 bytes + std::type_info const* typeid_ = nullptr; + }; + +} // namespace edm + +#endif // DataFormats_Common_interface_AnyBuffer_h diff --git a/DataFormats/Common/interface/TrivialCopyTraits.h b/DataFormats/Common/interface/TrivialCopyTraits.h new file mode 100644 index 0000000000000..2d081ea398bcb --- /dev/null +++ b/DataFormats/Common/interface/TrivialCopyTraits.h @@ -0,0 +1,142 @@ +#ifndef Dataformats_Common_interface_TrivialCopyTraits_h +#define Dataformats_Common_interface_TrivialCopyTraits_h + +#include +#include +#include +#include +#include + +namespace edm { + + // 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 TrivialCopyTraits; + + // Checks if the properties method is defined + template + concept HasTrivialCopyProperties = requires(T const& object) { TrivialCopyTraits::properties(object); }; + + // Get the return type of properties(...), if it exists. + template + requires HasTrivialCopyProperties + using TrivialCopyProperties = decltype(TrivialCopyTraits::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 + (not HasTrivialCopyProperties && requires(T& object) { TrivialCopyTraits::initialize(object); }) || + // or does have properties(...) and initialize(object, props) takes two arguments + (HasTrivialCopyProperties && + requires(T& object, TrivialCopyProperties props) { TrivialCopyTraits::initialize(object, props); }); + + // Checks for const and non const memory regions + template + concept HasRegions = requires(T& object, T const& const_object) { + TrivialCopyTraits::regions(object); + TrivialCopyTraits::regions(const_object); + }; + + // Checks if there is a valid specialisation of TrivialCopyTraits for a type T + template + concept HasTrivialCopyTraits = + // Has memory regions declared + (HasRegions) && + // and has either no initialize(...) or a valid one + (not requires { &TrivialCopyTraits::initialize; } || HasValidInitialize); + + // Checks if finalize(...) is defined + template + concept HasTrivialCopyFinalize = requires(T& object) { edm::TrivialCopyTraits::finalize(object); }; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Specialisations for various types + + // Specialisation for arithmetic types + template + requires std::is_arithmetic_v + struct TrivialCopyTraits { + static std::vector> regions(T& object) { + return {{reinterpret_cast(&object), sizeof(T)}}; + } + + static std::vector> regions(T const& object) { + return {{reinterpret_cast(&object), sizeof(T)}}; + } + }; + + // Specialisation for std::string + template <> + struct TrivialCopyTraits { + 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 TrivialCopyTraits> { + 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 edm + +#endif // Dataformats_Common_interface_TrivialCopyTraits_h diff --git a/DataFormats/Common/interface/Wrapper.h b/DataFormats/Common/interface/Wrapper.h index 8dc9cb09995ae..3200a672d072a 100644 --- a/DataFormats/Common/interface/Wrapper.h +++ b/DataFormats/Common/interface/Wrapper.h @@ -17,7 +17,6 @@ Wrapper: A template wrapper around EDProducts to hold the product ID. #include #include #include -#include #include namespace edm { @@ -38,6 +37,9 @@ namespace edm { T const* operator->() const { return product(); } T& bareProduct() { return obj; } + T const& bareProduct() const { return obj; } + + void markAsPresent() { present = true; } //these are used by FWLite static std::type_info const& productTypeInfo() { return typeid(T); } @@ -176,6 +178,7 @@ namespace edm { } }; } // namespace soa + template inline std::shared_ptr Wrapper::tableExaminer_() const { return soa::MakeTableExaminer::make(&obj); @@ -185,4 +188,4 @@ namespace edm { #include "DataFormats/Common/interface/WrapperView.icc" -#endif +#endif // DataFormats_Common_Wrapper_h diff --git a/DataFormats/Common/test/test_catch2_TrivialCopyTraits.cpp b/DataFormats/Common/test/test_catch2_TrivialCopyTraits.cpp new file mode 100644 index 0000000000000..6494698c2d198 --- /dev/null +++ b/DataFormats/Common/test/test_catch2_TrivialCopyTraits.cpp @@ -0,0 +1,329 @@ +#include + +#include "DataFormats/Common/interface/TrivialCopyTraits.h" + +#include +#include +#include +#include +#include +#include + +// Catch2 tests for TrivialCopyTraits +// +// This test file defines various types, and specializes TrivialCopyTraits for them. +// +// The following tests are performed: +// +// - TrivialCopyTraits specializations for int and double are correct. In +// particular, their memory regions are correct. +// - TrivialCopyTraits 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 TrivialCopyTraits +// specialization, and the method edm::TrivialCopyTraits::finalize() works +// correctly. +// - It can be checked that there is no TrivialCopyTraits specialization for +// std::map. +// - A type "S2" whose specialization requires initialization but has no +// properties can be copied correctly through the TrivialCopyTraits interface. +// - Several bad TrivialCopyTraits specializations can be detected correctly at +// compile time. +// + +// -------------------------------------------------------------------------- +// Definitions of various types, and their TrivialCopyTraits specializations + +// A trivially copyable type and its nice TrivialCopyTraits 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 edm::TrivialCopyTraits { + 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 +// TrivialCopyTraits 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 edm::TrivialCopyTraits { + 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 TrivialCopyTraits specialization, that is missing regions() +struct S3 { + std::vector vec; +}; + +template <> +struct edm::TrivialCopyTraits { + static void initialize(S3& object) { object.vec.resize(10); } +}; + +// A bad TrivialCopyTraits specialization, with a non-valid initialize() +struct S4 { + std::vector data; +}; + +template <> +struct edm::TrivialCopyTraits { + 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 TrivialCopyTraits specialization, missing only the const regions +struct S5 { + int value; +}; + +template <> +struct edm::TrivialCopyTraits { + static std::vector> regions(S5& object) { + return {{reinterpret_cast(&object.value), sizeof(int)}}; + } +}; + +// -------------------------------------------------------------------------- +// The tests + +TEST_CASE("test TrivialCopyTraits", "[TrivialCopyTraits]") { + SECTION("int") { + REQUIRE(edm::HasTrivialCopyTraits); + + auto checkInt = [](int v) { + // test non-const regions + auto regions = edm::TrivialCopyTraits::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 = edm::TrivialCopyTraits::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(edm::HasTrivialCopyTraits); + + auto checkDouble = [](double v) { + // test non-const regions + auto regions = edm::TrivialCopyTraits::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 = edm::TrivialCopyTraits::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(edm::HasTrivialCopyTraits); + REQUIRE(edm::HasTrivialCopyProperties); + REQUIRE(edm::HasValidInitialize); + + VectorType vec = {-5.5f, -3.3f, -1.1f, 4.4f, 8.8f}; + + // Test properties + auto vec_size = edm::TrivialCopyTraits::properties(vec); + REQUIRE(vec_size == 5); + + // Test initialize + VectorType new_vec; + edm::TrivialCopyTraits::initialize(new_vec, vec_size); + REQUIRE(new_vec.size() == 5); + + // Test non-const regions + auto regions = edm::TrivialCopyTraits::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 = edm::TrivialCopyTraits::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(edm::HasTrivialCopyTraits); + REQUIRE(edm::HasTrivialCopyProperties); + REQUIRE(edm::HasValidInitialize); + S s{test_msg, test_vec}; + + // initialize a clone of s + S s_clone; + edm::TrivialCopyTraits::initialize(s_clone, edm::TrivialCopyTraits::properties(s)); + + // Get memory regions + auto const s_regions = edm::TrivialCopyTraits::regions(s); + auto s_clone_regions = edm::TrivialCopyTraits::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 + edm::TrivialCopyTraits::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 TrivialCopyTraits specialization for std::map (and there shouldn't be, since std::map is not trivially copyable) + static_assert(!edm::HasTrivialCopyTraits); + } + + SECTION("A valid specialization with initialize() but without properties()") { + REQUIRE(edm::HasTrivialCopyTraits); + REQUIRE(!edm::HasTrivialCopyProperties); + REQUIRE(edm::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) + edm::TrivialCopyTraits::initialize(s2_clone); + + // get memory regions + auto const s2_regions = edm::TrivialCopyTraits::regions(s2); + auto s2_clone_regions = edm::TrivialCopyTraits::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(!edm::HasTrivialCopyTraits); + static_assert(!edm::HasRegions); + + // S5: Missing just the const regions() overload + static_assert(!edm::HasTrivialCopyTraits); + static_assert(!edm::HasRegions); + + // S4: Has properties, but the initialize() declaration takes only one + // argument. + static_assert(!edm::HasTrivialCopyTraits); + static_assert(edm::HasTrivialCopyProperties); + static_assert(!edm::HasValidInitialize); + } +} diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index a590f8e34470c..361bf8fd2d28d 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -6,6 +6,7 @@ #include +#include "DataFormats/Common/interface/TrivialCopyTraits.h" #include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Portable/interface/PortableCollectionCommon.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" @@ -26,7 +27,7 @@ class PortableHostCollection { PortableHostCollection() = delete; - explicit PortableHostCollection(edm::Uninitialized) noexcept {}; + explicit PortableHostCollection(edm::Uninitialized) noexcept {} PortableHostCollection(int32_t elements, alpaka_common::DevHost const& host) // allocate pageable host memory @@ -187,7 +188,7 @@ class PortableHostMultiCollection { public: PortableHostMultiCollection() = delete; - explicit PortableHostMultiCollection(edm::Uninitialized) noexcept {}; + explicit PortableHostMultiCollection(edm::Uninitialized) noexcept {} PortableHostMultiCollection(int32_t elements, alpaka_common::DevHost const& host) // allocate pageable host memory @@ -373,4 +374,62 @@ using PortableHostCollection4 = ::PortableHostMultiCollection; template using PortableHostCollection5 = ::PortableHostMultiCollection; +namespace edm { + + // Specialize the TrivialCopyTraits for PortableHostColletion + template + struct TrivialCopyTraits> { + 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) { + 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}}; + } + }; + + // Specialize the TrivialCopyTraits for PortableHostMultiCollection + template + struct TrivialCopyTraits> { + 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) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; +} // namespace edm + #endif // DataFormats_Portable_interface_PortableHostCollection_h diff --git a/DataFormats/Portable/interface/PortableHostObject.h b/DataFormats/Portable/interface/PortableHostObject.h index a2051a6ff2ab9..2f9243c50ea61 100644 --- a/DataFormats/Portable/interface/PortableHostObject.h +++ b/DataFormats/Portable/interface/PortableHostObject.h @@ -7,6 +7,7 @@ #include +#include "DataFormats/Common/interface/TrivialCopyTraits.h" #include "DataFormats/Common/interface/Uninitialized.h" #include "HeterogeneousCore/AlpakaInterface/interface/config.h" #include "HeterogeneousCore/AlpakaInterface/interface/host.h" @@ -115,4 +116,28 @@ class PortableHostObject { Product* product_; }; +// Specialize the TrivialCopyTraits for PortableHostObject +namespace edm { + + template + struct TrivialCopyTraits> { + // 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 edm + #endif // DataFormats_Portable_interface_PortableHostObject_h diff --git a/DataFormats/PortableTestObjects/plugins/BuildFile.xml b/DataFormats/PortableTestObjects/plugins/BuildFile.xml new file mode 100644 index 0000000000000..650e63ac4d167 --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/BuildFile.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/DataFormats/PortableTestObjects/plugins/TrivialSerialisationPlugins.cc b/DataFormats/PortableTestObjects/plugins/TrivialSerialisationPlugins.cc new file mode 100644 index 0000000000000..b3d7c079f0b72 --- /dev/null +++ b/DataFormats/PortableTestObjects/plugins/TrivialSerialisationPlugins.cc @@ -0,0 +1,19 @@ +#include "DataFormats/Portable/interface/PortableHostObject.h" +#include "DataFormats/Portable/interface/PortableHostCollection.h" +#include "TrivialSerialisation/Common/interface/SerialiserFactory.h" +#include "DataFormats/PortableTestObjects/interface/TestSoA.h" +#include "DataFormats/PortableTestObjects/interface/TestStruct.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(PortableHostObject); + +using PortableHostCollectionTestSoALayout = PortableHostCollection>; +DEFINE_TRIVIAL_SERIALISER_PLUGIN(PortableHostCollectionTestSoALayout); + +using PortableHostMultiCollectionTestSoALayout2 = + PortableHostMultiCollection, portabletest::TestSoALayout2<128, false>>; +DEFINE_TRIVIAL_SERIALISER_PLUGIN(PortableHostMultiCollectionTestSoALayout2); + +using PortableHostMultiCollectionTestSoALayout3 = PortableHostMultiCollection, + portabletest::TestSoALayout2<128, false>, + portabletest::TestSoALayout3<128, false>>; +DEFINE_TRIVIAL_SERIALISER_PLUGIN(PortableHostMultiCollectionTestSoALayout3); diff --git a/DataFormats/PortableTestObjects/test/BuildFile.xml b/DataFormats/PortableTestObjects/test/BuildFile.xml index 4f01bfa83d492..3e7bb42cf8f78 100644 --- a/DataFormats/PortableTestObjects/test/BuildFile.xml +++ b/DataFormats/PortableTestObjects/test/BuildFile.xml @@ -2,3 +2,5 @@ + + diff --git a/DataFormats/PortableTestObjects/test/testGenericCloner_cfg.py b/DataFormats/PortableTestObjects/test/testGenericCloner_cfg.py new file mode 100644 index 0000000000000..0e0314cc16efa --- /dev/null +++ b/DataFormats/PortableTestObjects/test/testGenericCloner_cfg.py @@ -0,0 +1,45 @@ +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('alpaka_serial_sync::TestAlpakaProducer', + size = cms.int32(42), + size2 = cms.int32(33), + size3 = cms.int32(61), + alpaka = cms.untracked.PSet( + backend = cms.untracked.string("") #"serial_sync", "cuda_async", "rocm_async" + ) +) + +process.clonePortableObjects = cms.EDProducer("edmtest::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/DataFormats/StdDictionaries/plugins/BuildFile.xml b/DataFormats/StdDictionaries/plugins/BuildFile.xml new file mode 100644 index 0000000000000..c2c06fcd684fb --- /dev/null +++ b/DataFormats/StdDictionaries/plugins/BuildFile.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/DataFormats/StdDictionaries/plugins/TrivialSerialisationPlugins.cc b/DataFormats/StdDictionaries/plugins/TrivialSerialisationPlugins.cc new file mode 100644 index 0000000000000..3fd0814fa565f --- /dev/null +++ b/DataFormats/StdDictionaries/plugins/TrivialSerialisationPlugins.cc @@ -0,0 +1,8 @@ +#include "TrivialSerialisation/Common/interface/SerialiserFactory.h" + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(int); + +DEFINE_TRIVIAL_SERIALISER_PLUGIN(unsigned short); + +using basic_string = std::basic_string>; +DEFINE_TRIVIAL_SERIALISER_PLUGIN(basic_string); diff --git a/FWCore/Framework/interface/WrapperBaseHandle.h b/FWCore/Framework/interface/WrapperBaseHandle.h new file mode 100644 index 0000000000000..93eaace948153 --- /dev/null +++ b/FWCore/Framework/interface/WrapperBaseHandle.h @@ -0,0 +1,119 @@ +#ifndef FWCore_Framework_interface_WrapperBaseHandle_h +#define FWCore_Framework_interface_WrapperBaseHandle_h +/* + Description: Allows interaction with data in the Event without actually using the C++ class + + Usage: + The Handle allows one to get data back from the edm::Event as an edm::Wrappter + via a polymorphic pointer of type edm::WrapperBase, instead of as the actual C++ class type. + + // make a handle to hold an instance of MyClass + edm::Handle handle(typeid(MyClass)); + event.getByToken(token, handle); + + // handle.product() returns a polymorphic pointer of type edm::WrapperBase to the underlying + // edm::Wrapper + assert(handle.product()->dynamicTypeInfo() == typeid(MyClass)); + edm::Wrapper const* wrapper = dynamic_cast const*>(handle.product()); +*/ + +// c++ include files +#include +#include + +// CMSSW include files +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/Provenance/interface/ProductID.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeID.h" + +// forward declarations +namespace edm { + + class Provenance; + + template <> + class Handle { + public: + explicit Handle(std::type_info const& type) : type_(type), product_(nullptr), prov_(nullptr) {} + + explicit Handle(TypeID type) : type_(type), product_(nullptr), prov_(nullptr) { + if (not type_) { + throw Exception(errors::NotFound, "Handle given an invalid type."); + } + } + + Handle(WrapperBase const* product, Provenance const* prov) + : type_(product->dynamicTypeInfo()), product_(product), prov_(prov) { + assert(product_); + assert(prov_); + } + + // Reimplement the interface of HandleBase + + void clear() { + product_ = nullptr; + prov_ = nullptr; + whyFailedFactory_ = nullptr; + } + + bool isValid() const { return nullptr != product_ and nullptr != prov_; } + + bool failedToGet() const { return bool(whyFailedFactory_); } + + Provenance const* provenance() const { return prov_; } + + ProductID id() const { return prov_->productID(); } + + std::shared_ptr whyFailed() const { + if (whyFailedFactory_.get()) { + return whyFailedFactory_->make(); + } + return std::shared_ptr(); + } + + std::shared_ptr const& whyFailedFactory() const { return whyFailedFactory_; } + + explicit operator bool() const { return isValid(); } + + bool operator!() const { return not isValid(); } + + // Reimplement the interface of Handle + + WrapperBase const* product() const { + if (this->failedToGet()) { + whyFailedFactory_->make()->raise(); + } + return product_; + } + + WrapperBase const* operator->() const { return this->product(); } + WrapperBase const& operator*() const { return *(this->product()); } + + // Additional methods + + TypeID const& type() const { return type_; } + + void setWhyFailedFactory(std::shared_ptr const& iWhyFailed) { + whyFailedFactory_ = iWhyFailed; + } + + private: + TypeID type_; + WrapperBase const* product_; + Provenance const* prov_; + std::shared_ptr whyFailedFactory_; + }; + + // Specialize convert_handle for Handle + void convert_handle(BasicHandle&& orig, Handle& result); + + // Specialize the Event's getByToken method to work with a Handle + template <> + bool Event::getByToken(EDGetToken token, Handle& result) const; + +} // namespace edm + +#endif // FWCore_Framework_interface_WrapperBaseHandle_h diff --git a/FWCore/Framework/interface/WrapperBaseOrphanHandle.h b/FWCore/Framework/interface/WrapperBaseOrphanHandle.h new file mode 100644 index 0000000000000..e3ec1524c1fa0 --- /dev/null +++ b/FWCore/Framework/interface/WrapperBaseOrphanHandle.h @@ -0,0 +1,80 @@ +#ifndef FWCore_Framework_interface_WrapperBaseOrphanHandle_h +#define FWCore_Framework_interface_WrapperBaseOrphanHandle_h + +// c++ include files +#include +#include + +// CMSSW include files +#include "DataFormats/Common/interface/OrphanHandle.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/Provenance/interface/ProductID.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Utilities/interface/TypeID.h" + +// forward declarations +namespace edm { + + template <> + class OrphanHandle { + public: + OrphanHandle() : product_(nullptr), id_() {} + + OrphanHandle(WrapperBase const* prod, ProductID const& id) : product_(prod), id_(id) { assert(product_); } + + // Reimplement the interface of OrphanHandleBase + + void clear() { product_ = nullptr; } + + bool isValid() const { return nullptr != product_; } + + ProductID id() const { return id_; } + + // Reimplement the interface of OrphanHandle + + WrapperBase const* product() const { return product_; } + + WrapperBase const* operator->() const { return this->product(); } + WrapperBase const& operator*() const { return *(this->product()); } + + private: + WrapperBase const* product_; + ProductID id_; + }; + + // specialise Event::putImpl for WrapperBase + template <> + inline OrphanHandle Event::putImpl(EDPutToken::value_type index, std::unique_ptr product) { + assert(index < putProducts().size()); + + // move the wrapped product into the event + putProducts()[index] = std::move(product); + + // construct and return a handle to the product + WrapperBase const* prod = putProducts()[index].get(); + ProductID const& prodID = provRecorder_.getProductID(index); + return OrphanHandle(prod, prodID); + } + + // specialise Event::put for WrapperBase + template <> + inline OrphanHandle Event::put(EDPutToken token, std::unique_ptr product) { + if (UNLIKELY(product.get() == nullptr)) { // null pointer is illegal + TypeID typeID(typeid(WrapperBase)); + principal_get_adapter_detail::throwOnPutOfNullProduct("Event", typeID, provRecorder_.productInstanceLabel(token)); + } + std::type_info const& type = product->dynamicTypeInfo(); + if (UNLIKELY(token.isUninitialized())) { + principal_get_adapter_detail::throwOnPutOfUninitializedToken("Event", type); + } + TypeID const& expected = provRecorder_.getTypeIDForPutTokenIndex(token.index()); + if (UNLIKELY(expected != TypeID{type})) { + principal_get_adapter_detail::throwOnPutOfWrongType(type, expected); + } + + return putImpl(token.index(), std::move(product)); + } + +} // namespace edm + +#endif // FWCore_Framework_interface_WrapperBaseOrphanHandle_h diff --git a/FWCore/Framework/src/WrapperBaseHandle.cc b/FWCore/Framework/src/WrapperBaseHandle.cc new file mode 100644 index 0000000000000..70fc247723a27 --- /dev/null +++ b/FWCore/Framework/src/WrapperBaseHandle.cc @@ -0,0 +1,47 @@ +// CMSSW include files +#include "DataFormats/Common/interface/Handle.h" +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/Provenance/interface/Provenance.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/WrapperBaseHandle.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" +#include "FWCore/Utilities/interface/TypeID.h" + +namespace edm { + + // Specialize the convert_handle free function for Handle + void convert_handle(BasicHandle&& handle, Handle& result) { + if (handle.failedToGet()) { + result.setWhyFailedFactory(handle.whyFailedFactory()); + return; + } + + WrapperBase const* wrapper = handle.wrapper(); + if (wrapper == nullptr) { + throw Exception(errors::InvalidReference, "NullPointer") << "edm::BasicHandle has null pointer to Wrapper"; + } + + if (TypeID(wrapper->dynamicTypeInfo()) != result.type()) { + throw Exception(errors::LogicError) << "WrapperBase asked for " << typeDemangle(result.type().name()) + << " but was given a " << typeDemangle(wrapper->dynamicTypeInfo().name()); + } + + // Move the handle into result + result = Handle(wrapper, handle.provenance()); + } + + // Specialize the Event::getByToken method for Handle + template <> + bool Event::getByToken(EDGetToken token, Handle& result) const { + result.clear(); + BasicHandle bh = provRecorder_.getByToken_(result.type(), PRODUCT_TYPE, token, moduleCallingContext_); + convert_handle(std::move(bh), result); // throws on conversion error + if (UNLIKELY(result.failedToGet())) { + return false; + } + addToGotBranchIDs(*result.provenance()); + return true; + } + +} // namespace edm diff --git a/FWCore/TestModules/plugins/BuildFile.xml b/FWCore/TestModules/plugins/BuildFile.xml index a7126fae22e96..70c63383decd8 100644 --- a/FWCore/TestModules/plugins/BuildFile.xml +++ b/FWCore/TestModules/plugins/BuildFile.xml @@ -1,4 +1,5 @@ + diff --git a/FWCore/TestModules/plugins/GenericCloner.cc b/FWCore/TestModules/plugins/GenericCloner.cc new file mode 100644 index 0000000000000..8b7a3e5cff4d9 --- /dev/null +++ b/FWCore/TestModules/plugins/GenericCloner.cc @@ -0,0 +1,227 @@ +/* + * 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_*_*" ) + * + * . + */ + +#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 "TrivialSerialisation/Common/interface/SerialiserFactory.h" +#include "TrivialSerialisation/Common/interface/TrivialSerialiserBase.h" + +namespace edmtest { + + 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: + 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(); + // TODO 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_) { + edm::Handle handle(product.objectType_.typeInfo()); + + event.getByToken(product.getToken_, handle); + + // create a "wrapper", whose wrapped product will be cloned into "clone" + edm::WrapperBase const* wrapper = handle.product(); + std::unique_ptr clone(static_cast(product.wrappedType_.getClass()->New())); + + // get a "serialiser" object from the plugin factory. This object can + // produce, for a given type, both const and mutable TrivialSerialisers. + std::unique_ptr serialiser{ + ngt::SerialiserFactory::get()->tryToCreate(product.objectType_.typeInfo().name())}; + + if (serialiser) { + // if a serialiser is found for this type, initialise a const and a mutable TrivialSerialisers. + auto reader = serialiser->initialize(*wrapper); + auto writer = serialiser->initialize(*clone); + + edm::LogInfo("GenericCloner") << "A specialization of TrivialCopyTraits exists for type " + << product.objectType_.typeInfo().name() << "."; + // 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->trivialCopyFinalize(); + } else { + edm::LogInfo("GenericCloner") << "No specialization of TrivialCopyTraits found for type " + << product.objectType_.typeInfo().name() + << ", falling back to the ROOT serialization."; + // Use ROOT-based serialisation and deserialisation to clone the wrapped object. + + // 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 form the TBuffer + buffer.SetReadMode(); + buffer.SetBufferOffset(0); + product.wrappedType_.getClass()->Streamer(clone.get(), buffer); + } + + // move the wrapper 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 edmtest + +#include "FWCore/Framework/interface/MakerMacros.h" +DEFINE_FWK_MODULE(edmtest::GenericCloner); diff --git a/FWCore/TestModules/test/BuildFile.xml b/FWCore/TestModules/test/BuildFile.xml index 386d18c359309..138bd06989365 100644 --- a/FWCore/TestModules/test/BuildFile.xml +++ b/FWCore/TestModules/test/BuildFile.xml @@ -1 +1,2 @@ + diff --git a/FWCore/TestModules/test/testGenericCloner_cfg.py b/FWCore/TestModules/test/testGenericCloner_cfg.py new file mode 100644 index 0000000000000..4d9d616eeb822 --- /dev/null +++ b/FWCore/TestModules/test/testGenericCloner_cfg.py @@ -0,0 +1,87 @@ +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("edmtest::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("edmtest::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("edmtest::GenericCloner", + eventProducts = cms.vstring("eventIds"), + verbose = cms.untracked.bool(True) +) + +process.cloneIdsByBranch = cms.EDProducer("edmtest::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("edmtest::GenericCloner", + eventProducts = cms.vstring("produceTransient"), + verbose = cms.untracked.bool(True) +) + +process.pathTransient = cms.Path(process.produceTransient + process.cloneTransient) diff --git a/TrivialSerialisation/Common/BuildFile.xml b/TrivialSerialisation/Common/BuildFile.xml new file mode 100644 index 0000000000000..cc0dace48eed7 --- /dev/null +++ b/TrivialSerialisation/Common/BuildFile.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/TrivialSerialisation/Common/interface/Serialiser.h b/TrivialSerialisation/Common/interface/Serialiser.h new file mode 100644 index 0000000000000..29cb88c4211f3 --- /dev/null +++ b/TrivialSerialisation/Common/interface/Serialiser.h @@ -0,0 +1,24 @@ +#ifndef TrivialSerialisation_Common_Serialiser_h +#define TrivialSerialisation_Common_Serialiser_h + +#include "TrivialSerialisation/Common/interface/SerialiserBase.h" +#include "TrivialSerialisation/Common/interface/TrivialSerialiser.h" + +namespace ngt { + template + class Serialiser : public SerialiserBase { + public: + std::unique_ptr initialize(edm::WrapperBase& wrapper) override { + edm::Wrapper& w = dynamic_cast&>(wrapper); + w.markAsPresent(); + return std::make_unique>(w); + } + std::unique_ptr initialize(edm::WrapperBase const& wrapper) override { + edm::Wrapper const& w = dynamic_cast const&>(wrapper); + return std::make_unique>(w); + } + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_Serialiser_h diff --git a/TrivialSerialisation/Common/interface/SerialiserBase.h b/TrivialSerialisation/Common/interface/SerialiserBase.h new file mode 100644 index 0000000000000..fd5d9769d97c7 --- /dev/null +++ b/TrivialSerialisation/Common/interface/SerialiserBase.h @@ -0,0 +1,19 @@ +#ifndef TrivialSerialisation_Common_SerialiserBase_h +#define TrivialSerialisation_Common_SerialiserBase_h + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "TrivialSerialisation/Common/interface/TrivialSerialiserBase.h" + +namespace ngt { + class SerialiserBase { + public: + SerialiserBase() = default; + + virtual std::unique_ptr initialize(edm::WrapperBase& wrapper) = 0; + virtual std::unique_ptr initialize(const edm::WrapperBase& wrapper) = 0; + + virtual ~SerialiserBase() = default; + }; +} // namespace ngt + +#endif // TrivialSerialisation_Common_SerialiserBase_h diff --git a/TrivialSerialisation/Common/interface/SerialiserFactory.h b/TrivialSerialisation/Common/interface/SerialiserFactory.h new file mode 100644 index 0000000000000..9ae157951e013 --- /dev/null +++ b/TrivialSerialisation/Common/interface/SerialiserFactory.h @@ -0,0 +1,16 @@ +#ifndef TrivialSerialisation_src_SerialiserFactory_h +#define TrivialSerialisation_src_SerialiserFactory_h + +#include "FWCore/PluginManager/interface/PluginFactory.h" +#include "TrivialSerialisation/Common/interface/Serialiser.h" +#include "TrivialSerialisation/Common/interface/SerialiserBase.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/TrivialSerialisation/Common/interface/TrivialSerialiser.h b/TrivialSerialisation/Common/interface/TrivialSerialiser.h new file mode 100644 index 0000000000000..3bf07ef405291 --- /dev/null +++ b/TrivialSerialisation/Common/interface/TrivialSerialiser.h @@ -0,0 +1,113 @@ +#ifndef TrivialSerialisation_Common_TrivialSerialiser_h +#define TrivialSerialisation_Common_TrivialSerialiser_h + +#include +#include +#include +#include +#include + +#include "DataFormats/Common/interface/AnyBuffer.h" +#include "DataFormats/Common/interface/TrivialCopyTraits.h" +#include "DataFormats/Common/interface/Wrapper.h" +#include "TrivialSerialisation/Common/interface/TrivialSerialiserBase.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" + +// defines all methods of TrivialSerialiserBase + +namespace ngt { + + template + class TrivialSerialiser : public TrivialSerialiserBase { + static_assert(edm::HasTrivialCopyTraits, "No specialization of TrivialCopyTraits found for type T"); + + public: + using WrapperType = edm::Wrapper; + TrivialSerialiser(WrapperType const& obj) : TrivialSerialiserBase(&obj) {} + + void initialize(edm::AnyBuffer const& args) override; + edm::AnyBuffer parameters() const override; + std::vector> regions() const override; + std::vector> regions() override; + void trivialCopyFinalize() override; + + private: + const T& getWrappedObj_() const { + WrapperType const& w = static_cast(*getWrapperBasePtr()); + if (not w.isPresent()) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + // return *w.product(); + return w.bareProduct(); + } + + T& getWrappedObj_() { + WrapperType& w = const_cast(static_cast(*getWrapperBasePtr())); + if (not w.isPresent()) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + return w.bareProduct(); + } + }; + + template + void TrivialSerialiser::initialize(edm::AnyBuffer const& args) { + if constexpr (not edm::HasValidInitialize) { + // If there is no valid initialize(), there shouldn't be present + static_assert(not edm::HasTrivialCopyProperties); + return; + } else { + auto& w = static_cast(*getWrapperBasePtr()); + // Each serialiser stores a pointer to the wrapper used to initialize it as "const edm::WrapperBase*" + // For serialisers used for writing, that were initialized with a non-const wrapper, the const_cast below is safe because the wrapper was originally non-const. + // For serialisers used for reading, that were initialized with a const wrapper, this function cannot be called because it is not marked as const. + if constexpr (not edm::HasTrivialCopyProperties) { + // if T has no TrivialCopyProperties, call initialize() without any additional arguments + edm::TrivialCopyTraits::initialize(const_cast(w).bareProduct()); + } else { + // if T has TrivialCopyProperties, cast args to Properties and pass it as an additional argument to initialize() + edm::TrivialCopyTraits::initialize(const_cast(w).bareProduct(), + args.cast_to>()); + } + } + } + + template + inline edm::AnyBuffer TrivialSerialiser::parameters() const { + const T& obj = getWrappedObj_(); + if constexpr (not edm::HasTrivialCopyProperties) { + // if edm::TrivialCopyTraits::properties(...) is not declared, do not call it. + return {}; + } else { + // if edm::TrivialCopyTraits::properties(...) is declared, call it and wrap the result in an edm::AnyBuffer + edm::TrivialCopyProperties p = edm::TrivialCopyTraits::properties(obj); + return edm::AnyBuffer(p); + } + } + + template + inline std::vector> TrivialSerialiser::regions() const { + static_assert(edm::HasRegions); + const T& obj = getWrappedObj_(); + return edm::TrivialCopyTraits::regions(obj); + } + + template + inline std::vector> TrivialSerialiser::regions() { + static_assert(edm::HasRegions); + T& obj = getWrappedObj_(); + return edm::TrivialCopyTraits::regions(obj); + } + + template + inline void TrivialSerialiser::trivialCopyFinalize() { + if constexpr (edm::HasTrivialCopyFinalize) { + const T& obj = getWrappedObj_(); + edm::TrivialCopyTraits::finalize(obj); + } + } + +} // namespace ngt + +#endif // TrivialSerialisation_Common_TrivialSerialiser_h diff --git a/TrivialSerialisation/Common/interface/TrivialSerialiserBase.h b/TrivialSerialisation/Common/interface/TrivialSerialiserBase.h new file mode 100644 index 0000000000000..c717227c897b7 --- /dev/null +++ b/TrivialSerialisation/Common/interface/TrivialSerialiserBase.h @@ -0,0 +1,31 @@ +#ifndef TrivialSerialisation_Common_interface_TrivialSerialiserBase_h +#define TrivialSerialisation_Common_interface_TrivialSerialiserBase_h + +#include "DataFormats/Common/interface/AnyBuffer.h" +#include "DataFormats/Common/interface/WrapperBase.h" + +#include +#include + +namespace ngt { + class TrivialSerialiserBase { + public: + TrivialSerialiserBase(const edm::WrapperBase* ptr) : ptr_(ptr) {} + + virtual void initialize(edm::AnyBuffer const& args) = 0; + virtual edm::AnyBuffer parameters() const = 0; + virtual std::vector> regions() const = 0; + virtual std::vector> regions() = 0; + virtual void trivialCopyFinalize() = 0; + + const edm::WrapperBase* getWrapperBasePtr() const { return ptr_; } + + virtual ~TrivialSerialiserBase() = default; + + private: + const edm::WrapperBase* ptr_; + }; + +} // namespace ngt + +#endif // TrivialSerialisation_Common_interface_TrivialSerialiserBase_h diff --git a/TrivialSerialisation/Common/src/SerialiserFactory.cc b/TrivialSerialisation/Common/src/SerialiserFactory.cc new file mode 100644 index 0000000000000..2c5a4c1e13f7f --- /dev/null +++ b/TrivialSerialisation/Common/src/SerialiserFactory.cc @@ -0,0 +1,3 @@ +#include "TrivialSerialisation/Common/interface/SerialiserFactory.h" + +EDM_REGISTER_PLUGINFACTORY(ngt::SerialiserFactory, "SerialiserFactory");