| author | |
| committer | |
| log | 9bdcd2a495d4189d6536d43f1294dffb38daa9a5 |
| tree | 95072bfc6959b2b7efd26381492934855b8c0fbf |
| parent | 5954c94d2079c9b378ae14bb4c9c24aed719beec |
This is like a promise, but it's for multiple getters, and
uses an event loop.7 files changed, 132 insertions(+), 11 deletions(-)
CMakeLists.txt+1| ... | ... | @@ -460,6 +460,7 @@ set(ZIG_STD_FILES |
| 460 | 460 | "empty.zig" |
| 461 | 461 | "event.zig" |
| 462 | 462 | "event/channel.zig" |
| 463 | "event/future.zig" | |
| 463 | 464 | "event/group.zig" |
| 464 | 465 | "event/lock.zig" |
| 465 | 466 | "event/locked.zig" |
src-self-hosted/module.zig+18-1| ... | ... | @@ -381,6 +381,7 @@ pub const Module = struct { |
| 381 | 381 | |
| 382 | 382 | if (is_export) { |
| 383 | 383 | try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl); |
| 384 | try self.build_group.call(generateDecl, self, parsed_file, decl); | |
| 384 | 385 | } |
| 385 | 386 | } |
| 386 | 387 | |
| ... | ... | @@ -429,6 +430,22 @@ pub const Module = struct { |
| 429 | 430 | } |
| 430 | 431 | } |
| 431 | 432 | |
| 433 | /// This declaration has been blessed as going into the final code generation. | |
| 434 | async fn generateDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) void { | |
| 435 | switch (decl.id) { | |
| 436 | Decl.Id.Var => @panic("TODO"), | |
| 437 | Decl.Id.Fn => { | |
| 438 | const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); | |
| 439 | return await (async self.generateDeclFn(parsed_file, fn_decl) catch unreachable); | |
| 440 | }, | |
| 441 | Decl.Id.CompTime => @panic("TODO"), | |
| 442 | } | |
| 443 | } | |
| 444 | ||
| 445 | async fn generateDeclFn(self: *Module, parsed_file: *ParsedFile, fn_decl: *Decl.Fn) void { | |
| 446 | fn_decl.value = Decl.Fn.Val{ .Ok = Value.Fn{} }; | |
| 447 | } | |
| 448 | ||
| 432 | 449 | pub fn link(self: *Module, out_file: ?[]const u8) !void { |
| 433 | 450 | warn("TODO link"); |
| 434 | 451 | return error.Todo; |
| ... | ... | @@ -589,7 +606,7 @@ pub const Decl = struct { |
| 589 | 606 | // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous |
| 590 | 607 | pub const Val = union { |
| 591 | 608 | Unresolved: void, |
| 592 | Ok: *Value.Fn, | |
| 609 | Ok: Value.Fn, | |
| 593 | 610 | }; |
| 594 | 611 | |
| 595 | 612 | pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { |
src-self-hosted/test.zig+2-9| ... | ... | @@ -12,14 +12,7 @@ test "compile errors" { |
| 12 | 12 | try ctx.init(); |
| 13 | 13 | defer ctx.deinit(); |
| 14 | 14 | |
| 15 | try ctx.testCompileError( | |
| 16 | \\export fn entry() void {} | |
| 17 | \\export fn entry() void {} | |
| 18 | , file1, 2, 8, "exported symbol collision: 'entry'"); | |
| 19 | ||
| 20 | try ctx.testCompileError( | |
| 21 | \\fn() void {} | |
| 22 | , file1, 1, 1, "missing function name"); | |
| 15 | try @import("../test/stage2/compile_errors.zig").addCases(&ctx); | |
| 23 | 16 | |
| 24 | 17 | try ctx.run(); |
| 25 | 18 | } |
| ... | ... | @@ -27,7 +20,7 @@ test "compile errors" { |
| 27 | 20 | const file1 = "1.zig"; |
| 28 | 21 | const allocator = std.heap.c_allocator; |
| 29 | 22 | |
| 30 | const TestContext = struct { | |
| 23 | pub const TestContext = struct { | |
| 31 | 24 | loop: std.event.Loop, |
| 32 | 25 | zig_lib_dir: []u8, |
| 33 | 26 | zig_cache_dir: []u8, |
std/event.zig+2| ... | ... | @@ -4,6 +4,7 @@ pub const Lock = @import("event/lock.zig").Lock; |
| 4 | 4 | pub const tcp = @import("event/tcp.zig"); |
| 5 | 5 | pub const Channel = @import("event/channel.zig").Channel; |
| 6 | 6 | pub const Group = @import("event/group.zig").Group; |
| 7 | pub const Future = @import("event/future.zig").Group; | |
| 7 | 8 | |
| 8 | 9 | test "import event tests" { |
| 9 | 10 | _ = @import("event/locked.zig"); |
| ... | ... | @@ -12,4 +13,5 @@ test "import event tests" { |
| 12 | 13 | _ = @import("event/tcp.zig"); |
| 13 | 14 | _ = @import("event/channel.zig"); |
| 14 | 15 | _ = @import("event/group.zig"); |
| 16 | _ = @import("event/future.zig"); | |
| 15 | 17 | } |
std/event/future.zig created+87| ... | ... | @@ -0,0 +1,87 @@ |
| 1 | const std = @import("../index.zig"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const builtin = @import("builtin"); | |
| 4 | const AtomicRmwOp = builtin.AtomicRmwOp; | |
| 5 | const AtomicOrder = builtin.AtomicOrder; | |
| 6 | const Lock = std.event.Lock; | |
| 7 | const Loop = std.event.Loop; | |
| 8 | ||
| 9 | /// This is a value that starts out unavailable, until a value is put(). | |
| 10 | /// While it is unavailable, coroutines suspend when they try to get() it, | |
| 11 | /// and then are resumed when the value is put(). | |
| 12 | /// At this point the value remains forever available, and another put() is not allowed. | |
| 13 | pub fn Future(comptime T: type) type { | |
| 14 | return struct { | |
| 15 | lock: Lock, | |
| 16 | data: T, | |
| 17 | available: u8, // TODO make this a bool | |
| 18 | ||
| 19 | const Self = this; | |
| 20 | const Queue = std.atomic.QueueMpsc(promise); | |
| 21 | ||
| 22 | pub fn init(loop: *Loop) Self { | |
| 23 | return Self{ | |
| 24 | .lock = Lock.initLocked(loop), | |
| 25 | .available = 0, | |
| 26 | .data = undefined, | |
| 27 | }; | |
| 28 | } | |
| 29 | ||
| 30 | /// Obtain the value. If it's not available, wait until it becomes | |
| 31 | /// available. | |
| 32 | /// Thread-safe. | |
| 33 | pub async fn get(self: *Self) T { | |
| 34 | if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) { | |
| 35 | return self.data; | |
| 36 | } | |
| 37 | const held = await (async self.lock.acquire() catch unreachable); | |
| 38 | defer held.release(); | |
| 39 | ||
| 40 | return self.data; | |
| 41 | } | |
| 42 | ||
| 43 | /// Make the data become available. May be called only once. | |
| 44 | pub fn put(self: *Self, value: T) void { | |
| 45 | self.data = value; | |
| 46 | const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 47 | assert(prev == 0); // put() called twice | |
| 48 | Lock.Held.release(Lock.Held{ .lock = &self.lock }); | |
| 49 | } | |
| 50 | }; | |
| 51 | } | |
| 52 | ||
| 53 | test "std.event.Future" { | |
| 54 | var da = std.heap.DirectAllocator.init(); | |
| 55 | defer da.deinit(); | |
| 56 | ||
| 57 | const allocator = &da.allocator; | |
| 58 | ||
| 59 | var loop: Loop = undefined; | |
| 60 | try loop.initMultiThreaded(allocator); | |
| 61 | defer loop.deinit(); | |
| 62 | ||
| 63 | const handle = try async<allocator> testFuture(&loop); | |
| 64 | defer cancel handle; | |
| 65 | ||
| 66 | loop.run(); | |
| 67 | } | |
| 68 | ||
| 69 | async fn testFuture(loop: *Loop) void { | |
| 70 | var future = Future(i32).init(loop); | |
| 71 | ||
| 72 | const a = async waitOnFuture(&future) catch @panic("memory"); | |
| 73 | const b = async waitOnFuture(&future) catch @panic("memory"); | |
| 74 | const c = async resolveFuture(&future) catch @panic("memory"); | |
| 75 | ||
| 76 | const result = (await a) + (await b); | |
| 77 | cancel c; | |
| 78 | assert(result == 12); | |
| 79 | } | |
| 80 | ||
| 81 | async fn waitOnFuture(future: *Future(i32)) i32 { | |
| 82 | return await (async future.get() catch @panic("memory")); | |
| 83 | } | |
| 84 | ||
| 85 | async fn resolveFuture(future: *Future(i32)) void { | |
| 86 | future.put(6); | |
| 87 | } |
std/event/lock.zig+10-1| ... | ... | @@ -73,6 +73,15 @@ pub const Lock = struct { |
| 73 | 73 | }; |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | pub fn initLocked(loop: *Loop) Lock { | |
| 77 | return Lock{ | |
| 78 | .loop = loop, | |
| 79 | .shared_bit = 1, | |
| 80 | .queue = Queue.init(), | |
| 81 | .queue_empty_bit = 1, | |
| 82 | }; | |
| 83 | } | |
| 84 | ||
| 76 | 85 | /// Must be called when not locked. Not thread safe. |
| 77 | 86 | /// All calls to acquire() and release() must complete before calling deinit(). |
| 78 | 87 | pub fn deinit(self: *Lock) void { |
| ... | ... | @@ -81,7 +90,7 @@ pub const Lock = struct { |
| 81 | 90 | } |
| 82 | 91 | |
| 83 | 92 | pub async fn acquire(self: *Lock) Held { |
| 84 | s: suspend |handle| { | |
| 93 | suspend |handle| { | |
| 85 | 94 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 86 | 95 | var my_tick_node = Loop.NextTickNode{ |
| 87 | 96 | .data = handle, |
test/stage2/compile_errors.zig created+12| ... | ... | @@ -0,0 +1,12 @@ |
| 1 | const TestContext = @import("../../src-self-hosted/test.zig").TestContext; | |
| 2 | ||
| 3 | pub fn addCases(ctx: *TestContext) !void { | |
| 4 | try ctx.testCompileError( | |
| 5 | \\export fn entry() void {} | |
| 6 | \\export fn entry() void {} | |
| 7 | , "1.zig", 2, 8, "exported symbol collision: 'entry'"); | |
| 8 | ||
| 9 | try ctx.testCompileError( | |
| 10 | \\fn() void {} | |
| 11 | , "1.zig", 1, 1, "missing function name"); | |
| 12 | } |