From 44ec5a3fff0c65547faaf683120af2be10598b35 Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Tue, 25 Aug 2026 14:43:14 -0500 Subject: [PATCH 1/8] Promote resource-limiting TBB preview check to top-level directory --- CMakeLists.txt | 3 ++ Modules/private/PhlexTBB.cmake | 39 +++++++++++++++++++++ phlex/core/CMakeLists.txt | 5 +++ test/tbb-preview/CMakeLists.txt | 61 ++++++--------------------------- 4 files changed, 57 insertions(+), 51 deletions(-) create mode 100644 Modules/private/PhlexTBB.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 332669d09..72cda4921 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,10 +157,13 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON) # add_compile_options(-fprofile-instr-generate -fcoverage-mapping) list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/Modules) +include(${PROJECT_SOURCE_DIR}/Modules/private/PhlexTBB.cmake) # Dependencies required by public API find_package(Boost REQUIRED COMPONENTS json EXPORT) find_package(TBB REQUIRED EXPORT) +phlex_check_tbb_resource_limiting() + find_package(fmt REQUIRED EXPORT) find_package(spdlog REQUIRED EXPORT) find_package(Microsoft.GSL REQUIRED EXPORT) diff --git a/Modules/private/PhlexTBB.cmake b/Modules/private/PhlexTBB.cmake new file mode 100644 index 000000000..8ec81b9ef --- /dev/null +++ b/Modules/private/PhlexTBB.cmake @@ -0,0 +1,39 @@ +# Provides phlex_check_tbb_resource_limiting(), which verifies that the +# TBB preview resource-limiting API required by Phlex is available. + +include_guard() + +include(CheckCXXSourceCompiles) + +function(phlex_check_tbb_resource_limiting) + set(_phlex_required_libraries_save ${CMAKE_REQUIRED_LIBRARIES}) + set(_phlex_required_includes_save ${CMAKE_REQUIRED_INCLUDES}) + set(_phlex_required_flags_save "${CMAKE_REQUIRED_FLAGS}") + + get_target_property(_phlex_tbb_includes TBB::tbb INTERFACE_INCLUDE_DIRECTORIES) + if(_phlex_tbb_includes) + set(CMAKE_REQUIRED_INCLUDES ${_phlex_tbb_includes}) + endif() + set(CMAKE_REQUIRED_LIBRARIES TBB::tbb) + set(CMAKE_REQUIRED_FLAGS "-std=c++${CMAKE_CXX_STANDARD}") + + check_cxx_source_compiles( + " + #define TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING 1 + #include + + using type = oneapi::tbb::flow::resource_limiter; + + int main() {} + " + HAVE_TBB_RESOURCE_LIMITING + ) + + set(CMAKE_REQUIRED_LIBRARIES ${_phlex_required_libraries_save}) + set(CMAKE_REQUIRED_INCLUDES ${_phlex_required_includes_save}) + set(CMAKE_REQUIRED_FLAGS "${_phlex_required_flags_save}") + + if(NOT HAVE_TBB_RESOURCE_LIMITING) + message(FATAL_ERROR "Phlex requires TBB with flow::resource_limiter support") + endif() +endfunction() diff --git a/phlex/core/CMakeLists.txt b/phlex/core/CMakeLists.txt index b35c04c0f..1b8ae6a98 100644 --- a/phlex/core/CMakeLists.txt +++ b/phlex/core/CMakeLists.txt @@ -79,6 +79,7 @@ install( DESTINATION include/phlex/core/detail ) target_include_directories(phlex_core PRIVATE ${PROJECT_SOURCE_DIR}) +target_compile_definitions(phlex_core PUBLIC TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING=1) phlex_apply_symbol_visibility(phlex_core) phlex_apply_optimizations(phlex_core) @@ -96,6 +97,10 @@ phlex_make_internal_library( PUBLIC TBB::tbb phlex::metaprogramming phlex_model_internal phlex_utilities_internal PRIVATE Boost::json spdlog::spdlog ) +target_compile_definitions( + phlex_core_internal + INTERFACE TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING=1 +) add_library(phlex::core_internal ALIAS phlex_core_internal) # Interface library diff --git a/test/tbb-preview/CMakeLists.txt b/test/tbb-preview/CMakeLists.txt index 4fd8abb10..852cd58a8 100644 --- a/test/tbb-preview/CMakeLists.txt +++ b/test/tbb-preview/CMakeLists.txt @@ -1,52 +1,11 @@ -include(CheckCXXSourceCompiles) - -# Check if flow::resource_limiter is available in TBB (preview feature) -set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) -set(CMAKE_REQUIRED_INCLUDES_SAVE ${CMAKE_REQUIRED_INCLUDES}) -set(CMAKE_REQUIRED_FLAGS_SAVE "${CMAKE_REQUIRED_FLAGS}") -set(CMAKE_TRY_COMPILE_VERBOSE_SAVE ${CMAKE_TRY_COMPILE_VERBOSE}) - -get_target_property(_TBB_INCLUDES TBB::tbb INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(_TBB_LOCATION TBB::tbb LOCATION) -if(_TBB_INCLUDES) - set(CMAKE_REQUIRED_INCLUDES ${_TBB_INCLUDES}) -endif() -if(_TBB_LOCATION) - set(CMAKE_REQUIRED_LIBRARIES ${_TBB_LOCATION}) -endif() -set(CMAKE_REQUIRED_FLAGS "-std=c++${CMAKE_CXX_STANDARD}") - -set(CMAKE_TRY_COMPILE_VERBOSE ON) - -check_cxx_source_compiles( - " - #define TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING 1 - #include - - using type = oneapi::tbb::flow::resource_limiter; - - int main() {} -" - HAVE_TBB_RESOURCE_LIMITING +cet_test( + resource_limiting + USE_CATCH2_MAIN + SOURCE + resource_limiting_test.cpp + LIBRARIES + phlex::core + TBB::tbb + spdlog::spdlog ) - -set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_SAVE}) -set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_SAVE}) -set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS_SAVE}") -set(CMAKE_TRY_COMPILE_VERBOSE ${CMAKE_TRY_COMPILE_VERBOSE_SAVE}) - -if(HAVE_TBB_RESOURCE_LIMITING) - cet_test( - resource_limiting - USE_CATCH2_MAIN - SOURCE - resource_limiting_test.cpp - LIBRARIES - phlex::core - TBB::tbb - spdlog::spdlog - ) - target_compile_definitions(resource_limiting PRIVATE TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING=1) -else() - message(STATUS "Skipping resource_limiting test: flow::resource_limiter not available in TBB") -endif() +target_compile_definitions(resource_limiting PRIVATE TBB_PREVIEW_FLOW_GRAPH_RESOURCE_LIMITING=1) From d3ca3a362671783bfb1f93e6b3f3bf27978334cd Mon Sep 17 00:00:00 2001 From: Kyle Knoepfel Date: Wed, 26 Aug 2026 16:09:44 -0500 Subject: [PATCH 2/8] feat(resources): add mutable resource registration --- phlex/core/CMakeLists.txt | 1 + phlex/core/framework_graph.hpp | 18 +++- phlex/core/glue.hpp | 52 +++++++++-- phlex/core/graph_proxy.hpp | 36 +++++--- phlex/core/registration_api.hpp | 31 +++++-- phlex/core/resource_api.hpp | 153 ++++++++++++++++++++++++++++++++ phlex/source.hpp | 5 +- test/CMakeLists.txt | 3 + test/resources_test.cpp | 38 ++++++++ 9 files changed, 305 insertions(+), 32 deletions(-) create mode 100644 phlex/core/resource_api.hpp create mode 100644 test/resources_test.cpp diff --git a/phlex/core/CMakeLists.txt b/phlex/core/CMakeLists.txt index 1b8ae6a98..68363ba5d 100644 --- a/phlex/core/CMakeLists.txt +++ b/phlex/core/CMakeLists.txt @@ -65,6 +65,7 @@ install( provider_node.hpp registrar.hpp registration_api.hpp + resource_api.hpp source.hpp upstream_predicates.hpp DESTINATION include/phlex/core diff --git a/phlex/core/framework_graph.hpp b/phlex/core/framework_graph.hpp index d01670d19..126469b27 100644 --- a/phlex/core/framework_graph.hpp +++ b/phlex/core/framework_graph.hpp @@ -8,6 +8,7 @@ #include "phlex/core/index_router.hpp" #include "phlex/core/message.hpp" #include "phlex/core/node_catalog.hpp" +#include "phlex/core/resource_api.hpp" #include "phlex/driver.hpp" #include "phlex/model/data_cell_tracker.hpp" #include "phlex/model/data_layer_hierarchy.hpp" @@ -31,6 +32,7 @@ #include #include #include +#include #include #include @@ -71,7 +73,7 @@ namespace phlex::detail { module_graph_proxy module_proxy(configuration const& config) { - return {config, graph_, nodes_, registration_errors_}; + return {config, graph_, nodes_, registration_errors_, resources_}; } source_bundle source_proxy(configuration const& config) @@ -79,6 +81,7 @@ namespace phlex::detail { return {.config = config, .graph = graph_, .nodes = nodes_, + .resources = resources_, .registration_errors = registration_errors_}; } @@ -146,6 +149,13 @@ namespace phlex::detail { return make_glue().template add_source(name, std::forward(args)...); } + template + requires(!std::is_const_v && std::constructible_from) + void add_resource(Args&&... args) + { + resources_.template add(std::forward(args)...); + } + template glue make(Args&&... args) { @@ -185,7 +195,7 @@ namespace phlex::detail { if constexpr (is_bound_object && Construct) { bound_object = std::make_shared(std::forward(args)...); } - return {graph_, nodes_, std::move(bound_object), registration_errors_}; + return {graph_, nodes_, std::move(bound_object), registration_errors_, resources_}; } void run(); @@ -200,10 +210,12 @@ namespace phlex::detail { resource_usage graph_resource_usage_; max_allowed_parallelism parallelism_limit_; fixed_hierarchy fixed_hierarchy_; + // The graph_ object uses the filters_, nodes_, resources_, and hierarchy_ objects implicitly. + // These must be declared before graph_ so that they outlive it during destruction. data_layer_hierarchy hierarchy_{}; + resource_catalog resources_; node_catalog nodes_; std::map filters_; - // The graph_ object uses the filters_, nodes_, and hierarchy_ objects implicitly. tbb::flow::graph graph_{}; std::optional driver_; std::vector registration_errors_; diff --git a/phlex/core/glue.hpp b/phlex/core/glue.hpp index 88ea9d653..a99418662 100644 --- a/phlex/core/glue.hpp +++ b/phlex/core/glue.hpp @@ -7,6 +7,7 @@ #include "phlex/core/concepts.hpp" #include "phlex/core/registrar.hpp" #include "phlex/core/registration_api.hpp" +#include "phlex/core/resource_api.hpp" #include "phlex/core/source.hpp" #include "phlex/metaprogramming/delegate.hpp" @@ -48,8 +49,14 @@ namespace phlex::detail { node_catalog& nodes, std::shared_ptr bound_obj, std::vector& errors, + resource_catalog& resources, configuration const* config = nullptr) : - graph_{g}, nodes_{nodes}, bound_obj_{std::move(bound_obj)}, errors_{errors}, config_{config} + graph_{g}, + nodes_{nodes}, + bound_obj_{std::move(bound_obj)}, + errors_{errors}, + resources_{resources}, + config_{config} { } @@ -70,6 +77,7 @@ namespace phlex::detail { graph_, nodes_, errors_, + resources_, std::move(partition), std::forward(init_args)...}; } @@ -82,8 +90,14 @@ namespace phlex::detail { concurrency c) { internal::verify_name(name, config_); - return make_registration( - config_, name, algorithm_bits{bound_obj_, std::move(f)}, c, graph_, nodes_, errors_); + return make_registration(config_, + name, + algorithm_bits{bound_obj_, std::move(f)}, + c, + graph_, + nodes_, + errors_, + resources_); } // 'f' is a by-value sink: it is moved into algorithm_bits. The clang-tidy @@ -94,8 +108,14 @@ namespace phlex::detail { concurrency c) { internal::verify_name(name, config_); - return provider_api{ - config_, name, algorithm_bits{bound_obj_, std::move(f)}, c, graph_, nodes_, errors_}; + return provider_api{config_, + name, + algorithm_bits{bound_obj_, std::move(f)}, + c, + graph_, + nodes_, + errors_, + resources_}; } // 'f' is a by-value sink: it is moved into algorithm_bits. The clang-tidy @@ -106,8 +126,14 @@ namespace phlex::detail { concurrency c) { internal::verify_name(name, config_); - return make_registration( - config_, name, algorithm_bits{bound_obj_, std::move(f)}, c, graph_, nodes_, errors_); + return make_registration(config_, + name, + algorithm_bits{bound_obj_, std::move(f)}, + c, + graph_, + nodes_, + errors_, + resources_); } // 'f' is a by-value sink: it is moved into algorithm_bits. The clang-tidy @@ -118,8 +144,14 @@ namespace phlex::detail { concurrency c) { internal::verify_name(name, config_); - return make_registration( - config_, name, algorithm_bits{bound_obj_, std::move(f)}, c, graph_, nodes_, errors_); + return make_registration(config_, + name, + algorithm_bits{bound_obj_, std::move(f)}, + c, + graph_, + nodes_, + errors_, + resources_); } auto unfold(std::string_view name, @@ -139,6 +171,7 @@ namespace phlex::detail { graph_, nodes_, errors_, + resources_, std::move(destination_data_layer)}; } @@ -168,6 +201,7 @@ namespace phlex::detail { node_catalog& nodes_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) std::shared_ptr bound_obj_; std::vector& errors_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + resource_catalog& resources_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) configuration const* config_; }; } diff --git a/phlex/core/graph_proxy.hpp b/phlex/core/graph_proxy.hpp index ed429def2..c2d557771 100644 --- a/phlex/core/graph_proxy.hpp +++ b/phlex/core/graph_proxy.hpp @@ -39,7 +39,8 @@ namespace phlex::detail { graph_proxy(configuration const& config, tbb::flow::graph& g, node_catalog& nodes, - std::vector& errors) + std::vector& errors, + resource_catalog& resources) requires(not is_bound_object); /// @brief Binds a user algorithm object of type @p U to this proxy. @@ -110,7 +111,8 @@ namespace phlex::detail { tbb::flow::graph& g, node_catalog& nodes, std::shared_ptr bound_obj, - std::vector& errors) + std::vector& errors, + resource_catalog& resources) requires(is_bound_object); private: @@ -123,15 +125,17 @@ namespace phlex::detail { node_catalog& nodes_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) std::shared_ptr bound_obj_; std::vector& errors_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + resource_catalog& resources_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; template graph_proxy::graph_proxy(configuration const& config, tbb::flow::graph& g, node_catalog& nodes, - std::vector& errors) + std::vector& errors, + resource_catalog& resources) requires(not is_bound_object) - : config_{&config}, graph_{g}, nodes_{nodes}, errors_{errors} + : config_{&config}, graph_{g}, nodes_{nodes}, errors_{errors}, resources_{resources} { } @@ -191,7 +195,7 @@ namespace phlex::detail { std::string destination_data_layer, concurrency c) const { - return glue{graph_, nodes_, nullptr, errors_, config_}.unfold( + return glue{graph_, nodes_, nullptr, errors_, resources_, config_}.unfold( name, std::move(pred), std::move(unf), c, std::move(destination_data_layer)); } @@ -216,8 +220,12 @@ namespace phlex::detail { Proxy graph_proxy::bind_to(Args&&... args) const requires(not is_bound_object) { - return Proxy{ - config_, graph_, nodes_, std::make_shared(std::forward(args)...), errors_}; + return Proxy{config_, + graph_, + nodes_, + std::make_shared(std::forward(args)...), + errors_, + resources_}; } template @@ -225,16 +233,24 @@ namespace phlex::detail { tbb::flow::graph& g, node_catalog& nodes, std::shared_ptr bound_obj, - std::vector& errors) + std::vector& errors, + resource_catalog& resources) requires(is_bound_object) - : config_{config}, graph_{g}, nodes_{nodes}, bound_obj_{std::move(bound_obj)}, errors_{errors} + : + config_{config}, + graph_{g}, + nodes_{nodes}, + bound_obj_{std::move(bound_obj)}, + errors_{errors}, + resources_{resources} { } template glue graph_proxy::create_glue(bool use_bound_object) const { - return glue{graph_, nodes_, (use_bound_object ? bound_obj_ : nullptr), errors_, config_}; + return glue{ + graph_, nodes_, (use_bound_object ? bound_obj_ : nullptr), errors_, resources_, config_}; } } diff --git a/phlex/core/registration_api.hpp b/phlex/core/registration_api.hpp index 790f60e2d..f82e57ee3 100644 --- a/phlex/core/registration_api.hpp +++ b/phlex/core/registration_api.hpp @@ -8,6 +8,7 @@ #include "phlex/core/declared_fold.hpp" #include "phlex/core/detail/make_algorithm_name.hpp" #include "phlex/core/node_catalog.hpp" +#include "phlex/core/resource_api.hpp" #include "phlex/core/upstream_predicates.hpp" #include "phlex/metaprogramming/delegate.hpp" #include "phlex/metaprogramming/type_deduction.hpp" @@ -43,13 +44,15 @@ namespace phlex::detail { concurrency c, tbb::flow::graph& g, node_catalog& nodes, - std::vector& errors) : + std::vector& errors, + resource_catalog& resources) : config_{config}, name_{phlex::experimental::internal::make_algorithm_name(config, name)}, alg_{std::move(alg)}, concurrency_{c}, graph_{g}, - registrar_{nodes.registrar_for(errors)} + registrar_{nodes.registrar_for(errors)}, + resources_{resources} { } @@ -98,6 +101,7 @@ namespace phlex::detail { // Non-owning reference to the TBB graph; this class is a short-lived registration builder. tbb::flow::graph& graph_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) registrar registrar_; + resource_catalog& resources_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; template