diff --git a/.clang-format b/.clang-format index ddab1f9..d68e552 100644 --- a/.clang-format +++ b/.clang-format @@ -13,7 +13,7 @@ AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakTemplateDeclarations: Yes @@ -52,6 +52,7 @@ MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false +PenaltyIndentedWhitespace: 1 PointerAlignment: Right ReflowComments: false SortIncludes: CaseSensitive diff --git a/3party/asyncplusplus/Async++Config.cmake.in b/3party/asyncplusplus/Async++Config.cmake.in new file mode 100644 index 0000000..ade2b3e --- /dev/null +++ b/3party/asyncplusplus/Async++Config.cmake.in @@ -0,0 +1,3 @@ +include(CMakeFindDependencyMacro) +find_dependency(Threads) +include("${CMAKE_CURRENT_LIST_DIR}/Async++.cmake") diff --git a/3party/asyncplusplus/CMakeLists.txt b/3party/asyncplusplus/CMakeLists.txt new file mode 100644 index 0000000..bb2de4d --- /dev/null +++ b/3party/asyncplusplus/CMakeLists.txt @@ -0,0 +1,165 @@ +# Copyright (c) 2015 Amanieu d'Antras +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +cmake_minimum_required(VERSION 3.1) +project(Async++ C CXX) + +option(BUILD_SHARED_LIBS "Build Async++ as a shared library" ON) +option(USE_CXX_EXCEPTIONS "Enable C++ exception support" ON) +if (APPLE) + option(BUILD_FRAMEWORK "Build a Mac OS X framework instead of a library" OFF) + if (BUILD_FRAMEWORK AND NOT BUILD_SHARED_LIBS) + message(FATAL_ERROR "Can't build a framework with static libraries") + endif() +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Add all source and header files so IDEs can see them +set(ASYNCXX_INCLUDE + ${PROJECT_SOURCE_DIR}/include/async++/aligned_alloc.h + ${PROJECT_SOURCE_DIR}/include/async++/cancel.h + ${PROJECT_SOURCE_DIR}/include/async++/continuation_vector.h + ${PROJECT_SOURCE_DIR}/include/async++/parallel_for.h + ${PROJECT_SOURCE_DIR}/include/async++/parallel_invoke.h + ${PROJECT_SOURCE_DIR}/include/async++/parallel_reduce.h + ${PROJECT_SOURCE_DIR}/include/async++/partitioner.h + ${PROJECT_SOURCE_DIR}/include/async++/range.h + ${PROJECT_SOURCE_DIR}/include/async++/ref_count.h + ${PROJECT_SOURCE_DIR}/include/async++/scheduler.h + ${PROJECT_SOURCE_DIR}/include/async++/scheduler_fwd.h + ${PROJECT_SOURCE_DIR}/include/async++/task.h + ${PROJECT_SOURCE_DIR}/include/async++/task_base.h + ${PROJECT_SOURCE_DIR}/include/async++/traits.h + ${PROJECT_SOURCE_DIR}/include/async++/when_all_any.h +) +set(ASYNCXX_SRC + ${PROJECT_SOURCE_DIR}/src/internal.h + ${PROJECT_SOURCE_DIR}/src/fifo_queue.h + ${PROJECT_SOURCE_DIR}/src/scheduler.cpp + ${PROJECT_SOURCE_DIR}/src/singleton.h + ${PROJECT_SOURCE_DIR}/src/task_wait_event.h + ${PROJECT_SOURCE_DIR}/src/threadpool_scheduler.cpp + ${PROJECT_SOURCE_DIR}/src/work_steal_queue.h +) +source_group(include FILES ${PROJECT_SOURCE_DIR}/include/async++.h ${ASYNCXX_INCLUDE}) +source_group(src FILES ${ASYNCXX_SRC}) +add_library(Async++ ${PROJECT_SOURCE_DIR}/include/async++.h ${ASYNCXX_INCLUDE} ${ASYNCXX_SRC}) + +# Async++ only depends on the C++11 standard libraries, but some implementations +# require the -pthread compiler flag to enable threading functionality. +if (NOT MSVC) + target_compile_options(Async++ PRIVATE -std=c++11) +endif() +if (APPLE) + # Use libc++ on Mac because the shipped libstdc++ version is ancient + target_compile_options(Async++ PRIVATE -stdlib=libc++) + set_target_properties(Async++ PROPERTIES LINK_FLAGS -stdlib=libc++) +endif() +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(Async++ PUBLIC Threads::Threads) + +# Set up preprocessor definitions +target_include_directories(Async++ PRIVATE ${PROJECT_SOURCE_DIR}/include) +set_target_properties(Async++ PROPERTIES DEFINE_SYMBOL LIBASYNC_BUILD) +if (BUILD_SHARED_LIBS) + # Minimize the set of symbols exported by libraries + set_target_properties(Async++ PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON) +else() + target_compile_definitions(Async++ PUBLIC LIBASYNC_STATIC) +endif() + +# Enable warnings for strict C++ standard conformance +if (NOT MSVC) + target_compile_options(Async++ PRIVATE -Wall -Wextra -pedantic) +endif() + +# Async++ doesn't make use of RTTI information, so don't generate it. +# There are issues on Apple platforms with exceptions and -fno-rtti, so keep it +# enabled there. +# See https://stackoverflow.com/questions/21737201/problems-throwing-and-catching-exceptions-on-os-x-with-fno-rtti +if (MSVC) + target_compile_options(Async++ PRIVATE /GR-) +elseif(NOT APPLE) + target_compile_options(Async++ PRIVATE -fno-rtti) +endif() + +# Allow disabling exceptions, but warn the user about the consequences +if (NOT USE_CXX_EXCEPTIONS) + message(WARNING "Exceptions have been disabled. Any operation that would " + "throw an exception will result in a call to std::abort() instead.") + target_compile_definitions(Async++ PUBLIC LIBASYNC_NO_EXCEPTIONS) + if (MSVC) + target_compile_options(Async++ PUBLIC /EHs-c-) + else() + target_compile_options(Async++ PUBLIC -fno-exceptions) + endif() +endif() + +# /Zc:__cplusplus is required to make __cplusplus accurate +# /Zc:__cplusplus is available starting with Visual Studio 2017 version 15.7 +# (according to https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus) +# That version is equivalent to _MSC_VER==1914 +# (according to https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=vs-2019) +# CMake's ${MSVC_VERSION} is equivalent to _MSC_VER +# (according to https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html#variable:MSVC_VERSION) +# GREATER and EQUAL are used because GREATER_EQUAL is available starting with CMake 3.7 +# (according to https://cmake.org/cmake/help/v3.7/release/3.7.html#commands) +if ((MSVC) AND ((MSVC_VERSION GREATER 1914) OR (MSVC_VERSION EQUAL 1914))) + target_compile_options(Async++ PUBLIC /Zc:__cplusplus) +endif() + +include(CMakePackageConfigHelpers) +configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/Async++Config.cmake.in" + "${PROJECT_BINARY_DIR}/Async++Config.cmake" + INSTALL_DESTINATION cmake +) + +install(FILES "${PROJECT_BINARY_DIR}/Async++Config.cmake" + DESTINATION cmake +) + +# Install the library and produce a CMake export script +include(GNUInstallDirs) +install(TARGETS Async++ + EXPORT Async++ + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION Frameworks +) +export(EXPORT Async++) +install(EXPORT Async++ DESTINATION cmake) +if (APPLE AND BUILD_FRAMEWORK) + set_target_properties(Async++ PROPERTIES OUTPUT_NAME Async++ FRAMEWORK ON) + set_source_files_properties(${ASYNCXX_INCLUDE} PROPERTIES MACOSX_PACKAGE_LOCATION Headers/async++) + set_source_files_properties(${PROJECT_SOURCE_DIR}/include/async++.h PROPERTIES MACOSX_PACKAGE_LOCATION Headers) +else() + set_target_properties(Async++ PROPERTIES OUTPUT_NAME async++) + target_include_directories(Async++ INTERFACE $ $) + install(FILES ${PROJECT_SOURCE_DIR}/include/async++.h DESTINATION include) + install(FILES ${ASYNCXX_INCLUDE} DESTINATION include/async++) +endif() + +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "none") #required + +INCLUDE(CPack) diff --git a/3party/asyncplusplus/LICENSE b/3party/asyncplusplus/LICENSE new file mode 100644 index 0000000..f6c080d --- /dev/null +++ b/3party/asyncplusplus/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Amanieu d'Antras + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/3party/asyncplusplus/README.md b/3party/asyncplusplus/README.md new file mode 100644 index 0000000..d19ce24 --- /dev/null +++ b/3party/asyncplusplus/README.md @@ -0,0 +1,91 @@ +Async++ +======= + +Async++ is a lightweight concurrency framework for C++11. The concept was inspired by the [Microsoft PPL library](http://msdn.microsoft.com/en-us/library/dd492418.aspx) and the [N3428](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdf) C++ standard proposal. + +Example +------- +Here is a short example which shows some features of Async++: + +```c++ +#include +#include + +int main() +{ + auto task1 = async::spawn([] { + std::cout << "Task 1 executes asynchronously" << std::endl; + }); + auto task2 = async::spawn([]() -> int { + std::cout << "Task 2 executes in parallel with task 1" << std::endl; + return 42; + }); + auto task3 = task2.then([](int value) -> int { + std::cout << "Task 3 executes after task 2, which returned " + << value << std::endl; + return value * 3; + }); + auto task4 = async::when_all(task1, task3); + auto task5 = task4.then([](std::tuple, + async::task> results) { + std::cout << "Task 5 executes after tasks 1 and 3. Task 3 returned " + << std::get<1>(results).get() << std::endl; + }); + + task5.get(); + std::cout << "Task 5 has completed" << std::endl; + + async::parallel_invoke([] { + std::cout << "This is executed in parallel..." << std::endl; + }, [] { + std::cout << "with this" << std::endl; + }); + + async::parallel_for(async::irange(0, 5), [](int x) { + std::cout << x; + }); + std::cout << std::endl; + + int r = async::parallel_reduce({1, 2, 3, 4}, 0, [](int x, int y) { + return x + y; + }); + std::cout << "The sum of {1, 2, 3, 4} is " << r << std::endl; +} + +// Output (order may vary in some places): +// Task 1 executes asynchronously +// Task 2 executes in parallel with task 1 +// Task 3 executes after task 2, which returned 42 +// Task 5 executes after tasks 1 and 3. Task 3 returned 126 +// Task 5 has completed +// This is executed in parallel... +// with this +// 01234 +// The sum of {1, 2, 3, 4} is 10 +``` + +Supported Platforms +------------------- + +The only requirement to use Async++ is a C++11 compiler and standard library. Unfortunately C++11 is not yet fully implemented on most platforms. Here is the list of OS and compiler combinations which are known to work. + +- Linux: Works with GCC 4.7+, Clang 3.2+ and Intel compiler 15+. +- Mac: Works with Apple Clang (using libc++). GCC also works but you must get a recent version (4.7+). +- iOS: Works with Apple Clang (using libc++). Note: because iOS has no thread local support, the library uses a workaround based on pthreads. +- Windows: Works with GCC 4.8+ (with pthread-win32) and Visual Studio 2013+. + +Building and Installing +----------------------- +Instructions for compiling Async++ and using it in your code are available on the [Building and Installing](https://github.com/Amanieu/asyncplusplus/wiki/Building-and-Installing) page. + +Documentation +------------ +The Async++ documentation is split into four parts: +- [Tasks](https://github.com/Amanieu/asyncplusplus/wiki/Tasks): This describes task objects which are the core Async++. Reading this first is strongly recommended. +- [Parallel algorithms](https://github.com/Amanieu/asyncplusplus/wiki/Parallel-algorithms): This describes functions to run work on ranges in parallel. +- [Schedulers](https://github.com/Amanieu/asyncplusplus/wiki/Schedulers): This describes the low-level details of Async++ and how to customize it. +- [API Reference](https://github.com/Amanieu/asyncplusplus/wiki/API-Reference): This gives detailed descriptions of all the classes and functions available in Async++. + +Contact +------- +You can contact me by email at amanieu@gmail.com. diff --git a/3party/asyncplusplus/examples/gtk_scheduler.cpp b/3party/asyncplusplus/examples/gtk_scheduler.cpp new file mode 100644 index 0000000..689eb85 --- /dev/null +++ b/3party/asyncplusplus/examples/gtk_scheduler.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include +#include +#include + +// Scheduler implementation +class gtk_scheduler_impl { + // Get the task from the void* and execute it in the UI thread + static gboolean callback(void* p) + { + async::task_run_handle::from_void_ptr(p).run(); + return FALSE; + } + +public: + // Convert a task to void* and send it to the gtk main loop + void schedule(async::task_run_handle t) + { + g_idle_add(callback, t.to_void_ptr()); + } +}; + +// Scheduler to run a task in the gtk main loop +gtk_scheduler_impl& gtk_scheduler() +{ + static gtk_scheduler_impl sched; + return sched; +} + +// In order to ensure the UI is always responsive, you can disallow blocking +// calls in the UI thread. Note that the wait handler isn't called when the +// result of a task is already available, so you can still call .get() on a +// completed task. This is completely optional and can be omitted if you don't +// need it. +void gtk_wait_handler(async::task_wait_handle) +{ + std::cerr << "Error: Blocking wait in UI thread" << std::endl; + std::abort(); +} + +// Thread which increments the label value every ms +void label_update_thread(GtkLabel *label) +{ + int counter = 0; + while (true) { + // Sleep for 1ms + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + counter++; + + // Update the label contents in the UI thread + async::spawn(gtk_scheduler(), [label, counter] { + gtk_label_set_text(label, std::to_string(counter).c_str()); + }); + } +} + +int main(int argc, char *argv[]) +{ + // Initialize GTK + gtk_init(&argc, &argv); + + // Set wait handler on GTK thread to disallow waiting for tasks + async::set_thread_wait_handler(gtk_wait_handler); + + // Create a window with a label + GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); + GtkLabel *label = GTK_LABEL(gtk_label_new("-")); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(label)); + gtk_widget_show_all(window); + + // Start a secondary thread to update the label + std::thread(label_update_thread, label).detach(); + + // Go to GTK main loop + gtk_main(); +} diff --git a/3party/asyncplusplus/include/async++.h b/3party/asyncplusplus/include/async++.h new file mode 100644 index 0000000..36e1516 --- /dev/null +++ b/3party/asyncplusplus/include/async++.h @@ -0,0 +1,158 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +#define ASYNCXX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Export declaration to make symbols visible for dll/so +#ifdef LIBASYNC_STATIC +# define LIBASYNC_EXPORT +# define LIBASYNC_EXPORT_EXCEPTION +#else +# ifdef _WIN32 +# ifdef LIBASYNC_BUILD +# define LIBASYNC_EXPORT __declspec(dllexport) +# else +# define LIBASYNC_EXPORT __declspec(dllimport) +# endif +# define LIBASYNC_EXPORT_EXCEPTION +# else +# define LIBASYNC_EXPORT __attribute__((visibility("default"))) +# define LIBASYNC_EXPORT_EXCEPTION __attribute__((visibility("default"))) +# endif +#endif + +// Support compiling without exceptions +#ifndef LIBASYNC_NO_EXCEPTIONS +# ifdef __clang__ +# if !defined(__EXCEPTIONS) || !__has_feature(cxx_exceptions) +# define LIBASYNC_NO_EXCEPTIONS +# endif +# elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define LIBASYNC_NO_EXCEPTIONS +# elif defined(_MSC_VER) && defined(_HAS_EXCEPTIONS) && !_HAS_EXCEPTIONS +# define LIBASYNC_NO_EXCEPTIONS +# endif +#endif +#ifdef LIBASYNC_NO_EXCEPTIONS +# define LIBASYNC_THROW(...) std::abort() +# define LIBASYNC_RETHROW() do {} while (false) +# define LIBASYNC_RETHROW_EXCEPTION(except) std::terminate() +# define LIBASYNC_TRY if (true) +# define LIBASYNC_CATCH(...) else if (false) +#else +# define LIBASYNC_THROW(...) throw __VA_ARGS__ +# define LIBASYNC_RETHROW() throw +# define LIBASYNC_RETHROW_EXCEPTION(except) std::rethrow_exception(except) +# define LIBASYNC_TRY try +# define LIBASYNC_CATCH(...) catch (__VA_ARGS__) +#endif + +// Optional debug assertions. If exceptions are enabled then use those, but +// otherwise fall back to an assert message. +#ifndef NDEBUG +# ifndef LIBASYNC_NO_EXCEPTIONS +# define LIBASYNC_ASSERT(pred, except, message) ((pred) ? ((void)0) : throw except(message)) +# else +# define LIBASYNC_ASSERT(pred, except, message) ((pred) ? ((void)0) : assert(message)) +# endif +#else +# define LIBASYNC_ASSERT(pred, except, message) ((void)0) +#endif + +// Annotate move constructors and move assignment with noexcept to allow objects +// to be moved if they are in containers. Compilers which don't support noexcept +// will usually move regardless. +#if defined(__GNUC__) || _MSC_VER >= 1900 +# define LIBASYNC_NOEXCEPT noexcept +#else +# define LIBASYNC_NOEXCEPT throw() +#endif + +// Cacheline alignment to avoid false sharing between different threads +#define LIBASYNC_CACHELINE_SIZE 64 +#ifdef __GNUC__ +# define LIBASYNC_CACHELINE_ALIGN __attribute__((aligned(LIBASYNC_CACHELINE_SIZE))) +#elif defined(_MSC_VER) +# define LIBASYNC_CACHELINE_ALIGN __declspec(align(LIBASYNC_CACHELINE_SIZE)) +#else +# define LIBASYNC_CACHELINE_ALIGN alignas(LIBASYNC_CACHELINE_SIZE) +#endif + +// Force symbol visibility to hidden unless explicity exported +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility push(hidden) +#endif +#endif + +// Some forward declarations +namespace async { + +template +class task; +template +class shared_task; +template +class event_task; + +} // namespace async + +// Include sub-headers +#include "async++/traits.h" +#include "async++/aligned_alloc.h" +#include "async++/ref_count.h" +#include "async++/scheduler_fwd.h" +#include "async++/continuation_vector.h" +#include "async++/task_base.h" +#include "async++/scheduler.h" +#include "async++/task.h" +#include "async++/when_all_any.h" +#include "async++/cancel.h" +#include "async++/range.h" +#include "async++/partitioner.h" +#include "async++/parallel_invoke.h" +#include "async++/parallel_for.h" +#include "async++/parallel_reduce.h" + +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility pop +#endif +#endif + +#endif diff --git a/3party/asyncplusplus/include/async++/aligned_alloc.h b/3party/asyncplusplus/include/async++/aligned_alloc.h new file mode 100644 index 0000000..cf4c2a3 --- /dev/null +++ b/3party/asyncplusplus/include/async++/aligned_alloc.h @@ -0,0 +1,99 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Allocate an aligned block of memory +LIBASYNC_EXPORT void* aligned_alloc(std::size_t size, std::size_t align); + +// Free an aligned block of memory +LIBASYNC_EXPORT void aligned_free(void* addr) LIBASYNC_NOEXCEPT; + +// Class representing an aligned array and its length +template::value> +class aligned_array { + std::size_t length; + T* ptr; + +public: + aligned_array() + : length(0), ptr(nullptr) {} + aligned_array(std::nullptr_t) + : length(0), ptr(nullptr) {} + explicit aligned_array(std::size_t length_) + : length(length_) + { + ptr = static_cast(aligned_alloc(length * sizeof(T), Align)); + std::size_t i; + LIBASYNC_TRY { + for (i = 0; i < length; i++) + new(ptr + i) T; + } LIBASYNC_CATCH(...) { + for (std::size_t j = 0; j < i; j++) + ptr[i].~T(); + aligned_free(ptr); + LIBASYNC_RETHROW(); + } + } + aligned_array(aligned_array&& other) LIBASYNC_NOEXCEPT + : length(other.length), ptr(other.ptr) + { + other.ptr = nullptr; + other.length = 0; + } + aligned_array& operator=(aligned_array&& other) LIBASYNC_NOEXCEPT + { + aligned_array(std::move(*this)); + std::swap(ptr, other.ptr); + std::swap(length, other.length); + return *this; + } + aligned_array& operator=(std::nullptr_t) + { + return *this = aligned_array(); + } + ~aligned_array() + { + for (std::size_t i = 0; i < length; i++) + ptr[i].~T(); + aligned_free(ptr); + } + + T& operator[](std::size_t i) const + { + return ptr[i]; + } + std::size_t size() const + { + return length; + } + T* get() const + { + return ptr; + } + explicit operator bool() const + { + return ptr != nullptr; + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/cancel.h b/3party/asyncplusplus/include/async++/cancel.h new file mode 100644 index 0000000..36d0307 --- /dev/null +++ b/3party/asyncplusplus/include/async++/cancel.h @@ -0,0 +1,68 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Exception thrown by cancel_current_task() +struct LIBASYNC_EXPORT_EXCEPTION task_canceled {}; + +// A flag which can be used to request cancellation +class cancellation_token { + std::atomic state; + +public: + cancellation_token() + : state(false) {} + + // Non-copyable and non-movable + cancellation_token(const cancellation_token&) = delete; + cancellation_token& operator=(const cancellation_token&) = delete; + + bool is_canceled() const + { + bool s = state.load(std::memory_order_relaxed); + if (s) + std::atomic_thread_fence(std::memory_order_acquire); + return s; + } + + void cancel() + { + state.store(true, std::memory_order_release); + } + + void reset() + { + state.store(false, std::memory_order_relaxed); + } +}; + +// Interruption point, throws task_canceled if the specified token is set. +inline void interruption_point(const cancellation_token& token) +{ + if (token.is_canceled()) + LIBASYNC_THROW(task_canceled()); +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/continuation_vector.h b/3party/asyncplusplus/include/async++/continuation_vector.h new file mode 100644 index 0000000..f0d9095 --- /dev/null +++ b/3party/asyncplusplus/include/async++/continuation_vector.h @@ -0,0 +1,225 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Compress the flags in the low bits of the pointer if the structures are +// suitably aligned. Fall back to a separate flags variable otherwise. +template +class compressed_ptr { + void* ptr; + std::uintptr_t flags; + +public: + compressed_ptr() = default; + compressed_ptr(void* ptr_, std::uintptr_t flags_) + : ptr(ptr_), flags(flags_) {} + + template + T* get_ptr() const + { + return static_cast(ptr); + } + std::uintptr_t get_flags() const + { + return flags; + } + + void set_ptr(void* p) + { + ptr = p; + } + void set_flags(std::uintptr_t f) + { + flags = f; + } +}; +template +class compressed_ptr { + std::uintptr_t data; + +public: + compressed_ptr() = default; + compressed_ptr(void* ptr_, std::uintptr_t flags_) + : data(reinterpret_cast(ptr_) | flags_) {} + + template + T* get_ptr() const + { + return reinterpret_cast(data & ~Mask); + } + std::uintptr_t get_flags() const + { + return data & Mask; + } + + void set_ptr(void* p) + { + data = reinterpret_cast(p) | (data & Mask); + } + void set_flags(std::uintptr_t f) + { + data = (data & ~Mask) | f; + } +}; + +// Thread-safe vector of task_ptr which is optimized for the common case of +// only having a single continuation. +class continuation_vector { + // Heap-allocated data for the slow path + struct vector_data { + std::vector vector; + std::mutex lock; + }; + + // Flags to describe the state of the vector + enum flags { + // If set, no more changes are allowed to internal_data + is_locked = 1, + + // If set, the pointer is a vector_data* instead of a task_base*. If + // there are 0 or 1 elements in the vector, the task_base* form is used. + is_vector = 2 + }; + static const std::uintptr_t flags_mask = 3; + + // Embed the two bits in the data if they are suitably aligned. We only + // check the alignment of vector_data here because task_base isn't defined + // yet. Since we align task_base to LIBASYNC_CACHELINE_SIZE just use that. + typedef compressed_ptr::value & flags_mask) == 0> internal_data; + + // All changes to the internal data are atomic + std::atomic atomic_data; + +public: + // Start unlocked with zero elements in the fast path + continuation_vector() + { + // Workaround for a bug in certain versions of clang with libc++ + // error: no viable conversion from 'async::detail::compressed_ptr<3, true>' to '_Atomic(async::detail::compressed_ptr<3, true>)' + atomic_data.store(internal_data(nullptr, 0), std::memory_order_relaxed); + } + + // Free any left over data + ~continuation_vector() + { + // Converting to task_ptr instead of using remove_ref because task_base + // isn't defined yet at this point. + internal_data data = atomic_data.load(std::memory_order_relaxed); + if (data.get_flags() & flags::is_vector) { + // No need to lock the mutex, we are the only thread at this point + for (task_base* i: data.get_ptr()->vector) + (task_ptr(i)); + delete data.get_ptr(); + } else { + // If the data is locked then the inline pointer is already gone + if (!(data.get_flags() & flags::is_locked)) + task_ptr tmp(data.get_ptr()); + } + } + + // Try adding an element to the vector. This fails and returns false if + // the vector has been locked. In that case t is not modified. + bool try_add(task_ptr&& t) + { + // Cache to avoid re-allocating vector_data multiple times. This is + // automatically freed if it is not successfully saved to atomic_data. + std::unique_ptr vector; + + // Compare-exchange loop on atomic_data + internal_data data = atomic_data.load(std::memory_order_relaxed); + internal_data new_data; + do { + // Return immediately if the vector is locked + if (data.get_flags() & flags::is_locked) + return false; + + if (data.get_flags() & flags::is_vector) { + // Larger vectors use a mutex, so grab the lock + std::atomic_thread_fence(std::memory_order_acquire); + std::lock_guard locked(data.get_ptr()->lock); + + // We need to check again if the vector has been locked here + // to avoid a race condition with flush_and_lock + if (atomic_data.load(std::memory_order_relaxed).get_flags() & flags::is_locked) + return false; + + // Add the element to the vector and return + data.get_ptr()->vector.push_back(t.release()); + return true; + } else { + if (data.get_ptr()) { + // Going from 1 to 2 elements, allocate a vector_data + if (!vector) + vector.reset(new vector_data{{data.get_ptr(), t.get()}, {}}); + new_data = {vector.get(), flags::is_vector}; + } else { + // Going from 0 to 1 elements + new_data = {t.get(), 0}; + } + } + } while (!atomic_data.compare_exchange_weak(data, new_data, std::memory_order_release, std::memory_order_relaxed)); + + // If we reach this point then atomic_data was successfully changed. + // Since the pointers are now saved in the vector, release them from + // the smart pointers. + t.release(); + vector.release(); + return true; + } + + // Lock the vector and flush all elements through the given function + template void flush_and_lock(Func&& func) + { + // Try to lock the vector using a compare-exchange loop + internal_data data = atomic_data.load(std::memory_order_relaxed); + internal_data new_data; + do { + new_data = data; + new_data.set_flags(data.get_flags() | flags::is_locked); + } while (!atomic_data.compare_exchange_weak(data, new_data, std::memory_order_acquire, std::memory_order_relaxed)); + + if (data.get_flags() & flags::is_vector) { + // If we are using vector_data, lock it and flush all elements + std::lock_guard locked(data.get_ptr()->lock); + for (auto i: data.get_ptr()->vector) + func(task_ptr(i)); + + // Clear the vector to save memory. Note that we don't actually free + // the vector_data here because other threads may still be using it. + // This isn't a very significant cost since multiple continuations + // are relatively rare. + data.get_ptr()->vector.clear(); + } else { + // If there is an inline element, just pass it on + if (data.get_ptr()) + func(task_ptr(data.get_ptr())); + } + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/parallel_for.h b/3party/asyncplusplus/include/async++/parallel_for.h new file mode 100644 index 0000000..4fe5268 --- /dev/null +++ b/3party/asyncplusplus/include/async++/parallel_for.h @@ -0,0 +1,77 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Internal implementation of parallel_for that only accepts a partitioner +// argument. +template +void internal_parallel_for(Sched& sched, Partitioner partitioner, const Func& func) +{ + // Split the partition, run inline if no more splits are possible + auto subpart = partitioner.split(); + if (subpart.begin() == subpart.end()) { + for (auto&& i: partitioner) + func(std::forward(i)); + return; + } + + // Run the function over each half in parallel + auto&& t = async::local_spawn(sched, [&sched, &subpart, &func] { + detail::internal_parallel_for(sched, std::move(subpart), func); + }); + detail::internal_parallel_for(sched, std::move(partitioner), func); + t.get(); +} + +} // namespace detail + +// Run a function for each element in a range +template +void parallel_for(Sched& sched, Range&& range, const Func& func) +{ + detail::internal_parallel_for(sched, async::to_partitioner(std::forward(range)), func); +} + +// Overload with default scheduler +template +void parallel_for(Range&& range, const Func& func) +{ + async::parallel_for(::async::default_scheduler(), range, func); +} + +// Overloads with std::initializer_list +template +void parallel_for(Sched& sched, std::initializer_list range, const Func& func) +{ + async::parallel_for(sched, async::make_range(range.begin(), range.end()), func); +} +template +void parallel_for(std::initializer_list range, const Func& func) +{ + async::parallel_for(async::make_range(range.begin(), range.end()), func); +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/parallel_invoke.h b/3party/asyncplusplus/include/async++/parallel_invoke.h new file mode 100644 index 0000000..0734bb6 --- /dev/null +++ b/3party/asyncplusplus/include/async++/parallel_invoke.h @@ -0,0 +1,70 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Recursively split the arguments so tasks are spawned in parallel +template +struct parallel_invoke_internal { + template + static void run(Sched& sched, const Tuple& args) + { + auto&& t = async::local_spawn(sched, [&sched, &args] { + parallel_invoke_internal::run(sched, args); + }); + parallel_invoke_internal::run(sched, args); + t.get(); + } +}; +template +struct parallel_invoke_internal { + template + static void run(Sched&, const Tuple& args) + { + // Make sure to preserve the rvalue/lvalue-ness of the original parameter + std::forward::type>(std::get(args))(); + } +}; +template +struct parallel_invoke_internal { + template + static void run(Sched&, const Tuple&) {} +}; + +} // namespace detail + +// Run several functions in parallel, optionally using the specified scheduler. +template +typename std::enable_if::value>::type parallel_invoke(Sched& sched, Args&&... args) +{ + detail::parallel_invoke_internal<0, sizeof...(Args)>::run(sched, std::forward_as_tuple(std::forward(args)...)); +} +template +void parallel_invoke(Args&&... args) +{ + async::parallel_invoke(::async::default_scheduler(), std::forward(args)...); +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/parallel_reduce.h b/3party/asyncplusplus/include/async++/parallel_reduce.h new file mode 100644 index 0000000..06545f6 --- /dev/null +++ b/3party/asyncplusplus/include/async++/parallel_reduce.h @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Default map function which simply passes its parameter through unmodified +struct default_map { + template + T&& operator()(T&& x) const + { + return std::forward(x); + } +}; + +// Internal implementation of parallel_map_reduce that only accepts a +// partitioner argument. +template +Result internal_parallel_map_reduce(Sched& sched, Partitioner partitioner, Result init, const MapFunc& map, const ReduceFunc& reduce) +{ + // Split the partition, run inline if no more splits are possible + auto subpart = partitioner.split(); + if (subpart.begin() == subpart.end()) { + Result out = init; + for (auto&& i: partitioner) + out = reduce(std::move(out), map(std::forward(i))); + return out; + } + + // Run the function over each half in parallel + auto&& t = async::local_spawn(sched, [&sched, &subpart, init, &map, &reduce] { + return detail::internal_parallel_map_reduce(sched, std::move(subpart), init, map, reduce); + }); + Result out = detail::internal_parallel_map_reduce(sched, std::move(partitioner), init, map, reduce); + return reduce(std::move(out), t.get()); +} + +} // namespace detail + +// Run a function for each element in a range and then reduce the results of that function to a single value +template +Result parallel_map_reduce(Sched& sched, Range&& range, Result init, const MapFunc& map, const ReduceFunc& reduce) +{ + return detail::internal_parallel_map_reduce(sched, async::to_partitioner(std::forward(range)), init, map, reduce); +} + +// Overload with default scheduler +template +Result parallel_map_reduce(Range&& range, Result init, const MapFunc& map, const ReduceFunc& reduce) +{ + return async::parallel_map_reduce(::async::default_scheduler(), range, init, map, reduce); +} + +// Overloads with std::initializer_list +template +Result parallel_map_reduce(Sched& sched, std::initializer_list range, Result init, const MapFunc& map, const ReduceFunc& reduce) +{ + return async::parallel_map_reduce(sched, async::make_range(range.begin(), range.end()), init, map, reduce); +} +template +Result parallel_map_reduce(std::initializer_list range, Result init, const MapFunc& map, const ReduceFunc& reduce) +{ + return async::parallel_map_reduce(async::make_range(range.begin(), range.end()), init, map, reduce); +} + +// Variant with identity map operation +template +Result parallel_reduce(Sched& sched, Range&& range, Result init, const ReduceFunc& reduce) +{ + return async::parallel_map_reduce(sched, range, init, detail::default_map(), reduce); +} +template +Result parallel_reduce(Range&& range, Result init, const ReduceFunc& reduce) +{ + return async::parallel_reduce(::async::default_scheduler(), range, init, reduce); +} +template +Result parallel_reduce(Sched& sched, std::initializer_list range, Result init, const ReduceFunc& reduce) +{ + return async::parallel_reduce(sched, async::make_range(range.begin(), range.end()), init, reduce); +} +template +Result parallel_reduce(std::initializer_list range, Result init, const ReduceFunc& reduce) +{ + return async::parallel_reduce(async::make_range(range.begin(), range.end()), init, reduce); +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/partitioner.h b/3party/asyncplusplus/include/async++/partitioner.h new file mode 100644 index 0000000..f6403bd --- /dev/null +++ b/3party/asyncplusplus/include/async++/partitioner.h @@ -0,0 +1,196 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Partitioners are essentially ranges with an extra split() function. The +// split() function returns a partitioner containing a range to be executed in a +// child task and modifies the parent partitioner's range to represent the rest +// of the original range. If the range cannot be split any more then split() +// should return an empty range. + +// Detect whether a range is a partitioner +template().split())> +two& is_partitioner_helper(int); +template +one& is_partitioner_helper(...); +template +struct is_partitioner: public std::integral_constant(0)) - 1> {}; + +// Automatically determine a grain size for a sequence length +inline std::size_t auto_grain_size(std::size_t dist) +{ + // Determine the grain size automatically using a heuristic + std::size_t grain = dist / (8 * hardware_concurrency()); + if (grain < 1) + grain = 1; + if (grain > 2048) + grain = 2048; + return grain; +} + +template +class static_partitioner_impl { + Iter iter_begin, iter_end; + std::size_t grain; + +public: + static_partitioner_impl(Iter begin_, Iter end_, std::size_t grain_) + : iter_begin(begin_), iter_end(end_), grain(grain_) {} + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } + static_partitioner_impl split() + { + // Don't split if below grain size + std::size_t length = std::distance(iter_begin, iter_end); + static_partitioner_impl out(iter_end, iter_end, grain); + if (length <= grain) + return out; + + // Split our range in half + iter_end = iter_begin; + std::advance(iter_end, (length + 1) / 2); + out.iter_begin = iter_end; + return out; + } +}; + +template +class auto_partitioner_impl { + Iter iter_begin, iter_end; + std::size_t grain; + std::size_t num_threads; + std::thread::id last_thread; + +public: + // thread_id is initialized to "no thread" and will be set on first split + auto_partitioner_impl(Iter begin_, Iter end_, std::size_t grain_) + : iter_begin(begin_), iter_end(end_), grain(grain_) {} + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } + auto_partitioner_impl split() + { + // Don't split if below grain size + std::size_t length = std::distance(iter_begin, iter_end); + auto_partitioner_impl out(iter_end, iter_end, grain); + if (length <= grain) + return out; + + // Check if we are in a different thread than we were before + std::thread::id current_thread = std::this_thread::get_id(); + if (current_thread != last_thread) + num_threads = hardware_concurrency(); + + // If we only have one thread, don't split + if (num_threads <= 1) + return out; + + // Split our range in half + iter_end = iter_begin; + std::advance(iter_end, (length + 1) / 2); + out.iter_begin = iter_end; + out.last_thread = current_thread; + last_thread = current_thread; + out.num_threads = num_threads / 2; + num_threads -= out.num_threads; + return out; + } +}; + +} // namespace detail + +// A simple partitioner which splits until a grain size is reached. If a grain +// size is not specified, one is chosen automatically. +template +detail::static_partitioner_impl()))> static_partitioner(Range&& range, std::size_t grain) +{ + return {std::begin(range), std::end(range), grain}; +} +template +detail::static_partitioner_impl()))> static_partitioner(Range&& range) +{ + std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); + return {std::begin(range), std::end(range), grain}; +} + +// A more advanced partitioner which initially divides the range into one chunk +// for each available thread. The range is split further if a chunk gets stolen +// by a different thread. +template +detail::auto_partitioner_impl()))> auto_partitioner(Range&& range) +{ + std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); + return {std::begin(range), std::end(range), grain}; +} + +// Wrap a range in a partitioner. If the input is already a partitioner then it +// is returned unchanged. This allows parallel algorithms to accept both ranges +// and partitioners as parameters. +template +typename std::enable_if::type>::value, Partitioner&&>::type to_partitioner(Partitioner&& partitioner) +{ + return std::forward(partitioner); +} +template +typename std::enable_if::type>::value, detail::auto_partitioner_impl()))>>::type to_partitioner(Range&& range) +{ + return async::auto_partitioner(std::forward(range)); +} + +// Overloads with std::initializer_list +template +detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range) +{ + return async::static_partitioner(async::make_range(range.begin(), range.end())); +} +template +detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range, std::size_t grain) +{ + return async::static_partitioner(async::make_range(range.begin(), range.end()), grain); +} +template +detail::auto_partitioner_impl>().begin())> auto_partitioner(std::initializer_list range) +{ + return async::auto_partitioner(async::make_range(range.begin(), range.end())); +} +template +detail::auto_partitioner_impl>().begin())> to_partitioner(std::initializer_list range) +{ + return async::auto_partitioner(async::make_range(range.begin(), range.end())); +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/range.h b/3party/asyncplusplus/include/async++/range.h new file mode 100644 index 0000000..d23ee0c --- /dev/null +++ b/3party/asyncplusplus/include/async++/range.h @@ -0,0 +1,182 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Range type representing a pair of iterators +template +class range { + Iter iter_begin, iter_end; + +public: + range() = default; + range(Iter a, Iter b) + : iter_begin(a), iter_end(b) {} + + Iter begin() const + { + return iter_begin; + } + Iter end() const + { + return iter_end; + } +}; + +// Construct a range from 2 iterators +template +range make_range(Iter begin, Iter end) +{ + return {begin, end}; +} + +// A range of integers +template +class int_range { + T value_begin, value_end; + + static_assert(std::is_integral::value, "int_range can only be used with integral types"); + +public: + class iterator { + T current; + + explicit iterator(T a) + : current(a) {} + friend class int_range; + + public: + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef iterator pointer; + typedef T reference; + typedef std::random_access_iterator_tag iterator_category; + + iterator() = default; + + T operator*() const + { + return current; + } + T operator[](difference_type offset) const + { + return current + offset; + } + + iterator& operator++() + { + ++current; + return *this; + } + iterator operator++(int) + { + return iterator(current++); + } + iterator& operator--() + { + --current; + return *this; + } + iterator operator--(int) + { + return iterator(current--); + } + + iterator& operator+=(difference_type offset) + { + current += offset; + return *this; + } + iterator& operator-=(difference_type offset) + { + current -= offset; + return *this; + } + + iterator operator+(difference_type offset) const + { + return iterator(current + offset); + } + iterator operator-(difference_type offset) const + { + return iterator(current - offset); + } + + friend iterator operator+(difference_type offset, iterator other) + { + return other + offset; + } + + friend difference_type operator-(iterator a, iterator b) + { + return a.current - b.current; + } + + friend bool operator==(iterator a, iterator b) + { + return a.current == b.current; + } + friend bool operator!=(iterator a, iterator b) + { + return a.current != b.current; + } + friend bool operator>(iterator a, iterator b) + { + return a.current > b.current; + } + friend bool operator<(iterator a, iterator b) + { + return a.current < b.current; + } + friend bool operator>=(iterator a, iterator b) + { + return a.current >= b.current; + } + friend bool operator<=(iterator a, iterator b) + { + return a.current <= b.current; + } + }; + + int_range(T begin, T end) + : value_begin(begin), value_end(end) {} + + iterator begin() const + { + return iterator(value_begin); + } + iterator end() const + { + return iterator(value_end); + } +}; + +// Construct an int_range between 2 values +template +int_range::type> irange(T begin, U end) +{ + return {begin, end}; +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/ref_count.h b/3party/asyncplusplus/include/async++/ref_count.h new file mode 100644 index 0000000..5eb1185 --- /dev/null +++ b/3party/asyncplusplus/include/async++/ref_count.h @@ -0,0 +1,177 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Default deleter which just uses the delete keyword +template +struct default_deleter { + static void do_delete(T* p) + { + delete p; + } +}; + +// Reference-counted object base class +template> +struct ref_count_base { + std::atomic ref_count; + + // By default the reference count is initialized to 1 + explicit ref_count_base(std::size_t count = 1) + : ref_count(count) {} + + void add_ref(std::size_t count = 1) + { + ref_count.fetch_add(count, std::memory_order_relaxed); + } + void remove_ref(std::size_t count = 1) + { + if (ref_count.fetch_sub(count, std::memory_order_release) == count) { + std::atomic_thread_fence(std::memory_order_acquire); + Deleter::do_delete(static_cast(this)); + } + } + void add_ref_unlocked() + { + ref_count.store(ref_count.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); + } + bool is_unique_ref(std::memory_order order) + { + return ref_count.load(order) == 1; + } +}; + +// Pointer to reference counted object, based on boost::intrusive_ptr +template +class ref_count_ptr { + T* p; + +public: + // Note that this doesn't increment the reference count, instead it takes + // ownership of a pointer which you already own a reference to. + explicit ref_count_ptr(T* t) + : p(t) {} + + ref_count_ptr() + : p(nullptr) {} + ref_count_ptr(std::nullptr_t) + : p(nullptr) {} + ref_count_ptr(const ref_count_ptr& other) LIBASYNC_NOEXCEPT + : p(other.p) + { + if (p) + p->add_ref(); + } + ref_count_ptr(ref_count_ptr&& other) LIBASYNC_NOEXCEPT + : p(other.p) + { + other.p = nullptr; + } + ref_count_ptr& operator=(std::nullptr_t) + { + if (p) + p->remove_ref(); + p = nullptr; + return *this; + } + ref_count_ptr& operator=(const ref_count_ptr& other) LIBASYNC_NOEXCEPT + { + if (p) { + p->remove_ref(); + p = nullptr; + } + p = other.p; + if (p) + p->add_ref(); + return *this; + } + ref_count_ptr& operator=(ref_count_ptr&& other) LIBASYNC_NOEXCEPT + { + if (p) { + p->remove_ref(); + p = nullptr; + } + p = other.p; + other.p = nullptr; + return *this; + } + ~ref_count_ptr() + { + if (p) + p->remove_ref(); + } + + T& operator*() const + { + return *p; + } + T* operator->() const + { + return p; + } + T* get() const + { + return p; + } + T* release() + { + T* out = p; + p = nullptr; + return out; + } + + explicit operator bool() const + { + return p != nullptr; + } + friend bool operator==(const ref_count_ptr& a, const ref_count_ptr& b) + { + return a.p == b.p; + } + friend bool operator!=(const ref_count_ptr& a, const ref_count_ptr& b) + { + return a.p != b.p; + } + friend bool operator==(const ref_count_ptr& a, std::nullptr_t) + { + return a.p == nullptr; + } + friend bool operator!=(const ref_count_ptr& a, std::nullptr_t) + { + return a.p != nullptr; + } + friend bool operator==(std::nullptr_t, const ref_count_ptr& a) + { + return a.p == nullptr; + } + friend bool operator!=(std::nullptr_t, const ref_count_ptr& a) + { + return a.p != nullptr; + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/scheduler.h b/3party/asyncplusplus/include/async++/scheduler.h new file mode 100644 index 0000000..6718095 --- /dev/null +++ b/3party/asyncplusplus/include/async++/scheduler.h @@ -0,0 +1,175 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Improved version of std::hardware_concurrency: +// - It never returns 0, 1 is returned instead. +// - It is guaranteed to remain constant for the duration of the program. +LIBASYNC_EXPORT std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT; + +// Task handle used by a wait handler +class task_wait_handle { + detail::task_base* handle; + + // Allow construction in wait_for_task() + friend LIBASYNC_EXPORT void detail::wait_for_task(detail::task_base* t); + task_wait_handle(detail::task_base* t) + : handle(t) {} + + // Execution function for use by wait handlers + template + struct wait_exec_func: private detail::func_base { + template + explicit wait_exec_func(F&& f) + : detail::func_base(std::forward(f)) {} + void operator()(detail::task_base*) + { + // Just call the function directly, all this wrapper does is remove + // the task_base* parameter. + this->get_func()(); + } + }; + +public: + task_wait_handle() + : handle(nullptr) {} + + // Check if the handle is valid + explicit operator bool() const + { + return handle != nullptr; + } + + // Check if the task has finished executing + bool ready() const + { + return detail::is_finished(handle->state.load(std::memory_order_acquire)); + } + + // Queue a function to be executed when the task has finished executing. + template + void on_finish(Func&& func) + { + // Make sure the function type is callable + static_assert(detail::is_callable::value, "Invalid function type passed to on_finish()"); + + auto cont = new detail::task_func::type, wait_exec_func::type>, detail::fake_void>(std::forward(func)); + cont->sched = std::addressof(inline_scheduler()); + handle->add_continuation(inline_scheduler(), detail::task_ptr(cont)); + } +}; + +// Wait handler function prototype +typedef void (*wait_handler)(task_wait_handle t); + +// Set a wait handler to control what a task does when it has "free time", which +// is when it is waiting for another task to complete. The wait handler can do +// other work, but should return when it detects that the task has completed. +// The previously installed handler is returned. +LIBASYNC_EXPORT wait_handler set_thread_wait_handler(wait_handler w) LIBASYNC_NOEXCEPT; + +// Exception thrown if a task_run_handle is destroyed without being run +struct LIBASYNC_EXPORT_EXCEPTION task_not_executed {}; + +// Task handle used in scheduler, acts as a unique_ptr to a task object +class task_run_handle { + detail::task_ptr handle; + + // Allow construction in schedule_task() + template + friend void detail::schedule_task(Sched& sched, detail::task_ptr t); + explicit task_run_handle(detail::task_ptr t) + : handle(std::move(t)) {} + +public: + // Movable but not copyable + task_run_handle() = default; + task_run_handle(task_run_handle&& other) LIBASYNC_NOEXCEPT + : handle(std::move(other.handle)) {} + task_run_handle& operator=(task_run_handle&& other) LIBASYNC_NOEXCEPT + { + handle = std::move(other.handle); + return *this; + } + + // If the task is not executed, cancel it with an exception + ~task_run_handle() + { + if (handle) + handle->vtable->cancel(handle.get(), std::make_exception_ptr(task_not_executed())); + } + + // Check if the handle is valid + explicit operator bool() const + { + return handle != nullptr; + } + + // Run the task and release the handle + void run() + { + handle->vtable->run(handle.get()); + handle = nullptr; + } + + // Run the task but run the given wait handler when waiting for a task, + // instead of just sleeping. + void run_with_wait_handler(wait_handler handler) + { + wait_handler old = set_thread_wait_handler(handler); + run(); + set_thread_wait_handler(old); + } + + // Conversion to and from void pointer. This allows the task handle to be + // sent through C APIs which don't preserve types. + void* to_void_ptr() + { + return handle.release(); + } + static task_run_handle from_void_ptr(void* ptr) + { + return task_run_handle(detail::task_ptr(static_cast(ptr))); + } +}; + +namespace detail { + +// Schedule a task for execution using its scheduler +template +void schedule_task(Sched& sched, task_ptr t) +{ + static_assert(is_scheduler::value, "Type is not a valid scheduler"); + sched.schedule(task_run_handle(std::move(t))); +} + +// Inline scheduler implementation +inline void inline_scheduler_impl::schedule(task_run_handle t) +{ + t.run(); +} + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/scheduler_fwd.h b/3party/asyncplusplus/include/async++/scheduler_fwd.h new file mode 100644 index 0000000..6e124b0 --- /dev/null +++ b/3party/asyncplusplus/include/async++/scheduler_fwd.h @@ -0,0 +1,157 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Forward declarations +class task_run_handle; +class threadpool_scheduler; + +// Scheduler interface: +// A scheduler is any type that implements this function: +// void schedule(async::task_run_handle t); +// This function should result in t.run() being called at some future point. + +namespace detail { + +// Detect whether an object is a scheduler +template().schedule(std::declval()))> +two& is_scheduler_helper(int); +template +one& is_scheduler_helper(...); +template +struct is_scheduler: public std::integral_constant(0)) - 1> {}; + +// Singleton scheduler classes +class thread_scheduler_impl { +public: + LIBASYNC_EXPORT static void schedule(task_run_handle t); +}; +class inline_scheduler_impl { +public: + static void schedule(task_run_handle t); +}; + +// Reference counted pointer to task data +struct task_base; +typedef ref_count_ptr task_ptr; + +// Helper function to schedule a task using a scheduler +template +void schedule_task(Sched& sched, task_ptr t); + +// Wait for the given task to finish. This will call the wait handler currently +// active for this thread, which causes the thread to sleep by default. +LIBASYNC_EXPORT void wait_for_task(task_base* wait_task); + +// Forward-declaration for data used by threadpool_scheduler +struct threadpool_data; + +} // namespace detail + +// Run a task in the current thread as soon as it is scheduled +inline detail::inline_scheduler_impl& inline_scheduler() +{ + static detail::inline_scheduler_impl instance; + return instance; +} + +// Run a task in a separate thread. Note that this scheduler does not wait for +// threads to finish at process exit. You must ensure that all threads finish +// before ending the process. +inline detail::thread_scheduler_impl& thread_scheduler() +{ + static detail::thread_scheduler_impl instance; + return instance; +} + +// Built-in thread pool scheduler with a size that is configurable from the +// LIBASYNC_NUM_THREADS environment variable. If that variable does not exist +// then the number of CPUs in the system is used instead. +LIBASYNC_EXPORT threadpool_scheduler& default_threadpool_scheduler(); + +// Default scheduler that is used when one isn't specified. This defaults to +// default_threadpool_scheduler(), but can be overriden by defining +// LIBASYNC_CUSTOM_DEFAULT_SCHEDULER before including async++.h. Keep in mind +// that in that case async::default_scheduler should be declared before +// including async++.h. +#ifndef LIBASYNC_CUSTOM_DEFAULT_SCHEDULER +inline threadpool_scheduler& default_scheduler() +{ + return default_threadpool_scheduler(); +} +#endif + +// Scheduler that holds a list of tasks which can then be explicitly executed +// by a thread. Both adding and running tasks are thread-safe operations. +class fifo_scheduler { + struct internal_data; + std::unique_ptr impl; + +public: + LIBASYNC_EXPORT fifo_scheduler(); + LIBASYNC_EXPORT ~fifo_scheduler(); + + // Add a task to the queue + LIBASYNC_EXPORT void schedule(task_run_handle t); + + // Try running one task from the queue. Returns false if the queue was empty. + LIBASYNC_EXPORT bool try_run_one_task(); + + // Run all tasks in the queue + LIBASYNC_EXPORT void run_all_tasks(); +}; + +// Scheduler that runs tasks in a work-stealing thread pool of the given size. +// Note that destroying the thread pool before all tasks have completed may +// result in some tasks not being executed. +class threadpool_scheduler { + std::unique_ptr impl; + +public: + LIBASYNC_EXPORT threadpool_scheduler(threadpool_scheduler&& other); + + // Create a thread pool with the given number of threads + LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads); + + // Create a thread pool with the given number of threads. Call `prerun` + // function before execution loop and `postrun` after. + LIBASYNC_EXPORT threadpool_scheduler(std::size_t num_threads, + std::function&& prerun_, + std::function&& postrun_); + + // Destroy the thread pool, tasks that haven't been started are dropped + LIBASYNC_EXPORT ~threadpool_scheduler(); + + // Schedule a task to be run in the thread pool + LIBASYNC_EXPORT void schedule(task_run_handle t); +}; + +namespace detail { + +// Work-around for Intel compiler handling decltype poorly in function returns +typedef std::remove_reference::type default_scheduler_type; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/task.h b/3party/asyncplusplus/include/async++/task.h new file mode 100644 index 0000000..7735c30 --- /dev/null +++ b/3party/asyncplusplus/include/async++/task.h @@ -0,0 +1,579 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Exception thrown when an event_task is destroyed without setting a value +struct LIBASYNC_EXPORT_EXCEPTION abandoned_event_task {}; + +namespace detail { + +// Common code for task and shared_task +template +class basic_task { + // Reference counted internal task object + detail::task_ptr internal_task; + + // Real result type, with void turned into fake_void + typedef typename void_to_fake_void::type internal_result; + + // Type-specific task object + typedef task_result internal_task_type; + + // Friend access + friend async::task; + friend async::shared_task; + template + friend typename T::internal_task_type* get_internal_task(const T& t); + template + friend void set_internal_task(T& t, task_ptr p); + + // Common code for get() + void get_internal() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + + // If the task was canceled, throw the associated exception + get_internal_task(*this)->wait_and_throw(); + } + + // Common code for then() + template + typename continuation_traits::task_type then_internal(Sched& sched, Func&& f, Parent&& parent) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + + // Save a copy of internal_task because it might get moved into exec_func + task_base* my_internal = internal_task.get(); + + // Create continuation + typedef continuation_traits traits; + typedef typename void_to_fake_void::type cont_internal_result; + typedef continuation_exec_func::type, cont_internal_result, typename traits::decay_func, typename traits::is_value_cont, is_task::value> exec_func; + typename traits::task_type cont; + set_internal_task(cont, task_ptr(new task_func(std::forward(f), std::forward(parent)))); + + // Add the continuation to this task + // Avoid an expensive ref-count modification since the task isn't shared yet + get_internal_task(cont)->add_ref_unlocked(); + get_internal_task(cont)->sched = std::addressof(sched); + my_internal->add_continuation(sched, task_ptr(get_internal_task(cont))); + + return cont; + } + +public: + // Task result type + typedef Result result_type; + + // Check if this task is not empty + bool valid() const + { + return internal_task != nullptr; + } + + // Query whether the task has finished executing + bool ready() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + return internal_task->ready(); + } + + // Query whether the task has been canceled with an exception + bool canceled() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + return internal_task->state.load(std::memory_order_acquire) == task_state::canceled; + } + + // Wait for the task to complete + void wait() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + internal_task->wait(); + } + + // Get the exception associated with a canceled task + std::exception_ptr get_exception() const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty task object"); + if (internal_task->wait() == task_state::canceled) + return get_internal_task(*this)->get_exception(); + else + return std::exception_ptr(); + } +}; + +// Common code for event_task specializations +template +class basic_event { + // Reference counted internal task object + detail::task_ptr internal_task; + + // Real result type, with void turned into fake_void + typedef typename detail::void_to_fake_void::type internal_result; + + // Type-specific task object + typedef detail::task_result internal_task_type; + + // Friend access + friend async::event_task; + template + friend typename T::internal_task_type* get_internal_task(const T& t); + + // Common code for set() + template + bool set_internal(T&& result) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + + // Only allow setting the value once + detail::task_state expected = detail::task_state::pending; + if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) + return false; + + LIBASYNC_TRY { + // Store the result and finish + get_internal_task(*this)->set_result(std::forward(result)); + internal_task->finish(); + } LIBASYNC_CATCH(...) { + // At this point we have already committed to setting a value, so + // we can't return the exception to the caller. If we did then it + // could cause concurrent set() calls to fail, thinking a value has + // already been set. Instead, we simply cancel the task with the + // exception we just got. + get_internal_task(*this)->cancel_base(std::current_exception()); + } + return true; + } + +public: + // Movable but not copyable + basic_event(basic_event&& other) LIBASYNC_NOEXCEPT + : internal_task(std::move(other.internal_task)) {} + basic_event& operator=(basic_event&& other) LIBASYNC_NOEXCEPT + { + internal_task = std::move(other.internal_task); + return *this; + } + + // Main constructor + basic_event() + : internal_task(new internal_task_type) + { + internal_task->event_task_got_task = false; + } + + // Cancel events if they are destroyed before they are set + ~basic_event() + { + // This check isn't thread-safe but set_exception does a proper check + if (internal_task && !internal_task->ready() && !internal_task->is_unique_ref(std::memory_order_relaxed)) { +#ifdef LIBASYNC_NO_EXCEPTIONS + // This will result in an abort if the task result is read + set_exception(std::exception_ptr()); +#else + set_exception(std::make_exception_ptr(abandoned_event_task())); +#endif + } + } + + // Get the task linked to this event. This can only be called once. + task get_task() + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + LIBASYNC_ASSERT(!internal_task->event_task_got_task, std::logic_error, "get_task() called twice on event_task"); + + // Even if we didn't trigger an assert, don't return a task if one has + // already been returned. + task out; + if (!internal_task->event_task_got_task) + set_internal_task(out, internal_task); + internal_task->event_task_got_task = true; + return out; + } + + // Cancel the event with an exception and cancel continuations + bool set_exception(std::exception_ptr except) const + { + LIBASYNC_ASSERT(internal_task, std::invalid_argument, "Use of empty event_task object"); + + // Only allow setting the value once + detail::task_state expected = detail::task_state::pending; + if (!internal_task->state.compare_exchange_strong(expected, detail::task_state::locked, std::memory_order_acquire, std::memory_order_relaxed)) + return false; + + // Cancel the task + get_internal_task(*this)->cancel_base(std::move(except)); + return true; + } +}; + +} // namespace detail + +template +class task: public detail::basic_task { +public: + // Movable but not copyable + task() = default; + task(task&& other) LIBASYNC_NOEXCEPT + : detail::basic_task(std::move(other)) {} + task& operator=(task&& other) LIBASYNC_NOEXCEPT + { + detail::basic_task::operator=(std::move(other)); + return *this; + } + + // Get the result of the task + Result get() + { + this->get_internal(); + + // Move the internal state pointer so that the task becomes invalid, + // even if an exception is thrown. + detail::task_ptr my_internal = std::move(this->internal_task); + return detail::fake_void_to_void(static_cast(my_internal.get())->get_result(*this)); + } + + // Add a continuation to the task + template + typename detail::continuation_traits::task_type then(Sched& sched, Func&& f) + { + return this->then_internal(sched, std::forward(f), std::move(*this)); + } + template + typename detail::continuation_traits::task_type then(Func&& f) + { + return then(::async::default_scheduler(), std::forward(f)); + } + + // Create a shared_task from this task + shared_task share() + { + LIBASYNC_ASSERT(this->internal_task, std::invalid_argument, "Use of empty task object"); + + shared_task out; + detail::set_internal_task(out, std::move(this->internal_task)); + return out; + } +}; + +template +class shared_task: public detail::basic_task { + // get() return value: const Result& -or- void + typedef typename std::conditional< + std::is_void::value, + void, + typename std::add_lvalue_reference< + typename std::add_const::type + >::type + >::type get_result; + +public: + // Movable and copyable + shared_task() = default; + + // Get the result of the task + get_result get() const + { + this->get_internal(); + return detail::fake_void_to_void(detail::get_internal_task(*this)->get_result(*this)); + } + + // Add a continuation to the task + template + typename detail::continuation_traits::task_type then(Sched& sched, Func&& f) const + { + return this->then_internal(sched, std::forward(f), *this); + } + template + typename detail::continuation_traits::task_type then(Func&& f) const + { + return then(::async::default_scheduler(), std::forward(f)); + } +}; + +// Special task type which can be triggered manually rather than when a function executes. +template +class event_task: public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task&& other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set(const Result& result) const + { + return this->set_internal(result); + } + bool set(Result&& result) const + { + return this->set_internal(std::move(result)); + } +}; + +// Specialization for references +template +class event_task: public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task&& other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set(Result& result) const + { + return this->set_internal(result); + } +}; + +// Specialization for void +template<> +class event_task: public detail::basic_event { +public: + // Movable but not copyable + event_task() = default; + event_task(event_task&& other) LIBASYNC_NOEXCEPT + : detail::basic_event(std::move(other)) {} + event_task& operator=(event_task&& other) LIBASYNC_NOEXCEPT + { + detail::basic_event::operator=(std::move(other)); + return *this; + } + + // Set the result of the task, mark it as completed and run its continuations + bool set() + { + return this->set_internal(detail::fake_void()); + } +}; + +// Task type returned by local_spawn() +template +class local_task { + // Make sure the function type is callable + typedef typename std::decay::type decay_func; + static_assert(detail::is_callable::value, "Invalid function type passed to local_spawn()"); + + // Task result type + typedef typename detail::remove_task()())>::type result_type; + typedef typename detail::void_to_fake_void::type internal_result; + + // Task execution function type + typedef detail::root_exec_func()())>::value> exec_func; + + // Task object embedded directly. The ref-count is initialized to 1 so it + // will never be freed using delete, only when the local_task is destroyed. + detail::task_func internal_task; + + // Friend access for local_spawn + template + friend local_task local_spawn(S& sched, F&& f); + template + friend local_task local_spawn(F&& f); + + // Constructor, used by local_spawn + local_task(Sched& sched, Func&& f) + : internal_task(std::forward(f)) + { + // Avoid an expensive ref-count modification since the task isn't shared yet + internal_task.add_ref_unlocked(); + detail::schedule_task(sched, detail::task_ptr(&internal_task)); + } + +public: + // Non-movable and non-copyable + local_task(const local_task&) = delete; + local_task& operator=(const local_task&) = delete; + + // Wait for the task to complete when destroying + ~local_task() + { + wait(); + + // Now spin until the reference count drops to 1, since the scheduler + // may still have a reference to the task. + while (!internal_task.is_unique_ref(std::memory_order_acquire)) { +#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140612 + // Some versions of libstdc++ (4.7 and below) don't include a + // definition of std::this_thread::yield(). + sched_yield(); +#else + std::this_thread::yield(); +#endif + } + } + + // Query whether the task has finished executing + bool ready() const + { + return internal_task.ready(); + } + + // Query whether the task has been canceled with an exception + bool canceled() const + { + return internal_task.state.load(std::memory_order_acquire) == detail::task_state::canceled; + } + + // Wait for the task to complete + void wait() + { + internal_task.wait(); + } + + // Get the result of the task + result_type get() + { + internal_task.wait_and_throw(); + return detail::fake_void_to_void(internal_task.get_result(task())); + } + + // Get the exception associated with a canceled task + std::exception_ptr get_exception() const + { + if (internal_task.wait() == detail::task_state::canceled) + return internal_task.get_exception(); + else + return std::exception_ptr(); + } +}; + +// Spawn a function asynchronously +#if (__cplusplus >= 201703L) +// Use std::invoke_result instead of std::result_of for C++17 or greater because std::result_of was deprecated in C++17 and removed in C++20 +template +task>>::type> spawn(Sched& sched, Func&& f) +#else +template +task::type()>::type>::type> spawn(Sched& sched, Func&& f) +#endif +{ + // Using result_of in the function return type to work around bugs in the Intel + // C++ compiler. + + // Make sure the function type is callable + typedef typename std::decay::type decay_func; + static_assert(detail::is_callable::value, "Invalid function type passed to spawn()"); + + // Create task + typedef typename detail::void_to_fake_void()())>::type>::type internal_result; + typedef detail::root_exec_func()())>::value> exec_func; + task()())>::type> out; + detail::set_internal_task(out, detail::task_ptr(new detail::task_func(std::forward(f)))); + + // Avoid an expensive ref-count modification since the task isn't shared yet + detail::get_internal_task(out)->add_ref_unlocked(); + detail::schedule_task(sched, detail::task_ptr(detail::get_internal_task(out))); + + return out; +} +template +decltype(async::spawn(::async::default_scheduler(), std::declval())) spawn(Func&& f) +{ + return async::spawn(::async::default_scheduler(), std::forward(f)); +} + +// Create a completed task containing a value +template +task::type> make_task(T&& value) +{ + task::type> out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); + detail::get_internal_task(out)->set_result(std::forward(value)); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} +template +task make_task(std::reference_wrapper value) +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); + detail::get_internal_task(out)->set_result(value.get()); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} +inline task make_task() +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result)); + detail::get_internal_task(out)->state.store(detail::task_state::completed, std::memory_order_relaxed); + + return out; +} + +// Create a canceled task containing an exception +template +task make_exception_task(std::exception_ptr except) +{ + task out; + + detail::set_internal_task(out, detail::task_ptr(new detail::task_result::type>)); + detail::get_internal_task(out)->set_exception(std::move(except)); + detail::get_internal_task(out)->state.store(detail::task_state::canceled, std::memory_order_relaxed); + + return out; +} + +// Spawn a very limited task which is restricted to the current function and +// joins on destruction. Because local_task is not movable, the result must +// be captured in a reference, like this: +// auto&& x = local_spawn(...); +template +#ifdef __GNUC__ +__attribute__((warn_unused_result)) +#endif +local_task local_spawn(Sched& sched, Func&& f) +{ + // Since local_task is not movable, we construct it in-place and let the + // caller extend the lifetime of the returned object using a reference. + return {sched, std::forward(f)}; +} +template +#ifdef __GNUC__ +__attribute__((warn_unused_result)) +#endif +local_task local_spawn(Func&& f) +{ + return {::async::default_scheduler(), std::forward(f)}; +} + +} // namespace async diff --git a/3party/asyncplusplus/include/async++/task_base.h b/3party/asyncplusplus/include/async++/task_base.h new file mode 100644 index 0000000..377ac8f --- /dev/null +++ b/3party/asyncplusplus/include/async++/task_base.h @@ -0,0 +1,615 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Task states +enum class task_state: unsigned char { + pending, // Task has not completed yet + locked, // Task is locked (used by event_task to prevent double set) + unwrapped, // Task is waiting for an unwrapped task to finish + completed, // Task has finished execution and a result is available + canceled // Task has been canceled and an exception is available +}; + +// Determine whether a task is in a final state +inline bool is_finished(task_state s) +{ + return s == task_state::completed || s == task_state::canceled; +} + +// Virtual function table used to allow dynamic dispatch for task objects. +// While this is very similar to what a compiler would generate with virtual +// functions, this scheme was found to result in significantly smaller +// generated code size. +struct task_base_vtable { + // Destroy the function and result + void (*destroy)(task_base*) LIBASYNC_NOEXCEPT; + + // Run the associated function + void (*run)(task_base*) LIBASYNC_NOEXCEPT; + + // Cancel the task with an exception + void (*cancel)(task_base*, std::exception_ptr&&) LIBASYNC_NOEXCEPT; + + // Schedule the task using its scheduler + void (*schedule)(task_base* parent, task_ptr t); +}; + +// Type-generic base task object +struct task_base_deleter; +struct LIBASYNC_CACHELINE_ALIGN task_base: public ref_count_base { + // Task state + std::atomic state; + + // Whether get_task() was already called on an event_task + bool event_task_got_task; + + // Vector of continuations + continuation_vector continuations; + + // Virtual function table used for dynamic dispatch + const task_base_vtable* vtable; + + // Use aligned memory allocation + static void* operator new(std::size_t size) + { + return aligned_alloc(size, LIBASYNC_CACHELINE_SIZE); + } + static void operator delete(void* ptr) + { + aligned_free(ptr); + } + + // Initialize task state + task_base() + : state(task_state::pending) {} + + // Check whether the task is ready and include an acquire barrier if it is + bool ready() const + { + return is_finished(state.load(std::memory_order_acquire)); + } + + // Run a single continuation + template + void run_continuation(Sched& sched, task_ptr&& cont) + { + LIBASYNC_TRY { + detail::schedule_task(sched, cont); + } LIBASYNC_CATCH(...) { + // This is suboptimal, but better than letting the exception leak + cont->vtable->cancel(cont.get(), std::current_exception()); + } + } + + // Run all of the task's continuations after it has completed or canceled. + // The list of continuations is emptied and locked to prevent any further + // continuations from being added. + void run_continuations() + { + continuations.flush_and_lock([this](task_ptr t) { + const task_base_vtable* vtable_ptr = t->vtable; + vtable_ptr->schedule(this, std::move(t)); + }); + } + + // Add a continuation to this task + template + void add_continuation(Sched& sched, task_ptr cont) + { + // Check for task completion + task_state current_state = state.load(std::memory_order_relaxed); + if (!is_finished(current_state)) { + // Try to add the task to the continuation list. This can fail only + // if the task has just finished, in which case we run it directly. + if (continuations.try_add(std::move(cont))) + return; + } + + // Otherwise run the continuation directly + std::atomic_thread_fence(std::memory_order_acquire); + run_continuation(sched, std::move(cont)); + } + + // Finish the task after it has been executed and the result set + void finish() + { + state.store(task_state::completed, std::memory_order_release); + run_continuations(); + } + + // Wait for the task to finish executing + task_state wait() + { + task_state s = state.load(std::memory_order_acquire); + if (!is_finished(s)) { + wait_for_task(this); + s = state.load(std::memory_order_relaxed); + } + return s; + } +}; + +// Deleter for task_ptr +struct task_base_deleter { + static void do_delete(task_base* p) + { + // Go through the vtable to delete p with its proper type + p->vtable->destroy(p); + } +}; + +// Result type-specific task object +template +struct task_result_holder: public task_base { + union { + alignas(Result) std::uint8_t result[sizeof(Result)]; + alignas(std::exception_ptr) std::uint8_t except[sizeof(std::exception_ptr)]; + + // Scheduler that should be used to schedule this task. The scheduler + // type has been erased and is held by vtable->schedule. + void* sched; + }; + + template + void set_result(T&& t) + { + new(&result) Result(std::forward(t)); + } + + // Return a result using an lvalue or rvalue reference depending on the task + // type. The task parameter is not used, it is just there for overload resolution. + template + Result&& get_result(const task&) + { + return std::move(*reinterpret_cast(&result)); + } + template + const Result& get_result(const shared_task&) + { + return *reinterpret_cast(&result); + } + + // Destroy the result + ~task_result_holder() + { + // Result is only present if the task completed successfully + if (state.load(std::memory_order_relaxed) == task_state::completed) + reinterpret_cast(&result)->~Result(); + } +}; + +// Specialization for references +template +struct task_result_holder: public task_base { + union { + // Store as pointer internally + Result* result; + alignas(std::exception_ptr) std::uint8_t except[sizeof(std::exception_ptr)]; + void* sched; + }; + + void set_result(Result& obj) + { + result = std::addressof(obj); + } + + template + Result& get_result(const task&) + { + return *result; + } + template + Result& get_result(const shared_task&) + { + return *result; + } +}; + +// Specialization for void +template<> +struct task_result_holder: public task_base { + union { + alignas(std::exception_ptr) std::uint8_t except[sizeof(std::exception_ptr)]; + void* sched; + }; + + void set_result(fake_void) {} + + // Get the result as fake_void so that it can be passed to set_result and + // continuations + template + fake_void get_result(const task&) + { + return fake_void(); + } + template + fake_void get_result(const shared_task&) + { + return fake_void(); + } +}; + +template +struct task_result: public task_result_holder { + // Virtual function table for task_result + static const task_base_vtable vtable_impl; + task_result() + { + this->vtable = &vtable_impl; + } + + // Destroy the exception + ~task_result() + { + // Exception is only present if the task was canceled + if (this->state.load(std::memory_order_relaxed) == task_state::canceled) + reinterpret_cast(&this->except)->~exception_ptr(); + } + + // Cancel a task with the given exception + void cancel_base(std::exception_ptr&& except_) + { + set_exception(std::move(except_)); + this->state.store(task_state::canceled, std::memory_order_release); + this->run_continuations(); + } + + // Set the exception value of the task + void set_exception(std::exception_ptr&& except_) + { + new(&this->except) std::exception_ptr(std::move(except_)); + } + + // Get the exception a task was canceled with + std::exception_ptr& get_exception() + { + return *reinterpret_cast(&this->except); + } + + // Wait and throw the exception if the task was canceled + void wait_and_throw() + { + if (this->wait() == task_state::canceled) + LIBASYNC_RETHROW_EXCEPTION(get_exception()); + } + + // Delete the task using its proper type + static void destroy(task_base* t) LIBASYNC_NOEXCEPT + { + delete static_cast*>(t); + } +}; +template +const task_base_vtable task_result::vtable_impl = { + task_result::destroy, // destroy + nullptr, // run + nullptr, // cancel + nullptr // schedule +}; + +// Class to hold a function object, with empty base class optimization +template +struct func_base { + Func func; + + template + explicit func_base(F&& f) + : func(std::forward(f)) {} + Func& get_func() + { + return func; + } +}; +template +struct func_base::value>::type> { + template + explicit func_base(F&& f) + { + new(this) Func(std::forward(f)); + } + ~func_base() + { + get_func().~Func(); + } + Func& get_func() + { + return *reinterpret_cast(this); + } +}; + +// Class to hold a function object and initialize/destroy it at any time +template +struct func_holder { + alignas(Func) std::uint8_t func[sizeof(Func)]; + + Func& get_func() + { + return *reinterpret_cast(&func); + } + template + void init_func(Args&&... args) + { + new(&func) Func(std::forward(args)...); + } + void destroy_func() + { + get_func().~Func(); + } +}; +template +struct func_holder::value>::type> { + Func& get_func() + { + return *reinterpret_cast(this); + } + template + void init_func(Args&&... args) + { + new(this) Func(std::forward(args)...); + } + void destroy_func() + { + get_func().~Func(); + } +}; + +// Task object with an associated function object +// Using private inheritance so empty Func doesn't take up space +template +struct task_func: public task_result, func_holder { + // Virtual function table for task_func + static const task_base_vtable vtable_impl; + template + explicit task_func(Args&&... args) + { + this->vtable = &vtable_impl; + this->init_func(std::forward(args)...); + } + + // Run the stored function + static void run(task_base* t) LIBASYNC_NOEXCEPT + { + LIBASYNC_TRY { + // Dispatch to execution function + static_cast*>(t)->get_func()(t); + } LIBASYNC_CATCH(...) { + cancel(t, std::current_exception()); + } + } + + // Cancel the task + static void cancel(task_base* t, std::exception_ptr&& except) LIBASYNC_NOEXCEPT + { + // Destroy the function object when canceling since it won't be + // used anymore. + static_cast*>(t)->destroy_func(); + static_cast*>(t)->cancel_base(std::move(except)); + } + + // Schedule a continuation task using its scheduler + static void schedule(task_base* parent, task_ptr t) + { + void* sched = static_cast*>(t.get())->sched; + parent->run_continuation(*static_cast(sched), std::move(t)); + } + + // Free the function + ~task_func() + { + // If the task hasn't completed yet, destroy the function object. Note + // that an unwrapped task has already destroyed its function object. + if (this->state.load(std::memory_order_relaxed) == task_state::pending) + this->destroy_func(); + } + + // Delete the task using its proper type + static void destroy(task_base* t) LIBASYNC_NOEXCEPT + { + delete static_cast*>(t); + } +}; +template +const task_base_vtable task_func::vtable_impl = { + task_func::destroy, // destroy + task_func::run, // run + task_func::cancel, // cancel + task_func::schedule // schedule +}; + +// Helper functions to access the internal_task member of a task object, which +// avoids us having to specify half of the functions in the detail namespace +// as friend. Also, internal_task is downcast to the appropriate task_result<>. +template +typename Task::internal_task_type* get_internal_task(const Task& t) +{ + return static_cast(t.internal_task.get()); +} +template +void set_internal_task(Task& t, task_ptr p) +{ + t.internal_task = std::move(p); +} + +// Common code for task unwrapping +template +struct unwrapped_func { + explicit unwrapped_func(task_ptr t) + : parent_task(std::move(t)) {} + void operator()(Child child_task) const + { + // Forward completion state and result to parent task + task_result* parent = static_cast*>(parent_task.get()); + LIBASYNC_TRY { + if (get_internal_task(child_task)->state.load(std::memory_order_relaxed) == task_state::completed) { + parent->set_result(get_internal_task(child_task)->get_result(child_task)); + parent->finish(); + } else { + // We don't call the generic cancel function here because + // the function of the parent task has already been destroyed. + parent->cancel_base(std::exception_ptr(get_internal_task(child_task)->get_exception())); + } + } LIBASYNC_CATCH(...) { + // If the copy/move constructor of the result threw, propagate the exception + parent->cancel_base(std::current_exception()); + } + } + task_ptr parent_task; +}; +template +void unwrapped_finish(task_base* parent_base, Child child_task) +{ + // Destroy the parent task's function since it has been executed + parent_base->state.store(task_state::unwrapped, std::memory_order_relaxed); + static_cast*>(parent_base)->destroy_func(); + + // Set up a continuation on the child to set the result of the parent + LIBASYNC_TRY { + parent_base->add_ref(); + child_task.then(inline_scheduler(), unwrapped_func(task_ptr(parent_base))); + } LIBASYNC_CATCH(...) { + // Use cancel_base here because the function object is already destroyed. + static_cast*>(parent_base)->cancel_base(std::current_exception()); + } +} + +// Execution functions for root tasks: +// - With and without task unwraping +template +struct root_exec_func: private func_base { + template + explicit root_exec_func(F&& f) + : func_base(std::forward(f)) {} + void operator()(task_base* t) + { + static_cast*>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()))); + static_cast*>(t)->destroy_func(); + t->finish(); + } +}; +template +struct root_exec_func: private func_base { + template + explicit root_exec_func(F&& f) + : func_base(std::forward(f)) {} + void operator()(task_base* t) + { + unwrapped_finish(t, std::move(this->get_func())()); + } +}; + +// Execution functions for continuation tasks: +// - With and without task unwraping +// - For void, value-based and task-based continuations +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + static_cast*>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), std::move(parent))); + static_cast*>(t)->destroy_func(); + t->finish(); + } + Parent parent; +}; +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else { + static_cast*>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), get_internal_task(parent)->get_result(parent))); + static_cast*>(t)->destroy_func(); + t->finish(); + } + } + Parent parent; +}; +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else { + static_cast*>(t)->set_result(detail::invoke_fake_void(std::move(this->get_func()), fake_void())); + static_cast*>(t)->destroy_func(); + t->finish(); + } + } + Parent parent; +}; +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), std::move(parent))); + } + Parent parent; +}; +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), get_internal_task(parent)->get_result(parent))); + } + Parent parent; +}; +template +struct continuation_exec_func: private func_base { + template + continuation_exec_func(F&& f, P&& p) + : func_base(std::forward(f)), parent(std::forward

