Skip to content

Instantly share code, notes, and snippets.

@andrewrk
Last active October 10, 2025 02:12
Show Gist options
  • Save andrewrk/1ad9d705ce6046fca76b4cb1220b3c53 to your computer and use it in GitHub Desktop.
Save andrewrk/1ad9d705ce6046fca76b4cb1220b3c53 to your computer and use it in GitHub Desktop.

Revisions

  1. andrewrk revised this gist Jul 10, 2025. 1 changed file with 18 additions and 18 deletions.
    36 changes: 18 additions & 18 deletions example.zig
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ pub fn main() !void {
    const gpa = debug_allocator.allocator();
    //const gpa = std.heap.smp_allocator;

    var thread_pool: std.Thread.Pool = undefined;
    var thread_pool: std.Io.ThreadPool = undefined;
    try thread_pool.init(.{
    .allocator = gpa,
    .n_jobs = std.Thread.getCpuCount() catch 1,
    @@ -23,29 +23,29 @@ pub fn main() !void {

    //const io = event_loop.io();

    var first_half = io.@"async"(calcSum, .{ 0, 100 });
    var second_half = io.@"async"(calcSum, .{ 100, 200 });
    var first_half = io.async(calcSum, .{ 0, 100 });
    var second_half = io.async(calcSum, .{ 100, 200 });

    var rot13 = io.@"async"(processTextFile, .{io});
    var rot13 = io.async(processTextFile, .{io});

    var select_example = io.@"async"(selectExample, .{ io, Io.Dir.cwd() });
    var select_example = io.async(selectExample, .{ io, Io.Dir.cwd() });
    defer select_example.cancel(io) catch {};

    var queue: Io.Queue(i32) = .init(&.{});
    var producer = io.@"async"(producerRun, .{ io, &queue });
    var producer = io.async(producerRun, .{ io, &queue });
    defer producer.cancel(io) catch {};
    var consumer = io.@"async"(consumerRun, .{ io, &queue });
    var consumer = io.async(consumerRun, .{ io, &queue });
    defer _ = consumer.cancel(io);

    const total = first_half.@"await"(io) + second_half.@"await"(io);
    const total = first_half.await(io) + second_half.await(io);
    std.log.info("total: {d}", .{total});

    try rot13.@"await"(io);
    try rot13.await(io);

    const consumer_sum = consumer.@"await"(io);
    const consumer_sum = consumer.await(io);
    std.log.info("consumer sum = {d}", .{consumer_sum});

    try select_example.@"await"(io);
    try select_example.await(io);
    }

    fn producerRun(io: Io, queue: *Io.Queue(i32)) !void {
    @@ -74,29 +74,29 @@ fn calcSum(start: usize, end: usize) usize {
    }

    fn processTextFile(io: Io) !void {
    var future_in_file = io.@"async"(Io.Dir.openFile, .{ .cwd(), io, "example.txt", .{} });
    var future_in_file = io.async(Io.Dir.openFile, .{ .cwd(), io, "example.txt", .{} });
    defer if (future_in_file.cancel(io)) |f| f.close(io) else |_| {};

    var future_out_file = io.@"async"(Io.Dir.createFile, .{ .cwd(), io, "output.txt", .{} });
    var future_out_file = io.async(Io.Dir.createFile, .{ .cwd(), io, "output.txt", .{} });
    defer if (future_out_file.cancel(io)) |f| f.close(io) else |_| {};

    const in_file = try future_in_file.@"await"(io);
    const in_file = try future_in_file.await(io);
    var buffer: [5000]u8 = undefined;
    const n = try in_file.readAll(io, &buffer);

    const contents = buffer[0..n];
    for (contents) |*elem| elem.* = elem.* +% 1;

    const out_file = try future_out_file.@"await"(io);
    const out_file = try future_out_file.await(io);
    try out_file.writeAll(io, contents);
    }

    fn selectExample(io: Io, dir: Io.Dir) !void {
    var a_future = io.@"async"(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "a.txt", .data = "a contents" } });
    var a_future = io.async(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "a.txt", .data = "a contents" } });
    defer _ = a_future.cancel(io) catch {};
    var b_future = io.@"async"(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "b.txt", .data = "b contents" } });
    var b_future = io.async(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "b.txt", .data = "b contents" } });
    defer _ = b_future.cancel(io) catch {};
    var timeout = io.@"async"(Io.sleepDuration, .{ io, .ms(100) });
    var timeout = io.async(Io.sleepDuration, .{ io, .ms(100) });
    defer timeout.cancel(io) catch {};

    switch (io.select(.{
  2. andrewrk revised this gist Apr 4, 2025. 1 changed file with 41 additions and 13 deletions.
    54 changes: 41 additions & 13 deletions example.zig
    Original file line number Diff line number Diff line change
    @@ -28,6 +28,9 @@ pub fn main() !void {

    var rot13 = io.@"async"(processTextFile, .{io});

    var select_example = io.@"async"(selectExample, .{ io, Io.Dir.cwd() });
    defer select_example.cancel(io) catch {};

    var queue: Io.Queue(i32) = .init(&.{});
    var producer = io.@"async"(producerRun, .{ io, &queue });
    defer producer.cancel(io) catch {};
    @@ -41,19 +44,21 @@ pub fn main() !void {

    const consumer_sum = consumer.@"await"(io);
    std.log.info("consumer sum = {d}", .{consumer_sum});

    try select_example.@"await"(io);
    }

    fn producerRun(io: Io, queue: *Io.Queue(i32)) !void {
    for (0..100) |i| {
    queue.putOne(io, @intCast(i * 2 + 1));
    try queue.putOne(io, @intCast(i * 2 + 1));
    }
    queue.putOne(io, 999);
    try queue.putOne(io, 999);
    }

    fn consumerRun(io: Io, queue: *Io.Queue(i32)) i32 {
    var sum: i32 = 0;
    while (true) {
    const n = queue.getOne(io);
    const n = queue.getOne(io) catch return undefined;
    if (n == 999) return sum;
    sum += n;
    }
    @@ -69,23 +74,46 @@ fn calcSum(start: usize, end: usize) usize {
    }

    fn processTextFile(io: Io) !void {
    var future_in_file = io.@"async"(Io.openFile, .{
    io, std.fs.cwd(), "example.txt", @as(Io.OpenFlags, .{}),
    });
    defer if (future_in_file.cancel(io)) |f| f.close() else |_| {};
    var future_in_file = io.@"async"(Io.Dir.openFile, .{ .cwd(), io, "example.txt", .{} });
    defer if (future_in_file.cancel(io)) |f| f.close(io) else |_| {};

    var future_out_file = io.@"async"(Io.createFile, .{
    io, std.fs.cwd(), "output.txt", @as(Io.CreateFlags, .{}),
    });
    defer if (future_out_file.cancel(io)) |f| f.close() else |_| {};
    var future_out_file = io.@"async"(Io.Dir.createFile, .{ .cwd(), io, "output.txt", .{} });
    defer if (future_out_file.cancel(io)) |f| f.close(io) else |_| {};

    const in_file = try future_in_file.@"await"(io);
    var buffer: [5000]u8 = undefined;
    const n = try io.readAll(in_file, &buffer);
    const n = try in_file.readAll(io, &buffer);

    const contents = buffer[0..n];
    for (contents) |*elem| elem.* = elem.* +% 1;

    const out_file = try future_out_file.@"await"(io);
    try io.writeAll(out_file, contents);
    try out_file.writeAll(io, contents);
    }

    fn selectExample(io: Io, dir: Io.Dir) !void {
    var a_future = io.@"async"(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "a.txt", .data = "a contents" } });
    defer _ = a_future.cancel(io) catch {};
    var b_future = io.@"async"(Io.Dir.writeFile, .{ dir, io, .{ .sub_path = "b.txt", .data = "b contents" } });
    defer _ = b_future.cancel(io) catch {};
    var timeout = io.@"async"(Io.sleepDuration, .{ io, .ms(100) });
    defer timeout.cancel(io) catch {};

    switch (io.select(.{
    .a_future = &a_future,
    .b_future = &b_future,
    .timeout = &timeout,
    })) {
    .a_future => |res| {
    std.log.info("a future won the race", .{});
    try res;
    },
    .b_future => |res| {
    std.log.info("b future won the race", .{});
    try res;
    },
    .timeout => {
    return error.Timeout;
    },
    }
    }
  3. andrewrk revised this gist Mar 31, 2025. 1 changed file with 25 additions and 0 deletions.
    25 changes: 25 additions & 0 deletions example.zig
    Original file line number Diff line number Diff line change
    @@ -28,10 +28,35 @@ pub fn main() !void {

    var rot13 = io.@"async"(processTextFile, .{io});

    var queue: Io.Queue(i32) = .init(&.{});
    var producer = io.@"async"(producerRun, .{ io, &queue });
    defer producer.cancel(io) catch {};
    var consumer = io.@"async"(consumerRun, .{ io, &queue });
    defer _ = consumer.cancel(io);

    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});

    try rot13.@"await"(io);

    const consumer_sum = consumer.@"await"(io);
    std.log.info("consumer sum = {d}", .{consumer_sum});
    }

    fn producerRun(io: Io, queue: *Io.Queue(i32)) !void {
    for (0..100) |i| {
    queue.putOne(io, @intCast(i * 2 + 1));
    }
    queue.putOne(io, 999);
    }

    fn consumerRun(io: Io, queue: *Io.Queue(i32)) i32 {
    var sum: i32 = 0;
    while (true) {
    const n = queue.getOne(io);
    if (n == 999) return sum;
    sum += n;
    }
    }

    fn calcSum(start: usize, end: usize) usize {
  4. andrewrk revised this gist Mar 30, 2025. 1 changed file with 24 additions and 18 deletions.
    42 changes: 24 additions & 18 deletions example.zig
    Original file line number Diff line number Diff line change
    @@ -8,20 +8,20 @@ pub fn main() !void {
    const gpa = debug_allocator.allocator();
    //const gpa = std.heap.smp_allocator;

    //var thread_pool: std.Thread.Pool = undefined;
    //try thread_pool.init(.{
    // .allocator = gpa,
    // .n_jobs = std.Thread.getCpuCount() catch 1,
    //});
    //defer thread_pool.deinit();
    var thread_pool: std.Thread.Pool = undefined;
    try thread_pool.init(.{
    .allocator = gpa,
    .n_jobs = std.Thread.getCpuCount() catch 1,
    });
    defer thread_pool.deinit();

    //const io = thread_pool.io();
    const io = thread_pool.io();

    var event_loop: std.Io.EventLoop = undefined;
    try event_loop.init(gpa);
    defer event_loop.deinit();
    //var event_loop: std.Io.EventLoop = undefined;
    //try event_loop.init(gpa);
    //defer event_loop.deinit();

    const io = event_loop.io();
    //const io = event_loop.io();

    var first_half = io.@"async"(calcSum, .{ 0, 100 });
    var second_half = io.@"async"(calcSum, .{ 100, 200 });
    @@ -44,17 +44,23 @@ fn calcSum(start: usize, end: usize) usize {
    }

    fn processTextFile(io: Io) !void {
    const f = try io.openFile(std.fs.cwd(), "example.txt", .{});
    defer io.closeFile(f);
    var future_in_file = io.@"async"(Io.openFile, .{
    io, std.fs.cwd(), "example.txt", @as(Io.OpenFlags, .{}),
    });
    defer if (future_in_file.cancel(io)) |f| f.close() else |_| {};

    var future_out_file = io.@"async"(Io.createFile, .{
    io, std.fs.cwd(), "output.txt", @as(Io.CreateFlags, .{}),
    });
    defer if (future_out_file.cancel(io)) |f| f.close() else |_| {};

    const in_file = try future_in_file.@"await"(io);
    var buffer: [5000]u8 = undefined;
    const n = try io.readAll(f, &buffer);
    const contents = buffer[0..n];
    const n = try io.readAll(in_file, &buffer);

    const contents = buffer[0..n];
    for (contents) |*elem| elem.* = elem.* +% 1;

    const out_file = try io.createFile(std.fs.cwd(), "output.txt", .{});
    defer io.closeFile(out_file);

    const out_file = try future_out_file.@"await"(io);
    try io.writeAll(out_file, contents);
    }
  5. andrewrk renamed this gist Mar 30, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. andrewrk revised this gist Mar 29, 2025. 1 changed file with 25 additions and 44 deletions.
    69 changes: 25 additions & 44 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -23,57 +23,38 @@ pub fn main() !void {

    const io = event_loop.io();

    var first_half = io.@"async"(struct {
    begin: usize,
    end: usize,
    var first_half = io.@"async"(calcSum, .{ 0, 100 });
    var second_half = io.@"async"(calcSum, .{ 100, 200 });

    pub fn start(s: @This()) usize {
    var sum: usize = 0;
    for (s.begin..s.end) |i| {
    sum += i;
    }
    std.log.info("first half = {d}", .{sum});
    return sum;
    }
    }, .{
    .begin = 0,
    .end = 100,
    });
    var rot13 = io.@"async"(processTextFile, .{io});

    var second_half = io.@"async"(struct {
    pub fn start(s: @This()) usize {
    _ = s;
    var sum: usize = 0;
    for (100..200) |i| {
    sum += i;
    }
    std.log.info("second half = {d}", .{sum});
    return sum;
    }
    }, .{});

    var rot13 = io.@"async"(struct {
    io: Io,
    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});

    pub fn start(s: @This()) !void {
    const f = try s.io.openFile(std.fs.cwd(), "example.txt", .{});
    defer s.io.closeFile(f);
    try rot13.@"await"(io);
    }

    var buffer: [5000]u8 = undefined;
    const n = try s.io.readAll(f, &buffer);
    const contents = buffer[0..n];
    fn calcSum(start: usize, end: usize) usize {
    var sum: usize = 0;
    for (start..end) |i| {
    sum += i;
    }
    std.log.debug("calcSum returning {d}", .{sum});
    return sum;
    }

    for (contents) |*elem| elem.* = elem.* +% 1;
    fn processTextFile(io: Io) !void {
    const f = try io.openFile(std.fs.cwd(), "example.txt", .{});
    defer io.closeFile(f);

    const out_file = try s.io.createFile(std.fs.cwd(), "output.txt", .{});
    defer s.io.closeFile(out_file);
    var buffer: [5000]u8 = undefined;
    const n = try io.readAll(f, &buffer);
    const contents = buffer[0..n];

    try s.io.writeAll(out_file, contents);
    }
    }, .{ .io = io });
    for (contents) |*elem| elem.* = elem.* +% 1;

    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});
    const out_file = try io.createFile(std.fs.cwd(), "output.txt", .{});
    defer io.closeFile(out_file);

    try rot13.@"await"(io);
    try io.writeAll(out_file, contents);
    }
  7. andrewrk revised this gist Mar 29, 2025. 1 changed file with 33 additions and 222 deletions.
    255 changes: 33 additions & 222 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -6,6 +6,7 @@ const Io = std.Io;
    pub fn main() !void {
    var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
    const gpa = debug_allocator.allocator();
    //const gpa = std.heap.smp_allocator;

    //var thread_pool: std.Thread.Pool = undefined;
    //try thread_pool.init(.{
    @@ -14,38 +15,32 @@ pub fn main() !void {
    //});
    //defer thread_pool.deinit();

    //const io: Io = .{
    // .userdata = &thread_pool,
    // .vtable = &.{
    // .@"async" = std.Thread.Pool.@"async",
    // .@"await" = std.Thread.Pool.@"await",
    // },
    //};
    //const io = thread_pool.io();

    var event_loop: EventLoop = undefined;
    event_loop.init(gpa);
    var event_loop: std.Io.EventLoop = undefined;
    try event_loop.init(gpa);
    defer event_loop.deinit();

    const io: Io = .{
    .userdata = &event_loop,
    .vtable = &.{
    .@"async" = green_async,
    .@"await" = green_await,
    },
    };
    const io = event_loop.io();

    var first_half = io.@"async"(struct {
    begin: usize,
    end: usize,

    var first_half = io.@"async"(@as(struct {
    pub fn start(s: @This()) usize {
    _ = s;
    var sum: usize = 0;
    for (0..100) |i| {
    for (s.begin..s.end) |i| {
    sum += i;
    }
    std.log.info("first half = {d}", .{sum});
    return sum;
    }
    }, .{}));
    }, .{
    .begin = 0,
    .end = 100,
    });

    var second_half = io.@"async"(@as(struct {
    var second_half = io.@"async"(struct {
    pub fn start(s: @This()) usize {
    _ = s;
    var sum: usize = 0;
    @@ -55,214 +50,30 @@ pub fn main() !void {
    std.log.info("second half = {d}", .{sum});
    return sum;
    }
    }, .{}));

    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});
    }

    const EventLoop = struct {
    gpa: Allocator,
    queue: std.DoublyLinkedList(void),
    free: std.DoublyLinkedList(void),
    main_fiber_buffer: [@sizeOf(Fiber) + max_result_len]u8 align(@alignOf(Fiber)),

    threadlocal var my_fiber: *Fiber = undefined;
    }, .{});

    const max_result_len = 64;
    const min_stack_size = 4 * 1024 * 1024;
    var rot13 = io.@"async"(struct {
    io: Io,

    const Fiber = struct {
    regs: Regs,
    awaiter: ?*Fiber,
    queue_node: std.DoublyLinkedList(void).Node,
    pub fn start(s: @This()) !void {
    const f = try s.io.openFile(std.fs.cwd(), "example.txt", .{});
    defer s.io.closeFile(f);

    const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber)));
    var buffer: [5000]u8 = undefined;
    const n = try s.io.readAll(f, &buffer);
    const contents = buffer[0..n];

    fn resultPointer(f: *Fiber) [*]u8 {
    const base: [*]u8 = @ptrCast(f);
    return base + @sizeOf(Fiber);
    }

    fn stackEndPointer(f: *Fiber) [*]u8 {
    const base: [*]u8 = @ptrCast(f);
    return base + std.mem.alignForward(
    usize,
    @sizeOf(Fiber) + max_result_len + min_stack_size,
    std.heap.page_size_max,
    );
    }
    };

    fn init(el: *EventLoop, gpa: Allocator) void {
    el.* = .{
    .gpa = gpa,
    .queue = .{},
    .free = .{},
    .main_fiber_buffer = undefined,
    };
    my_fiber = @ptrCast(&el.main_fiber_buffer);
    }
    for (contents) |*elem| elem.* = elem.* +% 1;

    fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {
    assert(result_len <= max_result_len);
    const free_node = el.free.pop() orelse {
    const n = std.mem.alignForward(
    usize,
    @sizeOf(Fiber) + max_result_len + min_stack_size,
    std.heap.page_size_max,
    );
    return @alignCast(@ptrCast(try el.gpa.alignedAlloc(u8, @alignOf(Fiber), n)));
    };
    return @fieldParentPtr("queue_node", free_node);
    }
    const out_file = try s.io.createFile(std.fs.cwd(), "output.txt", .{});
    defer s.io.closeFile(out_file);

    fn yield(el: *EventLoop, optional_fiber: ?*Fiber) void {
    if (optional_fiber) |fiber| {
    const old = &my_fiber.regs;
    my_fiber = fiber;
    contextSwitch(old, &fiber.regs);
    return;
    try s.io.writeAll(out_file, contents);
    }
    if (el.queue.pop()) |node| {
    const fiber: *Fiber = @fieldParentPtr("queue_node", node);
    const old = &my_fiber.regs;
    my_fiber = fiber;
    contextSwitch(old, &fiber.regs);
    return;
    }
    @panic("everything is done");
    }

    /// Equivalent to calling `yield` and then giving the fiber back to the event loop.
    fn exit(el: *EventLoop, optional_fiber: ?*Fiber) noreturn {
    yield(el, optional_fiber);
    @panic("TODO recycle the fiber");
    }

    fn schedule(el: *EventLoop, fiber: *Fiber) void {
    el.queue.append(&fiber.queue_node);
    }

    fn myFiber(el: *EventLoop) *Fiber {
    _ = el;
    return my_fiber;
    }
    };
    }, .{ .io = io });

    fn green_async(
    userdata: ?*anyopaque,
    eager_result: []u8,
    context: ?*anyopaque,
    start: *const fn (context: ?*anyopaque, result: *anyopaque) void,
    ) ?*std.Io.AnyFuture {
    const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
    const fiber = event_loop.allocateFiber(eager_result.len) catch {
    start(context, eager_result.ptr);
    return null;
    };
    fiber.awaiter = null;
    fiber.queue_node = .{ .data = {} };

    const closure: *AsyncClosure = @ptrFromInt(std.mem.alignBackward(
    usize,
    @intFromPtr(fiber.stackEndPointer() - @sizeOf(AsyncClosure)),
    @alignOf(AsyncClosure),
    ));
    closure.* = .{
    .event_loop = event_loop,
    .context = context,
    .fiber = fiber,
    .start = start,
    };
    const stack_end_ptr: [*]align(16) usize = @alignCast(@ptrCast(closure));
    (stack_end_ptr - 1)[0] = @intFromPtr(&AsyncClosure.call);
    (stack_end_ptr - 2)[0] = @intFromPtr(closure);
    (stack_end_ptr - 3)[0] = @intFromPtr(&popRet);

    fiber.regs = .{
    .rsp = @intFromPtr(stack_end_ptr - 3),
    .r15 = 0,
    .r14 = 0,
    .r13 = 0,
    .r12 = 0,
    .rbx = 0,
    .rbp = 0,
    };

    event_loop.schedule(fiber);
    return @ptrCast(fiber);
    }

    const AsyncClosure = struct {
    _: void align(16) = {},
    event_loop: *EventLoop,
    context: ?*anyopaque,
    fiber: *EventLoop.Fiber,
    start: *const fn (context: ?*anyopaque, result: *anyopaque) void,

    fn call(closure: *AsyncClosure) callconv(.c) void {
    std.log.debug("wrap called in async", .{});
    closure.start(closure.context, closure.fiber.resultPointer());
    const awaiter = @atomicRmw(?*EventLoop.Fiber, &closure.fiber.awaiter, .Xchg, EventLoop.Fiber.finished, .seq_cst);
    closure.event_loop.exit(awaiter);
    }
    };

    fn green_await(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void {
    const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
    const future_fiber: *EventLoop.Fiber = @alignCast(@ptrCast(any_future));
    const result_src = future_fiber.resultPointer()[0..result.len];
    const my_fiber = event_loop.myFiber();

    const prev = @atomicRmw(?*EventLoop.Fiber, &future_fiber.awaiter, .Xchg, my_fiber, .seq_cst);
    if (prev == EventLoop.Fiber.finished) {
    @memcpy(result, result_src);
    return;
    }
    event_loop.yield(prev);
    // Resumed when the value is available.
    std.log.debug("yield returned in await", .{});
    @memcpy(result, result_src);
    }

    const Regs = extern struct {
    rsp: usize,
    r15: usize,
    r14: usize,
    r13: usize,
    r12: usize,
    rbx: usize,
    rbp: usize,
    };

    const contextSwitch: *const fn (old: *Regs, new: *Regs) callconv(.c) void = @ptrCast(&contextSwitch_naked);

    noinline fn contextSwitch_naked() callconv(.naked) void {
    asm volatile (
    \\movq %%rsp, 0x00(%%rdi)
    \\movq %%r15, 0x08(%%rdi)
    \\movq %%r14, 0x10(%%rdi)
    \\movq %%r13, 0x18(%%rdi)
    \\movq %%r12, 0x20(%%rdi)
    \\movq %%rbx, 0x28(%%rdi)
    \\movq %%rbp, 0x30(%%rdi)
    \\
    \\movq 0x00(%%rsi), %%rsp
    \\movq 0x08(%%rsi), %%r15
    \\movq 0x10(%%rsi), %%r14
    \\movq 0x18(%%rsi), %%r13
    \\movq 0x20(%%rsi), %%r12
    \\movq 0x28(%%rsi), %%rbx
    \\movq 0x30(%%rsi), %%rbp
    \\
    \\ret
    );
    }
    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});

    fn popRet() callconv(.naked) void {
    asm volatile (
    \\pop %%rdi
    \\ret
    );
    try rot13.@"await"(io);
    }
  8. andrewrk created this gist Mar 27, 2025.
    268 changes: 268 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,268 @@
    const std = @import("std");
    const assert = std.debug.assert;
    const Allocator = std.mem.Allocator;
    const Io = std.Io;

    pub fn main() !void {
    var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
    const gpa = debug_allocator.allocator();

    //var thread_pool: std.Thread.Pool = undefined;
    //try thread_pool.init(.{
    // .allocator = gpa,
    // .n_jobs = std.Thread.getCpuCount() catch 1,
    //});
    //defer thread_pool.deinit();

    //const io: Io = .{
    // .userdata = &thread_pool,
    // .vtable = &.{
    // .@"async" = std.Thread.Pool.@"async",
    // .@"await" = std.Thread.Pool.@"await",
    // },
    //};

    var event_loop: EventLoop = undefined;
    event_loop.init(gpa);

    const io: Io = .{
    .userdata = &event_loop,
    .vtable = &.{
    .@"async" = green_async,
    .@"await" = green_await,
    },
    };

    var first_half = io.@"async"(@as(struct {
    pub fn start(s: @This()) usize {
    _ = s;
    var sum: usize = 0;
    for (0..100) |i| {
    sum += i;
    }
    std.log.info("first half = {d}", .{sum});
    return sum;
    }
    }, .{}));

    var second_half = io.@"async"(@as(struct {
    pub fn start(s: @This()) usize {
    _ = s;
    var sum: usize = 0;
    for (100..200) |i| {
    sum += i;
    }
    std.log.info("second half = {d}", .{sum});
    return sum;
    }
    }, .{}));

    const total = first_half.@"await"(io) + second_half.@"await"(io);
    std.log.info("total: {d}", .{total});
    }

    const EventLoop = struct {
    gpa: Allocator,
    queue: std.DoublyLinkedList(void),
    free: std.DoublyLinkedList(void),
    main_fiber_buffer: [@sizeOf(Fiber) + max_result_len]u8 align(@alignOf(Fiber)),

    threadlocal var my_fiber: *Fiber = undefined;

    const max_result_len = 64;
    const min_stack_size = 4 * 1024 * 1024;

    const Fiber = struct {
    regs: Regs,
    awaiter: ?*Fiber,
    queue_node: std.DoublyLinkedList(void).Node,

    const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber)));

    fn resultPointer(f: *Fiber) [*]u8 {
    const base: [*]u8 = @ptrCast(f);
    return base + @sizeOf(Fiber);
    }

    fn stackEndPointer(f: *Fiber) [*]u8 {
    const base: [*]u8 = @ptrCast(f);
    return base + std.mem.alignForward(
    usize,
    @sizeOf(Fiber) + max_result_len + min_stack_size,
    std.heap.page_size_max,
    );
    }
    };

    fn init(el: *EventLoop, gpa: Allocator) void {
    el.* = .{
    .gpa = gpa,
    .queue = .{},
    .free = .{},
    .main_fiber_buffer = undefined,
    };
    my_fiber = @ptrCast(&el.main_fiber_buffer);
    }

    fn allocateFiber(el: *EventLoop, result_len: usize) error{OutOfMemory}!*Fiber {
    assert(result_len <= max_result_len);
    const free_node = el.free.pop() orelse {
    const n = std.mem.alignForward(
    usize,
    @sizeOf(Fiber) + max_result_len + min_stack_size,
    std.heap.page_size_max,
    );
    return @alignCast(@ptrCast(try el.gpa.alignedAlloc(u8, @alignOf(Fiber), n)));
    };
    return @fieldParentPtr("queue_node", free_node);
    }

    fn yield(el: *EventLoop, optional_fiber: ?*Fiber) void {
    if (optional_fiber) |fiber| {
    const old = &my_fiber.regs;
    my_fiber = fiber;
    contextSwitch(old, &fiber.regs);
    return;
    }
    if (el.queue.pop()) |node| {
    const fiber: *Fiber = @fieldParentPtr("queue_node", node);
    const old = &my_fiber.regs;
    my_fiber = fiber;
    contextSwitch(old, &fiber.regs);
    return;
    }
    @panic("everything is done");
    }

    /// Equivalent to calling `yield` and then giving the fiber back to the event loop.
    fn exit(el: *EventLoop, optional_fiber: ?*Fiber) noreturn {
    yield(el, optional_fiber);
    @panic("TODO recycle the fiber");
    }

    fn schedule(el: *EventLoop, fiber: *Fiber) void {
    el.queue.append(&fiber.queue_node);
    }

    fn myFiber(el: *EventLoop) *Fiber {
    _ = el;
    return my_fiber;
    }
    };

    fn green_async(
    userdata: ?*anyopaque,
    eager_result: []u8,
    context: ?*anyopaque,
    start: *const fn (context: ?*anyopaque, result: *anyopaque) void,
    ) ?*std.Io.AnyFuture {
    const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
    const fiber = event_loop.allocateFiber(eager_result.len) catch {
    start(context, eager_result.ptr);
    return null;
    };
    fiber.awaiter = null;
    fiber.queue_node = .{ .data = {} };

    const closure: *AsyncClosure = @ptrFromInt(std.mem.alignBackward(
    usize,
    @intFromPtr(fiber.stackEndPointer() - @sizeOf(AsyncClosure)),
    @alignOf(AsyncClosure),
    ));
    closure.* = .{
    .event_loop = event_loop,
    .context = context,
    .fiber = fiber,
    .start = start,
    };
    const stack_end_ptr: [*]align(16) usize = @alignCast(@ptrCast(closure));
    (stack_end_ptr - 1)[0] = @intFromPtr(&AsyncClosure.call);
    (stack_end_ptr - 2)[0] = @intFromPtr(closure);
    (stack_end_ptr - 3)[0] = @intFromPtr(&popRet);

    fiber.regs = .{
    .rsp = @intFromPtr(stack_end_ptr - 3),
    .r15 = 0,
    .r14 = 0,
    .r13 = 0,
    .r12 = 0,
    .rbx = 0,
    .rbp = 0,
    };

    event_loop.schedule(fiber);
    return @ptrCast(fiber);
    }

    const AsyncClosure = struct {
    _: void align(16) = {},
    event_loop: *EventLoop,
    context: ?*anyopaque,
    fiber: *EventLoop.Fiber,
    start: *const fn (context: ?*anyopaque, result: *anyopaque) void,

    fn call(closure: *AsyncClosure) callconv(.c) void {
    std.log.debug("wrap called in async", .{});
    closure.start(closure.context, closure.fiber.resultPointer());
    const awaiter = @atomicRmw(?*EventLoop.Fiber, &closure.fiber.awaiter, .Xchg, EventLoop.Fiber.finished, .seq_cst);
    closure.event_loop.exit(awaiter);
    }
    };

    fn green_await(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void {
    const event_loop: *EventLoop = @alignCast(@ptrCast(userdata));
    const future_fiber: *EventLoop.Fiber = @alignCast(@ptrCast(any_future));
    const result_src = future_fiber.resultPointer()[0..result.len];
    const my_fiber = event_loop.myFiber();

    const prev = @atomicRmw(?*EventLoop.Fiber, &future_fiber.awaiter, .Xchg, my_fiber, .seq_cst);
    if (prev == EventLoop.Fiber.finished) {
    @memcpy(result, result_src);
    return;
    }
    event_loop.yield(prev);
    // Resumed when the value is available.
    std.log.debug("yield returned in await", .{});
    @memcpy(result, result_src);
    }

    const Regs = extern struct {
    rsp: usize,
    r15: usize,
    r14: usize,
    r13: usize,
    r12: usize,
    rbx: usize,
    rbp: usize,
    };

    const contextSwitch: *const fn (old: *Regs, new: *Regs) callconv(.c) void = @ptrCast(&contextSwitch_naked);

    noinline fn contextSwitch_naked() callconv(.naked) void {
    asm volatile (
    \\movq %%rsp, 0x00(%%rdi)
    \\movq %%r15, 0x08(%%rdi)
    \\movq %%r14, 0x10(%%rdi)
    \\movq %%r13, 0x18(%%rdi)
    \\movq %%r12, 0x20(%%rdi)
    \\movq %%rbx, 0x28(%%rdi)
    \\movq %%rbp, 0x30(%%rdi)
    \\
    \\movq 0x00(%%rsi), %%rsp
    \\movq 0x08(%%rsi), %%r15
    \\movq 0x10(%%rsi), %%r14
    \\movq 0x18(%%rsi), %%r13
    \\movq 0x20(%%rsi), %%r12
    \\movq 0x28(%%rsi), %%rbx
    \\movq 0x30(%%rsi), %%rbp
    \\
    \\ret
    );
    }

    fn popRet() callconv(.naked) void {
    asm volatile (
    \\pop %%rdi
    \\ret
    );
    }