Skip to content

Commit 261d77f

Browse files
Add win32/nt threads (#1194)
1 parent 17b4bb9 commit 261d77f

File tree

6 files changed

+583
-7
lines changed

6 files changed

+583
-7
lines changed

examples/0039.gen_win32_mangling/prebuild.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <cstdint>
66
#include <cstdlib>
7+
#include <cstring>
78
#include <fast_io.h>
89
#include <fast_io_device.h>
910
#include <fast_io_dsal/vector.h>
@@ -162,8 +163,11 @@ rule toasm
162163
}
163164
result.append(u8R"(
164165
rule compile
165-
command = clang++ $in -o $out -O2 -I ../../../include -std=c++23 -lntdll
166-
166+
command = clang++ $in -o $out -O2 -I ../../../include -std=c++23 )"
167+
#ifdef _WIN32
168+
"-lntdll"
169+
#endif
170+
R"(
167171
rule run
168172
command = ./$in
169173
)");
@@ -222,7 +226,7 @@ int main() noexcept {
222226
::fast_io::native_file build_ninja{"build/build.ninja", ::fast_io::open_mode::out};
223227
if (argc == 2)
224228
{
225-
::std::size_t len_argv = strlen(argv[1]);
229+
::std::size_t len_argv = ::std::strlen(argv[1]);
226230
if (argv[1][len_argv - 1] == '\\')
227231
{
228232
argv[1][len_argv - 1] = 0;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
namespace fast_io::details
4+
{
5+
class thread_start_routine_tuple_c_malloc_allocate_guard
6+
{
7+
public:
8+
void *ptr_{nullptr};
9+
10+
constexpr thread_start_routine_tuple_c_malloc_allocate_guard() = default;
11+
12+
constexpr thread_start_routine_tuple_c_malloc_allocate_guard(void *ptr) : ptr_{ptr}
13+
{}
14+
15+
constexpr thread_start_routine_tuple_c_malloc_allocate_guard(thread_start_routine_tuple_c_malloc_allocate_guard const &) noexcept = delete;
16+
constexpr thread_start_routine_tuple_c_malloc_allocate_guard(thread_start_routine_tuple_c_malloc_allocate_guard &&other) noexcept
17+
: ptr_{other.ptr_}
18+
{
19+
other.ptr_ = nullptr;
20+
}
21+
22+
constexpr ~thread_start_routine_tuple_c_malloc_allocate_guard()
23+
{
24+
if (ptr_ != nullptr)
25+
{
26+
::fast_io::generic_allocator_adapter<::fast_io::c_malloc_allocator>::deallocate(this->ptr_);
27+
}
28+
}
29+
};
30+
} // namespace fast_io::details
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,42 @@
1-
#pragma once
1+
#pragma once
2+
3+
#if (defined(_WIN32) && !defined(__WINE__)) && !defined(__CYGWIN__)
4+
#ifdef _WIN32_WINDOWS
5+
#include "win32.h"
6+
7+
namespace fast_io
8+
{
9+
using thread = ::fast_io::win32::win32_thread;
10+
11+
namespace this_thread
12+
{
13+
14+
using ::fast_io::win32::this_thread::get_id;
15+
using ::fast_io::win32::this_thread::sleep_for;
16+
using ::fast_io::win32::this_thread::sleep_until;
17+
18+
} // namespace this_thread
19+
20+
} // namespace fast_io
21+
#else
22+
#include "nt.h"
23+
24+
namespace fast_io
25+
{
26+
template <bool zw = false>
27+
using thread = ::fast_io::win32::nt::nt_thread<zw>;
28+
29+
namespace this_thread
30+
{
31+
32+
using ::fast_io::win32::nt::this_thread::get_id;
33+
using ::fast_io::win32::nt::this_thread::sleep_for;
34+
using ::fast_io::win32::nt::this_thread::sleep_until;
35+
36+
} // namespace this_thread
37+
38+
} // namespace fast_io
39+
#endif
40+
#elif defined (__linux__)
41+
42+
#endif
Lines changed: 277 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,277 @@
1-
#pragma once
1+
#pragma once
2+
3+
#include <chrono>
4+
#include <ranges>
5+
#include <cstdint>
6+
#include <utility>
7+
#include <functional>
8+
#include <type_traits>
9+
#include "../../../fast_io_dsal/tuple.h"
10+
#include "../../../fast_io_core_impl/allocation/common.h"
11+
12+
namespace fast_io::win32::nt
13+
{
14+
15+
namespace details
16+
{
17+
18+
class nt_thread_start_routine_tuple_allocate_guard
19+
{
20+
public:
21+
void *ptr_{nullptr};
22+
23+
constexpr nt_thread_start_routine_tuple_allocate_guard() = default;
24+
25+
constexpr nt_thread_start_routine_tuple_allocate_guard(void *ptr) : ptr_{ptr}
26+
{}
27+
28+
constexpr nt_thread_start_routine_tuple_allocate_guard(nt_thread_start_routine_tuple_allocate_guard const &) noexcept = delete;
29+
constexpr nt_thread_start_routine_tuple_allocate_guard(nt_thread_start_routine_tuple_allocate_guard &&other) noexcept
30+
: ptr_{other.ptr_}
31+
{
32+
other.ptr_ = nullptr;
33+
}
34+
35+
constexpr ~nt_thread_start_routine_tuple_allocate_guard()
36+
{
37+
if (ptr_ != nullptr)
38+
{
39+
::fast_io::generic_allocator_adapter<::fast_io::nt_rtlallocateheap_allocator>::deallocate(this->ptr_);
40+
}
41+
}
42+
};
43+
44+
template <typename Tuple, ::std::size_t... Is>
45+
constexpr ::std::uint_least32_t thread_start_routine(void *args) noexcept(noexcept(
46+
::std::invoke(::fast_io::get<Is>(*reinterpret_cast<Tuple *>(args))...)))
47+
{
48+
[[maybe_unused]] ::fast_io::win32::nt::details::nt_thread_start_routine_tuple_allocate_guard _(args);
49+
::std::invoke(::fast_io::get<Is>(*reinterpret_cast<Tuple *>(args))...);
50+
return 0;
51+
}
52+
53+
template <typename Tuple, ::std::size_t... Is>
54+
constexpr auto get_thread_start_routine(::std::index_sequence<Is...>) noexcept
55+
{
56+
return ::fast_io::win32::nt::details::thread_start_routine<Tuple, Is...>;
57+
}
58+
59+
} // namespace details
60+
61+
template <bool zw>
62+
class nt_thread
63+
{
64+
public:
65+
using id = void *;
66+
using native_handle_type = void *;
67+
68+
private:
69+
id id_;
70+
native_handle_type handle_;
71+
72+
public:
73+
nt_thread() noexcept
74+
: id_{nullptr}, handle_{nullptr}
75+
{}
76+
77+
template <typename Func, typename... Args>
78+
requires(::std::invocable<Func, Args...>)
79+
constexpr nt_thread(Func &&func, Args &&...args)
80+
{
81+
using start_routine_tuple_type = ::fast_io::tuple<decltype(func), decltype(args)...>;
82+
#if __has_cpp_attribute(indeterminate)
83+
::fast_io::win32::nt::client_id cid [[indeterminate]];
84+
#else
85+
::fast_io::win32::nt::client_id cid;
86+
#endif
87+
void *start_routine_tuple{::fast_io::generic_allocator_adapter<::fast_io::nt_rtlallocateheap_allocator>::allocate(sizeof(start_routine_tuple_type))};
88+
#if defined(__clang__)
89+
#pragma clang diagnostic push
90+
#pragma clang diagnostic ignored "-Wmissing-braces"
91+
#endif
92+
::new (start_routine_tuple) start_routine_tuple_type{::std::forward<Func>(func), ::std::forward<Args>(args)...};
93+
#if defined(__clang__)
94+
#pragma clang diagnostic pop
95+
#endif
96+
auto start_routine = ::fast_io::win32::nt::details::get_thread_start_routine<start_routine_tuple_type>(
97+
::std::make_index_sequence<sizeof...(Args) + 1>{});
98+
::std::uint_least32_t status{::fast_io::win32::nt::RtlCreateUserThread(
99+
reinterpret_cast<void *>(-1), // Result of GetCurrentProcess()
100+
nullptr, // SecurityDescriptor
101+
false, // no suspand
102+
0, // StackZeroBits
103+
0, // StackReserved
104+
0, // StackCommit
105+
reinterpret_cast<void *>(start_routine),
106+
start_routine_tuple, // args of func
107+
__builtin_addressof(this->handle_),
108+
__builtin_addressof(cid))};
109+
if (status) [[unlikely]]
110+
{
111+
::fast_io::throw_nt_error(status);
112+
}
113+
this->id_ = cid.UniqueThread;
114+
}
115+
116+
constexpr nt_thread(nt_thread const &) noexcept = delete;
117+
118+
constexpr nt_thread(nt_thread &&other) noexcept
119+
: id_(other.id_), handle_(other.handle_)
120+
{
121+
other.id_ = nullptr;
122+
other.handle_ = nullptr;
123+
}
124+
125+
constexpr ~nt_thread() noexcept
126+
{
127+
if (handle_ != nullptr)
128+
{
129+
if (this->joinable()) [[unlikely]]
130+
{
131+
::std::terminate();
132+
}
133+
auto status{::fast_io::win32::nt::nt_close<zw>(this->handle_)};
134+
if (status) [[unlikely]]
135+
{
136+
::fast_io::fast_terminate();
137+
}
138+
}
139+
}
140+
141+
constexpr nt_thread &operator=(nt_thread const &) noexcept = delete;
142+
143+
constexpr nt_thread &operator=(nt_thread &&other) noexcept
144+
{
145+
if (this == __builtin_addressof(other)) [[unlikely]]
146+
{
147+
return *this;
148+
}
149+
this->swap(other);
150+
return *this;
151+
}
152+
constexpr bool joinable() const noexcept
153+
{
154+
return this->id_ != nullptr;
155+
}
156+
157+
constexpr void join()
158+
{
159+
if (!this->joinable()) [[unlikely]]
160+
{
161+
::fast_io::fast_terminate();
162+
}
163+
auto status{::fast_io::win32::nt::nt_wait_for_single_object<zw>(this->handle_, /* INFINITE = */ int(0xffffffff), nullptr)};
164+
if (status) [[unlikely]]
165+
{
166+
::fast_io::throw_nt_error(status);
167+
}
168+
this->id_ = nullptr;
169+
}
170+
171+
constexpr void detach()
172+
{
173+
if (!this->joinable()) [[unlikely]]
174+
{
175+
::fast_io::fast_terminate();
176+
}
177+
auto status{::fast_io::win32::nt::nt_close<zw>(this->handle_)};
178+
if (status) [[unlikely]]
179+
{
180+
::fast_io::throw_nt_error(status);
181+
}
182+
this->handle_ = nullptr;
183+
this->id_ = nullptr;
184+
}
185+
186+
constexpr void swap(nt_thread &other) noexcept
187+
{
188+
::std::ranges::swap(handle_, other.handle_);
189+
::std::ranges::swap(id_, other.id_);
190+
}
191+
192+
/**
193+
* @note Unlike std::thread::get_id, this method return the const reference.
194+
*/
195+
[[nodiscard]]
196+
constexpr auto &&get_id() const noexcept
197+
{
198+
return this->id_;
199+
}
200+
201+
[[nodiscard]]
202+
constexpr auto &&native_handle() const noexcept
203+
{
204+
return this->handle_;
205+
}
206+
207+
/**
208+
* @brief Get the win32 id of the thread.
209+
* @note same as win32 GetCurrentThreadId
210+
*/
211+
constexpr ::std::uint_least32_t get_win32_id() const noexcept
212+
{
213+
auto peb = ::fast_io::win32::nt::nt_current_teb();
214+
return static_cast<::std::uint_least32_t>(reinterpret_cast<::std::size_t>(peb->ClientId.UniqueThread));
215+
}
216+
217+
static constexpr ::std::uint_least32_t hardware_concurrency()
218+
{
219+
::fast_io::win32::nt::system_basic_information sb{};
220+
auto status{::fast_io::win32::nt::nt_query_system_information<zw>(::fast_io::win32::nt::system_information_class::SystemBasicInformation,
221+
__builtin_addressof(sb), static_cast<::std::uint_least32_t>(sizeof(sb)), nullptr)};
222+
if (status) [[unlikely]]
223+
{
224+
::fast_io::throw_nt_error(status);
225+
}
226+
return static_cast<::std::uint_least32_t>(sb.NumberOfProcessors);
227+
}
228+
};
229+
230+
namespace this_thread
231+
{
232+
233+
template <bool zw = false>
234+
constexpr auto get_id() -> ::fast_io::win32::nt::nt_thread<zw>::id
235+
{
236+
::fast_io::win32::nt::thread_basic_information tbi;
237+
::std::uint_least32_t status{::fast_io::win32::nt::nt_query_information_thread<zw>(
238+
reinterpret_cast<void *>(-2), // NtCurrentThread
239+
::fast_io::win32::nt::thread_information_class::ThreadBasicInformation,
240+
__builtin_addressof(tbi),
241+
::std::uint_least32_t(sizeof(tbi)),
242+
nullptr)};
243+
if (status) [[unlikely]]
244+
{
245+
::fast_io::throw_nt_error(status);
246+
}
247+
return tbi.ClientId.UniqueThread;
248+
}
249+
250+
template <bool zw = false, typename Rep, typename Period>
251+
constexpr void sleep_for(::std::chrono::duration<Rep, Period> const &sleep_duration)
252+
{
253+
auto val = -static_cast<::std::int_least64_t>(::std::chrono::duration_cast<::std::chrono::microseconds>(sleep_duration).count() * 10);
254+
::std::uint_least32_t status{::fast_io::win32::nt::nt_delay_execution<zw>(false, __builtin_addressof(val))};
255+
if (status) [[unlikely]]
256+
{
257+
::fast_io::throw_nt_error(status);
258+
}
259+
}
260+
261+
template <bool zw = false, typename Clock, typename Duration>
262+
constexpr void sleep_until(::std::chrono::time_point<Clock, Duration> const &expect_time)
263+
{
264+
auto const unix_ts = ::std::chrono::duration_cast<std::chrono::seconds>(
265+
expect_time.time_since_epoch())
266+
.count();
267+
auto nt_ts = (unix_ts + 11644473600) * 10000000;
268+
::std::uint_least32_t status{::fast_io::win32::nt::nt_delay_execution<zw>(false, __builtin_addressof(nt_ts))};
269+
if (status) [[unlikely]]
270+
{
271+
::fast_io::throw_nt_error(status);
272+
}
273+
}
274+
275+
} // namespace this_thread
276+
277+
} // namespace fast_io::win32::nt
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
#pragma once
1+
#pragma once

0 commit comments

Comments
 (0)