diff --git a/src/experimental/makefile b/src/experimental/makefile new file mode 100644 index 0000000..6b60176 --- /dev/null +++ b/src/experimental/makefile @@ -0,0 +1,63 @@ +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +MKFILE_DIR := $(patsubst %/,%,$(dir $(MKFILE_PATH))) +PROJECT_ROOT := $(shell git rev-parse --show-toplevel) +MODULE_ROOT := $(MKFILE_DIR) +PRIVATE_DIR := $(MODULE_ROOT)/private +PUBLIC_DIR := $(MODULE_ROOT)/public +CONFIG_DIR := $(PROJECT_ROOT)/src/utl/public/utl/preprocessor + +BIN = main +OUTPUT_DIR := $(MODULE_ROOT)/build +INTERMEDIATE_DIR := $(OUTPUT_DIR)/obj +MODULE_SRCS := $(shell find $(PRIVATE_DIR) $(PUBLIC_DIR) -name '*.cpp') +MODULE_INCLUDES := $(shell find $(PRIVATE_DIR) $(PUBLIC_DIR) -name '*.h') + +.PHONY = all clean print preprocess compile +CXX := c++ +CXX_FLAGS := -std=c++17 -fPIC -O1 -I$(PUBLIC_DIR) -I$(PRIVATE_DIR) -I$(CONFIG_DIR) +LINKER_FLAGS := -lm +OBJECTS := $(addsuffix .o, $(basename $(MODULE_SRCS:$(MODULE_ROOT)/%=%))) +OBJECT_PATHS := $(addprefix $(INTERMEDIATE_DIR)/,$(OBJECTS)) +DEPENDENCIES := $(OBJECTS:.o=.d) +PREPROCESSED := $(OBJECTS:.o=.prep.cpp) + +all: $(BIN) + +$(BIN): $(OBJECT_PATHS) + @echo "Linking $(OBJECTS)" + @$(CXX) $(LINKER_FLAGS) $(OBJECT_PATHS) -o $(OUTPUT_DIR)/$(BIN) + +compile: $(OBJECTS) + @ + +$(OBJECTS):%.o: $(MODULE_ROOT)/%.cpp + @mkdir -p '$(INTERMEDIATE_DIR)/$(@D)' + @echo "Creating object" $@ + @$(CXX) $(CXX_FLAGS) -MMD -MP -MF '$(INTERMEDIATE_DIR)/$(patsubst %.o,%.d,$@)' -MT '$(INTERMEDIATE_DIR)/$@' -c $< -o '$(INTERMEDIATE_DIR)/$@' + +-include $(INTERMEDIATE_DIR)/$(DEPENDENCIES) + +preprocess: $(PREPROCESSED) + @ + +$(PREPROCESSED):%.prep.cpp: $(MODULE_ROOT)/%.cpp + @mkdir -p '$(INTERMEDIATE_DIR)/$(@D)' + @echo "Running preprocessor" $@ + @$(CXX) $(CXX_FLAGS) -MMD -MP -MF '$(INTERMEDIATE_DIR)/$(patsubst %.prep.cpp,%.d,$@)' -MT '$(INTERMEDIATE_DIR)/$@' -E $< -o '$(INTERMEDIATE_DIR)/$@' + +clean: + @echo "Cleaning up..." + @rm -rvf $(INTERMEDIATE_DIR)/**/*.o $(INTERMEDIATE_DIR)/**/*.d $(INTERMEDIATE_DIR)/**/*.prep.cpp $(OUTPUT_DIR)/$(BIN) + +print: + @echo "Bin: $(BIN)\n" + @echo "Makefile Directory: $(MKFILE_DIR)\n" + @echo "Sources: $(MODULE_SRCS)\n" + @echo "Objects: $(OBJECTS)\n" + @echo "Preprocessed: $(PREPROCESSED)\n" + @echo "Includes: $(MODULE_INCLUDES)\n" + @echo "Dependencies: $(DEPENDENCIES)\n" + @echo "CXX Flags: $(CXX_FLAGS)\n" + @echo "Linker Flags: $(LINKER_FLAGS)\n" + @echo "Output Directory: $(OUTPUT_DIR)\n" + @echo "Intermediate Directory: $(INTERMEDIATE_DIR)\n" diff --git a/src/experimental/private/main.cpp b/src/experimental/private/main.cpp new file mode 100644 index 0000000..2b53b3e --- /dev/null +++ b/src/experimental/private/main.cpp @@ -0,0 +1,7 @@ + +#include + +int main(int argc, char** argv) { + puts("hello world"); + return 0; +} diff --git a/src/experimental/public/thread_pool.h b/src/experimental/public/thread_pool.h new file mode 100644 index 0000000..e1d95e7 --- /dev/null +++ b/src/experimental/public/thread_pool.h @@ -0,0 +1,649 @@ +#pragma once +#include "latch.h" +#include "semaphore.h" +#include "utl_config.h" + +#include +#include +#include +#include +#include +#include +#include +// todo intrusive_ptr +// todo atomic_reference_counter + +// See https://godbolt.org/z/hWY84489E for test + +UTL_NAMESPACE_BEGIN + +// A +// / \ +// B C +// / \ / \ +// D E F +// \ / \ / \ +// G H I +// \ / / \ +// J K L + +// 1 >> N => N vertex from 1 to N +// N >> 1 => N vertex from N to 1 +// N >> N => N vertex one for each node +// N >> M where M != 1 && N != 1 => Not possible + +// graph, scatter, I, F>>>, group, A> +// barriers<0, <1,1>, <2, <1, < <0,0>, 2, 2>>>, <2,2> 2> +namespace task_graph { + +template +class vertex_space; +template +class graph; +template +struct vertex; +template +struct vertex_group; + +template +struct edge { + static_assert(From != To, "Loop detected!"); +}; + +template +struct edge_source {}; +template +struct edge_source, vertex_space> { + using type = template_element_t>; +}; +template +struct edge_destination {}; +template +struct edge_destination, vertex_space> { + using type = template_element_t>; +}; +template +using edge_source_t = typename edge_source::type; +template +using edge_destination_t = typename edge_destination::type; + +template +struct edge_list; +template +struct edge_list, edge...> { + static_assert((... && (template_count, type_list...>>::value == 1)), + "Non-unique edge detected"); +}; + +template +struct vertex, I> { + static_assert(I < sizeof...(Vs), "Index out of range"); + using type = template_element_t>; + +private: + template + using vertex_type = vertex, N>; + template + using edge_result = edge_list, E...>; + template + using group_type = vertex_group, Ns...>; + +public: + template + constexpr group_type operator+(vertex_type) const noexcept { + return {}; + } + + template + constexpr group_type operator+(group_type) const noexcept { + return {}; + } + + template + constexpr edge_result> operator>>(vertex_type) const noexcept { + return {}; + } + + template + constexpr edge_result> operator<<(vertex_type) const noexcept { + return {}; + } + + template + constexpr edge_result...> operator>>(group_type) const noexcept { + return {}; + } + + template + constexpr edge_result...> operator<<(group_type) const noexcept { + return {}; + } +}; + +template +struct vertex_group, Is...> { +private: + template + using vertex_type = vertex, N>; + template + using group_type = vertex_group, Ns...>; + template + using edge_result = edge_list, E...>; + +public: + template + constexpr group_type operator+(group_type) const noexcept { + return {}; + } + + template + constexpr group_type operator+(vertex_type) const noexcept { + return {}; + } + + template + constexpr edge_result...> operator>>(vertex_type) const noexcept { + return {}; + } + + template + constexpr edge_result...> operator<<(vertex_type) const noexcept { + return {}; + } + + template + UTL_CONSTRAINT_CXX20(sizeof...(Js) == sizeof...(Is)) + constexpr auto operator>>(group_type) const noexcept + -> UTL_ENABLE_IF_CXX11(edge_result...>, sizeof...(Js) == sizeof...(Is)) { + return {}; + } + + template + UTL_CONSTRAINT_CXX20(sizeof...(Js) == sizeof...(Is)) + constexpr auto operator<<(group_type) const noexcept + -> UTL_ENABLE_IF_CXX11(edge_result...>, sizeof...(Js) == sizeof...(Is)) { + return {}; + } +}; + +// https://godbolt.org/z/18zE4dvvs (C++11) +// https://godbolt.org/z/x51Mf41q7 (C++14) + +namespace details { +template +struct permanent_mark { + __UTL_HIDE_FROM_ABI friend constexpr auto has_mark(permanent_mark) noexcept; +}; + +template +struct activate_permanent_mark { + __UTL_HIDE_FROM_ABI friend constexpr auto has_mark(permanent_mark) noexcept { + return true_type{}; + } +}; + +template +__UTL_HIDE_FROM_ABI decltype(has_mark(permanent_mark{})) has_permanent_mark_impl(int) noexcept; +template +__UTL_HIDE_FROM_ABI false_type has_permanent_mark_impl(short) noexcept; + +template (0))> +using has_permanent_mark UTL_NODEBUG = R; + +template +struct temporary_mark { + __UTL_HIDE_FROM_ABI friend constexpr auto has_mark(temporary_mark) noexcept; +}; + +template +struct activate_temporary_mark { + __UTL_HIDE_FROM_ABI friend constexpr auto has_mark(temporary_mark) noexcept { + return true_type{}; + } +}; + +template +__UTL_HIDE_FROM_ABI decltype(has_mark(temporary_mark{})) has_temporary_mark_impl(int) noexcept; +template +__UTL_HIDE_FROM_ABI false_type has_temporary_mark_impl(short) noexcept; + +template (0)), + typename R1 = decltype(has_permanent_mark_impl(0))> +using has_temporary_mark = bool_constant; + +template +struct vertex_visitor; + +template +struct vertex_visitor, edge...>> {}; + +template +struct complete_visit_result : false_type {}; + +template +struct complete_visit_result, edge...>>> : + activate_permanent_mark, edge...>>>, + true_type {}; + +template +struct visit_vertex_result : false_type {}; + +template (0)), + typename R1 = decltype(has_temporary_mark_impl(0))> +using visit_vertex UTL_NODEBUG = visit_vertex_result; + +template +struct complete_visit {}; + +template +struct visit_next_vertex : true_type {}; + +template +struct visit_next_vertex, vertex_visitor> : + visit_vertex> {}; + +template +struct complete_visit, edge...>>> : + complete_visit_result< + conjunction, + vertex_visitor, edge...>>>...>::value, + vertex_visitor, edge...>>> {}; + +template +struct visit_vertex_result, edge...>>> : + activate_temporary_mark, edge...>>>, + complete_visit, edge...>>> {}; + +template +__UTL_HIDE_FROM_ABI auto is_acyclic_impl(Graph g, index_sequence) noexcept + -> conjunction>...>; +template +__UTL_HIDE_FROM_ABI auto is_acyclic_impl(graph, edge...> g) noexcept + -> decltype(is_acyclic_impl(g, index_sequence_for{})); + +template +using is_acyclic_impl_t UTL_NODEBUG = decltype(is_acyclic_impl(Graph{})); +} // namespace details + +template +struct is_acyclic : false_type {}; + +template +struct is_acyclic, edge...>> : + details::is_acyclic_impl_t, edge...>> {}; + +template +struct dependency_count {}; + +template +struct dependency_count, edge...>> : + size_constant<(0 + ... + (Ts == I))> {}; + +template +class barrier {}; + +#if UTL_TRAIT_SUPPORTED_is_final +# define UTL_TRAIT_is_final_or(X, F) UTL_TRAIT_is_final(F) +#else +# define UTL_TRAIT_is_final_or(X, F) X +#endif + +namespace details { +template +struct element; +#undef UTL_TRAIT_is_final_or + +template +class UTL_ATTRIBUTE(EMPTY_BASES) element : private F { +public: + using value_type = F; + + template + __UTL_HIDE_FROM_ABI explicit element(Args&&... args) noexcept( + UTL_TRAIT_is_nothrow_constructible(F, Args...)) + : F(__UTL forward(args)...) {} + __UTL_HIDE_FROM_ABI element(element const&) = default; + __UTL_HIDE_FROM_ABI element& operator=(element const&) = default; + __UTL_HIDE_FROM_ABI element(element&&) noexcept = default; + __UTL_HIDE_FROM_ABI element& operator=(element&&) noexcept = default; + + template + __UTL_HIDE_FROM_ABI invoke_result_t operator()(Args&&... args) const { + __UTL invoke(*static_cast(this), __UTL forward(args)...); + } + + __UTL_HIDE_FROM_ABI F&& value() && noexcept { return __UTL move(*static_cast(this)); } + __UTL_HIDE_FROM_ABI F const&& value() const&& noexcept { + return __UTL move(*static_cast(this)); + } + __UTL_HIDE_FROM_ABI F& value() & noexcept { return *static_cast(this); } + __UTL_HIDE_FROM_ABI F const& value() const& noexcept { return *static_cast(this); } +}; + +template +class element { +public: + using value_type = F; + + template + __UTL_HIDE_FROM_ABI invoke_result_t operator()(Args&&... args) const { + __UTL invoke(callable, __UTL forward(args)...); + } + + template + __UTL_HIDE_FROM_ABI explicit element(Args&&... args) noexcept( + UTL_TRAIT_is_nothrow_constructible(F, Args...)) + : callable(__UTL forward(args)...) {} + __UTL_HIDE_FROM_ABI element(element const&) = default; + __UTL_HIDE_FROM_ABI element& operator=(element const&) = default; + __UTL_HIDE_FROM_ABI element(element&&) noexcept = default; + __UTL_HIDE_FROM_ABI element& operator=(element&&) noexcept = default; + + __UTL_HIDE_FROM_ABI F&& value() && noexcept { return callable; } + __UTL_HIDE_FROM_ABI F const&& value() const&& noexcept { return callable; } + __UTL_HIDE_FROM_ABI F& value() & noexcept { return callable; } + __UTL_HIDE_FROM_ABI F const& value() const& noexcept { return callable; } + +private: + F callable; +}; + +template +class graph_tuple_impl; +template +class graph_tuple_impl, Vs...> : element... { + template + using element_type UTL_NODEBUG = template_element_t>; + template + using base_type UTL_NODEBUG = element>; + +public: + template + __UTL_HIDE_FROM_ABI explicit graph_tuple_impl(Args&&... args) noexcept( + __UTL conjunction<__UTL is_nothrow_constructible, Args>...>::value) + : element(__UTL forward(args))... {} + __UTL_HIDE_FROM_ABI graph_tuple_impl(graph_tuple_impl const&) noexcept( + __UTL conjunction<__UTL is_nothrow_copy_constructible...>::value) = default; + __UTL_HIDE_FROM_ABI graph_tuple_impl& operator=(graph_tuple_impl const&) noexcept( + __UTL conjunction<__UTL is_nothrow_copy_assignable...>::value) = default; + __UTL_HIDE_FROM_ABI graph_tuple_impl(graph_tuple_impl&&) noexcept( + __UTL conjunction<__UTL is_nothrow_move_constructible...>::value) = default; + __UTL_HIDE_FROM_ABI graph_tuple_impl& operator=(graph_tuple_impl&&) noexcept( + __UTL conjunction<__UTL is_nothrow_move_assignable...>::value) = default; + + template + __UTL_HIDE_FROM_ABI invoke_result_t, Args...> operator()( + size_constant, Args&&... args) const + noexcept(UTL_TRAIT_is_nothrow_invocable_r( + invoke_result_t, Args...>, base_type, Args...)) { + return __UTL invoke(*static_cast*>(this), __UTL forward(args)...); + } + + template + __UTL_HIDE_FROM_ABI element_type const& get() const& noexcept { + return static_cast const*>(this)->value(); + } + + template + __UTL_HIDE_FROM_ABI element_type& get() & noexcept { + return static_cast*>(this)->value(); + } + + template + __UTL_HIDE_FROM_ABI element_type const&& get() const&& noexcept { + return __UTL move(*static_cast const*>(this)).value(); + } + + template + __UTL_HIDE_FROM_ABI element_type&& get() && noexcept { + return __UTL move(*static_cast*>(this)).value(); + } +}; +template +using graph_tuple = graph_tuple_impl, Vs...>; + +template +struct vertex_sequence_impl; +template +struct vertex_sequence_impl, Es...>> { + using type UTL_NODEBUG = index_sequence_for; +}; + +template +using vertex_sequence UTL_NODEBUG = typename vertex_sequence_impl::type; + +template +__UTL_HIDE_FROM_ABI graph_tuple::value>...> decl_barriers( + index_sequence) noexcept; + +template +__UTL_HIDE_FROM_ABI index_sequence<(dependency_count::value == 0)...> decl_entrymask( + index_sequence) noexcept; + +template +using vertex_barriers UTL_NODEBUG = decltype(decl_barriers(vertex_sequence{})); +template +using entrypoint_mask UTL_NODEBUG = decltype(decl_entrymask(vertex_sequence{})); + +using size_array_t UTL_NODEBUG = size_t[]; +template +struct sum_before; +template +struct sum_before<0, Vs...> : size_constant<0> {}; +template +struct sum_before : size_constant::value> {}; + +template +__UTL_HIDE_FROM_ABI auto exclusive_scan_sequence_impl(index_sequence, + index_sequence) noexcept -> index_sequence::value...>; + +template +using exclusive_scan_for = decltype(exclusive_scan_sequence_impl( + make_index_sequence{}, index_sequence{})); + +template +__UTL_HIDE_FROM_ABI auto path_sequence_impl(index_sequence) noexcept + -> exclusive_scan_for; + +template +using path_sequence UTL_NODEBUG = decltype(path_sequence_impl(entrypoint_mask{})); + +template +__UTL_HIDE_FROM_ABI auto path_id_impl(index_sequence) noexcept + -> size_constant; + +template +using path_id UTL_NODEBUG = decltype(path_id_impl(path_sequence{})); + +template +__UTL_HIDE_FROM_ABI auto path_count_impl(index_sequence) noexcept + -> size_constant((dependency_count::value == 0)...)>; + +template +using path_count UTL_NODEBUG = decltype(path_count_impl(vertex_sequence{})); + +} // namespace details + +template +class vertex_space : details::graph_tuple { + using base_type UTL_NODEBUG = details::graph_tuple; + +public: + using base_type::base_type; + using base_type::get; + using base_type::operator(); +}; + +template +class graph, edge...> { + + static_assert((... && (template_count, type_list...>>::value == 1)), + "Non-unique edge detected"); + using exception_span = __UTL span<__UTL exception_ptr, details::path_count::value>; + +public: + template + void begin(S& scheduler) UTL_THROWS { + UTL_THROW_IF(!is_running(), + program_exception(UTL_MESSAGE_FORMAT( + "[UTL] task graph intiation error, Reason=[Graph already initiated]"))); + + static_assert(is_acyclic::value, "Cycle detected!"); + for (auto& ptr : exceptions_) { + ptr = nullptr; + } + [&](index_sequence) { (..., begin(scheduler)); }( + index_sequence_for{}); + } + + __UTL expected wait() noexcept UTL_LIFETIMEBOUND { + if (is_running()) { + graph_barrier_.wait(); + } + + return create_result(); + } + + template + __UTL expected wait(std::chrono::duration duration) noexcept UTL_LIFETIMEBOUND { + if (is_running()) { + graph_barrier_.wait(duration); + } + + return create_result(); + } + + ~graph() noexcept { wait(); } + +private: + __UTL expected create_result() noexcept UTL_LIFETIMEBOUND { +#if UTL_WITH_EXCEPTIONS + auto exp_span = exceptions(); + auto const has_error = __UTL any_of(exp_span.data(), exp_span + exp_span.size(), + [](auto const& ptr) { return ptr != nullptr; }); + if (has_error) { + return __UTL unexpected(exp_span); + } +#endif + + return __UTL expected(__UTL in_place); + } + +private: + using graph_barrier UTL_NODEBUG = barrier; + using vertices UTL_NODEBUG = vertex_space; + using vertex_barriers UTL_NODEBUG = details::vertex_barriers; + using exception_array UTL_NODEBUG = exception_ptr[details::path_count::value]; + + template + void begin(S& scheduler) { + static constexpr details::path_id path{}; + if constexpr (dependency_count::value == 0) { + this->schedule(path, scheduler); + } + } + + template + void schedule(size_constant path, S& scheduler) { + static constexpr size_constant index{}; + scheduler.schedule([&]() { + UTL_TRY { + __UTL invoke(vertices_, index); + (..., on_complete(path, scheduler)); + graph_barrier_.arrive(); // noexcept + } UTL_CATCH(...) { + exceptions_[path] = __UTL current_exception(); + (..., on_fail()); + } + }); + } + + template + void on_complete(size_constant path, S& scheduler) { + if constexpr (Current == From) { + if (__UTL get_element(vertex_barriers_).arrive()) { + this->schedule(path, scheduler); + } + } + } + + template + UTL_ATTRIBUTE(FLATTEN) void on_fail() noexcept { + if constexpr (Current == From) { + graph_barrier_.arrive(); // noexcept + (..., on_fail()); + } + } + +#if UTL_WITH_EXCEPTIONS + exception_array exceptions_ = {}; +#endif + graph_barrier graph_barrier_; + alignas(64) vertices vertices_; + alignas(64) vertex_barriers vertex_barriers_; +}; +} // namespace task_graph + +// A +// / \ +// B C +// / \ / \ +// D E F +// \ / \ / \ +// G H I +// \ / / \ +// J K L + +void func(utl::thread_pool<16> pool) { + auto vs = make_vertices(A, B, C, D, E, F, G, H, I, J, K, L); + auto [a, b, c, d, e, f, g, h, i, j, k, l] = split(vs); + + auto graph = make_graph(move(vs), k + l >> i, i >> f, j >> g + h, h >> f, g + h >> e, g >> d, + d >> b, e >> b + c, f >> c, b + c >> a); + + pool.execute(graph); + + // graph is copyable and/or movable depending on nodes + + // graph_handle moveable only + // ctor of graph handle will call `new`; allocators cannot be used due to type-hiding + // requirement + utl::job::graph_handle handle(graph); // copy/move graph + utl::job::graph_handle handle2(move(graph)); + + pool.execute(handle); // execute and wait until done + // future moveable only + // empty future does noting + + auto future = pool.execute_async(move(handle)); + + auto future2 = future.then(move(handle2)); + // future.detach(); // Similar to thread::detach + // future.wait(); // Similar to thread::join + // release the graph handle back for reuse, will wait until completion + handle = future.release(); + + // dtor of future should call wait +} + +UTL_NAMESPACE_END + +// A : B, C +// B : D, E +// C : E, F +// D : G +// E : G, H +// F : H, I +// G : J +// H : J +// I : K, L +// J : +// K : +// L : + +// I : waiting for 2 +// G : waiting for 1 diff --git a/utl.sublime-project b/utl.sublime-project index 99b5e7d..e630764 100644 --- a/utl.sublime-project +++ b/utl.sublime-project @@ -5,10 +5,17 @@ "path": "src/utl", "name": "UTL" }, + { + "path": "src/experimental", + "name": "EXP" + }, { "path": ".", "name": "Root", "folder_exclude_patterns": [ "*/" ] + }, + { + "path" : "SCRATCH" } ], "settings": @@ -32,11 +39,11 @@ "variants": [ { - "name": "Compile Tests", + "name": "Compile Private", "shell_cmd": "make -C $project_path/src/utl compile -j8" }, { - "name": "Preprocess Tests", + "name": "Preprocess Private", "shell_cmd": "make -C $project_path/src/utl preprocess -j8" }, { @@ -44,6 +51,26 @@ "shell_cmd": "make -C $project_path/src/utl print -j8" } ] + }, + { + "name": "EXP Build", + "cancel": { "kill": true }, + "file_regex": "^(/...*?):([0-9]+):?([0-9]+)", + "variants": + [ + { + "name": "Compile Private", + "shell_cmd": "make -C $project_path/src/experimental compile -j8" + }, + { + "name": "Preprocess Private", + "shell_cmd": "make -C $project_path/src/experimental preprocess -j8" + }, + { + "name": "Print Build Variables", + "shell_cmd": "make -C $project_path/src/experimental print -j8" + } + ] } ]