├── doc └── benchmark.png ├── cmake └── Modules │ └── FindCatch.cmake ├── include └── co2 │ ├── utility │ ├── task_cancelled.hpp │ ├── stack_allocator.hpp │ └── ornion.hpp │ ├── detail │ ├── void.hpp │ ├── copy_or_move.hpp │ ├── storage.hpp │ ├── fixed_storage.hpp │ └── task.hpp │ ├── sync │ ├── event.hpp │ ├── work_group.hpp │ ├── mutex.hpp │ ├── when_all.hpp │ ├── when_any.hpp │ └── multiplexer.hpp │ ├── adapted │ ├── boost_optional.hpp │ └── boost_future.hpp │ ├── .obsolete │ ├── batch_storage.hpp │ └── nth_ready.hpp │ ├── shared_task.hpp │ ├── generator.hpp │ ├── task.hpp │ ├── lazy_task.hpp │ ├── blocking.hpp │ ├── recursive_generator.hpp │ └── coroutine.hpp ├── test ├── common.hpp ├── CMakeLists.txt ├── blocking.cpp ├── range_algo.hpp ├── trigger.hpp ├── boost_optional.cpp ├── generator.cpp ├── benchmark.cpp ├── coroutine.cpp ├── recursive_generator.cpp ├── task.cpp └── shared_task.cpp ├── example ├── asio_tcp_echo_server.cpp ├── asio_threadpool.cpp ├── demo.cpp └── same_fringe.cpp ├── CMakeLists.txt └── README.md /doc/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamboree/co2/HEAD/doc/benchmark.png -------------------------------------------------------------------------------- /cmake/Modules/FindCatch.cmake: -------------------------------------------------------------------------------- 1 | find_path( 2 | CATCH_INCLUDE_DIR 3 | NAMES catch.hpp 4 | PATH_SUFFIXES catch2 5 | DOC "catch include dir" 6 | ) 7 | 8 | set(CATCH_INCLUDE_DIRS ${CATCH_INCLUDE_DIR}) 9 | 10 | include(FindPackageHandleStandardArgs) 11 | 12 | find_package_handle_standard_args(Catch DEFAULT_MSG 13 | CATCH_INCLUDE_DIR) 14 | 15 | mark_as_advanced (CATCH_INCLUDE_DIR) 16 | -------------------------------------------------------------------------------- /include/co2/utility/task_cancelled.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2016 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_TASK_CANCELLED_HPP_INCLUDED 8 | #define CO2_TASK_CANCELLED_HPP_INCLUDED 9 | 10 | namespace co2 11 | { 12 | struct task_cancelled {}; 13 | } 14 | 15 | #endif -------------------------------------------------------------------------------- /test/common.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_TEST_COMMON_HPP_INCLUDED 8 | #define CO2_TEST_COMMON_HPP_INCLUDED 9 | 10 | struct ball {}; 11 | 12 | struct inc_on_finalize 13 | { 14 | int& count; 15 | ~inc_on_finalize() { ++count; } 16 | }; 17 | 18 | #endif -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch REQUIRED) 2 | 3 | add_library(Catch INTERFACE) 4 | target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) 5 | 6 | function(add_catch_test name) 7 | set(TEST_TARGET test_${name}) 8 | add_executable(${TEST_TARGET} 9 | ${name}.cpp 10 | ) 11 | target_link_libraries(${TEST_TARGET} 12 | ${PROJECT_NAME} Catch 13 | ) 14 | set_target_properties(${TEST_TARGET} PROPERTIES 15 | CXX_STANDARD 14 16 | ) 17 | add_test(${TEST_TARGET} ${TEST_TARGET}) 18 | endfunction() 19 | 20 | add_catch_test(blocking) 21 | add_catch_test(boost_optional) 22 | add_catch_test(coroutine) 23 | add_catch_test(generator) 24 | add_catch_test(recursive_generator) 25 | add_catch_test(shared_task) 26 | add_catch_test(task) 27 | -------------------------------------------------------------------------------- /test/blocking.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | 12 | struct pitcher 13 | { 14 | bool await_ready() noexcept 15 | { 16 | return false; 17 | } 18 | 19 | void await_suspend(co2::coroutine<>&) 20 | { 21 | throw ball(); 22 | } 23 | 24 | void await_resume() noexcept {} 25 | }; 26 | 27 | TEST_CASE("throw check") 28 | { 29 | CHECK_THROWS_AS(co2::wait(pitcher{}), ball); 30 | CHECK_THROWS_AS(co2::wait(co2::suspend_always{}), std::system_error); 31 | } -------------------------------------------------------------------------------- /include/co2/detail/void.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_VOID_HPP_INCLUDED 8 | #define CO2_DETAIL_VOID_HPP_INCLUDED 9 | 10 | namespace co2 { namespace detail 11 | { 12 | struct void_ 13 | { 14 | constexpr operator bool() const noexcept 15 | { 16 | return true; 17 | } 18 | 19 | void operator()() const {} 20 | }; 21 | 22 | template 23 | constexpr LHS&& operator,(LHS&& lhs, void_) noexcept 24 | { 25 | return static_cast(lhs); 26 | } 27 | 28 | inline void operator,(void_, void_) noexcept {} 29 | }} 30 | 31 | #endif -------------------------------------------------------------------------------- /test/range_algo.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_TEST_RANGE_ALGO_HPP_INCLUDED 8 | #define CO2_TEST_RANGE_ALGO_HPP_INCLUDED 9 | 10 | template 11 | void skip(Gen& gen, int n) 12 | { 13 | if (!n) 14 | return; 15 | 16 | for (auto i : gen) 17 | if (!--n) 18 | break; 19 | }; 20 | 21 | template 22 | bool empty(Gen& gen) 23 | { 24 | return gen.begin() == gen.end(); 25 | } 26 | 27 | template 28 | bool equal_since(Gen& gen, T n) 29 | { 30 | for (auto i : gen) 31 | { 32 | if (i != n) 33 | return false; 34 | ++n; 35 | } 36 | return true; 37 | } 38 | 39 | #endif -------------------------------------------------------------------------------- /test/trigger.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_TEST_TRIGGER_HPP_INCLUDED 8 | #define CO2_TEST_TRIGGER_HPP_INCLUDED 9 | 10 | #include 11 | 12 | template 13 | struct trigger 14 | { 15 | co2::coroutine<> _coro; 16 | T _val; 17 | 18 | bool await_ready() noexcept 19 | { 20 | return false; 21 | } 22 | 23 | void await_suspend(co2::coroutine<>& coro) 24 | { 25 | _coro = std::move(coro); 26 | } 27 | 28 | T await_resume() 29 | { 30 | return _val; 31 | } 32 | 33 | void operator()(T val) 34 | { 35 | _val = val; 36 | if (_coro) 37 | _coro(); 38 | } 39 | 40 | void cancel() 41 | { 42 | _coro.reset(); 43 | } 44 | }; 45 | 46 | #endif -------------------------------------------------------------------------------- /test/boost_optional.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include 11 | #include "common.hpp" 12 | 13 | auto plus(boost::optional i, int n) CO2_BEG(boost::optional, (i, n)) 14 | { 15 | CO2_AWAIT_APPLY(n +=, i); 16 | CO2_RETURN(n); 17 | } CO2_END 18 | 19 | auto hang(int& terminated) CO2_BEG(boost::optional, (terminated), 20 | inc_on_finalize _{terminated}; 21 | ) 22 | { 23 | CO2_AWAIT(co2::suspend_always{}); 24 | CO2_RETURN(42); 25 | } CO2_END 26 | 27 | TEST_CASE("normal check") 28 | { 29 | CHECK_FALSE(plus({}, 5)); 30 | { 31 | auto ret(plus(6, 5)); 32 | REQUIRE(ret); 33 | CHECK(ret.get() == 11); 34 | } 35 | } 36 | 37 | TEST_CASE("abort check") 38 | { 39 | int terminated = 0; 40 | CHECK_FALSE(hang(terminated)); 41 | CHECK(terminated); 42 | } -------------------------------------------------------------------------------- /include/co2/detail/copy_or_move.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_COPY_OR_MOVE_HPP_INCLUDED 8 | #define CO2_DETAIL_COPY_OR_MOVE_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | 13 | namespace co2 { namespace detail 14 | { 15 | template 16 | struct copy_or_move_iter 17 | { 18 | static It const& wrap(It const& it) 19 | { 20 | return it; 21 | } 22 | }; 23 | 24 | template 25 | struct copy_or_move_iter 26 | { 27 | static std::move_iterator wrap(It const& it) 28 | { 29 | return std::move_iterator(it); 30 | } 31 | }; 32 | 33 | template 34 | struct copy_or_move_impl 35 | { 36 | using type = T&&; 37 | }; 38 | 39 | template 40 | struct copy_or_move_impl 41 | { 42 | using type = std::conditional_t< 43 | std::is_copy_constructible::value, T const&, T&&>; 44 | }; 45 | 46 | template 47 | using copy_or_move_t = typename copy_or_move_impl::type; 48 | 49 | template 50 | inline copy_or_move_t copy_or_move(T& t) 51 | { 52 | return static_cast>(t); 53 | } 54 | }} 55 | 56 | #endif -------------------------------------------------------------------------------- /include/co2/detail/storage.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_STORAGE_HPP_INCLUDED 8 | #define CO2_DETAIL_STORAGE_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace detail 15 | { 16 | template 17 | struct wrap_reference 18 | { 19 | using type = T; 20 | }; 21 | 22 | template 23 | struct wrap_reference 24 | { 25 | using type = std::reference_wrapper; 26 | }; 27 | 28 | template 29 | using wrap_reference_t = typename wrap_reference::type; 30 | 31 | enum class tag 32 | { 33 | // Intermediate state. 34 | pending, running, cancelling, 35 | // Resultant state. 36 | value, exception, cancelled 37 | }; 38 | 39 | template 40 | union storage 41 | { 42 | char data[1]; 43 | T value; 44 | std::exception_ptr exception; 45 | 46 | storage() {} 47 | ~storage() {} 48 | 49 | void destroy(tag t) noexcept 50 | { 51 | switch (t) 52 | { 53 | case tag::value: 54 | value.~T(); 55 | break; 56 | case tag::exception: 57 | exception.~exception_ptr(); 58 | default: 59 | break; 60 | } 61 | } 62 | }; 63 | }} 64 | 65 | #endif -------------------------------------------------------------------------------- /example/asio_tcp_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asio = boost::asio; 10 | 11 | auto session(asio::ip::tcp::socket sock) CO2_BEG(void, (sock), 12 | char buf[1024]; 13 | std::size_t len; 14 | act::error_code ec; 15 | ) 16 | { 17 | CO2_TRY 18 | { 19 | std::cout << "connected: " << sock.remote_endpoint() << std::endl; 20 | for ( ; ; ) 21 | { 22 | CO2_AWAIT_SET(len, act::read_some(sock, asio::buffer(buf), ec)); 23 | if (ec == asio::error::eof) 24 | CO2_RETURN(); 25 | CO2_AWAIT(act::write(sock, asio::buffer(buf, len))); 26 | } 27 | } 28 | CO2_CATCH(std::exception& e) 29 | { 30 | std::cout << "error: " << sock.remote_endpoint() << ": " << e.what() << std::endl; 31 | } 32 | } CO2_END 33 | 34 | auto server(asio::io_service& io, unsigned short port) CO2_BEG(void, (io, port), 35 | asio::ip::tcp::endpoint endpoint{asio::ip::tcp::v4(), port}; 36 | asio::ip::tcp::acceptor acceptor{io, endpoint}; 37 | asio::ip::tcp::socket sock{io}; 38 | ) 39 | { 40 | std::cout << "server running at: " << endpoint << std::endl; 41 | for ( ; ; ) 42 | { 43 | CO2_AWAIT(act::accept(acceptor, sock)); 44 | session(std::move(sock)); 45 | } 46 | } CO2_END 47 | 48 | int main(int argc, char *argv[]) 49 | { 50 | if (argc != 2) 51 | { 52 | std::cerr << "Usage: asio_tcp_echo_server \n"; 53 | return EXIT_FAILURE; 54 | } 55 | asio::io_service io; 56 | server(io, std::atoi(argv[1])); 57 | io.run(); 58 | 59 | return EXIT_SUCCESS; 60 | } -------------------------------------------------------------------------------- /test/generator.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | #include "range_algo.hpp" 12 | 13 | auto range(int i, int e) CO2_BEG(co2::generator, (i, e)) 14 | { 15 | for (; i != e; ++i) 16 | CO2_YIELD(i); 17 | } CO2_END 18 | 19 | auto throws() CO2_BEG(co2::generator, ()) 20 | { 21 | throw ball(); 22 | } CO2_END 23 | 24 | auto forever(int& terminated) CO2_BEG(co2::generator, (terminated), 25 | inc_on_finalize _{terminated}; 26 | ) 27 | { 28 | for (;;) 29 | CO2_YIELD(0); 30 | } CO2_END 31 | 32 | TEST_CASE("move check") 33 | { 34 | auto gen = range(1, 11); 35 | auto moved = std::move(gen); 36 | CHECK(empty(gen)); 37 | CHECK_FALSE(empty(moved)); 38 | } 39 | 40 | TEST_CASE("value check") 41 | { 42 | auto gen = range(0, 10); 43 | SECTION("all") 44 | { 45 | CHECK(equal_since(gen, 0)); 46 | } 47 | SECTION("skip 5") 48 | { 49 | skip(gen, 5); 50 | CHECK(equal_since(gen, 5)); 51 | } 52 | } 53 | 54 | TEST_CASE("throw check") 55 | { 56 | auto gen = throws(); 57 | CHECK_THROWS_AS(gen.begin(), ball); 58 | CHECK(empty(gen)); 59 | } 60 | 61 | TEST_CASE("unwind check") 62 | { 63 | int terminated = 0; 64 | auto gen = forever(terminated); 65 | CHECK_FALSE(terminated); 66 | gen.begin(); 67 | CHECK_FALSE(terminated); 68 | gen = {}; 69 | CHECK(terminated); 70 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2 FATAL_ERROR) 2 | 3 | #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/") 5 | 6 | project(co2 VERSION 1.0.0 LANGUAGES CXX) 7 | 8 | option(CO2_ENABLE_TESTING "Enable testing of the co2 library." OFF) 9 | 10 | # Set the default CMAKE_BUILD_TYPE to Release. 11 | # This should be done before the project command since the latter can set 12 | # CMAKE_BUILD_TYPE itself (it does so for nmake). 13 | if (NOT CMAKE_BUILD_TYPE) 14 | set(CMAKE_BUILD_TYPE Release CACHE STRING 15 | "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") 16 | endif() 17 | 18 | add_library(${PROJECT_NAME} 19 | INTERFACE 20 | ) 21 | 22 | find_package(Boost REQUIRED) 23 | 24 | # Define headers for this library. PUBLIC headers are used for 25 | # compiling the library, and will be added to consumers' build 26 | # paths. 27 | target_include_directories(${PROJECT_NAME} 28 | INTERFACE 29 | include 30 | ${Boost_INCLUDE_DIR} 31 | ) 32 | 33 | # If we have compiler requirements for this library, list them here 34 | # target_compile_features(${PROJECT_NAME} 35 | # PUBLIC 36 | # cxx_alias_templates 37 | # cxx_auto_type 38 | # cxx_decltype_auto 39 | # cxx_user_literals 40 | # cxx_rvalue_references 41 | # cxx_range_for 42 | # cxx_nullptr 43 | # cxx_noexcept 44 | # cxx_inline_namespaces 45 | # ) 46 | 47 | # Below doesn't work for INTERFACE target :( 48 | # set_target_properties(${PROJECT_NAME} PROPERTIES 49 | # CXX_STANDARD 14 50 | # ) 51 | 52 | install(DIRECTORY include/ DESTINATION include) 53 | 54 | if (CO2_ENABLE_TESTING) 55 | enable_testing() 56 | add_subdirectory(test) 57 | endif() 58 | -------------------------------------------------------------------------------- /example/asio_threadpool.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | std::thread::id tasks[1024]; 16 | 17 | auto task(boost::asio::io_service& io, std::thread::id& id) CO2_BEG(co2::task<>, (io, id)) 18 | { 19 | using namespace std::chrono_literals; 20 | 21 | CO2_SUSPEND([&](co2::coroutine<>& c) { io.post([h = c.detach()]{co2::coroutine<>{h}();}); }); 22 | id = std::this_thread::get_id(); 23 | // Do some work... 24 | std::this_thread::sleep_for(10ms); 25 | } CO2_END 26 | 27 | int main() 28 | { 29 | boost::asio::io_service io; 30 | boost::asio::io_service::work work(io); 31 | std::map stats; 32 | std::vector threads(std::thread::hardware_concurrency()); 33 | for (auto& thread : threads) 34 | { 35 | thread = std::thread([&io] { io.run(); }); 36 | stats[thread.get_id()]; 37 | } 38 | std::cout << "threads: " << threads.size() << ", tasks: 1024\n"; 39 | std::cout << "----------------------------------------------\n"; 40 | 41 | for (auto& id : tasks) 42 | task(io, id); 43 | 44 | io.stop(); 45 | for (auto& thread : threads) 46 | thread.join(); 47 | 48 | for (auto id : tasks) 49 | ++stats[id]; 50 | 51 | unsigned idx = 0; 52 | for (auto const& pair : stats) 53 | std::cout << "thread[" << idx++ << "]: " << pair.second << " tasks\n"; 54 | 55 | return EXIT_SUCCESS; 56 | } -------------------------------------------------------------------------------- /test/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void run_coroutines2(boost::coroutines2::coroutine::push_type& c) 7 | { 8 | for (;;) 9 | c(); 10 | } 11 | 12 | void bench_coroutines2(benchmark::State& state) 13 | { 14 | boost::coroutines2::coroutine::pull_type c(run_coroutines2); 15 | while (state.KeepRunning()) 16 | c(); 17 | } 18 | 19 | auto run_co2() CO2_BEG(co2::coroutine<>, ()) 20 | { 21 | for (;;) 22 | CO2_SUSPEND([](co2::coroutine<>&) {}); 23 | } CO2_END 24 | 25 | void bench_co2(benchmark::State& state) 26 | { 27 | auto c = run_co2(); 28 | while (state.KeepRunning()) 29 | c(); 30 | } 31 | 32 | BENCHMARK(bench_coroutines2); 33 | BENCHMARK(bench_co2); 34 | 35 | #ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 36 | namespace std { namespace experimental 37 | { 38 | template 39 | struct coroutine_traits, Ts...> 40 | { 41 | struct promise_type 42 | { 43 | bool initial_suspend() noexcept 44 | { 45 | return false; 46 | } 47 | 48 | bool final_suspend() noexcept 49 | { 50 | return false; 51 | } 52 | 53 | coroutine_handle<> get_return_object() 54 | { 55 | return coroutine_handle::from_promise(*this); 56 | } 57 | 58 | void return_void() noexcept {} 59 | }; 60 | }; 61 | }} 62 | 63 | auto run_msvc_await() -> std::experimental::coroutine_handle<> 64 | { 65 | for (;;) 66 | co_await std::experimental::suspend_always{}; 67 | } 68 | 69 | void bench_msvc(benchmark::State& state) 70 | { 71 | auto c = run_msvc_await(); 72 | while (state.KeepRunning()) 73 | c(); 74 | c.destroy(); 75 | } 76 | 77 | BENCHMARK(bench_msvc); 78 | #endif 79 | 80 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /test/coroutine.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | 12 | auto times(int i) CO2_BEG(co2::coroutine<>, (i)) 13 | { 14 | while (i--) 15 | CO2_AWAIT(co2::suspend_always{}); 16 | } CO2_END 17 | 18 | auto throws_nth(int i) CO2_BEG(co2::coroutine<>, (i)) 19 | { 20 | while (i--) 21 | CO2_AWAIT(co2::suspend_always{}); 22 | throw ball(); 23 | } CO2_END 24 | 25 | auto forever(int& terminated) CO2_BEG(co2::coroutine<>, (terminated), 26 | inc_on_finalize _{terminated}; 27 | ) 28 | { 29 | for (;;) 30 | CO2_AWAIT(co2::suspend_always{}); 31 | } CO2_END 32 | 33 | int total_times(co2::coroutine<>& coro) 34 | { 35 | int i = 0; 36 | while (coro) 37 | { 38 | coro(); 39 | ++i; 40 | } 41 | return i; 42 | } 43 | 44 | TEST_CASE("move check") 45 | { 46 | auto coro = times(5); 47 | auto moved = std::move(coro); 48 | CHECK_FALSE(coro); 49 | CHECK(moved); 50 | } 51 | 52 | TEST_CASE("times check") 53 | { 54 | SECTION("5 times") 55 | { 56 | auto coro = times(5); 57 | CHECK(total_times(coro) == 5); 58 | } 59 | SECTION("0 times") 60 | { 61 | auto coro = times(0); 62 | CHECK_FALSE(coro); 63 | } 64 | } 65 | 66 | TEST_CASE("throw check") 67 | { 68 | CHECK_THROWS_AS(throws_nth(0), ball); 69 | auto coro = throws_nth(2); 70 | CHECK_NOTHROW(coro.resume()); 71 | CHECK_THROWS_AS(coro.resume(), ball); 72 | CHECK_FALSE(coro); 73 | } 74 | 75 | TEST_CASE("unwind check") 76 | { 77 | int terminated = 0; 78 | auto coro = forever(terminated); 79 | CHECK_FALSE(terminated); 80 | coro(); 81 | CHECK_FALSE(terminated); 82 | coro.reset(); 83 | CHECK(terminated); 84 | } -------------------------------------------------------------------------------- /include/co2/detail/fixed_storage.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2016 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_FIXED_STORAGE_HPP_INCLUDED 8 | #define CO2_DETAIL_FIXED_STORAGE_HPP_INCLUDED 9 | 10 | #include 11 | 12 | namespace co2 { namespace detail 13 | { 14 | struct fixed_allocator_base 15 | { 16 | explicit fixed_allocator_base(void* data) noexcept : data(data) {} 17 | fixed_allocator_base(fixed_allocator_base const&) = default; 18 | fixed_allocator_base& operator=(fixed_allocator_base const&) = delete; 19 | 20 | bool operator==(fixed_allocator_base const& other) noexcept 21 | { 22 | return data == other.data; 23 | } 24 | 25 | bool operator!=(fixed_allocator_base const& other) noexcept 26 | { 27 | return data != other.data; 28 | } 29 | 30 | void* data; 31 | }; 32 | 33 | template 34 | struct fixed_storage 35 | { 36 | alignas(std::max_align_t) char data[N]; 37 | 38 | template 39 | struct allocator : fixed_allocator_base 40 | { 41 | using value_type = T; 42 | 43 | using fixed_allocator_base::fixed_allocator_base; 44 | 45 | explicit allocator(fixed_storage& m) noexcept : fixed_allocator_base(m.data) {} 46 | 47 | allocator(fixed_allocator_base const& other) noexcept : fixed_allocator_base(other) {} 48 | 49 | T* allocate(std::size_t) noexcept 50 | { 51 | static_assert(sizeof(T) <= N, "insufficient memory size"); 52 | return static_cast(data); 53 | } 54 | 55 | void deallocate(T*, std::size_t) noexcept {} 56 | }; 57 | 58 | allocator<> alloc() 59 | { 60 | return allocator<>(*this); 61 | } 62 | }; 63 | }} 64 | 65 | #endif -------------------------------------------------------------------------------- /test/recursive_generator.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | #include "range_algo.hpp" 12 | 13 | auto to(int i) CO2_BEG(co2::recursive_generator, (i)) 14 | { 15 | if (i) 16 | CO2_YIELD(to(i - 1)); 17 | CO2_YIELD(i); 18 | } CO2_END 19 | 20 | auto throws_depth(int i, int& terminated) CO2_BEG(co2::recursive_generator, (i, terminated), 21 | inc_on_finalize _{terminated}; 22 | ) 23 | { 24 | if (i) 25 | CO2_YIELD(throws_depth(i - 1, terminated)); 26 | else 27 | throw ball(); 28 | } CO2_END 29 | 30 | auto forever_depth(int i, int& terminated) CO2_BEG(co2::recursive_generator, (i, terminated), 31 | inc_on_finalize _{terminated}; 32 | ) 33 | { 34 | if (i) 35 | CO2_YIELD(forever_depth(i - 1, terminated)); 36 | else 37 | for (;;) 38 | CO2_YIELD(0); 39 | } CO2_END 40 | 41 | TEST_CASE("move check") 42 | { 43 | auto gen = to(10); 44 | auto moved = std::move(gen); 45 | CHECK(empty(gen)); 46 | CHECK_FALSE(empty(moved)); 47 | } 48 | 49 | TEST_CASE("value check") 50 | { 51 | auto gen = to(10); 52 | SECTION("all") 53 | { 54 | CHECK(equal_since(gen, 0)); 55 | } 56 | SECTION("skip 5") 57 | { 58 | skip(gen, 5); 59 | CHECK(equal_since(gen, 5)); 60 | } 61 | } 62 | 63 | TEST_CASE("throw check") 64 | { 65 | int terminated = 0; 66 | auto gen = throws_depth(3, terminated); 67 | CHECK_THROWS_AS(gen.begin(), ball); 68 | CHECK(terminated == 4); 69 | CHECK(empty(gen)); 70 | } 71 | 72 | TEST_CASE("unwind check") 73 | { 74 | int terminated = 0; 75 | auto gen = forever_depth(4, terminated); 76 | CHECK_FALSE(terminated); 77 | gen.begin(); 78 | CHECK_FALSE(terminated); 79 | gen = {}; 80 | CHECK(terminated == 5); 81 | } -------------------------------------------------------------------------------- /include/co2/sync/event.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_EVENT_HPP_INCLUDED 8 | #define CO2_SYNC_EVENT_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | 13 | namespace co2 14 | { 15 | class event 16 | { 17 | std::atomic _then{this}; 18 | 19 | template 20 | void flush(F f) 21 | { 22 | if (auto next = _then.exchange(nullptr, std::memory_order_acquire)) 23 | { 24 | while (next != this) 25 | { 26 | auto then = static_cast(next); 27 | next = coroutine_data(then); 28 | f(then); 29 | } 30 | } 31 | } 32 | 33 | public: 34 | ~event() 35 | { 36 | // Destroy the pending coroutines in case that set() is not called. 37 | flush([](coroutine_handle h) { coroutine<>{h}; }); 38 | } 39 | 40 | void set() noexcept 41 | { 42 | flush([](coroutine_handle h) { coroutine<>{h}(); }); 43 | } 44 | 45 | bool await_ready() noexcept 46 | { 47 | return !_then.load(std::memory_order_relaxed); 48 | } 49 | 50 | bool await_suspend(coroutine<>& cb) noexcept 51 | { 52 | auto prev = _then.load(std::memory_order_relaxed); 53 | auto curr = cb.handle(); 54 | auto& next = coroutine_data(curr); 55 | while (prev) 56 | { 57 | next = prev; 58 | if (_then.compare_exchange_weak(prev, curr, std::memory_order_release)) 59 | { 60 | cb.detach(); 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | void await_resume() noexcept {} 68 | }; 69 | } 70 | 71 | #endif -------------------------------------------------------------------------------- /include/co2/adapted/boost_optional.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_ADAPTED_BOOST_OPTIONAL_HPP_INCLUDED 8 | #define CO2_ADAPTED_BOOST_OPTIONAL_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | 13 | namespace co2 { namespace boost_optional_detail 14 | { 15 | using namespace boost; 16 | 17 | template 18 | struct promise 19 | { 20 | bool initial_suspend() noexcept 21 | { 22 | return false; 23 | } 24 | 25 | bool final_suspend() noexcept 26 | { 27 | return false; 28 | } 29 | 30 | bool cancellation_requested() const noexcept 31 | { 32 | return false; 33 | } 34 | 35 | optional get_return_object(coroutine& coro) 36 | { 37 | optional ret; 38 | _ret = &ret; 39 | coro.resume(); 40 | // At this point, the promise itself and the coroutine frame is 41 | // destroyed. 42 | return ret; 43 | } 44 | 45 | void set_result(T val) 46 | { 47 | *_ret = std::forward(val); 48 | } 49 | 50 | private: 51 | 52 | optional* _ret = nullptr; 53 | }; 54 | }} 55 | 56 | namespace co2 57 | { 58 | template 59 | struct coroutine_traits> 60 | { 61 | using promise_type = boost_optional_detail::promise; 62 | }; 63 | } 64 | 65 | namespace boost 66 | { 67 | template 68 | inline bool await_ready(optional const& opt) noexcept 69 | { 70 | return !!opt; 71 | } 72 | 73 | template 74 | inline void await_suspend(optional const&, co2::coroutine<>&) noexcept 75 | { 76 | // Empty. 77 | } 78 | 79 | template 80 | decltype(auto) await_resume(optional const& opt) 81 | { 82 | return opt.get(); 83 | } 84 | } 85 | 86 | #endif -------------------------------------------------------------------------------- /example/demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | auto coro(int i, int e) CO2_BEG(co2::coroutine<>, (i, e)) 11 | { 12 | for (; i != e; ++i) 13 | { 14 | CO2_AWAIT(co2::suspend_always{}); 15 | std::cout << i; 16 | } 17 | } CO2_END 18 | 19 | auto range(int i, int e) CO2_BEG(co2::generator, (i, e)) 20 | { 21 | for (; i != e; ++i) 22 | CO2_YIELD(i); 23 | } CO2_END 24 | 25 | template 26 | auto recursive_range(Alloc alloc, int a, int b) 27 | CO2_BEG(co2::recursive_generator, (alloc, a, b) new(alloc), 28 | int n = b - a; 29 | ) 30 | { 31 | if (n <= 0) 32 | CO2_RETURN(); 33 | 34 | if (n == 1) 35 | { 36 | CO2_YIELD(a); 37 | CO2_RETURN(); 38 | } 39 | 40 | n = a + n / 2; 41 | CO2_YIELD(recursive_range(alloc, a, n)); 42 | CO2_YIELD(recursive_range(alloc, n, b)); 43 | } CO2_END 44 | 45 | auto stall(co2::coroutine<>& ret) CO2_BEG(co2::task, (ret)) 46 | { 47 | CO2_SUSPEND([&](co2::coroutine<>& c) { ret = std::move(c); }); 48 | CO2_RETURN(0); 49 | } CO2_END 50 | 51 | auto inc(co2::task t) CO2_BEG(co2::task, (t), int n;) 52 | { 53 | CO2_AWAIT_SET(n, t); 54 | CO2_RETURN(n + 1); 55 | } CO2_END 56 | 57 | int main() 58 | { 59 | std::cout << "[coro]\n"; 60 | auto c = coro(1, 10); 61 | while (c) 62 | { 63 | c(); 64 | std::cout << ", "; 65 | } 66 | std::cout << "\n------------\n"; 67 | 68 | std::cout << "[range]\n"; 69 | for (auto i : range(1, 10)) 70 | { 71 | std::cout << i << ", "; 72 | } 73 | std::cout << "\n------------\n"; 74 | 75 | std::cout << "[recursive_range]\n"; 76 | co2::stack_buffer<64 * 1024> buf; 77 | co2::stack_allocator<> alloc(buf); 78 | for (auto i : recursive_range(alloc, 1, 10)) 79 | { 80 | std::cout << i << ", "; 81 | } 82 | std::cout << "\n------------\n"; 83 | 84 | std::cout << "[task]\n"; 85 | auto t = stall(c); 86 | for (int i = 0; i != 65536; ++i) 87 | t = inc(std::move(t)); 88 | std::thread thr([&c] 89 | { 90 | std::this_thread::sleep_for(std::chrono::seconds(1)); 91 | c(); 92 | }); 93 | std::cout << "ans: " << co2::get(t); 94 | thr.join(); 95 | std::cout << "\n------------\n"; 96 | 97 | return EXIT_SUCCESS; 98 | } -------------------------------------------------------------------------------- /include/co2/sync/work_group.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_WORK_GROUP_HPP_INCLUDED 8 | #define CO2_SYNC_WORK_GROUP_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace co2 16 | { 17 | class work_group 18 | { 19 | std::atomic _then{nullptr}; 20 | std::atomic _work_count{0}; 21 | 22 | void push_work() noexcept 23 | { 24 | _work_count.fetch_add(1u, std::memory_order_release); 25 | } 26 | 27 | void pop_work() noexcept 28 | { 29 | if (_work_count.fetch_sub(1u, std::memory_order_relaxed) == 1u) 30 | { 31 | if (auto then = _then.exchange(this, std::memory_order_acquire)) 32 | coroutine<>{static_cast(then)}(); 33 | } 34 | } 35 | 36 | struct work_deleter 37 | { 38 | void operator()(work_group* group) const noexcept 39 | { 40 | group->pop_work(); 41 | } 42 | }; 43 | 44 | public: 45 | class work 46 | { 47 | std::unique_ptr _group; 48 | 49 | public: 50 | explicit work(work_group& group) noexcept : _group(&group) 51 | { 52 | group.push_work(); 53 | } 54 | }; 55 | 56 | ~work_group() 57 | { 58 | BOOST_ASSERT_MSG(await_ready(), "pending work in work_group"); 59 | } 60 | 61 | work create() 62 | { 63 | return work(*this); 64 | } 65 | 66 | bool await_ready() noexcept 67 | { 68 | return !_work_count.load(std::memory_order_relaxed); 69 | } 70 | 71 | bool await_suspend(coroutine<>& cb) noexcept 72 | { 73 | if (_then.exchange(cb.handle(), std::memory_order_release)) 74 | return false; 75 | cb.detach(); 76 | return true; 77 | } 78 | 79 | void await_resume() noexcept 80 | { 81 | _then.store(nullptr, std::memory_order_relaxed); 82 | } 83 | }; 84 | } 85 | 86 | #endif -------------------------------------------------------------------------------- /include/co2/.obsolete/batch_storage.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_BATCH_STORAGE_HPP_INCLUDED 8 | #define CO2_DETAIL_BATCH_STORAGE_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace detail 15 | { 16 | struct batch_storage 17 | { 18 | batch_storage(std::size_t n) noexcept : _n(n) {} 19 | batch_storage(batch_storage const&) = delete; 20 | batch_storage& operator=(batch_storage const&) = delete; 21 | 22 | void* allocate_one(std::size_t bytes) 23 | { 24 | if (_p) 25 | _p = ::operator new(bytes * _n); 26 | return std::exchange(_p, static_cast(_p) + bytes); 27 | } 28 | 29 | ~batch_storage() 30 | { 31 | ::operator delete(_p); 32 | } 33 | 34 | private: 35 | std::size_t _n; 36 | void* _p; 37 | }; 38 | 39 | template 40 | struct batch_allocator; 41 | 42 | template<> 43 | struct batch_allocator<> 44 | { 45 | using value_type = void; 46 | 47 | batch_allocator(batch_storage& storage) noexcept : _storage(storage) {} 48 | batch_allocator(batch_allocator const&) = default; 49 | batch_allocator& operator=(batch_allocator const&) = delete; 50 | 51 | bool operator==(batch_allocator const& other) noexcept 52 | { 53 | return &_storage == &other._storage; 54 | } 55 | 56 | bool operator!=(batch_allocator const& other) noexcept 57 | { 58 | return &_storage != &other._storage; 59 | } 60 | 61 | protected: 62 | 63 | batch_storage& _storage; 64 | }; 65 | 66 | template 67 | struct batch_allocator : batch_allocator<> 68 | { 69 | using value_type = T; 70 | 71 | using batch_allocator<>::batch_allocator; 72 | 73 | batch_allocator(batch_allocator<> const& other) : batch_allocator<>(other) {} 74 | 75 | T* allocate(std::size_t n) 76 | { 77 | BOOST_ASSERT(n == 1); 78 | return static_cast(_storage.allocate_one(sizeof(T))); 79 | } 80 | 81 | void deallocate(T*, std::size_t) noexcept {} 82 | }; 83 | }} 84 | 85 | #endif -------------------------------------------------------------------------------- /include/co2/.obsolete/nth_ready.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_NTH_READY_HPP_INCLUDED 8 | #define CO2_SYNC_NTH_READY_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace detail 15 | { 16 | template 17 | struct nth_ready_awaiter 18 | { 19 | Tuple args; 20 | int n; 21 | 22 | using args_t = std::remove_reference_t; 23 | static constexpr std::size_t tuple_size = std::tuple_size::value; 24 | using indices_t = std::make_index_sequence; 25 | 26 | template 27 | static bool await_ready_fn(args_t& args) 28 | { 29 | return co2::await_ready(std::get(args)); 30 | } 31 | 32 | template 33 | static bool await_suspend_fn(args_t& args, F& f) 34 | { 35 | return co2::await_suspend(std::get(args), f), void_{}; 36 | } 37 | 38 | template 39 | static auto await_ready_fn_table(std::index_sequence) 40 | { 41 | using fptr = bool(*)(args_t&); 42 | static fptr fs[tuple_size] = 43 | { 44 | await_ready_fn... 45 | }; 46 | return fs; 47 | } 48 | 49 | template 50 | static auto await_suspend_fn_table(std::index_sequence) 51 | { 52 | using fptr = bool(*)(args_t&, F&); 53 | static fptr fs[tuple_size] = 54 | { 55 | await_suspend_fn... 56 | }; 57 | return fs; 58 | } 59 | 60 | nth_ready_awaiter(Tuple&& args, int n) : args(std::forward(args)), n(n) {} 61 | 62 | bool await_ready() 63 | { 64 | return await_ready_fn_table(indices_t{})[n](args); 65 | } 66 | 67 | template 68 | bool await_suspend(F& then) 69 | { 70 | return await_suspend_fn_table(indices_t{})[n](args, then); 71 | } 72 | 73 | void await_resume() const noexcept {} 74 | }; 75 | }} 76 | 77 | namespace co2 78 | { 79 | template 80 | inline detail::nth_ready_awaiter nth_ready(int n, Tuple&& args) 81 | { 82 | return {std::forward(args), n}; 83 | } 84 | } 85 | 86 | #endif -------------------------------------------------------------------------------- /include/co2/shared_task.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SHARED_TASK_HPP_INCLUDED 8 | #define CO2_SHARED_TASK_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace task_detail 15 | { 16 | struct shared_promise_base : promise_base 17 | { 18 | std::atomic _then {this}; 19 | std::atomic _use_count {2u}; 20 | tag _tag {tag::pending}; 21 | 22 | bool test_last() noexcept 23 | { 24 | return _use_count.fetch_sub(1u, std::memory_order_acquire) == 1u; 25 | } 26 | 27 | bool final_suspend() noexcept 28 | { 29 | auto next = _then.exchange(nullptr, std::memory_order_acq_rel); 30 | while (next != this) 31 | { 32 | auto then = static_cast(next); 33 | next = coroutine_data(then); 34 | coroutine_final_run(then); 35 | } 36 | return _use_count.fetch_sub(1u, std::memory_order_release) != 1u; 37 | } 38 | 39 | bool follow(coroutine<>& cb) 40 | { 41 | auto curr = cb.handle(); 42 | auto& next = coroutine_data(curr); 43 | next = _then.load(std::memory_order_acquire); 44 | while (next) 45 | { 46 | if (_then.compare_exchange_weak(next, curr, std::memory_order_release, std::memory_order_acquire)) 47 | { 48 | cb.detach(); 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | }; 55 | }} 56 | 57 | namespace co2 58 | { 59 | template 60 | struct shared_task 61 | : task_detail::impl, task_detail::shared_promise_base> 62 | { 63 | using base_type = task_detail::impl, 64 | task_detail::shared_promise_base>; 65 | 66 | using base_type::base_type; 67 | 68 | shared_task() = default; 69 | 70 | shared_task(shared_task&&) = default; 71 | 72 | shared_task(shared_task const& other) noexcept : base_type(other._promise) 73 | { 74 | if (auto promise = this->_promise) 75 | promise->_use_count.fetch_add(1u, std::memory_order_relaxed); 76 | } 77 | 78 | shared_task(task&& other) 79 | : base_type(task_detail::convert(std::move(other))) 80 | {} 81 | 82 | shared_task& operator=(shared_task other) noexcept 83 | { 84 | this->~shared_task(); 85 | return *new(this) shared_task(std::move(other)); 86 | } 87 | 88 | task_detail::cref_t await_resume() 89 | { 90 | return this->_promise->get(); 91 | } 92 | }; 93 | 94 | template 95 | inline void swap(shared_task& a, shared_task& b) noexcept 96 | { 97 | a.swap(b); 98 | } 99 | } 100 | 101 | #endif -------------------------------------------------------------------------------- /include/co2/adapted/boost_future.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_ADAPTED_BOOST_FUTURE_HPP_INCLUDED 8 | #define CO2_ADAPTED_BOOST_FUTURE_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | # if BOOST_THREAD_VERSION < 3 15 | # error "BOOST_THREAD_VERSION >= 3 is required" 16 | # endif 17 | 18 | namespace boost 19 | { 20 | template 21 | inline bool await_ready(future const& fut) 22 | { 23 | return fut.is_ready(); 24 | } 25 | 26 | template 27 | inline void await_suspend(future const& fut, ::co2::coroutine<>& cb) 28 | { 29 | // Cannot use future::then because the dtor will block. 30 | // Note that it's fine to capture `fut` by reference since it's 31 | // guranteed to live by the time `cb()` is called. 32 | thread([&fut, cb = std::move(cb)]() mutable 33 | { 34 | fut.wait(); 35 | cb(); 36 | }).detach(); 37 | } 38 | 39 | template 40 | inline T await_resume(future& fut) 41 | { 42 | return fut.get(); 43 | } 44 | } 45 | 46 | namespace co2 { namespace boost_future_detail 47 | { 48 | using namespace boost; 49 | 50 | template 51 | struct promise_base 52 | { 53 | promise promise; 54 | 55 | future get_return_object(coroutine<>& coro) 56 | { 57 | auto ret(promise.get_future()); 58 | coro(); 59 | return ret; 60 | } 61 | 62 | bool initial_suspend() noexcept 63 | { 64 | return false; 65 | } 66 | 67 | bool final_suspend() noexcept 68 | { 69 | promise.notify_deferred(); 70 | return false; 71 | } 72 | 73 | bool cancellation_requested() const 74 | { 75 | return false; 76 | } 77 | 78 | void set_exception(std::exception_ptr const& e) 79 | { 80 | promise.set_exception_deferred(e); 81 | } 82 | }; 83 | }} 84 | 85 | namespace co2 86 | { 87 | template 88 | struct coroutine_traits> 89 | { 90 | struct promise_type : boost_future_detail::promise_base 91 | { 92 | void set_result(T&& t) 93 | { 94 | set_result(std::forward(t)); 95 | } 96 | 97 | template 98 | void set_result(U&& u) 99 | { 100 | this->promise.set_value_deferred(std::forward(u)); 101 | } 102 | }; 103 | }; 104 | 105 | template<> 106 | struct coroutine_traits> 107 | { 108 | struct promise_type : boost_future_detail::promise_base 109 | { 110 | void set_result() 111 | { 112 | this->promise.set_value_deferred(); 113 | } 114 | }; 115 | }; 116 | } 117 | 118 | #endif -------------------------------------------------------------------------------- /include/co2/sync/mutex.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_MUTEX_HPP_INCLUDED 8 | #define CO2_SYNC_MUTEX_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 15 | { 16 | class mutex 17 | { 18 | std::atomic _then; 19 | 20 | public: 21 | constexpr mutex() : _then{nullptr} {} 22 | 23 | // Non-copyable. 24 | mutex(mutex const&) = delete; 25 | mutex& operator=(mutex const&) = delete; 26 | 27 | ~mutex() { BOOST_ASSERT_MSG(!_then.load(), "mutex is not released"); } 28 | 29 | bool try_lock() noexcept 30 | { 31 | void* curr = nullptr; 32 | return _then.compare_exchange_strong(curr, this, std::memory_order_acquire, std::memory_order_relaxed); 33 | } 34 | 35 | bool lock_suspend(coroutine<>& coro) noexcept 36 | { 37 | auto& next = coroutine_data(coro.handle()); 38 | next = _then.load(std::memory_order_relaxed); 39 | for (;;) 40 | { 41 | // If not locked, try to lock it. 42 | if (!next && _then.compare_exchange_strong(next, this, std::memory_order_acquire, std::memory_order_relaxed)) 43 | return false; 44 | BOOST_ASSERT(next); 45 | // Already locked, try to enqueue the coroutine. 46 | if (_then.compare_exchange_weak(next, coro.handle(), std::memory_order_acq_rel, std::memory_order_relaxed)) 47 | break; 48 | } 49 | coro.detach(); 50 | return true; 51 | } 52 | 53 | void unlock() noexcept 54 | { 55 | void* curr = this; 56 | void* next = nullptr; 57 | // No others waiting, we're done. 58 | if (_then.compare_exchange_strong(curr, next, std::memory_order_release, std::memory_order_acquire)) 59 | return; 60 | // Wake up next waiting coroutine. 61 | do 62 | { 63 | next = coroutine_data(static_cast(curr)); 64 | } while (!_then.compare_exchange_weak(curr, next, std::memory_order_acq_rel)); 65 | coroutine<>{static_cast(curr)}(); 66 | } 67 | }; 68 | 69 | template 70 | struct unlock_guard 71 | { 72 | Lock& lock; 73 | 74 | explicit unlock_guard(Lock& lock) : lock(lock) {} 75 | unlock_guard(unlock_guard const&) = delete; 76 | unlock_guard& operator=(unlock_guard const&) = delete; 77 | 78 | ~unlock_guard() 79 | { 80 | lock.unlock(); 81 | } 82 | }; 83 | 84 | template 85 | struct lock_awaiter 86 | { 87 | Lock& lock; 88 | 89 | bool await_ready() const noexcept 90 | { 91 | return lock.try_lock(); 92 | } 93 | 94 | bool await_suspend(co2::coroutine<>& coro) const noexcept 95 | { 96 | return lock.lock_suspend(coro); 97 | } 98 | 99 | void await_resume() const noexcept {} 100 | }; 101 | 102 | template 103 | inline lock_awaiter make_lock(Lock& lock) 104 | { 105 | return {lock}; 106 | } 107 | } 108 | 109 | #endif -------------------------------------------------------------------------------- /test/task.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | #include "trigger.hpp" 12 | 13 | auto wait(trigger& t) CO2_BEG(co2::task, (t)) 14 | { 15 | CO2_AWAIT_RETURN(t); 16 | } CO2_END 17 | 18 | auto throws(trigger& t) CO2_BEG(co2::task<>, (t)) 19 | { 20 | CO2_AWAIT(t); 21 | throw ball(); 22 | } CO2_END 23 | 24 | auto suspend(int& terminated) CO2_BEG(co2::task<>, (terminated), 25 | inc_on_finalize _{terminated}; 26 | ) 27 | { 28 | CO2_AWAIT(co2::suspend_always{}); 29 | } CO2_END 30 | 31 | auto wait(trigger& t, int& terminated) CO2_BEG(co2::task<>, (t, terminated), 32 | inc_on_finalize _{terminated}; 33 | ) 34 | { 35 | CO2_AWAIT(t); 36 | } CO2_END 37 | 38 | auto follow(co2::task<> task, int& terminated) CO2_BEG(co2::task<>, (task, terminated), 39 | inc_on_finalize _{terminated}; 40 | ) 41 | { 42 | CO2_AWAIT(task); 43 | } CO2_END 44 | 45 | auto inc(co2::task prev) CO2_BEG(co2::task, (prev), 46 | int n; 47 | ) 48 | { 49 | CO2_AWAIT_SET(n, prev); 50 | CO2_RETURN(n + 1); 51 | } CO2_END 52 | 53 | TEST_CASE("move check") 54 | { 55 | trigger event; 56 | auto task = wait(event); 57 | auto moved = std::move(task); 58 | CHECK_FALSE(task); 59 | CHECK(moved); 60 | } 61 | 62 | TEST_CASE("value check") 63 | { 64 | trigger event; 65 | auto task = wait(event); 66 | CHECK_FALSE(task.await_ready()); 67 | event(16); 68 | REQUIRE(task.await_ready()); 69 | CHECK(task.await_resume() == 16); 70 | CHECK_FALSE(task); 71 | } 72 | 73 | TEST_CASE("throw check") 74 | { 75 | trigger event; 76 | auto task = throws(event); 77 | CHECK_FALSE(task.await_ready()); 78 | event(16); 79 | REQUIRE(task.await_ready()); 80 | CHECK_THROWS_AS(task.await_resume(), ball); 81 | CHECK_FALSE(task); 82 | } 83 | 84 | TEST_CASE("unwind check") 85 | { 86 | int terminated = 0; 87 | trigger event; 88 | auto task = wait(event, terminated); 89 | CHECK_FALSE(task.await_ready()); 90 | CHECK_FALSE(terminated); 91 | task.reset(); 92 | CHECK_FALSE(terminated); 93 | event(16); 94 | CHECK(terminated); 95 | } 96 | 97 | TEST_CASE("abort check") 98 | { 99 | int terminated = 0; 100 | auto task = suspend(terminated); 101 | CHECK(terminated); 102 | CHECK_THROWS_AS(task.await_resume(), co2::task_cancelled); 103 | } 104 | 105 | TEST_CASE("cancel check") 106 | { 107 | int terminated = 0; 108 | trigger event; 109 | auto task = follow(wait(event, terminated), terminated); 110 | event.cancel(); 111 | REQUIRE(task); 112 | REQUIRE(task.await_ready()); 113 | CHECK_THROWS_AS(task.await_resume(), co2::task_cancelled); 114 | CHECK(terminated == 2); 115 | } 116 | 117 | TEST_CASE("recursion check") 118 | { 119 | trigger event; 120 | auto t = wait(event); 121 | for (int i = 0; i != 65536; ++i) 122 | t = inc(std::move(t)); 123 | event(0); 124 | REQUIRE(t); 125 | REQUIRE(t.await_ready()); 126 | CHECK(t.await_resume() == 65536); 127 | } 128 | 129 | TEST_CASE("recursion cancel check") 130 | { 131 | int terminated = 0; 132 | trigger event; 133 | auto t = wait(event, terminated); 134 | for (int i = 0; i != 65536; ++i) 135 | t = follow(std::move(t), terminated); 136 | event.cancel(); 137 | REQUIRE(t); 138 | REQUIRE(t.await_ready()); 139 | CHECK_THROWS_AS(t.await_resume(), co2::task_cancelled); 140 | CHECK(terminated == 65537); 141 | } -------------------------------------------------------------------------------- /include/co2/utility/stack_allocator.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2016 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_STACK_ALLOCATOR_HPP_INCLUDED 8 | #define CO2_STACK_ALLOCATOR_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 15 | { 16 | struct stack_manager 17 | { 18 | stack_manager(void* data, std::size_t n) noexcept 19 | : _beg(static_cast(data)), _ptr(_beg), _rest(n) 20 | {} 21 | 22 | stack_manager(stack_manager const&) = delete; 23 | stack_manager& operator=(stack_manager const&) = delete; 24 | 25 | void* allocate(std::size_t n, std::size_t align) 26 | { 27 | if (auto p = std::align(align, n, reinterpret_cast(_ptr), _rest)) 28 | { 29 | _ptr += n; 30 | _rest -= n; 31 | return p; 32 | } 33 | return ::operator new(n); 34 | } 35 | 36 | void deallocate(void* ptr, std::size_t n) noexcept 37 | { 38 | auto p = static_cast(ptr); 39 | if (contains(p)) 40 | { 41 | if (p + n == _ptr) 42 | { 43 | _ptr = p; 44 | _rest += n; 45 | } 46 | } 47 | else 48 | ::operator delete(p); 49 | } 50 | 51 | std::size_t used() const noexcept 52 | { 53 | return _ptr - _beg; 54 | } 55 | 56 | void clear() noexcept 57 | { 58 | _ptr = _beg; 59 | } 60 | 61 | private: 62 | bool contains(char* p) noexcept 63 | { 64 | return _beg <= p && p < _ptr + _rest; 65 | } 66 | 67 | char* const _beg; 68 | char* _ptr; 69 | std::size_t _rest; 70 | }; 71 | 72 | template 73 | struct stack_buffer : stack_manager 74 | { 75 | stack_buffer() : stack_manager(&_data, Bytes) {} 76 | 77 | private: 78 | std::aligned_storage_t _data; 79 | }; 80 | 81 | template 82 | struct stack_allocator; 83 | 84 | template<> 85 | struct stack_allocator<> 86 | { 87 | using value_type = void; 88 | 89 | stack_allocator(stack_manager& manager) noexcept : _manager(manager) {} 90 | stack_allocator(stack_allocator const&) = default; 91 | stack_allocator& operator=(stack_allocator const&) = delete; 92 | 93 | bool operator==(stack_allocator const& other) noexcept 94 | { 95 | return &_manager == &other._manager; 96 | } 97 | 98 | bool operator!=(stack_allocator const& other) noexcept 99 | { 100 | return &_manager != &other._manager; 101 | } 102 | 103 | protected: 104 | 105 | stack_manager& _manager; 106 | }; 107 | 108 | template 109 | struct stack_allocator : stack_allocator<> 110 | { 111 | using value_type = T; 112 | 113 | using stack_allocator<>::stack_allocator; 114 | 115 | stack_allocator(stack_allocator<> const& other) : stack_allocator<>(other) {} 116 | 117 | T* allocate(std::size_t n) 118 | { 119 | return static_cast(_manager.allocate(n * sizeof(T), alignof(T))); 120 | } 121 | 122 | void deallocate(T* p, std::size_t n) noexcept 123 | { 124 | _manager.deallocate(p, n * sizeof(T)); 125 | } 126 | }; 127 | } 128 | 129 | #endif -------------------------------------------------------------------------------- /include/co2/sync/when_all.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_WHEN_ALL_HPP_INCLUDED 8 | #define CO2_SYNC_WHEN_ALL_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace co2 16 | { 17 | namespace detail 18 | { 19 | template 20 | struct await_table_t 21 | { 22 | static constexpr std::size_t size = std::tuple_size::value; 23 | 24 | template 25 | static bool ready_fn(Tuple& t) 26 | { 27 | return co2::await_ready(std::get(t)); 28 | } 29 | 30 | template 31 | static bool suspend_fn(Tuple& t, coroutine<>& coro) 32 | { 33 | return co2::await_suspend(std::get(t), coro); 34 | } 35 | 36 | constexpr await_table_t() 37 | : await_table_t(std::make_index_sequence{}) 38 | {} 39 | 40 | template 41 | constexpr await_table_t(std::index_sequence) 42 | : ready{ready_fn...} 43 | , suspend{suspend_fn...} 44 | {} 45 | 46 | bool(*ready[size])(Tuple& t); 47 | bool(*suspend[size])(Tuple& t, coroutine<>& coro); 48 | }; 49 | 50 | template 51 | await_table_t const await_table = {}; 52 | 53 | template 54 | auto when_all_impl(std::vector seq) CO2_BEG(task>, (seq), 55 | CO2_TEMP_SIZE(0); 56 | CO2_AUTO(it, seq.begin()); 57 | ) 58 | { 59 | for (; it != seq.end(); ++it) 60 | { 61 | if (!it->await_ready()) 62 | { 63 | CO2_SUSPEND(it->await_suspend); 64 | } 65 | } 66 | CO2_RETURN_LOCAL(seq); 67 | } CO2_END 68 | 69 | template 70 | auto when_all_impl(std::tuple seq) CO2_BEG(task>, (seq), 71 | CO2_TEMP_SIZE(0); 72 | std::size_t i = 0; 73 | ) 74 | { 75 | using seq_t = std::tuple; 76 | for (; i != std::tuple_size::value; ++i) 77 | { 78 | if (!await_table.ready[i](seq)) 79 | { 80 | CO2_SUSPEND([&](coroutine<>& coro) 81 | { 82 | return await_table.suspend[i](seq, coro); 83 | }); 84 | } 85 | } 86 | CO2_RETURN_LOCAL(seq); 87 | } CO2_END 88 | } 89 | 90 | template 91 | inline auto when_all(InputIt first, InputIt last) -> 92 | task::value_type>> 93 | { 94 | using task_t = typename std::iterator_traits::value_type; 95 | using seq_t = std::vector; 96 | using iter = detail::copy_or_move_iter::value>; 97 | return detail::when_all_impl(seq_t(iter::wrap(first), iter::wrap(last))); 98 | } 99 | 100 | template 101 | inline auto when_all(Futures&&... futures) -> 102 | task...>> 103 | { 104 | using seq_t = std::tuple...>; 105 | return detail::when_all_impl(seq_t(detail::copy_or_move(futures)...)); 106 | } 107 | } 108 | 109 | #endif -------------------------------------------------------------------------------- /include/co2/generator.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_GENERATOR_HPP_INCLUDED 8 | #define CO2_GENERATOR_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 15 | { 16 | template 17 | struct generator 18 | { 19 | struct promise_type 20 | { 21 | using val_t = detail::wrap_reference_t; 22 | 23 | generator get_return_object(coroutine& coro) 24 | { 25 | coro.resume(); 26 | return generator(coro); 27 | } 28 | 29 | bool initial_suspend() noexcept 30 | { 31 | return true; 32 | } 33 | 34 | bool final_suspend() noexcept 35 | { 36 | reset_value(); 37 | return false; 38 | } 39 | 40 | bool cancellation_requested() const noexcept 41 | { 42 | return false; 43 | } 44 | 45 | void set_result() noexcept {} 46 | 47 | suspend_always yield_value(T&& t) 48 | { 49 | return yield_value(std::forward(t)); 50 | } 51 | 52 | template 53 | suspend_always yield_value(U&& u) 54 | { 55 | reset_value(); 56 | _valid = true; 57 | new(&_data) val_t(std::forward(u)); 58 | return {}; 59 | } 60 | 61 | T&& get() 62 | { 63 | return std::forward(*reinterpret_cast(&_data)); 64 | } 65 | 66 | private: 67 | 68 | void reset_value() 69 | { 70 | if (_valid) 71 | reinterpret_cast(&_data)->~val_t(); 72 | } 73 | 74 | detail::storage_for _data; 75 | bool _valid = false; 76 | }; 77 | 78 | struct iterator 79 | : boost::iterator_facade 80 | { 81 | iterator() : _coro() {} 82 | 83 | explicit iterator(coroutine& coro) : _coro(&coro) 84 | { 85 | increment(); 86 | } 87 | 88 | private: 89 | 90 | friend class boost::iterator_core_access; 91 | 92 | void increment() 93 | { 94 | _coro->resume(); 95 | if (!*_coro) 96 | _coro = nullptr; 97 | } 98 | 99 | bool equal(iterator const& other) const 100 | { 101 | return _coro == other._coro; 102 | } 103 | 104 | T&& dereference() const 105 | { 106 | return _coro->promise().get(); 107 | } 108 | 109 | coroutine* _coro; 110 | }; 111 | 112 | generator() = default; 113 | 114 | generator(generator&& other) = default; 115 | 116 | generator& operator=(generator&& other) = default; 117 | 118 | void swap(generator& other) noexcept 119 | { 120 | _coro.swap(other._coro); 121 | } 122 | 123 | iterator begin() 124 | { 125 | if (!_coro) 126 | return {}; 127 | return iterator(_coro); 128 | } 129 | 130 | iterator end() 131 | { 132 | return {}; 133 | } 134 | 135 | private: 136 | 137 | explicit generator(coroutine& coro) : _coro(std::move(coro)) {} 138 | 139 | coroutine _coro; 140 | }; 141 | 142 | template 143 | inline void swap(generator& a, generator& b) noexcept 144 | { 145 | a.swap(b); 146 | } 147 | } 148 | 149 | #endif -------------------------------------------------------------------------------- /include/co2/task.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_TASK_HPP_INCLUDED 8 | #define CO2_TASK_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace task_detail 15 | { 16 | struct unique_promise_base : promise_base 17 | { 18 | std::atomic _then {this}; 19 | tag _tag {tag::pending}; 20 | 21 | bool test_last() noexcept 22 | { 23 | return !_then.exchange(nullptr, std::memory_order_acquire); 24 | } 25 | 26 | bool final_suspend() noexcept 27 | { 28 | auto then = _then.exchange(nullptr, std::memory_order_acq_rel); 29 | if (then != this) 30 | { 31 | if (!then) // Task is destroyed, we're the last owner. 32 | return false; 33 | coroutine_final_run(static_cast(then)); 34 | } 35 | return true; // We're done. Let the task do the cleanup. 36 | } 37 | 38 | bool follow(coroutine<>& cb) 39 | { 40 | void* last = this; 41 | if (_then.compare_exchange_strong(last, cb.handle(), std::memory_order_release, std::memory_order_acquire)) 42 | { 43 | cb.detach(); 44 | return true; 45 | } 46 | // If there's a previous waiter, just cancel it because it's only 47 | // allowed for when_any. 48 | if (last) 49 | { 50 | if (_then.compare_exchange_strong(last, cb.handle(), std::memory_order_acq_rel)) 51 | { 52 | coroutine<>{static_cast(last)}; 53 | cb.detach(); 54 | return true; 55 | } 56 | BOOST_ASSERT_MSG(!last, "multiple coroutines await on same task"); 57 | } 58 | return false; 59 | } 60 | }; 61 | }} 62 | 63 | namespace co2 64 | { 65 | template 66 | struct task 67 | : task_detail::impl, task_detail::unique_promise_base> 68 | { 69 | using base_type = 70 | task_detail::impl, task_detail::unique_promise_base>; 71 | 72 | using base_type::base_type; 73 | 74 | task() = default; 75 | 76 | task(task&&) = default; 77 | 78 | task& operator=(task&& other) = default; 79 | 80 | T await_resume() 81 | { 82 | return detail::extract_promise{this->_promise}->get(); 83 | } 84 | 85 | shared_task share() 86 | { 87 | return task_detail::convert>(std::move(*this)); 88 | } 89 | }; 90 | 91 | template 92 | inline void swap(task& a, task& b) noexcept 93 | { 94 | a.swap(b); 95 | } 96 | 97 | inline auto make_ready_task() CO2_BEG(task<>, (), CO2_TEMP_SIZE(0);) 98 | { 99 | CO2_RETURN(); 100 | } CO2_END 101 | 102 | template 103 | inline auto make_ready_task(T&& val) CO2_BEG(task>, (val), CO2_TEMP_SIZE(0);) 104 | { 105 | CO2_RETURN(std::forward(val)); 106 | } CO2_END 107 | 108 | template 109 | inline auto make_ready_task(std::reference_wrapper val) CO2_BEG(task, (val), CO2_TEMP_SIZE(0);) 110 | { 111 | CO2_RETURN(val); 112 | } CO2_END 113 | 114 | template 115 | inline auto make_exceptional_task(std::exception_ptr ex) CO2_BEG(task, (ex), CO2_TEMP_SIZE(0);) 116 | { 117 | std::rethrow_exception(std::move(ex)); 118 | } CO2_END 119 | 120 | template 121 | inline auto make_cancelled_task() CO2_BEG(task, (), CO2_TEMP_SIZE(0);) 122 | { 123 | #define Zz_CO2_CANCEL_OP(coro) true // suspend 124 | CO2_SUSPEND(Zz_CO2_CANCEL_OP); 125 | #undef Zz_CO2_CANCEL_OP 126 | } CO2_END 127 | } 128 | 129 | #endif -------------------------------------------------------------------------------- /example/same_fringe.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Copyright Nat Goodspeed 2013. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSEstd::placeholders::_1_0.txt or copy at 5 | // http://www.boost.org/LICENSEstd::placeholders::_1_0.txt) 6 | 7 | // Adapted example from Boost.Coroutine - Jamboree 2015 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | struct node 20 | { 21 | typedef std::shared_ptr< node > ptr_t; 22 | 23 | // Each tree node has an optional left subtree, an optional right subtree 24 | // and a value of its own. The value is considered to be between the left 25 | // subtree and the right. 26 | ptr_t left, right; 27 | std::string value; 28 | 29 | // construct leaf 30 | node(const std::string& v): 31 | left(),right(),value(v) 32 | {} 33 | // construct nonleaf 34 | node(ptr_t l, const std::string& v, ptr_t r): 35 | left(l),right(r),value(v) 36 | {} 37 | 38 | static ptr_t create(const std::string& v) 39 | { 40 | return ptr_t(new node(v)); 41 | } 42 | 43 | static ptr_t create(ptr_t l, const std::string& v, ptr_t r) 44 | { 45 | return ptr_t(new node(l, v, r)); 46 | } 47 | }; 48 | 49 | node::ptr_t create_left_tree_from(const std::string& root) 50 | { 51 | /* -------- 52 | root 53 | / \ 54 | b e 55 | / \ 56 | a c 57 | -------- */ 58 | 59 | return node::create( 60 | node::create( 61 | node::create("a"), 62 | "b", 63 | node::create("c")), 64 | root, 65 | node::create("e")); 66 | } 67 | 68 | node::ptr_t create_right_tree_from(const std::string& root) 69 | { 70 | /* -------- 71 | root 72 | / \ 73 | a d 74 | / \ 75 | c e 76 | -------- */ 77 | 78 | return node::create( 79 | node::create("a"), 80 | root, 81 | node::create( 82 | node::create("c"), 83 | "d", 84 | node::create("e"))); 85 | } 86 | 87 | // recursively walk the tree, delivering values in order 88 | template 89 | auto traverse(Alloc alloc, node::ptr_t n) 90 | CO2_BEG(co2::recursive_generator, (alloc, n)new(alloc)) 91 | { 92 | if (n->left) 93 | CO2_YIELD(traverse(alloc, n->left)); 94 | CO2_YIELD(n->value); 95 | if (n->right) 96 | CO2_YIELD(traverse(alloc, n->right)); 97 | } CO2_END 98 | 99 | int main() 100 | { 101 | co2::stack_buffer<2 * 1024> buf; 102 | co2::stack_allocator<> alloc(buf); 103 | { 104 | node::ptr_t left_d(create_left_tree_from("d")); 105 | auto left_d_reader(traverse(alloc, left_d)); 106 | std::cout << "left tree from d:\n"; 107 | std::copy(std::begin(left_d_reader), 108 | std::end(left_d_reader), 109 | std::ostream_iterator(std::cout, " ")); 110 | std::cout << std::endl; 111 | 112 | node::ptr_t right_b(create_right_tree_from("b")); 113 | auto right_b_reader(traverse(alloc, right_b)); 114 | std::cout << "right tree from b:\n"; 115 | std::copy(std::begin(right_b_reader), 116 | std::end(right_b_reader), 117 | std::ostream_iterator(std::cout, " ")); 118 | std::cout << std::endl; 119 | 120 | node::ptr_t right_x(create_right_tree_from("x")); 121 | auto right_x_reader(traverse(alloc, right_x)); 122 | std::cout << "right tree from x:\n"; 123 | std::copy(std::begin(right_x_reader), 124 | std::end(right_x_reader), 125 | std::ostream_iterator(std::cout, " ")); 126 | std::cout << std::endl; 127 | } 128 | buf.clear(); 129 | { 130 | node::ptr_t left_d(create_left_tree_from("d")); 131 | auto left_d_reader(traverse(alloc, left_d)); 132 | 133 | node::ptr_t right_b(create_right_tree_from("b")); 134 | auto right_b_reader(traverse(alloc, right_b)); 135 | 136 | std::cout << "left tree from d == right tree from b? " 137 | << std::boolalpha 138 | << std::equal(std::begin(left_d_reader), 139 | std::end(left_d_reader), 140 | std::begin(right_b_reader)) 141 | << std::endl; 142 | } 143 | buf.clear(); 144 | { 145 | node::ptr_t left_d(create_left_tree_from("d")); 146 | auto left_d_reader(traverse(alloc, left_d)); 147 | 148 | node::ptr_t right_x(create_right_tree_from("x")); 149 | auto right_x_reader(traverse(alloc, right_x)); 150 | 151 | std::cout << "left tree from d == right tree from x? " 152 | << std::boolalpha 153 | << std::equal(std::begin(left_d_reader), 154 | std::end(left_d_reader), 155 | std::begin(right_x_reader)) 156 | << std::endl; 157 | } 158 | buf.clear(); 159 | std::cout << "Done" << std::endl; 160 | 161 | return EXIT_SUCCESS; 162 | } 163 | -------------------------------------------------------------------------------- /test/shared_task.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | #include 10 | #include "common.hpp" 11 | #include "trigger.hpp" 12 | 13 | auto wait(trigger& t) CO2_BEG(co2::shared_task, (t)) 14 | { 15 | CO2_AWAIT_RETURN(t); 16 | } CO2_END 17 | 18 | auto throws(trigger& t) CO2_BEG(co2::shared_task<>, (t)) 19 | { 20 | CO2_AWAIT(t); 21 | throw ball(); 22 | } CO2_END 23 | 24 | auto suspend(int& terminated) CO2_BEG(co2::shared_task<>, (terminated), 25 | inc_on_finalize _{terminated}; 26 | ) 27 | { 28 | CO2_AWAIT(co2::suspend_always{}); 29 | } CO2_END 30 | 31 | auto wait(trigger& t, int& terminated) CO2_BEG(co2::shared_task<>, (t, terminated), 32 | inc_on_finalize _{terminated}; 33 | ) 34 | { 35 | CO2_AWAIT(t); 36 | } CO2_END 37 | 38 | auto follow(co2::shared_task<> task, int& terminated) CO2_BEG(co2::shared_task<>, (task, terminated), 39 | inc_on_finalize _{terminated}; 40 | ) 41 | { 42 | CO2_AWAIT(task); 43 | } CO2_END 44 | 45 | auto x2(co2::shared_task task) CO2_BEG(co2::shared_task, (task), int i;) 46 | { 47 | CO2_AWAIT_SET(i, task); 48 | CO2_RETURN(i * 2); 49 | } CO2_END 50 | 51 | auto inc(co2::shared_task prev) CO2_BEG(co2::shared_task, (prev), 52 | int n; 53 | ) 54 | { 55 | CO2_AWAIT_SET(n, prev); 56 | CO2_RETURN(n + 1); 57 | } CO2_END 58 | 59 | auto count(co2::shared_task task, int& n) CO2_BEG(co2::shared_task<>, (task, n)) 60 | { 61 | CO2_AWAIT(task); 62 | ++n; 63 | } CO2_END 64 | 65 | TEST_CASE("move check") 66 | { 67 | trigger event; 68 | auto task = wait(event); 69 | auto moved = std::move(task); 70 | CHECK_FALSE(task); 71 | CHECK(moved); 72 | } 73 | 74 | TEST_CASE("copy check") 75 | { 76 | trigger event; 77 | auto task = wait(event); 78 | auto copied = task; 79 | CHECK(task); 80 | CHECK(copied); 81 | } 82 | 83 | TEST_CASE("value check") 84 | { 85 | trigger event; 86 | auto task = wait(event); 87 | auto child1 = x2(task); 88 | auto child2 = x2(task); 89 | CHECK_FALSE(task.await_ready()); 90 | CHECK_FALSE(child1.await_ready()); 91 | CHECK_FALSE(child2.await_ready()); 92 | event(16); 93 | REQUIRE(task.await_ready()); 94 | REQUIRE(child1.await_ready()); 95 | REQUIRE(child2.await_ready()); 96 | CHECK(task.await_resume() == 16); 97 | CHECK(child1.await_resume() == 32); 98 | CHECK(child2.await_resume() == 32); 99 | CHECK(task); 100 | CHECK(child1); 101 | CHECK(child2); 102 | } 103 | 104 | TEST_CASE("throw check") 105 | { 106 | trigger event; 107 | auto task = throws(event); 108 | CHECK_FALSE(task.await_ready()); 109 | event(16); 110 | REQUIRE(task.await_ready()); 111 | CHECK_THROWS_AS(task.await_resume(), ball); 112 | CHECK(task); 113 | } 114 | 115 | TEST_CASE("unwind check") 116 | { 117 | int terminated = 0; 118 | trigger event; 119 | auto task = wait(event, terminated); 120 | follow(task, terminated); 121 | follow(task, terminated); 122 | CHECK_FALSE(task.await_ready()); 123 | CHECK_FALSE(terminated); 124 | task.reset(); 125 | CHECK_FALSE(terminated); 126 | event(16); 127 | CHECK(terminated == 3); 128 | } 129 | 130 | TEST_CASE("abort check") 131 | { 132 | int terminated = 0; 133 | auto task = suspend(terminated); 134 | auto child1 = follow(task, terminated); 135 | auto child2 = follow(task, terminated); 136 | CHECK(terminated == 3); 137 | CHECK_THROWS_AS(task.await_resume(), co2::task_cancelled); 138 | CHECK_THROWS_AS(child1.await_resume(), co2::task_cancelled); 139 | CHECK_THROWS_AS(child2.await_resume(), co2::task_cancelled); 140 | } 141 | 142 | TEST_CASE("cancel check") 143 | { 144 | int terminated = 0; 145 | trigger event; 146 | auto task = wait(event, terminated); 147 | auto child1 = follow(task, terminated); 148 | auto child2 = follow(task, terminated); 149 | event.cancel(); 150 | REQUIRE(task); 151 | REQUIRE(child1); 152 | REQUIRE(child2); 153 | REQUIRE(task.await_ready()); 154 | REQUIRE(child1.await_ready()); 155 | REQUIRE(child2.await_ready()); 156 | CHECK_THROWS_AS(task.await_resume(), co2::task_cancelled); 157 | CHECK_THROWS_AS(child1.await_resume(), co2::task_cancelled); 158 | CHECK_THROWS_AS(child2.await_resume(), co2::task_cancelled); 159 | CHECK(terminated == 3); 160 | } 161 | 162 | TEST_CASE("recursion check") 163 | { 164 | int n = 0; 165 | trigger event; 166 | auto t = wait(event); 167 | for (int i = 0; i != 65536; ++i) 168 | { 169 | count(t, n); 170 | t = inc(t); 171 | } 172 | event(0); 173 | REQUIRE(t); 174 | REQUIRE(t.await_ready()); 175 | CHECK(t.await_resume() == 65536); 176 | CHECK(n == 65536); 177 | } 178 | 179 | TEST_CASE("recursion cancel check") 180 | { 181 | int terminated = 0; 182 | trigger event; 183 | auto t = wait(event, terminated); 184 | for (int i = 0; i != 65536; ++i) 185 | t = follow(t, terminated); 186 | event.cancel(); 187 | REQUIRE(t); 188 | REQUIRE(t.await_ready()); 189 | CHECK_THROWS_AS(t.await_resume(), co2::task_cancelled); 190 | CHECK(terminated == 65537); 191 | } -------------------------------------------------------------------------------- /include/co2/sync/when_any.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_WHEN_ANY_HPP_INCLUDED 8 | #define CO2_SYNC_WHEN_ANY_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace co2 18 | { 19 | template 20 | struct when_any_result 21 | { 22 | std::size_t index; 23 | Sequence futures; 24 | }; 25 | 26 | namespace detail 27 | { 28 | template 29 | auto wait_any_at(std::size_t i, std::shared_ptr state) 30 | CO2_BEG(void, (i, state), CO2_TEMP_SIZE(0);) 31 | { 32 | CO2_SUSPEND(state->result.futures[i].await_suspend); 33 | state->set_ready(i); 34 | } CO2_END 35 | 36 | template 37 | auto wait_any_at(std::integral_constant, std::shared_ptr state) 38 | CO2_BEG(void, (state), CO2_TEMP_SIZE(0);) 39 | { 40 | CO2_SUSPEND(std::get(state->result.futures).await_suspend); 41 | state->set_ready(I); 42 | } CO2_END 43 | 44 | template 45 | struct when_any_state 46 | { 47 | std::atomic coro; 48 | when_any_result result; 49 | 50 | template 51 | when_any_state(bool/*dummy*/, T&&... t) 52 | : coro{nullptr}, result{0, {std::forward(t)...}} 53 | {} 54 | 55 | ~when_any_state() 56 | { 57 | coroutine<>{coro.load(std::memory_order_relaxed)}; 58 | } 59 | 60 | void set_ready(std::size_t i) 61 | { 62 | if (auto then = coro.exchange(nullptr, std::memory_order_relaxed)) 63 | { 64 | result.index = i; 65 | coroutine<>{then}(); 66 | } 67 | } 68 | 69 | static auto make_task(when_any_state& state) 70 | CO2_BEG(task>, (state), CO2_TEMP_SIZE(0);) 71 | { 72 | CO2_SUSPEND([&](coroutine<>& coro) 73 | { 74 | state.coro.store(coro.detach(), std::memory_order_relaxed); 75 | }); 76 | CO2_RETURN(std::move(state.result)); 77 | } CO2_END 78 | }; 79 | 80 | template 81 | inline void wait_any_each(State const& state, std::integral_constant, std::integral_constant) 82 | { 83 | state->set_ready(std::size_t(-1)); 84 | } 85 | 86 | template 87 | inline void wait_any_each(State const& state, std::integral_constant i, std::integral_constant e) 88 | { 89 | if (std::get(state->result.futures).await_ready()) 90 | return state->set_ready(I); 91 | wait_any_at(i, state); 92 | if (state->coro.load(std::memory_order_relaxed)) 93 | wait_any_each(state, std::integral_constant{}, e); 94 | } 95 | 96 | template 97 | inline void wait_any_each(State const&, std::integral_constant, std::integral_constant) {} 98 | } 99 | 100 | template 101 | auto when_any(InputIt first, InputIt last) -> 102 | task::value_type>>> 103 | { 104 | using task_t = typename std::iterator_traits::value_type; 105 | using seq_t = std::vector; 106 | using iter = detail::copy_or_move_iter::value>; 107 | auto state = std::make_shared>(true, iter::wrap(first), iter::wrap(last)); 108 | auto ret(detail::when_any_state::make_task(*state)); 109 | if (const std::size_t n = state->result.futures.size()) 110 | { 111 | for (std::size_t i = 0; i != n; ++i) 112 | { 113 | if (state->result.futures[i].await_ready()) 114 | { 115 | state->set_ready(i); 116 | break; 117 | } 118 | wait_any_at(i, state); 119 | if (!state->coro.load(std::memory_order_relaxed)) 120 | break; 121 | } 122 | } 123 | else 124 | state->set_ready(std::size_t(-1)); 125 | return ret; 126 | } 127 | 128 | template 129 | auto when_any(Futures&&... futures) -> 130 | task...>>> 131 | { 132 | using seq_t = std::tuple...>; 133 | auto state = std::make_shared>(true, detail::copy_or_move(futures)...); 134 | auto ret(detail::when_any_state::make_task(*state)); 135 | detail::wait_any_each(state, std::integral_constant{}, std::tuple_size{}); 136 | return ret; 137 | } 138 | } 139 | 140 | #endif -------------------------------------------------------------------------------- /include/co2/sync/multiplexer.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_SYNC_MULTIPLEXER_HPP_INCLUDED 8 | #define CO2_SYNC_MULTIPLEXER_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace co2 { namespace detail 16 | { 17 | template 18 | struct multiplexer_state 19 | { 20 | Task result; 21 | void* active_list = nullptr; 22 | std::atomic ready_list{this}; 23 | coroutine_handle then = nullptr; 24 | std::atomic_flag lock_flag = ATOMIC_FLAG_INIT; 25 | 26 | multiplexer_state() 27 | { 28 | lock_flag.test_and_set(std::memory_order_relaxed); 29 | } 30 | 31 | ~multiplexer_state() 32 | { 33 | kill_list(active_list); 34 | auto next = ready_list.load(std::memory_order_relaxed); 35 | if (next != this) 36 | kill_list(next); 37 | } 38 | 39 | void kill_list(void* next) 40 | { 41 | while (next) 42 | { 43 | auto then = static_cast(next); 44 | next = coroutine_data(then); 45 | coroutine<>{then}; 46 | if (next == this) 47 | break; 48 | } 49 | } 50 | 51 | void set_result(Task&& t) 52 | { 53 | result = std::move(t); 54 | if (auto next = then) 55 | { 56 | then = nullptr; 57 | coroutine<>{next}(); 58 | } 59 | } 60 | 61 | coroutine_handle pop_active() 62 | { 63 | auto ret = static_cast(active_list); 64 | if (ret) 65 | { 66 | active_list = coroutine_data(ret); 67 | if (active_list == this) 68 | active_list = nullptr; 69 | } 70 | return ret; 71 | } 72 | 73 | bool add_ready(coroutine<>& coro) 74 | { 75 | auto h = coro.handle(); 76 | auto& next = coroutine_data(h); 77 | next = nullptr; 78 | if (ready_list.compare_exchange_strong(next, this, std::memory_order_relaxed)) 79 | { 80 | if (lock()) 81 | return false; 82 | next = this; 83 | } 84 | while (!ready_list.compare_exchange_weak(next, h, std::memory_order_relaxed)) 85 | { 86 | if (!next && lock()) 87 | return false; 88 | } 89 | coro.detach(); 90 | return true; 91 | } 92 | 93 | bool lock() 94 | { 95 | return !lock_flag.test_and_set(std::memory_order_acquire); 96 | } 97 | 98 | bool suspend(coroutine<>& coro) 99 | { 100 | then = coro.handle(); 101 | lock_flag.clear(std::memory_order_release); 102 | void* sentinel = this; 103 | if (!ready_list.compare_exchange_strong(sentinel, nullptr, std::memory_order_relaxed) && sentinel) 104 | { 105 | then = nullptr; 106 | active_list = ready_list.exchange(this, std::memory_order_relaxed); 107 | return false; 108 | } 109 | coro.detach(); 110 | return true; 111 | } 112 | 113 | static auto wait_task(Task t, std::weak_ptr state) 114 | CO2_BEG(void, (t, state), CO2_TEMP_SIZE(0);) 115 | { 116 | if (!t.await_ready()) 117 | { 118 | CO2_SUSPEND(t.await_suspend); 119 | } 120 | CO2_SUSPEND([&](coroutine<>& coro) 121 | { 122 | if (auto p = state.lock()) 123 | return p->add_ready(coro); 124 | return true; 125 | }); 126 | if (auto p = state.lock()) 127 | p->set_result(std::move(t)); 128 | } CO2_END 129 | }; 130 | }} 131 | 132 | namespace co2 133 | { 134 | template 135 | class multiplexer 136 | { 137 | using state_t = detail::multiplexer_state; 138 | 139 | struct awaiter 140 | { 141 | state_t& state; 142 | 143 | bool await_ready() const 144 | { 145 | return !!state.active_list; 146 | } 147 | 148 | bool await_suspend(coroutine<>& coro) 149 | { 150 | return state.suspend(coro); 151 | } 152 | 153 | await_result_t await_resume() 154 | { 155 | if (auto coro = state.pop_active()) 156 | coroutine<>{coro}(); 157 | return state.result.await_resume(); 158 | } 159 | }; 160 | 161 | std::shared_ptr _state; 162 | std::size_t _count; 163 | 164 | public: 165 | multiplexer() 166 | : _state(std::make_shared()), _count(0) 167 | {} 168 | 169 | void add(Task t) 170 | { 171 | state_t::wait_task(std::move(t), _state); 172 | ++_count; 173 | } 174 | 175 | template 176 | void add(InputIt first, InputIt last) 177 | { 178 | for (; first != last; ++first) 179 | { 180 | state_t::wait_task(detail::copy_or_move(*first), _state); 181 | ++_count; 182 | } 183 | } 184 | 185 | explicit operator bool() const 186 | { 187 | return !!_count; 188 | } 189 | 190 | awaiter select() 191 | { 192 | --_count; 193 | return {*_state}; 194 | } 195 | }; 196 | } 197 | 198 | #endif -------------------------------------------------------------------------------- /include/co2/lazy_task.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2016-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_LAZY_TASK_HPP_INCLUDED 8 | #define CO2_LAZY_TASK_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace detail 15 | { 16 | struct lazy_promise_base 17 | { 18 | bool initial_suspend() noexcept 19 | { 20 | return false; 21 | } 22 | 23 | bool final_suspend() noexcept 24 | { 25 | if (_then) 26 | { 27 | coroutine_final_run(_then); 28 | return true; 29 | } 30 | return false; 31 | } 32 | 33 | bool cancellation_requested() const noexcept 34 | { 35 | return false; 36 | } 37 | 38 | coroutine_handle _then = nullptr; 39 | }; 40 | 41 | template 42 | struct lazy_promise : lazy_promise_base 43 | { 44 | using val_t = wrap_reference_t; 45 | 46 | template 47 | void set_result(U&& u) 48 | { 49 | new(&_data.value) val_t(std::forward(u)); 50 | _tag = tag::value; 51 | } 52 | 53 | void set_exception(std::exception_ptr e) noexcept 54 | { 55 | new(&_data.exception) std::exception_ptr(std::move(e)); 56 | _tag = tag::exception; 57 | } 58 | 59 | T&& get() 60 | { 61 | switch (_tag) 62 | { 63 | case tag::exception: 64 | std::rethrow_exception(_data.exception); 65 | case tag::cancelled: 66 | throw task_cancelled(); 67 | default: 68 | break; 69 | } 70 | return static_cast(_data.value); 71 | } 72 | 73 | ~lazy_promise() 74 | { 75 | _data.destroy(_tag); 76 | } 77 | 78 | storage _data; 79 | tag _tag = tag::pending; 80 | }; 81 | 82 | template<> 83 | struct lazy_promise : lazy_promise_base 84 | { 85 | void set_result() noexcept 86 | { 87 | _tag = tag::value; 88 | } 89 | 90 | void set_exception(std::exception_ptr e) noexcept 91 | { 92 | _e = std::move(e); 93 | _tag = tag::exception; 94 | } 95 | 96 | void get() 97 | { 98 | switch (_tag) 99 | { 100 | case tag::exception: 101 | std::rethrow_exception(_e); 102 | case tag::cancelled: 103 | throw task_cancelled(); 104 | default: 105 | return; 106 | } 107 | } 108 | 109 | std::exception_ptr _e; 110 | tag _tag = tag::pending; 111 | }; 112 | }} 113 | 114 | namespace co2 115 | { 116 | template 117 | struct lazy_task 118 | { 119 | struct promise_type : detail::lazy_promise 120 | { 121 | lazy_task get_return_object(coroutine& coro) 122 | { 123 | coro.detach(); 124 | return lazy_task(this); 125 | } 126 | 127 | void cancel() noexcept 128 | { 129 | this->_tag = detail::tag::cancelled; 130 | } 131 | }; 132 | 133 | lazy_task() noexcept : _promise() {} 134 | 135 | lazy_task(lazy_task&& other) noexcept : _promise(other._promise) 136 | { 137 | other._promise = nullptr; 138 | } 139 | 140 | explicit lazy_task(promise_type* promise) noexcept : _promise(promise) {} 141 | 142 | lazy_task& operator=(lazy_task&& other) noexcept 143 | { 144 | if (_promise) 145 | release(); 146 | _promise = other._promise; 147 | other._promise = nullptr; 148 | return *this; 149 | } 150 | 151 | bool await_ready() const noexcept 152 | { 153 | return _promise->_tag != detail::tag::pending; 154 | } 155 | 156 | void await_suspend(coroutine<>& coro) noexcept 157 | { 158 | _promise->_then = coro.detach(); 159 | coroutine_final_run(coroutine::from_promise(_promise)); 160 | } 161 | 162 | T await_resume() 163 | { 164 | return detail::extract_promise{_promise}->get(); 165 | } 166 | 167 | explicit operator bool() const noexcept 168 | { 169 | return !!_promise; 170 | } 171 | 172 | bool valid() const noexcept 173 | { 174 | return !!_promise; 175 | } 176 | 177 | void swap(lazy_task& other) noexcept 178 | { 179 | std::swap(_promise, other._promise); 180 | } 181 | 182 | void reset() noexcept 183 | { 184 | if (_promise) 185 | { 186 | release(); 187 | _promise = nullptr; 188 | } 189 | } 190 | 191 | bool is_cancelled() const noexcept 192 | { 193 | return _promise->_tag == detail::tag::cancelled; 194 | } 195 | 196 | ~lazy_task() 197 | { 198 | if (_promise) 199 | release(); 200 | } 201 | 202 | private: 203 | void release() noexcept 204 | { 205 | if (_promise->_then) 206 | coroutine::destroy(_promise); 207 | else 208 | coroutine_final_run(coroutine::from_promise(_promise)); 209 | } 210 | 211 | promise_type* _promise; 212 | }; 213 | 214 | template 215 | inline void swap(lazy_task& a, lazy_task& b) noexcept 216 | { 217 | a.swap(b); 218 | } 219 | } 220 | 221 | #endif -------------------------------------------------------------------------------- /include/co2/detail/task.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_DETAIL_TASK_HPP_INCLUDED 8 | #define CO2_DETAIL_TASK_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace co2 17 | { 18 | template 19 | struct task; 20 | 21 | template 22 | struct shared_task; 23 | } 24 | 25 | namespace co2 { namespace task_detail 26 | { 27 | using detail::tag; 28 | 29 | template 30 | using cref_t = std::add_lvalue_reference_t>; 31 | 32 | struct promise_base 33 | { 34 | bool initial_suspend() noexcept 35 | { 36 | return false; 37 | } 38 | 39 | bool cancellation_requested() const noexcept 40 | { 41 | return false; 42 | } 43 | }; 44 | 45 | template 46 | struct promise_data : Base 47 | { 48 | using val_t = detail::wrap_reference_t; 49 | 50 | template 51 | void set_result(U&& u) 52 | { 53 | new(&_data.value) val_t(std::forward(u)); 54 | Base::_tag = tag::value; 55 | } 56 | 57 | void set_exception(std::exception_ptr e) noexcept 58 | { 59 | new(&_data.exception) std::exception_ptr(std::move(e)); 60 | Base::_tag = tag::exception; 61 | } 62 | 63 | T&& get() 64 | { 65 | switch (Base::_tag) 66 | { 67 | case tag::exception: 68 | std::rethrow_exception(_data.exception); 69 | case tag::cancelled: 70 | throw task_cancelled(); 71 | default: 72 | break; 73 | } 74 | return static_cast(_data.value); 75 | } 76 | 77 | ~promise_data() 78 | { 79 | _data.destroy(Base::_tag); 80 | } 81 | 82 | detail::storage _data; 83 | }; 84 | 85 | template 86 | struct promise_data : Base 87 | { 88 | void set_result() noexcept 89 | { 90 | Base::_tag = tag::value; 91 | } 92 | 93 | void set_exception(std::exception_ptr e) noexcept 94 | { 95 | _e = std::move(e); 96 | Base::_tag = tag::exception; 97 | } 98 | 99 | void get() 100 | { 101 | switch (Base::_tag) 102 | { 103 | case tag::exception: 104 | std::rethrow_exception(_e); 105 | case tag::cancelled: 106 | throw task_cancelled(); 107 | default: 108 | return; 109 | } 110 | } 111 | 112 | std::exception_ptr _e; 113 | }; 114 | 115 | template 116 | struct impl; 117 | 118 | template class Task, class T, class Promise> 119 | struct impl, Promise> 120 | { 121 | struct promise_type : promise_data 122 | { 123 | Task get_return_object(coroutine& coro) 124 | { 125 | coro(); 126 | return Task(this); 127 | } 128 | 129 | void cancel() noexcept 130 | { 131 | Promise::_tag = tag::cancelled; 132 | } 133 | }; 134 | 135 | impl() noexcept : _promise() {} 136 | 137 | impl(impl&& other) noexcept : _promise(other._promise) 138 | { 139 | other._promise = nullptr; 140 | } 141 | 142 | impl& operator=(impl&& other) noexcept 143 | { 144 | if (_promise) 145 | release(); 146 | _promise = other._promise; 147 | other._promise = nullptr; 148 | return *this; 149 | } 150 | 151 | explicit impl(promise_type* promise) noexcept : _promise(promise) {} 152 | 153 | ~impl() 154 | { 155 | if (_promise) 156 | release(); 157 | } 158 | 159 | explicit operator bool() const noexcept 160 | { 161 | return !!_promise; 162 | } 163 | 164 | bool valid() const noexcept 165 | { 166 | return !!_promise; 167 | } 168 | 169 | void swap(Task& other) noexcept 170 | { 171 | std::swap(_promise, other._promise); 172 | } 173 | 174 | void reset() noexcept 175 | { 176 | if (_promise) 177 | { 178 | release(); 179 | _promise = nullptr; 180 | } 181 | } 182 | 183 | bool is_cancelled() const noexcept 184 | { 185 | return _promise->_tag == tag::cancelled; 186 | } 187 | 188 | bool await_ready() const noexcept 189 | { 190 | return !_promise->_then.load(std::memory_order_acquire); 191 | } 192 | 193 | bool await_suspend(coroutine<>& cb) noexcept 194 | { 195 | return _promise->follow(cb); 196 | } 197 | 198 | protected: 199 | void release() noexcept 200 | { 201 | if (_promise->test_last()) 202 | coroutine::destroy(_promise); 203 | } 204 | 205 | promise_type* _promise; 206 | }; 207 | 208 | template 209 | auto convert(FromTask t) CO2_BEG(ToTask, (t), CO2_TEMP_SIZE(0);) 210 | { 211 | if (!t.await_ready()) 212 | { 213 | CO2_SUSPEND([&](coroutine<>& coro) 214 | { 215 | return t.await_suspend(coro); 216 | }); 217 | } 218 | CO2_RETURN(t.await_resume()); 219 | } CO2_END 220 | }} 221 | 222 | #endif -------------------------------------------------------------------------------- /include/co2/blocking.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2017 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_BLOCKING_HPP_INCLUDED 8 | #define CO2_BLOCKING_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace co2 { namespace blocking_detail 21 | { 22 | struct promise_base 23 | { 24 | bool initial_suspend() noexcept 25 | { 26 | return false; 27 | } 28 | 29 | bool cancellation_requested() const noexcept 30 | { 31 | return false; 32 | } 33 | 34 | void set_result() noexcept 35 | { 36 | std::unique_lock lock(mtx); 37 | ready = true; 38 | } 39 | 40 | void cancel() noexcept 41 | { 42 | std::unique_lock lock(mtx); 43 | ready = -1; 44 | } 45 | 46 | std::mutex mtx; 47 | std::condition_variable cond; 48 | int ready = false; 49 | }; 50 | 51 | struct task 52 | { 53 | struct promise_type : promise_base 54 | { 55 | bool final_suspend() noexcept 56 | { 57 | cond.notify_one(); 58 | return true; 59 | } 60 | 61 | task get_return_object(coroutine& coro) 62 | { 63 | task ret(this); 64 | coro.resume(); 65 | return ret; 66 | } 67 | 68 | void wait() 69 | { 70 | std::unique_lock lock(mtx); 71 | while (!ready) 72 | cond.wait(lock); 73 | if (ready == -1) 74 | throw std::system_error(std::make_error_code(std::errc::operation_canceled)); 75 | } 76 | }; 77 | 78 | explicit task(promise_type* promise) : promise(promise) {} 79 | 80 | struct finalizer 81 | { 82 | promise_type* promise; 83 | 84 | ~finalizer() 85 | { 86 | coroutine::destroy(promise); 87 | } 88 | }; 89 | 90 | template 91 | static auto run(Awaitable& a, Alloc alloc) CO2_BEG(task, (a) new(alloc), CO2_TEMP_SIZE(0);) 92 | { 93 | CO2_SUSPEND([&](co2::coroutine<>& c) { return co2::await_suspend(a, c); }); 94 | } CO2_END 95 | 96 | using frame_storage = detail::fixed_storage>; 98 | 99 | promise_type* promise; 100 | }; 101 | 102 | struct timed_task 103 | { 104 | struct promise_type : promise_base 105 | { 106 | bool final_suspend() noexcept 107 | { 108 | cond.notify_one(); 109 | return !last_owner.test_and_set(std::memory_order_relaxed); 110 | } 111 | 112 | timed_task get_return_object(coroutine& coro) 113 | { 114 | timed_task ret(this); 115 | coro.resume(); 116 | return ret; 117 | } 118 | 119 | template 120 | bool wait_until(std::chrono::time_point const& timeout_time) 121 | { 122 | std::unique_lock lock(mtx); 123 | while (!ready) 124 | { 125 | if (cond.wait_until(lock, timeout_time) == std::cv_status::timeout) 126 | break; 127 | } 128 | if (ready == -1) 129 | throw std::system_error(std::make_error_code(std::errc::operation_canceled)); 130 | return !!ready; 131 | } 132 | 133 | std::atomic_flag last_owner = ATOMIC_FLAG_INIT; 134 | }; 135 | 136 | explicit timed_task(promise_type* promise) : promise(promise) {} 137 | 138 | struct finalizer 139 | { 140 | promise_type* promise; 141 | 142 | ~finalizer() 143 | { 144 | if (promise->last_owner.test_and_set(std::memory_order_relaxed)) 145 | coroutine::destroy(promise); 146 | } 147 | }; 148 | 149 | template 150 | static auto run(Awaitable a) CO2_BEG(timed_task, (a), CO2_TEMP_SIZE(0);) 151 | { 152 | CO2_SUSPEND([&](co2::coroutine<>& c) { return co2::await_suspend(a, c); }); 153 | } CO2_END 154 | 155 | promise_type* promise; 156 | }; 157 | }} 158 | 159 | namespace co2 160 | { 161 | template 162 | void wait(Awaitable&& a) 163 | { 164 | if (await_ready(a)) 165 | return; 166 | 167 | blocking_detail::task::frame_storage mem; 168 | auto task(blocking_detail::task::run(a, mem.alloc())); 169 | blocking_detail::task::finalizer _{task.promise}; 170 | task.promise->wait(); 171 | } 172 | 173 | template 174 | bool wait_until(Awaitable&& a, std::chrono::time_point const& timeout_time) 175 | { 176 | if (await_ready(a)) 177 | return true; 178 | 179 | auto task(blocking_detail::timed_task::run(std::forward(a))); 180 | blocking_detail::timed_task::finalizer _{task.promise}; 181 | return task.promise->wait_until(timeout_time); 182 | } 183 | 184 | template 185 | inline bool wait_for(Awaitable&& a, std::chrono::duration const& rel_time) 186 | { 187 | return wait_until(std::forward(a), std::chrono::steady_clock::now() + rel_time); 188 | } 189 | 190 | template 191 | inline decltype(auto) get(Awaitable&& a) 192 | { 193 | wait(a); 194 | return await_resume(a); 195 | } 196 | } 197 | 198 | #endif -------------------------------------------------------------------------------- /include/co2/recursive_generator.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_RECURSIVE_GENERATOR_HPP_INCLUDED 8 | #define CO2_RECURSIVE_GENERATOR_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 15 | { 16 | template 17 | struct recursive_generator 18 | { 19 | struct promise_type 20 | { 21 | using val_t = detail::wrap_reference_t; 22 | 23 | recursive_generator get_return_object(coroutine& coro) 24 | { 25 | _parent = std::move(coro); 26 | _parent(); 27 | return recursive_generator(this); 28 | } 29 | 30 | bool initial_suspend() noexcept 31 | { 32 | return true; 33 | } 34 | 35 | bool final_suspend() noexcept 36 | { 37 | if (_head != &_parent) 38 | { 39 | _head->swap(_parent); 40 | (*_head)(); 41 | } 42 | return true; 43 | } 44 | 45 | bool cancellation_requested() const noexcept 46 | { 47 | return false; 48 | } 49 | 50 | void set_result() 51 | { 52 | reset_value(); 53 | } 54 | 55 | void cancel() 56 | { 57 | reset_value(); 58 | } 59 | 60 | void set_exception(std::exception_ptr e) 61 | { 62 | reset_value(); 63 | _tag = detail::tag::exception; 64 | new(&_data) std::exception_ptr(std::move(e)); 65 | } 66 | 67 | suspend_always yield_value(T&& t) 68 | { 69 | return yield_value(std::forward(t)); 70 | } 71 | 72 | template 73 | suspend_always yield_value(U&& u) 74 | { 75 | reset_value(); 76 | _tag = detail::tag::value; 77 | new(&_data) val_t(std::forward(u)); 78 | return {}; 79 | } 80 | 81 | auto yield_value(recursive_generator&& child) 82 | { 83 | return yield_from(std::move(child)); 84 | } 85 | 86 | auto yield_value(recursive_generator& child) 87 | { 88 | return yield_from(child); 89 | } 90 | 91 | T&& get() 92 | { 93 | return std::forward(_data.value); 94 | } 95 | 96 | void rethrow_exception() 97 | { 98 | if (_tag == detail::tag::exception) 99 | { 100 | auto ex(std::move(_data.exception)); 101 | _data.exception.~exception_ptr(); 102 | std::rethrow_exception(std::move(ex)); 103 | } 104 | } 105 | 106 | coroutine& current() 107 | { 108 | return _parent; 109 | } 110 | 111 | private: 112 | 113 | void reset_value() 114 | { 115 | if (_tag == detail::tag::value) 116 | _data.value.~val_t(); 117 | } 118 | 119 | template 120 | static auto yield_from(Generator&& child) 121 | { 122 | struct awaiter 123 | { 124 | bool await_ready() noexcept 125 | { 126 | return !_child._promise; 127 | } 128 | 129 | void await_suspend(coroutine const& coro) noexcept 130 | { 131 | auto head = coro.promise()._head; 132 | head->swap(_child._promise->_parent); 133 | head->promise()._head = head; 134 | (*head)(); 135 | } 136 | 137 | void await_resume() 138 | { 139 | if (_child._promise) 140 | _child._promise->rethrow_exception(); 141 | } 142 | 143 | Generator _child; 144 | }; 145 | return awaiter{std::forward(child)}; 146 | } 147 | 148 | coroutine* _head = &_parent; 149 | coroutine _parent; 150 | detail::storage _data; 151 | detail::tag _tag = detail::tag::pending; 152 | }; 153 | 154 | struct iterator 155 | : boost::iterator_facade 156 | { 157 | iterator() : _coro() {} 158 | 159 | explicit iterator(coroutine& coro) : _coro(&coro) 160 | { 161 | increment(); 162 | } 163 | 164 | private: 165 | 166 | friend class boost::iterator_core_access; 167 | 168 | void increment() 169 | { 170 | auto& promise = _coro->promise(); 171 | (*_coro)(); 172 | promise.rethrow_exception(); 173 | if (!*_coro) 174 | _coro = nullptr; 175 | } 176 | 177 | bool equal(iterator const& other) const 178 | { 179 | return _coro == other._coro; 180 | } 181 | 182 | T&& dereference() const 183 | { 184 | return _coro->promise().get(); 185 | } 186 | 187 | coroutine* _coro; 188 | }; 189 | 190 | recursive_generator() noexcept : _promise() {} 191 | 192 | recursive_generator(recursive_generator&& other) noexcept 193 | : _promise(other._promise) 194 | { 195 | other._promise = nullptr; 196 | } 197 | 198 | recursive_generator& operator=(recursive_generator other) noexcept 199 | { 200 | this->~recursive_generator(); 201 | return *new(this) recursive_generator(std::move(other)); 202 | } 203 | 204 | ~recursive_generator() 205 | { 206 | if (_promise) 207 | { 208 | _promise->current().reset(); 209 | coroutine::destroy(_promise); 210 | } 211 | } 212 | 213 | void swap(recursive_generator& other) noexcept 214 | { 215 | std::swap(_promise, other._promise); 216 | } 217 | 218 | iterator begin() 219 | { 220 | if (_promise) 221 | { 222 | if (auto& coro = _promise->current()) 223 | return iterator(coro); 224 | } 225 | return {}; 226 | } 227 | 228 | iterator end() 229 | { 230 | return {}; 231 | } 232 | 233 | private: 234 | 235 | explicit recursive_generator(promise_type* promise) noexcept 236 | : _promise(promise) 237 | {} 238 | 239 | promise_type* _promise; 240 | }; 241 | 242 | template 243 | inline void swap(recursive_generator& a, recursive_generator& b) noexcept 244 | { 245 | a.swap(b); 246 | } 247 | } 248 | 249 | #endif -------------------------------------------------------------------------------- /include/co2/utility/ornion.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_ORNION_HPP_INCLUDED 8 | #define CO2_ORNION_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace co2 { namespace ornion_detail 15 | { 16 | template 17 | struct list 18 | { 19 | static void at(); 20 | }; 21 | 22 | template 23 | struct list : list 24 | { 25 | using list::at; 26 | 27 | static T at(std::integral_constant); 28 | }; 29 | 30 | template 31 | struct switcher; 32 | 33 | template 34 | struct switcher 35 | { 36 | template 37 | static void apply(std::size_t n, F&& f) 38 | { 39 | return f(T1{}); 40 | } 41 | }; 42 | 43 | template 44 | struct switcher 45 | { 46 | template 47 | static void apply(std::size_t n, F&& f) 48 | { 49 | switch (n) 50 | { 51 | case 0: return f(T1{}); 52 | case 1: return f(T2{}); 53 | } 54 | } 55 | }; 56 | 57 | template 58 | struct switcher 59 | { 60 | template 61 | static void apply(std::size_t n, F&& f) 62 | { 63 | switch (n) 64 | { 65 | case 0: return f(T1{}); 66 | case 1: return f(T2{}); 67 | case 2: return f(T3{}); 68 | } 69 | } 70 | }; 71 | 72 | template 73 | struct tag 74 | { 75 | using type = T; 76 | }; 77 | 78 | template<> 79 | struct tag 80 | { 81 | using type = detail::void_; 82 | }; 83 | 84 | template 85 | using storage = std::aligned_union_t<0, std::exception_ptr, typename tag::type...>; 86 | 87 | template 88 | using type_at = typename tag::at(std::integral_constant{}))>::type; 89 | 90 | template 91 | struct index_switcher; 92 | 93 | template 94 | struct index_switcher> 95 | { 96 | using type = switcher...>; 97 | }; 98 | 99 | struct reset_fn 100 | { 101 | void* p; 102 | 103 | template 104 | void operator()(Tag) const 105 | { 106 | using type = typename Tag::type; 107 | static_cast(p)->~type(); 108 | } 109 | }; 110 | 111 | struct move_fn 112 | { 113 | void* src; 114 | void* dst; 115 | 116 | template 117 | void operator()(Tag) const 118 | { 119 | using type = typename Tag::type; 120 | new(dst) type(std::move(*static_cast(src))); 121 | } 122 | }; 123 | 124 | struct copy_fn 125 | { 126 | void const* src; 127 | void* dst; 128 | 129 | template 130 | void operator()(Tag) const 131 | { 132 | using type = typename Tag::type; 133 | new(dst) type(*static_cast(src)); 134 | } 135 | }; 136 | }} 137 | 138 | namespace co2 139 | { 140 | template 141 | struct ornion 142 | { 143 | using dispatch = ornion_detail::switcher< 144 | ornion_detail::tag, ornion_detail::tag...>; 145 | 146 | template 147 | friend struct ornion; 148 | 149 | ornion() : _which(0) {} 150 | 151 | ornion(ornion&& other) : _which(other._which) 152 | { 153 | if (_which & 1) 154 | new(&_data) std::exception_ptr(reinterpret_cast(other._data)); 155 | else 156 | dispatch::apply(_which >> 1, ornion_detail::move_fn{&other._data, &_data}); 157 | } 158 | 159 | ornion(ornion const& other) : _which(other._which) 160 | { 161 | if (_which & 1) 162 | new(&_data) std::exception_ptr(reinterpret_cast(other._data)); 163 | else 164 | dispatch::apply(_which >> 1, ornion_detail::copy_fn{&other._data, &_data}); 165 | } 166 | 167 | template 168 | ornion(ornion&& other) : _which(other._which) 169 | { 170 | using index_dispatch = typename 171 | ornion_detail::index_switcher>::type; 172 | if (_which & 1) 173 | new(&_data) std::exception_ptr(reinterpret_cast(other._data)); 174 | else 175 | index_dispatch::apply(_which >> 1, [src = &other._data, dst = &_data](auto idx) 176 | { 177 | using namespace ornion_detail; 178 | using from = typename tag::at(idx))>::type; 179 | using to = typename tag::at(idx))>::type; 180 | new(dst) to(std::move(*reinterpret_cast(src))); 181 | }); 182 | } 183 | 184 | template 185 | ornion(ornion const& other) : _which(other._which) 186 | { 187 | using index_dispatch = typename 188 | ornion_detail::index_switcher>::type; 189 | if (_which & 1) 190 | new(&_data) std::exception_ptr(reinterpret_cast(other._data)); 191 | else 192 | index_dispatch::apply(_which >> 1, [src = &other._data, dst = &_data](auto idx) 193 | { 194 | using namespace ornion_detail; 195 | using from = typename tag::at(idx))>::type; 196 | using to = typename tag::at(idx))>::type; 197 | new(dst) to(*reinterpret_cast(src)); 198 | }); 199 | } 200 | 201 | ornion& operator=(ornion&& other) 202 | { 203 | this->~ornion(); 204 | return *new(this) ornion(std::move(other)); 205 | } 206 | 207 | ornion& operator=(ornion const& other) 208 | { 209 | this->~ornion(); 210 | return *new(this) ornion(other); 211 | } 212 | 213 | ~ornion() 214 | { 215 | reset(); 216 | } 217 | 218 | int which() const 219 | { 220 | return int(_which >> 1) - 1; 221 | } 222 | 223 | template 224 | decltype(auto) get() 225 | { 226 | using type = ornion_detail::type_at; 227 | if (_which & 1) 228 | std::rethrow_exception(reinterpret_cast(_data)); 229 | return reinterpret_cast(_data), detail::void_{}; 230 | } 231 | 232 | template 233 | void set_value(U&& u) 234 | { 235 | using type = ornion_detail::type_at; 236 | reset(); 237 | new(&_data) type(std::forward(u)); 238 | _which = (N + 1) << 1; 239 | } 240 | 241 | template 242 | void set_exception(std::exception_ptr const& e) 243 | { 244 | reset(); 245 | new(&_data) std::exception_ptr(e); 246 | _which = ((N + 1) << 1) | 1; 247 | } 248 | 249 | void reset() 250 | { 251 | if (_which & 1) 252 | reinterpret_cast(_data).~exception_ptr(); 253 | else 254 | dispatch::apply(_which >> 1, ornion_detail::reset_fn{&_data}); 255 | _which = 0; 256 | } 257 | 258 | private: 259 | 260 | ornion_detail::storage _data; 261 | unsigned _which; 262 | }; 263 | 264 | template 265 | inline void set_value(ornion& any, U&& u) 266 | { 267 | any.template set_value(std::forward(u)); 268 | } 269 | 270 | template 271 | inline void set_exception(ornion& any, std::exception_ptr const& e) 272 | { 273 | any.template set_exception(e); 274 | } 275 | 276 | template 277 | inline decltype(auto) get(ornion& any) 278 | { 279 | return any.template get(); 280 | } 281 | } 282 | 283 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CO2 - Coroutine II [![Try it online][badge.wandbox]](https://wandbox.org/permlink/hl2KlNuVWPFwggGE) 2 | === 3 | 4 | A header-only C++ stackless coroutine emulation library, providing interface close to [N4286](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4286.pdf). 5 | 6 | > __Note__ \ 7 | > All the major compilers support coroutine now, CO2 has accomplished its mission and we don't recommed using it for new code. 8 | However, it does have a successor - [COZ](https://github.com/jamboree/coz), which features zero-allocation. 9 | 10 | ## Requirements 11 | 12 | - C++14 13 | - [Boost](http://www.boost.org/) 14 | 15 | ## Overview 16 | 17 | Many of the concepts are similar to N4286, if you're not familiar with the proposal, please read the paper first. 18 | 19 | A coroutine written in this library looks like below: 20 | ```c++ 21 | auto function(Args... args) CO2_BEG(return_type, (args...), locals...) 22 | { 23 | 24 | } CO2_END 25 | ``` 26 | 27 | `function` is really just a plain-old function, you can forward declare it as usual: 28 | ```c++ 29 | auto function(Args... args) -> return_type; 30 | return_type function(Args... args); // same as above 31 | ``` 32 | 33 | Of course, lambda expressions can be used as well: 34 | ```c++ 35 | [](Args... args) CO2_BEG(return_type, (args...), locals...) 36 | { 37 | 38 | } CO2_END 39 | ``` 40 | 41 | The coroutine body has to be surrounded with 2 macros: `CO2_BEG` and `CO2_END`. 42 | 43 | The macro `CO2_BEG` requires you to provide some parameters: 44 | * _return-type_ - the function's return-type, e.g. `co2::task<>` 45 | * _captures_ - a list of comma separated args with an optional `new` clause, e.g. `(a, b) new(alloc)` 46 | * _locals_ - a list of local-variable definitions, e.g. `int a;` 47 | 48 | If there's no _captures_ and _locals_, it looks like: 49 | ```c++ 50 | CO2_BEG(return_type, ()) 51 | ``` 52 | 53 | You can intialize the local variables as below: 54 | ```c++ 55 | auto f(int i) CO2_BEG(return_type, (i), 56 | int i2 = i * 2; 57 | std::string msg{"hello"}; 58 | ) 59 | { 60 | // coroutine-body 61 | } CO2_END 62 | ``` 63 | 64 | Note that the `()` initializer cannot be used here, e.g. `int i2(i * 2);`, due to some emulation restrictions. 65 | Besides, `auto` deduced variable cannot be used directly, i.e. `auto var{expr};`, you have to use `CO2_AUTO(var, expr);` instead. 66 | 67 | Note that in this emulation, local variables intialization happens before `initial_suspend`, and if any exception is thrown during the intialization, `set_exception` won't be called, instead, the exception will propagate to the caller directly. 68 | 69 | By default, the library allocates memory for coroutines using `std::allocator`, you can specify the allocator by appending the `new` clause after the args-list, for example: 70 | 71 | ```c++ 72 | template 73 | auto coro(Alloc alloc, int i) CO2_BEG(return_type, (i) new(alloc)) 74 | ``` 75 | 76 | The `alloc` doesn't have to appear in the args-list if it's not used inside the coroutine-body. The `new` clause accepts an expression that evaluates to an _Allocator_, it's not restricted to identifiers as in the args-list. 77 | 78 | Inside the coroutine body, there are some restrictions: 79 | * local variables with automatic storage cannot cross suspend-resume points - you should specify them in local variables section of `CO2_BEG` as described above 80 | * `return` should be replaced with `CO2_RETURN`/`CO2_RETURN_FROM`/`CO2_RETURN_LOCAL` 81 | * try-catch block surrouding suspend-resume points should be replaced with `CO2_TRY` & `CO2_CATCH` 82 | * identifiers starting with `_co2_` are reserved for this library 83 | 84 | After defining the coroutine body, remember to close it with `CO2_END`. 85 | 86 | ### await & yield 87 | 88 | In _CO2_, `await` is implemented as a statement instead of an expression due to the emulation limitation, and it has 4 variants: `CO2_AWAIT`, `CO2_AWAIT_SET`, `CO2_AWAIT_LET` and `CO2_AWAIT_RETURN`. 89 | 90 | * `CO2_AWAIT(expr)` 91 | 92 | Equivalent to `await expr`. 93 | 94 | * `CO2_AWAIT_SET(var, expr)` 95 | 96 | Equivalent to `var = await expr`. 97 | 98 | * `CO2_AWAIT_LET(var-decl, expr, body)` 99 | 100 | This allows you bind the awaited result to a temporary and do something to it. 101 | ```c++ 102 | CO2_AWAIT_LET(auto i, task, 103 | { 104 | doSomething(i); 105 | }); 106 | ``` 107 | 108 | * `CO2_AWAIT_RETURN(expr)` 109 | 110 | Equivalent to `return await expr`. 111 | 112 | * `CO2_AWAIT_APPLY(f, expr)` 113 | 114 | Equivalent to `f(await expr)`, where `f` can be a unary function or macro. 115 | 116 | > *Note* - 117 | > If your compiler supports _Statement Expression_ extension (e.g. GCC & Clang), you can use `CO2_AWAIT` as an expression. 118 | However, don't use more than one `CO2_AWAIT` in a single statement, and don't use it as an argument of a function in company with other arguments. 119 | 120 | * `CO2_YIELD(expr)` 121 | 122 | Equivalent to `CO2_AWAIT(.yield_value(expr))`, as how `yield` is defined in N4286. 123 | 124 | * `CO2_SUSPEND(fn)` 125 | 126 | Suspend the coroutine with the callable object `fn`. This signature of `fn` is the same as `await_suspend`. 127 | 128 | 129 | The fact that `await` in _CO2_ is not an expression has an implication on object lifetime, consider this case: 130 | 131 | `await something{temporaries}` and `something` holds references to temporaries. 132 | 133 | It's safe if `await` is an expression as in N4286, but in _CO2_, `CO2_AWAIT(something{temporaries})` is an emulated statement, the `temporaries` will go out of scope. 134 | 135 | Besides, the awaiter itself has to be stored somewhere, by default, _CO2_ reserves `(sizeof(pointer) + sizeof(int)) * 2` bytes for that, if the size of awaiter is larger than that, dynamic allocation will be used. 136 | If the default size is too large or too small for you, you can specify the desired size with `CO2_TEMP_SIZE` anywhere in the local variables section: 137 | ```c++ 138 | auto f() CO2_BEG(return_type, (), 139 | CO2_TEMP_SIZE(bytes); 140 | ) 141 | { 142 | ... 143 | } CO2_END 144 | ``` 145 | 146 | If you want to avoid dynamic allocation, you can define `CO2_WARN_DYN_ALLOC` to turn on dynamic allocation warning and enlarge `CO2_TEMP_SIZE` accordingly. 147 | 148 | ### Replacements for normal language constructs 149 | 150 | Sometimes you can't use the normal language constructs directly, in such cases, you need to use the macro replacements instead. 151 | 152 | #### return 153 | 154 | * `return` -> `CO2_RETURN()` 155 | * `return non-void-expr` -> `CO2_RETURN(non-void-expr)` 156 | * `return maybe-void-expr` -> `CO2_RETURN_FROM(maybe-void-expr)` (useful in generic code) 157 | * `return local-variable` -> `CO2_RETURN_LOCAL(local-variable)` (RV w/o explicit move) 158 | 159 | #### try-catch 160 | 161 | Needed only if the try-block is involved with the suspend-resume points. 162 | 163 | ```c++ 164 | CO2_TRY {...} 165 | CO2_CATCH (std::runtime_error& e) {...} 166 | catch (std::exception& e) {...} 167 | ``` 168 | 169 | Note that only the first `catch` clause needs to be spelled as `CO2_CATCH`, the subsequent ones should use the plain `catch`. 170 | 171 | #### switch-case 172 | 173 | Needed only if the switch-body is involved with the suspend-resume points. There are 2 variants: 174 | * `CO2_SWITCH` 175 | * `CO2_SWITCH_CONT` - use when switch-body contains `continue`. 176 | 177 | ```c++ 178 | CO2_SWITCH (which, 179 | case 1, 180 | ( 181 | ... 182 | ), 183 | case N, 184 | ( 185 | ... 186 | ), 187 | default, 188 | ( 189 | ... 190 | )) 191 | ``` 192 | 193 | Note that `break` is still needed if you don't want the control flow to fall through the subsequent cases, also note that `continue` **cannot** be used in `CO2_SWITCH` to continue the outer loop, use `CO2_SWITCH_CONT` instead in that case. 194 | 195 | ## Difference from N4286 196 | 197 | * Unlike `coroutine_handle` in N4286 which has raw-pointer semantic (i.e. no RAII), `coroutine` has unique-semantic (move-only). 198 | * `coroutine_traits` depends on return_type only. 199 | 200 | ### Additional customization points for promise_type 201 | 202 | * `void cancel()` 203 | 204 | This allows you specify the behavior of the coroutine when it is cancelled (i.e. when `cancellation_requested()` returns true or coroutine is reset). 205 | 206 | * `bool try_suspend()` 207 | 208 | This is called before the coroutine is suspended, if it returns `false`, the coroutine won't be suspended, instead, it will be cancelled. 209 | However, it won't be called for `final_suspend`. 210 | 211 | * `bool try_resume()` 212 | 213 | This is called before the coroutine is resumed, if it returns `false`, the coroutine won't be resumed, instead, it will be detached. 214 | 215 | * `bool try_cancel()` 216 | 217 | This is called before the coroutine is reset, if it returns `false`, the coroutine won't be cancelled, instead, it will be detached. 218 | 219 | ## Reference 220 | 221 | __Headers__ 222 | * `#include ` 223 | * `#include ` 224 | * `#include ` 225 | * `#include ` 226 | * `#include ` 227 | * `#include ` 228 | * `#include ` 229 | * `#include ` 230 | * `#include ` 231 | * `#include ` 232 | * `#include ` 233 | * `#include ` 234 | * `#include ` 235 | * `#include ` 236 | * `#include ` 237 | 238 | __Macros__ 239 | * `CO2_BEG` 240 | * `CO2_END` 241 | * `CO2_AWAIT` 242 | * `CO2_AWAIT_SET` 243 | * `CO2_AWAIT_LET` 244 | * `CO2_AWAIT_RETURN` 245 | * `CO2_AWAIT_APPLY` 246 | * `CO2_YIELD` 247 | * `CO2_SUSPEND` 248 | * `CO2_RETURN` 249 | * `CO2_RETURN_FROM` 250 | * `CO2_RETURN_LOCAL` 251 | * `CO2_TRY` 252 | * `CO2_CATCH` 253 | * `CO2_SWITCH` 254 | * `CO2_TEMP_SIZE` 255 | * `CO2_AUTO` 256 | 257 | __Classes__ 258 | * `co2::coroutine_traits` 259 | * `co2::coroutine` 260 | * `co2::generator` 261 | * `co2::recursive_generator` 262 | * `co2::task` 263 | * `co2::shared_task` 264 | * `co2::lazy_task` 265 | * `co2::event` 266 | * `co2::mutex` 267 | * `co2::work_group` 268 | * `co2::suspend_always` 269 | * `co2::suspend_never` 270 | * `co2::stack_manager` 271 | * `co2::stack_buffer` 272 | * `co2::stack_allocator` 273 | 274 | ## Example 275 | 276 | ### Generator 277 | 278 | __Define a generator__ 279 | ```c++ 280 | auto range(int i, int e) CO2_BEG(co2::generator, (i, e)) 281 | { 282 | for ( ; i != e; ++i) 283 | CO2_YIELD(i); 284 | } CO2_END 285 | ``` 286 | For those interested in the black magic, [here](https://gist.github.com/jamboree/d6c324b6cd4a11676cda) is the preprocessed output (formatted for reading). 287 | 288 | __Use a generator__ 289 | ```c++ 290 | for (auto i : range(1, 10)) 291 | { 292 | std::cout << i << ", "; 293 | } 294 | ``` 295 | 296 | ### Recursive Generator 297 | 298 | Same example as above, using `recursive_generator` with custom allocator: 299 | ```c++ 300 | template 301 | auto recursive_range(Alloc alloc, int a, int b) 302 | CO2_BEG(co2::recursive_generator, (alloc, a, b) new(alloc), 303 | int n = b - a; 304 | ) 305 | { 306 | if (n <= 0) 307 | CO2_RETURN(); 308 | 309 | if (n == 1) 310 | { 311 | CO2_YIELD(a); 312 | CO2_RETURN(); 313 | } 314 | 315 | n = a + n / 2; 316 | CO2_YIELD(recursive_range(alloc, a, n)); 317 | CO2_YIELD(recursive_range(alloc, n, b)); 318 | } CO2_END 319 | ``` 320 | We use `stack_allocator` here: 321 | ```c++ 322 | co2::stack_buffer<64 * 1024> buf; 323 | co2::stack_allocator<> alloc(buf); 324 | for (auto i : recursive_range(alloc, 1, 10)) 325 | { 326 | std::cout << i << ", "; 327 | } 328 | ``` 329 | 330 | ### Task scheduling 331 | It's very easy to write a generic task that can be used with different schedulers. 332 | For example, a `fib` task that works with [`concurrency::task_group`](https://msdn.microsoft.com/en-us/library/dd470722.aspx) and [`tbb::task_group`](https://software.intel.com/en-us/node/506287) can be defined as below: 333 | ```c++ 334 | template 335 | auto fib(Scheduler& sched, int n) CO2_BEG(co2::task, (sched, n), 336 | co2::task a, b; 337 | ) 338 | { 339 | // Schedule the continuation. 340 | CO2_SUSPEND([&](co2::coroutine<>& c) { sched.run([h = c.detach()]{ co2::coroutine<>{h}(); }); }); 341 | // From now on, the code is executed on the Scheduler. 342 | if (n >= 2) 343 | { 344 | a = fib(sched, n - 1); 345 | b = fib(sched, n - 2); 346 | CO2_AWAIT_SET(n, a); 347 | CO2_AWAIT_APPLY(n +=, b); 348 | } 349 | CO2_RETURN(n); 350 | } CO2_END 351 | ``` 352 | 353 | #### PPL Usage 354 | ```c++ 355 | concurrency::task_group sched; 356 | auto val = fib(sched, 16); 357 | std::cout << "ans: " << co2::get(val); 358 | sched.wait(); 359 | ``` 360 | 361 | #### TBB Usage 362 | ```c++ 363 | tbb::task_group sched; 364 | auto val = fib(sched, 16); 365 | std::cout << "ans: " << co2::get(val); 366 | sched.wait(); 367 | ``` 368 | 369 | ### ASIO echo server 370 | 371 | This example uses the sister library [act](https://github.com/jamboree/act) to change ASIO style callback into await. 372 | 373 | ```c++ 374 | auto session(asio::ip::tcp::socket sock) CO2_BEG(void, (sock), 375 | char buf[1024]; 376 | std::size_t len; 377 | act::error_code ec; 378 | ) 379 | { 380 | CO2_TRY 381 | { 382 | std::cout << "connected: " << sock.remote_endpoint() << std::endl; 383 | for ( ; ; ) 384 | { 385 | CO2_AWAIT_SET(len, act::read_some(sock, asio::buffer(buf), ec)); 386 | if (ec == asio::error::eof) 387 | CO2_RETURN(); 388 | CO2_AWAIT(act::write(sock, asio::buffer(buf, len))); 389 | } 390 | } 391 | CO2_CATCH (std::exception& e) 392 | { 393 | std::cout << "error: " << sock.remote_endpoint() << ": " << e.what() << std::endl; 394 | } 395 | } CO2_END 396 | 397 | auto server(asio::io_service& io, unsigned short port) CO2_BEG(void, (io, port), 398 | asio::ip::tcp::endpoint endpoint{asio::ip::tcp::v4(), port}; 399 | asio::ip::tcp::acceptor acceptor{io, endpoint}; 400 | asio::ip::tcp::socket sock{io}; 401 | ) 402 | { 403 | std::cout << "server running at: " << endpoint << std::endl; 404 | for ( ; ; ) 405 | { 406 | CO2_AWAIT(act::accept(acceptor, sock)); 407 | session(std::move(sock)); 408 | } 409 | } CO2_END 410 | ``` 411 | 412 | ## Performance 413 | The overhead of context-switch. See [benchmark.cpp](test/benchmark.cpp). 414 | 415 | Sample run (VS2015 Update 3, boost 1.63.0, 64-bit release build): 416 | ``` 417 | Run on (4 X 3200 MHz CPU s) 418 | Benchmark Time CPU Iterations 419 | -------------------------------------------------------- 420 | bench_coroutine2 82 ns 80 ns 8960000 421 | bench_co2 6 ns 6 ns 112000000 422 | bench_msvc 5 ns 5 ns 112000000 423 | ``` 424 | Lower is better. 425 | 426 | ![benchmark](doc/benchmark.png?raw=true) 427 | 428 | ## License 429 | 430 | Copyright (c) 2015-2018 Jamboree 431 | 432 | Distributed under the Boost Software License, Version 1.0. (See accompanying 433 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 434 | 435 | 436 | [badge.Wandbox]: https://img.shields.io/badge/try%20it-online-green.svg 437 | -------------------------------------------------------------------------------- /include/co2/coroutine.hpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////// 2 | Copyright (c) 2015-2018 Jamboree 3 | 4 | Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | //////////////////////////////////////////////////////////////////////////////*/ 7 | #ifndef CO2_COROUTINE_HPP_INCLUDED 8 | #define CO2_COROUTINE_HPP_INCLUDED 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace co2 28 | { 29 | template 30 | struct coroutine; 31 | 32 | template 33 | struct coroutine_traits 34 | { 35 | using promise_type = typename R::promise_type; 36 | }; 37 | 38 | template 39 | struct suspend 40 | { 41 | bool await_ready() noexcept 42 | { 43 | return !flag; 44 | } 45 | 46 | void await_suspend(coroutine<> const&) noexcept {} 47 | 48 | void await_resume() noexcept {} 49 | }; 50 | 51 | using suspend_always = suspend; 52 | using suspend_never = suspend; 53 | 54 | namespace detail 55 | { 56 | struct resumable_base 57 | { 58 | unsigned _next; 59 | unsigned _eh; 60 | void* _data; 61 | virtual void run(coroutine<>&) = 0; 62 | virtual void unwind(coroutine<>&) = 0; 63 | virtual void release() noexcept = 0; 64 | }; 65 | 66 | template 67 | struct resumable : resumable_base, private Promise 68 | { 69 | Promise& promise() 70 | { 71 | return *this; 72 | } 73 | 74 | static resumable* from_promise(Promise* p) 75 | { 76 | return static_cast(p); 77 | } 78 | }; 79 | 80 | struct trivial_promise_base 81 | { 82 | bool initial_suspend() noexcept 83 | { 84 | return false; 85 | } 86 | 87 | bool final_suspend() noexcept 88 | { 89 | return false; 90 | } 91 | 92 | bool cancellation_requested() const noexcept 93 | { 94 | return false; 95 | } 96 | 97 | void set_result() noexcept {} 98 | }; 99 | } 100 | 101 | using coroutine_handle = detail::resumable_base*; 102 | 103 | inline void*& coroutine_data(coroutine_handle h) 104 | { 105 | return h->_data; 106 | } 107 | 108 | template<> 109 | struct coroutine 110 | { 111 | using handle_type = coroutine_handle; 112 | 113 | struct promise_type; 114 | 115 | coroutine() noexcept : _ptr() {} 116 | 117 | explicit coroutine(handle_type handle) noexcept : _ptr(handle) {} 118 | 119 | coroutine(coroutine&& other) noexcept : _ptr(other._ptr) 120 | { 121 | other._ptr = nullptr; 122 | } 123 | 124 | coroutine& operator=(coroutine&& other) noexcept 125 | { 126 | reset(other._ptr); 127 | other._ptr = nullptr; 128 | return *this; 129 | } 130 | 131 | ~coroutine() 132 | { 133 | if (_ptr) 134 | _ptr->unwind(*this); 135 | } 136 | 137 | void reset() noexcept 138 | { 139 | if (_ptr) 140 | { 141 | _ptr->unwind(*this); 142 | _ptr = nullptr; 143 | } 144 | } 145 | 146 | void reset(handle_type handle) noexcept 147 | { 148 | if (_ptr) 149 | _ptr->unwind(*this); 150 | _ptr = handle; 151 | } 152 | 153 | void swap(coroutine& other) noexcept 154 | { 155 | std::swap(_ptr, other._ptr); 156 | } 157 | 158 | explicit operator bool() const noexcept 159 | { 160 | return !!_ptr; 161 | } 162 | 163 | void operator()() noexcept 164 | { 165 | _ptr->run(*this); 166 | } 167 | 168 | void resume() 169 | { 170 | _ptr->run(*this); 171 | } 172 | 173 | void* to_address() const noexcept 174 | { 175 | return _ptr; 176 | } 177 | 178 | handle_type handle() const noexcept 179 | { 180 | return _ptr; 181 | } 182 | 183 | handle_type detach() noexcept 184 | { 185 | auto handle = _ptr; 186 | _ptr = nullptr; 187 | return handle; 188 | } 189 | 190 | protected: 191 | 192 | detail::resumable_base* _ptr; 193 | }; 194 | 195 | template 196 | struct coroutine : coroutine<> 197 | { 198 | using promise_type = Promise; 199 | 200 | coroutine() = default; 201 | 202 | coroutine(std::nullptr_t) noexcept {} 203 | 204 | explicit coroutine(detail::resumable* p) noexcept : coroutine<>(p) {} 205 | 206 | explicit coroutine(Promise* p) noexcept 207 | { 208 | if (p) 209 | _ptr = detail::resumable::from_promise(p); 210 | } 211 | 212 | Promise& promise() const noexcept 213 | { 214 | return static_cast*>(_ptr)->promise(); 215 | } 216 | 217 | static void destroy(Promise* p) 218 | { 219 | detail::resumable::from_promise(p)->release(); 220 | } 221 | 222 | static handle_type from_promise(Promise* p) 223 | { 224 | return detail::resumable::from_promise(p); 225 | } 226 | }; 227 | 228 | struct coroutine<>::promise_type : detail::trivial_promise_base 229 | { 230 | coroutine get_return_object(coroutine& coro) 231 | { 232 | coro.resume(); 233 | return std::move(coro); 234 | } 235 | }; 236 | 237 | template<> 238 | struct coroutine_traits 239 | { 240 | struct promise_type : detail::trivial_promise_base 241 | { 242 | void get_return_object(coroutine& coro) 243 | { 244 | coro(); 245 | } 246 | 247 | void set_exception(std::exception_ptr const&) noexcept 248 | { 249 | // Like std::thread, terminate the program if it throws. 250 | std::terminate(); 251 | } 252 | }; 253 | }; 254 | 255 | template 256 | inline bool operator==(coroutine const& lhs, coroutine const& rhs) 257 | { 258 | return lhs.to_address() == rhs.to_address(); 259 | } 260 | 261 | template 262 | inline bool operator!=(coroutine const& lhs, coroutine const& rhs) 263 | { 264 | return lhs.to_address() != rhs.to_address(); 265 | } 266 | 267 | namespace detail 268 | { 269 | template 270 | void coroutine_local_sched(coroutine_handle then, F f) noexcept 271 | { 272 | thread_local coroutine_handle* chain = nullptr; 273 | if (chain) 274 | { 275 | auto& next = *chain; 276 | coroutine_data(then) = next; 277 | next = then; 278 | } 279 | else 280 | { 281 | chain = &then; 282 | { 283 | coroutine<> coro{then}; 284 | then = nullptr; 285 | f(coro); 286 | } 287 | while (then) 288 | { 289 | coroutine<> coro{then}; 290 | then = static_cast(coroutine_data(then)); 291 | f(coro); 292 | } 293 | chain = nullptr; 294 | } 295 | } 296 | } 297 | 298 | inline void coroutine_final_run(coroutine_handle then) noexcept 299 | { 300 | detail::coroutine_local_sched(then, [](coroutine<>& coro) { coro(); }); 301 | } 302 | 303 | inline void coroutine_final_cancel(coroutine_handle then) noexcept 304 | { 305 | detail::coroutine_local_sched(then, [](coroutine<>&) {/*noop*/}); 306 | } 307 | } 308 | 309 | namespace co2 { namespace detail 310 | { 311 | namespace temp 312 | { 313 | struct warning; 314 | 315 | template 316 | using adjust_size = std::integral_constant RefSize ? Bytes : RefSize)>; 317 | 318 | struct default_size 319 | { 320 | using _co2_sz = std::integral_constant; 321 | }; 322 | 323 | template 324 | struct traits_non_ref 325 | { 326 | static void create(void* p, T&& t) 327 | { 328 | # if defined(CO2_WARN_DYN_ALLOC) 329 | # if defined(BOOST_MSVC) 330 | warning resort_to_dynamic_allocation(char(&)[sizeof(T)]); 331 | # else 332 | warning* resort_to_dynamic_allocation; 333 | # endif 334 | # endif 335 | *static_cast(p) = new T(std::move(t)); 336 | } 337 | 338 | static T& get(void* p) 339 | { 340 | return **static_cast(p); 341 | } 342 | 343 | static void reset(void* p) 344 | { 345 | delete *static_cast(p); 346 | } 347 | }; 348 | 349 | template 350 | struct traits_non_ref 351 | { 352 | static void create(void* p, T&& t) 353 | { 354 | new(p) T(std::move(t)); 355 | } 356 | 357 | static T& get(void* p) 358 | { 359 | return *static_cast(p); 360 | } 361 | 362 | static void reset(void* p) 363 | { 364 | static_cast(p)->~T(); 365 | } 366 | }; 367 | 368 | template 369 | struct traits_ref 370 | { 371 | static void create(void* p, T& t) 372 | { 373 | *static_cast(p) = &t; 374 | } 375 | 376 | static T& get(void* p) 377 | { 378 | return **static_cast(p); 379 | } 380 | 381 | static void reset(void*) {} 382 | }; 383 | 384 | template 385 | struct traits_non_ref_check 386 | { 387 | static constexpr bool needs_alloc = sizeof(T) > Bytes; 388 | 389 | static_assert(!needs_alloc || Bytes >= sizeof(void*), 390 | "CO2_TEMP_SIZE too small to hold a pointer"); 391 | 392 | using type = traits_non_ref; 393 | }; 394 | 395 | template 396 | struct traits : traits_non_ref_check::type {}; 397 | 398 | template 399 | struct traits : traits_ref 400 | { 401 | static_assert(Bytes >= sizeof(void*), 402 | "CO2_TEMP_SIZE too small to hold a reference"); 403 | }; 404 | 405 | template 406 | struct auto_reset 407 | { 408 | void* tmp; 409 | 410 | ~auto_reset() 411 | { 412 | traits::reset(tmp); 413 | } 414 | }; 415 | } 416 | 417 | template 418 | using storage_for = std::aligned_storage_t; 419 | 420 | struct exception_storage 421 | { 422 | void set(std::exception_ptr&& e) noexcept 423 | { 424 | new(&_data) std::exception_ptr(std::move(e)); 425 | } 426 | 427 | void set(std::exception_ptr const& e) noexcept 428 | { 429 | new(&_data) std::exception_ptr(e); 430 | } 431 | 432 | std::exception_ptr get() noexcept 433 | { 434 | auto& ex = *reinterpret_cast(&_data); 435 | std::exception_ptr ret(std::move(ex)); 436 | ex.~exception_ptr(); 437 | return ret; 438 | } 439 | 440 | storage_for _data; 441 | }; 442 | 443 | using sentinel = std::integral_constant; 444 | 445 | template 446 | inline auto try_suspend(Promise* p) -> decltype(p->try_suspend()) 447 | { 448 | { decltype(p->try_resume()) is_also_required(void); } 449 | { decltype(p->try_cancel()) is_also_required(void); } 450 | return p->try_suspend(); 451 | } 452 | 453 | inline std::true_type try_suspend(void*) noexcept 454 | { 455 | return {}; 456 | } 457 | 458 | template 459 | inline auto try_resume(Promise* p) -> decltype(p->try_resume()) 460 | { 461 | { decltype(p->try_suspend()) is_also_required(void); } 462 | { decltype(p->try_cancel()) is_also_required(void); } 463 | return p->try_resume(); 464 | } 465 | 466 | inline std::true_type try_resume(void*) noexcept 467 | { 468 | return {}; 469 | } 470 | 471 | template 472 | inline auto try_cancel(Promise* p) -> decltype(p->try_cancel()) 473 | { 474 | { decltype(p->try_suspend()) is_also_required(void); } 475 | { decltype(p->try_resume()) is_also_required(void); } 476 | return p->try_cancel(); 477 | } 478 | 479 | inline std::true_type try_cancel(void*) noexcept 480 | { 481 | return {}; 482 | } 483 | 484 | template 485 | using rebind_alloc_t = 486 | typename std::allocator_traits::template rebind_alloc; 487 | 488 | template 489 | struct frame_storage : Alloc 490 | { 491 | explicit frame_storage(Alloc&& alloc) : Alloc(std::move(alloc)) {} 492 | 493 | alignas(std::max_align_t) char tmp[N]; 494 | storage_for f; 495 | }; 496 | 497 | template 498 | struct frame_storage : Alloc 499 | { 500 | explicit frame_storage(Alloc&& alloc) : Alloc(std::move(alloc)) {} 501 | 502 | union 503 | { 504 | storage_for f; 505 | char tmp; // dummy 506 | }; 507 | }; 508 | 509 | template 510 | struct frame final 511 | : resumable 512 | { 513 | using alloc_t = rebind_alloc_t>; 514 | using traits = std::allocator_traits; 515 | 516 | frame_storage _mem; 517 | 518 | template 519 | static frame* create(alloc_t alloc, Pack&& pack) 520 | { 521 | auto p = traits::allocate(alloc, 1); 522 | try 523 | { 524 | return new(p) frame(alloc, std::forward(pack)); 525 | } 526 | catch (...) 527 | { 528 | traits::deallocate(alloc, p, 1); 529 | throw; 530 | } 531 | } 532 | 533 | template 534 | frame(alloc_t alloc, Pack&& pack) : _mem(std::move(alloc)) 535 | { 536 | new(&_mem.f) F(std::forward(pack)); 537 | this->_next = F::_co2_start::value; 538 | } 539 | 540 | void run(coroutine<>& coro) override 541 | { 542 | if (detail::try_resume(&this->promise())) 543 | { 544 | reinterpret_cast(_mem.f) 545 | (static_cast&>(coro), this->_next, this->_eh, &_mem.tmp); 546 | } 547 | else 548 | coro.detach(); 549 | } 550 | 551 | void unwind(coroutine<>& coro) override 552 | { 553 | if (detail::try_cancel(&this->promise())) 554 | { 555 | reinterpret_cast(_mem.f) 556 | (static_cast&>(coro), ++this->_next, this->_eh, &_mem.tmp); 557 | } 558 | } 559 | 560 | void release() noexcept override 561 | { 562 | alloc_t alloc(static_cast(_mem)); 563 | this->~frame(); 564 | traits::deallocate(alloc, this, 1); 565 | } 566 | }; 567 | 568 | // Use this function instead of calling frame<...>::create directly to 569 | // workaround strange MSVC bug when used in lambda functions. 570 | template 571 | inline frame* create_frame(Alloc&& alloc, Pack&& pack) 572 | { 573 | return frame::create(std::move(alloc), std::forward(pack)); 574 | } 575 | 576 | template 577 | struct frame_size_helper : resumable 578 | { 579 | frame_storage _mem; 580 | }; 581 | 582 | template 583 | constexpr std::size_t frame_size = 584 | sizeof(frame_size_helper); 585 | 586 | template 587 | using promise_t = typename T::promise_type; 588 | 589 | template 590 | T unrvref(T&&); 591 | 592 | struct avoid_plain_return 593 | { 594 | explicit avoid_plain_return() = default; 595 | }; 596 | 597 | template 598 | inline auto final_result(Promise* p) -> decltype(p->set_result()) 599 | { 600 | p->set_result(); 601 | } 602 | 603 | inline void final_result(void*) 604 | { 605 | BOOST_ASSERT_MSG(false, "missing return statement"); 606 | } 607 | 608 | template 609 | inline void set_result(Promise& p, T&& t) 610 | { 611 | p.set_result(std::forward(t)); 612 | } 613 | 614 | template 615 | inline void set_result(Promise& p, void_) 616 | { 617 | p.set_result(); 618 | } 619 | 620 | template 621 | inline auto set_exception(Promise* p, exception_storage& ex) -> decltype(p->set_exception(ex.get())) 622 | { 623 | return p->set_exception(ex.get()); 624 | } 625 | 626 | inline void set_exception(void*, exception_storage& ex) 627 | { 628 | std::rethrow_exception(ex.get()); 629 | } 630 | 631 | template 632 | inline auto cancel(Promise* p) -> decltype(p->cancel()) 633 | { 634 | return p->cancel(); 635 | } 636 | 637 | inline void cancel(void*) {} 638 | 639 | #if 0 // Now deprecated. 640 | template 641 | inline std::allocator get_alloc(T&&...) 642 | { 643 | return {}; 644 | } 645 | 646 | template 647 | inline A get_alloc(std::allocator_arg_t, A a, T&&...) 648 | { 649 | return a; 650 | } 651 | #endif 652 | 653 | template 654 | struct finalizer 655 | { 656 | F* f; 657 | coroutine

