diff --git a/DataFormats/Common/interface/AnyBuffer.h b/DataFormats/Common/interface/AnyBuffer.h new file mode 100644 index 0000000000000..f0fecbe958a22 --- /dev/null +++ b/DataFormats/Common/interface/AnyBuffer.h @@ -0,0 +1,73 @@ +#ifndef DataFormats_Common_interface_AnyBuffer_h +#define DataFormats_Common_interface_AnyBuffer_h + +#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..c242717aadaa5 --- /dev/null +++ b/DataFormats/Common/interface/TrivialCopyTraits.h @@ -0,0 +1,92 @@ +#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 type alias Properties to describe the + // properties of an object that can be queried from an existing object via the + // properties() method, and used to initialise a newly allocated copy of the + // object via the initialize() method. + // + // If Properties is void, the properties() method should not be implemented, + // and the initialize() method takes a single argument: + // + // using Properties = void; + // static void initialise(T& object); + // + // If Properties is a concrete type, the properties() method should return an + // instance of Properties, and the initialise() method should take as a second + // parameter a const reference to a Properties object: + // + // using Properties = ...; + // static Properties properties(T const& object); + // static void initialise(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; + + // Specialisation for arithmetic types + template + requires std::is_arithmetic_v + struct TrivialCopyTraits { + using value_type = T; + + static std::vector> regions(value_type& object) { + return {{reinterpret_cast(&object), sizeof(value_type)}}; + } + + static std::vector> regions(value_type const& object) { + return {{reinterpret_cast(&object), sizeof(value_type)}}; + } + }; + + // Specialisation for vectors of arithmetic types + template + requires(std::is_arithmetic_v and not std::is_same_v) + struct TrivialCopyTraits> { + using value_type = std::vector; + + using Properties = std::vector::size_type; + + static Properties properties(value_type const& object) { return object.size(); } + + static void initialize(value_type& object, Properties const& size) { object.resize(size); } + + static std::vector> regions(value_type& object) { + return {{reinterpret_cast(object.data()), object.size() * sizeof(T)}}; + } + + static std::vector> regions(value_type 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..12966b72186c3 100644 --- a/DataFormats/Common/interface/Wrapper.h +++ b/DataFormats/Common/interface/Wrapper.h @@ -7,18 +7,23 @@ Wrapper: A template wrapper around EDProducts to hold the product ID. ----------------------------------------------------------------------*/ -#include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Common/interface/CMS_CLASS_VERSION.h" +#include "DataFormats/Common/interface/TrivialCopyTraits.h" +#include "DataFormats/Common/interface/Uninitialized.h" #include "DataFormats/Common/interface/WrapperBase.h" #include "DataFormats/Common/interface/WrapperDetail.h" #include "DataFormats/Provenance/interface/ProductID.h" +#include "FWCore/Utilities/interface/EDMException.h" +#include "FWCore/Utilities/interface/TypeDemangler.h" #include "FWCore/Utilities/interface/Visibility.h" #include #include #include +#include #include #include +#include namespace edm { template @@ -59,6 +64,7 @@ namespace edm { } bool isPresent_() const override { return present; } + void markAsPresent_() override { present = true; } std::type_info const& dynamicTypeInfo_() const override { return typeid(T); } std::type_info const& wrappedTypeInfo_() const override { return typeid(Wrapper); } @@ -81,6 +87,14 @@ namespace edm { std::shared_ptr tableExaminer_() const override; + bool hasTrivialCopyTraits_() const override; + bool hasTrivialCopyProperties_() const override; + void trivialCopyInitialize_(edm::AnyBuffer const& args) override; + edm::AnyBuffer trivialCopyParameters_() const override; + std::vector> trivialCopyRegions_() const override; + std::vector> trivialCopyRegions_() override; + void trivialCopyFinalize_() override; + private: T obj; bool present; @@ -176,13 +190,109 @@ namespace edm { } }; } // namespace soa + template inline std::shared_ptr Wrapper::tableExaminer_() const { return soa::MakeTableExaminer::make(&obj); } + template + inline bool Wrapper::hasTrivialCopyTraits_() const { + if constexpr (requires(T& t) { edm::TrivialCopyTraits::regions(t); }) { + return true; + } + return false; + } + + template + inline bool Wrapper::hasTrivialCopyProperties_() const { + if constexpr (requires { typename edm::TrivialCopyTraits::Properties; }) { + return true; + } + return false; + } + + template + void Wrapper::trivialCopyInitialize_([[maybe_unused]] edm::AnyBuffer const& args) { + if (not present) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + // if edm::TrivialCopyTraits::Properties is not defined, do not call initialize() + if constexpr (not requires { typename edm::TrivialCopyTraits::Properties; }) { + return; + } else + // if edm::TrivialCopyTraits::Properties is void, call initialize() without any additional arguments + if constexpr (std::is_same_v::Properties, void>) { + edm::TrivialCopyTraits::initialize(obj); + } else + // if edm::TrivialCopyTraits::Properties is not void, cast args to Properties and pass it as an additional argument to initialize() + { + edm::TrivialCopyTraits::initialize(obj, args.cast_to::Properties>()); + } + } + + template + inline edm::AnyBuffer Wrapper::trivialCopyParameters_() const { + if (not present) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + // if edm::TrivialCopyTraits::Properties is not defined, do not call properties() + if constexpr (not requires { typename edm::TrivialCopyTraits::Properties; }) { + return {}; + } else + // if edm::TrivialCopyTraits::Properties is void, do not call properties() + if constexpr (std::is_same_v::Properties, void>) { + return {}; + } else + // if edm::TrivialCopyTraits::Properties is not void, call properties() and wrap the result in an edm::AnyBuffer + { + typename edm::TrivialCopyTraits::Properties p = edm::TrivialCopyTraits::properties(obj); + return edm::AnyBuffer(p); + } + } + + template + inline std::vector> Wrapper::trivialCopyRegions_() const { + if (not present) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + if constexpr (requires(T const& t) { edm::TrivialCopyTraits::regions(t); }) { + return edm::TrivialCopyTraits::regions(obj); + } else { + throw edm::Exception(edm::errors::LogicError) + << "edm::TrivialCopyTraits::regions(const T&) is not defined for type " + << edm::typeDemangle(typeid(T).name()); + return {}; + } + } + + template + inline std::vector> Wrapper::trivialCopyRegions_() { + if (not present) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + if constexpr (requires(T& t) { edm::TrivialCopyTraits::regions(t); }) { + return edm::TrivialCopyTraits::regions(obj); + } else { + throw edm::Exception(edm::errors::LogicError) + << "edm::TrivialCopyTraits::regions(const T&) is not defined for type " + << edm::typeDemangle(typeid(T).name()); + return {}; + } + } + + template + inline void Wrapper::trivialCopyFinalize_() { + if (not present) { + throw edm::Exception(edm::errors::LogicError) << "Attempt to access an empty Wrapper"; + } + if constexpr (requires(T& t) { edm::TrivialCopyTraits::finalize(t); }) { + edm::TrivialCopyTraits::finalize(obj); + } + } + } // namespace edm #include "DataFormats/Common/interface/WrapperView.icc" -#endif +#endif // DataFormats_Common_Wrapper_h diff --git a/DataFormats/Common/interface/WrapperBase.h b/DataFormats/Common/interface/WrapperBase.h index 0a8ecfc9d5257..446aee2e6dd9c 100644 --- a/DataFormats/Common/interface/WrapperBase.h +++ b/DataFormats/Common/interface/WrapperBase.h @@ -7,10 +7,12 @@ WrapperBase: The base class of all things that will be inserted into the Event. ----------------------------------------------------------------------*/ +#include "DataFormats/Common/interface/AnyBuffer.h" #include "DataFormats/Common/interface/EDProductfwd.h" #include "DataFormats/Common/interface/FillViewHelperVector.h" #include "DataFormats/Provenance/interface/ViewTypeChecker.h" +#include #include #include #include @@ -28,6 +30,7 @@ namespace edm { WrapperBase(); ~WrapperBase() override; bool isPresent() const { return isPresent_(); } + void markAsPresent() { markAsPresent_(); } // We have to use vector to keep the type information out // of the WrapperBase class. @@ -54,6 +57,15 @@ namespace edm { std::shared_ptr tableExaminer() const { return tableExaminer_(); } + bool hasTrivialCopyTraits() const { return hasTrivialCopyTraits_(); } + bool hasTrivialCopyProperties() const { return hasTrivialCopyProperties_(); } + + void trivialCopyInitialize(edm::AnyBuffer const& args) { trivialCopyInitialize_(args); } + edm::AnyBuffer trivialCopyParameters() const { return trivialCopyParameters_(); } + std::vector> trivialCopyRegions() const { return trivialCopyRegions_(); } + std::vector> trivialCopyRegions() { return trivialCopyRegions_(); } + void trivialCopyFinalize() { trivialCopyFinalize_(); } + private: virtual std::type_info const& dynamicTypeInfo_() const = 0; @@ -63,6 +75,7 @@ namespace edm { // For technical ROOT related reasons, we cannot // declare it = 0. virtual bool isPresent_() const { return true; } + virtual void markAsPresent_() = 0; virtual bool isMergeable_() const = 0; virtual bool mergeProduct_(WrapperBase const* newProduct) = 0; @@ -81,6 +94,16 @@ namespace edm { std::vector& oPtr) const = 0; virtual std::shared_ptr tableExaminer_() const = 0; + + virtual bool hasTrivialCopyTraits_() const = 0; + virtual bool hasTrivialCopyProperties_() const = 0; + virtual void trivialCopyInitialize_(edm::AnyBuffer const& args) = 0; + virtual edm::AnyBuffer trivialCopyParameters_() const = 0; + virtual std::vector> trivialCopyRegions_() const = 0; + virtual std::vector> trivialCopyRegions_() = 0; + virtual void trivialCopyFinalize_() = 0; }; + } // namespace edm -#endif + +#endif // DataFormats_Common_WrapperBase_h diff --git a/DataFormats/Portable/BuildFile.xml b/DataFormats/Portable/BuildFile.xml index ff3cadd88a5d7..da29fa5eb0000 100644 --- a/DataFormats/Portable/BuildFile.xml +++ b/DataFormats/Portable/BuildFile.xml @@ -1,4 +1,5 @@ + diff --git a/DataFormats/Portable/interface/PortableCollection.h b/DataFormats/Portable/interface/PortableCollection.h index 8cedd9eabc7a0..3b58fb1bd2ccc 100644 --- a/DataFormats/Portable/interface/PortableCollection.h +++ b/DataFormats/Portable/interface/PortableCollection.h @@ -1,6 +1,8 @@ #ifndef DataFormats_Portable_interface_PortableCollection_h #define DataFormats_Portable_interface_PortableCollection_h +#include + #include #include "DataFormats/Portable/interface/PortableHostCollection.h" diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index 581fe23d78988..2aa2a392254ce 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" @@ -100,6 +101,35 @@ class PortableHostCollection { View view_; //! }; +// Specialize the TrivialCopyTraits for PortableHostColletion +namespace edm { + + template + struct TrivialCopyTraits> { + using Properties = int32_t; + + static Properties properties(PortableHostCollection const& object) { return object->metadata().size(); } + + static void initialize(PortableHostCollection& object, Properties const& size) { + // replace the default-constructed empty object with one where the buffer has been allocated in pageable system memory + object = PortableHostCollection(size, cms::alpakatools::host()); + } + + static std::vector> regions(PortableHostCollection& object) { + std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + + static std::vector> regions(PortableHostCollection const& object) { + const std::byte* address = reinterpret_cast(object.buffer().data()); + size_t size = alpaka::getExtentProduct(object.buffer()); + return {{address, size}}; + } + }; + +} // namespace edm + // generic SoA-based product in host memory template class PortableHostMultiCollection { 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/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/Integration/plugins/BuildFile.xml b/FWCore/Integration/plugins/BuildFile.xml index 8e9720b7a202f..5cc9abb5ed36a 100644 --- a/FWCore/Integration/plugins/BuildFile.xml +++ b/FWCore/Integration/plugins/BuildFile.xml @@ -32,6 +32,15 @@ + + + + + + + + + diff --git a/FWCore/Integration/plugins/WrapperBaseProducer.cc b/FWCore/Integration/plugins/WrapperBaseProducer.cc new file mode 100644 index 0000000000000..55542581ab8a8 --- /dev/null +++ b/FWCore/Integration/plugins/WrapperBaseProducer.cc @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include + +#include "DataFormats/Common/interface/WrapperBase.h" +#include "DataFormats/TestObjects/interface/Thing.h" +#include "DataFormats/TestObjects/interface/ThingWithDoNotSort.h" +#include "DataFormats/TestObjects/interface/ThingWithPostInsert.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/WrapperBaseOrphanHandle.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/EDPutToken.h" + +// FIXME for do_post_insert_if_available, remove when no longer needed +#include "FWCore/Framework/interface/PrincipalGetAdapter.h" + +namespace edmtest { + + class WrapperBaseProducer : public edm::global::EDProducer<> { + public: + explicit WrapperBaseProducer(edm::ParameterSet const& ps); + + void produce(edm::StreamID sid, edm::Event& event, edm::EventSetup const& es) const override; + + static void fillDescriptions(edm::ConfigurationDescriptions& descriptions); + + private: + edm::EDPutToken intToken_; + edm::EDPutToken floatToken_; + edm::EDPutToken stringToken_; + edm::EDPutToken vectorToken_; + edm::EDPutToken thingToken_; + edm::EDPutToken thingWithPostInsertToken_; + edm::EDPutToken thingWithDoNotSortToken_; + edm::EDPutToken badToken_; + }; + + template + static T& unwrap_as(edm::WrapperBase& wrapper) { + // throws std::bad_cast on conversion error + edm::Wrapper& w = dynamic_cast&>(wrapper); + + // throws an execption if the wrapper is empty + if (w.product() == nullptr) { + throw cms::Exception("LogicError") << "Empty or invalid wrapper"; + } + + // return the content of the wrapper + return w.bareProduct(); + } + + template + static T const& unwrap_as(edm::WrapperBase const& wrapper) { + // throws std::bad_cast on conversion error + edm::Wrapper const& w = dynamic_cast const&>(wrapper); + + // throws an execption if the wrapper is empty + if (w.product() == nullptr) { + throw cms::Exception("LogicError") << "Empty or invalid wrapper"; + } + + // return the content of the wrapper + return *w.product(); + } + + WrapperBaseProducer::WrapperBaseProducer(edm::ParameterSet const& iConfig) + : intToken_(produces(edm::TypeID(typeid(int)))), + floatToken_(produces(edm::TypeID(typeid(float)))), + stringToken_(produces(edm::TypeID(typeid(std::string)))), + vectorToken_(produces(edm::TypeID(typeid(std::vector)))), + thingToken_(produces(edm::TypeID(typeid(Thing)))), + thingWithPostInsertToken_(produces(edm::TypeID(typeid(ThingWithPostInsert)))), + thingWithDoNotSortToken_(produces(edm::TypeID(typeid(ThingWithDoNotSort)))), + badToken_(produces(edm::TypeID(typeid(int)), "bad")) {} + + void WrapperBaseProducer::produce(edm::StreamID sid, edm::Event& event, edm::EventSetup const& es) const { + { + // Check that an int can be put into the event via a WrapperBase. + int value = 42; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product) == value); + // Move the wrapper into the event + auto handle = event.put(intToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle) == value); + } + + { + // Check that a float can be put into the event via a WrapperBase. + float value = 3.14159f; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product) == value); + // Move the wrapper into the event + auto handle = event.put(floatToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle) == value); + } + + { + // Check that a string can be put into the event via a WrapperBase. + // With small string optimisation, the underlying buffer should be inside the string object itself. + std::string value = "42"; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product) == value); + // Move the wrapper into the event + auto handle = event.put(stringToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle) == value); + } + + { + // Check that a vector can be put into the event via a WrapperBase. + // The underlying buffer is outside the vector object itself. + using Vector = std::vector; + Vector value = {1., 1., 2., 3., 5., 8., 11., 19., 30.}; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product) == value); + // Move the wrapper into the event + auto handle = event.put(vectorToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle) == value); + } + + { + // Check that a Thing can be put into the event via a WrapperBase. + cms_int32_t value = 99; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product).a == value); + // Move the wrapper into the event + auto handle = event.put(thingToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle).a == value); + } + + { + // Check that a ThingWithPostInsert can be put into the event via a WrapperBase, + // and that ThingWithPostInsert::post_insert() is called by Wrapper. + int32_t value = 2147483647; + // Copy the value into the wrapper + std::unique_ptr product( + new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + assert(not unwrap_as(*product).valid()); + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + // end-of-FIXME + assert(unwrap_as(*product).value() == value); + assert(unwrap_as(*product).valid()); + // Move the wrapper into the event + auto handle = event.put(thingWithPostInsertToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle).value() == value); + assert(unwrap_as(*handle).valid()); + } + + { + // Check that a ThingWithDoNotSort can be put into the event via a WrapperBase, + // and that ThingWithDoNotSort::post_insert() is *not* called by Wrapper. + int32_t value = 2147483647; + // Copy the value into the wrapper + std::unique_ptr product( + new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product).value() == value); + // Move the wrapper into the event + auto handle = event.put(thingWithDoNotSortToken_, std::move(product)); + assert(not product); + assert(handle.isValid()); + assert(unwrap_as(*handle).value() == value); + } + + { + // Check that mismatches in the type of the token and of the underlying object are caught + float value = 3.14159f; + // Copy the value into the wrapper + std::unique_ptr product(new edm::Wrapper(edm::WrapperBase::Emplace{}, value)); + // FIXME Wrapper should call post_insert (if available), but that is not implemented yet + edm::detail::do_post_insert_if_available(unwrap_as(*product)); + assert(unwrap_as(*product) == value); + bool failed = false; + try { + event.put(badToken_, std::move(product)); + } catch (edm::Exception const& e) { + assert(e.categoryCode() == edm::errors::LogicError); + failed = true; + } + assert(failed); + } + } + + void WrapperBaseProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + descriptions.addWithDefaultLabel(desc); + } + +} // namespace edmtest + +#include "FWCore/Framework/interface/MakerMacros.h" +using edmtest::WrapperBaseProducer; +DEFINE_FWK_MODULE(WrapperBaseProducer); diff --git a/FWCore/Integration/test/BuildFile.xml b/FWCore/Integration/test/BuildFile.xml index 2be611c092a66..6cc6b0eac5b2d 100644 --- a/FWCore/Integration/test/BuildFile.xml +++ b/FWCore/Integration/test/BuildFile.xml @@ -43,6 +43,7 @@ + diff --git a/FWCore/Integration/test/testWrapperBaseProducer_cfg.py b/FWCore/Integration/test/testWrapperBaseProducer_cfg.py new file mode 100644 index 0000000000000..643a3f90452ff --- /dev/null +++ b/FWCore/Integration/test/testWrapperBaseProducer_cfg.py @@ -0,0 +1,39 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("TEST") + +process.source = cms.Source("EmptySource") + +process.maxEvents = cms.untracked.PSet( + input = cms.untracked.int32(10) +) + +process.producer = cms.EDProducer("WrapperBaseProducer") + +process.validateInt = cms.EDAnalyzer("edmtest::GlobalIntAnalyzer", + source = cms.InputTag("producer"), + expected = cms.int32(42) # hard-coded in WrapperBaseProducer +) + +process.validateFloat = cms.EDAnalyzer("edmtest::GlobalFloatAnalyzer", + source = cms.InputTag("producer"), + expected = cms.double(3.14159) # hard-coded in WrapperBaseProducer +) + +process.validateString = cms.EDAnalyzer("edmtest::GlobalStringAnalyzer", + source = cms.InputTag("producer"), + expected = cms.string("42") # hard-coded in WrapperBaseProducer +) + +process.validateVector = cms.EDAnalyzer("edmtest::GlobalVectorAnalyzer", + source = cms.InputTag("producer"), + expected = cms.vdouble(1., 1., 2., 3., 5., 8., 11., 19., 30.) # hard-coded in WrapperBaseProducer +) + +process.path = cms.Path( + process.producer + + process.validateInt + + process.validateFloat + + process.validateString + + process.validateVector +) diff --git a/FWCore/TestModules/plugins/GenericCloner.cc b/FWCore/TestModules/plugins/GenericCloner.cc new file mode 100644 index 0000000000000..d92a632d59aba --- /dev/null +++ b/FWCore/TestModules/plugins/GenericCloner.cc @@ -0,0 +1,217 @@ +/* + * 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 "FWCore/Utilities/interface/TypeDemangler.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); + edm::WrapperBase const* wrapper = handle.product(); + + std::unique_ptr clone( + reinterpret_cast(product.wrappedType_.getClass()->New())); + + if (wrapper->hasTrivialCopyTraits()) { + // Use the trivialCopy traits to clone the wrapped object. + + // mark the clone as present + clone->markAsPresent(); + + // initialise the clone, if the type requires it + if (wrapper->hasTrivialCopyProperties()) { + clone->trivialCopyInitialize(wrapper->trivialCopyParameters()); + } + + // copy the source regions to the target + auto sources = wrapper->trivialCopyRegions(); + auto targets = clone->trivialCopyRegions(); + 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 + clone->trivialCopyFinalize(); + } else { + // 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..d16951110027d 100644 --- a/FWCore/TestModules/test/BuildFile.xml +++ b/FWCore/TestModules/test/BuildFile.xml @@ -1 +1,3 @@ + + diff --git a/FWCore/TestModules/test/testGenericCloner_cfg.py b/FWCore/TestModules/test/testGenericCloner_cfg.py new file mode 100644 index 0000000000000..ad0b2f0af5bae --- /dev/null +++ b/FWCore/TestModules/test/testGenericCloner_cfg.py @@ -0,0 +1,117 @@ +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) + +#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( + 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)