-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtask.cpp
More file actions
195 lines (178 loc) · 8.3 KB
/
Copy pathtask.cpp
File metadata and controls
195 lines (178 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include "sys.h"
#include <openkal/task.h>
#include <openkal/memory.h>
// Execution contexts, and the primitive they are built upon.
//
// The primitive is a kernel call: this system has the same operation the other
// kernel's futex provides, under a different name and with no shared ancestry,
// and openkal's kal_task_wait is that operation. That two unrelated systems
// offer it is the evidence that it is the shape of the thing rather than the
// shape of one kernel.
//
// Contexts are not a kernel call. Creating one on this system means arranging
// state the kernel does not arrange, and the arrangement belongs to this
// system's thread library rather than to its kernel. Which of that library's
// names may be used is decided by one property: a program above openkal may
// itself define every ordinary name, so only a name no C library defines is
// reachable. `pthread_create_from_mach_thread' is such a name, and the
// measurement that established it is in .github/workflows/probe.yml.
//
// Where the program carries a runtime of its own --- the ordinary arrangement,
// and the default --- there is no such constraint and the ordinary names are
// used, which also avoids the cost the other arrangement pays.
// ⚠️ DECLARED, NOT INCLUDED — AND THAT IS THE SAME RULE THE BRANCH BELOW
// ALREADY FOLLOWS.
//
// `pthread_create_from_mach_thread` has always been declared here rather than
// taken from a header, because no header declares it. The three ordinary names
// were taken from `<pthread.h>`, and that header is this system's SDK — the one
// thing every other implementation in this ecosystem avoids: openkal-linux
// writes the system-call numbers, openkal-opensbi the SBI identifiers,
// openkal-windows the Win32 declarations.
//
// ⚠️ Measured 2026-08-23, building this package ON this system with the target
// side coming from the graph:
//
// …/MacOSX.sdk/usr/include/mach/mach_time.h:61:1:
// error: a type specifier is required for all declarations (× 19)
//
// The SDK's `<pthread.h>` won the search over the C library's in the graph, and
// pulled in a Mach header that its own prerequisites were not there for. On a
// Linux host the same build had taken the graph's copy and said nothing, which
// is why the difference is a HOST difference and shows up only here.
//
// ⭐ `pthread_t` IS A POINTER ON BOTH C LIBRARIES — `struct __pthread*` in musl
// and `struct _opaque_pthread_t*` on this system — so `void*` is the same
// argument at the ABI, which is the level `extern "C"` matches at. The value is
// stored as an integer below in either case.
extern "C" {
#ifdef OKM_STANDALONE
int pthread_create_from_mach_thread(void** thread, const void* attr,
void* (*start)(void*), void* arg);
#else
int pthread_create(void** thread, const void* attr,
void* (*start)(void*), void* arg);
int pthread_join(void* thread, void** value);
#endif
}
namespace {
struct context {
void (*entry)(void*);
void* arg;
#ifdef OKM_STANDALONE
volatile okm_u32 finished;
#else
unsigned long thread;
#endif
};
void* run(void* p) {
auto* c = static_cast<context*>(p);
c->entry(c->arg);
#ifdef OKM_STANDALONE
__atomic_store_n(&c->finished, 1u, __ATOMIC_RELEASE);
okm::sys(okm::nr_ulock_wake,
static_cast<okm_long>(okm::ul_compare_and_wait | okm::ulf_no_errno
| okm::ulf_wake_all),
reinterpret_cast<okm_long>(const_cast<okm_u32*>(&c->finished)), 0);
#endif
return nullptr;
}
int translate_posix(int e) {
switch (e) {
case okm::e_inval: case okm::e_fault: return kal_err_invalid;
case okm::e_again: return kal_err_again;
case okm::e_nomem: return kal_err_no_memory;
case okm::e_perm: return kal_err_permission;
default: return kal_err_io;
}
}
} // namespace
extern "C" {
int kal_task_start(void (*entry)(void*), void* arg, kal_task* out) {
if (entry == nullptr || out == nullptr) return kal_err_invalid;
auto* c = static_cast<context*>(kal_alloc(sizeof(context), alignof(context)));
if (c == nullptr) return kal_err_no_memory;
okm::fill(c, 0, sizeof(context));
c->entry = entry; c->arg = arg;
#ifdef OKM_STANDALONE
void* thread = nullptr;
const int rc = pthread_create_from_mach_thread(&thread, nullptr, run, c);
#else
void* id = nullptr;
const int rc = ::pthread_create(&id, nullptr, run, c);
if (rc == 0) c->thread = static_cast<unsigned long>(reinterpret_cast<okm_uptr>(id));
#endif
if (rc != 0) { kal_free(c, sizeof(context), alignof(context)); return translate_posix(rc); }
*out = kal_task{ reinterpret_cast<kal_uintptr>(c) };
return kal_ok;
}
int kal_task_join(kal_task h) {
auto* c = reinterpret_cast<context*>(h.h);
if (c == nullptr) return kal_err_invalid;
#ifdef OKM_STANDALONE
// Waited for with the primitive rather than with the thread library's own
// wait, because that one's name is among the ones a program above may
// define. The word the context sets before it ends is what is waited upon.
while (__atomic_load_n(&c->finished, __ATOMIC_ACQUIRE) == 0) {
okm::sys(okm::nr_ulock_wait,
static_cast<okm_long>(okm::ul_compare_and_wait | okm::ulf_no_errno),
reinterpret_cast<okm_long>(const_cast<okm_u32*>(&c->finished)), 0, 0);
}
#else
const int rc = ::pthread_join(reinterpret_cast<void*>(
static_cast<okm_uptr>(c->thread)), nullptr);
if (rc != 0) return translate_posix(rc);
#endif
kal_free(c, sizeof(context), alignof(context));
return kal_ok;
}
void kal_task_yield(void) { okm::relax(); }
kal_uintptr kal_task_current(void) { return okm::current_context(); }
int kal_task_wait(const kal_u32* word, kal_u32 expected,
kal_u64 timeout_ns) {
// The unit this system takes is the microsecond, and zero means no timeout.
// A timeout shorter than a microsecond is rounded up rather than down: a
// wait that returned before the time it was given would make every timed
// wait above it wrong.
okm_u32 microseconds = 0;
if (timeout_ns != 0) {
const kal_u64 rounded = (timeout_ns + 999u) / 1000u;
microseconds = rounded > 0xfffffffeu ? 0xfffffffeu : static_cast<okm_u32>(rounded);
}
for (;;) {
const okm_long r = okm::sys(okm::nr_ulock_wait,
static_cast<okm_long>(okm::ul_compare_and_wait
| okm::ulf_no_errno),
reinterpret_cast<okm_long>(const_cast<kal_u32*>(word)),
static_cast<okm_long>(expected),
static_cast<okm_long>(microseconds));
if (r >= 0) return kal_ok;
// The value had already changed, which is a successful outcome: the
// caller's condition no longer holds and it should re-examine it.
if (r == -okm::e_again) return kal_ok;
if (okm::interrupted(r)) continue;
if (r == -okm::e_timedout) return kal_err_again;
return okm::translate(r);
}
}
int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) {
if (count == 0) { if (woken) *woken = 0; return kal_ok; }
okm_long operation = okm::ul_compare_and_wait | okm::ulf_no_errno;
if (count > 1) operation |= okm::ulf_wake_all;
const okm_long r = okm::sys(okm::nr_ulock_wake, operation,
reinterpret_cast<okm_long>(const_cast<kal_u32*>(word)), 0);
// This system reports that nothing was waiting as a failure. Nothing having
// been waiting is not a failure of the operation, so it is reported as none
// woken.
if (okm::failed(r) && r != -okm::e_noent) return okm::translate(r);
if (woken) *woken = okm::failed(r) ? 0u : count;
return kal_ok;
}
// A context started here observes the thread-local storage of the toolchain
// that compiled the program: this system's thread library establishes it for
// every context it creates, which is why the position can be reported without
// this implementation doing anything to earn it.
const kal_uintptr kal_task_props =
KAL_TASK_PROP_PREEMPTIVE | KAL_TASK_PROP_PARALLEL
| KAL_TASK_PROP_WAIT_TIMEOUT | KAL_TASK_PROP_THREAD_LOCAL;
}