diff --git a/core/test/base/CMakeLists.txt b/core/test/base/CMakeLists.txt index 0359923e912..fc1bd84d83a 100644 --- a/core/test/base/CMakeLists.txt +++ b/core/test/base/CMakeLists.txt @@ -31,6 +31,7 @@ ginkgo_create_test(range_accessors) ginkgo_create_test(sanitizers ADDITIONAL_LIBRARIES Threads::Threads) ginkgo_create_test(segmented_array LABELS distributed) ginkgo_create_test(segmented_range) +ginkgo_create_test(temporary_conversion) ginkgo_create_test(types) ginkgo_create_test(precision) ginkgo_create_test(utils) diff --git a/core/test/base/temporary_conversion.cpp b/core/test/base/temporary_conversion.cpp new file mode 100644 index 00000000000..b711e662602 --- /dev/null +++ b/core/test/base/temporary_conversion.cpp @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: 2017 - 2026 The Ginkgo authors +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "ginkgo/core/base/temporary_conversion.hpp" + +#include + +#include + +#include +#include +#include + +#include "core/test/utils.hpp" + + +class LinOpA : public gko::LinOp { +public: + LinOpA(const std::shared_ptr& exec, double value = 0.0) + : LinOp(exec, {}, gko::precision::fp64), value(value) + {} + + static std::unique_ptr create( + const std::shared_ptr& exec, double value = 0.0) + { + return std::make_unique(exec, value); + } + + virtual double get_value() const { return value; } + + // unused but required to compile + template + void convert_to(Other* ptr) + {} + +protected: + void apply_impl(const LinOp* b, LinOp* x) const override {} + void apply_impl(const LinOp* alpha, const LinOp* b, const LinOp* beta, + LinOp* x) const override + {} + +private: + double value; +}; + +class LinOpB : public LinOpA { +public: + LinOpB(const std::shared_ptr& exec, double value = 0.0) + : LinOpA(exec, 0.0), value(value) + {} + + static std::unique_ptr create( + const std::shared_ptr& exec, double value = 0.0) + { + return std::make_unique(exec, value); + } + + + double get_value() const override { return value; } + +private: + double value; +}; + + +class alloc : public gko::log::Logger { +public: + mutable int count = 0; + +protected: + void on_allocation_started(const gko::Executor* exec, + const gko::size_type& num_bytes) const override + { + count++; + } +}; + + +class TemporaryConversion : public ::testing::Test { +protected: + using value_type = double; + using Vec = gko::matrix::MultiVector; + + void SetUp() override + { + log->count = 0; + exec->add_logger(log); + } + + std::shared_ptr log = std::make_shared(); + std::shared_ptr exec = + gko::ReferenceExecutor::create(); + std::unique_ptr vec = gko::initialize({2, 3}, exec); + std::unique_ptr lA = std::make_unique(exec, 3); + std::unique_ptr lB = std::make_unique(exec, 4); +}; + +TEST_F(TemporaryConversion, CreateFromNullptr) +{ + auto tmp = + gko::temporary_conversion::create(static_cast(nullptr)); + + EXPECT_EQ(typeid(tmp.get()), typeid(Vec*)); + EXPECT_EQ(tmp.get(), nullptr); +} + + +TEST_F(TemporaryConversion, ConstCreateFromSameType) +{ + auto tmp = gko::temporary_conversion::create(vec.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(const Vec*)); + EXPECT_EQ(tmp->get_const_values(), vec->get_values()); + EXPECT_EQ(log->count, 0); + GKO_ASSERT_EQUAL_DIMENSIONS(tmp, vec); +} + + +TEST_F(TemporaryConversion, ConstCreateFromDerivedType) +{ + auto tmp = gko::temporary_conversion::create(lB.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(const LinOpA*)); + EXPECT_EQ(tmp->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, ConstCreateFromBaseType) +{ + auto tmp = gko::temporary_conversion::create( + gko::as(lB.get())); + + EXPECT_EQ(typeid(tmp.get()), typeid(const LinOpB*)); + EXPECT_EQ(tmp->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, ConstCreateFromConvertibleType) +{ + using NewVec = gko::matrix::MultiVector; + auto tmp = gko::temporary_conversion::create(vec.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(const NewVec*)); + EXPECT_NE(reinterpret_cast(tmp->get_const_values()), + reinterpret_cast(vec->get_values())); + EXPECT_GT(log->count, 0); + GKO_ASSERT_MTX_NEAR(tmp.get(), vec, 0); +} + + +TEST_F(TemporaryConversion, CreateFromSameType) +{ + auto tmp = gko::temporary_conversion::create(vec.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(Vec*)); + EXPECT_EQ(tmp->get_values(), vec->get_values()); + EXPECT_EQ(log->count, 0); + GKO_ASSERT_EQUAL_DIMENSIONS(tmp, vec); +} + + +TEST_F(TemporaryConversion, CreateFromDerivedType) +{ + auto tmp = gko::temporary_conversion::create(lB.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(LinOpA*)); + EXPECT_EQ(tmp->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, CreateFromBaseType) +{ + auto tmp = + gko::temporary_conversion::create(gko::as(lB.get())); + + EXPECT_EQ(typeid(tmp.get()), typeid(LinOpB*)); + EXPECT_EQ(tmp->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, CreateFromConvertibleType) +{ + using NewVec = gko::matrix::MultiVector; + auto tmp = gko::temporary_conversion::create(vec.get()); + + EXPECT_EQ(typeid(tmp.get()), typeid(NewVec*)); + EXPECT_NE(reinterpret_cast(tmp->get_const_values()), + reinterpret_cast(vec->get_values())); + EXPECT_GT(log->count, 0); + GKO_ASSERT_MTX_NEAR(tmp.get(), vec, 0); +} + + +TEST_F(TemporaryConversion, CreateNonConstCopiesBack) +{ + { + auto tmp = gko::temporary_conversion::create(vec.get()); + tmp->at(0, 0) = -1.0; + tmp->at(0, 1) = -2.0; + } + + EXPECT_EQ(vec->at(0, 0), -1.0); + EXPECT_EQ(vec->at(0, 1), -2.0); +} + + +TEST_F(TemporaryConversion, ConstCreateChainFromDerivedType) +{ + auto tmp = gko::temporary_conversion::create(vec.get()); + + auto tmp_linop = + gko::temporary_conversion::create_from_derived( + std::move(tmp)); + + EXPECT_EQ(typeid(tmp_linop.get()), typeid(const gko::LinOp*)); + EXPECT_NE(dynamic_cast(tmp_linop.get()), nullptr); + EXPECT_EQ(dynamic_cast(tmp_linop.get())->get_const_values(), + vec->get_values()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, ConstCreateChainFromBaseType) +{ + auto tmp = gko::temporary_conversion::create(lB.get()); + + auto tmp_linop = gko::temporary_conversion::create_from_base( + std::move(tmp)); + + EXPECT_EQ(typeid(tmp_linop.get()), typeid(const LinOpB*)); + EXPECT_EQ(lB->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, CreateChainFromDerivedType) +{ + auto tmp = gko::temporary_conversion::create(vec.get()); + + auto tmp_linop = gko::temporary_conversion::create_from_derived( + std::move(tmp)); + + EXPECT_EQ(typeid(tmp_linop.get()), typeid(gko::LinOp*)); + EXPECT_NE(dynamic_cast(tmp_linop.get()), nullptr); + EXPECT_EQ(dynamic_cast(tmp_linop.get())->get_values(), + vec->get_values()); + EXPECT_EQ(log->count, 0); +} + + +TEST_F(TemporaryConversion, CreateChainFromBaseType) +{ + auto tmp = gko::temporary_conversion::create(lB.get()); + + auto tmp_linop = + gko::temporary_conversion::create_from_base(std::move(tmp)); + + EXPECT_EQ(typeid(tmp_linop.get()), typeid(LinOpB*)); + EXPECT_EQ(lB->get_value(), lB->get_value()); + EXPECT_EQ(log->count, 0); +} diff --git a/include/ginkgo/core/base/temporary_conversion.hpp b/include/ginkgo/core/base/temporary_conversion.hpp index d0e14806719..667f33516ed 100644 --- a/include/ginkgo/core/base/temporary_conversion.hpp +++ b/include/ginkgo/core/base/temporary_conversion.hpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2017 - 2024 The Ginkgo authors +// SPDX-FileCopyrightText: 2017 - 2026 The Ginkgo authors // // SPDX-License-Identifier: BSD-3-Clause @@ -68,10 +68,10 @@ class convert_back_deleter { // specialization for constant objects, no need to convert back something that // cannot change template -class convert_back_deleter { +class convert_back_deleter { public: using pointer = const CopyType*; - using original_pointer = const OrigType*; + using original_pointer = OrigType*; convert_back_deleter(original_pointer) {} void operator()(pointer ptr) const { delete ptr; } @@ -89,6 +89,14 @@ class convert_back_deleter { */ template struct conversion_target_helper { + constexpr static bool is_distributed = +#if GINKGO_BUILD_MPI + std::is_base_of_v; +#else + false; +#endif + /** * Creates an empty object on the same executor as source. * * @@ -96,12 +104,29 @@ struct conversion_target_helper { * @param source The source object for the conversion * @return An unique_ptr of TargetType on the same executor as source. */ - template , SourceType>::value>> + template static std::unique_ptr create_empty(const SourceType* source) { - return TargetType::create(source->get_executor()); + if constexpr (is_distributed) { + return TargetType::create( + source->get_executor(), + as(source) + ->get_communicator()); + } else { + return TargetType::create(source->get_executor()); + } + } + + static std::unique_ptr create_empty(const TargetType* source) + { + if constexpr (is_distributed) { + return TargetType::create( + source->get_executor(), + as(source) + ->get_communicator()); + } else { + return TargetType::create(source->get_executor()); + } } }; @@ -178,6 +203,9 @@ struct conversion_helper<> { }; +} // namespace detail + + /** * A temporary_conversion is a special smart pointer-like object that is * designed to hold an object temporarily converted to another format. @@ -192,6 +220,13 @@ struct conversion_helper<> { */ template class temporary_conversion { + // std::function deleter allows to decide the (type of) deleter at + // runtime + using handle_type = std::unique_ptr>; + + template + friend class temporary_conversion; + public: using value_type = T; using pointer = T*; @@ -211,9 +246,113 @@ class temporary_conversion { if ((cast_ptr = dynamic_cast(ptr.get()))) { return handle_type{cast_ptr, null_deleter{}}; } else { - return conversion_helper::template convert< - T>(ptr.get()); + return detail::conversion_helper< + ConversionCandidates...>::template convert(ptr.get()); + } + } + + /** + * Create a temporary conversion from a bare pointer. + * + * @tparam OrigT Type of the pointer to convert from, either same as T, base + * class of T, or convertible to T. + * @param orig Object to convert from + * @return Temporary conversion of orig_ptr to type T + */ + template + static temporary_conversion create(OrigT* orig) + { + if constexpr (std::is_same_v) { + return handle_type{orig, null_deleter{}}; + } + if (auto p = dynamic_cast(orig)) { + return {handle_type{p, null_deleter{}}}; + } + using DecayT = std::decay_t; + auto converted = + detail::conversion_target_helper::create_empty(orig); + as>(orig)->convert_to(converted); + return {handle_type(converted.release(), + detail::convert_back_deleter{orig})}; + } + + /** + * Create a temporary conversion that also owns orig. + * + * This create method takes ownership of the input pointer. It can be + * useful, when chaining conversion. For example if the conversion A -> C + * isn't implemented, but A -> B and B -> C are, then this can simplify the + * conversion by just chaining the two. + * + * When the temporary conversion is deleted, orig will be deleted as well. + * + * @tparam OrigT Type of the pointer to convert from, either same as T, base + * class of T, or convertible to T. + * @param orig Object to convert from and take ownership of + * @return Temporary conversion of orig_ptr to type T + */ + template + static temporary_conversion create(std::unique_ptr orig) + { + std::function deleter = orig.get_deleter(); + auto orig_ptr = orig.release(); + if constexpr (std::is_same_v) { + return handle_type{orig_ptr, deleter}; + } + if (auto p = dynamic_cast(orig_ptr)) { + return { + handle_type{p, [deleter, orig_ptr](T*) { deleter(orig_ptr); }}}; } + using DecayT = std::decay_t; + auto converted = + detail::conversion_target_helper::create_empty(orig_ptr); + as>(orig_ptr)->convert_to(converted); + return {handle_type( + converted.release(), [orig_deleter = deleter, orig_ptr](T* ptr) { + auto deleter = detail::convert_back_deleter{orig_ptr}; + deleter(ptr); + orig_deleter(orig_ptr); + })}; + } + + /** + * Create a temporary conversion for a base type T from an object of a + * derived type. + * + * @tparam Derived A derived type of T + * @param derived_ptr Object to convert from + * @return Temporary conversion of orig_ptr to type T + */ + template , std::decay_t>>> + static temporary_conversion create_from_derived( + temporary_conversion&& derived_ptr) + { + auto handle = std::move(derived_ptr).empty_out(); + return {handle_type{handle.release(), + [deleter = handle.get_deleter()](T* ptr) { + deleter(dynamic_cast(ptr)); + }}}; + } + + /** + * Create a temporary conversion for a derived type T from an object of a + * base type. + * + * @tparam Base A base type of T + * @param base_ptr Object to convert from + * @return Temporary conversion of orig_ptr to type T + */ + template , std::decay_t>>> + static temporary_conversion create_from_base( + temporary_conversion&& base_ptr) + { + auto handle = std::move(base_ptr).empty_out(); + return {handle_type{dynamic_cast(handle.release()), + [deleter = handle.get_deleter()](T* ptr) { + deleter(static_cast(ptr)); + }}}; } /** @@ -236,16 +375,51 @@ class temporary_conversion { explicit operator bool() { return static_cast(handle_); } private: - // std::function deleter allows to decide the (type of) deleter at - // runtime - using handle_type = std::unique_ptr>; - temporary_conversion(handle_type handle) : handle_{std::move(handle)} {} + handle_type empty_out() && { return std::move(handle_); } + handle_type handle_; }; +/** + * Performs polymorphic type conversion of a shared_ptr. + * + * @tparam T requested result type + * @tparam U static type of the passed object + * + * @param obj the shared_ptr to the object which should be converted. + * + * @return If successful, returns a shared_ptr to the subtype, otherwise throws + * NotSupported. This pointer shares ownership with the input pointer. + */ +template +temporary_conversion as(temporary_conversion&& obj) +{ + if (!dynamic_cast(obj.get())) { + GKO_NOT_SUPPORTED(*obj.get()); + } + return temporary_conversion::create_from_base(std::move(obj)); +} + +template +temporary_conversion as(temporary_conversion&& obj) +{ + if (!dynamic_cast(obj.get())) { + GKO_NOT_SUPPORTED(*obj.get()); + } + return temporary_conversion::create_from_base(std::move(obj)); +} + + +namespace detail { + + +// For backwards compatibility +using gko::temporary_conversion; + + } // namespace detail } // namespace gko diff --git a/include/ginkgo/core/distributed/vector.hpp b/include/ginkgo/core/distributed/vector.hpp index 896d0424890..bfdf34d2a8f 100644 --- a/include/ginkgo/core/distributed/vector.hpp +++ b/include/ginkgo/core/distributed/vector.hpp @@ -700,71 +700,6 @@ class Vector } // namespace distributed } // namespace experimental - - -namespace detail { - - -template -struct conversion_target_helper; - - -/** - * @internal - * - * Specialization of conversion_target_helper for distributed vectors. - * This is necessary, since Vector needs to be created from both an executor and - * a communicator. - * - * @see conversion_target_helper - */ -template -struct conversion_target_helper> { - using target_type = experimental::distributed::Vector; - using source_type = - experimental::distributed::Vector>; - - static std::unique_ptr create_empty(const source_type* source) - { - return target_type::create(source->get_executor(), - source->get_communicator()); - } - - // Allow to create_empty of the same type - // For distributed case, next> will be V in the candidate list. - // TODO: decide to whether to add this or add condition to the list - static std::unique_ptr create_empty(const target_type* source) - { - return target_type::create(source->get_executor(), - source->get_communicator()); - } - -#if GINKGO_ENABLE_HALF || GINKGO_ENABLE_BFLOAT16 - using snd_source_type = - experimental::distributed::Vector>; - - static std::unique_ptr create_empty( - const snd_source_type* source) - { - return target_type::create(source->get_executor(), - source->get_communicator()); - } -#endif -#if GINKGO_ENABLE_HALF && GINKGO_ENABLE_BFLOAT16 - using trd_source_type = - experimental::distributed::Vector>; - - static std::unique_ptr create_empty( - const trd_source_type* source) - { - return target_type::create(source->get_executor(), - source->get_communicator()); - } -#endif -}; - - -} // namespace detail } // namespace gko