diff options
author | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2019-10-04 20:34:02 +0300 |
---|---|---|
committer | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2019-10-04 20:34:02 +0300 |
commit | dbf76606c10d325eee673b82173ec0897e0db3ff (patch) | |
tree | 1a22996563a4929707d4e468a7c70a20bfc6945d | |
parent | 1849f4e63413ea6f79ad50f18fba4c91bf7b28dd (diff) |
A watershed: LLFIO finally works with clang coroutines! All unit tests pass.
-rw-r--r-- | CMakeLists.txt | 71 | ||||
-rw-r--r-- | include/llfio/revision.hpp | 6 | ||||
-rw-r--r-- | include/llfio/v2.0/async_file_handle.hpp | 4 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/posix/directory_handle.ipp | 2 | ||||
-rw-r--r-- | include/llfio/v2.0/io_service.hpp | 28 | ||||
-rw-r--r-- | include/llfio/v2.0/status_code.hpp | 26 | ||||
-rw-r--r-- | test/tests/coroutines.cpp | 39 |
7 files changed, 120 insertions, 56 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2757afd5..8f36ac09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,8 @@ if(WIN32) add_subdirectory("include/llfio/ntkernel-error-category" EXCLUDE_FROM_ALL) endif() +set(UNIT_TESTS_CXX_VERSION "latest" CACHE STRING "The version of C++ to use in the header-only unit tests") + # Make the standard static and shared libraries, and if supported by this compiler, C++ modules # for both static and shared libraries as well. For the non-C++ module variants, makes the # interface headers into precompiled headers. Only builds all of them if this is the topmost @@ -148,12 +150,32 @@ endif() # Set the library dependencies this library has all_link_libraries(PUBLIC quickcpplib::hl outcome::hl Threads::Threads) # Set the system dependencies this library has +set(USING_LIBCXX_ON_LINUX 0) if(CMAKE_SYSTEM_NAME MATCHES "Linux") find_library(libstdcxx_stdcxxfs stdc++fs) if(libstdcxx_stdcxxfs MATCHES "NOTFOUND") set(libstdcxx_stdcxxfs -lstdc++fs) endif() - all_link_libraries(PUBLIC ${libstdcxx_stdcxxfs} rt) + get_target_property(val llfio_dl USING_LIBCXX_ON_LINUX) + if(val MATCHES "NOTFOUND") + all_link_libraries(PUBLIC ${libstdcxx_stdcxxfs} rt) + else() + set(USING_LIBCXX_ON_LINUX 1) + # We had to use libc++ to get coroutines on Linux, so don't link the filesystem TS + target_link_libraries(${PROJECT_NAME}_hl INTERFACE ${libstdcxx_stdcxxfs} rt) + target_link_libraries(${PROJECT_NAME}_sl PUBLIC ${libstdcxx_stdcxxfs} rt) + foreach(special ${SPECIAL_BUILDS}) + target_link_libraries(${PROJECT_NAME}_sl-${special} PUBLIC ${libstdcxx_stdcxxfs} rt) + endforeach() + # Do make libc++ a requirement + target_compile_definitions(${PROJECT_NAME}_dl PUBLIC _LIBCPP_NO_EXPERIMENTAL_DEPRECATION_WARNING_FILESYSTEM=1) + target_compile_options(${PROJECT_NAME}_dl PUBLIC -stdlib=libc++) + target_link_libraries(${PROJECT_NAME}_dl PUBLIC -stdlib=libc++ -lc++abi) + foreach(special ${SPECIAL_BUILDS}) + target_compile_options(${PROJECT_NAME}_dl-${special} PUBLIC -stdlib=libc++) + target_link_libraries(${PROJECT_NAME}_dl-${special} PUBLIC -stdlib=libc++ -lc++abi) + endforeach() + endif() endif() if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" OR APPLE) find_library(libcxx_cxxexperimental c++experimental) @@ -207,8 +229,14 @@ if(NOT PROJECT_IS_DEPENDENCY) ) foreach(test_target ${llfio_TEST_TARGETS}) target_link_libraries(${test_target} PRIVATE kerneltest::hl) - if(test_target MATCHES "coroutines") - apply_cxx_coroutines_to(PRIVATE ${test_target}) + if(test_target MATCHES coroutines) + if(USING_LIBCXX_ON_LINUX) + if(NOT test_target MATCHES _sl) + apply_cxx_coroutines_to(PRIVATE ${test_target}) + endif() + else() + apply_cxx_coroutines_to(PRIVATE ${test_target}) + endif() endif() endforeach() if(MSVC) @@ -219,6 +247,43 @@ if(NOT PROJECT_IS_DEPENDENCY) endif() endforeach() endif() + + # Turn on latest C++ where possible for the test suite + if(UNIT_TESTS_CXX_VERSION STREQUAL "latest") + set(LATEST_CXX_FEATURE) + foreach(feature ${CMAKE_CXX_COMPILE_FEATURES}) + if(feature STREQUAL "cxx_std_23") + set(LATEST_CXX_FEATURE "cxx_std_23") + indented_message(STATUS "NOTE: This compiler claims to support C++ 23, enabling for header-only unit test suite") + endif() + endforeach() + if(NOT LATEST_CXX_FEATURE) + foreach(feature ${CMAKE_CXX_COMPILE_FEATURES}) + if(feature STREQUAL "cxx_std_20") + set(LATEST_CXX_FEATURE "cxx_std_20") + indented_message(STATUS "NOTE: This compiler claims to support C++ 20, enabling for header-only unit test suite") + endif() + endforeach() + endif() + if(NOT LATEST_CXX_FEATURE) + foreach(feature ${CMAKE_CXX_COMPILE_FEATURES}) + if(feature STREQUAL "cxx_std_17") + set(LATEST_CXX_FEATURE "cxx_std_17") + indented_message(STATUS "NOTE: This compiler claims to support C++ 17, enabling for header-only unit test suite") + endif() + endforeach() + endif() + elseif(UNIT_TESTS_CXX_VERSION) + set(LATEST_CXX_FEATURE "cxx_std_${UNIT_TESTS_CXX_VERSION}") + endif() + if(LATEST_CXX_FEATURE) + # Turn on latest C++ where possible for the header only test suite + foreach(test_target ${llfio_TEST_TARGETS} ${llfio_EXAMPLE_TARGETS}) + if(test_target MATCHES _hl) + target_compile_features(${test_target} PUBLIC ${LATEST_CXX_FEATURE}) + endif() + endforeach() + endif() endif() # Cache this library's auto scanned sources for later reuse diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp index 311d81b9..3eece543 100644 --- a/include/llfio/revision.hpp +++ b/include/llfio/revision.hpp @@ -1,4 +1,4 @@ // Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time -#define LLFIO_PREVIOUS_COMMIT_REF 7b5a2b5fa56cd53959e573a4750e09fe76030284 -#define LLFIO_PREVIOUS_COMMIT_DATE "2019-09-25 17:14:32 +00:00" -#define LLFIO_PREVIOUS_COMMIT_UNIQUE 7b5a2b5f +#define LLFIO_PREVIOUS_COMMIT_REF 1849f4e63413ea6f79ad50f18fba4c91bf7b28dd +#define LLFIO_PREVIOUS_COMMIT_DATE "2019-10-04 14:20:39 +00:00" +#define LLFIO_PREVIOUS_COMMIT_UNIQUE 1849f4e6 diff --git a/include/llfio/v2.0/async_file_handle.hpp b/include/llfio/v2.0/async_file_handle.hpp index b7133c61..853afc8e 100644 --- a/include/llfio/v2.0/async_file_handle.hpp +++ b/include/llfio/v2.0/async_file_handle.hpp @@ -505,7 +505,7 @@ public: using file_handle::write; LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override; -#if LLFIO_HAVE_COROUTINES || defined(DOXYGEN_IS_IN_THE_HOUSE) +#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) private: template <class BuffersType> class awaitable_state { @@ -731,7 +731,7 @@ template <class CompletionRoutine> inline result<async_file_handle::io_state_ptr { return self.async_write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(mem)); } -#if LLFIO_HAVE_COROUTINES || defined(DOXYGEN_IS_IN_THE_HOUSE) +#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) /*! \brief Schedule a read to occur asynchronously. \return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine diff --git a/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp b/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp index dfa79a29..e95bbda5 100644 --- a/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/directory_handle.ipp @@ -26,7 +26,7 @@ http://www.boost.org/LICENSE_1_0.txt) #include "import.hpp" #ifdef QUICKCPPLIB_ENABLE_VALGRIND -#include "../valgrind/memcheck.h" // from quickcpplib include directory +#include "quickcpplib/valgrind/memcheck.h" // from quickcpplib include directory #define LLFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b) VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE((a), (b)) #else #define LLFIO_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(a, b) diff --git a/include/llfio/v2.0/io_service.hpp b/include/llfio/v2.0/io_service.hpp index 1b8f590b..420836d8 100644 --- a/include/llfio/v2.0/io_service.hpp +++ b/include/llfio/v2.0/io_service.hpp @@ -31,30 +31,6 @@ Distributed under the Boost Software License, Version 1.0. #include <deque> #include <mutex> -#if defined(__cpp_coroutines) && !defined(LLFIO_HAVE_COROUTINES) -#define LLFIO_HAVE_COROUTINES 1 -#endif - -#if defined(LLFIO_HAVE_COROUTINES) -// clang-format off -#if defined(__has_include) -#if __has_include(<coroutine>) -#include <coroutine> -LLFIO_V2_NAMESPACE_EXPORT_BEGIN -template<class T = void> using coroutine_handle = std::coroutine_handle<T>; -LLFIO_V2_NAMESPACE_END -#elif __has_include(<experimental/coroutine>) -#include <experimental/coroutine> -LLFIO_V2_NAMESPACE_EXPORT_BEGIN -template<class T = void> using coroutine_handle = std::experimental::coroutine_handle<T>; -LLFIO_V2_NAMESPACE_END -#else -#error Cannot use C++ Coroutines without the <coroutine> header! -#endif -#endif -// clang-format on -#endif - #undef _threadid // windows macro splosh sigh //! \file io_service.hpp Provides io_service. @@ -291,7 +267,7 @@ public: */ template <class U> void post(U &&f) { _post(detail::make_function_ptr<void(io_service *)>(std::forward<U>(f))); } -#if LLFIO_HAVE_COROUTINES || defined(DOXYGEN_IS_IN_THE_HOUSE) +#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) /*! An awaitable suspending execution of this coroutine on the current kernel thread, and resuming execution on the kernel thread running this i/o service. This is a convenience wrapper for `post()`. @@ -309,7 +285,7 @@ public: bool await_ready() { return false; } void await_suspend(coroutine_handle<> co) { - service->post([co = co](io_service * /*unused*/) { co.resume(); }); + service->post([co = co](io_service * /*unused*/) mutable { co.resume(); }); } void await_resume() {} }; diff --git a/include/llfio/v2.0/status_code.hpp b/include/llfio/v2.0/status_code.hpp index 03c3c4c9..14a691c6 100644 --- a/include/llfio/v2.0/status_code.hpp +++ b/include/llfio/v2.0/status_code.hpp @@ -57,6 +57,12 @@ as that (a) enables safe header only LLFIO on Windows (b) produces better codege // Bring in a result implementation based on status_code #include "outcome/experimental/status_result.hpp" #include "outcome/try.hpp" +#if __cpp_coroutines +#include "outcome/experimental/coroutine_support.hpp" +#ifdef OUTCOME_FOUND_COROUTINE_HEADER +#define LLFIO_ENABLE_COROUTINES 1 +#endif +#endif LLFIO_V2_NAMESPACE_BEGIN @@ -241,6 +247,13 @@ template <class T> using result = OUTCOME_V2_NAMESPACE::experimental::status_res using OUTCOME_V2_NAMESPACE::failure; using OUTCOME_V2_NAMESPACE::in_place_type; using OUTCOME_V2_NAMESPACE::success; +#if defined(LLFIO_ENABLE_COROUTINES) +template <class T> using atomic_eager = OUTCOME_V2_NAMESPACE::experimental::awaitables::atomic_eager<T>; +template <class T> using atomic_lazy = OUTCOME_V2_NAMESPACE::experimental::awaitables::atomic_lazy<T>; +template <class T> using eager = OUTCOME_V2_NAMESPACE::experimental::awaitables::eager<T>; +template <class T> using lazy = OUTCOME_V2_NAMESPACE::experimental::awaitables::lazy<T>; +template <class T = void> using coroutine_handle = OUTCOME_V2_NAMESPACE::experimental::awaitables::coroutine_handle<T>; +#endif //! Choose an errc implementation using SYSTEM_ERROR2_NAMESPACE::errc; @@ -341,6 +354,12 @@ LLFIO_V2_NAMESPACE_END #include "outcome/result.hpp" #include "outcome/try.hpp" #include "outcome/utils.hpp" +#if __cpp_coroutines +#include "outcome/coroutine_support.hpp" +#ifdef OUTCOME_FOUND_COROUTINE_HEADER +#define LLFIO_ENABLE_COROUTINES 1 +#endif +#endif LLFIO_V2_NAMESPACE_BEGIN @@ -562,6 +581,13 @@ inline error_info error_from_exception(std::exception_ptr &&ep = std::current_ex return error_info(OUTCOME_V2_NAMESPACE::error_from_exception(std::move(ep), not_matched)); } using OUTCOME_V2_NAMESPACE::in_place_type; +#if defined(LLFIO_ENABLE_COROUTINES) +template <class T> using atomic_eager = OUTCOME_V2_NAMESPACE::awaitables::atomic_eager<T>; +template <class T> using atomic_lazy = OUTCOME_V2_NAMESPACE::awaitables::atomic_lazy<T>; +template <class T> using eager = OUTCOME_V2_NAMESPACE::awaitables::eager<T>; +template <class T> using lazy = OUTCOME_V2_NAMESPACE::awaitables::lazy<T>; +template <class T = void> using coroutine_handle = OUTCOME_V2_NAMESPACE::awaitables::coroutine_handle<T>; +#endif static_assert(OUTCOME_V2_NAMESPACE::trait::is_error_code_available_v<error_info>, "error_info is not detected to be an error code"); diff --git a/test/tests/coroutines.cpp b/test/tests/coroutines.cpp index 08727bbc..dd962116 100644 --- a/test/tests/coroutines.cpp +++ b/test/tests/coroutines.cpp @@ -28,7 +28,7 @@ Distributed under the Boost Software License, Version 1.0. static inline void TestAsyncFileHandleCoroutines() { -#if LLFIO_HAVE_COROUTINES +#if defined(LLFIO_ENABLE_COROUTINES) //! [coroutines_example] namespace llfio = LLFIO_V2_NAMESPACE; @@ -39,10 +39,10 @@ static inline void TestAsyncFileHandleCoroutines() llfio::async_file_handle h = llfio::async_file_handle::async_file(service, {}, "temp", llfio::file_handle::mode::write, llfio::file_handle::creation::if_needed, llfio::file_handle::caching::only_metadata, llfio::file_handle::flag::unlink_on_first_close).value(); // Truncate to 1Mb - h.truncate(1024 * 4096); + (void) h.truncate(1024 * 4096); // Launch 8 coroutines, each writing 4Kb of chars 0-8 to every 32Kb block - auto coroutine = [&h](size_t no) -> std::future<void> { + auto coroutine = [&h](size_t no) -> llfio::eager<void> { std::vector<llfio::byte, llfio::utils::page_allocator<llfio::byte>> buffer(4096); memset(buffer.data(), (int) ('0' + no), 4096); llfio::async_file_handle::const_buffer_type bt{buffer.data(), buffer.size()}; @@ -54,7 +54,7 @@ static inline void TestAsyncFileHandleCoroutines() written.value(); } }; - std::vector<std::future<void>> coroutines; + std::vector<llfio::eager<void>> coroutines; for(size_t n = 0; n < 8; n++) { // Construct each coroutine, initiating the i/o, then suspending. @@ -63,11 +63,6 @@ static inline void TestAsyncFileHandleCoroutines() // Pump the i/o, multiplexing the coroutines, until no more work remains. while(service.run().value()) ; - // Make sure nothing went wrong by fetching the futures. - for(auto &i : coroutines) - { - i.get(); - } //! [coroutines_example] // Check that the file has the right contents @@ -88,24 +83,26 @@ static inline void TestAsyncFileHandleCoroutines() static inline void TestPostSelfToRunCoroutines() { -#if LLFIO_HAVE_COROUTINES +#if defined(LLFIO_ENABLE_COROUTINES) namespace llfio = LLFIO_V2_NAMESPACE; llfio::io_service service; std::atomic<bool> ready(false); auto runthreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(); + // Start a coroutine which immediately suspends + auto coroutine = [&]() -> llfio::lazy<void> { + auto thisthreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(); + BOOST_CHECK(thisthreadid != runthreadid); + ready = true; + co_await llfio::io_service::awaitable_post_to_self(service); + thisthreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(); + BOOST_CHECK(thisthreadid == runthreadid); + // std::cout << "Coroutine exiting" << std::endl; + ready = false; + }(); + // Run the coroutine's body in another thread auto coroutinethread = [&]() -> void { - auto coroutine = [&]() -> std::future<void> { - auto thisthreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(); - BOOST_CHECK(thisthreadid != runthreadid); - ready = true; - co_await llfio::io_service::awaitable_post_to_self(service); - thisthreadid = QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(); - BOOST_CHECK(thisthreadid == runthreadid); - // std::cout << "Coroutine exiting" << std::endl; - ready = false; - }; // std::cout << "Thread waiting on coroutine" << std::endl; - coroutine().get(); + coroutine.await_suspend({}); // std::cout << "Thread exiting" << std::endl; }; auto asynch = std::async(std::launch::async, coroutinethread); |