Skip to content

Commit

Permalink
Make PFR optional. (#747)
Browse files Browse the repository at this point in the history
* Make PFR optional.

* Support for using a system provided Boost installation which includes PFR.

* Do not use system Boost by default.

* Better option naming.
  • Loading branch information
niklas-uhl authored Sep 9, 2024
1 parent 8679c87 commit 7dad0e5
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 16 deletions.
47 changes: 41 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ option(KAMPING_BUILD_EXAMPLES_AND_TESTS OFF)
option(KAMPING_TESTS_DISCOVER OFF)
option(KAMPING_ENABLE_ULFM "Enable User-Level Failure-Mitigation (ULFM)" OFF)
option(KAMPING_ENABLE_SERIALIZATION "Enable support for serialization (requires Cereal)" ON)
option(KAMPING_ENABLE_REFLECTION "Enable support for reflecting struct members (requires Boost.PFR)" ON)
option(
KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR
"Use Boost.PFR from system installed Boost, instead of using a standalone PFR install or building PFR from source."
OFF
)

# Enable compilation with ccache. Defaults to ON if this is the main project.
if (PROJECT_IS_TOP_LEVEL)
Expand Down Expand Up @@ -190,22 +196,51 @@ FetchContent_MakeAvailable(kassert)

target_link_libraries(kamping_base INTERFACE kassert::kassert)

# TODO: make this dependency optional
FetchContent_Declare(
pfr
GIT_REPOSITORY https://github.com/apolukhin/pfr_non_boost
GIT_REPOSITORY https://github.com/boostorg/pfr
GIT_TAG 2.2.0
SYSTEM
SYSTEM FIND_PACKAGE_ARGS 2.2.0
)
FetchContent_MakeAvailable(pfr)
target_link_libraries(kamping_base INTERFACE Boost::pfr)

if (KAMPING_ENABLE_REFLECTION)
if (KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR)
find_package(Boost 1.75 COMPONENTS headers CONFIG)
if (NOT Boost_FOUND)
message(
FATAL_ERROR
"Boost.PFR: No compatible Boost version found. Use KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR=OFF to use standalone Boost.PFR."
)
else ()
message(STATUS "Found Boost ${Boost_VERSION}: ${Boost_DIR}")
message(STATUS "Using system Boost for Boost.PFR")
add_library(kamping_pfr INTERFACE)
# when using system installed Boost, it does not provide a PFR target, so we have to link to the headers
# target
target_link_libraries(kamping_pfr INTERFACE Boost::headers)
add_library(Boost::pfr ALIAS kamping_pfr)
endif ()
else ()
FetchContent_MakeAvailable(pfr)
if (pr_FOUND)
message(STATUS "Found Boost.PFR: ${pfr_DIR}")
else ()
message(STATUS "Boost.PFR: building from source.")
endif ()
endif ()
target_link_libraries(kamping_base INTERFACE Boost::pfr)
target_compile_definitions(kamping_base INTERFACE KAMPING_ENABLE_REFLECTION)
message(STATUS "Reflection: enabled")
else ()
message(STATUS "Reflection: disabled")
endif ()

if (KAMPING_ENABLE_SERIALIZATION)
FetchContent_Declare(
cereal
GIT_REPOSITORY https://github.com/USCiLab/cereal
GIT_TAG v1.3.2
SYSTEM FIND_PACKAGE_ARGS NAMES cereal 1.3.2
SYSTEM FIND_PACKAGE_ARGS 1.3.2
)
set(JUST_INSTALL_CEREAL ON)
FetchContent_MakeAvailable(cereal)
Expand Down
7 changes: 7 additions & 0 deletions include/kamping/distributed_graph_communicator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,18 @@ namespace internal {
template <typename NeighborhoodRange>
constexpr bool are_neighborhoods_weighted() {
using NeighborType = typename NeighborhoodRange::value_type;
#ifdef KAMPING_ENABLE_REFLECTION
static_assert(
kamping::internal::tuple_size<NeighborType> == 1 || kamping::internal::tuple_size<NeighborType> == 2,
"Neighbor type has to be a scalar (in the unweighted case) or pair-like (in the weighted case) type"
);
return kamping::internal::tuple_size<NeighborType> == 2;
#else
// If reflection is not available, we assume that the neighbor type is a pair-like type if it is not integral.
// This is the best we can do, because kamping::internal::tuple_size does not work with arbitrary types without
// reflection.
return !std::is_integral_v<NeighborType>;
#endif
}
} // namespace internal

Expand Down
44 changes: 34 additions & 10 deletions include/kamping/mpi_datatype.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

#include <kassert/kassert.hpp>
#include <mpi.h>
#include <pfr.hpp>
#ifdef KAMPING_ENABLE_REFLECTION
#include <boost/pfr.hpp>
#endif

#include "kamping/builtin_types.hpp"
#include "kamping/environment.hpp"
Expand Down Expand Up @@ -84,17 +86,25 @@ struct byte_serialized : contiguous_type<std::byte, sizeof(T)> {};
/// @tparam T The type to construct the MPI_Datatype for.
///
/// This requires that \p T is a `std::pair`, `std::tuple` or a type that is reflectable with
/// [pfr](https://github.com/apolukhin/pfr_non_boost). If you do not agree with PFR's decision if a type is implicitly
/// [pfr](https://github.com/boostorg/pfr). If you do not agree with PFR's decision if a type is implicitly
/// reflectable, you can override it by providing a specialization of \c pfr::is_reflectable with the tag \ref
/// kamping_tag.
/// @see https://apolukhin.github.io/pfr_non_boost/pfr/is_reflectable.html for details
/// @see https://apolukhin.github.io/pfr_non_boost/pfr/is_reflectable.html
/// https://www.boost.org/doc/libs/master/doc/html/reference_section_of_pfr.htmlfor details
/// @note Reflection support for arbitrary struct types is only suppported when KaMPIng is compiled with PFR.
template <typename T>
struct struct_type {
#ifdef KAMPING_ENABLE_REFLECTION
static_assert(
internal::is_std_pair<T>::value || internal::is_std_tuple<T>::value
|| pfr::is_implicitly_reflectable<T, kamping_tag>::value,
|| boost::pfr::is_implicitly_reflectable<T, kamping_tag>::value,
"Type must be a std::pair, std::tuple or reflectable"
);
#else
static_assert(
internal::is_std_pair<T>::value || internal::is_std_tuple<T>::value, "Type must be a std::pair or std::tuple"
);
#endif
/// @brief The category of the type.
static constexpr TypeCategory category = TypeCategory::struct_like;
/// @brief Whether the type has to be committed before it can be used in MPI calls.
Expand Down Expand Up @@ -332,8 +342,8 @@ void for_each_tuple_field(T& t, F&& f) {
}

/// @brief Applies functor \p f to each field of the tuple-like type \p t.
/// This works for `std::pair` and `std::tuple` as well as types that are reflectable with
/// [pfr](https://github.com/apolukhin/pfr_non_boost).
/// This works for `std::pair` and `std::tuple`. If KaMPIng's reflection support is enabled, this also works with types
/// that are reflectable with [pfr](https://github.com/boostorg/pfr).
///
/// \p f should be a callable that takes a reference to the field and
/// its index.
Expand All @@ -342,21 +352,35 @@ void for_each_field(T& t, F&& f) {
if constexpr (internal::is_std_pair<T>::value || internal::is_std_tuple<T>::value) {
for_each_tuple_field(t, std::forward<F>(f));
} else {
pfr::for_each_field(t, std::forward<F>(f));
#ifdef KAMPING_ENABLE_REFLECTION
boost::pfr::for_each_field(t, std::forward<F>(f));
#else
// should not happen
static_assert(internal::is_std_pair<T>::value || internal::is_std_tuple<T>::value);
#endif
}
}

/// @brief The number of elements in a tuple-like type.
/// This works for `std::pair` and `std::tuple` as well as types that are reflectable with
/// [pfr](https://github.com/apolukhin/pfr_non_boost).
/// This works for `std::pair` and `std::tuple`.
/// If KaMPIng's reflection support is enabled, this also works with types that are reflectable with
/// [pfr](https://github.com/boostorg/pfr).
template <typename T>
constexpr size_t tuple_size = [] {
if constexpr (internal::is_std_pair<T>::value) {
return 2;
} else if constexpr (internal::is_std_tuple<T>::value) {
return std::tuple_size_v<T>;
} else {
return pfr::tuple_size_v<T>;
#ifdef KAMPING_ENABLE_REFLECTION
return boost::pfr::tuple_size_v<T>;
#else
if constexpr (std::is_arithmetic_v<T>) {
return 1;
} else {
return std::tuple_size_v<T>;
}
#endif
}
}();
} // namespace internal
Expand Down
2 changes: 2 additions & 0 deletions tests/mpi_datatype_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ TEST(MpiDataTypeTest, byte_serialized_type_works) {
PMPI_Type_free(&byte_serialized_type);
}

#ifdef KAMPING_ENABLE_REFLECTION
TEST(MpiDataTypeTest, struct_type_works_with_struct) {
struct TestStruct {
uint8_t a;
Expand Down Expand Up @@ -556,6 +557,7 @@ TEST(MpiDataTypeTest, struct_type_works_with_nested_struct) {
EXPECT_EQ(u.nested.d, t.nested.d);
PMPI_Type_free(&struct_type);
}
#endif

TEST(MpiDataTypeTest, struct_type_works_with_pair) {
MPI_Datatype resized_type = kamping::struct_type<std::pair<uint8_t, uint64_t>>::data_type();
Expand Down

0 comments on commit 7dad0e5

Please sign in to comment.