(p)) {} + void operator()(task_base* t) + { + if (get_internal_task(parent)->state.load(std::memory_order_relaxed) == task_state::canceled) + task_func::cancel(t, std::exception_ptr(get_internal_task(parent)->get_exception())); + else + unwrapped_finish(t, detail::invoke_fake_void(std::move(this->get_func()), fake_void())); + } + Parent parent; +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/traits.h b/3party/asyncplusplus/include/async++/traits.h new file mode 100644 index 0000000..087e60b --- /dev/null +++ b/3party/asyncplusplus/include/async++/traits.h @@ -0,0 +1,140 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Pseudo-void type: it takes up no space but can be moved and copied +struct fake_void {}; +template +struct void_to_fake_void { + typedef T type; +}; +template<> +struct void_to_fake_void { + typedef fake_void type; +}; +template +T fake_void_to_void(T&& x) +{ + return std::forward(x); +} +inline void fake_void_to_void(fake_void) {} + +// Check if type is a task type, used to detect task unwraping +template +struct is_task: public std::false_type {}; +template +struct is_task>: public std::true_type {}; +template +struct is_task>: public std::true_type {}; +template +struct is_task>: public std::true_type {}; +template +struct is_task>: public std::true_type {}; + +// Extract the result type of a task if T is a task, otherwise just return T +template +struct remove_task { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; +template +struct remove_task> { + typedef T type; +}; + +// Check if a type is callable with the given arguments +typedef char one[1]; +typedef char two[2]; +template()(std::declval()...))> +two& is_callable_helper(int); +template +one& is_callable_helper(...); +template +struct is_callable; +template +struct is_callable: public std::integral_constant(0)) - 1> {}; + +// Wrapper to run a function object with an optional parameter: +// - void returns are turned into fake_void +// - fake_void parameter will invoke the function with no arguments +template()())>::value>::type> +decltype(std::declval()()) invoke_fake_void(Func&& f) +{ + return std::forward(f)(); +} +template()())>::value>::type> +fake_void invoke_fake_void(Func&& f) +{ + std::forward(f)(); + return fake_void(); +} +template +typename void_to_fake_void()(std::declval()))>::type invoke_fake_void(Func&& f, Param&& p) +{ + return detail::invoke_fake_void([&f, &p] {return std::forward(f)(std::forward(p));}); +} +template +typename void_to_fake_void()())>::type invoke_fake_void(Func&& f, fake_void) +{ + return detail::invoke_fake_void(std::forward(f)); +} + +// Various properties of a continuation function +template()())> +fake_void is_value_cont_helper(const Parent&, int, int); +template()(std::declval().get()))> +std::true_type is_value_cont_helper(const Parent&, int, int); +template()())> +std::true_type is_value_cont_helper(const task&, int, int); +template()())> +std::true_type is_value_cont_helper(const shared_task&, int, int); +template()(std::declval()))> +std::false_type is_value_cont_helper(const Parent&, int, ...); +template +void is_value_cont_helper(const Parent&, ...); +template +struct continuation_traits { + typedef typename std::decay::type decay_func; + typedef decltype(detail::is_value_cont_helper(std::declval(), 0, 0)) is_value_cont; + static_assert(!std::is_void::value, "Parameter type for continuation function is invalid for parent task type"); + typedef typename std::conditional::value, fake_void, typename std::conditional::value, typename void_to_fake_void().get())>::type, Parent>::type>::type param_type; + typedef decltype(detail::fake_void_to_void(detail::invoke_fake_void(std::declval(), std::declval()))) result_type; + typedef task::type> task_type; +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/include/async++/when_all_any.h b/3party/asyncplusplus/include/async++/when_all_any.h new file mode 100644 index 0000000..7166026 --- /dev/null +++ b/3party/asyncplusplus/include/async++/when_all_any.h @@ -0,0 +1,292 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { + +// Result type for when_any +template +struct when_any_result { + // Index of the task that finished first + std::size_t index; + + // List of tasks that were passed in + Result tasks; +}; + +namespace detail { + +// Shared state for when_all +template +struct when_all_state: public ref_count_base> { + event_task event; + Result result; + + when_all_state(std::size_t count) + : ref_count_base>(count) {} + + // When all references are dropped, signal the event + ~when_all_state() + { + event.set(std::move(result)); + } +}; + +// Execution functions for when_all, for ranges and tuples +template +struct when_all_func_range { + std::size_t index; + ref_count_ptr> state; + + when_all_func_range(std::size_t index_, ref_count_ptr> state_) + : index(index_), state(std::move(state_)) {} + + // Copy the completed task object to the shared state. The event is + // automatically signaled when all references are dropped. + void operator()(Task t) const + { + state->result[index] = std::move(t); + } +}; +template +struct when_all_func_tuple { + ref_count_ptr> state; + + when_all_func_tuple(ref_count_ptr> state_) + : state(std::move(state_)) {} + + // Copy the completed task object to the shared state. The event is + // automatically signaled when all references are dropped. + void operator()(Task t) const + { + std::get(state->result) = std::move(t); + } +}; + +// Shared state for when_any +template +struct when_any_state: public ref_count_base> { + event_task> event; + Result result; + + when_any_state(std::size_t count) + : ref_count_base>(count) {} + + // Signal the event when the first task reaches here + void set(std::size_t i) + { + event.set({i, std::move(result)}); + } +}; + +// Execution function for when_any +template +struct when_any_func { + std::size_t index; + ref_count_ptr> state; + + when_any_func(std::size_t index_, ref_count_ptr> state_) + : index(index_), state(std::move(state_)) {} + + // Simply tell the state that our task has finished, it already has a copy + // of the task object. + void operator()(Task) const + { + state->set(index); + } +}; + +// Internal implementation of when_all for variadic arguments +template +void when_all_variadic(when_all_state*) {} +template +void when_all_variadic(when_all_state* state, First&& first, T&&... tasks) +{ + typedef typename std::decay::type task_type; + + // Add a continuation to the task + LIBASYNC_TRY { + first.then(inline_scheduler(), detail::when_all_func_tuple(detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(sizeof...(T)); + LIBASYNC_RETHROW(); + } + + // Add continuations to remaining tasks + detail::when_all_variadic(state, std::forward(tasks)...); +} + +// Internal implementation of when_any for variadic arguments +template +void when_any_variadic(when_any_state*) {} +template +void when_any_variadic(when_any_state* state, First&& first, T&&... tasks) +{ + typedef typename std::decay::type task_type; + + // Add a copy of the task to the results because the event may be + // set before all tasks have finished. + detail::task_base* t = detail::get_internal_task(first); + t->add_ref(); + detail::set_internal_task(std::get(state->result), detail::task_ptr(t)); + + // Add a continuation to the task + LIBASYNC_TRY { + first.then(inline_scheduler(), detail::when_any_func(index, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(sizeof...(T)); + LIBASYNC_RETHROW(); + } + + // Add continuations to remaining tasks + detail::when_any_variadic(state, std::forward(tasks)...); +} + +} // namespace detail + +// Combine a set of tasks into one task which is signaled when all specified tasks finish +template +task::value_type>::type>> when_all(Iter begin, Iter end) +{ + typedef typename std::decay::value_type>::type task_type; + typedef std::vector result_type; + + // Handle empty ranges + if (begin == end) + return make_task(result_type()); + + // Create shared state, initialized with the proper reference count + std::size_t count = std::distance(begin, end); + auto* state = new detail::when_all_state(count); + state->result.resize(count); + auto out = state->event.get_task(); + + // Add a continuation to each task to add its result to the shared state + // Last task sets the event result + for (std::size_t i = 0; begin != end; i++, ++begin) { + LIBASYNC_TRY { + (*begin).then(inline_scheduler(), detail::when_all_func_range(i, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(std::distance(begin, end) - 1); + LIBASYNC_RETHROW(); + } + } + + return out; +} + +// Combine a set of tasks into one task which is signaled when one of the tasks finishes +template +task::value_type>::type>>> when_any(Iter begin, Iter end) +{ + typedef typename std::decay::value_type>::type task_type; + typedef std::vector result_type; + + // Handle empty ranges + if (begin == end) + return make_task(when_any_result()); + + // Create shared state, initialized with the proper reference count + std::size_t count = std::distance(begin, end); + auto* state = new detail::when_any_state(count); + state->result.resize(count); + auto out = state->event.get_task(); + + // Add a continuation to each task to set the event. First one wins. + for (std::size_t i = 0; begin != end; i++, ++begin) { + // Add a copy of the task to the results because the event may be + // set before all tasks have finished. + detail::task_base* t = detail::get_internal_task(*begin); + t->add_ref(); + detail::set_internal_task(state->result[i], detail::task_ptr(t)); + + LIBASYNC_TRY { + (*begin).then(inline_scheduler(), detail::when_any_func(i, detail::ref_count_ptr>(state))); + } LIBASYNC_CATCH(...) { + // Make sure we don't leak memory if then() throws + state->remove_ref(std::distance(begin, end) - 1); + LIBASYNC_RETHROW(); + } + } + + return out; +} + +// when_all wrapper accepting ranges +template +decltype(async::when_all(std::begin(std::declval()), std::end(std::declval()))) when_all(T&& tasks) +{ + return async::when_all(std::begin(std::forward(tasks)), std::end(std::forward(tasks))); +} + +// when_any wrapper accepting ranges +template +decltype(async::when_any(std::begin(std::declval()), std::end(std::declval()))) when_any(T&& tasks) +{ + return async::when_any(std::begin(std::forward(tasks)), std::end(std::forward(tasks))); +} + +// when_all with variadic arguments +inline task> when_all() +{ + return async::make_task(std::tuple<>()); +} +template +task::type...>> when_all(T&&... tasks) +{ + typedef std::tuple::type...> result_type; + + // Create shared state + auto state = new detail::when_all_state(sizeof...(tasks)); + auto out = state->event.get_task(); + + // Register all the tasks on the event + detail::when_all_variadic<0>(state, std::forward(tasks)...); + + return out; +} + +// when_any with variadic arguments +inline task>> when_any() +{ + return async::make_task(when_any_result>()); +} +template +task::type...>>> when_any(T&&... tasks) +{ + typedef std::tuple::type...> result_type; + + // Create shared state + auto state = new detail::when_any_state(sizeof...(tasks)); + auto out = state->event.get_task(); + + // Register all the tasks on the event + detail::when_any_variadic<0>(state, std::forward(tasks)...); + + return out; +} + +} // namespace async diff --git a/3party/asyncplusplus/src/fifo_queue.h b/3party/asyncplusplus/src/fifo_queue.h new file mode 100644 index 0000000..5556977 --- /dev/null +++ b/3party/asyncplusplus/src/fifo_queue.h @@ -0,0 +1,77 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Queue which holds tasks in FIFO order. Note that this queue is not +// thread-safe and must be protected by a lock. +class fifo_queue { + detail::aligned_array items; + std::size_t head, tail; + +public: + fifo_queue() + : items(32), head(0), tail(0) {} + ~fifo_queue() + { + // Free any unexecuted tasks + for (std::size_t i = head; i != tail; i = (i + 1) & (items.size() - 1)) + task_run_handle::from_void_ptr(items[i]); + } + + // Push a task to the end of the queue + void push(task_run_handle t) + { + // Resize queue if it is full + if (head == ((tail + 1) & (items.size() - 1))) { + detail::aligned_array new_items(items.size() * 2); + for (std::size_t i = 0; i != items.size(); i++) + new_items[i] = items[(i + head) & (items.size() - 1)]; + head = 0; + tail = items.size() - 1; + items = std::move(new_items); + } + + // Push the item + items[tail] = t.to_void_ptr(); + tail = (tail + 1) & (items.size() - 1); + } + + // Pop a task from the front of the queue + task_run_handle pop() + { + // See if an item is available + if (head == tail) + return task_run_handle(); + else { + void* x = items[head]; + head = (head + 1) & (items.size() - 1); + return task_run_handle::from_void_ptr(x); + } + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/src/internal.h b/3party/asyncplusplus/src/internal.h new file mode 100644 index 0000000..4fa08c9 --- /dev/null +++ b/3party/asyncplusplus/src/internal.h @@ -0,0 +1,95 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// For posix_memalign/_aligned_malloc +#ifdef _WIN32 +# include +# ifdef __MINGW32__ +# define _aligned_malloc __mingw_aligned_malloc +# define _aligned_free __mingw_aligned_free +# endif +#else +# include +#endif + +// We don't make use of dynamic TLS initialization/destruction so we can just +// use the legacy TLS attributes. +#ifdef __GNUC__ +# define THREAD_LOCAL __thread +#elif defined (_MSC_VER) +# define THREAD_LOCAL __declspec(thread) +#else +# define THREAD_LOCAL thread_local +#endif + +// GCC, Clang and the Linux version of the Intel compiler and MSVC 2015 support +// thread-safe initialization of function-scope static variables. +#ifdef __GNUC__ +# define HAVE_THREAD_SAFE_STATIC +#elif _MSC_VER >= 1900 && !defined(__INTEL_COMPILER) +# define HAVE_THREAD_SAFE_STATIC +#endif + +// MSVC deadlocks when joining a thread from a static destructor. Use a +// workaround in that case to avoid the deadlock. +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define BROKEN_JOIN_IN_DESTRUCTOR +#endif + +// Apple's iOS has no thread local support yet. They claim that they don't want to +// introduce a binary compatility issue when they got a better implementation available. +// Luckily, pthreads supports some kind of "emulation" for that. This detects if the we +// are compiling for iOS and enables the workaround accordingly. +// It is also possible enabling it forcibly by setting the EMULATE_PTHREAD_THREAD_LOCAL +// macro. Obviously, this will only works on platforms with pthread available. +#if __APPLE__ +# include "TargetConditionals.h" +# if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +# define EMULATE_PTHREAD_THREAD_LOCAL +# endif +#endif + +// Force symbol visibility to hidden unless explicity exported +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility push(hidden) +#endif +#endif + +// Include other internal headers +#include "singleton.h" +#include "task_wait_event.h" +#include "fifo_queue.h" +#include "work_steal_queue.h" diff --git a/3party/asyncplusplus/src/scheduler.cpp b/3party/asyncplusplus/src/scheduler.cpp new file mode 100644 index 0000000..9afb0cc --- /dev/null +++ b/3party/asyncplusplus/src/scheduler.cpp @@ -0,0 +1,247 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "internal.h" + +// for pthread thread_local emulation +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +# include +#endif + +namespace async { +namespace detail { + +void* aligned_alloc(std::size_t size, std::size_t align) +{ +#ifdef _WIN32 + void* ptr = _aligned_malloc(size, align); + if (!ptr) + LIBASYNC_THROW(std::bad_alloc()); + return ptr; +#else + void* result; + if (posix_memalign(&result, align, size)) + LIBASYNC_THROW(std::bad_alloc()); + else + return result; +#endif +} + +void aligned_free(void* addr) LIBASYNC_NOEXCEPT +{ +#ifdef _WIN32 + _aligned_free(addr); +#else + free(addr); +#endif +} + +// Wait for a task to complete (for threads outside thread pool) +static void generic_wait_handler(task_wait_handle wait_task) +{ + // Create an event to wait on + task_wait_event event; + event.init(); + + // Create a continuation for the task we are waiting for + wait_task.on_finish([&event] { + // Just signal the thread event + event.signal(wait_type::task_finished); + }); + + // Wait for the event to be set + event.wait(); +} + +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +// Wait handler function, per-thread, defaults to generic version +struct pthread_emulation_thread_wait_handler_key_initializer { + pthread_key_t key; + + pthread_emulation_thread_wait_handler_key_initializer() + { + pthread_key_create(&key, nullptr); + } + + ~pthread_emulation_thread_wait_handler_key_initializer() + { + pthread_key_delete(key); + } +}; + +static pthread_key_t get_thread_wait_handler_key() +{ + static pthread_emulation_thread_wait_handler_key_initializer initializer; + return initializer.key; +} + +#else +static THREAD_LOCAL wait_handler thread_wait_handler = generic_wait_handler; +#endif + +static void set_thread_wait_handler(wait_handler handler) +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // we need to call this here, because the pthread initializer is lazy, + // this means the it could be null and we need to set it before trying to + // get or set it + pthread_setspecific(get_thread_wait_handler_key(), reinterpret_cast(handler)); +#else + thread_wait_handler = handler; +#endif +} + +static wait_handler get_thread_wait_handler() +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // we need to call this here, because the pthread initializer is lazy, + // this means the it could be null and we need to set it before trying to + // get or set it + wait_handler handler = (wait_handler) pthread_getspecific(get_thread_wait_handler_key()); + if(handler == nullptr) { + return generic_wait_handler; + } + return handler; +#else + return thread_wait_handler; +#endif +} + +// Wait for a task to complete +void wait_for_task(task_base* wait_task) +{ + // Dispatch to the current thread's wait handler + wait_handler thread_wait_handler = get_thread_wait_handler(); + thread_wait_handler(task_wait_handle(wait_task)); +} + +// The default scheduler is just a thread pool which can be configured +// using environment variables. +class default_scheduler_impl: public threadpool_scheduler { + static std::size_t get_num_threads() + { + // Get the requested number of threads from the environment + // If that fails, use the number of CPUs in the system. + std::size_t num_threads; +#ifdef _MSC_VER + char* s; +# ifdef __cplusplus_winrt + // Windows store applications do not support environment variables + s = nullptr; +# else + // MSVC gives an error when trying to use getenv, work around this + // by using _dupenv_s instead. + _dupenv_s(&s, nullptr, "LIBASYNC_NUM_THREADS"); +# endif +#else + const char *s = std::getenv("LIBASYNC_NUM_THREADS"); +#endif + if (s) + num_threads = std::strtoul(s, nullptr, 10); + else + num_threads = hardware_concurrency(); + +#if defined(_MSC_VER) && !defined(__cplusplus_winrt) + // Free the string allocated by _dupenv_s + free(s); +#endif + + // Make sure the thread count is reasonable + if (num_threads < 1) + num_threads = 1; + return num_threads; + } + +public: + default_scheduler_impl() + : threadpool_scheduler(get_num_threads()) {} +}; + +// Thread scheduler implementation +void thread_scheduler_impl::schedule(task_run_handle t) +{ + // A shared_ptr is used here because not all implementations of + // std::thread support move-only objects. + std::thread([](const std::shared_ptr& t) { + t->run(); + }, std::make_shared(std::move(t))).detach(); +} + +} // namespace detail + +threadpool_scheduler& default_threadpool_scheduler() +{ + return detail::singleton::get_instance(); +} + +// FIFO scheduler implementation +struct fifo_scheduler::internal_data { + detail::fifo_queue queue; + std::mutex lock; +}; +fifo_scheduler::fifo_scheduler() + : impl(new internal_data) {} +fifo_scheduler::~fifo_scheduler() {} +void fifo_scheduler::schedule(task_run_handle t) +{ + std::lock_guard locked(impl->lock); + impl->queue.push(std::move(t)); +} +bool fifo_scheduler::try_run_one_task() +{ + task_run_handle t; + { + std::lock_guard locked(impl->lock); + t = impl->queue.pop(); + } + if (t) { + t.run(); + return true; + } + return false; +} +void fifo_scheduler::run_all_tasks() +{ + while (try_run_one_task()) {} +} + +std::size_t hardware_concurrency() LIBASYNC_NOEXCEPT +{ + // Cache the value because calculating it may be expensive + static std::size_t value = std::thread::hardware_concurrency(); + + // Always return at least 1 core + return value == 0 ? 1 : value; +} + +wait_handler set_thread_wait_handler(wait_handler handler) LIBASYNC_NOEXCEPT +{ + wait_handler old = detail::get_thread_wait_handler(); + detail::set_thread_wait_handler(handler); + return old; +} + +} // namespace async + +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility pop +#endif +#endif diff --git a/3party/asyncplusplus/src/singleton.h b/3party/asyncplusplus/src/singleton.h new file mode 100644 index 0000000..869800d --- /dev/null +++ b/3party/asyncplusplus/src/singleton.h @@ -0,0 +1,73 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Thread-safe singleton wrapper class +#ifdef HAVE_THREAD_SAFE_STATIC +// C++11 guarantees thread safety for static initialization +template +class singleton { +public: + static T& get_instance() + { + static T instance; + return instance; + } +}; +#else +// Some compilers don't support thread-safe static initialization, so emulate it +template +class singleton { + std::mutex lock; + std::atomic init_flag; + alignas(T) std::uint8_t storage[sizeof(T)]; + + static singleton instance; + + // Use a destructor instead of atexit() because the latter does not work + // properly when the singleton is in a library that is unloaded. + ~singleton() + { + if (init_flag.load(std::memory_order_acquire)) + reinterpret_cast(&storage)->~T(); + } + +public: + static T& get_instance() + { + T* ptr = reinterpret_cast(&instance.storage); + if (!instance.init_flag.load(std::memory_order_acquire)) { + std::lock_guard locked(instance.lock); + if (!instance.init_flag.load(std::memory_order_relaxed)) { + new(ptr) T; + instance.init_flag.store(true, std::memory_order_release); + } + } + return *ptr; + } +}; + +template singleton singleton::instance; +#endif + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/src/task_wait_event.h b/3party/asyncplusplus/src/task_wait_event.h new file mode 100644 index 0000000..2f10ff8 --- /dev/null +++ b/3party/asyncplusplus/src/task_wait_event.h @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +namespace async { +namespace detail { + +// Set of events that an task_wait_event can hold +enum wait_type { + // The task that is being waited on has completed + task_finished = 1, + + // A task is available to execute from the scheduler + task_available = 2 +}; + +// OS-supported event object which can be used to wait for either a task to +// finish or for the scheduler to have more work for the current thread. +// +// The event object is lazily initialized to avoid unnecessary API calls. +class task_wait_event { + alignas(std::mutex) std::uint8_t m[sizeof(std::mutex)]; + alignas(std::condition_variable) std::uint8_t c[sizeof(std::condition_variable)]; + int event_mask; + bool initialized; + + std::mutex& mutex() + { + return *reinterpret_cast(&m); + } + std::condition_variable& cond() + { + return *reinterpret_cast(&c); + } + +public: + task_wait_event() + : event_mask(0), initialized(false) {} + + ~task_wait_event() + { + if (initialized) { + mutex().~mutex(); + cond().~condition_variable(); + } + } + + // Initialize the event, must be done before any other functions are called. + void init() + { + if (!initialized) { + new(&m) std::mutex; + new(&c) std::condition_variable; + initialized = true; + } + } + + // Wait for an event to occur. Returns the event(s) that occurred. This also + // clears any pending events afterwards. + int wait() + { + std::unique_lock lock(mutex()); + while (event_mask == 0) + cond().wait(lock); + int result = event_mask; + event_mask = 0; + return result; + } + + // Check if a specific event is ready + bool try_wait(int event) + { + std::lock_guard lock(mutex()); + int result = event_mask & event; + event_mask &= ~event; + return result != 0; + } + + // Signal an event and wake up a sleeping thread + void signal(int event) + { + std::unique_lock lock(mutex()); + event_mask |= event; + + // This must be done while holding the lock otherwise we may end up with + // a use-after-free due to a race with wait(). + cond().notify_one(); + lock.unlock(); + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/asyncplusplus/src/threadpool_scheduler.cpp b/3party/asyncplusplus/src/threadpool_scheduler.cpp new file mode 100644 index 0000000..6318c16 --- /dev/null +++ b/3party/asyncplusplus/src/threadpool_scheduler.cpp @@ -0,0 +1,448 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "internal.h" + +// For GetProcAddress and GetModuleHandle +#ifdef _WIN32 +#include +#endif + +// for pthread thread_local emulation +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +# include +#endif + +namespace async { +namespace detail { + +// Per-thread data, aligned to cachelines to avoid false sharing +struct LIBASYNC_CACHELINE_ALIGN thread_data_t { + work_steal_queue queue; + std::minstd_rand rng; + std::thread handle; +}; + +// Internal data used by threadpool_scheduler +struct threadpool_data { + threadpool_data(std::size_t num_threads) + : thread_data(num_threads), shutdown(false), num_waiters(0), waiters(new task_wait_event*[num_threads]) {} + + threadpool_data(std::size_t num_threads, std::function&& prerun_, std::function&& postrun_) + : thread_data(num_threads), shutdown(false), num_waiters(0), waiters(new task_wait_event*[num_threads]), + prerun(std::move(prerun_)), postrun(std::move(postrun_)) {} + + // Mutex protecting everything except thread_data + std::mutex lock; + + // Array of per-thread data + aligned_array thread_data; + + // Global queue for tasks from outside the pool + fifo_queue public_queue; + + // Shutdown request indicator + bool shutdown; + + // List of threads waiting for tasks to run. num_waiters needs to be atomic + // because it is sometimes read outside the mutex. + std::atomic num_waiters; + std::unique_ptr waiters; + + // Pre/Post run functions. + std::function prerun; + std::function postrun; + +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Shutdown complete event, used instead of thread::join() + std::size_t shutdown_num_threads; + std::condition_variable shutdown_complete_event; +#endif +}; + +// this wrapper encapsulates both the owning_threadpool pointer and the thread id. +// this is done to improve performance on the emulated thread_local reducing the number +// of calls to "pthread_getspecific" +struct threadpool_data_wrapper { + threadpool_data* owning_threadpool; + std::size_t thread_id; + + threadpool_data_wrapper(threadpool_data* owning_threadpool, std::size_t thread_id): + owning_threadpool(owning_threadpool), thread_id(thread_id) { } +}; + +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) +struct pthread_emulation_threadpool_data_initializer { + pthread_key_t key; + + pthread_emulation_threadpool_data_initializer() + { + pthread_key_create(&key, [](void* wrapper_ptr) { + threadpool_data_wrapper* wrapper = static_cast(wrapper_ptr); + delete wrapper; + }); + } + + ~pthread_emulation_threadpool_data_initializer() + { + pthread_key_delete(key); + } +}; + +static pthread_key_t get_local_threadpool_data_key() +{ + static pthread_emulation_threadpool_data_initializer initializer; + return initializer.key; +} + +#else +// Thread pool this thread belongs to, or null if not in pool +static THREAD_LOCAL threadpool_data* owning_threadpool = nullptr; + +// Current thread's index in the pool +static THREAD_LOCAL std::size_t thread_id; +#endif + +static void create_threadpool_data(threadpool_data* owning_threadpool_, std::size_t thread_id_) +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + // the memory allocated here gets deallocated by the lambda declared on the key creation + pthread_setspecific(get_local_threadpool_data_key(), new threadpool_data_wrapper(owning_threadpool_, thread_id_)); +#else + owning_threadpool = owning_threadpool_; + thread_id = thread_id_; +#endif +} + +static threadpool_data_wrapper get_threadpool_data_wrapper() +{ +#if defined(EMULATE_PTHREAD_THREAD_LOCAL) + threadpool_data_wrapper* wrapper = static_cast(pthread_getspecific(get_local_threadpool_data_key())); + if(wrapper == nullptr) { + // if, for some reason, the wrapper is not set, this won't cause a crash + return threadpool_data_wrapper(nullptr, 0); + } + return *wrapper; +#else + return threadpool_data_wrapper(owning_threadpool, thread_id); +#endif +} + +// Try to steal a task from another thread's queue +static task_run_handle steal_task(threadpool_data* impl, std::size_t thread_id) +{ + // Make a list of victim thread ids and shuffle it + std::vector victims(impl->thread_data.size()); + std::iota(victims.begin(), victims.end(), 0); + std::shuffle(victims.begin(), victims.end(), impl->thread_data[thread_id].rng); + + // Try to steal from another thread + for (std::size_t i: victims) { + // Don't try to steal from ourself + if (i == thread_id) + continue; + + if (task_run_handle t = impl->thread_data[i].queue.steal()) + return t; + } + + // No tasks found, but we might have missed one if it was just added. In + // practice this doesn't really matter since it will be handled by another + // thread. + return task_run_handle(); +} + +// Main task stealing loop which is used by worker threads when they have +// nothing to do. +static void thread_task_loop(threadpool_data* impl, std::size_t thread_id, task_wait_handle wait_task) +{ + // Get our thread's data + thread_data_t& current_thread = impl->thread_data[thread_id]; + + // Flag indicating if we have added a continuation to the task + bool added_continuation = false; + + // Event to wait on + task_wait_event event; + + // Loop while waiting for the task to complete + while (true) { + // Check if the task has finished. If we have added a continuation, we + // need to make sure the event has been signaled, otherwise the other + // thread may try to signal it after we have freed it. + if (wait_task && (added_continuation ? event.try_wait(wait_type::task_finished) : wait_task.ready())) + return; + + // Try to get a task from the local queue + if (task_run_handle t = current_thread.queue.pop()) { + t.run(); + continue; + } + + // Stealing loop + while (true) { + // Try to steal a task + if (task_run_handle t = steal_task(impl, thread_id)) { + t.run(); + break; + } + + // Try to fetch from the public queue + std::unique_lock locked(impl->lock); + if (task_run_handle t = impl->public_queue.pop()) { + // Don't hold the lock while running the task + locked.unlock(); + t.run(); + break; + } + + // If shutting down and we don't have a task to wait for, return. + if (!wait_task && impl->shutdown) { +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Notify once all worker threads have exited + if (--impl->shutdown_num_threads == 0) + impl->shutdown_complete_event.notify_one(); +#endif + return; + } + + // Initialize the event object + event.init(); + + // No tasks found, so sleep until something happens. + // If a continuation has not been added yet, add it. + if (wait_task && !added_continuation) { + // Create a continuation for the task we are waiting for + wait_task.on_finish([&event] { + // Signal the thread's event + event.signal(wait_type::task_finished); + }); + added_continuation = true; + } + + // Add our thread to the list of waiting threads + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + impl->waiters[num_waiters_val] = &event; + impl->num_waiters.store(num_waiters_val + 1, std::memory_order_relaxed); + + // Wait for our event to be signaled when a task is scheduled or + // the task we are waiting for has completed. + locked.unlock(); + int events = event.wait(); + locked.lock(); + + // Remove our thread from the list of waiting threads + num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + for (std::size_t i = 0; i < num_waiters_val; i++) { + if (impl->waiters[i] == &event) { + if (i != num_waiters_val - 1) + std::swap(impl->waiters[i], impl->waiters[num_waiters_val - 1]); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + break; + } + } + + // Check again if the task has finished. We have added a + // continuation at this point, so we need to check that the + // continuation has finished signaling the event. + if (wait_task && (events & wait_type::task_finished)) + return; + } + } +} + +// Wait for a task to complete (for worker threads inside thread pool) +static void threadpool_wait_handler(task_wait_handle wait_task) +{ + threadpool_data_wrapper wrapper = get_threadpool_data_wrapper(); + thread_task_loop(wrapper.owning_threadpool, wrapper.thread_id, wait_task); +} + +// Worker thread main loop +static void worker_thread(threadpool_data* owning_threadpool, std::size_t thread_id) +{ + // store on the local thread data + create_threadpool_data(owning_threadpool, thread_id); + + // Set the wait handler so threads from the pool do useful work while + // waiting for another task to finish. + set_thread_wait_handler(threadpool_wait_handler); + + // Seed the random number generator with our id. This gives each thread a + // different steal order. + owning_threadpool->thread_data[thread_id].rng.seed(static_cast(thread_id)); + + // Prerun hook + if (owning_threadpool->prerun) owning_threadpool->prerun(); + + // Main loop, runs until the shutdown signal is recieved + thread_task_loop(owning_threadpool, thread_id, task_wait_handle()); + + // Postrun hook + if (owning_threadpool->postrun) owning_threadpool->postrun(); +} + +// Recursive function to spawn all worker threads in parallel +static void recursive_spawn_worker_thread(threadpool_data* impl, std::size_t index, std::size_t threads) +{ + // If we are down to one thread, go to the worker main loop + if (threads == 1) + worker_thread(impl, index); + else { + // Split thread range into 2 sub-ranges + std::size_t mid = index + threads / 2; + + // Spawn a thread for half of the range + impl->thread_data[mid].handle = std::thread(recursive_spawn_worker_thread, impl, mid, threads - threads / 2); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[mid].handle.detach(); +#endif + + // Tail-recurse to handle our half of the range + recursive_spawn_worker_thread(impl, index, threads / 2); + } +} + +} // namespace detail + +threadpool_scheduler::threadpool_scheduler(threadpool_scheduler&& other) + : impl(std::move(other.impl)) {} + +threadpool_scheduler::threadpool_scheduler(std::size_t num_threads) + : impl(new detail::threadpool_data(num_threads)) +{ + // Start worker threads + impl->thread_data[0].handle = std::thread(detail::recursive_spawn_worker_thread, impl.get(), 0, num_threads); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[0].handle.detach(); +#endif +} + +threadpool_scheduler::threadpool_scheduler(std::size_t num_threads, + std::function&& prerun, + std::function&& postrun) + : impl(new detail::threadpool_data(num_threads, std::move(prerun), std::move(postrun))) +{ + // Start worker threads + impl->thread_data[0].handle = std::thread(detail::recursive_spawn_worker_thread, impl.get(), 0, num_threads); +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + impl->thread_data[0].handle.detach(); +#endif +} + + +// Wait for all currently running tasks to finish +threadpool_scheduler::~threadpool_scheduler() +{ + if (!impl) return; +#ifdef _WIN32 + // Windows kills all threads except one on process exit before calling + // global destructors in DLLs. Waiting for dead threads to exit will likely + // result in deadlocks, so we just exit early if we detect that the process + // is exiting. + auto RtlDllShutdownInProgress = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlDllShutdownInProgress")); + if (RtlDllShutdownInProgress && RtlDllShutdownInProgress()) { +# ifndef BROKEN_JOIN_IN_DESTRUCTOR + // We still need to detach the thread handles otherwise the std::thread + // destructor will throw an exception. + for (std::size_t i = 0; i < impl->thread_data.size(); i++) { + try { + impl->thread_data[i].handle.detach(); + } catch (...) {} + } +# endif + return; + } +#endif + + { + std::unique_lock locked(impl->lock); + + // Signal shutdown + impl->shutdown = true; + + // Wake up any sleeping threads + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + for (std::size_t i = 0; i < num_waiters_val; i++) + impl->waiters[i]->signal(detail::wait_type::task_available); + impl->num_waiters.store(0, std::memory_order_relaxed); + +#ifdef BROKEN_JOIN_IN_DESTRUCTOR + // Wait for the threads to exit + impl->shutdown_num_threads = impl->thread_data.size(); + impl->shutdown_complete_event.wait(locked); +#endif + } + +#ifndef BROKEN_JOIN_IN_DESTRUCTOR + // Wait for the threads to exit + for (std::size_t i = 0; i < impl->thread_data.size(); i++) + impl->thread_data[i].handle.join(); +#endif +} + +// Schedule a task on the thread pool +void threadpool_scheduler::schedule(task_run_handle t) +{ + detail::threadpool_data_wrapper wrapper = detail::get_threadpool_data_wrapper(); + + // Check if we are in the thread pool + if (wrapper.owning_threadpool == impl.get()) { + // Push the task onto our task queue + impl->thread_data[wrapper.thread_id].queue.push(std::move(t)); + + // If there are no sleeping threads, just return. We check outside the + // lock to avoid locking overhead in the fast path. + if (impl->num_waiters.load(std::memory_order_relaxed) == 0) + return; + + // Get a thread to wake up from the list + std::lock_guard locked(impl->lock); + + // Check again if there are waiters + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + if (num_waiters_val == 0) + return; + + // Pop a thread from the list and wake it up + impl->waiters[num_waiters_val - 1]->signal(detail::wait_type::task_available); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + } else { + std::lock_guard locked(impl->lock); + + // Push task onto the public queue + impl->public_queue.push(std::move(t)); + + // Wake up a sleeping thread + size_t num_waiters_val = impl->num_waiters.load(std::memory_order_relaxed); + if (num_waiters_val == 0) + return; + impl->waiters[num_waiters_val - 1]->signal(detail::wait_type::task_available); + impl->num_waiters.store(num_waiters_val - 1, std::memory_order_relaxed); + } +} + +} // namespace async + +#ifndef LIBASYNC_STATIC +#if defined(__GNUC__) && !defined(_WIN32) +# pragma GCC visibility pop +#endif +#endif diff --git a/3party/asyncplusplus/src/work_steal_queue.h b/3party/asyncplusplus/src/work_steal_queue.h new file mode 100644 index 0000000..004ab9a --- /dev/null +++ b/3party/asyncplusplus/src/work_steal_queue.h @@ -0,0 +1,186 @@ +// Copyright (c) 2015 Amanieu d'Antras +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASYNCXX_H_ +# error "Do not include this header directly, include instead." +#endif + +namespace async { +namespace detail { + +// Chase-Lev work stealing deque +// +// Dynamic Circular Work-Stealing Deque +// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.170.1097&rep=rep1&type=pdf +// +// Correct and Efficient Work-Stealing for Weak Memory Models +// http://www.di.ens.fr/~zappa/readings/ppopp13.pdf +class work_steal_queue { + // Circular array of void* + class circular_array { + detail::aligned_array items; + std::unique_ptr previous; + + public: + circular_array(std::size_t n) + : items(n) {} + + std::size_t size() const + { + return items.size(); + } + + void* get(std::size_t index) + { + return items[index & (size() - 1)]; + } + + void put(std::size_t index, void* x) + { + items[index & (size() - 1)] = x; + } + + // Growing the array returns a new circular_array object and keeps a + // linked list of all previous arrays. This is done because other threads + // could still be accessing elements from the smaller arrays. + circular_array* grow(std::size_t top, std::size_t bottom) + { + circular_array* new_array = new circular_array(size() * 2); + new_array->previous.reset(this); + for (std::size_t i = top; i != bottom; i++) + new_array->put(i, get(i)); + return new_array; + } + }; + + std::atomic array; + std::atomic top, bottom; + + // Convert a 2's complement unsigned value to a signed value. We need to do + // this because (b - t) may not always be positive. + static std::ptrdiff_t to_signed(std::size_t x) + { + // Unsigned to signed conversion is implementation-defined if the value + // doesn't fit, so we convert manually. + static_assert(static_cast(PTRDIFF_MAX) + 1 == static_cast(PTRDIFF_MIN), "Wrong integer wrapping behavior"); + if (x > static_cast(PTRDIFF_MAX)) + return static_cast(x - static_cast(PTRDIFF_MIN)) + PTRDIFF_MIN; + else + return static_cast(x); + } + +public: + work_steal_queue() + : array(new circular_array(32)), top(0), bottom(0) {} + ~work_steal_queue() + { + // Free any unexecuted tasks + std::size_t b = bottom.load(std::memory_order_relaxed); + std::size_t t = top.load(std::memory_order_relaxed); + circular_array* a = array.load(std::memory_order_relaxed); + for (std::size_t i = t; i != b; i++) + task_run_handle::from_void_ptr(a->get(i)); + delete a; + } + + // Push a task to the bottom of this thread's queue + void push(task_run_handle x) + { + std::size_t b = bottom.load(std::memory_order_relaxed); + std::size_t t = top.load(std::memory_order_acquire); + circular_array* a = array.load(std::memory_order_relaxed); + + // Grow the array if it is full + if (to_signed(b - t) >= to_signed(a->size())) { + a = a->grow(t, b); + array.store(a, std::memory_order_release); + } + + // Note that we only convert to void* here in case grow throws due to + // lack of memory. + a->put(b, x.to_void_ptr()); + std::atomic_thread_fence(std::memory_order_release); + bottom.store(b + 1, std::memory_order_relaxed); + } + + // Pop a task from the bottom of this thread's queue + task_run_handle pop() + { + std::size_t b = bottom.load(std::memory_order_relaxed); + + // Early exit if queue is empty + std::size_t t = top.load(std::memory_order_relaxed); + if (to_signed(b - t) <= 0) + return task_run_handle(); + + // Make sure bottom is stored before top is read + bottom.store(--b, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); + t = top.load(std::memory_order_relaxed); + + // If the queue is empty, restore bottom and exit + if (to_signed(b - t) < 0) { + bottom.store(b + 1, std::memory_order_relaxed); + return task_run_handle(); + } + + // Fetch the element from the queue + circular_array* a = array.load(std::memory_order_relaxed); + void* x = a->get(b); + + // If this was the last element in the queue, check for races + if (b == t) { + if (!top.compare_exchange_strong(t, t + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + bottom.store(b + 1, std::memory_order_relaxed); + return task_run_handle(); + } + bottom.store(b + 1, std::memory_order_relaxed); + } + return task_run_handle::from_void_ptr(x); + } + + // Steal a task from the top of this thread's queue + task_run_handle steal() + { + // Loop while the compare_exchange fails. This is still lock-free because + // a fail means that another thread has sucessfully stolen a task. + while (true) { + // Make sure top is read before bottom + std::size_t t = top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + std::size_t b = bottom.load(std::memory_order_acquire); + + // Exit if the queue is empty + if (to_signed(b - t) <= 0) + return task_run_handle(); + + // Fetch the element from the queue + circular_array* a = array.load(std::memory_order_consume); + void* x = a->get(t); + + // Attempt to increment top + if (top.compare_exchange_weak(t, t + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) + return task_run_handle::from_void_ptr(x); + } + } +}; + +} // namespace detail +} // namespace async diff --git a/3party/fmt/fmt/format.h b/3party/fmt/fmt/format.h index 3b5a18e..b5e78a9 100644 --- a/3party/fmt/fmt/format.h +++ b/3party/fmt/fmt/format.h @@ -2200,11 +2200,11 @@ private: //FIXME: @tqcq cast_pointer spec_.flags_ = HASH_FLAG; spec_.type_ = 'x'; -#if (sizeof(uintptr_t) < sizeof(void *)) + // #if (sizeof(uintptr_t) < sizeof(void *)) writer_.write_int(reinterpret_cast(p), spec_); -#else - writer_.write_int(reinterpret_cast(p), spec_); -#endif + // #else + // writer_.write_int(reinterpret_cast(p), spec_); + // #endif } // workaround MSVC two-phase lookup issue @@ -2596,9 +2596,10 @@ make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ FMT_WRAP1(func, arg_type, 1) \ FMT_WRAP1(func, arg_type, 2) \ - FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) FMT_WRAP1(func, arg_type, 5) \ - FMT_WRAP1(func, arg_type, 6) FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ - FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) + FMT_WRAP1(func, arg_type, 3) \ + FMT_WRAP1(func, arg_type, 4) FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ + FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) FMT_WRAP1(func, arg_type, 9) \ + FMT_WRAP1(func, arg_type, 10) #define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ template \ diff --git a/CMakeLists.txt b/CMakeLists.txt index d8bfb40..0b25ed6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,22 +20,23 @@ set(BUILD_EXAMPLES OFF) add_library(sled STATIC "") add_subdirectory(3party/gperftools EXCLUDE_FROM_ALL) +add_subdirectory(3party/asyncplusplus EXCLUDE_FROM_ALL) # add_subdirectory(3party/cppuprofile EXCLUDE_FROM_ALL) -add_subdirectory(3party/protobuf-3.21.12 EXCLUDE_FROM_ALL) +# add_subdirectory(3party/protobuf-3.21.12 EXCLUDE_FROM_ALL) if(NOT TARGET marl) - add_subdirectory(3party/marl EXCLUDE_FROM_ALL) + add_subdirectory(3party/marl EXCLUDE_FROM_ALL) endif() if(NOT TARGET rpc_core) - add_subdirectory(3party/rpc_core EXCLUDE_FROM_ALL) + add_subdirectory(3party/rpc_core EXCLUDE_FROM_ALL) endif() if(NOT TARGET fmt) - add_subdirectory(3party/fmt EXCLUDE_FROM_ALL) + add_subdirectory(3party/fmt EXCLUDE_FROM_ALL) endif() if(SLED_LOCATION_PATH) - target_compile_definitions( + target_compile_definitions( sled PRIVATE __SLED_LOCATION_PATH="${SLED_LOCATION_PATH}") endif() # add_subdirectory(3party/eigen EXCLUDE_FROM_ALL) @@ -43,7 +44,9 @@ target_include_directories(sled PUBLIC include 3party/eigen 3party/inja 3party/rxcpp) target_sources( sled - PRIVATE src/filesystem/path.cc + PRIVATE + src/async/async.cc + src/filesystem/path.cc src/log/log.cc src/network/async_resolver.cc src/network/ip_address.cc @@ -81,13 +84,15 @@ target_sources( include(CheckCCompilerFlag) check_c_compiler_flag("-Wl,--whole-archive" SUPPORT_COMPILE_WHOLE_ARCHIVE) if(SUPPORT_COMPILE_WHOLE_ARCHIVE) - set(WHOLE_ARCHIVE_WRAPPER_START "-Wl,--whole-archive") - set(WHOLE_ARCHIVE_WRAPPER_END "-Wl,--no-whole-archive") + set(WHOLE_ARCHIVE_WRAPPER_START "-Wl,--whole-archive") + set(WHOLE_ARCHIVE_WRAPPER_END "-Wl,--no-whole-archive") endif() target_link_libraries( sled - PUBLIC rpc_core fmt marl protobuf::libprotobuf # cppuprofile + PUBLIC rpc_core fmt marl + Async++ + # protobuf::libprotobuf # ${WHOLE_ARCHIVE_WRAPPER_START} tcmalloc_and_profiler_static # ${WHOLE_ARCHIVE_WRAPPER_END} @@ -97,32 +102,34 @@ target_link_libraries( set_target_properties(sled PROPERTIES POSITION_INDEPENDENT_CODE ON) if(SLED_BUILD_BENCHMARK) - if(NOT TARGET benchmark) - find_package(benchmark REQUIRED) - endif() + if(NOT TARGET benchmark) + find_package(benchmark REQUIRED) + endif() - add_executable( + add_executable( sled_benchmark src/random_bench.cc src/strings/base64_bench.cc src/system/fiber/fiber_bench.cc src/system/thread_pool_bench.cc src/system_time_bench.cc) - target_link_libraries(sled_benchmark PRIVATE sled benchmark::benchmark + target_link_libraries(sled_benchmark PRIVATE sled benchmark::benchmark benchmark::benchmark_main) endif(SLED_BUILD_BENCHMARK) if(SLED_BUILD_TESTS) - include(FetchContent) - FetchContent_Declare( + include(FetchContent) + FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip ) - FetchContent_MakeAvailable(googletest) - add_executable( + FetchContent_MakeAvailable(googletest) + add_executable( sled_tests # src/exec/just_test.cc + src/async/async_test.cc src/any_test.cc src/filesystem/path_test.cc src/futures/promise_test.cc + src/futures/detail/just_test.cc src/log/fmt_test.cc # src/profiling/profiling_test.cc src/strings/base64_test.cc @@ -134,20 +141,20 @@ if(SLED_BUILD_TESTS) src/system/thread_pool_test.cc src/rx_test.cc src/uri_test.cc) - target_link_libraries(sled_tests PRIVATE sled GTest::gtest GTest::gtest_main) - add_test(NAME sled_tests COMMAND sled_tests) + target_link_libraries(sled_tests PRIVATE sled GTest::gtest GTest::gtest_main) + add_test(NAME sled_tests COMMAND sled_tests) endif(SLED_BUILD_TESTS) if(SLED_BUILD_FUZZ) - macro(add_fuzz_test name sources) - add_executable(${name} ${sources}) - target_link_libraries(${name} PRIVATE sled) - target_compile_options(${name} PRIVATE -g -O1 -fsanitize=fuzzer,address + macro(add_fuzz_test name sources) + add_executable(${name} ${sources}) + target_link_libraries(${name} PRIVATE sled) + target_compile_options(${name} PRIVATE -g -O1 -fsanitize=fuzzer,address -fsanitize-coverage=trace-cmp) - target_link_options(${name} PRIVATE -fsanitize=fuzzer,address + target_link_options(${name} PRIVATE -fsanitize=fuzzer,address -fsanitize-coverage=trace-cmp) - endmacro() + endmacro() - add_fuzz_test(base64_fuzz src/strings/base64_fuzz.cc) + add_fuzz_test(base64_fuzz src/strings/base64_fuzz.cc) endif(SLED_BUILD_FUZZ) diff --git a/include/sled/async/async.h b/include/sled/async/async.h new file mode 100644 index 0000000..35f147a --- /dev/null +++ b/include/sled/async/async.h @@ -0,0 +1,23 @@ +#ifndef SLED_ASYNC_ASYNC_H +#define SLED_ASYNC_ASYNC_H + +namespace sled { +class FiberScheduler; +} + +namespace async { +sled::FiberScheduler &default_scheduler(); +} + +#define LIBASYNC_CUSTOM_DEFAULT_SCHEDULER +#include + +namespace sled { +class FiberScheduler { +public: + void schedule(async::task_run_handle t); +}; + +}// namespace sled + +#endif// SLED_ASYNC_ASYNC_H diff --git a/include/sled/exec/detail/just.h b/include/sled/exec/detail/just.h deleted file mode 100644 index a75c370..0000000 --- a/include/sled/exec/detail/just.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SLED_EXEC_DETAIL_JUST_H -#define SLED_EXEC_DETAIL_JUST_H -#pragma once - -#include -#include - -namespace sled { - -template -struct JustOperation { - TReceiver receiver; - T value; - - void Start() { receiver.SetValue(value); } -}; - -template -struct JustSender { - using result_t = T; - T value; - - template - JustOperation Connect(TReceiver &&receiver) - { - return {std::forward(receiver), std::forward(value)}; - } -}; - -template -JustSender -Just(T &&t) -{ - return {std::forward(t)}; -} - -}// namespace sled - -#endif// SLED_EXEC_DETAIL_JUST_H diff --git a/include/sled/exec/detail/retry.h b/include/sled/exec/detail/retry.h deleted file mode 100644 index ec16a18..0000000 --- a/include/sled/exec/detail/retry.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SLED_EXEC_DETAIL_RETRY_H -#define SLED_EXEC_DETAIL_RETRY_H -#include -#include -#include -#pragma once - -#include "traits.h" - -namespace sled { - -struct RetryState { - int retry_count; - bool need_retry; -}; - -template -struct RetryReceiver { - TReceiver receiver; - std::shared_ptr state; - - template - void SetValue(T &&value) - { - receiver.SetValue(value); - } - - void SetError(std::exception_ptr err) - { - if (state->retry_count < 0) {} - } - - void SetStopped() { receiver.SetStopped(); } -}; - -template -struct RetryOperation { - ConnectResultT> op; - std::shared_ptr state; - - void Start() {} -}; - -template -struct RetrySender { - using S = typename std::remove_cv::type>::type; - using result_t = SenderResultT; - S sender; - int retry_count; - - template - RetryOperation Connect(TReceiver &&receiver) - { - auto retry_state = std::make_shared(new RetryState{retry_count, false}); - return {sender.Connect(RetryReceiver{receiver, retry_state}), retry_state}; - } -}; - -template -RetrySender -Retry(TSender &&sender, int retry_count) -{ - return {std::forward(sender), retry_count}; -} - -}// namespace sled -#endif// SLED_EXEC_DETAIL_RETRY_H diff --git a/include/sled/exec/detail/sync_wait.h b/include/sled/exec/detail/sync_wait.h deleted file mode 100644 index 63dbace..0000000 --- a/include/sled/exec/detail/sync_wait.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef SLED_EXEC_DETAIL_SYNC_WAIT_H -#define SLED_EXEC_DETAIL_SYNC_WAIT_H -#pragma once - -#include "sled/optional.h" -#include "sled/synchronization/mutex.h" -#include "traits.h" -#include - -namespace sled { -struct SyncWaitState { - sled::Mutex lock; - sled::ConditionVariable cv; - std::exception_ptr err; - bool done = false; -}; - -template -struct SyncWaitReceiver { - SyncWaitState &data; - sled::optional &value; - - void SetValue(T &&val) - { - sled::MutexLock lock(&data.lock); - value.emplace(val); - data.done = true; - data.cv.NotifyOne(); - } - - void SetError(std::exception_ptr err) - { - sled::MutexLock lock(&data.lock); - data.err = err; - data.done = true; - data.cv.NotifyOne(); - } - - void SetStopped(std::exception_ptr err) - { - sled::MutexLock lock(&data.lock); - data.done = true; - data.cv.NotifyOne(); - } -}; - -template -sled::optional> -SyncWait(TSender sender) -{ - using T = SenderResultT; - SyncWaitState data; - sled::optional value; - - auto op = sender.Connect(SyncWaitReceiver{data, value}); - op.Start(); - - sled::MutexLock lock(&data.lock); - data.cv.Wait(lock, [&data] { return data.done; }); - - if (data.err) { std::rethrow_exception(data.err); } - - return value; -} - -}// namespace sled - -#endif// SLED_EXEC_DETAIL_SYNC_WAIT_H diff --git a/include/sled/exec/detail/then.h b/include/sled/exec/detail/then.h deleted file mode 100644 index 77b403b..0000000 --- a/include/sled/exec/detail/then.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef SLED_EXEC_DETAIL_THEN_H -#define SLED_EXEC_DETAIL_THEN_H -#pragma once - -#include "traits.h" -#include -#include - -namespace sled { -template -struct ThenReceiver { - TReceiver receiver; - F func; - - template - void SetValue(T &&value) - { - try { - receiver.SetValue(func(std::forward(value))); - } catch (...) { - receiver.SetError(std::current_exception()); - } - } - - void SetError(std::exception_ptr err) { receiver.SetError(err); } - - void SetStopped() { receiver.SetStopped(); } -}; - -template -struct ThenOperation { - ConnectResultT> op; - - void Start() { op.Start(); } -}; - -template -struct ThenSender { - using S = typename std::remove_cv::type>::type; - using result_t = typename eggs::invoke_result_t>; - S sender; - F func; - - template - ThenOperation Connect(TReceiver &&receiver) - { - return {sender.Connect(ThenReceiver{std::forward(receiver), func})}; - } -}; - -template -ThenSender -Then(TSender &&sender, F &&func) -{ - return {std::forward(sender), std::forward(func)}; -} - -}// namespace sled -#endif// SLED_EXEC_DETAIL_THEN_H diff --git a/include/sled/exec/detail/traits.h b/include/sled/exec/detail/traits.h deleted file mode 100644 index 47cfe03..0000000 --- a/include/sled/exec/detail/traits.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef SLED_EXEC_DETAIL_TRAITS_H -#define SLED_EXEC_DETAIL_TRAITS_H -#pragma once -#include "invoke_result.h" -#include - -namespace sled { -template -using ConnectResultT = decltype(std::declval().Connect(std::declval())); - -template -using SenderResultT = typename TSender::result_t; - -}// namespace sled - -#endif// SLED_EXEC_DETAIL_TRAITS_H diff --git a/include/sled/exec/exec.h b/include/sled/exec/exec.h deleted file mode 100644 index 9a9ea19..0000000 --- a/include/sled/exec/exec.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef SLED_EXEC_EXEC_H -#define SLED_EXEC_EXEC_H -#pragma once -#include "detail/just.h" -#include "detail/sync_wait.h" -#include "detail/then.h" - -namespace sled {} - -#endif// SLED_EXEC_EXEC_H diff --git a/include/sled/futures/future.h b/include/sled/futures/future.h new file mode 100644 index 0000000..a21378b --- /dev/null +++ b/include/sled/futures/future.h @@ -0,0 +1,6 @@ +#ifndef SLED_FUTURES_FUTURE_H +#define SLED_FUTURES_FUTURE_H + +namespace sled {} + +#endif// SLED_FUTURES_FUTURE_H diff --git a/include/sled/sled.h b/include/sled/sled.h index d3a06d0..71aac9f 100644 --- a/include/sled/sled.h +++ b/include/sled/sled.h @@ -2,7 +2,10 @@ #ifndef SLED_SLED_H #define SLED_SLED_H +namespace async {} + // thrid_party +#include "async/async.h" #include "inja.hpp" #include "rx.h" diff --git a/include/sled/utility/move_on_copy.h b/include/sled/utility/move_on_copy.h new file mode 100644 index 0000000..a195e3c --- /dev/null +++ b/include/sled/utility/move_on_copy.h @@ -0,0 +1,31 @@ +#ifndef SLED_UTILITY_MOVE_ON_COPY_H +#define SLED_UTILITY_MOVE_ON_COPY_H +#include +#include + +namespace sled { + +template +struct MoveOnCopy { + using type = typename std::remove_reference::type; + + MoveOnCopy(type &&value) : value(std::move(value)) {} + + MoveOnCopy(const MoveOnCopy &other) : value(std::move(other.value)) {} + + MoveOnCopy(MoveOnCopy &&other) : value(std::move(other.value)) {} + + // MoveOnCopy(MoveOnCopy &&) = delete; + MoveOnCopy &operator=(const MoveOnCopy &) = delete; + + mutable type value; +}; + +template +auto +MakeMoveOnCopy(T &&value) -> MoveOnCopy +{ + return {std::move(value)}; +} +}// namespace sled +#endif// SLED_UTILITY_MOVE_ON_COPY_H diff --git a/src/async/async.cc b/src/async/async.cc new file mode 100644 index 0000000..66f347e --- /dev/null +++ b/src/async/async.cc @@ -0,0 +1,36 @@ +#include "sled/async/async.h" +#include "sled/synchronization/event.h" +#include "sled/system/thread_pool.h" +#include "sled/utility/move_on_copy.h" +// clang-format off +#include + +// clang-format on +namespace async { +sled::FiberScheduler & +default_scheduler() +{ + static sled::FiberScheduler scheduler; + return scheduler; +} +}// namespace async + +namespace sled { + +void +SleepWaitHandler(async::task_wait_handle t) +{ + sled::Event event; + t.on_finish([&] { event.Set(); }); + event.Wait(sled::Event::kForever); +} + +void +FiberScheduler::schedule(async::task_run_handle t) +{ + static ThreadPool thread_pool; + auto move_on_copy = sled::MakeMoveOnCopy(t); + thread_pool.PostTask([move_on_copy] { move_on_copy.value.run_with_wait_handler(SleepWaitHandler); }); +} + +}// namespace sled diff --git a/src/async/async_test.cc b/src/async/async_test.cc new file mode 100644 index 0000000..409a322 --- /dev/null +++ b/src/async/async_test.cc @@ -0,0 +1,10 @@ +#include +#include + +TEST(Async, task) +{ + auto task1 = async::spawn([] { return 42; }).then([](int value) { return value * 3; }).then([](int value) { + EXPECT_EQ(value, 126); + }); + task1.wait(); +} diff --git a/src/futures/detail/just_test.cc b/src/futures/detail/just_test.cc new file mode 100644 index 0000000..1687169 --- /dev/null +++ b/src/futures/detail/just_test.cc @@ -0,0 +1,4 @@ +#include +#include + +TEST(Just, basic) { auto s1 = sled::detail::Just(42); } diff --git a/src/futures/promise_test.cc b/src/futures/promise_test.cc index fffe6f1..d4d675b 100644 --- a/src/futures/promise_test.cc +++ b/src/futures/promise_test.cc @@ -1,4 +1,5 @@ #include +#include TEST(Promise, Basic) { @@ -21,7 +22,7 @@ TEST(Promise, Basic) TEST(Future, Basic) { - auto p = sled::Promise(); + auto p = sled::Promise(); auto future = p.GetFuture() .Then([](int v) { EXPECT_EQ(v, 1);