| 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,6 +460,7 @@ set(ZIG_STD_FILES |
| 460 | "empty.zig" | 460 | "empty.zig" |
| 461 | "event.zig" | 461 | "event.zig" |
| 462 | "event/channel.zig" | 462 | "event/channel.zig" |
| 463 | "event/future.zig" | ||
| 463 | "event/group.zig" | 464 | "event/group.zig" |
| 464 | "event/lock.zig" | 465 | "event/lock.zig" |
| 465 | "event/locked.zig" | 466 | "event/locked.zig" |
src-self-hosted/module.zig+18-1| ... | @@ -381,6 +381,7 @@ pub const Module = struct { | ... | @@ -381,6 +381,7 @@ pub const Module = struct { |
| 381 | 381 | ||
| 382 | if (is_export) { | 382 | if (is_export) { |
| 383 | try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl); | 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,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 | pub fn link(self: *Module, out_file: ?[]const u8) !void { | 449 | pub fn link(self: *Module, out_file: ?[]const u8) !void { |
| 433 | warn("TODO link"); | 450 | warn("TODO link"); |
| 434 | return error.Todo; | 451 | return error.Todo; |
| ... | @@ -589,7 +606,7 @@ pub const Decl = struct { | ... | @@ -589,7 +606,7 @@ pub const Decl = struct { |
| 589 | // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous | 606 | // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous |
| 590 | pub const Val = union { | 607 | pub const Val = union { |
| 591 | Unresolved: void, | 608 | Unresolved: void, |
| 592 | Ok: *Value.Fn, | 609 | Ok: Value.Fn, |
| 593 | }; | 610 | }; |
| 594 | 611 | ||
| 595 | pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { | 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,14 +12,7 @@ test "compile errors" { |
| 12 | try ctx.init(); | 12 | try ctx.init(); |
| 13 | defer ctx.deinit(); | 13 | defer ctx.deinit(); |
| 14 | 14 | ||
| 15 | try ctx.testCompileError( | 15 | try @import("../test/stage2/compile_errors.zig").addCases(&ctx); |
| 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"); | ||
| 23 | 16 | ||
| 24 | try ctx.run(); | 17 | try ctx.run(); |
| 25 | } | 18 | } |
| ... | @@ -27,7 +20,7 @@ test "compile errors" { | ... | @@ -27,7 +20,7 @@ test "compile errors" { |
| 27 | const file1 = "1.zig"; | 20 | const file1 = "1.zig"; |
| 28 | const allocator = std.heap.c_allocator; | 21 | const allocator = std.heap.c_allocator; |
| 29 | 22 | ||
| 30 | const TestContext = struct { | 23 | pub const TestContext = struct { |
| 31 | loop: std.event.Loop, | 24 | loop: std.event.Loop, |
| 32 | zig_lib_dir: []u8, | 25 | zig_lib_dir: []u8, |
| 33 | zig_cache_dir: []u8, | 26 | zig_cache_dir: []u8, |
std/event.zig+2| ... | @@ -4,6 +4,7 @@ pub const Lock = @import("event/lock.zig").Lock; | ... | @@ -4,6 +4,7 @@ pub const Lock = @import("event/lock.zig").Lock; |
| 4 | pub const tcp = @import("event/tcp.zig"); | 4 | pub const tcp = @import("event/tcp.zig"); |
| 5 | pub const Channel = @import("event/channel.zig").Channel; | 5 | pub const Channel = @import("event/channel.zig").Channel; |
| 6 | pub const Group = @import("event/group.zig").Group; | 6 | pub const Group = @import("event/group.zig").Group; |
| 7 | pub const Future = @import("event/future.zig").Group; | ||
| 7 | 8 | ||
| 8 | test "import event tests" { | 9 | test "import event tests" { |
| 9 | _ = @import("event/locked.zig"); | 10 | _ = @import("event/locked.zig"); |
| ... | @@ -12,4 +13,5 @@ test "import event tests" { | ... | @@ -12,4 +13,5 @@ test "import event tests" { |
| 12 | _ = @import("event/tcp.zig"); | 13 | _ = @import("event/tcp.zig"); |
| 13 | _ = @import("event/channel.zig"); | 14 | _ = @import("event/channel.zig"); |
| 14 | _ = @import("event/group.zig"); | 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,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 | /// Must be called when not locked. Not thread safe. | 85 | /// Must be called when not locked. Not thread safe. |
| 77 | /// All calls to acquire() and release() must complete before calling deinit(). | 86 | /// All calls to acquire() and release() must complete before calling deinit(). |
| 78 | pub fn deinit(self: *Lock) void { | 87 | pub fn deinit(self: *Lock) void { |
| ... | @@ -81,7 +90,7 @@ pub const Lock = struct { | ... | @@ -81,7 +90,7 @@ pub const Lock = struct { |
| 81 | } | 90 | } |
| 82 | 91 | ||
| 83 | pub async fn acquire(self: *Lock) Held { | 92 | pub async fn acquire(self: *Lock) Held { |
| 84 | s: suspend |handle| { | 93 | suspend |handle| { |
| 85 | // TODO explicitly put this memory in the coroutine frame #1194 | 94 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 86 | var my_tick_node = Loop.NextTickNode{ | 95 | var my_tick_node = Loop.NextTickNode{ |
| 87 | .data = handle, | 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 | } | ||