authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-23 03:21:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:51-07:00
logdd945bf1f8963452f5acf448dd26c73d2d7b29f6
tree60992ddb4b6b2d82c86c3660312f805f8f271bab
parent41070932f8a3a1dead7fb424f427b04824c19c72

one kqueue per thread


2 files changed, 816 insertions(+), 43 deletions(-)

lib/std/Io/IoUring.zig+3-31
...@@ -67,8 +67,8 @@ const Fiber = struct {...@@ -67,8 +67,8 @@ const Fiber = struct {
67 const min_stack_size = 4 * 1024 * 1024;67 const min_stack_size = 4 * 1024 * 1024;
68 const max_context_align: Alignment = .@"16";68 const max_context_align: Alignment = .@"16";
69 const max_context_size = max_context_align.forward(1024);69 const max_context_size = max_context_align.forward(1024);
70 const max_closure_size: usize = @max(@sizeOf(AsyncClosure), @sizeOf(DetachedClosure));70 const max_closure_size: usize = @sizeOf(AsyncClosure);
71 const max_closure_align: Alignment = .max(.of(AsyncClosure), .of(DetachedClosure));71 const max_closure_align: Alignment = .of(AsyncClosure);
72 const allocation_size = std.mem.alignForward(72 const allocation_size = std.mem.alignForward(
73 usize,73 usize,
74 max_closure_align.max(max_context_align).forward(74 max_closure_align.max(max_context_align).forward(
...@@ -886,7 +886,7 @@ fn concurrent(...@@ -886,7 +886,7 @@ fn concurrent(
886 .rip = @intFromPtr(&fiberEntry),886 .rip = @intFromPtr(&fiberEntry),
887 },887 },
888 .aarch64 => .{888 .aarch64 => .{
889 .sp = @intFromPtr(closure) - @sizeOf(usize) - 1,889 .sp = @intFromPtr(closure),
890 .fp = 0,890 .fp = 0,
891 .pc = @intFromPtr(&fiberEntry),891 .pc = @intFromPtr(&fiberEntry),
892 },892 },
...@@ -910,34 +910,6 @@ fn concurrent(...@@ -910,34 +910,6 @@ fn concurrent(
910 return @ptrCast(fiber);910 return @ptrCast(fiber);
911}911}
912912
913const DetachedClosure = struct {
914 event_loop: *EventLoop,
915 fiber: *Fiber,
916 start: *const fn (context: *const anyopaque) void,
917 detached_queue_node: std.DoublyLinkedList.Node,
918
919 fn contextPointer(closure: *DetachedClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 {
920 return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(DetachedClosure));
921 }
922
923 fn call(closure: *DetachedClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(DetachedClosure))) noreturn {
924 message.handle(closure.event_loop);
925 std.log.debug("{*} performing async detached", .{closure.fiber});
926 closure.start(closure.contextPointer());
927 const awaiter = @atomicRmw(?*Fiber, &closure.fiber.awaiter, .Xchg, Fiber.finished, .acq_rel);
928 closure.event_loop.yield(awaiter, pending_task: {
929 closure.event_loop.detached.mutex.lock(closure.event_loop.io()) catch |err| switch (err) {
930 error.Canceled => break :pending_task .nothing,
931 };
932 defer closure.event_loop.detached.mutex.unlock(closure.event_loop.io());
933 if (closure.detached_queue_node.next == &closure.detached_queue_node) break :pending_task .nothing;
934 closure.event_loop.detached.list.remove(&closure.detached_queue_node);
935 break :pending_task .recycle;
936 });
937 unreachable; // switched to dead fiber
938 }
939};
940
941fn await(913fn await(
942 userdata: ?*anyopaque,914 userdata: ?*anyopaque,
943 any_future: *std.Io.AnyFuture,915 any_future: *std.Io.AnyFuture,
lib/std/Io/Kqueue.zig+813-12
...@@ -9,23 +9,795 @@ const net = std.Io.net;...@@ -9,23 +9,795 @@ const net = std.Io.net;
9const assert = std.debug.assert;9const assert = std.debug.assert;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const Alignment = std.mem.Alignment;11const Alignment = std.mem.Alignment;
12const posix = std.posix;
13const IpAddress = std.Io.net.IpAddress;12const IpAddress = std.Io.net.IpAddress;
14const errnoBug = std.Io.Threaded.errnoBug;13const errnoBug = std.Io.Threaded.errnoBug;
14const posix = std.posix;
1515
16/// Must be a thread-safe allocator.16/// Must be a thread-safe allocator.
17gpa: Allocator,17gpa: Allocator,
18mutex: std.Thread.Mutex,
19main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),
20threads: Thread.List,
1821
19pub fn init(gpa: Allocator) Kqueue {22/// Empirically saw >128KB being used by the self-hosted backend to panic.
20 return .{23const idle_stack_size = 256 * 1024;
24
25const max_idle_search = 4;
26const max_steal_ready_search = 4;
27
28const changes_buffer_len = 64;
29
30const Thread = struct {
31 thread: std.Thread,
32 idle_context: Context,
33 current_context: *Context,
34 ready_queue: ?*Fiber,
35 kq_fd: posix.fd_t,
36 idle_search_index: u32,
37 steal_ready_search_index: u32,
38
39 const canceling: ?*Thread = @ptrFromInt(@alignOf(Thread));
40
41 threadlocal var self: *Thread = undefined;
42
43 fn current() *Thread {
44 return self;
45 }
46
47 fn currentFiber(thread: *Thread) *Fiber {
48 return @fieldParentPtr("context", thread.current_context);
49 }
50
51 const List = struct {
52 allocated: []Thread,
53 reserved: u32,
54 active: u32,
55 };
56};
57
58const Fiber = struct {
59 required_align: void align(4),
60 context: Context,
61 awaiter: ?*Fiber,
62 queue_next: ?*Fiber,
63 cancel_thread: ?*Thread,
64 awaiting_completions: std.StaticBitSet(3),
65
66 const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread));
67
68 const max_result_align: Alignment = .@"16";
69 const max_result_size = max_result_align.forward(64);
70 /// This includes any stack realignments that need to happen, and also the
71 /// initial frame return address slot and argument frame, depending on target.
72 const min_stack_size = 4 * 1024 * 1024;
73 const max_context_align: Alignment = .@"16";
74 const max_context_size = max_context_align.forward(1024);
75 const max_closure_size: usize = @sizeOf(AsyncClosure);
76 const max_closure_align: Alignment = .of(AsyncClosure);
77 const allocation_size = std.mem.alignForward(
78 usize,
79 max_closure_align.max(max_context_align).forward(
80 max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size,
81 ) + max_closure_size + max_context_size,
82 std.heap.page_size_max,
83 );
84
85 fn allocate(k: *Kqueue) error{OutOfMemory}!*Fiber {
86 return @ptrCast(try k.gpa.alignedAlloc(u8, .of(Fiber), allocation_size));
87 }
88
89 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
90 return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size];
91 }
92
93 fn allocatedEnd(f: *Fiber) [*]u8 {
94 const allocated_slice = f.allocatedSlice();
95 return allocated_slice[allocated_slice.len..].ptr;
96 }
97
98 fn resultPointer(f: *Fiber, comptime Result: type) *Result {
99 return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
100 }
101
102 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
103 return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber)));
104 }
105
106 fn enterCancelRegion(fiber: *Fiber, thread: *Thread) error{Canceled}!void {
107 if (@cmpxchgStrong(
108 ?*Thread,
109 &fiber.cancel_thread,
110 null,
111 thread,
112 .acq_rel,
113 .acquire,
114 )) |cancel_thread| {
115 assert(cancel_thread == Thread.canceling);
116 return error.Canceled;
117 }
118 }
119
120 fn exitCancelRegion(fiber: *Fiber, thread: *Thread) void {
121 if (@cmpxchgStrong(
122 ?*Thread,
123 &fiber.cancel_thread,
124 thread,
125 null,
126 .acq_rel,
127 .acquire,
128 )) |cancel_thread| assert(cancel_thread == Thread.canceling);
129 }
130
131 const Queue = struct { head: *Fiber, tail: *Fiber };
132};
133
134fn recycle(k: *Kqueue, fiber: *Fiber) void {
135 std.log.debug("recyling {*}", .{fiber});
136 assert(fiber.queue_next == null);
137 k.gpa.free(fiber.allocatedSlice());
138}
139
140pub fn init(k: *Kqueue, gpa: Allocator) !void {
141 const threads_size = @max(std.Thread.getCpuCount() catch 1, 1) * @sizeOf(Thread);
142 const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);
143 const allocated_slice = try gpa.alignedAlloc(u8, .of(Thread), idle_stack_end_offset);
144 errdefer gpa.free(allocated_slice);
145 k.* = .{
21 .gpa = gpa,146 .gpa = gpa,
147 .mutex = .{},
148 .main_fiber_buffer = undefined,
149 .threads = .{
150 .allocated = @ptrCast(allocated_slice[0..threads_size]),
151 .reserved = 1,
152 .active = 1,
153 },
22 };154 };
155 const main_fiber: *Fiber = @ptrCast(&k.main_fiber_buffer);
156 main_fiber.* = .{
157 .required_align = {},
158 .context = undefined,
159 .awaiter = null,
160 .queue_next = null,
161 .cancel_thread = null,
162 .awaiting_completions = .initEmpty(),
163 };
164 const main_thread = &k.threads.allocated[0];
165 Thread.self = main_thread;
166 const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr));
167 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(k)};
168 main_thread.* = .{
169 .thread = undefined,
170 .idle_context = switch (builtin.cpu.arch) {
171 .aarch64 => .{
172 .sp = @intFromPtr(idle_stack_end),
173 .fp = 0,
174 .pc = @intFromPtr(&mainIdleEntry),
175 .x18 = asm (""
176 : [x18] "={x18}" (-> u64),
177 ),
178 },
179 .x86_64 => .{
180 .rsp = @intFromPtr(idle_stack_end - 1),
181 .rbp = 0,
182 .rip = @intFromPtr(&mainIdleEntry),
183 },
184 else => @compileError("unimplemented architecture"),
185 },
186 .current_context = &main_fiber.context,
187 .ready_queue = null,
188 .kq_fd = try posix.kqueue(),
189 .idle_search_index = 1,
190 .steal_ready_search_index = 1,
191 };
192 errdefer std.posix.close(main_thread.kq_fd);
193 std.log.debug("created main idle {*}", .{&main_thread.idle_context});
194 std.log.debug("created main {*}", .{main_fiber});
23}195}
24196
25pub fn deinit(k: *Kqueue) void {197pub fn deinit(k: *Kqueue) void {
198 const active_threads = @atomicLoad(u32, &k.threads.active, .acquire);
199 for (k.threads.allocated[0..active_threads]) |*thread| {
200 const ready_fiber = @atomicLoad(?*Fiber, &thread.ready_queue, .monotonic);
201 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
202 }
203 k.yield(null, .exit);
204 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(k.threads.allocated.ptr));
205 const idle_stack_end_offset = std.mem.alignForward(usize, k.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
206 for (k.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
207 k.gpa.free(allocated_ptr[0..idle_stack_end_offset]);
26 k.* = undefined;208 k.* = undefined;
27}209}
28210
211fn findReadyFiber(k: *Kqueue, thread: *Thread) ?*Fiber {
212 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {
213 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
214 ready_fiber.queue_next = null;
215 return ready_fiber;
216 }
217 const active_threads = @atomicLoad(u32, &k.threads.active, .acquire);
218 for (0..@min(max_steal_ready_search, active_threads)) |_| {
219 defer thread.steal_ready_search_index += 1;
220 if (thread.steal_ready_search_index == active_threads) thread.steal_ready_search_index = 0;
221 const steal_ready_search_thread = &k.threads.allocated[0..active_threads][thread.steal_ready_search_index];
222 if (steal_ready_search_thread == thread) continue;
223 const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue;
224 if (ready_fiber == Fiber.finished) continue;
225 if (@cmpxchgWeak(
226 ?*Fiber,
227 &steal_ready_search_thread.ready_queue,
228 ready_fiber,
229 null,
230 .acquire,
231 .monotonic,
232 )) |_| continue;
233 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
234 ready_fiber.queue_next = null;
235 return ready_fiber;
236 }
237 // couldn't find anything to do, so we are now open for business
238 @atomicStore(?*Fiber, &thread.ready_queue, null, .monotonic);
239 return null;
240}
241
242fn yield(k: *Kqueue, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void {
243 const thread: *Thread = .current();
244 const ready_context = if (maybe_ready_fiber orelse k.findReadyFiber(thread)) |ready_fiber|
245 &ready_fiber.context
246 else
247 &thread.idle_context;
248 const message: SwitchMessage = .{
249 .contexts = .{
250 .prev = thread.current_context,
251 .ready = ready_context,
252 },
253 .pending_task = pending_task,
254 };
255 std.log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready });
256 contextSwitch(&message).handle(k);
257}
258
259fn schedule(k: *Kqueue, thread: *Thread, ready_queue: Fiber.Queue) void {
260 {
261 var fiber = ready_queue.head;
262 while (true) {
263 std.log.debug("scheduling {*}", .{fiber});
264 fiber = fiber.queue_next orelse break;
265 }
266 assert(fiber == ready_queue.tail);
267 }
268 // shared fields of previous `Thread` must be initialized before later ones are marked as active
269 const new_thread_index = @atomicLoad(u32, &k.threads.active, .acquire);
270 for (0..@min(max_idle_search, new_thread_index)) |_| {
271 defer thread.idle_search_index += 1;
272 if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0;
273 const idle_search_thread = &k.threads.allocated[0..new_thread_index][thread.idle_search_index];
274 if (idle_search_thread == thread) continue;
275 if (@cmpxchgWeak(
276 ?*Fiber,
277 &idle_search_thread.ready_queue,
278 null,
279 ready_queue.head,
280 .release,
281 .monotonic,
282 )) |_| continue;
283 const changes = [_]posix.Kevent{
284 .{
285 .ident = 0,
286 .filter = std.c.EVFILT.USER,
287 .flags = std.c.EV.ADD | std.c.EV.ONESHOT,
288 .fflags = std.c.NOTE.TRIGGER,
289 .data = 0,
290 .udata = @intFromEnum(Completion.UserData.wakeup),
291 },
292 };
293 // If an error occurs it only pessimises scheduling.
294 _ = posix.kevent(idle_search_thread.kq_fd, &changes, &.{}, null) catch {};
295 return;
296 }
297 spawn_thread: {
298 // previous failed reservations must have completed before retrying
299 if (new_thread_index == k.threads.allocated.len or @cmpxchgWeak(
300 u32,
301 &k.threads.reserved,
302 new_thread_index,
303 new_thread_index + 1,
304 .acquire,
305 .monotonic,
306 ) != null) break :spawn_thread;
307 const new_thread = &k.threads.allocated[new_thread_index];
308 const next_thread_index = new_thread_index + 1;
309 new_thread.* = .{
310 .thread = undefined,
311 .idle_context = undefined,
312 .current_context = &new_thread.idle_context,
313 .ready_queue = ready_queue.head,
314 .kq_fd = posix.kqueue() catch |err| {
315 @atomicStore(u32, &k.threads.reserved, new_thread_index, .release);
316 // no more access to `thread` after giving up reservation
317 std.log.warn("unable to create worker thread due to kqueue init failure: {t}", .{err});
318 break :spawn_thread;
319 },
320 .idle_search_index = 0,
321 .steal_ready_search_index = 0,
322 };
323 new_thread.thread = std.Thread.spawn(.{
324 .stack_size = idle_stack_size,
325 .allocator = k.gpa,
326 }, threadEntry, .{ k, new_thread_index }) catch |err| {
327 posix.close(new_thread.kq_fd);
328 @atomicStore(u32, &k.threads.reserved, new_thread_index, .release);
329 // no more access to `thread` after giving up reservation
330 std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)});
331 break :spawn_thread;
332 };
333 // shared fields of `Thread` must be initialized before being marked active
334 @atomicStore(u32, &k.threads.active, next_thread_index, .release);
335 return;
336 }
337 // nobody wanted it, so just queue it on ourselves
338 while (@cmpxchgWeak(
339 ?*Fiber,
340 &thread.ready_queue,
341 ready_queue.tail.queue_next,
342 ready_queue.head,
343 .acq_rel,
344 .acquire,
345 )) |old_head| ready_queue.tail.queue_next = old_head;
346}
347
348fn mainIdle(k: *Kqueue, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn {
349 message.handle(k);
350 k.idle(&k.threads.allocated[0]);
351 k.yield(@ptrCast(&k.main_fiber_buffer), .nothing);
352 unreachable; // switched to dead fiber
353}
354
355fn threadEntry(k: *Kqueue, index: u32) void {
356 const thread: *Thread = &k.threads.allocated[index];
357 Thread.self = thread;
358 std.log.debug("created thread idle {*}", .{&thread.idle_context});
359 k.idle(thread);
360}
361
362const Completion = struct {
363 const UserData = enum(usize) {
364 unused,
365 wakeup,
366 cleanup,
367 exit,
368 /// *Fiber
369 _,
370 };
371 /// Corresponds to Kevent field.
372 flags: u16,
373 /// Corresponds to Kevent field.
374 fflags: u32,
375 /// Corresponds to Kevent field.
376 data: isize,
377};
378
379fn idle(k: *Kqueue, thread: *Thread) void {
380 var events_buffer: [changes_buffer_len]posix.Kevent = undefined;
381 var maybe_ready_fiber: ?*Fiber = null;
382 while (true) {
383 while (maybe_ready_fiber orelse k.findReadyFiber(thread)) |ready_fiber| {
384 k.yield(ready_fiber, .nothing);
385 maybe_ready_fiber = null;
386 }
387 const n = posix.kevent(thread.kq_fd, &.{}, &events_buffer, null) catch |err| {
388 // TODO handle EINTR for cancellation purposes
389 @panic(@errorName(err));
390 };
391 var maybe_ready_queue: ?Fiber.Queue = null;
392 for (events_buffer[0..n]) |event| switch (@as(Completion.UserData, @enumFromInt(event.udata))) {
393 .unused => unreachable, // bad submission queued?
394 .wakeup => {},
395 .cleanup => @panic("failed to notify other threads that we are exiting"),
396 .exit => {
397 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
398 return;
399 },
400 _ => {
401 const fiber: *Fiber = @ptrFromInt(event.udata);
402 assert(fiber.queue_next == null);
403 fiber.resultPointer(Completion).* = .{
404 .flags = event.flags,
405 .fflags = event.fflags,
406 .data = event.data,
407 };
408 if (maybe_ready_fiber == null) maybe_ready_fiber = fiber else if (maybe_ready_queue) |*ready_queue| {
409 ready_queue.tail.queue_next = fiber;
410 ready_queue.tail = fiber;
411 } else maybe_ready_queue = .{ .head = fiber, .tail = fiber };
412 },
413 };
414 if (maybe_ready_queue) |ready_queue| k.schedule(thread, ready_queue);
415 }
416}
417
418const SwitchMessage = struct {
419 contexts: extern struct {
420 prev: *Context,
421 ready: *Context,
422 },
423 pending_task: PendingTask,
424
425 const PendingTask = union(enum) {
426 nothing,
427 reschedule,
428 recycle: *Fiber,
429 register_awaiter: *?*Fiber,
430 register_select: []const *Io.AnyFuture,
431 mutex_lock: struct {
432 prev_state: Io.Mutex.State,
433 mutex: *Io.Mutex,
434 },
435 condition_wait: struct {
436 cond: *Io.Condition,
437 mutex: *Io.Mutex,
438 },
439 exit,
440 };
441
442 fn handle(message: *const SwitchMessage, k: *Kqueue) void {
443 const thread: *Thread = .current();
444 thread.current_context = message.contexts.ready;
445 switch (message.pending_task) {
446 .nothing => {},
447 .reschedule => if (message.contexts.prev != &thread.idle_context) {
448 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
449 assert(prev_fiber.queue_next == null);
450 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
451 },
452 .recycle => |fiber| {
453 k.recycle(fiber);
454 },
455 .register_awaiter => |awaiter| {
456 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
457 assert(prev_fiber.queue_next == null);
458 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
459 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
460 },
461 .register_select => |futures| {
462 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
463 assert(prev_fiber.queue_next == null);
464 for (futures) |any_future| {
465 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
466 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
467 const closure: *AsyncClosure = .fromFiber(future_fiber);
468 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
469 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
470 }
471 }
472 }
473 },
474 .mutex_lock => |mutex_lock| {
475 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
476 assert(prev_fiber.queue_next == null);
477 var prev_state = mutex_lock.prev_state;
478 while (switch (prev_state) {
479 else => next_state: {
480 prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state));
481 break :next_state @cmpxchgWeak(
482 Io.Mutex.State,
483 &mutex_lock.mutex.state,
484 prev_state,
485 @enumFromInt(@intFromPtr(prev_fiber)),
486 .release,
487 .acquire,
488 );
489 },
490 .unlocked => @cmpxchgWeak(
491 Io.Mutex.State,
492 &mutex_lock.mutex.state,
493 .unlocked,
494 .locked_once,
495 .acquire,
496 .acquire,
497 ) orelse {
498 prev_fiber.queue_next = null;
499 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
500 return;
501 },
502 }) |next_state| prev_state = next_state;
503 },
504 .condition_wait => |condition_wait| {
505 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
506 assert(prev_fiber.queue_next == null);
507 const cond_impl = prev_fiber.resultPointer(Condition);
508 cond_impl.* = .{
509 .tail = prev_fiber,
510 .event = .queued,
511 };
512 if (@cmpxchgStrong(
513 ?*Fiber,
514 @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)),
515 null,
516 prev_fiber,
517 .release,
518 .acquire,
519 )) |waiting_fiber| {
520 const waiting_cond_impl = waiting_fiber.?.resultPointer(Condition);
521 assert(waiting_cond_impl.tail.queue_next == null);
522 waiting_cond_impl.tail.queue_next = prev_fiber;
523 waiting_cond_impl.tail = prev_fiber;
524 }
525 condition_wait.mutex.unlock(k.io());
526 },
527 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
528 const changes = [_]posix.Kevent{
529 .{
530 .ident = 0,
531 .filter = std.c.EVFILT.USER,
532 .flags = std.c.EV.ADD | std.c.EV.ONESHOT,
533 .fflags = std.c.NOTE.TRIGGER,
534 .data = 0,
535 .udata = @intFromEnum(Completion.UserData.exit),
536 },
537 };
538 _ = posix.kevent(each_thread.kq_fd, &changes, &.{}, null) catch |err| {
539 @panic(@errorName(err));
540 };
541 },
542 }
543 }
544};
545
546const Context = switch (builtin.cpu.arch) {
547 .aarch64 => extern struct {
548 sp: u64,
549 fp: u64,
550 pc: u64,
551 x18: u64,
552 },
553 .x86_64 => extern struct {
554 rsp: u64,
555 rbp: u64,
556 rip: u64,
557 },
558 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
559};
560
561inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
562 return @fieldParentPtr("contexts", switch (builtin.cpu.arch) {
563 .aarch64 => asm volatile (
564 \\ ldp x0, x2, [x1]
565 \\ ldp x3, x18, [x2, #16]
566 \\ mov x4, sp
567 \\ stp x4, fp, [x0]
568 \\ adr x5, 0f
569 \\ ldp x4, fp, [x2]
570 \\ stp x5, x18, [x0, #16]
571 \\ mov sp, x4
572 \\ br x3
573 \\0:
574 : [received_message] "={x1}" (-> *const @FieldType(SwitchMessage, "contexts")),
575 : [message_to_send] "{x1}" (&message.contexts),
576 : .{
577 .x0 = true,
578 .x1 = true,
579 .x2 = true,
580 .x3 = true,
581 .x4 = true,
582 .x5 = true,
583 .x6 = true,
584 .x7 = true,
585 .x8 = true,
586 .x9 = true,
587 .x10 = true,
588 .x11 = true,
589 .x12 = true,
590 .x13 = true,
591 .x14 = true,
592 .x15 = true,
593 .x16 = true,
594 .x17 = true,
595 .x19 = true,
596 .x20 = true,
597 .x21 = true,
598 .x22 = true,
599 .x23 = true,
600 .x24 = true,
601 .x25 = true,
602 .x26 = true,
603 .x27 = true,
604 .x28 = true,
605 .x30 = true,
606 .z0 = true,
607 .z1 = true,
608 .z2 = true,
609 .z3 = true,
610 .z4 = true,
611 .z5 = true,
612 .z6 = true,
613 .z7 = true,
614 .z8 = true,
615 .z9 = true,
616 .z10 = true,
617 .z11 = true,
618 .z12 = true,
619 .z13 = true,
620 .z14 = true,
621 .z15 = true,
622 .z16 = true,
623 .z17 = true,
624 .z18 = true,
625 .z19 = true,
626 .z20 = true,
627 .z21 = true,
628 .z22 = true,
629 .z23 = true,
630 .z24 = true,
631 .z25 = true,
632 .z26 = true,
633 .z27 = true,
634 .z28 = true,
635 .z29 = true,
636 .z30 = true,
637 .z31 = true,
638 .p0 = true,
639 .p1 = true,
640 .p2 = true,
641 .p3 = true,
642 .p4 = true,
643 .p5 = true,
644 .p6 = true,
645 .p7 = true,
646 .p8 = true,
647 .p9 = true,
648 .p10 = true,
649 .p11 = true,
650 .p12 = true,
651 .p13 = true,
652 .p14 = true,
653 .p15 = true,
654 .fpcr = true,
655 .fpsr = true,
656 .ffr = true,
657 .memory = true,
658 }),
659 .x86_64 => asm volatile (
660 \\ movq 0(%%rsi), %%rax
661 \\ movq 8(%%rsi), %%rcx
662 \\ leaq 0f(%%rip), %%rdx
663 \\ movq %%rsp, 0(%%rax)
664 \\ movq %%rbp, 8(%%rax)
665 \\ movq %%rdx, 16(%%rax)
666 \\ movq 0(%%rcx), %%rsp
667 \\ movq 8(%%rcx), %%rbp
668 \\ jmpq *16(%%rcx)
669 \\0:
670 : [received_message] "={rsi}" (-> *const @FieldType(SwitchMessage, "contexts")),
671 : [message_to_send] "{rsi}" (&message.contexts),
672 : .{
673 .rax = true,
674 .rcx = true,
675 .rdx = true,
676 .rbx = true,
677 .rsi = true,
678 .rdi = true,
679 .r8 = true,
680 .r9 = true,
681 .r10 = true,
682 .r11 = true,
683 .r12 = true,
684 .r13 = true,
685 .r14 = true,
686 .r15 = true,
687 .mm0 = true,
688 .mm1 = true,
689 .mm2 = true,
690 .mm3 = true,
691 .mm4 = true,
692 .mm5 = true,
693 .mm6 = true,
694 .mm7 = true,
695 .zmm0 = true,
696 .zmm1 = true,
697 .zmm2 = true,
698 .zmm3 = true,
699 .zmm4 = true,
700 .zmm5 = true,
701 .zmm6 = true,
702 .zmm7 = true,
703 .zmm8 = true,
704 .zmm9 = true,
705 .zmm10 = true,
706 .zmm11 = true,
707 .zmm12 = true,
708 .zmm13 = true,
709 .zmm14 = true,
710 .zmm15 = true,
711 .zmm16 = true,
712 .zmm17 = true,
713 .zmm18 = true,
714 .zmm19 = true,
715 .zmm20 = true,
716 .zmm21 = true,
717 .zmm22 = true,
718 .zmm23 = true,
719 .zmm24 = true,
720 .zmm25 = true,
721 .zmm26 = true,
722 .zmm27 = true,
723 .zmm28 = true,
724 .zmm29 = true,
725 .zmm30 = true,
726 .zmm31 = true,
727 .fpsr = true,
728 .fpcr = true,
729 .mxcsr = true,
730 .rflags = true,
731 .dirflag = true,
732 .memory = true,
733 }),
734 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
735 });
736}
737
738fn mainIdleEntry() callconv(.naked) void {
739 switch (builtin.cpu.arch) {
740 .x86_64 => asm volatile (
741 \\ movq (%%rsp), %%rdi
742 \\ jmp %[mainIdle:P]
743 :
744 : [mainIdle] "X" (&mainIdle),
745 ),
746 .aarch64 => asm volatile (
747 \\ ldr x0, [sp, #-8]
748 \\ b %[mainIdle]
749 :
750 : [mainIdle] "X" (&mainIdle),
751 ),
752 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
753 }
754}
755
756fn fiberEntry() callconv(.naked) void {
757 switch (builtin.cpu.arch) {
758 .x86_64 => asm volatile (
759 \\ leaq 8(%%rsp), %%rdi
760 \\ jmp %[AsyncClosure_call:P]
761 :
762 : [AsyncClosure_call] "X" (&AsyncClosure.call),
763 ),
764 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
765 }
766}
767
768const AsyncClosure = struct {
769 event_loop: *Kqueue,
770 fiber: *Fiber,
771 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
772 result_align: Alignment,
773 already_awaited: bool,
774
775 fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 {
776 return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure));
777 }
778
779 fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
780 message.handle(closure.event_loop);
781 const fiber = closure.fiber;
782 std.log.debug("{*} performing async", .{fiber});
783 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
784 const awaiter = @atomicRmw(?*Fiber, &fiber.awaiter, .Xchg, Fiber.finished, .acq_rel);
785 const ready_awaiter = r: {
786 const a = awaiter orelse break :r null;
787 if (@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .acq_rel)) break :r null;
788 break :r a;
789 };
790 closure.event_loop.yield(ready_awaiter, .nothing);
791 unreachable; // switched to dead fiber
792 }
793
794 fn fromFiber(fiber: *Fiber) *AsyncClosure {
795 return @ptrFromInt(Fiber.max_context_align.max(.of(AsyncClosure)).backward(
796 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
797 ) - @sizeOf(AsyncClosure));
798 }
799};
800
29pub fn io(k: *Kqueue) Io {801pub fn io(k: *Kqueue) Io {
30 return .{802 return .{
31 .userdata = k,803 .userdata = k,
...@@ -229,11 +1001,33 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut...@@ -229,11 +1001,33 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
2291001
230fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {1002fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
231 const k: *Kqueue = @ptrCast(@alignCast(userdata));1003 const k: *Kqueue = @ptrCast(@alignCast(userdata));
232 _ = k;1004 k.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
233 _ = cond;1005 const thread = Thread.current();
234 _ = mutex;1006 const fiber = thread.currentFiber();
235 @panic("TODO");1007 const cond_impl = fiber.resultPointer(Condition);
1008 try mutex.lock(k.io());
1009 switch (cond_impl.event) {
1010 .queued => {},
1011 .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) {
1012 .one => if (@cmpxchgStrong(
1013 ?*Fiber,
1014 @as(*?*Fiber, @ptrCast(&cond.state)),
1015 null,
1016 next_fiber,
1017 .release,
1018 .acquire,
1019 )) |old_fiber| {
1020 const old_cond_impl = old_fiber.?.resultPointer(Condition);
1021 assert(old_cond_impl.tail.queue_next == null);
1022 old_cond_impl.tail.queue_next = next_fiber;
1023 old_cond_impl.tail = cond_impl.tail;
1024 },
1025 .all => k.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }),
1026 },
1027 }
1028 fiber.queue_next = null;
236}1029}
1030
237fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {1031fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {
238 const k: *Kqueue = @ptrCast(@alignCast(userdata));1032 const k: *Kqueue = @ptrCast(@alignCast(userdata));
239 _ = k;1033 _ = k;
...@@ -243,10 +1037,9 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex:...@@ -243,10 +1037,9 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex:
243}1037}
244fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {1038fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
245 const k: *Kqueue = @ptrCast(@alignCast(userdata));1039 const k: *Kqueue = @ptrCast(@alignCast(userdata));
246 _ = k;1040 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
247 _ = cond;1041 waiting_fiber.resultPointer(Condition).event = .{ .wake = wake };
248 _ = wake;1042 k.yield(waiting_fiber, .reschedule);
249 @panic("TODO");
250}1043}
2511044
252fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {1045fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {
...@@ -426,7 +1219,7 @@ fn netBindIp(...@@ -426,7 +1219,7 @@ fn netBindIp(
426 const k: *Kqueue = @ptrCast(@alignCast(userdata));1219 const k: *Kqueue = @ptrCast(@alignCast(userdata));
427 const family = Io.Threaded.posixAddressFamily(address);1220 const family = Io.Threaded.posixAddressFamily(address);
428 const socket_fd = try openSocketPosix(k, family, options);1221 const socket_fd = try openSocketPosix(k, family, options);
429 errdefer posix.close(socket_fd);1222 errdefer std.posix.close(socket_fd);
430 var storage: Io.Threaded.PosixAddress = undefined;1223 var storage: Io.Threaded.PosixAddress = undefined;
431 var addr_len = Io.Threaded.addressToPosix(address, &storage);1224 var addr_len = Io.Threaded.addressToPosix(address, &storage);
432 try posixBind(k, socket_fd, &storage.any, addr_len);1225 try posixBind(k, socket_fd, &storage.any, addr_len);
...@@ -704,3 +1497,11 @@ fn setSocketOption(k: *Kqueue, fd: posix.fd_t, level: i32, opt_name: u32, option...@@ -704,3 +1497,11 @@ fn setSocketOption(k: *Kqueue, fd: posix.fd_t, level: i32, opt_name: u32, option
704fn checkCancel(k: *Kqueue) error{Canceled}!void {1497fn checkCancel(k: *Kqueue) error{Canceled}!void {
705 if (cancelRequested(k)) return error.Canceled;1498 if (cancelRequested(k)) return error.Canceled;
706}1499}
1500
1501const Condition = struct {
1502 tail: *Fiber,
1503 event: union(enum) {
1504 queued,
1505 wake: Io.Condition.Wake,
1506 },
1507};