diff --git a/.gitignore b/.gitignore index 6fdd208..fd5411d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,9 @@ x64 .vs # imgui -imgui.ini \ No newline at end of file +imgui.ini + +# build +.cache/ +build*/ +install*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cc3414b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.0) +# Consider if MSVC, cross-compile MinGW or MinGW with explicit toolchain path provided as -DTOOLCHAIN_ROOT= +if(NOT (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows") OR TOOLCHAIN_ROOT) + if(NOT TOOLCHAIN_ROOT) + set(TOOLCHAIN_ROOT /usr/bin/) + endif() + set(TOOLCHAIN_PREFIX ${TOOLCHAIN_ROOT}x86_64-w64-mingw32) + include(MinGW64.cmake) +endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +project(triangleBin LANGUAGES CXX) + +file(GLOB SOURCES_MAIN *.cpp) +file(GLOB_RECURSE SOURCES_IMGUI imgui/*.cpp) +file(GLOB SHADERS *.hlsl) +set(SOURCES ${SOURCES_IMGUI} ${SOURCES_MAIN}) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_executable(${PROJECT_NAME} ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC imgui) +target_link_libraries(${PROJECT_NAME} PUBLIC dxgi d3d11 d3dcompiler imm32) +install(FILES ${SHADERS} DESTINATION .) +install(TARGETS ${PROJECT_NAME} DESTINATION .) + +if(NOT MSVC) +target_compile_definitions(${PROJECT_NAME} PUBLIC WINVER=0x0600 _WIN32_WINNT=0x0600) + if(CMAKE_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_HAVE_LIBC_PTHREAD EQUAL 1 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) # refine if needed: MinGW GCC8 and 10 needs it, MinGW GCC13 not, MinGW Clang 16 provides std::threads / std::mutex, so not considered + option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON) + add_subdirectory(mingw-std-threads) + target_link_libraries(${PROJECT_NAME} PUBLIC mingw_stdthreads) + endif() +endif() diff --git a/MinGW64.cmake b/MinGW64.cmake new file mode 100644 index 0000000..06d7e7f --- /dev/null +++ b/MinGW64.cmake @@ -0,0 +1,32 @@ +# source: https://gist.github.com/peterspackman/8cf73f7f12ba270aa8192d6911972fe8 + +# Sample toolchain file for building for Windows from an Ubuntu Linux system. +# +# Typical usage: *) install cross compiler: `sudo apt-get install mingw-w64` *) +# cd build *) cmake -DCMAKE_TOOLCHAIN_FILE=~/mingw-w64-x86_64.cmake .. This is +# free and unencumbered software released into the public domain. + +if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(EXE_EXTENSION .exe) +else() + set(EXE_EXTENSION) +endif() +set(CMAKE_SYSTEM_NAME Windows) + +# cross compilers to use for C, C++ and Fortran +set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}-gcc${EXE_EXTENSION}) +set(CMAKE_C_COMPILER_AR ${TOOLCHAIN_PREFIX}-ar${EXE_EXTENSION}) +set(CMAKE_C_COMPILER_RANLIB ${TOOLCHAIN_PREFIX}-ranlib${EXE_EXTENSION}) +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc${EXE_EXTENSION}) +set(CMAKE_CXX_COMPILER_AR ${TOOLCHAIN_PREFIX}-ar${EXE_EXTENSION}) +set(CMAKE_CXX_COMPILER_RANLIB ${TOOLCHAIN_PREFIX}-ranlib${EXE_EXTENSION}) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++${EXE_EXTENSION}) +set(CMAKE_Fortran_COMPILER ${TOOLCHAIN_PREFIX}-gfortran${EXE_EXTENSION}) +set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres${EXE_EXTENSION}) +# target environment on the build host system +set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_PREFIX}) + +# modify default behavior of FIND_XXX() commands +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/dxutil.cpp b/dxutil.cpp index 33dde4f..e844df0 100644 --- a/dxutil.cpp +++ b/dxutil.cpp @@ -46,7 +46,7 @@ std::string MultiByteFromWide(const std::wstring& ws) std::string MultiByteFromHR(HRESULT hr) { _com_error err(hr); - return MultiByteFromWide(err.ErrorMessage()); + return MultiByteFromWide(WideFromMultiByte(err.ErrorMessage())); } bool detail_CheckHR(HRESULT hr, const char* file, const char* function, int line) @@ -71,7 +71,7 @@ bool detail_CheckHR(HRESULT hr, const char* file, const char* function, int line L"File: " + wfile + L"\n" + L"Function: " + wfunction + L"\n" + L"Line: " + std::to_wstring(line) + L"\n" + - L"ErrorMessage: " + err.ErrorMessage() + L"\n"; + L"ErrorMessage: " + WideFromMultiByte(err.ErrorMessage()) + L"\n"; int result = MessageBoxW(NULL, msg.c_str(), L"Error", MB_ABORTRETRYIGNORE); if (result == IDABORT) diff --git a/dxutil.h b/dxutil.h index 98c9e2d..ea95722 100644 --- a/dxutil.h +++ b/dxutil.h @@ -1,12 +1,13 @@ #pragma once +#ifndef NOMINMAX #define NOMINMAX +#endif #define WIN32_LEAN_AND_MEAN -#include +#include #undef GetWindowFont // because imgui -#include +#include #include -#include #include #include @@ -25,5 +26,12 @@ void SimpleMessageBox_FatalError(const char* fmt, ...); bool detail_CheckHR(HRESULT hr, const char* file, const char* function, int line); bool detail_CheckWin32(BOOL okay, const char* file, const char* function, int line); +#if defined(_MSC_VER) #define CHECKHR(hr) detail_CheckHR(hr, __FILE__, __FUNCSIG__, __LINE__) -#define CHECKWIN32(okay) detail_CheckWin32(okay, __FILE__, __FUNCSIG__, __LINE__) \ No newline at end of file +#define CHECKWIN32(okay) \ + detail_CheckWin32(okay, __FILE__, __FUNCSIG__, __LINE__) +#else + #define CHECKHR(hr) detail_CheckHR(hr, __FILE__, __FUNCTION__, __LINE__) + #define CHECKWIN32(okay) \ + detail_CheckWin32(okay, __FILE__, __FUNCTION__, __LINE__) +#endif diff --git a/imgui/imgui_impl_dx11.cpp b/imgui/imgui_impl_dx11.cpp index a4094e3..b22d90f 100644 --- a/imgui/imgui_impl_dx11.cpp +++ b/imgui/imgui_impl_dx11.cpp @@ -389,9 +389,9 @@ bool ImGui_ImplDX11_CreateDeviceObjects() // Create the input layout D3D11_INPUT_ELEMENT_DESC local_layout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (size_t)(&((ImDrawVert*)0)->pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (size_t)(&((ImDrawVert*)0)->uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (size_t)(&((ImDrawVert*)0)->col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)(size_t)(&((ImDrawVert*)0)->pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)(size_t)(&((ImDrawVert*)0)->uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)(size_t)(&((ImDrawVert*)0)->col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; if (g_pd3dDevice->CreateInputLayout(local_layout, 3, g_pVertexShaderBlob->GetBufferPointer(), g_pVertexShaderBlob->GetBufferSize(), &g_pInputLayout) != S_OK) return false; diff --git a/main.cpp b/main.cpp index 4d42a39..83ad6d4 100644 --- a/main.cpp +++ b/main.cpp @@ -56,7 +56,7 @@ void WindowInit(int width, int height, const char* title) wc.cbSize = sizeof(wc); wc.lpfnWndProc = WndProc; wc.hInstance = GetModuleHandleW(NULL); - wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW); wc.lpszClassName = L"WindowClass"; CHECKWIN32(RegisterClassExW(&wc)); @@ -92,7 +92,7 @@ void RendererInit() scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.OutputWindow = g_hWnd; scd.Windowed = TRUE; - scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; scd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; UINT flags = 0; @@ -102,12 +102,9 @@ void RendererInit() ComPtr pDevice; ComPtr pDeviceContext; - ComPtr pSwapChain; - HANDLE hFrameLatencyWaitableObject; + ComPtr pSwapChain; D3D_FEATURE_LEVEL kFeatureLevels[] = { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -137,7 +134,6 @@ void RendererInit() (featureLevel >> 12) & 0x0F, (featureLevel >> 8) & 0x0F); } - hFrameLatencyWaitableObject = pSwapChain->GetFrameLatencyWaitableObject(); CHECKHR(pDXGIFactory->MakeWindowAssociation(g_hWnd, DXGI_MWA_NO_WINDOW_CHANGES)); g_Device = pDevice.Get(); @@ -146,7 +142,6 @@ void RendererInit() g_DeviceContext->AddRef(); g_SwapChain = pSwapChain.Get(); g_SwapChain->AddRef(); - g_FrameLatencyWaitableObject = hFrameLatencyWaitableObject; } void RendererResize(int width, int height) @@ -168,9 +163,6 @@ void RendererPaint() ID3D11DeviceContext* dc = g_DeviceContext; IDXGISwapChain* sc = g_SwapChain; - // Wait until the previous frame is presented before drawing the next frame - CHECKWIN32(WaitForSingleObject(g_FrameLatencyWaitableObject, INFINITE) == WAIT_OBJECT_0); - // grab the current backbuffer ComPtr pBackBufferTex2D; ComPtr pBackBufferRTV; diff --git a/mingw-std-threads/CMakeLists.txt b/mingw-std-threads/CMakeLists.txt new file mode 100644 index 0000000..ea2f559 --- /dev/null +++ b/mingw-std-threads/CMakeLists.txt @@ -0,0 +1,35 @@ +project(mingw_stdthreads) +cmake_minimum_required(VERSION 3.0) + +option(MINGW_STDTHREADS_BUILD_TEST "Build tests") +option(MINGW_STDTHREADS_GENERATE_STDHEADERS "Generate std-like headers") + +string(CONCAT mingw_stdthreads_dir_docstring + "Optional. When generating std-like headers , this variable can be set" + "to manually specify the path to mingw-stdthreads directory containing" + "original library headers.") +set(MINGW_STDTHREADS_DIR "${PROJECT_SOURCE_DIR}" + CACHE PATH ${mingw_stdthreads_dir_docstring}) + +# mingw-stdthreads is a header-only library, so make it a INTERFACE target +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories(${PROJECT_NAME} INTERFACE "${PROJECT_SOURCE_DIR}") + +if(MINGW_STDTHREADS_GENERATE_STDHEADERS) + # Check if we are using gcc or clang + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + # Add as dependency and generate std headers + add_subdirectory(cmake_stdheaders_generator) + target_link_libraries(${PROJECT_NAME} INTERFACE + cmake_stdheaders_generator) + else() + message(WARNING "Cannot generate std headers with this compiler: " + ${CMAKE_CXX_COMPILER_ID} ". " + "Please fall back to #include ") + endif() +endif() + +# Build tests.exe +if(MINGW_STDTHREADS_BUILD_TEST) + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/mingw-std-threads/LICENSE b/mingw-std-threads/LICENSE new file mode 100644 index 0000000..ac525cf --- /dev/null +++ b/mingw-std-threads/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2016, Mega Limited +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/mingw-std-threads/README.md b/mingw-std-threads/README.md new file mode 100644 index 0000000..049df0d --- /dev/null +++ b/mingw-std-threads/README.md @@ -0,0 +1,60 @@ +mingw-std-threads +================= + +Implementation of standard C++11 threading classes, which are currently still missing on MinGW GCC. + +Target Windows version +---------------------- +This implementation should work with Windows XP (regardless of service pack), or newer. +The library automatically detects the version of Windows that is being targeted (at compile time), and selects an implementation that takes advantage of available Windows features. +In MinGW GCC, the target Windows version may optionally be selected by the command-line option `-D _WIN32_WINNT=...`. +Use `0x0600` for Windows Vista, or `0x0601` for Windows 7. +See "[Modifying `WINVER` and `_WIN32_WINNT`](https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt)" for more details. + +Usage +----- + +This is a header-only library. To use, just include the corresponding `mingw.xxx.h file`, where `xxx` would be the name of the standard header that you would normally include. + +For example, `#include "mingw.thread.h"` replaces `#include `. + +A `CMakeLists.txt` has also been provided. You can add it to your project by using `add_subdirectory()`, and then this library can be added as your targets' dependency by using `target_link_libraries(YOUR_TARGET PRIVATE mingw_stdthreads)`. By default it just adds a include path, allowing you to include headers using angle brackets (for example `#include `). But you can also provide options to let it generate "std-like" headers (see next paragraph). + +Using "std-like" headers +------------------------ + +Probably you don't really want to replace all your includes from `#include
` to `#include "mingw.header.h"`. So if you are using GCC or clang, here are some ways to make you happy :) + +With CMake, you just need to turn on the option `MINGW_STDTHREADS_GENERATE_STDHEADERS` before adding mingw-stdthreads, something like this: +```CMake +option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON) +add_subdirectory(mingw_stdthreads) +target_link_libraries(${TARGET} PRIVATE mingw_stdthreads) +``` +When CMake generates project files, headers named in the "standard header" way will be generated and added to your include path. Then you can avoid stuffs like `mingw.thread.h`, and keep using `#include ` like always. In addition, `MINGW_STDTHREADS_GENERATED_STDHEADERS` will be defined, you can use this macro to check if those generated headers are actually available. + +If you aren't using CMake, you can use one of the three scripts inside [utility_scripts](utility_scripts) directory to manually generate those "std-like" headers. Note that this requires Microsoft Power Shell, so if you are cross-compiling, you would need to install Power Shell. + +Compatibility +------------- + +This code has been tested to work with MinGW-w64 5.3.0, but should work with any other MinGW version that has the `std` threading classes missing, has C++11 support for lambda functions, variadic templates, and has working mutex helper classes in ``. + +Switching from the win32-pthread based implementation +----------------------------------------------------- +It seems that recent versions of MinGW-w64 include a Win32 port of pthreads, and have the `std::thread`, `std::mutex`, etc. classes implemented and working based on that compatibility layer. + +You could use the built-in pthread implementation of Mingw by using the posix compiler, eg: `x86_64-w64-mingw32-g++-posix` (for Windows 64-bit). + +That is a somewhat heavier implementation, as it relies on an abstraction layer, so you may still want to use this implementation for efficiency purposes. +Unfortunately you can't use this library standalone and independent of the system `` headers, as it relies on those headers for `std::unique_lock` and other non-trivial utility classes. +In that case you will need to edit the `c++-config.h` file of your MinGW setup and comment out the definition of _GLIBCXX_HAS_GTHREADS. +This will cause the system headers not to define the actual `thread`, `mutex`, etc. classes, but still define the necessary utility classes. + +Why MinGW has no threading classes +---------------------------------- +It seems that for cross-platform threading implementation, the GCC standard library relies on the gthreads/pthreads library. +If this library is not available, as is the case with MinGW, the classes `std::thread`, `std::mutex`, `std::condition_variable` are not defined. +However, various usable helper classes are still defined in the system headers. +Hence, this implementation does not re-define them, and instead includes those headers. + diff --git a/mingw-std-threads/cmake_stdheaders_generator/CMakeLists.txt b/mingw-std-threads/cmake_stdheaders_generator/CMakeLists.txt new file mode 100644 index 0000000..5ecd6ec --- /dev/null +++ b/mingw-std-threads/cmake_stdheaders_generator/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.0) +project(cmake_stdheaders_generator) + +set(output_include_path "${PROJECT_BINARY_DIR}/${PROJECT_NAME}") +message("${PROJECT_NAME}: output_include_path set to ${output_include_path}") + +function(generate_mingw_stdthreads_header header_file_name + mingw_stdthreads_folder) + set(template_file_path "${PROJECT_SOURCE_DIR}/template.cpp") + set(destination_file_path "${output_include_path}/${header_file_name}") + + # Check if compiler is gcc or clang + if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + # Actually this should never happen because it should have already + # been checked in the parent CMakeLists.txt + message(FATAL_ERROR "Unsupported compiler") + endif() + + # Call g++ to retrieve header path + # The -H option will let g++ outputs header dependencies to stderr, and the + # content of stderr is saved to variable compiler_output. + try_compile(unused_compiler_exit_code ${CMAKE_CURRENT_BINARY_DIR} + "${template_file_path}" + COMPILE_DEFINITIONS + -H "-DMINGW_STDTHREADS_DETECTING_SYSTEM_HEADER=<${header_file_name}>" + OUTPUT_VARIABLE compiler_output) + + # Get full path to system header + string(REGEX MATCH "[.] ([^\r\n]*)" _ "${compiler_output}") + set(mingw_stdthreads_headers_generator_system_header "${CMAKE_MATCH_1}") + message("Matched: <${mingw_stdthreads_headers_generator_system_header}>") + + # Ensure file exists + if(NOT EXISTS "${mingw_stdthreads_headers_generator_system_header}") + message(FATAL_ERROR "<${header_file_name}>'s path not found, " + "compiler output was:\n${compiler_output}") + endif() + + # Get full path to mingw-stdthreads header + set(mingw_stdthreads_headers_generator_library_header + "${mingw_stdthreads_folder}/mingw.${header_file_name}.h") + + # Normalize paths + file(TO_CMAKE_PATH "${mingw_stdthreads_headers_generator_system_header}" + mingw_stdthreads_headers_generator_system_header) + file(TO_CMAKE_PATH "${mingw_stdthreads_headers_generator_library_header}" + mingw_stdthreads_headers_generator_library_header) + + configure_file("${template_file_path}" "${destination_file_path}") +endfunction() + +if(EXISTS "${MINGW_STDTHREADS_DIR}") + message("${PROJECT_NAME}: MINGW_STDTHREADS_DIR: " + "${MINGW_STDTHREADS_DIR}") +else() + message(FATAL_ERROR "${PROECT_NAME}: MINGW_STDTHREADS_DIR does not " + "exist: ${MINGW_STDTHREADS_DIR}") +endif() + +# +generate_mingw_stdthreads_header(condition_variable "${MINGW_STDTHREADS_DIR}") +# +generate_mingw_stdthreads_header(future "${MINGW_STDTHREADS_DIR}") +# +generate_mingw_stdthreads_header(mutex "${MINGW_STDTHREADS_DIR}") +# +generate_mingw_stdthreads_header(shared_mutex "${MINGW_STDTHREADS_DIR}") +# +generate_mingw_stdthreads_header(thread "${MINGW_STDTHREADS_DIR}") + +# the generated headers are to be considered as a header only library +# so we create an interface target +add_library(${PROJECT_NAME} INTERFACE) +target_compile_definitions(${PROJECT_NAME} INTERFACE + MINGW_STDTHREADS_GENERATED_STDHEADERS) +target_include_directories(${PROJECT_NAME} INTERFACE "${output_include_path}") diff --git a/mingw-std-threads/cmake_stdheaders_generator/template.cpp b/mingw-std-threads/cmake_stdheaders_generator/template.cpp new file mode 100644 index 0000000..e7c712c --- /dev/null +++ b/mingw-std-threads/cmake_stdheaders_generator/template.cpp @@ -0,0 +1,11 @@ +#ifdef MINGW_STDTHREADS_DETECTING_SYSTEM_HEADER + #include MINGW_STDTHREADS_DETECTING_SYSTEM_HEADER + static_assert(false, "Prevent compilation") +#else + #pragma once + // both system header and mignw-stdthreads header should already have include + // guards. But we still add a #pragma once just to be safe. + + #include "${mingw_stdthreads_headers_generator_system_header}" + #include "${mingw_stdthreads_headers_generator_library_header}" +#endif \ No newline at end of file diff --git a/mingw-std-threads/mingw.condition_variable.h b/mingw-std-threads/mingw.condition_variable.h new file mode 100644 index 0000000..493a5c4 --- /dev/null +++ b/mingw-std-threads/mingw.condition_variable.h @@ -0,0 +1,564 @@ +/** +* @file condition_variable.h +* @brief std::condition_variable implementation for MinGW +* +* (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* @author Alexander Vassilev +* +* @copyright Simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* @note +* This file may become part of the mingw-w64 runtime package. If/when this happens, +* the appropriate license will be added, i.e. this code will become dual-licensed, +* and the current BSD 2-clause license will stay. +*/ + +#ifndef MINGW_CONDITIONAL_VARIABLE_H +#define MINGW_CONDITIONAL_VARIABLE_H + +#if !defined(__cplusplus) || (__cplusplus < 201103L) +#error A C++11 compiler is required! +#endif +// Use the standard classes for std::, if available. +#include + +#include +#include +#include + +#include // Detect Windows version. +#if (WINVER < _WIN32_WINNT_VISTA) +#include +#endif +#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) +#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ + with Microsoft's API. We'll try to work around this, but we can make no\ + guarantees. This problem does not exist in MinGW-w64." +#include // No further granularity can be expected. +#else +#if (WINVER < _WIN32_WINNT_VISTA) +#include +#include // For CreateSemaphore +#include +#endif +#include +#endif + +#include "mingw.mutex.h" +#include "mingw.shared_mutex.h" + +#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501) +#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher. +#endif + +namespace mingw_stdthread +{ +#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) +enum class cv_status { no_timeout, timeout }; +#else +using std::cv_status; +#endif +namespace xp +{ +// Include the XP-compatible condition_variable classes only if actually +// compiling for XP. The XP-compatible classes are slower than the newer +// versions, and depend on features not compatible with Windows Phone 8. +#if (WINVER < _WIN32_WINNT_VISTA) +class condition_variable_any +{ + recursive_mutex mMutex {}; + std::atomic mNumWaiters {0}; + HANDLE mSemaphore; + HANDLE mWakeEvent {}; +public: + using native_handle_type = HANDLE; + native_handle_type native_handle() + { + return mSemaphore; + } + condition_variable_any(const condition_variable_any&) = delete; + condition_variable_any& operator=(const condition_variable_any&) = delete; + condition_variable_any() + : mSemaphore(CreateSemaphoreA(NULL, 0, 0xFFFF, NULL)) + { + if (mSemaphore == NULL) + throw std::system_error(GetLastError(), std::generic_category()); + mWakeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (mWakeEvent == NULL) + { + CloseHandle(mSemaphore); + throw std::system_error(GetLastError(), std::generic_category()); + } + } + ~condition_variable_any() + { + CloseHandle(mWakeEvent); + CloseHandle(mSemaphore); + } +private: + template + bool wait_impl(M& lock, DWORD timeout) + { + { + lock_guard guard(mMutex); + mNumWaiters++; + } + lock.unlock(); + DWORD ret = WaitForSingleObject(mSemaphore, timeout); + + mNumWaiters--; + SetEvent(mWakeEvent); + lock.lock(); + if (ret == WAIT_OBJECT_0) + return true; + else if (ret == WAIT_TIMEOUT) + return false; +//2 possible cases: +//1)The point in notify_all() where we determine the count to +//increment the semaphore with has not been reached yet: +//we just need to decrement mNumWaiters, but setting the event does not hurt +// +//2)Semaphore has just been released with mNumWaiters just before +//we decremented it. This means that the semaphore count +//after all waiters finish won't be 0 - because not all waiters +//woke up by acquiring the semaphore - we woke up by a timeout. +//The notify_all() must handle this gracefully +// + else + { + using namespace std; + throw system_error(make_error_code(errc::protocol_error)); + } + } +public: + template + void wait(M& lock) + { + wait_impl(lock, INFINITE); + } + template + void wait(M& lock, Predicate pred) + { + while(!pred()) + { + wait(lock); + }; + } + + void notify_all() noexcept + { + lock_guard lock(mMutex); //block any further wait requests until all current waiters are unblocked + if (mNumWaiters.load() <= 0) + return; + + ReleaseSemaphore(mSemaphore, mNumWaiters, NULL); + while(mNumWaiters > 0) + { + auto ret = WaitForSingleObject(mWakeEvent, 1000); + if (ret == WAIT_FAILED || ret == WAIT_ABANDONED) + std::terminate(); + } + assert(mNumWaiters == 0); +//in case some of the waiters timed out just after we released the +//semaphore by mNumWaiters, it won't be zero now, because not all waiters +//woke up by acquiring the semaphore. So we must zero the semaphore before +//we accept waiters for the next event +//See _wait_impl for details + while(WaitForSingleObject(mSemaphore, 0) == WAIT_OBJECT_0); + } + void notify_one() noexcept + { + lock_guard lock(mMutex); + int targetWaiters = mNumWaiters.load() - 1; + if (targetWaiters <= -1) + return; + ReleaseSemaphore(mSemaphore, 1, NULL); + while(mNumWaiters > targetWaiters) + { + auto ret = WaitForSingleObject(mWakeEvent, 1000); + if (ret == WAIT_FAILED || ret == WAIT_ABANDONED) + std::terminate(); + } + assert(mNumWaiters == targetWaiters); + } + template + cv_status wait_for(M& lock, + const std::chrono::duration& rel_time) + { + using namespace std::chrono; + auto timeout = duration_cast(rel_time).count(); + DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (INFINITE - 1); + bool ret = wait_impl(lock, waittime) || (timeout >= INFINITE); + return ret?cv_status::no_timeout:cv_status::timeout; + } + + template + bool wait_for(M& lock, + const std::chrono::duration& rel_time, Predicate pred) + { + return wait_until(lock, std::chrono::steady_clock::now()+rel_time, pred); + } + template + cv_status wait_until (M& lock, + const std::chrono::time_point& abs_time) + { + return wait_for(lock, abs_time - Clock::now()); + } + template + bool wait_until (M& lock, + const std::chrono::time_point& abs_time, + Predicate pred) + { + while (!pred()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + { + return pred(); + } + } + return true; + } +}; +class condition_variable: condition_variable_any +{ + using base = condition_variable_any; +public: + using base::native_handle_type; + using base::native_handle; + using base::base; + using base::notify_all; + using base::notify_one; + void wait(unique_lock &lock) + { + base::wait(lock); + } + template + void wait(unique_lock& lock, Predicate pred) + { + base::wait(lock, pred); + } + template + cv_status wait_for(unique_lock& lock, const std::chrono::duration& rel_time) + { + return base::wait_for(lock, rel_time); + } + template + bool wait_for(unique_lock& lock, const std::chrono::duration& rel_time, Predicate pred) + { + return base::wait_for(lock, rel_time, pred); + } + template + cv_status wait_until (unique_lock& lock, const std::chrono::time_point& abs_time) + { + return base::wait_until(lock, abs_time); + } + template + bool wait_until (unique_lock& lock, const std::chrono::time_point& abs_time, Predicate pred) + { + return base::wait_until(lock, abs_time, pred); + } +}; +#endif // Compiling for XP +} // Namespace mingw_stdthread::xp + +#if (WINVER >= _WIN32_WINNT_VISTA) +namespace vista +{ +// If compiling for Vista or higher, use the native condition variable. +class condition_variable +{ + static constexpr DWORD kInfinite = 0xffffffffl; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT; +#pragma GCC diagnostic pop + + friend class condition_variable_any; + +#if STDMUTEX_RECURSION_CHECKS + template + inline static void before_wait (MTX * pmutex) + { + pmutex->mOwnerThread.checkSetOwnerBeforeUnlock(); + } + template + inline static void after_wait (MTX * pmutex) + { + pmutex->mOwnerThread.setOwnerAfterLock(GetCurrentThreadId()); + } +#else + inline static void before_wait (void *) { } + inline static void after_wait (void *) { } +#endif + + bool wait_impl (unique_lock & lock, DWORD time) + { + using mutex_handle_type = typename xp::mutex::native_handle_type; + static_assert(std::is_same::value, + "Native Win32 condition variable requires std::mutex to \ +use native Win32 critical section objects."); + xp::mutex * pmutex = lock.release(); + before_wait(pmutex); + BOOL success = SleepConditionVariableCS(&cvariable_, + pmutex->native_handle(), + time); + after_wait(pmutex); + lock = unique_lock(*pmutex, adopt_lock); + return success; + } + + bool wait_unique (windows7::mutex * pmutex, DWORD time) + { + before_wait(pmutex); + BOOL success = SleepConditionVariableSRW( native_handle(), + pmutex->native_handle(), + time, +// CONDITION_VARIABLE_LOCKMODE_SHARED has a value not specified by +// Microsoft's Dev Center, but is known to be (convertible to) a ULONG. To +// ensure that the value passed to this function is not equal to Microsoft's +// constant, we can either use a static_assert, or simply generate an +// appropriate value. + !CONDITION_VARIABLE_LOCKMODE_SHARED); + after_wait(pmutex); + return success; + } + bool wait_impl (unique_lock & lock, DWORD time) + { + windows7::mutex * pmutex = lock.release(); + bool success = wait_unique(pmutex, time); + lock = unique_lock(*pmutex, adopt_lock); + return success; + } +public: + using native_handle_type = PCONDITION_VARIABLE; + native_handle_type native_handle (void) + { + return &cvariable_; + } + + condition_variable (void) = default; + ~condition_variable (void) = default; + + condition_variable (const condition_variable &) = delete; + condition_variable & operator= (const condition_variable &) = delete; + + void notify_one (void) noexcept + { + WakeConditionVariable(&cvariable_); + } + + void notify_all (void) noexcept + { + WakeAllConditionVariable(&cvariable_); + } + + void wait (unique_lock & lock) + { + wait_impl(lock, kInfinite); + } + + template + void wait (unique_lock & lock, Predicate pred) + { + while (!pred()) + wait(lock); + } + + template + cv_status wait_for(unique_lock& lock, + const std::chrono::duration& rel_time) + { + using namespace std::chrono; + auto timeout = duration_cast(rel_time).count(); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template + bool wait_for(unique_lock& lock, + const std::chrono::duration& rel_time, + Predicate pred) + { + return wait_until(lock, + std::chrono::steady_clock::now() + rel_time, + std::move(pred)); + } + template + cv_status wait_until (unique_lock& lock, + const std::chrono::time_point& abs_time) + { + return wait_for(lock, abs_time - Clock::now()); + } + template + bool wait_until (unique_lock& lock, + const std::chrono::time_point& abs_time, + Predicate pred) + { + while (!pred()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + { + return pred(); + } + } + return true; + } +}; + +class condition_variable_any +{ + static constexpr DWORD kInfinite = 0xffffffffl; + using native_shared_mutex = windows7::shared_mutex; + + condition_variable internal_cv_ {}; +// When available, the SRW-based mutexes should be faster than the +// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista, +// and try_lock is not used by condition_variable_any. + windows7::mutex internal_mutex_ {}; + + template + bool wait_impl (L & lock, DWORD time) + { + unique_lock internal_lock(internal_mutex_); + lock.unlock(); + bool success = internal_cv_.wait_impl(internal_lock, time); + lock.lock(); + return success; + } +// If the lock happens to be called on a native Windows mutex, skip any extra +// contention. + inline bool wait_impl (unique_lock & lock, DWORD time) + { + return internal_cv_.wait_impl(lock, time); + } +// Some shared_mutex functionality is available even in Vista, but it's not +// until Windows 7 that a full implementation is natively possible. The class +// itself is defined, with missing features, at the Vista feature level. + bool wait_impl (unique_lock & lock, DWORD time) + { + native_shared_mutex * pmutex = lock.release(); + bool success = internal_cv_.wait_unique(pmutex, time); + lock = unique_lock(*pmutex, adopt_lock); + return success; + } + bool wait_impl (shared_lock & lock, DWORD time) + { + native_shared_mutex * pmutex = lock.release(); + BOOL success = SleepConditionVariableSRW(native_handle(), + pmutex->native_handle(), time, + CONDITION_VARIABLE_LOCKMODE_SHARED); + lock = shared_lock(*pmutex, adopt_lock); + return success; + } +public: + using native_handle_type = typename condition_variable::native_handle_type; + + native_handle_type native_handle (void) + { + return internal_cv_.native_handle(); + } + + void notify_one (void) noexcept + { + internal_cv_.notify_one(); + } + + void notify_all (void) noexcept + { + internal_cv_.notify_all(); + } + + condition_variable_any (void) = default; + ~condition_variable_any (void) = default; + + template + void wait (L & lock) + { + wait_impl(lock, kInfinite); + } + + template + void wait (L & lock, Predicate pred) + { + while (!pred()) + wait(lock); + } + + template + cv_status wait_for(L& lock, const std::chrono::duration& period) + { + using namespace std::chrono; + auto timeout = duration_cast(period).count(); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template + bool wait_for(L& lock, const std::chrono::duration& period, + Predicate pred) + { + return wait_until(lock, std::chrono::steady_clock::now() + period, + std::move(pred)); + } + template + cv_status wait_until (L& lock, + const std::chrono::time_point& abs_time) + { + return wait_for(lock, abs_time - Clock::now()); + } + template + bool wait_until (L& lock, + const std::chrono::time_point& abs_time, + Predicate pred) + { + while (!pred()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + { + return pred(); + } + } + return true; + } +}; +} // Namespace vista +#endif +#if WINVER < 0x0600 +using xp::condition_variable; +using xp::condition_variable_any; +#else +using vista::condition_variable; +using vista::condition_variable_any; +#endif +} // Namespace mingw_stdthread + +// Push objects into std, but only if they are not already there. +namespace std +{ +// Because of quirks of the compiler, the common "using namespace std;" +// directive would flatten the namespaces and introduce ambiguity where there +// was none. Direct specification (std::), however, would be unaffected. +// Take the safe option, and include only in the presence of MinGW's win32 +// implementation. +#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) +using mingw_stdthread::cv_status; +using mingw_stdthread::condition_variable; +using mingw_stdthread::condition_variable_any; +#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#pragma message "This version of MinGW seems to include a win32 port of\ + pthreads, and probably already has C++11 std threading classes implemented,\ + based on pthreads. These classes, found in namespace std, are not overridden\ + by the mingw-std-thread library. If you would still like to use this\ + implementation (as it is more lightweight), use the classes provided in\ + namespace mingw_stdthread." +#endif +} +#endif // MINGW_CONDITIONAL_VARIABLE_H diff --git a/mingw-std-threads/mingw.future.h b/mingw-std-threads/mingw.future.h new file mode 100644 index 0000000..f6a87e1 --- /dev/null +++ b/mingw-std-threads/mingw.future.h @@ -0,0 +1,1133 @@ +/// \file mingw.future.h +/// \brief Standard-compliant C++ futures for MinGW +/// +/// (c) 2018 by Nathaniel J. McClatchey, San Jose, California +/// \author Nathaniel J. McClatchey, PhD +/// +/// \copyright Simplified (2-clause) BSD License. +/// +/// \note This file may become part of the mingw-w64 runtime package. If/when +/// this happens, the appropriate license will be added, i.e. this code will +/// become dual-licensed, and the current BSD 2-clause license will stay. +/// \note Target Windows version is determined by WINVER, which is determined in +/// from _WIN32_WINNT, which can itself be set by the user. + +#ifndef MINGW_FUTURE_H_ +#define MINGW_FUTURE_H_ + +#if !defined(__cplusplus) || (__cplusplus < 201103L) +#error The MinGW STD Threads library requires a compiler supporting C++11. +#endif + +#include + +#include +#include +#include // For std::pair +#include +#include +#include // For std::hash + +#include "mingw.thread.h" // Start new threads, and use invoke. + +// Mutexes and condition variables are used explicitly. +#include "mingw.mutex.h" +#include "mingw.condition_variable.h" + +#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) +#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ + with Microsoft's API. We'll try to work around this, but we can make no\ + guarantees. This problem does not exist in MinGW-w64." +#include // No further granularity can be expected. +#else +#include +#include +#include +#endif + +// Note: +// std::shared_ptr is the natural choice for this. However, a custom +// implementation removes the need to keep a control block separate from the +// class itself (no need to support weak pointers). + +namespace mingw_stdthread +{ +using std::future_errc; +using std::future_error; +using std::future_status; +using std::launch; +using std::future_category; + +namespace detail +{ +struct Empty { }; + +// Use a class template to allow instantiation of statics in a header-only +// library. Note: Template will only be instantiated once to avoid bloat. +template +struct FutureStatic +{ + enum Type : uint_fast8_t + { + kUndecided = 0x00, + kValueFlag = 0x01, + kExceptionFlag = 0x02, + kDeferredFlag = 0x04, // Needs special handling. Must not wait. + kReadyFlag = 0x10, // Results are ready for consumption + kTypeMask = 0x07, + kNoWaitMask = 0x14 // Indicates that waits should immediately exit. + }; + + static std::vector > sync_pool; + + static mutex & get_mutex (void const * ptr) + { + std::hash hash_func; + return sync_pool[hash_func(ptr) % sync_pool.size()].first; + } + static condition_variable & get_condition_variable (void const * ptr) + { + std::hash hash_func; + return sync_pool[hash_func(ptr) % sync_pool.size()].second; + } +}; +template +std::vector > FutureStatic::sync_pool (thread::hardware_concurrency() * 2 + 1); + +struct FutureStateBase +{ + inline mutex & get_mutex (void) const + { + return FutureStatic::get_mutex(this); + } + inline condition_variable & get_condition_variable (void) const + { + return FutureStatic::get_condition_variable(this); + } + typedef typename FutureStatic::Type Type; +// Destroys this object. Used for allocator-awareness. + virtual void deallocate_this (void) noexcept = 0; + virtual ~FutureStateBase (void) = default; + + FutureStateBase (FutureStateBase const &) = delete; + FutureStateBase & operator= (FutureStateBase const &) = delete; + + FutureStateBase(Type t) noexcept + : mReferences(0), mType(t) + { + } + + void increment_references (void) noexcept + { + mReferences.fetch_add(1, std::memory_order_relaxed); + } + + void decrement_references (void) noexcept + { + if (mReferences.fetch_sub(1, std::memory_order_acquire) == 0) + deallocate_this(); + } + + std::atomic mReferences; + std::atomic mType; +}; + +// Reduce compilation time and improve code re-use. +struct FutureBase : public FutureStatic +{ + typedef FutureStatic Base; + FutureStateBase * mState; + + mutex & get_mutex (void) const + { + return FutureStatic::get_mutex(mState); + } + condition_variable & get_condition_variable (void) const + { + return FutureStatic::get_condition_variable(mState); + } + + FutureBase (FutureStateBase * ptr) noexcept + : mState(ptr) + { + } + + FutureBase (FutureBase && source) noexcept + : mState(source.mState) + { + source.mState = nullptr; + } + + ~FutureBase (void) + { + release(); + } + + FutureBase (FutureBase const &) = delete; + FutureBase & operator= (FutureBase const &) = delete; + + bool valid (void) const noexcept + { + return mState != nullptr; + } + +// Releases this object's hold on its state. Requires a specification of +// which state is being used. + inline void release (void) noexcept + { + if (valid()) + mState->decrement_references(); + mState = nullptr; + } + + void wait (std::unique_lock & lock) const + { +#if !defined(NDEBUG) + if (!valid()) + throw future_error(future_errc::no_state); +#endif +// If there's already a value or exception, don't do any extraneous +// synchronization. The `get()` method will do that for us. + if (mState->mType.load(std::memory_order_relaxed) & Type::kNoWaitMask) + return; + get_condition_variable().wait(lock, [this](void)->bool { + return mState->mType.load(std::memory_order_relaxed) & Type::kNoWaitMask; + }); + } + + template + future_status wait_for (std::chrono::duration const & dur) const + { +#if !defined(NDEBUG) + if (!valid()) + throw future_error(future_errc::no_state); +#endif + auto current_state = mState->mType.load(std::memory_order_relaxed); + if (current_state & Type::kNoWaitMask) + return (current_state & Type::kDeferredFlag) ? future_status::deferred : future_status::ready; + std::unique_lock lock { get_mutex() }; + if (get_condition_variable().wait_for(lock, dur, + [this](void)->bool { + return mState->mType.load(std::memory_order_relaxed) & Type::kNoWaitMask; + })) + return future_status::ready; + else + return future_status::timeout; + } + + template + future_status wait_until(const std::chrono::time_point& time) const + { + return wait_for(time - Clock::now()); + } +}; + +template +struct FutureState : public FutureStateBase +{ +// The state never needs more than one of these at any one time, so don't +// waste space or allocation time. + union { + struct {} mUndecided; // Included to make the active member unambiguous. + T mObject; + std::exception_ptr mException; + std::function mFunction; + }; + + FutureState (void) noexcept + : FutureStateBase(Type::kUndecided), mUndecided() + { + } + + FutureState (std::function && deferred_function) + : FutureStateBase(Type::kDeferredFlag), mFunction(std::move(deferred_function)) + { + } + + void deallocate_this (void) noexcept override + { + delete this; + } + + template + void set_value (Arg && arg) + { + assert(!(mType.load(std::memory_order_relaxed) & Type::kReadyFlag)); + new(&mObject) T (std::forward(arg)); + mType.store(Type::kValueFlag | Type::kReadyFlag, std::memory_order_release); + } + template + void set_exception (Arg && arg) + { + assert(!(mType.load(std::memory_order_relaxed) & Type::kReadyFlag)); + new(&mException) std::exception_ptr (std::forward(arg)); + mType.store(Type::kExceptionFlag | Type::kReadyFlag, std::memory_order_release); + } +// These overloads set value/exception, but don't make it ready. + template + void set_value (Arg && arg, bool) + { + assert(!(mType.load(std::memory_order_relaxed) & Type::kReadyFlag)); + new(&mObject) T (std::forward(arg)); + mType.store(Type::kValueFlag, std::memory_order_release); + } + template + void set_exception (Arg && arg, bool) + { + assert(!(mType.load(std::memory_order_relaxed) & Type::kReadyFlag)); + new(&mException) std::exception_ptr (std::forward(arg)); + mType.store(Type::kExceptionFlag, std::memory_order_release); + } + //private: + ~FutureState (void) + { + auto type = mType.load(std::memory_order_acquire); + switch (type & Type::kTypeMask) + { + case Type::kDeferredFlag: + mFunction.~function(); + break; + case Type::kValueFlag: + mObject.~T(); + break; + case Type::kExceptionFlag: + mException.~exception_ptr(); + break; + default: + assert(type == Type::kUndecided); + } + } +}; + +template +struct FutureStateAllocated : public FutureState +{ + typedef typename std::allocator_traits::void_pointer void_pointer; + void_pointer mThis; + Alloc mAllocator; + + FutureStateAllocated (Alloc const & alloc, void_pointer const & vptr) noexcept + : FutureState(), mThis(vptr), mAllocator(alloc) + { + } + + FutureStateAllocated (FutureStateAllocated const &) = delete; + FutureStateAllocated & operator= (FutureStateAllocated const &) = delete; + + void deallocate_this (void) noexcept override + { + typedef typename std::allocator_traits::template rebind_traits > allocator_traits; + typename allocator_traits::allocator_type alloc(std::move(mAllocator)); + typedef typename allocator_traits::pointer pointer; + pointer ptr(static_cast(mThis)); + allocator_traits::destroy(alloc, this); + allocator_traits::deallocate(alloc, ptr, 1); + } +}; +} // Namespace "detail" + +#if (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) +} +namespace std { +#else +template +class future; +template +class shared_future; +template +class promise; +#endif + +template +class future : mingw_stdthread::detail::FutureBase +{ + typedef mingw_stdthread::detail::FutureState state_type; + future (state_type * ptr) noexcept + : FutureBase(ptr) + { + } + + friend class shared_future; + friend class promise; + + template + friend class future; + + template + friend future<__async_result_of<_Fn, _Args...>> async (std::launch, _Fn &&, _Args&&...); + public: + using FutureBase::valid; + using FutureBase::wait_for; + using FutureBase::wait_until; + + future (void) noexcept + : FutureBase(nullptr) + { + } + future & operator= (future && source) noexcept + { +// Check for this atypical behavior rather than creating a nonsensical state. + if (this != &source) + { + release(); + mState = source.mState; + source.mState = nullptr; + } + return *this; + } + future (future && source) noexcept + : FutureBase(std::move(source)) + { + } + + ~future (void) = default; + + future (future const &) = delete; + future & operator= (future const &) = delete; + + T const & get (void) const + { + wait(); + auto type = mState->mType.load(std::memory_order_acquire); + if (type == (Type::kValueFlag | Type::kReadyFlag)) + return static_cast(mState)->mObject; + else + { + assert(type == (Type::kExceptionFlag | Type::kReadyFlag)); + std::rethrow_exception(static_cast(mState)->mException); + } + } + + shared_future share (void) noexcept; + + void wait (void) const + { + auto state = mState->mType.load(std::memory_order_relaxed); + if (state & Type::kReadyFlag) + return; + std::unique_lock lock { get_mutex() }; + FutureBase::wait(lock); + if (mState->mType.load(std::memory_order_acquire) & Type::kDeferredFlag) + { + state_type * ptr = static_cast(mState); + decltype(ptr->mFunction) func = std::move(ptr->mFunction); + ptr->mFunction.~function(); + func(); + lock.unlock(); + ptr->get_condition_variable().notify_all(); + } + } +}; + +template +class shared_future : future +{ + typedef typename future::state_type state_type; + public: + using future::get; + using future::wait; + using future::wait_for; + using future::wait_until; + using future::valid; + + shared_future (void) noexcept : future() + { + } + + shared_future (shared_future && source) noexcept + : future(std::move(source)) + { + } + + shared_future & operator= (shared_future && source) noexcept + { + return future::operator=(std::move(source)); + } + + shared_future (shared_future const & source) noexcept(__cplusplus >= 201703L) + : future(static_cast(source.mState)) + { + future::mState->increment_references(); + } + + shared_future & operator= (shared_future const & source) noexcept(__cplusplus >= 201703L) + { + if (future::mState == source.mState) + return *this; + future::release(); + future::mState = source.mState; + future::mState->increment_references(); + return *this; + } + + shared_future (future && source) noexcept + : future(std::move(source)) + { + } + + shared_future & operator= (future && source) noexcept + { + future::operator=(std::move(source)); + return *this; + } + + ~shared_future (void) = default; +}; + +template +class promise : mingw_stdthread::detail::FutureBase +{ + bool mRetrieved; + typedef mingw_stdthread::detail::FutureState state_type; + void check_before_set (void) const + { + if (!valid()) + throw future_error(future_errc::no_state); + if (mState->mType.load(std::memory_order_relaxed) & Type::kReadyFlag) + throw future_error(future_errc::promise_already_satisfied); + } + + void check_abandon (void) + { + if (valid() && !(mState->mType.load(std::memory_order_relaxed) & Type::kReadyFlag)) + { + set_exception(std::make_exception_ptr(future_error(future_errc::broken_promise))); + } + } +/// \bug Might throw more exceptions than specified by the standard... +// Need OS support for this... + void make_ready_at_thread_exit (void) + { + static constexpr DWORD kInfinite = 0xffffffffl; +// Need to turn the pseudohandle from GetCurrentThread() into a true handle... + HANDLE thread_handle; + BOOL success = DuplicateHandle(GetCurrentProcess(), + GetCurrentThread(), + GetCurrentProcess(), + &thread_handle, + 0, // Access doesn't matter. Will be duplicated. + FALSE, // No need for this to be inherited. + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + if (!success) + throw std::runtime_error("MinGW STD Threads library failed to make a promise ready after thread exit."); + + mState->increment_references(); + bool handle_handled = false; + try { + state_type * ptr = static_cast(mState); + mingw_stdthread::thread watcher_thread ([ptr, thread_handle, &handle_handled](void) + { + { + std::lock_guard guard (ptr->get_mutex()); + handle_handled = true; + } + ptr->get_condition_variable().notify_all(); +// Wait for the original thread to die. + WaitForSingleObject(thread_handle, kInfinite); + CloseHandle(thread_handle); + + { + std::lock_guard guard (ptr->get_mutex()); + ptr->mType.fetch_or(Type::kReadyFlag, std::memory_order_relaxed); + } + ptr->get_condition_variable().notify_all(); + + ptr->decrement_references(); + }); + { + std::unique_lock guard (ptr->get_mutex()); + ptr->get_condition_variable().wait(guard, [&handle_handled](void)->bool + { + return handle_handled; + }); + } + watcher_thread.detach(); + } + catch (...) + { +// Because the original promise is still alive, this can't be the decrement +// destroys it. + mState->decrement_references(); + if (!handle_handled) + CloseHandle(thread_handle); + } + } + + template + future make_future (void) + { + if (!valid()) + throw future_error(future_errc::no_state); + if (mRetrieved) + throw future_error(future_errc::future_already_retrieved); + mState->increment_references(); + mRetrieved = true; + return future(static_cast(mState)); + } + + template + friend class promise; + public: +// Create a promise with an empty state, with the reference counter set to +// indicate that the state is only held by this promise (i.e. not by any +// futures). + promise (void) + : FutureBase(new state_type ()), mRetrieved(false) + { + } + + template + promise (std::allocator_arg_t, Alloc const & alloc) + : FutureBase(nullptr), mRetrieved(false) + { + typedef mingw_stdthread::detail::FutureStateAllocated State; + typedef typename std::allocator_traits::template rebind_traits Traits; + typename Traits::allocator_type rebound_alloc(alloc); + typename Traits::pointer ptr = Traits::allocate(rebound_alloc, 1); + typename Traits::void_pointer vptr = ptr; + State * sptr = std::addressof(*ptr); + Traits::construct(rebound_alloc, sptr, std::move(rebound_alloc), vptr); + mState = static_cast(sptr); + } + + promise (promise && source) noexcept + : FutureBase(std::move(source)), mRetrieved(source.mRetrieved) + { + } + + ~promise (void) + { + check_abandon(); + } + + promise & operator= (promise && source) noexcept + { + if (this == &source) + return *this; + check_abandon(); + release(); + mState = source.mState; + mRetrieved = source.mRetrieved; + source.mState = nullptr; + return *this; + } + + void swap (promise & other) noexcept + { + std::swap(mState, other.mState); + std::swap(mRetrieved, other.mRetrieved); + } + + promise (promise const &) = delete; + promise & operator= (promise const &) = delete; + + future get_future (void) + { + return make_future(); + } + + void set_value (T const & value) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_value(value); + } + get_condition_variable().notify_all(); + } + + void set_value (T && value) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_value(std::move(value)); + } + get_condition_variable().notify_all(); + } + + void set_value_at_thread_exit (T const & value) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_value(value, false); + } + make_ready_at_thread_exit(); + } + + void set_value_at_thread_exit (T && value) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_value(std::move(value), false); + } + make_ready_at_thread_exit(); + } + + void set_exception (std::exception_ptr eptr) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_exception(eptr); + } + get_condition_variable().notify_all(); + } + + void set_exception_at_thread_exit (std::exception_ptr eptr) + { + { + std::lock_guard lock { get_mutex() }; + check_before_set(); + static_cast(mState)->set_exception(eptr, false); + } + make_ready_at_thread_exit(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Reference Specialization // +//////////////////////////////////////////////////////////////////////////////// + +template +class future : future +{ + typedef future Base; + template + friend class shared_future; + template + friend class promise; + + future (typename Base::state_type * state) + : Base(state) + { + } + + template + friend future<__async_result_of<_Fn, _Args...>> async (std::launch, _Fn &&, _Args&&...); + public: + using Base::valid; + using Base::wait_for; + using Base::wait_until; + using Base::wait; + + future (void) noexcept = default; + + inline T& get (void) const + { + return *static_cast(Base::get()); + } + + shared_future share (void) noexcept; +}; + +template +class shared_future : shared_future +{ + typedef shared_future Base; + public: + using Base::wait; + using Base::wait_for; + using Base::wait_until; + using Base::valid; + + inline T& get (void) const + { + return *static_cast(Base::get()); + } + + shared_future (future && source) noexcept + : Base(std::move(source)) + { + } + + shared_future & operator= (future && source) noexcept + { + Base::operator=(std::move(source)); + return *this; + } + + ~shared_future (void) = default; +}; + +template +class promise : private promise +{ + typedef promise Base; + public: + using Base::set_exception; + using Base::set_exception_at_thread_exit; + + promise (void) = default; + template + promise (std::allocator_arg_t arg, Alloc const & alloc) + : Base(arg, alloc) + { + } + + inline void set_value (T & value) + { + typedef typename std::remove_cv::type T_non_cv; + Base::set_value(const_cast(std::addressof(value))); + } + + inline void set_value_at_thread_exit (T & value) + { + typedef typename std::remove_cv::type T_non_cv; + Base::set_value_at_thread_exit(const_cast(std::addressof(value))); + } + + inline future get_future (void) + { + return Base::template make_future(); + } + + void swap (promise & other) noexcept + { + Base::swap(other); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Void Specialization // +//////////////////////////////////////////////////////////////////////////////// + +template<> +class future : future +{ + typedef mingw_stdthread::detail::Empty Empty; + template + friend class shared_future; + template + friend class promise; + + future(future::state_type * state) + : future(state) + { + } + + template + friend future<__async_result_of<_Fn, _Args...>> async (std::launch, _Fn &&, _Args&&...); + + public: + using future::valid; + using future::wait_for; + using future::wait_until; + using future::wait; + + future (void) noexcept = default; + + void get (void) const + { + future::get(); + } + + shared_future share (void) noexcept; +}; + +template<> +class shared_future : shared_future +{ + typedef mingw_stdthread::detail::Empty Empty; + public: + using shared_future::wait; + using shared_future::wait_for; + using shared_future::wait_until; + using shared_future::valid; + + void get (void) const + { + shared_future::get(); + } + + shared_future (void) noexcept = default; + + shared_future (shared_future && source) noexcept = default; + + shared_future & operator= (shared_future && source) noexcept = default; + + shared_future (shared_future const & source) noexcept(__cplusplus >= 201703L) = default; + + shared_future & operator= (shared_future const & source) noexcept(__cplusplus >= 201703L) = default; + + shared_future (future && source) noexcept + : shared_future(std::move(source)) + { + } + + shared_future & operator= (future && source) noexcept + { + shared_future::operator=(std::move(source)); + return *this; + } + + ~shared_future (void) = default; +}; + +inline shared_future future::share (void) noexcept +{ + return shared_future(std::move(*this)); +} + +template +shared_future future::share (void) noexcept +{ + return shared_future(std::move(*this)); +} + +template +shared_future future::share (void) noexcept +{ + return shared_future(std::move(*this)); +} + +template<> +class promise : private promise +{ + typedef mingw_stdthread::detail::Empty Empty; + public: + using promise::set_exception; + using promise::set_exception_at_thread_exit; + + promise (void) = default; + template + promise (std::allocator_arg_t arg, Alloc const & alloc) + : promise(arg, alloc) + { + } + + inline void set_value (void) + { + promise::set_value(Empty()); + } + + inline void set_value_at_thread_exit (void) + { + promise::set_value_at_thread_exit(Empty()); + } + + inline future get_future (void) + { + return promise::template make_future(); + } + + void swap (promise & other) noexcept + { + promise::swap(other); + } +}; + + + +template +void swap(promise & lhs, promise & rhs) noexcept +{ + lhs.swap(rhs); +} +} // Namespace "std" + +namespace mingw_stdthread +{ +namespace detail +{ +template +struct StorageHelper +{ + template + static void store_deferred (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + state_ptr->set_value(invoke(std::forward(func), std::forward(args)...)); + } catch (...) { + state_ptr->set_exception(std::current_exception()); + } + } + template + static void store (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + auto result = invoke(std::forward(func), std::forward(args)...); + std::lock_guard lock { state_ptr->get_mutex() }; // Reveal the results of the above action to + state_ptr->set_value(std::move(result)); + } catch (...) { + std::lock_guard lock { state_ptr->get_mutex() }; // Reveal the results of the above action to + state_ptr->set_exception(std::current_exception()); + } + state_ptr->get_condition_variable().notify_all(); + } +}; + +template +struct StorageHelper +{ + template + static void store_deferred (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + typedef typename std::remove_cv::type Ref_non_cv; + Ref & rf = invoke(std::forward(func), std::forward(args)...); + state_ptr->set_value(const_cast(std::addressof(rf))); + } catch (...) { + state_ptr->set_exception(std::current_exception()); + } + } + template + static void store (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + typedef typename std::remove_cv::type Ref_non_cv; + Ref & rf = invoke(std::forward(func), std::forward(args)...); + std::lock_guard lock { state_ptr->get_mutex() }; + state_ptr->set_value(const_cast(std::addressof(rf))); + } catch (...) { + std::lock_guard lock { state_ptr->get_mutex() }; + state_ptr->set_exception(std::current_exception()); + } + state_ptr->get_condition_variable().notify_all(); + } +}; + +template<> +struct StorageHelper +{ + template + static void store_deferred (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + invoke(std::forward(func), std::forward(args)...); + state_ptr->set_value(Empty{}); + } catch (...) { + state_ptr->set_exception(std::current_exception()); + } + } + template + static void store (FutureState * state_ptr, Func && func, Args&&... args) + { + try { + invoke(std::forward(func), std::forward(args)...); + std::lock_guard lock { state_ptr->get_mutex() }; + state_ptr->set_value(Empty{}); + } catch (...) { + std::lock_guard lock { state_ptr->get_mutex() }; + state_ptr->set_exception(std::current_exception()); + } + state_ptr->get_condition_variable().notify_all(); + } +}; +} // Namespace "detail" +} // Namespace "mingw_stdthread" +namespace std +{ + + +// Unfortunately, MinGW's locks us into a particular (non-standard) +// signature for async. +template< class Function, class... Args> +/*#if (__cplusplus < 201703L) +std::future::type(std::decay::type...)>::type> +#else +#if (__cplusplus > 201703L) +[[nodiscard]] +#endif +std::future, std::decay_t...>> +#endif*/ +#if (__cplusplus > 201703L) +[[nodiscard]] +#endif +std::future<__async_result_of > + async(Function&& f, Args&&... args) +{ + return async(launch::async | launch::deferred, std::forward(f), std::forward(args)...); +} +template< class Function, class... Args > +/*#if (__cplusplus < 201703L) +std::future::type(std::decay::type...)>::type> +#else +#if (__cplusplus > 201703L) +[[nodiscard]] +#endif +std::future, std::decay_t...> > +#endif*/ +#if (__cplusplus > 201703L) +[[nodiscard]] +#endif +std::future<__async_result_of > + async(std::launch policy, Function&& f, Args&&... args) +{ + typedef __async_result_of result_type; +/*#if (__cplusplus < 201703L) + typedef std::result_of::type(std::decay::type...)>::type result_type; +#else + typedef std::invoke_result_t, std::decay_t...> result_type; +#endif*/ + typedef future future_type; + typedef typename future_type::state_type state_type; + + //auto setter = [] + + state_type * state_ptr = nullptr; + /*if ((policy & std::launch::async) == std::launch::async) + state_ptr = new state_type (); + else + state_ptr = new state_type (std::function(std::bind(std::forward(f), std::forward(args)...)));*/ + + + if ((policy & std::launch::async) == std::launch::async) + { + auto deleter = [](state_type * ptr) { ptr->decrement_references(); }; + state_ptr = new state_type (); + state_ptr->increment_references(); + std::unique_ptr ooptr { state_ptr, deleter }; + mingw_stdthread::thread t ([](decltype(ooptr) ptr, typename std::decay::type f2, typename std::decay::type... args2) + { + typedef mingw_stdthread::detail::StorageHelper s_helper; + s_helper::store(ptr.get(), f2, args2...); + }, std::move(ooptr), std::forward(f), std::forward(args)...); + t.detach(); + } else { + typedef std::function func_type; + struct Packed + { + func_type func; + state_type * ptr; + }; + std::shared_ptr bound { new Packed { std::bind(std::forward(f), std::forward(args)...), nullptr } }; + state_ptr = new state_type (std::function([bound](void) + { + typedef mingw_stdthread::detail::StorageHelper s_helper; + s_helper::store_deferred(bound->ptr, std::move(bound->func)); + })); + bound->ptr = state_ptr; + } + assert(state_ptr != nullptr); + return future { state_ptr }; +} + +#if (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) +} // Namespace std +namespace mingw_stdthread +{ +using std::future; +using std::shared_future; +using std::promise; +using std::async; +#else +} // Namespace mingw_stdthread +namespace std +{ +template +void swap(mingw_stdthread::promise & lhs, mingw_stdthread::promise & rhs) noexcept +{ + lhs.swap(rhs); +} +#endif +} // Namespace + +template +struct std::uses_allocator, Alloc> : ::std::true_type +{ +}; + +#endif // MINGW_FUTURE_H_ diff --git a/mingw-std-threads/mingw.invoke.h b/mingw-std-threads/mingw.invoke.h new file mode 100644 index 0000000..d5c9dd3 --- /dev/null +++ b/mingw-std-threads/mingw.invoke.h @@ -0,0 +1,109 @@ +/// \file mingw.invoke.h +/// \brief Lightweight `invoke` implementation, for C++11 and C++14. +/// +/// (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States +/// \author Nathaniel J. McClatchey, PhD +/// +/// \copyright Simplified (2-clause) BSD License. +/// +/// \note This file may become part of the mingw-w64 runtime package. If/when +/// this happens, the appropriate license will be added, i.e. this code will +/// become dual-licensed, and the current BSD 2-clause license will stay. + +#ifndef MINGW_INVOKE_H_ +#define MINGW_INVOKE_H_ + +#include // For std::result_of, etc. +#include // For std::forward +#include // For std::reference_wrapper + +namespace mingw_stdthread +{ +namespace detail +{ +// For compatibility, implement std::invoke for C++11 and C++14 +#if __cplusplus < 201703L + template + struct Invoker + { + template + inline static typename std::result_of::type invoke (F&& f, Args&&... args) + { + return std::forward(f)(std::forward(args)...); + } + }; + template + struct InvokerHelper; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(*std::forward(t1)) + { + return *std::forward(t1); + } + + template + inline static auto get (const std::reference_wrapper& t1) -> decltype(t1.get()) + { + return t1.get(); + } + }; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(std::forward(t1)) + { + return std::forward(t1); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ + decltype((InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...)) + { + return (InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ + decltype(InvokerHelper::type>::value>::get(t1).*f) + { + return InvokerHelper::type>::value>::get(t1).*f; + } + }; + + template + struct InvokeResult + { + typedef Invoker::type>::value, + std::is_member_object_pointer::type>::value && + (sizeof...(Args) == 1)> invoker; + inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward(f), std::forward(args)...)) + { + return invoker::invoke(std::forward(f), std::forward(args)...); + } + }; + + template + auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult::invoke(std::forward(f), std::forward(args)...)) + { + return InvokeResult::invoke(std::forward(f), std::forward(args)...); + } +#else + using std::invoke; +#endif +} // Namespace "detail" +} // Namespace "mingw_stdthread" + +#endif diff --git a/mingw-std-threads/mingw.mutex.h b/mingw-std-threads/mingw.mutex.h new file mode 100644 index 0000000..1a0b257 --- /dev/null +++ b/mingw-std-threads/mingw.mutex.h @@ -0,0 +1,500 @@ +/** +* @file mingw.mutex.h +* @brief std::mutex et al implementation for MinGW +** (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* @author Alexander Vassilev +* +* @copyright Simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* @note +* This file may become part of the mingw-w64 runtime package. If/when this happens, +* the appropriate license will be added, i.e. this code will become dual-licensed, +* and the current BSD 2-clause license will stay. +*/ + +#ifndef WIN32STDMUTEX_H +#define WIN32STDMUTEX_H + +#if !defined(__cplusplus) || (__cplusplus < 201103L) +#error A C++11 compiler is required! +#endif +// Recursion checks on non-recursive locks have some performance penalty, and +// the C++ standard does not mandate them. The user might want to explicitly +// enable or disable such checks. If the user has no preference, enable such +// checks in debug builds, but not in release builds. +#ifdef STDMUTEX_RECURSION_CHECKS +#elif defined(NDEBUG) +#define STDMUTEX_RECURSION_CHECKS 0 +#else +#define STDMUTEX_RECURSION_CHECKS 1 +#endif + +#include +#include +#include +#include //need for call_once() + +#if STDMUTEX_RECURSION_CHECKS || !defined(NDEBUG) +#include +#endif + +#include // Detect Windows version. + +#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) +#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ + with Microsoft's API. We'll try to work around this, but we can make no\ + guarantees. This problem does not exist in MinGW-w64." +#include // No further granularity can be expected. +#else +#if STDMUTEX_RECURSION_CHECKS +#include // For GetCurrentThreadId +#endif +#include // For InitializeCriticalSection, etc. +#include // For GetLastError +#include +#endif + +// Need for the implementation of invoke +#include "mingw.invoke.h" + +#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501) +#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher. +#endif + +namespace mingw_stdthread +{ +// The _NonRecursive class has mechanisms that do not play nice with direct +// manipulation of the native handle. This forward declaration is part of +// a friend class declaration. +#if STDMUTEX_RECURSION_CHECKS +namespace vista +{ +class condition_variable; +} +#endif +// To make this namespace equivalent to the thread-related subset of std, +// pull in the classes and class templates supplied by std but not by this +// implementation. +using std::lock_guard; +using std::unique_lock; +using std::adopt_lock_t; +using std::defer_lock_t; +using std::try_to_lock_t; +using std::adopt_lock; +using std::defer_lock; +using std::try_to_lock; + +class recursive_mutex +{ + CRITICAL_SECTION mHandle; +public: + typedef LPCRITICAL_SECTION native_handle_type; + native_handle_type native_handle() {return &mHandle;} + recursive_mutex() noexcept : mHandle() + { + InitializeCriticalSection(&mHandle); + } + recursive_mutex (const recursive_mutex&) = delete; + recursive_mutex& operator=(const recursive_mutex&) = delete; + ~recursive_mutex() noexcept + { + DeleteCriticalSection(&mHandle); + } + void lock() + { + EnterCriticalSection(&mHandle); + } + void unlock() + { + LeaveCriticalSection(&mHandle); + } + bool try_lock() + { + return (TryEnterCriticalSection(&mHandle)!=0); + } +}; + +#if STDMUTEX_RECURSION_CHECKS +struct _OwnerThread +{ +// If this is to be read before locking, then the owner-thread variable must +// be atomic to prevent a torn read from spuriously causing errors. + std::atomic mOwnerThread; + constexpr _OwnerThread () noexcept : mOwnerThread(0) {} + static void on_deadlock (void) + { + using namespace std; + fprintf(stderr, "FATAL: Recursive locking of non-recursive mutex\ + detected. Throwing system exception\n"); + fflush(stderr); + throw system_error(make_error_code(errc::resource_deadlock_would_occur)); + } + DWORD checkOwnerBeforeLock() const + { + DWORD self = GetCurrentThreadId(); + if (mOwnerThread.load(std::memory_order_relaxed) == self) + on_deadlock(); + return self; + } + void setOwnerAfterLock(DWORD id) + { + mOwnerThread.store(id, std::memory_order_relaxed); + } + void checkSetOwnerBeforeUnlock() + { + DWORD self = GetCurrentThreadId(); + if (mOwnerThread.load(std::memory_order_relaxed) != self) + on_deadlock(); + mOwnerThread.store(0, std::memory_order_relaxed); + } +}; +#endif + +// Though the Slim Reader-Writer (SRW) locks used here are not complete until +// Windows 7, implementing partial functionality in Vista will simplify the +// interaction with condition variables. + +//Define SRWLOCK_INIT. + +#if !defined(SRWLOCK_INIT) +#pragma message "SRWLOCK_INIT macro is not defined. Defining automatically." +#define SRWLOCK_INIT {0} +#endif + +#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA) +namespace windows7 +{ +class mutex +{ + SRWLOCK mHandle; +// Track locking thread for error checking. +#if STDMUTEX_RECURSION_CHECKS + friend class vista::condition_variable; + _OwnerThread mOwnerThread {}; +#endif +public: + typedef PSRWLOCK native_handle_type; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { } +#pragma GCC diagnostic pop + mutex (const mutex&) = delete; + mutex & operator= (const mutex&) = delete; + void lock (void) + { +// Note: Undefined behavior if called recursively. +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + AcquireSRWLockExclusive(&mHandle); +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.setOwnerAfterLock(self); +#endif + } + void unlock (void) + { +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.checkSetOwnerBeforeUnlock(); +#endif + ReleaseSRWLockExclusive(&mHandle); + } +// TryAcquireSRW functions are a Windows 7 feature. +#if (WINVER >= _WIN32_WINNT_WIN7) + bool try_lock (void) + { +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + BOOL ret = TryAcquireSRWLockExclusive(&mHandle); +#if STDMUTEX_RECURSION_CHECKS + if (ret) + mOwnerThread.setOwnerAfterLock(self); +#endif + return ret; + } +#endif + native_handle_type native_handle (void) + { + return &mHandle; + } +}; +} // Namespace windows7 +#endif // Compiling for Vista +namespace xp +{ +class mutex +{ + CRITICAL_SECTION mHandle; + std::atomic_uchar mState; +// Track locking thread for error checking. +#if STDMUTEX_RECURSION_CHECKS + friend class vista::condition_variable; + _OwnerThread mOwnerThread {}; +#endif +public: + typedef PCRITICAL_SECTION native_handle_type; + constexpr mutex () noexcept : mHandle(), mState(2) { } + mutex (const mutex&) = delete; + mutex & operator= (const mutex&) = delete; + ~mutex() noexcept + { +// Undefined behavior if the mutex is held (locked) by any thread. +// Undefined behavior if a thread terminates while holding ownership of the +// mutex. + DeleteCriticalSection(&mHandle); + } + void lock (void) + { + unsigned char state = mState.load(std::memory_order_acquire); + while (state) { + if ((state == 2) && mState.compare_exchange_weak(state, 1, std::memory_order_acquire)) + { + InitializeCriticalSection(&mHandle); + mState.store(0, std::memory_order_release); + break; + } + if (state == 1) + { + Sleep(0); + state = mState.load(std::memory_order_acquire); + } + } +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + EnterCriticalSection(&mHandle); +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.setOwnerAfterLock(self); +#endif + } + void unlock (void) + { +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.checkSetOwnerBeforeUnlock(); +#endif + LeaveCriticalSection(&mHandle); + } + bool try_lock (void) + { + unsigned char state = mState.load(std::memory_order_acquire); + if ((state == 2) && mState.compare_exchange_strong(state, 1, std::memory_order_acquire)) + { + InitializeCriticalSection(&mHandle); + mState.store(0, std::memory_order_release); + } + if (state == 1) + return false; +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + BOOL ret = TryEnterCriticalSection(&mHandle); +#if STDMUTEX_RECURSION_CHECKS + if (ret) + mOwnerThread.setOwnerAfterLock(self); +#endif + return ret; + } + native_handle_type native_handle (void) + { + return &mHandle; + } +}; +} // Namespace "xp" +#if (WINVER >= _WIN32_WINNT_WIN7) +using windows7::mutex; +#else +using xp::mutex; +#endif + +class recursive_timed_mutex +{ + static constexpr DWORD kWaitAbandoned = 0x00000080l; + static constexpr DWORD kWaitObject0 = 0x00000000l; + static constexpr DWORD kInfinite = 0xffffffffl; + inline bool try_lock_internal (DWORD ms) noexcept + { + DWORD ret = WaitForSingleObject(mHandle, ms); +#ifndef NDEBUG + if (ret == kWaitAbandoned) + { + using namespace std; + fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); + terminate(); + } +#endif + return (ret == kWaitObject0) || (ret == kWaitAbandoned); + } +protected: + HANDLE mHandle; +// Track locking thread for error checking of non-recursive timed_mutex. For +// standard compliance, this must be defined in same class and at the same +// access-control level as every other variable in the timed_mutex. +#if STDMUTEX_RECURSION_CHECKS + friend class vista::condition_variable; + _OwnerThread mOwnerThread {}; +#endif +public: + typedef HANDLE native_handle_type; + native_handle_type native_handle() const {return mHandle;} + recursive_timed_mutex(const recursive_timed_mutex&) = delete; + recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; + recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {} + ~recursive_timed_mutex() + { + CloseHandle(mHandle); + } + void lock() + { + DWORD ret = WaitForSingleObject(mHandle, kInfinite); +// If (ret == WAIT_ABANDONED), then the thread that held ownership was +// terminated. Behavior is undefined, but Windows will pass ownership to this +// thread. +#ifndef NDEBUG + if (ret == kWaitAbandoned) + { + using namespace std; + fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); + terminate(); + } +#endif + if ((ret != kWaitObject0) && (ret != kWaitAbandoned)) + { + throw std::system_error(GetLastError(), std::system_category()); + } + } + void unlock() + { + if (!ReleaseMutex(mHandle)) + throw std::system_error(GetLastError(), std::system_category()); + } + bool try_lock() + { + return try_lock_internal(0); + } + template + bool try_lock_for(const std::chrono::duration& dur) + { + using namespace std::chrono; + auto timeout = duration_cast(dur).count(); + while (timeout > 0) + { + constexpr auto kMaxStep = static_cast(kInfinite-1); + auto step = (timeout < kMaxStep) ? timeout : kMaxStep; + if (try_lock_internal(static_cast(step))) + return true; + timeout -= step; + } + return false; + } + template + bool try_lock_until(const std::chrono::time_point& timeout_time) + { + return try_lock_for(timeout_time - Clock::now()); + } +}; + +// Override if, and only if, it is necessary for error-checking. +#if STDMUTEX_RECURSION_CHECKS +class timed_mutex: recursive_timed_mutex +{ +public: + timed_mutex() = default; + timed_mutex(const timed_mutex&) = delete; + timed_mutex& operator=(const timed_mutex&) = delete; + void lock() + { + DWORD self = mOwnerThread.checkOwnerBeforeLock(); + recursive_timed_mutex::lock(); + mOwnerThread.setOwnerAfterLock(self); + } + void unlock() + { + mOwnerThread.checkSetOwnerBeforeUnlock(); + recursive_timed_mutex::unlock(); + } + template + bool try_lock_for(const std::chrono::duration& dur) + { + DWORD self = mOwnerThread.checkOwnerBeforeLock(); + bool ret = recursive_timed_mutex::try_lock_for(dur); + if (ret) + mOwnerThread.setOwnerAfterLock(self); + return ret; + } + template + bool try_lock_until(const std::chrono::time_point& timeout_time) + { + return try_lock_for(timeout_time - Clock::now()); + } + bool try_lock () + { + return try_lock_for(std::chrono::milliseconds(0)); + } +}; +#else +typedef recursive_timed_mutex timed_mutex; +#endif + +class once_flag +{ +// When available, the SRW-based mutexes should be faster than the +// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista, +// and try_lock is not used by once_flag. +#if (_WIN32_WINNT == _WIN32_WINNT_VISTA) + windows7::mutex mMutex; +#else + mutex mMutex; +#endif + std::atomic_bool mHasRun; + once_flag(const once_flag&) = delete; + once_flag& operator=(const once_flag&) = delete; + template + friend void call_once(once_flag& once, Callable&& f, Args&&... args); +public: + constexpr once_flag() noexcept: mMutex(), mHasRun(false) {} +}; + +template +void call_once(once_flag& flag, Callable&& func, Args&&... args) +{ + if (flag.mHasRun.load(std::memory_order_acquire)) + return; + lock_guard lock(flag.mMutex); + if (flag.mHasRun.load(std::memory_order_relaxed)) + return; + detail::invoke(std::forward(func),std::forward(args)...); + flag.mHasRun.store(true, std::memory_order_release); +} +} // Namespace mingw_stdthread + +// Push objects into std, but only if they are not already there. +namespace std +{ +// Because of quirks of the compiler, the common "using namespace std;" +// directive would flatten the namespaces and introduce ambiguity where there +// was none. Direct specification (std::), however, would be unaffected. +// Take the safe option, and include only in the presence of MinGW's win32 +// implementation. +#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) +using mingw_stdthread::recursive_mutex; +using mingw_stdthread::mutex; +using mingw_stdthread::recursive_timed_mutex; +using mingw_stdthread::timed_mutex; +using mingw_stdthread::once_flag; +using mingw_stdthread::call_once; +#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#pragma message "This version of MinGW seems to include a win32 port of\ + pthreads, and probably already has C++11 std threading classes implemented,\ + based on pthreads. These classes, found in namespace std, are not overridden\ + by the mingw-std-thread library. If you would still like to use this\ + implementation (as it is more lightweight), use the classes provided in\ + namespace mingw_stdthread." +#endif +} +#endif // WIN32STDMUTEX_H diff --git a/mingw-std-threads/mingw.shared_mutex.h b/mingw-std-threads/mingw.shared_mutex.h new file mode 100644 index 0000000..ff1ac65 --- /dev/null +++ b/mingw-std-threads/mingw.shared_mutex.h @@ -0,0 +1,503 @@ +/// \file mingw.shared_mutex.h +/// \brief Standard-compliant shared_mutex for MinGW +/// +/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States +/// \author Nathaniel J. McClatchey +/// +/// \copyright Simplified (2-clause) BSD License. +/// +/// \note This file may become part of the mingw-w64 runtime package. If/when +/// this happens, the appropriate license will be added, i.e. this code will +/// become dual-licensed, and the current BSD 2-clause license will stay. +/// \note Target Windows version is determined by WINVER, which is determined in +/// from _WIN32_WINNT, which can itself be set by the user. + +// Notes on the namespaces: +// - The implementation can be accessed directly in the namespace +// mingw_stdthread. +// - Objects will be brought into namespace std by a using directive. This +// will cause objects declared in std (such as MinGW's implementation) to +// hide this implementation's definitions. +// - To avoid poluting the namespace with implementation details, all objects +// to be pushed into std will be placed in mingw_stdthread::visible. +// The end result is that if MinGW supplies an object, it is automatically +// used. If MinGW does not supply an object, this implementation's version will +// instead be used. + +#ifndef MINGW_SHARED_MUTEX_H_ +#define MINGW_SHARED_MUTEX_H_ + +#if !defined(__cplusplus) || (__cplusplus < 201103L) +#error A C++11 compiler is required! +#endif + +#include +// For descriptive errors. +#include +// Implementing a shared_mutex without OS support will require atomic read- +// modify-write capacity. +#include +// For timing in shared_lock and shared_timed_mutex. +#include +#include + +// Use MinGW's shared_lock class template, if it's available. Requires C++14. +// If unavailable (eg. because this library is being used in C++11), then an +// implementation of shared_lock is provided by this header. +#if (__cplusplus >= 201402L) +#include +#endif + +// For defer_lock_t, adopt_lock_t, and try_to_lock_t +#include "mingw.mutex.h" +// For this_thread::yield. +//#include "mingw.thread.h" + +// Might be able to use native Slim Reader-Writer (SRW) locks. +#ifdef _WIN32 +#include // Detect Windows version. +#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) +#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ + with Microsoft's API. We'll try to work around this, but we can make no\ + guarantees. This problem does not exist in MinGW-w64." +#include // No further granularity can be expected. +#else +#include +#endif +#endif + +namespace mingw_stdthread +{ +// Define a portable atomics-based shared_mutex +namespace portable +{ +class shared_mutex +{ + typedef uint_fast16_t counter_type; + std::atomic mCounter {0}; + static constexpr counter_type kWriteBit = 1 << (std::numeric_limits::digits - 1); + +#if STDMUTEX_RECURSION_CHECKS +// Runtime checker for verifying owner threads. Note: Exclusive mode only. + _OwnerThread mOwnerThread {}; +#endif +public: + typedef shared_mutex * native_handle_type; + + shared_mutex () = default; + +// No form of copying or moving should be allowed. + shared_mutex (const shared_mutex&) = delete; + shared_mutex & operator= (const shared_mutex&) = delete; + + ~shared_mutex () + { +// Terminate if someone tries to destroy an owned mutex. + assert(mCounter.load(std::memory_order_relaxed) == 0); + } + + void lock_shared (void) + { + counter_type expected = mCounter.load(std::memory_order_relaxed); + do + { +// Delay if writing or if too many readers are attempting to read. + if (expected >= kWriteBit - 1) + { + using namespace std; + expected = mCounter.load(std::memory_order_relaxed); + continue; + } + if (mCounter.compare_exchange_weak(expected, + static_cast(expected + 1), + std::memory_order_acquire, + std::memory_order_relaxed)) + break; + } + while (true); + } + + bool try_lock_shared (void) + { + counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast(~kWriteBit); + if (expected + 1 == kWriteBit) + return false; + else + return mCounter.compare_exchange_strong( expected, + static_cast(expected + 1), + std::memory_order_acquire, + std::memory_order_relaxed); + } + + void unlock_shared (void) + { + using namespace std; +#ifndef NDEBUG + if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast(~kWriteBit))) + throw system_error(make_error_code(errc::operation_not_permitted)); +#else + mCounter.fetch_sub(1, memory_order_release); +#endif + } + +// Behavior is undefined if a lock was previously acquired. + void lock (void) + { +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + using namespace std; +// Might be able to use relaxed memory order... +// Wait for the write-lock to be unlocked, then claim the write slot. + counter_type current; + while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit); + //this_thread::yield(); +// Wait for readers to finish up. + while (current != kWriteBit) + { + //this_thread::yield(); + current = mCounter.load(std::memory_order_acquire); + } +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.setOwnerAfterLock(self); +#endif + } + + bool try_lock (void) + { +#if STDMUTEX_RECURSION_CHECKS + DWORD self = mOwnerThread.checkOwnerBeforeLock(); +#endif + counter_type expected = 0; + bool ret = mCounter.compare_exchange_strong(expected, kWriteBit, + std::memory_order_acquire, + std::memory_order_relaxed); +#if STDMUTEX_RECURSION_CHECKS + if (ret) + mOwnerThread.setOwnerAfterLock(self); +#endif + return ret; + } + + void unlock (void) + { +#if STDMUTEX_RECURSION_CHECKS + mOwnerThread.checkSetOwnerBeforeUnlock(); +#endif + using namespace std; +#ifndef NDEBUG + if (mCounter.load(memory_order_relaxed) != kWriteBit) + throw system_error(make_error_code(errc::operation_not_permitted)); +#endif + mCounter.store(0, memory_order_release); + } + + native_handle_type native_handle (void) + { + return this; + } +}; + +} // Namespace portable + +// The native shared_mutex implementation primarily uses features of Windows +// Vista, but the features used for try_lock and try_lock_shared were not +// introduced until Windows 7. To allow limited use while compiling for Vista, +// I define the class without try_* functions in that case. +// Only fully-featured implementations will be placed into namespace std. +#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA) +namespace vista +{ +class condition_variable_any; +} + +namespace windows7 +{ +// We already #include "mingw.mutex.h". May as well reduce redundancy. +class shared_mutex : windows7::mutex +{ +// Allow condition_variable_any (and only condition_variable_any) to treat a +// shared_mutex as its base class. + friend class vista::condition_variable_any; +public: + using windows7::mutex::native_handle_type; + using windows7::mutex::lock; + using windows7::mutex::unlock; + using windows7::mutex::native_handle; + + void lock_shared (void) + { + AcquireSRWLockShared(native_handle()); + } + + void unlock_shared (void) + { + ReleaseSRWLockShared(native_handle()); + } + +// TryAcquireSRW functions are a Windows 7 feature. +#if (WINVER >= _WIN32_WINNT_WIN7) + bool try_lock_shared (void) + { + return TryAcquireSRWLockShared(native_handle()) != 0; + } + + using windows7::mutex::try_lock; +#endif +}; + +} // Namespace windows7 +#endif // Compiling for Vista +#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7)) +using windows7::shared_mutex; +#else +using portable::shared_mutex; +#endif + +class shared_timed_mutex : shared_mutex +{ + typedef shared_mutex Base; +public: + using Base::lock; + using Base::try_lock; + using Base::unlock; + using Base::lock_shared; + using Base::try_lock_shared; + using Base::unlock_shared; + + template< class Clock, class Duration > + bool try_lock_until ( const std::chrono::time_point& cutoff ) + { + do + { + if (try_lock()) + return true; + } + while (std::chrono::steady_clock::now() < cutoff); + return false; + } + + template< class Rep, class Period > + bool try_lock_for (const std::chrono::duration& rel_time) + { + return try_lock_until(std::chrono::steady_clock::now() + rel_time); + } + + template< class Clock, class Duration > + bool try_lock_shared_until ( const std::chrono::time_point& cutoff ) + { + do + { + if (try_lock_shared()) + return true; + } + while (std::chrono::steady_clock::now() < cutoff); + return false; + } + + template< class Rep, class Period > + bool try_lock_shared_for (const std::chrono::duration& rel_time) + { + return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time); + } +}; + +#if __cplusplus >= 201402L +using std::shared_lock; +#else +// If not supplied by shared_mutex (eg. because C++14 is not supported), I +// supply the various helper classes that the header should have defined. +template +class shared_lock +{ + Mutex * mMutex; + bool mOwns; +// Reduce code redundancy + void verify_lockable (void) + { + using namespace std; + if (mMutex == nullptr) + throw system_error(make_error_code(errc::operation_not_permitted)); + if (mOwns) + throw system_error(make_error_code(errc::resource_deadlock_would_occur)); + } +public: + typedef Mutex mutex_type; + + shared_lock (void) noexcept + : mMutex(nullptr), mOwns(false) + { + } + + shared_lock (shared_lock && other) noexcept + : mMutex(other.mutex_), mOwns(other.owns_) + { + other.mMutex = nullptr; + other.mOwns = false; + } + + explicit shared_lock (mutex_type & m) + : mMutex(&m), mOwns(true) + { + mMutex->lock_shared(); + } + + shared_lock (mutex_type & m, defer_lock_t) noexcept + : mMutex(&m), mOwns(false) + { + } + + shared_lock (mutex_type & m, adopt_lock_t) + : mMutex(&m), mOwns(true) + { + } + + shared_lock (mutex_type & m, try_to_lock_t) + : mMutex(&m), mOwns(m.try_lock_shared()) + { + } + + template< class Rep, class Period > + shared_lock( mutex_type& m, const std::chrono::duration& timeout_duration ) + : mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration)) + { + } + + template< class Clock, class Duration > + shared_lock( mutex_type& m, const std::chrono::time_point& timeout_time ) + : mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time)) + { + } + + shared_lock& operator= (shared_lock && other) noexcept + { + if (&other != this) + { + if (mOwns) + mMutex->unlock_shared(); + mMutex = other.mMutex; + mOwns = other.mOwns; + other.mMutex = nullptr; + other.mOwns = false; + } + return *this; + } + + + ~shared_lock (void) + { + if (mOwns) + mMutex->unlock_shared(); + } + + shared_lock (const shared_lock &) = delete; + shared_lock& operator= (const shared_lock &) = delete; + +// Shared locking + void lock (void) + { + verify_lockable(); + mMutex->lock_shared(); + mOwns = true; + } + + bool try_lock (void) + { + verify_lockable(); + mOwns = mMutex->try_lock_shared(); + return mOwns; + } + + template< class Clock, class Duration > + bool try_lock_until( const std::chrono::time_point& cutoff ) + { + verify_lockable(); + do + { + mOwns = mMutex->try_lock_shared(); + if (mOwns) + return mOwns; + } + while (std::chrono::steady_clock::now() < cutoff); + return false; + } + + template< class Rep, class Period > + bool try_lock_for (const std::chrono::duration& rel_time) + { + return try_lock_until(std::chrono::steady_clock::now() + rel_time); + } + + void unlock (void) + { + using namespace std; + if (!mOwns) + throw system_error(make_error_code(errc::operation_not_permitted)); + mMutex->unlock_shared(); + mOwns = false; + } + +// Modifiers + void swap (shared_lock & other) noexcept + { + using namespace std; + swap(mMutex, other.mMutex); + swap(mOwns, other.mOwns); + } + + mutex_type * release (void) noexcept + { + mutex_type * ptr = mMutex; + mMutex = nullptr; + mOwns = false; + return ptr; + } +// Observers + mutex_type * mutex (void) const noexcept + { + return mMutex; + } + + bool owns_lock (void) const noexcept + { + return mOwns; + } + + explicit operator bool () const noexcept + { + return owns_lock(); + } +}; + +template< class Mutex > +void swap( shared_lock& lhs, shared_lock& rhs ) noexcept +{ + lhs.swap(rhs); +} +#endif // C++11 +} // Namespace mingw_stdthread + +namespace std +{ +// Because of quirks of the compiler, the common "using namespace std;" +// directive would flatten the namespaces and introduce ambiguity where there +// was none. Direct specification (std::), however, would be unaffected. +// Take the safe option, and include only in the presence of MinGW's win32 +// implementation. +#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) +using mingw_stdthread::shared_mutex; +#endif +#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) +using mingw_stdthread::shared_timed_mutex; +using mingw_stdthread::shared_lock; +#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#pragma message "This version of MinGW seems to include a win32 port of\ + pthreads, and probably already has C++ std threading classes implemented,\ + based on pthreads. These classes, found in namespace std, are not overridden\ + by the mingw-std-thread library. If you would still like to use this\ + implementation (as it is more lightweight), use the classes provided in\ + namespace mingw_stdthread." +#endif +} // Namespace std +#endif // MINGW_SHARED_MUTEX_H_ diff --git a/mingw-std-threads/mingw.thread.h b/mingw-std-threads/mingw.thread.h new file mode 100644 index 0000000..6d61a00 --- /dev/null +++ b/mingw-std-threads/mingw.thread.h @@ -0,0 +1,360 @@ +/** +* @file mingw.thread.h +* @brief std::thread implementation for MinGW +* (c) 2013-2016 by Mega Limited, Auckland, New Zealand +* @author Alexander Vassilev +* +* @copyright Simplified (2-clause) BSD License. +* You should have received a copy of the license along with this +* program. +* +* This code is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* @note +* This file may become part of the mingw-w64 runtime package. If/when this happens, +* the appropriate license will be added, i.e. this code will become dual-licensed, +* and the current BSD 2-clause license will stay. +*/ + +#ifndef WIN32STDTHREAD_H +#define WIN32STDTHREAD_H + +#if !defined(__cplusplus) || (__cplusplus < 201103L) +#error A C++11 compiler is required! +#endif + +// Use the standard classes for std::, if available. +#include + +#include // For std::size_t +#include // Detect error type. +#include // For std::terminate +#include // For std::system_error +#include // For std::hash +#include // For std::tuple +#include // For sleep timing. +#include // For std::unique_ptr +#include // Stream output for thread ids. +#include // For std::swap, std::forward + +#include "mingw.invoke.h" + +#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) +#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ + with Microsoft's API. We'll try to work around this, but we can make no\ + guarantees. This problem does not exist in MinGW-w64." +#include // No further granularity can be expected. +#else +#include // For WaitForSingleObject +#include // For CloseHandle, etc. +#include // For GetNativeSystemInfo +#include // For GetCurrentThreadId +#endif +#include // For _beginthreadex + +#ifndef NDEBUG +#include +#endif + +#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501) +#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher. +#endif + +// Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0. +namespace mingw_stdthread +{ +namespace detail +{ + template + struct IntSeq {}; + + template + struct GenIntSeq : GenIntSeq { }; + + template + struct GenIntSeq<0, S...> { typedef IntSeq type; }; + +// Use a template specialization to avoid relying on compiler optimization +// when determining the parameter integer sequence. + template + class ThreadFuncCall; +// We can't define the Call struct in the function - the standard forbids template methods in that case + template + class ThreadFuncCall, Args...> + { + static_assert(sizeof...(S) == sizeof...(Args), "Args must match."); + using Tuple = std::tuple::type...>; + typename std::decay::type mFunc; + Tuple mArgs; + + public: + ThreadFuncCall(Func&& aFunc, Args&&... aArgs) + : mFunc(std::forward(aFunc)), + mArgs(std::forward(aArgs)...) + { + } + + void callFunc() + { + detail::invoke(std::move(mFunc), std::move(std::get(mArgs)) ...); + } + }; + +// Allow construction of threads without exposing implementation. + class ThreadIdTool; +} // Namespace "detail" + +class thread +{ +public: + class id + { + DWORD mId = 0; + friend class thread; + friend class std::hash; + friend class detail::ThreadIdTool; + explicit id(DWORD aId) noexcept : mId(aId){} + public: + id (void) noexcept = default; + friend bool operator==(id x, id y) noexcept {return x.mId == y.mId; } + friend bool operator!=(id x, id y) noexcept {return x.mId != y.mId; } + friend bool operator< (id x, id y) noexcept {return x.mId < y.mId; } + friend bool operator<=(id x, id y) noexcept {return x.mId <= y.mId; } + friend bool operator> (id x, id y) noexcept {return x.mId > y.mId; } + friend bool operator>=(id x, id y) noexcept {return x.mId >= y.mId; } + + template + friend std::basic_ostream<_CharT, _Traits>& + operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id) + { + if (__id.mId == 0) + { + return __out << "(invalid std::thread::id)"; + } + else + { + return __out << __id.mId; + } + } + }; +private: + static constexpr HANDLE kInvalidHandle = nullptr; + static constexpr DWORD kInfinite = 0xffffffffl; + HANDLE mHandle; + id mThreadId; + + template + static unsigned __stdcall threadfunc(void* arg) + { + std::unique_ptr call(static_cast(arg)); + call->callFunc(); + return 0; + } + + static unsigned int _hardware_concurrency_helper() noexcept + { + SYSTEM_INFO sysinfo; +// This is one of the few functions used by the library which has a nearly- +// equivalent function defined in earlier versions of Windows. Include the +// workaround, just as a reminder that it does exist. +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) + ::GetNativeSystemInfo(&sysinfo); +#else + ::GetSystemInfo(&sysinfo); +#endif + return sysinfo.dwNumberOfProcessors; + } +public: + typedef HANDLE native_handle_type; + id get_id() const noexcept {return mThreadId;} + native_handle_type native_handle() const {return mHandle;} + thread(): mHandle(kInvalidHandle), mThreadId(){} + + thread(thread&& other) + :mHandle(other.mHandle), mThreadId(other.mThreadId) + { + other.mHandle = kInvalidHandle; + other.mThreadId = id{}; + } + + thread(const thread &other)=delete; + + template + explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId() + { + using ArgSequence = typename detail::GenIntSeq::type; + using Call = detail::ThreadFuncCall; + auto call = new Call( + std::forward(func), std::forward(args)...); + unsigned id_receiver; + auto int_handle = _beginthreadex(NULL, 0, threadfunc, + static_cast(call), 0, &id_receiver); + if (int_handle == 0) + { + mHandle = kInvalidHandle; + int errnum = errno; + delete call; +// Note: Should only throw EINVAL, EAGAIN, EACCES + throw std::system_error(errnum, std::generic_category()); + } else { + mThreadId.mId = id_receiver; + mHandle = reinterpret_cast(int_handle); + } + } + + bool joinable() const {return mHandle != kInvalidHandle;} + +// Note: Due to lack of synchronization, this function has a race condition +// if called concurrently, which leads to undefined behavior. The same applies +// to all other member functions of this class, but this one is mentioned +// explicitly. + void join() + { + using namespace std; + if (get_id() == id(GetCurrentThreadId())) + throw system_error(make_error_code(errc::resource_deadlock_would_occur)); + if (mHandle == kInvalidHandle) + throw system_error(make_error_code(errc::no_such_process)); + if (!joinable()) + throw system_error(make_error_code(errc::invalid_argument)); + WaitForSingleObject(mHandle, kInfinite); + CloseHandle(mHandle); + mHandle = kInvalidHandle; + mThreadId = id{}; + } + + ~thread() + { + if (joinable()) + { +#ifndef NDEBUG + std::printf("Error: Must join() or detach() a thread before \ +destroying it.\n"); +#endif + std::terminate(); + } + } + thread& operator=(const thread&) = delete; + thread& operator=(thread&& other) noexcept + { + if (joinable()) + { +#ifndef NDEBUG + std::printf("Error: Must join() or detach() a thread before \ +moving another thread to it.\n"); +#endif + std::terminate(); + } + swap(std::forward(other)); + return *this; + } + void swap(thread&& other) noexcept + { + std::swap(mHandle, other.mHandle); + std::swap(mThreadId.mId, other.mThreadId.mId); + } + + static unsigned int hardware_concurrency() noexcept + { + static unsigned int cached = _hardware_concurrency_helper(); + return cached; + } + + void detach() + { + if (!joinable()) + { + using namespace std; + throw system_error(make_error_code(errc::invalid_argument)); + } + if (mHandle != kInvalidHandle) + { + CloseHandle(mHandle); + mHandle = kInvalidHandle; + } + mThreadId = id{}; + } +}; + +namespace detail +{ + class ThreadIdTool + { + public: + static thread::id make_id (DWORD base_id) noexcept + { + return thread::id(base_id); + } + }; +} // Namespace "detail" + +namespace this_thread +{ + inline thread::id get_id() noexcept + { + return detail::ThreadIdTool::make_id(GetCurrentThreadId()); + } + inline void yield() noexcept {Sleep(0);} + template< class Rep, class Period > + void sleep_for( const std::chrono::duration& sleep_duration) + { + static constexpr DWORD kInfinite = 0xffffffffl; + using namespace std::chrono; + using rep = milliseconds::rep; + rep ms = duration_cast(sleep_duration).count(); + while (ms > 0) + { + constexpr rep kMaxRep = static_cast(kInfinite - 1); + auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep; + Sleep(static_cast(sleepTime)); + ms -= sleepTime; + } + } + template + void sleep_until(const std::chrono::time_point& sleep_time) + { + sleep_for(sleep_time-Clock::now()); + } +} +} // Namespace mingw_stdthread + +namespace std +{ +// Because of quirks of the compiler, the common "using namespace std;" +// directive would flatten the namespaces and introduce ambiguity where there +// was none. Direct specification (std::), however, would be unaffected. +// Take the safe option, and include only in the presence of MinGW's win32 +// implementation. +#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) +using mingw_stdthread::thread; +// Remove ambiguity immediately, to avoid problems arising from the above. +//using std::thread; +namespace this_thread +{ +using namespace mingw_stdthread::this_thread; +} +#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#pragma message "This version of MinGW seems to include a win32 port of\ + pthreads, and probably already has C++11 std threading classes implemented,\ + based on pthreads. These classes, found in namespace std, are not overridden\ + by the mingw-std-thread library. If you would still like to use this\ + implementation (as it is more lightweight), use the classes provided in\ + namespace mingw_stdthread." +#endif + +// Specialize hash for this implementation's thread::id, even if the +// std::thread::id already has a hash. +template<> +struct hash +{ + typedef mingw_stdthread::thread::id argument_type; + typedef size_t result_type; + size_t operator() (const argument_type & i) const noexcept + { + return i.mId; + } +}; +} +#endif // WIN32STDTHREAD_H diff --git a/mingw-std-threads/tests/CMakeLists.txt b/mingw-std-threads/tests/CMakeLists.txt new file mode 100644 index 0000000..2d3d50d --- /dev/null +++ b/mingw-std-threads/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +project(stdthreadtest) +cmake_minimum_required(VERSION 3.0) + +string(CONCAT mingw_stdthreads_tests_compile_options_docstring + "Compiler flags used to compile mingw-stdthreads's tests. By default " + "it's -std=c++11 -Wall -Wextra") +set(MINGW_STDTHREADS_TESTS_COMPILE_OPTIONS "-std=c++11;-Wall;-Wextra" + CACHE STRING ${mingw_stdthreads_tests_compile_options_docstring}) + +set(MINGW_STDTHREADS_TESTS_ADDITIONAL_LINKER_FLAGS "" CACHE STRING + "Optional linker flags to be passed when linking mingw-stdthreads's tests") + +add_executable(${PROJECT_NAME} tests.cpp) +target_compile_options(${PROJECT_NAME} PRIVATE + ${MINGW_STDTHREADS_TESTS_COMPILE_OPTIONS}) +target_link_libraries(${PROJECT_NAME} PRIVATE mingw_stdthreads) +target_link_libraries(${PROJECT_NAME} PRIVATE + ${MINGW_STDTHREADS_TESTS_ADDITIONAL_LINKER_FLAGS}) \ No newline at end of file diff --git a/mingw-std-threads/tests/tests.cpp b/mingw-std-threads/tests/tests.cpp new file mode 100644 index 0000000..a9d3c5d --- /dev/null +++ b/mingw-std-threads/tests/tests.cpp @@ -0,0 +1,486 @@ +#ifndef MINGW_STDTHREADS_GENERATED_STDHEADERS + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include +#endif +#include +#include +#include +#include +#include + +using namespace std; + +int test_int = 42; + +// Pre-declaration to suppress some warnings. +void test_call_once(int, char const *); + +int cond = 0; +std::mutex m; +std::shared_mutex sm; +std::condition_variable cv; +std::condition_variable_any cv_any; + +std::atomic failure_count {0}; +template +void log_error (char const * fmtString, Args ...args) { + ++failure_count; + printf("%s", "ERROR: "); + printf(fmtString, args...); + printf("%s", "\n"); + fflush(stdout); +} + + +template +void log (char const * fmtString, Args ...args) { + printf(fmtString, args...); + printf("%s", "\n"); + fflush(stdout); +} + +void test_call_once(int a, const char* str) +{ + static std::atomic_flag already_run = ATOMIC_FLAG_INIT; + if (already_run.test_and_set(std::memory_order_relaxed)) + log_error("test_call_once was run at least twice."); + log("test_call_once called with a=%d, str=%s", a, str); + this_thread::sleep_for(std::chrono::milliseconds(500)); +} + +struct TestMove +{ + std::string mStr; + TestMove(const std::string& aStr): mStr(aStr){} + TestMove(TestMove&& other): mStr(other.mStr+" moved") + { printf("%s: Object moved\n", mStr.c_str()); } + TestMove(const TestMove&) : mStr() + { + assert(false && "TestMove: Object COPIED instead of moved"); + } +}; + +template +void test_future_set_value (promise & promise) +{ + promise.set_value(T(test_int)); +} + +template<> +void test_future_set_value (promise & promise) +{ + promise.set_value(); +} + +template +bool test_future_get_value (future & future) +{ + return (future.get() == T(test_int)); +} + +template<> +bool test_future_get_value (future & future) +{ + future.get(); + return true; +} + +template +struct CustomAllocator +{ + CustomAllocator (void) noexcept + { + } + + template + CustomAllocator (CustomAllocator const &) noexcept + { + } + + template + CustomAllocator & operator= (CustomAllocator const &) noexcept + { + return *this; + } + + typedef T value_type; + T * allocate (size_t n) + { + log("Used custom allocator to allocate %zu object(s).", n); + return static_cast(std::malloc(n * sizeof(T))); + } + void deallocate (T * ptr, size_t n) + { + log("Used custom allocator to deallocate %zu object(s).", n); + std::free(ptr); + } +}; + +template +void test_future () +{ + static_assert(is_move_constructible >::value, + "std::promise must be move-constructible."); + static_assert(is_move_assignable >::value, + "std::promise must be move-assignable."); + static_assert(!is_copy_constructible >::value, + "std::promise must not be copy-constructible."); + static_assert(!is_copy_assignable >::value, + "std::promise must not be copy-assignable."); + + static_assert(is_move_constructible >::value, + "std::future must be move-constructible."); + static_assert(is_move_assignable >::value, + "std::future must be move-assignable."); + static_assert(!is_copy_constructible >::value, + "std::future must not be copy-constructible."); + static_assert(!is_copy_assignable >::value, + "std::future must not be copy-assignable."); + + static_assert(is_move_constructible >::value, + "std::shared_future must be move-constructible."); + static_assert(is_move_assignable >::value, + "std::shared_future must be move-assignable."); + static_assert(is_copy_constructible >::value, + "std::shared_future must be copy-constructible."); + static_assert(is_copy_assignable >::value, + "std::shared_future must be copy-assignable."); + + log("\tMaking a few promises, and getting their futures..."); + promise promise_value, promise_exception, promise_broken, promise_late; + + future future_value = promise_value.get_future(); + future future_exception = promise_exception.get_future(); + future future_broken = promise_broken.get_future(); + future future_late = promise_late.get_future(); + + try { + future impossible_future = promise_value.get_future(); + log_error("Promise failed to detect that its future was already retrieved."); + } catch(...) { + log("\tPromise successfully prevented redundant future retrieval."); + } + + log("\tPassing promises to a new thread..."); + thread t ([](promise p_value, promise p_exception, promise, promise p_late) + { + this_thread::sleep_for(std::chrono::seconds(1)); + p_late.set_exception_at_thread_exit(std::make_exception_ptr(std::runtime_error("Thrown during the thread."))); + test_future_set_value(p_value); + p_exception.set_exception(std::make_exception_ptr(std::runtime_error("Things happened as expected."))); + this_thread::sleep_for(std::chrono::seconds(2)); + }, + std::move(promise_value), + std::move(promise_exception), + std::move(promise_broken), + std::move(promise_late)); + t.detach(); + + try { + bool was_expected = test_future_get_value(future_value); + log("\tReceived %sexpected value.", (was_expected ? "" : "un")); + } catch (...) { + log_error("Exception where there should be none!"); + throw; + } + try { + test_future_get_value(future_exception); + log_error("Got a value where there should be an exception!"); + } catch (std::exception & e) { + log("\tReceived an exception (\"%s\") as expected.", e.what()); + } + + log("\tWaiting for the thread to exit..."); + try { + test_future_get_value(future_late); + log_error("Got a value where there should be an exception!"); + } catch (std::exception & e) { + log("\tReceived an exception (\"%s\") as expected.", e.what()); + } + + try { + test_future_get_value(future_broken); + log_error("Got a value where there should be an exception!"); + } catch (std::future_error & e) { + log("\tReceived a future_error (\"%s\") as expected.", e.what()); + } + + { // Part 1: Test whether `async` has correct return behavior. + log("\tDeferring a function..."); + auto async_deferred = async(launch::deferred, [] (thread::id other_id) -> T + { + std::hash hasher; + log("\t\tDeferred function called on thread %zu (expected %zu)", hasher(std::this_thread::get_id()), hasher(other_id)); + if (std::this_thread::get_id() != other_id) + throw std::logic_error("Test failed. Function should have been deferred, but was asynchronous."); + if (!is_void::value) + return T(test_int); + }, this_thread::get_id()); + log("\tCalling a function asynchronously..."); + auto async_async = async(launch::async, [] (thread::id other_id) -> T + { + std::hash hasher; + log("\t\tAsynchronous function called on thread %zu (expected anything except %zu)", hasher(std::this_thread::get_id()), hasher(other_id)); + if (std::this_thread::get_id() == other_id) + throw std::logic_error("Test failed. Function should have been asynchronous, but was deferred."); + if (!is_void::value) + return T(test_int); + }, this_thread::get_id()); + log("\tLetting the implementation decide..."); + auto async_either = async([] (thread::id other_id) -> T + { + std::hash hasher; + log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous"); + if (!is_void::value) + return T(test_int); + }, this_thread::get_id()); + + if (async_deferred.wait_for(std::chrono::milliseconds(50)) != std::future_status::deferred) + log_error("Deferred future did not return deferred status."); + + log("\tFetching asynchronous result."); + test_future_get_value(async_async); + log("\tFetching deferred result."); + test_future_get_value(async_deferred); + log("\tFetching implementation-defined result."); + test_future_get_value(async_either); + } + + { // Part 2: Test waiting + log("\tCalling a make-work function asynchronously..."); + auto async_async = async(launch::async, [] (void) noexcept -> T + { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (!is_void::value) + return T(test_int); + }); + + unsigned sleep_count = 0; + while (async_async.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) + { + ++sleep_count; + } + + if (sleep_count < 4) + log_error("Number of timeouts (%u) was signficantly less than expected (9).", sleep_count); + else + log("\tTimed out %u times. Should be close to 9.", sleep_count); + } + + log("\tTesting async on pointer-to-member-function."); + struct Helper + { + thread::id other_id; + T call (void) const + { + std::hash hasher; + log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous"); + if (!is_void::value) + return T(test_int); + } + } test_class { this_thread::get_id() }; + auto async_member = async(Helper::call, test_class); + log("\tFetching result."); + test_future_get_value(async_member); +} + +#define TEST_SL_MV_CPY(ClassName) \ + static_assert(std::is_standard_layout::value, \ + "ClassName does not satisfy concept StandardLayoutType."); \ + static_assert(!std::is_move_constructible::value, \ + "ClassName must not be move-constructible."); \ + static_assert(!std::is_move_assignable::value, \ + "ClassName must not be move-assignable."); \ + static_assert(!std::is_copy_constructible::value, \ + "ClassName must not be copy-constructible."); \ + static_assert(!std::is_copy_assignable::value, \ + "ClassName must not be copy-assignable."); + +int main() +{ +#ifdef MINGW_STDTHREADS_GENERATED_STDHEADERS + std::cout << "Using cmake-generated stdheaders, "; +#endif + static_assert(std::is_trivially_copyable::value, + "thread::id must be trivially copyable."); + + TEST_SL_MV_CPY(mutex) + TEST_SL_MV_CPY(recursive_mutex) + TEST_SL_MV_CPY(timed_mutex) + TEST_SL_MV_CPY(recursive_timed_mutex) + TEST_SL_MV_CPY(shared_mutex) + TEST_SL_MV_CPY(shared_timed_mutex) + TEST_SL_MV_CPY(condition_variable) + TEST_SL_MV_CPY(condition_variable_any) + static_assert(!std::is_move_constructible::value, + "once_flag must not be move-constructible."); + static_assert(!std::is_move_assignable::value, + "once_flag must not be move-assignable."); + static_assert(!std::is_copy_constructible::value, + "once_flag must not be copy-constructible."); + static_assert(!std::is_copy_assignable::value, + "once_flag must not be copy-assignable."); + +// With C++ feature level and target Windows version potentially affecting +// behavior, make this information visible. + { + switch (__cplusplus) + { + case 201103L: std::cout << "Compiled in C++11"; break; + case 201402L: std::cout << "Compiled in C++14"; break; + case 201703L: std::cout << "Compiled in C++17"; break; + default: std::cout << "Compiled in a non-conforming C++ compiler"; + } + std::cout << ", targeting Windows "; + static_assert(WINVER > 0x0500, "Windows NT and earlier are not supported."); + switch (WINVER) + { + case 0x0501: std::cout << "XP"; break; + case 0x0502: std::cout << "Server 2003"; break; + case 0x0600: std::cout << "Vista"; break; + case 0x0601: std::cout << "7"; break; + case 0x0602: std::cout << "8"; break; + case 0x0603: std::cout << "8.1"; break; + case 0x0A00: std::cout << "10"; break; + default: std::cout << "10+"; + } + std::cout << "\n"; + } + + { + log("Testing serialization and hashing for thread::id..."); + std::cout << "Serialization:\t" << this_thread::get_id() << "\n"; + std::hash hasher; + std::cout << "Hash:\t" << hasher(this_thread::get_id()) << "\n"; + } + +// Regression test: Thread must copy any argument that is passed by value. + { + std::vector loop_threads; + std::atomic i_vals_touched [4];// { 0, 0, 0, 0 }; + for (int i = 0; i < 4; ++i) + i_vals_touched[i].store(0, std::memory_order_relaxed); + for (int i = 0; i < 4; ++i) + { + loop_threads.push_back(std::thread([&](int c) + { + log("For-loop test thread got value: %i", c); + i_vals_touched[c].fetch_add(1, std::memory_order_relaxed); + }, i)); + } + for (std::thread & thr : loop_threads) + thr.join(); + for (int i = 0; i < 4; ++i) + { + if (i_vals_touched[i] != 1) + { + log_error("Threads are not copying arguments!"); + return 1; + } + } + } + + std::thread t([](TestMove&& a, const char* b, int c) mutable + { + try + { + log("Worker thread started, sleeping for a while..."); +// Thread might move the string more than once. + assert(a.mStr.substr(0, 15) == "move test moved"); + assert(!strcmp(b, "test message")); + assert(c == -20); + auto move2nd = std::move(a); //test move to final destination + this_thread::sleep_for(std::chrono::milliseconds(1000)); + { + lock_guard lock(m); + cond = 1; + log("Notifying condvar"); + cv.notify_all(); + } + + this_thread::sleep_for(std::chrono::milliseconds(500)); + { + lock_guard lock(sm); + cond = 2; + log("Notifying condvar"); + cv_any.notify_all(); + } + + this_thread::sleep_for(std::chrono::milliseconds(500)); + { + lock_guard lock(sm); + cond = 3; + log("Notifying condvar"); + cv_any.notify_all(); + } + + log("Worker thread finishing"); + } + catch(std::exception& e) + { + log_error("EXCEPTION in worker thread: %s\n", e.what()); + } + }, + TestMove("move test"), "test message", -20); + try + { + log("Main thread: Locking mutex, waiting on condvar..."); + { + std::unique_lock lk(m); + cv.wait(lk, []{ return cond >= 1;} ); + log("condvar notified, cond = %d", cond); + assert(lk.owns_lock()); + } + log("Main thread: Locking shared_mutex, waiting on condvar..."); + { + std::unique_lock lk(sm); + cv_any.wait(lk, []{ return cond >= 2;} ); + log("condvar notified, cond = %d", cond); + assert(lk.owns_lock()); + } + log("Main thread: Locking shared_mutex in shared mode, waiting on condvar..."); + { + std::shared_lock lk(sm); + cv_any.wait(lk, []{ return cond >= 3;} ); + log("condvar notified, cond = %d", cond); + assert(lk.owns_lock()); + } + log("Main thread: Waiting on worker join..."); + + t.join(); + log("Main thread: Worker thread joined"); + fflush(stdout); + } + catch(std::exception& e) + { + log_error("EXCEPTION in main thread: %s", e.what()); + } + once_flag of; + call_once(of, test_call_once, 1, "test"); + call_once(of, test_call_once, 1, "ERROR! Should not be called second time"); + log("Test complete"); + + { + log("Testing implementation of ..."); + test_future(); + test_future(); + test_future(); + test_future(); + test_future(); + test_future(); + log("Testing 's use of allocators. Should allocate, then deallocate."); + promise allocated_promise (std::allocator_arg, CustomAllocator()); + allocated_promise.set_value(7); + } + + return failure_count.load() != 0; +} diff --git a/mingw-std-threads/utility_scripts/Generate-StdLikeHeaders.ps1 b/mingw-std-threads/utility_scripts/Generate-StdLikeHeaders.ps1 new file mode 100644 index 0000000..5208e27 --- /dev/null +++ b/mingw-std-threads/utility_scripts/Generate-StdLikeHeaders.ps1 @@ -0,0 +1,226 @@ + +<# +.SYNOPSIS + Generate std-like headers which you can use just like standard c++'s ones. + For example include . +.PARAMETER GccPath + Path to GCC. Will try to use the default one from $env:Path if not + specified. +.PARAMETER MinGWStdThreadsPath + Path to mingw-std-threads folder. Will try to use $PSScriptRoot/.. if not + specified. +.PARAMETER DestinationFolder + Destination folder where generated headers will be saved to +.PARAMETER GenerateCompilerWrapperWithFileName + If specified, will be generated a wrapper batch script for g++ which automatically + adds $DestinationFolder as an include path +.PARAMETER Interactive + Use this switch if you want to pass parameters interactively +#> +[CmdletBinding(PositionalBinding = $false)] +param ( + # Path of GCC + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "NonInteractive", + HelpMessage = "Pathtof GCC. Will try to use the default one from `$env:Path if not specified.")] + [string] + $GccPath, + + # Path of mingw-std-threads + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "NonInteractive", + HelpMessage = "Path to mingw-std-threads folder. Will try to use `$PSScriptRoot/.. if not specified.")] + [string] + $MinGWStdThreadsPath, + + # Destination folder path + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "NonInteractive", + HelpMessage = "Destination folder where generated headers will be saved to")] + [ValidateNotNullOrEmpty()] + [string] + $DestinationFolder, + + # Compiler wrapper path + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = "NonInteractive", + HelpMessage = "If specified, will generate a wrapper batch script for g++ which automatically adds `$DestinationFolder as an include path")] + [string] + $GenerateCompilerWrapperWithFileName, + + # Interactive Switch + [Parameter(ParameterSetName = "Interactive")] + [switch] + $Interactive = $false +) + +# Stop execution when encountering any error (includeing Write-Error command) +$ErrorActionPreference = "Stop"; + +# headers to be generated +$headers = @("condition_variable", "future", "mutex", "shared_mutex", "thread") + +# ask for user input in interactive mode +if ($Interactive) { + Write-Host "Generate std-like headers which you can use just like standard c++'s ones." + Write-Host "Something like `"include `"." + + $DestinationFolder = Read-Host -Prompt "Destination folder into which headers will be generated" + $GccPath = Read-Host -Prompt "Path to GCC, optional. Press Enter to let it be retrieved from PATH" + $MinGWStdThreadsPath = Read-Host -Prompt "Path to mingw-std-threads folder, optional. Press Enter to use default value" + $GenerateCompilerWrapperWithFileName = Read-Host "Optional path to which a wrapper batch script for g++ will be created. It will automatically use $DestinationFolder as an include path. Press Enter to skip" +} + +if (-not $GccPath) { + $GccPath = "gcc" +} + +# set default value of $MinGWStdThreadsPath +if (-not $MinGWStdThreadsPath) { + $scriptFilePath = $null + if ($MyInvocation.MyCommand.CommandType -eq "ExternalScript") { + $scriptFilePath = $MyInvocation.MyCommand.Definition + } + else { + $scriptFilePath = [Environment]::GetCommandLineArgs()[0] + } + $MinGWStdThreadsPath = (Get-Item -LiteralPath $scriptFilePath).Directory.Parent.FullName +} + +# Normalize paths +$GccPath = (Get-Command -Name $GccPath).Source +$MinGWStdThreadsPath = Resolve-Path -LiteralPath $MinGWStdThreadsPath +$DestinationFolder = New-Item -Path $DestinationFolder -ItemType "Directory" -Force + +Write-Output "GccPath: $GccPath" +Write-Output "MinGWStdThreadsPath: $MinGWStdThreadsPath" +Write-Output "DestinationFolder: $DestinationFolder" +if ($GenerateCompilerWrapperWithFileName) { + Write-Output "GenerateCompilerWrapperWithFileName: $GenerateCompilerWrapperWithFileName" +} + +# Find path of real headers +Write-Output "Retrieving system header search paths..." + +$readingIncludePath = $false +# Empty array which will later store include paths +$includePaths = @() + +# Launch GCC +$processStartInfo = New-Object -TypeName "System.Diagnostics.ProcessStartInfo" +$processStartInfo.FileName = $GccPath +$processStartInfo.Arguments = "-xc++ -E -v -" +$processStartInfo.RedirectStandardInput = $true +$processStartInfo.RedirectStandardOutput = $true +$processStartInfo.RedirectStandardError = $true +$processStartInfo.UseShellExecute = $false + +$outputLines = @() +$gcc = New-Object -TypeName "System.Diagnostics.Process" +try { + $gcc.StartInfo = $processStartInfo + $gcc.Start() | Out-Null + $gcc.StandardInput.Close() + $gcc.WaitForExit() + $output = $gcc.StandardError.ReadToEnd() + $outputLines = $output -split "[\r\n]" | + ForEach-Object { return $_.Trim() } | + Where-Object { return $_.Length -gt 0 } +} +finally { + $gcc.StandardInput.Dispose() + $gcc.StandardOutput.Dispose() + $gcc.StandardError.Dispose() + $gcc.Dispose() +} + +# Parse Output +foreach ($line in $outputLines) { + if (-not $readingIncludePath) { + if ($line -match "#include <...> search starts here:") { + $readingIncludePath = $true + } + continue + } + + if ($line -match "End of search list.") { + break + } + + Write-Output "Retrieved search path: $line" + $includePaths += $line +} + +if ($includePaths.Count -eq 0) { + Write-Error "Error: didn't find any #inlcude <...> search paths" +} + +# look for std header paths +Write-Output "Searching for standard headers..." +$stdHeaders = @() +# set a label called "nextHeader" to allow continue with outer loop +:nextHeader foreach ($header in $headers) { + # check if mingw-std-threads headers exist + $myHeader = "mingw.$header.h" + $myHeader = Join-Path -Path $MinGWStdThreadsPath -ChildPath $myHeader + if (-not (Test-Path -LiteralPath $myHeader -PathType "Leaf")) { + Write-Error "Error: mingw-std-threads header not found: $myHeader" + } + + foreach ($inludePath in $includePaths) { + $fullPath = Join-Path -Path $inludePath -ChildPath $header + if (Test-Path -LiteralPath $fullPath -PathType "Leaf") { + $fullPath = (Get-Item -LiteralPath $fullPath).FullName + $stdHeaders += $fullPath + Write-Output "Found std header: $fullPath" + # if found matching header, continue with outer loop + continue nextHeader + } + } + + Write-Error "Error: didn't find $header in any search paths" +} + +# generate headers +Write-Output "Generating headers..." +foreach ($stdHeader in $stdHeaders) { + $headerFileName = (Get-Item -LiteralPath $stdHeader).Name + $myHeader = "mingw.$headerFileName.h" + $myHeader = Join-Path -Path $MinGWStdThreadsPath -ChildPath $myHeader + Write-Output "Generating <$headerFileName> from $myHeader and $stdHeader..." + + # both two headers should already have include guards + # but we still add a #pragma once just to be safe + $content = "#pragma once`r`n" + $content += "#include `"$stdHeader`"`r`n" + $content += "#include `"$myHeader`"`r`n"; + + $outputFileName = Join-Path -Path $DestinationFolder -ChildPath $headerFileName + Write-Output "Writing file: $outputFileName" + + # use .NET's method to output lines to avoid UTF-8 BOM + $noBomEncoding = New-Object -TypeName "System.Text.UTF8Encoding" -ArgumentList $false + [IO.File]::WriteAllText($outputFileName, $content, $noBomEncoding) +} + +$message = "Successfully generated std-like headers. Use them by adding " +$message += "`"-I$DestinationFolder`" to your compiler command line parameters" +Write-Output $message + +if ($GenerateCompilerWrapperWithFileName) { + $compilerFolder = Split-Path -LiteralPath $GccPath + $compiler = Join-Path -Path $compilerFolder -ChildPath "g++" + $command = "@echo off`r`n" + $command += "$compiler %* `"-I$DestinationFolder`"" + $wrapper = New-Item -Path $GenerateCompilerWrapperWithFileName -ItemType "File" -Force + + # use .NET's method to output lines to avoid UTF-8 BOM + $noBomEncoding = New-Object -TypeName "System.Text.UTF8Encoding" -ArgumentList $false + [IO.File]::WriteAllText($wrapper, $command, $noBomEncoding) + + Write-Output "Wrapper batch script successfully generated to $wrapper" +} \ No newline at end of file diff --git a/mingw-std-threads/utility_scripts/generate_std_like_headers.bat b/mingw-std-threads/utility_scripts/generate_std_like_headers.bat new file mode 100644 index 0000000..9e2440c --- /dev/null +++ b/mingw-std-threads/utility_scripts/generate_std_like_headers.bat @@ -0,0 +1 @@ +powershell -NonInteractive -ExecutionPolicy ByPass -File %~dp0Generate-StdLikeHeaders.ps1 %* \ No newline at end of file diff --git a/mingw-std-threads/utility_scripts/generate_std_like_headers_interactive.bat b/mingw-std-threads/utility_scripts/generate_std_like_headers_interactive.bat new file mode 100644 index 0000000..aad146d --- /dev/null +++ b/mingw-std-threads/utility_scripts/generate_std_like_headers_interactive.bat @@ -0,0 +1 @@ +powershell -ExecutionPolicy ByPass -File %~dp0Generate-StdLikeHeaders.ps1 -Interactive \ No newline at end of file diff --git a/scene.cpp b/scene.cpp index c37e35d..391bc9c 100644 --- a/scene.cpp +++ b/scene.cpp @@ -169,23 +169,36 @@ void SceneInit(ID3D11Device* dev, ID3D11DeviceContext* dc) CHECKHR(dev->CreateBlendState(&blitBlendDesc, &g_BlitBlendState)); } + CD3D11_BUFFER_DESC pixelCountBufferDesc = CD3D11_BUFFER_DESC(sizeof(UINT32), D3D11_BIND_UNORDERED_ACCESS, D3D11_USAGE_DEFAULT, 0, D3D11_RESOURCE_MISC_BUFFER_STRUCTURED, sizeof(UINT32)); + + D3D11_UNORDERED_ACCESS_VIEW_DESC pixelCountUAVDesc = {}; // the available MinGW d3d11.h does not have a constructor, so replicate it + pixelCountUAVDesc.Format = DXGI_FORMAT_UNKNOWN; + pixelCountUAVDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + pixelCountUAVDesc.Buffer.FirstElement = 0; + pixelCountUAVDesc.Buffer.NumElements = 1; + pixelCountUAVDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_COUNTER; + + CD3D11_BUFFER_DESC MaxNumPixelsBufferDesc = CD3D11_BUFFER_DESC(16, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE, 0, sizeof(UINT32)); + + CD3D11_SAMPLER_DESC TrianglesSMPDesc = CD3D11_SAMPLER_DESC(D3D11_DEFAULT); + CHECKHR(dev->CreateBuffer( - &CD3D11_BUFFER_DESC(sizeof(UINT32), D3D11_BIND_UNORDERED_ACCESS, D3D11_USAGE_DEFAULT, 0, D3D11_RESOURCE_MISC_BUFFER_STRUCTURED, sizeof(UINT32)), + &pixelCountBufferDesc, NULL, &g_PixelCountBuffer)); CHECKHR(dev->CreateUnorderedAccessView( g_PixelCountBuffer, - &CD3D11_UNORDERED_ACCESS_VIEW_DESC(g_PixelCountBuffer, DXGI_FORMAT_UNKNOWN, 0, 1, D3D11_BUFFER_UAV_FLAG_COUNTER), + &pixelCountUAVDesc, &g_PixelCountUAV)); CHECKHR(dev->CreateBuffer( - &CD3D11_BUFFER_DESC(16, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE, 0, sizeof(UINT32)), + &MaxNumPixelsBufferDesc, NULL, &g_MaxNumPixelsBuffer)); CHECKHR(dev->CreateSamplerState( - &CD3D11_SAMPLER_DESC(D3D11_DEFAULT), + &TrianglesSMPDesc, &g_TrianglesSMP)); } @@ -205,27 +218,31 @@ void SceneResize(int width, int height) UINT sampleCount = kSampleCountCounts[g_SampleCountIndex]; if (g_TrianglesTex2DMS) g_TrianglesTex2DMS->Release(); + CD3D11_TEXTURE2D_DESC TrianglesTex2DMSDesc = CD3D11_TEXTURE2D_DESC(trianglesFormat, width, height, 1, 1, D3D11_BIND_RENDER_TARGET, D3D11_USAGE_DEFAULT, 0, sampleCount, 0, 0); CHECKHR(dev->CreateTexture2D( - &CD3D11_TEXTURE2D_DESC(trianglesFormat, width, height, 1, 1, D3D11_BIND_RENDER_TARGET, D3D11_USAGE_DEFAULT, 0, sampleCount, 0, 0), + &TrianglesTex2DMSDesc, NULL, &g_TrianglesTex2DMS)); if (g_TrianglesRTV) g_TrianglesRTV->Release(); + CD3D11_RENDER_TARGET_VIEW_DESC TrianglesRtvDesc = CD3D11_RENDER_TARGET_VIEW_DESC(D3D11_RTV_DIMENSION_TEXTURE2DMS, trianglesFormat, 0, 1); CHECKHR(dev->CreateRenderTargetView( g_TrianglesTex2DMS, - &CD3D11_RENDER_TARGET_VIEW_DESC(D3D11_RTV_DIMENSION_TEXTURE2DMS, trianglesFormat, 0, 1), + &TrianglesRtvDesc, &g_TrianglesRTV)); if (g_TrianglesTex2D) g_TrianglesTex2D->Release(); + CD3D11_TEXTURE2D_DESC TrianglesTex2DDesc = CD3D11_TEXTURE2D_DESC(trianglesFormat, width, height, 1, 1, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0, 1, 0, 0); CHECKHR(dev->CreateTexture2D( - &CD3D11_TEXTURE2D_DESC(trianglesFormat, width, height, 1, 1, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0, 1, 0, 0), + &TrianglesTex2DDesc, NULL, &g_TrianglesTex2D)); if (g_TrianglesSRV) g_TrianglesSRV->Release(); + CD3D11_SHADER_RESOURCE_VIEW_DESC TrianglesSRVDesc = CD3D11_SHADER_RESOURCE_VIEW_DESC(D3D11_SRV_DIMENSION_TEXTURE2D, trianglesFormat, 0, 1); CHECKHR(dev->CreateShaderResourceView( g_TrianglesTex2D, - &CD3D11_SHADER_RESOURCE_VIEW_DESC(D3D11_SRV_DIMENSION_TEXTURE2D, trianglesFormat, 0, 1), + &TrianglesSRVDesc, &g_TrianglesSRV)); }