& coro; 658 | P& promise; 659 | 660 | ~finalizer() 661 | { 662 | f->~F(); 663 | coro.detach(); // this order is important! 664 | if (!promise.final_suspend()) 665 | coroutine

::destroy(&promise); 666 | } 667 | }; 668 | 669 | template 670 | struct extract_promise 671 | { 672 | using P = promise_t; 673 | 674 | P*& p; 675 | 676 | P* operator->() const noexcept { return p; } 677 | 678 | ~extract_promise() 679 | { 680 | coroutine

::destroy(p); 681 | p = nullptr; 682 | } 683 | }; 684 | 685 | template 686 | inline auto await_ready(T& t) -> decltype(t.await_ready()) 687 | { 688 | return t.await_ready(); 689 | } 690 | 691 | template 692 | inline auto await_suspend(T& t, F&& f) -> 693 | decltype(t.await_suspend(std::forward(f))) 694 | { 695 | return t.await_suspend(std::forward(f)); 696 | } 697 | 698 | template 699 | inline auto await_resume(T& t) -> decltype(t.await_resume()) 700 | { 701 | return t.await_resume(); 702 | } 703 | 704 | struct await_ready_fn 705 | { 706 | template 707 | auto operator()(T& t) const -> decltype(await_ready(t)) 708 | { 709 | return await_ready(t); 710 | } 711 | }; 712 | 713 | struct await_suspend_fn 714 | { 715 | template 716 | auto operator()(T& t, F&& f) const -> 717 | decltype(await_suspend(t, std::forward(f))) 718 | { 719 | return await_suspend(t, std::forward(f)); 720 | } 721 | }; 722 | 723 | struct await_resume_fn 724 | { 725 | template 726 | auto operator()(T& t) const -> decltype(await_resume(t)) 727 | { 728 | return await_resume(t); 729 | } 730 | }; 731 | }} 732 | 733 | namespace co2 734 | { 735 | constexpr detail::await_ready_fn await_ready{}; 736 | constexpr detail::await_suspend_fn await_suspend{}; 737 | constexpr detail::await_resume_fn await_resume{}; 738 | 739 | template 740 | struct await_result 741 | { 742 | using type = decltype(await_resume(std::declval>())); 743 | }; 744 | 745 | template 746 | using await_result_t = decltype(await_resume(std::declval>())); 747 | 748 | namespace detail 749 | { 750 | template 751 | struct ready_awaiter 752 | { 753 | Task task; 754 | 755 | bool await_ready() 756 | { 757 | return co2::await_ready(task); 758 | } 759 | 760 | template 761 | auto await_suspend(F&& f) -> decltype(co2::await_suspend(task, std::forward(f))) 762 | { 763 | return co2::await_suspend(task, std::forward(f)); 764 | } 765 | 766 | void await_resume() noexcept {} 767 | }; 768 | } 769 | 770 | template 771 | inline detail::ready_awaiter ready(Task&& task) 772 | { 773 | return {std::forward(task)}; 774 | } 775 | } 776 | 777 | # if defined(BOOST_MSVC) 778 | # define Zz_CO2_IS_EMPTY BOOST_PP_IS_EMPTY 779 | # define Zz_CO2_PUSH_NAME_HIDDEN_WARNING \ 780 | __pragma(warning(push)) \ 781 | __pragma(warning(disable:4456)) \ 782 | /***/ 783 | # define Zz_CO2_POP_WARNING __pragma(warning(pop)) 784 | # else 785 | # define Zz_CO2_PUSH_NAME_HIDDEN_WARNING 786 | # define Zz_CO2_POP_WARNING 787 | // The IS_EMPTY trick is from: 788 | // http://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ 789 | // IS_EMPTY { 790 | # define Zz_CO2_ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15 791 | # define Zz_CO2_HAS_COMMA(...) Zz_CO2_ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) 792 | # define Zz_CO2_TRIGGER_PARENTHESIS_(...) , 793 | 794 | # define Zz_CO2_IS_EMPTY(...) \ 795 | Zz_CO2_IS_EMPTY_IMPL( \ 796 | /* test if there is just one argument, eventually an empty one */ \ 797 | Zz_CO2_HAS_COMMA(__VA_ARGS__), \ 798 | /* test if Zz_CO2_TRIGGER_PARENTHESIS_ together with the argument adds a comma */\ 799 | Zz_CO2_HAS_COMMA(Zz_CO2_TRIGGER_PARENTHESIS_ __VA_ARGS__), \ 800 | /* test if the argument together with a parenthesis adds a comma */ \ 801 | Zz_CO2_HAS_COMMA(__VA_ARGS__ (/*empty*/)), \ 802 | /* test if placing it between Zz_CO2_TRIGGER_PARENTHESIS_ and the parenthesis adds a comma */\ 803 | Zz_CO2_HAS_COMMA(Zz_CO2_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/)) \ 804 | ) \ 805 | /***/ 806 | 807 | # define Zz_CO2_PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4 808 | # define Zz_CO2_IS_EMPTY_IMPL(_0, _1, _2, _3) Zz_CO2_HAS_COMMA(Zz_CO2_PASTE5(Zz_CO2_IS_EMPTY_CASE_, _0, _1, _2, _3)) 809 | # define Zz_CO2_IS_EMPTY_CASE_0001 , 810 | // } IS_EMPTY 811 | # endif 812 | 813 | #define Zz_CO2_TUPLE_FOR_EACH_IMPL(macro, t) \ 814 | BOOST_PP_SEQ_FOR_EACH(macro, ~, BOOST_PP_VARIADIC_TO_SEQ t) \ 815 | /***/ 816 | 817 | #define Zz_CO2_TUPLE_FOR_EACH_EMPTY(macro, t) 818 | 819 | #define Zz_CO2_TUPLE_FOR_EACH(macro, t) \ 820 | BOOST_PP_IF(Zz_CO2_IS_EMPTY t, Zz_CO2_TUPLE_FOR_EACH_EMPTY, \ 821 | Zz_CO2_TUPLE_FOR_EACH_IMPL)(macro, t) \ 822 | /***/ 823 | 824 | # if defined(__has_cpp_attribute) 825 | # if __has_cpp_attribute(fallthrough) 826 | # define Zz_CO2_FALLTHROUGH [[fallthrough]]; 827 | # endif 828 | # endif 829 | # if !defined(Zz_CO2_FALLTHROUGH) 830 | # define Zz_CO2_FALLTHROUGH 831 | # endif 832 | 833 | // Try to detect statement expressions support. 834 | # if !defined(CO2_HAS_STMT_EXPR) 835 | # if defined(BOOST_GCC) | defined(BOOST_CLANG) 836 | # define CO2_HAS_STMT_EXPR 837 | # endif 838 | # endif 839 | 840 | # if defined(CO2_HAS_STMT_EXPR) 841 | # define Zz_CO2_STMT_EXPR_BEG ( 842 | # define Zz_CO2_STMT_EXPR_END ) 843 | # else 844 | # define Zz_CO2_STMT_EXPR_BEG if (true) 845 | # define Zz_CO2_STMT_EXPR_END else do; while (false) 846 | # endif 847 | 848 | #define Zz_CO2_AWAIT(ret, expr, next, ...) \ 849 | Zz_CO2_STMT_EXPR_BEG { \ 850 | using _co2_expr_t = decltype(::co2::detail::unrvref(expr)); \ 851 | using _co2_await = ::co2::detail::temp::traits<_co2_expr_t, _co2_sz::value>;\ 852 | _co2_await::create(_co2_tmp, expr); \ 853 | try \ 854 | { \ 855 | if (!::co2::await_ready(_co2_await::get(_co2_tmp))) \ 856 | { \ 857 | _co2_next = next; \ 858 | if (!::co2::detail::try_suspend(&_co2_p)) \ 859 | goto BOOST_PP_CAT(_co2_cancel_, next); \ 860 | if ((::co2::await_suspend(_co2_await::get(_co2_tmp), _co2_c), \ 861 | ::co2::detail::void_{})) \ 862 | return ::co2::detail::avoid_plain_return{}; \ 863 | else if (!::co2::detail::try_resume(&_co2_p)) \ 864 | { \ 865 | _co2_c.detach(); \ 866 | return ::co2::detail::avoid_plain_return{}; \ 867 | } \ 868 | } \ 869 | } \ 870 | catch (...) \ 871 | { \ 872 | _co2_await::reset(_co2_tmp); \ 873 | throw; \ 874 | } \ 875 | Zz_CO2_FALLTHROUGH \ 876 | case next: \ 877 | if (_co2_p.cancellation_requested()) \ 878 | { \ 879 | case __COUNTER__: \ 880 | BOOST_PP_CAT(_co2_cancel_, next): \ 881 | _co2_await::reset(_co2_tmp); \ 882 | ::co2::detail::cancel(&_co2_p); \ 883 | goto _co2_finalize; \ 884 | } \ 885 | ::co2::detail::temp::auto_reset<_co2_expr_t, _co2_sz::value> \ 886 | _co2_reset = {_co2_tmp}; \ 887 | ret (::co2::await_resume(_co2_await::get(_co2_tmp))); \ 888 | __VA_ARGS__ \ 889 | } Zz_CO2_STMT_EXPR_END \ 890 | /***/ 891 | 892 | #define Zz_CO2_SUSPEND(f, next) \ 893 | do { \ 894 | _co2_next = next; \ 895 | if (!::co2::detail::try_suspend(&_co2_p)) \ 896 | goto BOOST_PP_CAT(_co2_cancel_, next); \ 897 | if ((f(_co2_c), ::co2::detail::void_{})) \ 898 | return ::co2::detail::avoid_plain_return{}; \ 899 | else if (!::co2::detail::try_resume(&_co2_p)) \ 900 | { \ 901 | _co2_c.detach(); \ 902 | return ::co2::detail::avoid_plain_return{}; \ 903 | } \ 904 | Zz_CO2_FALLTHROUGH \ 905 | case next: \ 906 | if (_co2_p.cancellation_requested()) \ 907 | { \ 908 | case __COUNTER__: \ 909 | BOOST_PP_CAT(_co2_cancel_, next): \ 910 | ::co2::detail::cancel(&_co2_p); \ 911 | goto _co2_finalize; \ 912 | } \ 913 | } while (false) \ 914 | /***/ 915 | 916 | #define Zz_CO2_SUSPEND_IF(expr, next) \ 917 | { \ 918 | if (_co2_p.expr) \ 919 | { \ 920 | _co2_next = next; \ 921 | if (!::co2::detail::try_suspend(&_co2_p)) \ 922 | goto BOOST_PP_CAT(_co2_cancel_, next); \ 923 | return ::co2::detail::avoid_plain_return{}; \ 924 | } \ 925 | Zz_CO2_FALLTHROUGH \ 926 | case next: \ 927 | if (_co2_p.cancellation_requested()) \ 928 | { \ 929 | case __COUNTER__: \ 930 | BOOST_PP_CAT(_co2_cancel_, next): \ 931 | ::co2::detail::cancel(&_co2_p); \ 932 | goto _co2_finalize; \ 933 | } \ 934 | } \ 935 | /***/ 936 | 937 | #define CO2_AWAIT_APPLY(f, expr) Zz_CO2_AWAIT(f, expr, __COUNTER__,) 938 | #define CO2_AWAIT_SET(var, expr) Zz_CO2_AWAIT(var =, expr, __COUNTER__,) 939 | #define CO2_AWAIT(expr) Zz_CO2_AWAIT(, expr, __COUNTER__,) 940 | #define CO2_AWAIT_LET(init, expr, ...) \ 941 | Zz_CO2_AWAIT(init =, expr, __COUNTER__, __VA_ARGS__) \ 942 | /***/ 943 | 944 | #define CO2_YIELD(...) CO2_AWAIT(_co2_p.yield_value(__VA_ARGS__)) 945 | 946 | #define CO2_SUSPEND(f) Zz_CO2_SUSPEND(f, __COUNTER__) 947 | 948 | #define CO2_RETURN(...) \ 949 | do { \ 950 | _co2_next = ::co2::detail::sentinel::value; \ 951 | _co2_p.set_result(__VA_ARGS__); \ 952 | goto _co2_finalize; \ 953 | } while (false) \ 954 | /***/ 955 | 956 | #define CO2_RETURN_LOCAL(var) \ 957 | do { \ 958 | _co2_next = ::co2::detail::sentinel::value; \ 959 | _co2_p.set_result(std::forward(var)); \ 960 | goto _co2_finalize; \ 961 | } while (false) \ 962 | /***/ 963 | 964 | #define CO2_RETURN_FROM(...) \ 965 | do { \ 966 | _co2_next = ::co2::detail::sentinel::value; \ 967 | ::co2::detail::set_result(_co2_p, (__VA_ARGS__, ::co2::detail::void_{})); \ 968 | goto _co2_finalize; \ 969 | } while (false) \ 970 | /***/ 971 | 972 | #define CO2_RETURN_EXCEPTION(ex) \ 973 | do { \ 974 | _co2_next = ::co2::detail::sentinel::value; \ 975 | _co2_ex.set(ex); \ 976 | ::co2::detail::finalizer<_co2_F, _co2_P> fin{this, _co2_c, _co2_p}; \ 977 | ::co2::detail::set_exception(&_co2_p, _co2_ex); \ 978 | return ::co2::detail::avoid_plain_return{}; \ 979 | } while (false) \ 980 | /***/ 981 | 982 | #define CO2_AWAIT_RETURN(expr) Zz_CO2_AWAIT(CO2_RETURN_FROM, expr, __COUNTER__,) 983 | 984 | #define CO2_TRY \ 985 | Zz_CO2_PUSH_NAME_HIDDEN_WARNING \ 986 | using _co2_prev_eh = _co2_curr_eh; \ 987 | using _co2_curr_eh = std::integral_constant; \ 988 | Zz_CO2_POP_WARNING \ 989 | _co2_eh = _co2_curr_eh::value; \ 990 | if (true) \ 991 | /***/ 992 | 993 | #define CO2_CATCH \ 994 | else case _co2_curr_eh::value: \ 995 | try \ 996 | { \ 997 | _co2_eh = _co2_prev_eh::value; \ 998 | std::rethrow_exception(_co2_ex.get()); \ 999 | } \ 1000 | catch \ 1001 | /***/ 1002 | 1003 | #define Zz_CO2_SWITCH_LABEL(n) BOOST_PP_CAT(BOOST_PP_CAT(_co2_case_, __LINE__), n) 1004 | 1005 | #define Zz_CO2_SWITCH_CASE(r, _, i, e) \ 1006 | BOOST_PP_IF(BOOST_PP_MOD(i, 2), , e: goto Zz_CO2_SWITCH_LABEL(i);) \ 1007 | /***/ 1008 | 1009 | #define Zz_CO2_UNPAREN(...) __VA_ARGS__ 1010 | #define Zz_CO2_SWITCH_BODY_TRUE(i, e) Zz_CO2_SWITCH_LABEL(BOOST_PP_DEC(i)): Zz_CO2_UNPAREN e 1011 | #define Zz_CO2_SWITCH_BODY_FALSE(i, e) 1012 | 1013 | #define Zz_CO2_SWITCH_BODY(r, _, i, e) \ 1014 | BOOST_PP_IF(BOOST_PP_MOD(i, 2), \ 1015 | Zz_CO2_SWITCH_BODY_TRUE, Zz_CO2_SWITCH_BODY_FALSE)(i, e) \ 1016 | /***/ 1017 | 1018 | #define Zz_CO2_SWITCH(n, seq, on_cont, cont_pad) \ 1019 | { \ 1020 | switch (n) \ 1021 | { \ 1022 | BOOST_PP_SEQ_FOR_EACH_I(Zz_CO2_SWITCH_CASE, ~, seq) \ 1023 | } \ 1024 | if (false) \ 1025 | { \ 1026 | cont_pad; \ 1027 | for (;;) \ 1028 | { \ 1029 | on_cont; \ 1030 | break; \ 1031 | BOOST_PP_SEQ_FOR_EACH_I(Zz_CO2_SWITCH_BODY, ~, seq) \ 1032 | break; \ 1033 | } \ 1034 | } \ 1035 | } \ 1036 | /***/ 1037 | 1038 | #define Zz_CO2_SWITCH_CONT(n, label, ...) \ 1039 | Zz_CO2_SWITCH(n, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__), \ 1040 | goto label, label: continue) \ 1041 | /***/ 1042 | 1043 | #define CO2_SWITCH(n, ...) \ 1044 | Zz_CO2_SWITCH(n, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__), \ 1045 | assert(!"Use CO2_SWITCH_CONT for continue"),) \ 1046 | /***/ 1047 | 1048 | #define CO2_SWITCH_CONT(n, ...) \ 1049 | Zz_CO2_SWITCH_CONT(n, BOOST_PP_CAT(_co2_cont_, __LINE__), __VA_ARGS__) \ 1050 | /***/ 1051 | 1052 | #if defined(BOOST_GCC) 1053 | #define Zz_CO2_TYPE_PARAM(r, _, e) using BOOST_PP_CAT(e, _t) = decltype(e); 1054 | #define Zz_CO2_DECL_PARAM(r, _, e) BOOST_PP_CAT(e, _t) e; 1055 | #define Zz_CO2_K(args) \ 1056 | struct _co2_KK \ 1057 | { \ 1058 | Zz_CO2_TUPLE_FOR_EACH(Zz_CO2_TYPE_PARAM, args) \ 1059 | struct pack \ 1060 | { \ 1061 | Zz_CO2_TUPLE_FOR_EACH(Zz_CO2_DECL_PARAM, args) \ 1062 | }; \ 1063 | }; \ 1064 | using _co2_K = typename _co2_KK::pack; \ 1065 | /***/ 1066 | #else 1067 | #define Zz_CO2_DECL_PARAM(r, _, e) decltype(e) e; 1068 | #define Zz_CO2_K(args) \ 1069 | struct _co2_K \ 1070 | { \ 1071 | Zz_CO2_TUPLE_FOR_EACH(Zz_CO2_DECL_PARAM, args) \ 1072 | }; \ 1073 | /***/ 1074 | #endif 1075 | 1076 | #define Zz_CO2_FWD_PARAM(r, _, e) std::forward(e), 1077 | #define Zz_CO2_USE_PARAM(r, _, e) using _co2_K::e; 1078 | 1079 | #define Zz_CO2_1ST(a, b) a 1080 | #define Zz_CO2_2ND(a, b) b 1081 | 1082 | #define Zz_CO2_NEW_ALLOC(alloc, args) std::forward(alloc) 1083 | #define Zz_CO2_OLD_ALLOC(alloc, args) std::allocator{} 1084 | #if 0 // Allocator deduced from args is now deprecated. 1085 | #define Zz_CO2_OLD_ALLOC(alloc, args) \ 1086 | ::co2::detail::get_alloc(Zz_CO2_TUPLE_FOR_EACH( \ 1087 | Zz_CO2_FWD_PARAM, args) ::co2::detail::void_{}) \ 1088 | /***/ 1089 | #endif 1090 | 1091 | #define Zz_CO2_INVOKE(f, args) f args 1092 | #define Zz_CO2_DISPATCHZz_CO2_GET_ALLOC_ (Zz_CO2_OLD_ALLOC, ~) 1093 | #define Zz_CO2_DISPATCH_NEW_ALLOC 1094 | #define Zz_CO2_GET_ALLOC_new(a) _NEW_ALLOC (Zz_CO2_NEW_ALLOC, a) 1095 | #define Zz_CO2_SKIP_CAPTURE(...) 1096 | #define Zz_CO2_GET_ALLOC(x) BOOST_PP_EXPAND(Zz_CO2_INVOKE( \ 1097 | BOOST_PP_CAT, (Zz_CO2_DISPATCH, Zz_CO2_INVOKE( \ 1098 | BOOST_PP_CAT, (Zz_CO2_GET_ALLOC_, Zz_CO2_SKIP_CAPTURE x))))) \ 1099 | /***/ 1100 | 1101 | #define Zz_CO2_SEPARATE_ALLOC(...) (__VA_ARGS__), 1102 | #define Zz_CO2_GET_ARGS(x) BOOST_PP_EXPAND(Zz_CO2_INVOKE( \ 1103 | Zz_CO2_1ST, (Zz_CO2_SEPARATE_ALLOC x))) \ 1104 | /***/ 1105 | 1106 | #define CO2_TEMP_SIZE(bytes) using _co2_sz = ::co2::detail::temp::adjust_size 1107 | 1108 | # if defined(BOOST_MSVC) | defined(BOOST_CLANG) 1109 | namespace co2 { namespace detail 1110 | { 1111 | template 1112 | R ret_of(R(T::*)()); 1113 | }} 1114 | # if defined(BOOST_MSVC) 1115 | # define Zz_CO2_SUPPRESS_NO_DEF __pragma(warning(suppress:4822)) 1116 | # else 1117 | # define Zz_CO2_SUPPRESS_NO_DEF 1118 | # endif 1119 | # define Zz_CO2_AUTO_F(var) BOOST_PP_CAT(_co2_auto_, var) 1120 | # define CO2_AUTO(var, expr) \ 1121 | Zz_CO2_SUPPRESS_NO_DEF \ 1122 | auto Zz_CO2_AUTO_F(var)() -> std::decay_t; \ 1123 | decltype(::co2::detail::ret_of(&_co2_F::Zz_CO2_AUTO_F(var))) var{expr} \ 1124 | /***/ 1125 | # else 1126 | # define CO2_AUTO(var, expr) std::decay_t var{expr} 1127 | # endif 1128 | 1129 | #define Zz_CO2_HEAD(R, args, alloc, ...) \ 1130 | { \ 1131 | using _co2_T = ::co2::coroutine_traits; \ 1132 | using _co2_P = ::co2::detail::promise_t<_co2_T>; \ 1133 | using _co2_C = ::co2::coroutine<_co2_P>; \ 1134 | Zz_CO2_K(args) \ 1135 | _co2_K _co2_k = {Zz_CO2_TUPLE_FOR_EACH(Zz_CO2_FWD_PARAM, args)}; \ 1136 | auto _co2_a(Zz_CO2_1ST alloc(Zz_CO2_2ND alloc, args)); \ 1137 | struct _co2_F : ::co2::detail::temp::default_size, _co2_K \ 1138 | { \ 1139 | Zz_CO2_TUPLE_FOR_EACH(Zz_CO2_USE_PARAM, args) \ 1140 | __VA_ARGS__ \ 1141 | _co2_F(_co2_K&& pack) : _co2_K(std::move(pack)) {} \ 1142 | using _co2_start = std::integral_constant; \ 1143 | ::co2::detail::avoid_plain_return operator() \ 1144 | (_co2_C& _co2_c, unsigned& _co2_next, unsigned& _co2_eh, void* _co2_tmp)\ 1145 | { \ 1146 | (void)_co2_tmp; \ 1147 | (void)_co2_sz::value; \ 1148 | auto& _co2_p = _co2_c.promise(); \ 1149 | ::co2::detail::exception_storage _co2_ex; \ 1150 | _co2_try_again: \ 1151 | try \ 1152 | { \ 1153 | switch (_co2_next) \ 1154 | { \ 1155 | case _co2_start::value: \ 1156 | using _co2_curr_eh = ::co2::detail::sentinel; \ 1157 | _co2_eh = _co2_curr_eh::value; \ 1158 | Zz_CO2_SUSPEND_IF(initial_suspend(), __COUNTER__); \ 1159 | /***/ 1160 | 1161 | #define CO2_BEG(R, capture, ...) -> BOOST_PP_REMOVE_PARENS(R) Zz_CO2_HEAD(R, \ 1162 | Zz_CO2_GET_ARGS(capture), Zz_CO2_GET_ALLOC(capture), __VA_ARGS__) \ 1163 | /***/ 1164 | 1165 | #define CO2_END \ 1166 | ::co2::detail::final_result(&_co2_p); \ 1167 | _co2_finalize: \ 1168 | ::co2::detail::finalizer<_co2_F, _co2_P>{this, _co2_c, _co2_p};\ 1169 | } \ 1170 | } \ 1171 | catch (...) \ 1172 | { \ 1173 | _co2_next = _co2_eh; \ 1174 | _co2_ex.set(std::current_exception()); \ 1175 | if (_co2_next != ::co2::detail::sentinel::value) \ 1176 | goto _co2_try_again; \ 1177 | ::co2::detail::finalizer<_co2_F, _co2_P> fin{this, _co2_c, _co2_p};\ 1178 | ::co2::detail::set_exception(&_co2_p, _co2_ex); \ 1179 | } \ 1180 | return ::co2::detail::avoid_plain_return{}; \ 1181 | } \ 1182 | }; \ 1183 | _co2_C _co2_c(::co2::detail::create_frame<_co2_P, _co2_F>( \ 1184 | std::move(_co2_a), std::move(_co2_k))); \ 1185 | return _co2_c.promise().get_return_object(_co2_c); \ 1186 | } \ 1187 | /***/ 1188 | 1189 | #endif --------------------------------------------------------------------------------