| author | |
| committer | |
| log | 821805aa92898cfdb770b87ac916e45e428621b8 |
| tree | 9fc4468728398b92c5d3c4bdf62fd57434abf9ee |
| parent | e3ae2cfb5243e7255bf4dbcc8a9b7e77a31e9d45 |
17 files changed, 493 insertions(+), 321 deletions(-)
src-self-hosted/compilation.zig+133-91| ... | ... | @@ -717,13 +717,13 @@ pub const Compilation = struct { |
| 717 | 717 | } |
| 718 | 718 | |
| 719 | 719 | async fn buildAsync(self: *Compilation) void { |
| 720 | while (true) { | |
| 721 | // TODO directly awaiting async should guarantee memory allocation elision | |
| 722 | const build_result = await (async self.compileAndLink() catch unreachable); | |
| 720 | var build_result = await (async self.initialCompile() catch unreachable); | |
| 723 | 721 | |
| 722 | while (true) { | |
| 723 | const link_result = if (build_result) self.maybeLink() else |err| err; | |
| 724 | 724 | // this makes a handy error return trace and stack trace in debug mode |
| 725 | 725 | if (std.debug.runtime_safety) { |
| 726 | build_result catch unreachable; | |
| 726 | link_result catch unreachable; | |
| 727 | 727 | } |
| 728 | 728 | |
| 729 | 729 | const compile_errors = blk: { |
| ... | ... | @@ -732,7 +732,7 @@ pub const Compilation = struct { |
| 732 | 732 | break :blk held.value.toOwnedSlice(); |
| 733 | 733 | }; |
| 734 | 734 | |
| 735 | if (build_result) |_| { | |
| 735 | if (link_result) |_| { | |
| 736 | 736 | if (compile_errors.len == 0) { |
| 737 | 737 | await (async self.events.put(Event.Ok) catch unreachable); |
| 738 | 738 | } else { |
| ... | ... | @@ -745,108 +745,158 @@ pub const Compilation = struct { |
| 745 | 745 | await (async self.events.put(Event{ .Error = err }) catch unreachable); |
| 746 | 746 | } |
| 747 | 747 | |
| 748 | // for now we stop after 1 | |
| 749 | return; | |
| 748 | var group = event.Group(BuildError!void).init(self.loop); | |
| 749 | while (self.fs_watch.channel.getOrNull()) |root_scope| { | |
| 750 | try group.call(rebuildFile, self, root_scope); | |
| 751 | } | |
| 752 | build_result = await (async group.wait() catch unreachable); | |
| 750 | 753 | } |
| 751 | 754 | } |
| 752 | 755 | |
| 753 | async fn compileAndLink(self: *Compilation) !void { | |
| 754 | if (self.root_src_path) |root_src_path| { | |
| 755 | // TODO async/await os.path.real | |
| 756 | const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { | |
| 757 | try printError("unable to get real path '{}': {}", root_src_path, err); | |
| 756 | async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) !void { | |
| 757 | const tree_scope = blk: { | |
| 758 | const source_code = (await (async fs.readFile( | |
| 759 | self.loop, | |
| 760 | root_src_real_path, | |
| 761 | max_src_size, | |
| 762 | ) catch unreachable)) catch |err| { | |
| 763 | try printError("unable to open '{}': {}", root_src_real_path, err); | |
| 758 | 764 | return err; |
| 759 | 765 | }; |
| 760 | const root_scope = blk: { | |
| 761 | errdefer self.gpa().free(root_src_real_path); | |
| 766 | errdefer self.gpa().free(source_code); | |
| 762 | 767 | |
| 763 | const source_code = (await (async fs.readFile( | |
| 764 | self.loop, | |
| 765 | root_src_real_path, | |
| 766 | max_src_size, | |
| 767 | ) catch unreachable)) catch |err| { | |
| 768 | try printError("unable to open '{}': {}", root_src_real_path, err); | |
| 769 | return err; | |
| 770 | }; | |
| 771 | errdefer self.gpa().free(source_code); | |
| 768 | const tree = try self.gpa().createOne(ast.Tree); | |
| 769 | tree.* = try std.zig.parse(self.gpa(), source_code); | |
| 770 | errdefer { | |
| 771 | tree.deinit(); | |
| 772 | self.gpa().destroy(tree); | |
| 773 | } | |
| 772 | 774 | |
| 773 | const tree = try self.gpa().createOne(ast.Tree); | |
| 774 | tree.* = try std.zig.parse(self.gpa(), source_code); | |
| 775 | errdefer { | |
| 776 | tree.deinit(); | |
| 777 | self.gpa().destroy(tree); | |
| 778 | } | |
| 775 | break :blk try Scope.AstTree.create(self, tree, root_scope); | |
| 776 | }; | |
| 777 | defer tree_scope.base.deref(self); | |
| 779 | 778 | |
| 780 | break :blk try Scope.Root.create(self, tree, root_src_real_path); | |
| 781 | }; | |
| 782 | defer root_scope.base.deref(self); | |
| 783 | const tree = root_scope.tree; | |
| 779 | var error_it = tree_scope.tree.errors.iterator(0); | |
| 780 | while (error_it.next()) |parse_error| { | |
| 781 | const msg = try Msg.createFromParseErrorAndScope(self, tree_scope, parse_error); | |
| 782 | errdefer msg.destroy(); | |
| 784 | 783 | |
| 785 | var error_it = tree.errors.iterator(0); | |
| 786 | while (error_it.next()) |parse_error| { | |
| 787 | const msg = try Msg.createFromParseErrorAndScope(self, root_scope, parse_error); | |
| 788 | errdefer msg.destroy(); | |
| 784 | try await (async self.addCompileErrorAsync(msg) catch unreachable); | |
| 785 | } | |
| 786 | if (tree_scope.tree.errors.len != 0) { | |
| 787 | return; | |
| 788 | } | |
| 789 | 789 | |
| 790 | try await (async self.addCompileErrorAsync(msg) catch unreachable); | |
| 791 | } | |
| 792 | if (tree.errors.len != 0) { | |
| 793 | return; | |
| 794 | } | |
| 790 | const locked_table = await (async root_scope.decls.table.acquireWrite() catch unreachable); | |
| 791 | defer locked_table.release(); | |
| 795 | 792 | |
| 796 | const decls = try Scope.Decls.create(self, &root_scope.base); | |
| 797 | defer decls.base.deref(self); | |
| 793 | var decl_group = event.Group(BuildError!void).init(self.loop); | |
| 794 | defer decl_group.deinit(); | |
| 798 | 795 | |
| 799 | var decl_group = event.Group(BuildError!void).init(self.loop); | |
| 800 | var decl_group_consumed = false; | |
| 801 | errdefer if (!decl_group_consumed) decl_group.cancelAll(); | |
| 796 | try self.rebuildChangedDecls( | |
| 797 | &decl_group, | |
| 798 | locked_table, | |
| 799 | root_scope.decls, | |
| 800 | &tree_scope.tree.root_node.decls, | |
| 801 | tree_scope, | |
| 802 | ); | |
| 802 | 803 | |
| 803 | var it = tree.root_node.decls.iterator(0); | |
| 804 | while (it.next()) |decl_ptr| { | |
| 805 | const decl = decl_ptr.*; | |
| 806 | switch (decl.id) { | |
| 807 | ast.Node.Id.Comptime => { | |
| 808 | const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); | |
| 804 | try await (async decl_group.wait() catch unreachable); | |
| 805 | } | |
| 809 | 806 | |
| 810 | try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node); | |
| 811 | }, | |
| 812 | ast.Node.Id.VarDecl => @panic("TODO"), | |
| 813 | ast.Node.Id.FnProto => { | |
| 814 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); | |
| 815 | ||
| 816 | const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else { | |
| 817 | try self.addCompileError(root_scope, Span{ | |
| 818 | .first = fn_proto.fn_token, | |
| 819 | .last = fn_proto.fn_token + 1, | |
| 820 | }, "missing function name"); | |
| 821 | continue; | |
| 822 | }; | |
| 807 | async fn rebuildChangedDecls( | |
| 808 | self: *Compilation, | |
| 809 | group: *event.Group(BuildError!void), | |
| 810 | locked_table: *Decl.Table, | |
| 811 | decl_scope: *Scope.Decls, | |
| 812 | ast_decls: &ast.Node.Root.DeclList, | |
| 813 | tree_scope: *Scope.AstTree, | |
| 814 | ) !void { | |
| 815 | var existing_decls = try locked_table.clone(); | |
| 816 | defer existing_decls.deinit(); | |
| 817 | ||
| 818 | var ast_it = ast_decls.iterator(0); | |
| 819 | while (ast_it.next()) |decl_ptr| { | |
| 820 | const decl = decl_ptr.*; | |
| 821 | switch (decl.id) { | |
| 822 | ast.Node.Id.Comptime => { | |
| 823 | const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); | |
| 824 | ||
| 825 | // TODO connect existing comptime decls to updated source files | |
| 823 | 826 | |
| 827 | try self.prelink_group.call(addCompTimeBlock, self, &decl_scope.base, comptime_node); | |
| 828 | }, | |
| 829 | ast.Node.Id.VarDecl => @panic("TODO"), | |
| 830 | ast.Node.Id.FnProto => { | |
| 831 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); | |
| 832 | ||
| 833 | const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else { | |
| 834 | try self.addCompileError(root_scope, Span{ | |
| 835 | .first = fn_proto.fn_token, | |
| 836 | .last = fn_proto.fn_token + 1, | |
| 837 | }, "missing function name"); | |
| 838 | continue; | |
| 839 | }; | |
| 840 | ||
| 841 | if (existing_decls.remove(name)) |entry| { | |
| 842 | // compare new code to existing | |
| 843 | const existing_decl = entry.value; | |
| 844 | // Just compare the old bytes to the new bytes of the top level decl. | |
| 845 | // Even if the AST is technically the same, we want error messages to display | |
| 846 | // from the most recent source. | |
| 847 | @panic("TODO handle decl comparison"); | |
| 848 | // Add the new thing before dereferencing the old thing. This way we don't end | |
| 849 | // up pointlessly re-creating things we end up using in the new thing. | |
| 850 | } else { | |
| 851 | // add new decl | |
| 824 | 852 | const fn_decl = try self.gpa().create(Decl.Fn{ |
| 825 | 853 | .base = Decl{ |
| 826 | 854 | .id = Decl.Id.Fn, |
| 827 | 855 | .name = name, |
| 828 | .visib = parseVisibToken(tree, fn_proto.visib_token), | |
| 856 | .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token), | |
| 829 | 857 | .resolution = event.Future(BuildError!void).init(self.loop), |
| 830 | .parent_scope = &decls.base, | |
| 858 | .parent_scope = &decl_scope.base, | |
| 831 | 859 | }, |
| 832 | 860 | .value = Decl.Fn.Val{ .Unresolved = {} }, |
| 833 | 861 | .fn_proto = fn_proto, |
| 834 | 862 | }); |
| 835 | 863 | errdefer self.gpa().destroy(fn_decl); |
| 836 | 864 | |
| 837 | try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base); | |
| 838 | }, | |
| 839 | ast.Node.Id.TestDecl => @panic("TODO"), | |
| 840 | else => unreachable, | |
| 841 | } | |
| 865 | try group.call(addTopLevelDecl, self, &fn_decl.base, locked_table); | |
| 866 | } | |
| 867 | }, | |
| 868 | ast.Node.Id.TestDecl => @panic("TODO"), | |
| 869 | else => unreachable, | |
| 842 | 870 | } |
| 843 | decl_group_consumed = true; | |
| 844 | try await (async decl_group.wait() catch unreachable); | |
| 871 | } | |
| 872 | ||
| 873 | var existing_decl_it = existing_decls.iterator(); | |
| 874 | while (existing_decl_it.next()) |entry| { | |
| 875 | // this decl was deleted | |
| 876 | const existing_decl = entry.value; | |
| 877 | @panic("TODO handle decl deletion"); | |
| 878 | } | |
| 879 | } | |
| 880 | ||
| 881 | async fn initialCompile(self: *Compilation) !void { | |
| 882 | if (self.root_src_path) |root_src_path| { | |
| 883 | const root_scope = blk: { | |
| 884 | // TODO async/await os.path.real | |
| 885 | const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| { | |
| 886 | try printError("unable to get real path '{}': {}", root_src_path, err); | |
| 887 | return err; | |
| 888 | }; | |
| 889 | errdefer self.gpa().free(root_src_real_path); | |
| 845 | 890 | |
| 846 | // Now other code can rely on the decls scope having a complete list of names. | |
| 847 | decls.name_future.resolve(); | |
| 891 | break :blk try Scope.Root.create(self, root_src_real_path); | |
| 892 | }; | |
| 893 | defer root_scope.base.deref(self); | |
| 894 | ||
| 895 | try self.rebuildFile(root_scope); | |
| 848 | 896 | } |
| 897 | } | |
| 849 | 898 | |
| 899 | async fn maybeLink(self: *Compilation) !void { | |
| 850 | 900 | (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) { |
| 851 | 901 | error.SemanticAnalysisFailed => {}, |
| 852 | 902 | else => return err, |
| ... | ... | @@ -920,28 +970,20 @@ pub const Compilation = struct { |
| 920 | 970 | analyzed_code.destroy(comp.gpa()); |
| 921 | 971 | } |
| 922 | 972 | |
| 923 | async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { | |
| 973 | async fn addTopLevelDecl( | |
| 974 | self: *Compilation, | |
| 975 | decl: *Decl, | |
| 976 | locked_table: *Decl.Table, | |
| 977 | ) !void { | |
| 924 | 978 | const tree = decl.findRootScope().tree; |
| 925 | 979 | const is_export = decl.isExported(tree); |
| 926 | 980 | |
| 927 | var add_to_table_resolved = false; | |
| 928 | const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable; | |
| 929 | errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261 | |
| 930 | ||
| 931 | 981 | if (is_export) { |
| 932 | 982 | try self.prelink_group.call(verifyUniqueSymbol, self, decl); |
| 933 | 983 | try self.prelink_group.call(resolveDecl, self, decl); |
| 934 | 984 | } |
| 935 | 985 | |
| 936 | add_to_table_resolved = true; | |
| 937 | try await add_to_table; | |
| 938 | } | |
| 939 | ||
| 940 | async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void { | |
| 941 | const held = await (async decls.table.acquire() catch unreachable); | |
| 942 | defer held.release(); | |
| 943 | ||
| 944 | if (try held.value.put(decl.name, decl)) |other_decl| { | |
| 986 | if (try locked_table.put(decl.name, decl)) |other_decl| { | |
| 945 | 987 | try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name); |
| 946 | 988 | // TODO note: other definition here |
| 947 | 989 | } |
src-self-hosted/decl.zig+1| ... | ... | @@ -16,6 +16,7 @@ pub const Decl = struct { |
| 16 | 16 | visib: Visib, |
| 17 | 17 | resolution: event.Future(Compilation.BuildError!void), |
| 18 | 18 | parent_scope: *Scope, |
| 19 | tree_scope: *Scope.AstTree, | |
| 19 | 20 | |
| 20 | 21 | pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); |
| 21 | 22 |
src-self-hosted/errmsg.zig+12-12| ... | ... | @@ -49,7 +49,7 @@ pub const Msg = struct { |
| 49 | 49 | }; |
| 50 | 50 | |
| 51 | 51 | const ScopeAndComp = struct { |
| 52 | root_scope: *Scope.Root, | |
| 52 | tree_scope: *Scope.AstTree, | |
| 53 | 53 | compilation: *Compilation, |
| 54 | 54 | }; |
| 55 | 55 | |
| ... | ... | @@ -60,7 +60,7 @@ pub const Msg = struct { |
| 60 | 60 | path_and_tree.allocator.destroy(self); |
| 61 | 61 | }, |
| 62 | 62 | Data.ScopeAndComp => |scope_and_comp| { |
| 63 | scope_and_comp.root_scope.base.deref(scope_and_comp.compilation); | |
| 63 | scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation); | |
| 64 | 64 | scope_and_comp.compilation.gpa().free(self.text); |
| 65 | 65 | scope_and_comp.compilation.gpa().destroy(self); |
| 66 | 66 | }, |
| ... | ... | @@ -84,7 +84,7 @@ pub const Msg = struct { |
| 84 | 84 | return path_and_tree.realpath; |
| 85 | 85 | }, |
| 86 | 86 | Data.ScopeAndComp => |scope_and_comp| { |
| 87 | return scope_and_comp.root_scope.realpath; | |
| 87 | return scope_and_comp.tree_scope.root().realpath; | |
| 88 | 88 | }, |
| 89 | 89 | } |
| 90 | 90 | } |
| ... | ... | @@ -95,31 +95,31 @@ pub const Msg = struct { |
| 95 | 95 | return path_and_tree.tree; |
| 96 | 96 | }, |
| 97 | 97 | Data.ScopeAndComp => |scope_and_comp| { |
| 98 | return scope_and_comp.root_scope.tree; | |
| 98 | return scope_and_comp.tree_scope.tree; | |
| 99 | 99 | }, |
| 100 | 100 | } |
| 101 | 101 | } |
| 102 | 102 | |
| 103 | 103 | /// Takes ownership of text |
| 104 | /// References root_scope, and derefs when the msg is freed | |
| 105 | pub fn createFromScope(comp: *Compilation, root_scope: *Scope.Root, span: Span, text: []u8) !*Msg { | |
| 104 | /// References tree_scope, and derefs when the msg is freed | |
| 105 | pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg { | |
| 106 | 106 | const msg = try comp.gpa().create(Msg{ |
| 107 | 107 | .text = text, |
| 108 | 108 | .span = span, |
| 109 | 109 | .data = Data{ |
| 110 | 110 | .ScopeAndComp = ScopeAndComp{ |
| 111 | .root_scope = root_scope, | |
| 111 | .tree_scope = tree_scope, | |
| 112 | 112 | .compilation = comp, |
| 113 | 113 | }, |
| 114 | 114 | }, |
| 115 | 115 | }); |
| 116 | root_scope.base.ref(); | |
| 116 | tree_scope.base.ref(); | |
| 117 | 117 | return msg; |
| 118 | 118 | } |
| 119 | 119 | |
| 120 | 120 | pub fn createFromParseErrorAndScope( |
| 121 | 121 | comp: *Compilation, |
| 122 | root_scope: *Scope.Root, | |
| 122 | tree_scope: *Scope.AstTree, | |
| 123 | 123 | parse_error: *const ast.Error, |
| 124 | 124 | ) !*Msg { |
| 125 | 125 | const loc_token = parse_error.loc(); |
| ... | ... | @@ -127,7 +127,7 @@ pub const Msg = struct { |
| 127 | 127 | defer text_buf.deinit(); |
| 128 | 128 | |
| 129 | 129 | var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; |
| 130 | try parse_error.render(&root_scope.tree.tokens, out_stream); | |
| 130 | try parse_error.render(&tree_scope.tree.tokens, out_stream); | |
| 131 | 131 | |
| 132 | 132 | const msg = try comp.gpa().create(Msg{ |
| 133 | 133 | .text = undefined, |
| ... | ... | @@ -137,12 +137,12 @@ pub const Msg = struct { |
| 137 | 137 | }, |
| 138 | 138 | .data = Data{ |
| 139 | 139 | .ScopeAndComp = ScopeAndComp{ |
| 140 | .root_scope = root_scope, | |
| 140 | .tree_scope = tree_scope, | |
| 141 | 141 | .compilation = comp, |
| 142 | 142 | }, |
| 143 | 143 | }, |
| 144 | 144 | }); |
| 145 | root_scope.base.ref(); | |
| 145 | tree_scope.base.ref(); | |
| 146 | 146 | msg.text = text_buf.toOwnedSlice(); |
| 147 | 147 | return msg; |
| 148 | 148 | } |
src-self-hosted/ir.zig+3-2| ... | ... | @@ -1929,8 +1929,9 @@ pub const Builder = struct { |
| 1929 | 1929 | Scope.Id.Root => return Ident.NotFound, |
| 1930 | 1930 | Scope.Id.Decls => { |
| 1931 | 1931 | const decls = @fieldParentPtr(Scope.Decls, "base", s); |
| 1932 | const table = await (async decls.getTableReadOnly() catch unreachable); | |
| 1933 | if (table.get(name)) |entry| { | |
| 1932 | const locked_table = await (async decls.table.acquireRead() catch unreachable); | |
| 1933 | defer locked_table.release(); | |
| 1934 | if (locked_table.value.get(name)) |entry| { | |
| 1934 | 1935 | return Ident{ .Decl = entry.value }; |
| 1935 | 1936 | } |
| 1936 | 1937 | }, |
src-self-hosted/libc_installation.zig+2-4| ... | ... | @@ -143,7 +143,7 @@ pub const LibCInstallation = struct { |
| 143 | 143 | pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void { |
| 144 | 144 | self.initEmpty(); |
| 145 | 145 | var group = event.Group(FindError!void).init(loop); |
| 146 | errdefer group.cancelAll(); | |
| 146 | errdefer group.deinit(); | |
| 147 | 147 | var windows_sdk: ?*c.ZigWindowsSDK = null; |
| 148 | 148 | errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk)); |
| 149 | 149 | |
| ... | ... | @@ -313,7 +313,7 @@ pub const LibCInstallation = struct { |
| 313 | 313 | }, |
| 314 | 314 | }; |
| 315 | 315 | var group = event.Group(FindError!void).init(loop); |
| 316 | errdefer group.cancelAll(); | |
| 316 | errdefer group.deinit(); | |
| 317 | 317 | for (dyn_tests) |*dyn_test| { |
| 318 | 318 | try group.call(testNativeDynamicLinker, self, loop, dyn_test); |
| 319 | 319 | } |
| ... | ... | @@ -341,7 +341,6 @@ pub const LibCInstallation = struct { |
| 341 | 341 | } |
| 342 | 342 | } |
| 343 | 343 | |
| 344 | ||
| 345 | 344 | async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void { |
| 346 | 345 | var search_buf: [2]Search = undefined; |
| 347 | 346 | const searches = fillSearch(&search_buf, sdk); |
| ... | ... | @@ -450,7 +449,6 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search { |
| 450 | 449 | return search_buf[0..search_end]; |
| 451 | 450 | } |
| 452 | 451 | |
| 453 | ||
| 454 | 452 | fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool { |
| 455 | 453 | if (std.os.File.access(allocator, path)) |_| { |
| 456 | 454 | return true; |
src-self-hosted/main.zig+32-33| ... | ... | @@ -71,26 +71,26 @@ pub fn main() !void { |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | const commands = []Command{ |
| 74 | Command{ | |
| 75 | .name = "build-exe", | |
| 76 | .exec = cmdBuildExe, | |
| 77 | }, | |
| 78 | Command{ | |
| 79 | .name = "build-lib", | |
| 80 | .exec = cmdBuildLib, | |
| 81 | }, | |
| 82 | Command{ | |
| 83 | .name = "build-obj", | |
| 84 | .exec = cmdBuildObj, | |
| 85 | }, | |
| 74 | //Command{ | |
| 75 | // .name = "build-exe", | |
| 76 | // .exec = cmdBuildExe, | |
| 77 | //}, | |
| 78 | //Command{ | |
| 79 | // .name = "build-lib", | |
| 80 | // .exec = cmdBuildLib, | |
| 81 | //}, | |
| 82 | //Command{ | |
| 83 | // .name = "build-obj", | |
| 84 | // .exec = cmdBuildObj, | |
| 85 | //}, | |
| 86 | 86 | Command{ |
| 87 | 87 | .name = "fmt", |
| 88 | 88 | .exec = cmdFmt, |
| 89 | 89 | }, |
| 90 | Command{ | |
| 91 | .name = "libc", | |
| 92 | .exec = cmdLibC, | |
| 93 | }, | |
| 90 | //Command{ | |
| 91 | // .name = "libc", | |
| 92 | // .exec = cmdLibC, | |
| 93 | //}, | |
| 94 | 94 | Command{ |
| 95 | 95 | .name = "targets", |
| 96 | 96 | .exec = cmdTargets, |
| ... | ... | @@ -472,23 +472,22 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 472 | 472 | } |
| 473 | 473 | |
| 474 | 474 | async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { |
| 475 | // TODO directly awaiting async should guarantee memory allocation elision | |
| 476 | const build_event = await (async comp.events.get() catch unreachable); | |
| 477 | ||
| 478 | switch (build_event) { | |
| 479 | Compilation.Event.Ok => { | |
| 480 | return; | |
| 481 | }, | |
| 482 | Compilation.Event.Error => |err| { | |
| 483 | std.debug.warn("build failed: {}\n", @errorName(err)); | |
| 484 | os.exit(1); | |
| 485 | }, | |
| 486 | Compilation.Event.Fail => |msgs| { | |
| 487 | for (msgs) |msg| { | |
| 488 | defer msg.destroy(); | |
| 489 | msg.printToFile(&stderr_file, color) catch os.exit(1); | |
| 490 | } | |
| 491 | }, | |
| 475 | while (true) { | |
| 476 | // TODO directly awaiting async should guarantee memory allocation elision | |
| 477 | const build_event = await (async comp.events.get() catch unreachable); | |
| 478 | ||
| 479 | switch (build_event) { | |
| 480 | Compilation.Event.Ok => {}, | |
| 481 | Compilation.Event.Error => |err| { | |
| 482 | stderr.print("build failed: {}\n", @errorName(err)) catch os.exit(1); | |
| 483 | }, | |
| 484 | Compilation.Event.Fail => |msgs| { | |
| 485 | for (msgs) |msg| { | |
| 486 | defer msg.destroy(); | |
| 487 | msg.printToFile(&stderr_file, color) catch os.exit(1); | |
| 488 | } | |
| 489 | }, | |
| 490 | } | |
| 492 | 491 | } |
| 493 | 492 | } |
| 494 | 493 |
src-self-hosted/scope.zig+40-20| ... | ... | @@ -36,6 +36,7 @@ pub const Scope = struct { |
| 36 | 36 | Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), |
| 37 | 37 | Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), |
| 38 | 38 | Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp), |
| 39 | Id.AstTree => @fieldParentPtr(AstTree, "base", base).destroy(comp), | |
| 39 | 40 | } |
| 40 | 41 | } |
| 41 | 42 | } |
| ... | ... | @@ -97,6 +98,7 @@ pub const Scope = struct { |
| 97 | 98 | |
| 98 | 99 | pub const Id = enum { |
| 99 | 100 | Root, |
| 101 | AstTree, | |
| 100 | 102 | Decls, |
| 101 | 103 | Block, |
| 102 | 104 | FnDef, |
| ... | ... | @@ -108,13 +110,12 @@ pub const Scope = struct { |
| 108 | 110 | |
| 109 | 111 | pub const Root = struct { |
| 110 | 112 | base: Scope, |
| 111 | tree: *ast.Tree, | |
| 112 | 113 | realpath: []const u8, |
| 114 | decls: *Decls, | |
| 113 | 115 | |
| 114 | 116 | /// Creates a Root scope with 1 reference |
| 115 | 117 | /// Takes ownership of realpath |
| 116 | /// Takes ownership of tree, will deinit and destroy when done. | |
| 117 | pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root { | |
| 118 | pub fn create(comp: *Compilation, realpath: []u8) !*Root { | |
| 118 | 119 | const self = try comp.gpa().createOne(Root); |
| 119 | 120 | self.* = Root{ |
| 120 | 121 | .base = Scope{ |
| ... | ... | @@ -122,40 +123,64 @@ pub const Scope = struct { |
| 122 | 123 | .parent = null, |
| 123 | 124 | .ref_count = std.atomic.Int(usize).init(1), |
| 124 | 125 | }, |
| 125 | .tree = tree, | |
| 126 | 126 | .realpath = realpath, |
| 127 | .decls = undefined, | |
| 127 | 128 | }; |
| 128 | ||
| 129 | errdefer comp.gpa().destroy(self); | |
| 130 | self.decls = try Decls.create(comp, &self.base); | |
| 129 | 131 | return self; |
| 130 | 132 | } |
| 131 | 133 | |
| 132 | 134 | pub fn destroy(self: *Root, comp: *Compilation) void { |
| 135 | self.decls.base.deref(comp); | |
| 136 | comp.gpa().free(self.realpath); | |
| 137 | comp.gpa().destroy(self); | |
| 138 | } | |
| 139 | }; | |
| 140 | ||
| 141 | pub const AstTree = struct { | |
| 142 | base: Scope, | |
| 143 | tree: *ast.Tree, | |
| 144 | ||
| 145 | /// Creates a scope with 1 reference | |
| 146 | /// Takes ownership of tree, will deinit and destroy when done. | |
| 147 | pub fn create(comp: *Compilation, tree: *ast.Tree, root: *Root) !*AstTree { | |
| 148 | const self = try comp.gpa().createOne(Root); | |
| 149 | self.* = AstTree{ | |
| 150 | .base = undefined, | |
| 151 | .tree = tree, | |
| 152 | }; | |
| 153 | self.base.init(Id.AstTree, &root.base); | |
| 154 | ||
| 155 | return self; | |
| 156 | } | |
| 157 | ||
| 158 | pub fn destroy(self: *AstTree, comp: *Compilation) void { | |
| 133 | 159 | comp.gpa().free(self.tree.source); |
| 134 | 160 | self.tree.deinit(); |
| 135 | 161 | comp.gpa().destroy(self.tree); |
| 136 | comp.gpa().free(self.realpath); | |
| 137 | 162 | comp.gpa().destroy(self); |
| 138 | 163 | } |
| 164 | ||
| 165 | pub fn root(self: *AstTree) *Root { | |
| 166 | return self.base.findRoot(); | |
| 167 | } | |
| 139 | 168 | }; |
| 140 | 169 | |
| 141 | 170 | pub const Decls = struct { |
| 142 | 171 | base: Scope, |
| 143 | 172 | |
| 144 | /// The lock must be respected for writing. However once name_future resolves, | |
| 145 | /// readers can freely access it. | |
| 146 | table: event.Locked(Decl.Table), | |
| 147 | ||
| 148 | /// Once this future is resolved, the table is complete and available for unlocked | |
| 149 | /// read-only access. It does not mean all the decls are resolved; it means only that | |
| 150 | /// the table has all the names. Each decl in the table has its own resolution state. | |
| 151 | name_future: event.Future(void), | |
| 173 | /// This table remains Write Locked when the names are incomplete or possibly outdated. | |
| 174 | /// So if a reader manages to grab a lock, it can be sure that the set of names is complete | |
| 175 | /// and correct. | |
| 176 | table: event.RwLocked(Decl.Table), | |
| 152 | 177 | |
| 153 | 178 | /// Creates a Decls scope with 1 reference |
| 154 | 179 | pub fn create(comp: *Compilation, parent: *Scope) !*Decls { |
| 155 | 180 | const self = try comp.gpa().createOne(Decls); |
| 156 | 181 | self.* = Decls{ |
| 157 | 182 | .base = undefined, |
| 158 | .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), | |
| 183 | .table = event.RwLocked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), | |
| 159 | 184 | .name_future = event.Future(void).init(comp.loop), |
| 160 | 185 | }; |
| 161 | 186 | self.base.init(Id.Decls, parent); |
| ... | ... | @@ -166,11 +191,6 @@ pub const Scope = struct { |
| 166 | 191 | self.table.deinit(); |
| 167 | 192 | comp.gpa().destroy(self); |
| 168 | 193 | } |
| 169 | ||
| 170 | pub async fn getTableReadOnly(self: *Decls) *Decl.Table { | |
| 171 | _ = await (async self.name_future.get() catch unreachable); | |
| 172 | return &self.table.private_data; | |
| 173 | } | |
| 174 | 194 | }; |
| 175 | 195 | |
| 176 | 196 | pub const Block = struct { |
src-self-hosted/test.zig+1-1| ... | ... | @@ -50,7 +50,7 @@ pub const TestContext = struct { |
| 50 | 50 | errdefer self.event_loop_local.deinit(); |
| 51 | 51 | |
| 52 | 52 | self.group = std.event.Group(error!void).init(&self.loop); |
| 53 | errdefer self.group.cancelAll(); | |
| 53 | errdefer self.group.deinit(); | |
| 54 | 54 | |
| 55 | 55 | self.zig_lib_dir = try introspect.resolveZigLibDir(allocator); |
| 56 | 56 | errdefer allocator.free(self.zig_lib_dir); |
std/atomic/queue.zig+60-24| ... | ... | @@ -1,40 +1,38 @@ |
| 1 | const std = @import("../index.zig"); | |
| 1 | 2 | const builtin = @import("builtin"); |
| 2 | 3 | const AtomicOrder = builtin.AtomicOrder; |
| 3 | 4 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 5 | const assert = std.debug.assert; | |
| 4 | 6 | |
| 5 | 7 | /// Many producer, many consumer, non-allocating, thread-safe. |
| 6 | /// Uses a spinlock to protect get() and put(). | |
| 8 | /// Uses a mutex to protect access. | |
| 7 | 9 | pub fn Queue(comptime T: type) type { |
| 8 | 10 | return struct { |
| 9 | 11 | head: ?*Node, |
| 10 | 12 | tail: ?*Node, |
| 11 | lock: u8, | |
| 13 | mutex: std.Mutex, | |
| 12 | 14 | |
| 13 | 15 | pub const Self = this; |
| 14 | ||
| 15 | pub const Node = struct { | |
| 16 | next: ?*Node, | |
| 17 | data: T, | |
| 18 | }; | |
| 16 | pub const Node = std.LinkedList(T).Node; | |
| 19 | 17 | |
| 20 | 18 | pub fn init() Self { |
| 21 | 19 | return Self{ |
| 22 | 20 | .head = null, |
| 23 | 21 | .tail = null, |
| 24 | .lock = 0, | |
| 22 | .mutex = std.Mutex.init(), | |
| 25 | 23 | }; |
| 26 | 24 | } |
| 27 | 25 | |
| 28 | 26 | pub fn put(self: *Self, node: *Node) void { |
| 29 | 27 | node.next = null; |
| 30 | 28 | |
| 31 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 32 | defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 29 | const held = self.mutex.acquire(); | |
| 30 | defer held.release(); | |
| 33 | 31 | |
| 34 | const opt_tail = self.tail; | |
| 32 | node.prev = self.tail; | |
| 35 | 33 | self.tail = node; |
| 36 | if (opt_tail) |tail| { | |
| 37 | tail.next = node; | |
| 34 | if (node.prev) |prev_tail| { | |
| 35 | prev_tail.next = node; | |
| 38 | 36 | } else { |
| 39 | 37 | assert(self.head == null); |
| 40 | 38 | self.head = node; |
| ... | ... | @@ -42,18 +40,27 @@ pub fn Queue(comptime T: type) type { |
| 42 | 40 | } |
| 43 | 41 | |
| 44 | 42 | pub fn get(self: *Self) ?*Node { |
| 45 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 46 | defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 43 | const held = self.mutex.acquire(); | |
| 44 | defer held.release(); | |
| 47 | 45 | |
| 48 | 46 | const head = self.head orelse return null; |
| 49 | 47 | self.head = head.next; |
| 50 | if (head.next == null) self.tail = null; | |
| 48 | if (head.next) |new_head| { | |
| 49 | new_head.prev = null; | |
| 50 | } else { | |
| 51 | self.tail = null; | |
| 52 | } | |
| 53 | // This way, a get() and a remove() are thread-safe with each other. | |
| 54 | head.prev = null; | |
| 55 | head.next = null; | |
| 51 | 56 | return head; |
| 52 | 57 | } |
| 53 | 58 | |
| 54 | 59 | pub fn unget(self: *Self, node: *Node) void { |
| 55 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 56 | defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 60 | node.prev = null; | |
| 61 | ||
| 62 | const held = self.mutex.acquire(); | |
| 63 | defer held.release(); | |
| 57 | 64 | |
| 58 | 65 | const opt_head = self.head; |
| 59 | 66 | self.head = node; |
| ... | ... | @@ -65,13 +72,39 @@ pub fn Queue(comptime T: type) type { |
| 65 | 72 | } |
| 66 | 73 | } |
| 67 | 74 | |
| 75 | /// Thread-safe with get() and remove(). Returns whether node was actually removed. | |
| 76 | pub fn remove(self: *Self, node: *Node) bool { | |
| 77 | const held = self.mutex.acquire(); | |
| 78 | defer held.release(); | |
| 79 | ||
| 80 | if (node.prev == null and node.next == null and self.head != node) { | |
| 81 | return false; | |
| 82 | } | |
| 83 | ||
| 84 | if (node.prev) |prev| { | |
| 85 | prev.next = node.next; | |
| 86 | } else { | |
| 87 | self.head = node.next; | |
| 88 | } | |
| 89 | if (node.next) |next| { | |
| 90 | next.prev = node.prev; | |
| 91 | } else { | |
| 92 | self.tail = node.prev; | |
| 93 | } | |
| 94 | node.prev = null; | |
| 95 | node.next = null; | |
| 96 | return true; | |
| 97 | } | |
| 98 | ||
| 68 | 99 | pub fn isEmpty(self: *Self) bool { |
| 69 | return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null; | |
| 100 | const held = self.mutex.acquire(); | |
| 101 | defer held.release(); | |
| 102 | return self.head != null; | |
| 70 | 103 | } |
| 71 | 104 | |
| 72 | 105 | pub fn dump(self: *Self) void { |
| 73 | while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {} | |
| 74 | defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1); | |
| 106 | const held = self.mutex.acquire(); | |
| 107 | defer held.release(); | |
| 75 | 108 | |
| 76 | 109 | std.debug.warn("head: "); |
| 77 | 110 | dumpRecursive(self.head, 0); |
| ... | ... | @@ -93,9 +126,6 @@ pub fn Queue(comptime T: type) type { |
| 93 | 126 | }; |
| 94 | 127 | } |
| 95 | 128 | |
| 96 | const std = @import("../index.zig"); | |
| 97 | const assert = std.debug.assert; | |
| 98 | ||
| 99 | 129 | const Context = struct { |
| 100 | 130 | allocator: *std.mem.Allocator, |
| 101 | 131 | queue: *Queue(i32), |
| ... | ... | @@ -169,6 +199,7 @@ fn startPuts(ctx: *Context) u8 { |
| 169 | 199 | std.os.time.sleep(0, 1); // let the os scheduler be our fuzz |
| 170 | 200 | const x = @bitCast(i32, r.random.scalar(u32)); |
| 171 | 201 | const node = ctx.allocator.create(Queue(i32).Node{ |
| 202 | .prev = undefined, | |
| 172 | 203 | .next = undefined, |
| 173 | 204 | .data = x, |
| 174 | 205 | }) catch unreachable; |
| ... | ... | @@ -198,12 +229,14 @@ test "std.atomic.Queue single-threaded" { |
| 198 | 229 | var node_0 = Queue(i32).Node{ |
| 199 | 230 | .data = 0, |
| 200 | 231 | .next = undefined, |
| 232 | .prev = undefined, | |
| 201 | 233 | }; |
| 202 | 234 | queue.put(&node_0); |
| 203 | 235 | |
| 204 | 236 | var node_1 = Queue(i32).Node{ |
| 205 | 237 | .data = 1, |
| 206 | 238 | .next = undefined, |
| 239 | .prev = undefined, | |
| 207 | 240 | }; |
| 208 | 241 | queue.put(&node_1); |
| 209 | 242 | |
| ... | ... | @@ -212,12 +245,14 @@ test "std.atomic.Queue single-threaded" { |
| 212 | 245 | var node_2 = Queue(i32).Node{ |
| 213 | 246 | .data = 2, |
| 214 | 247 | .next = undefined, |
| 248 | .prev = undefined, | |
| 215 | 249 | }; |
| 216 | 250 | queue.put(&node_2); |
| 217 | 251 | |
| 218 | 252 | var node_3 = Queue(i32).Node{ |
| 219 | 253 | .data = 3, |
| 220 | 254 | .next = undefined, |
| 255 | .prev = undefined, | |
| 221 | 256 | }; |
| 222 | 257 | queue.put(&node_3); |
| 223 | 258 | |
| ... | ... | @@ -228,6 +263,7 @@ test "std.atomic.Queue single-threaded" { |
| 228 | 263 | var node_4 = Queue(i32).Node{ |
| 229 | 264 | .data = 4, |
| 230 | 265 | .next = undefined, |
| 266 | .prev = undefined, | |
| 231 | 267 | }; |
| 232 | 268 | queue.put(&node_4); |
| 233 | 269 |
std/event.zig+1-1| ... | ... | @@ -3,7 +3,7 @@ pub const Future = @import("event/future.zig").Future; |
| 3 | 3 | pub const Group = @import("event/group.zig").Group; |
| 4 | 4 | pub const Lock = @import("event/lock.zig").Lock; |
| 5 | 5 | pub const Locked = @import("event/locked.zig").Locked; |
| 6 | pub const RwLock = @import("event/rwlock.zig").Lock; | |
| 6 | pub const RwLock = @import("event/rwlock.zig").RwLock; | |
| 7 | 7 | pub const RwLocked = @import("event/rwlocked.zig").RwLocked; |
| 8 | 8 | pub const Loop = @import("event/loop.zig").Loop; |
| 9 | 9 | pub const fs = @import("event/fs.zig"); |
std/event/channel.zig+168-29| ... | ... | @@ -5,7 +5,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp; |
| 5 | 5 | const AtomicOrder = builtin.AtomicOrder; |
| 6 | 6 | const Loop = std.event.Loop; |
| 7 | 7 | |
| 8 | /// many producer, many consumer, thread-safe, lock-free, runtime configurable buffer size | |
| 8 | /// many producer, many consumer, thread-safe, runtime configurable buffer size | |
| 9 | 9 | /// when buffer is empty, consumers suspend and are resumed by producers |
| 10 | 10 | /// when buffer is full, producers suspend and are resumed by consumers |
| 11 | 11 | pub fn Channel(comptime T: type) type { |
| ... | ... | @@ -13,6 +13,7 @@ pub fn Channel(comptime T: type) type { |
| 13 | 13 | loop: *Loop, |
| 14 | 14 | |
| 15 | 15 | getters: std.atomic.Queue(GetNode), |
| 16 | or_null_queue: std.atomic.Queue(*std.atomic.Queue(GetNode).Node), | |
| 16 | 17 | putters: std.atomic.Queue(PutNode), |
| 17 | 18 | get_count: usize, |
| 18 | 19 | put_count: usize, |
| ... | ... | @@ -26,8 +27,22 @@ pub fn Channel(comptime T: type) type { |
| 26 | 27 | |
| 27 | 28 | const SelfChannel = this; |
| 28 | 29 | const GetNode = struct { |
| 29 | ptr: *T, | |
| 30 | 30 | tick_node: *Loop.NextTickNode, |
| 31 | data: Data, | |
| 32 | ||
| 33 | const Data = union(enum) { | |
| 34 | Normal: Normal, | |
| 35 | OrNull: OrNull, | |
| 36 | }; | |
| 37 | ||
| 38 | const Normal = struct { | |
| 39 | ptr: *T, | |
| 40 | }; | |
| 41 | ||
| 42 | const OrNull = struct { | |
| 43 | ptr: *?T, | |
| 44 | or_null: *std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node, | |
| 45 | }; | |
| 31 | 46 | }; |
| 32 | 47 | const PutNode = struct { |
| 33 | 48 | data: T, |
| ... | ... | @@ -48,6 +63,7 @@ pub fn Channel(comptime T: type) type { |
| 48 | 63 | .need_dispatch = 0, |
| 49 | 64 | .getters = std.atomic.Queue(GetNode).init(), |
| 50 | 65 | .putters = std.atomic.Queue(PutNode).init(), |
| 66 | .or_null_queue = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).init(), | |
| 51 | 67 | .get_count = 0, |
| 52 | 68 | .put_count = 0, |
| 53 | 69 | }); |
| ... | ... | @@ -71,18 +87,31 @@ pub fn Channel(comptime T: type) type { |
| 71 | 87 | /// puts a data item in the channel. The promise completes when the value has been added to the |
| 72 | 88 | /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter. |
| 73 | 89 | pub async fn put(self: *SelfChannel, data: T) void { |
| 90 | // TODO fix this workaround | |
| 91 | var my_handle: promise = undefined; | |
| 92 | suspend |p| { | |
| 93 | my_handle = p; | |
| 94 | resume p; | |
| 95 | } | |
| 96 | ||
| 97 | var my_tick_node = Loop.NextTickNode.init(my_handle); | |
| 98 | var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{ | |
| 99 | .tick_node = &my_tick_node, | |
| 100 | .data = data, | |
| 101 | }); | |
| 102 | ||
| 103 | // TODO test canceling a put() | |
| 104 | errdefer { | |
| 105 | _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | |
| 106 | const need_dispatch = !self.putters.remove(&queue_node); | |
| 107 | self.loop.cancelOnNextTick(&my_tick_node); | |
| 108 | if (need_dispatch) { | |
| 109 | // oops we made the put_count incorrect for a period of time. fix by dispatching. | |
| 110 | _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 111 | self.dispatch(); | |
| 112 | } | |
| 113 | } | |
| 74 | 114 | suspend |handle| { |
| 75 | var my_tick_node = Loop.NextTickNode{ | |
| 76 | .next = undefined, | |
| 77 | .data = handle, | |
| 78 | }; | |
| 79 | var queue_node = std.atomic.Queue(PutNode).Node{ | |
| 80 | .data = PutNode{ | |
| 81 | .tick_node = &my_tick_node, | |
| 82 | .data = data, | |
| 83 | }, | |
| 84 | .next = undefined, | |
| 85 | }; | |
| 86 | 115 | self.putters.put(&queue_node); |
| 87 | 116 | _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 88 | 117 | |
| ... | ... | @@ -93,21 +122,37 @@ pub fn Channel(comptime T: type) type { |
| 93 | 122 | /// await this function to get an item from the channel. If the buffer is empty, the promise will |
| 94 | 123 | /// complete when the next item is put in the channel. |
| 95 | 124 | pub async fn get(self: *SelfChannel) T { |
| 125 | // TODO fix this workaround | |
| 126 | var my_handle: promise = undefined; | |
| 127 | suspend |p| { | |
| 128 | my_handle = p; | |
| 129 | resume p; | |
| 130 | } | |
| 131 | ||
| 96 | 132 | // TODO integrate this function with named return values |
| 97 | 133 | // so we can get rid of this extra result copy |
| 98 | 134 | var result: T = undefined; |
| 99 | suspend |handle| { | |
| 100 | var my_tick_node = Loop.NextTickNode{ | |
| 101 | .next = undefined, | |
| 102 | .data = handle, | |
| 103 | }; | |
| 104 | var queue_node = std.atomic.Queue(GetNode).Node{ | |
| 105 | .data = GetNode{ | |
| 106 | .ptr = &result, | |
| 107 | .tick_node = &my_tick_node, | |
| 108 | }, | |
| 109 | .next = undefined, | |
| 110 | }; | |
| 135 | var my_tick_node = Loop.NextTickNode.init(my_handle); | |
| 136 | var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{ | |
| 137 | .tick_node = &my_tick_node, | |
| 138 | .data = GetNode.Data{ | |
| 139 | .Normal = GetNode.Normal{ .ptr = &result }, | |
| 140 | }, | |
| 141 | }); | |
| 142 | ||
| 143 | // TODO test canceling a get() | |
| 144 | errdefer { | |
| 145 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | |
| 146 | const need_dispatch = !self.getters.remove(&queue_node); | |
| 147 | self.loop.cancelOnNextTick(&my_tick_node); | |
| 148 | if (need_dispatch) { | |
| 149 | // oops we made the get_count incorrect for a period of time. fix by dispatching. | |
| 150 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 151 | self.dispatch(); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | suspend |_| { | |
| 111 | 156 | self.getters.put(&queue_node); |
| 112 | 157 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 113 | 158 | |
| ... | ... | @@ -116,8 +161,64 @@ pub fn Channel(comptime T: type) type { |
| 116 | 161 | return result; |
| 117 | 162 | } |
| 118 | 163 | |
| 119 | fn getOrNull(self: *SelfChannel) ?T { | |
| 120 | TODO(); | |
| 164 | //pub async fn select(comptime EnumUnion: type, channels: ...) EnumUnion { | |
| 165 | // assert(@memberCount(EnumUnion) == channels.len); // enum union and channels mismatch | |
| 166 | // assert(channels.len != 0); // enum unions cannot have 0 fields | |
| 167 | // if (channels.len == 1) { | |
| 168 | // const result = await (async channels[0].get() catch unreachable); | |
| 169 | // return @unionInit(EnumUnion, @memberName(EnumUnion, 0), result); | |
| 170 | // } | |
| 171 | //} | |
| 172 | ||
| 173 | /// Await this function to get an item from the channel. If the buffer is empty and there are no | |
| 174 | /// puts waiting, this returns null. | |
| 175 | /// Await is necessary for locking purposes. The function will be resumed after checking the channel | |
| 176 | /// for data and will not wait for data to be available. | |
| 177 | pub async fn getOrNull(self: *SelfChannel) ?T { | |
| 178 | // TODO fix this workaround | |
| 179 | var my_handle: promise = undefined; | |
| 180 | suspend |p| { | |
| 181 | my_handle = p; | |
| 182 | resume p; | |
| 183 | } | |
| 184 | ||
| 185 | // TODO integrate this function with named return values | |
| 186 | // so we can get rid of this extra result copy | |
| 187 | var result: ?T = null; | |
| 188 | var my_tick_node = Loop.NextTickNode.init(my_handle); | |
| 189 | var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node.init(undefined); | |
| 190 | var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{ | |
| 191 | .tick_node = &my_tick_node, | |
| 192 | .data = GetNode.Data{ | |
| 193 | .OrNull = GetNode.OrNull{ | |
| 194 | .ptr = &result, | |
| 195 | .or_null = &or_null_node, | |
| 196 | }, | |
| 197 | }, | |
| 198 | }); | |
| 199 | or_null_node.data = &queue_node; | |
| 200 | ||
| 201 | // TODO test canceling getOrNull | |
| 202 | errdefer { | |
| 203 | _ = self.or_null_queue.remove(&or_null_node); | |
| 204 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); | |
| 205 | const need_dispatch = !self.getters.remove(&queue_node); | |
| 206 | self.loop.cancelOnNextTick(&my_tick_node); | |
| 207 | if (need_dispatch) { | |
| 208 | // oops we made the get_count incorrect for a period of time. fix by dispatching. | |
| 209 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 210 | self.dispatch(); | |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | suspend |_| { | |
| 215 | self.getters.put(&queue_node); | |
| 216 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); | |
| 217 | self.or_null_queue.put(&or_null_node); | |
| 218 | ||
| 219 | self.dispatch(); | |
| 220 | } | |
| 221 | return result; | |
| 121 | 222 | } |
| 122 | 223 | |
| 123 | 224 | fn dispatch(self: *SelfChannel) void { |
| ... | ... | @@ -143,7 +244,15 @@ pub fn Channel(comptime T: type) type { |
| 143 | 244 | if (get_count == 0) break :one_dispatch; |
| 144 | 245 | |
| 145 | 246 | const get_node = &self.getters.get().?.data; |
| 146 | get_node.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len]; | |
| 247 | switch (get_node.data) { | |
| 248 | GetNode.Data.Normal => |info| { | |
| 249 | info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len]; | |
| 250 | }, | |
| 251 | GetNode.Data.OrNull => |info| { | |
| 252 | _ = self.or_null_queue.remove(info.or_null); | |
| 253 | info.ptr.* = self.buffer_nodes[self.buffer_index -% self.buffer_len]; | |
| 254 | }, | |
| 255 | } | |
| 147 | 256 | self.loop.onNextTick(get_node.tick_node); |
| 148 | 257 | self.buffer_len -= 1; |
| 149 | 258 | |
| ... | ... | @@ -155,7 +264,15 @@ pub fn Channel(comptime T: type) type { |
| 155 | 264 | const get_node = &self.getters.get().?.data; |
| 156 | 265 | const put_node = &self.putters.get().?.data; |
| 157 | 266 | |
| 158 | get_node.ptr.* = put_node.data; | |
| 267 | switch (get_node.data) { | |
| 268 | GetNode.Data.Normal => |info| { | |
| 269 | info.ptr.* = put_node.data; | |
| 270 | }, | |
| 271 | GetNode.Data.OrNull => |info| { | |
| 272 | _ = self.or_null_queue.remove(info.or_null); | |
| 273 | info.ptr.* = put_node.data; | |
| 274 | }, | |
| 275 | } | |
| 159 | 276 | self.loop.onNextTick(get_node.tick_node); |
| 160 | 277 | self.loop.onNextTick(put_node.tick_node); |
| 161 | 278 | |
| ... | ... | @@ -180,6 +297,16 @@ pub fn Channel(comptime T: type) type { |
| 180 | 297 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 181 | 298 | _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); |
| 182 | 299 | |
| 300 | // All the "get or null" functions should resume now. | |
| 301 | var remove_count: usize = 0; | |
| 302 | while (self.or_null_queue.get()) |or_null_node| { | |
| 303 | remove_count += @boolToInt(self.getters.remove(or_null_node.data)); | |
| 304 | self.loop.onNextTick(or_null_node.data.data.tick_node); | |
| 305 | } | |
| 306 | if (remove_count != 0) { | |
| 307 | _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, remove_count, AtomicOrder.SeqCst); | |
| 308 | } | |
| 309 | ||
| 183 | 310 | // clear need-dispatch flag |
| 184 | 311 | const need_dispatch = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 185 | 312 | if (need_dispatch != 0) continue; |
| ... | ... | @@ -230,6 +357,15 @@ async fn testChannelGetter(loop: *Loop, channel: *Channel(i32)) void { |
| 230 | 357 | const value2_promise = try async channel.get(); |
| 231 | 358 | const value2 = await value2_promise; |
| 232 | 359 | assert(value2 == 4567); |
| 360 | ||
| 361 | const value3_promise = try async channel.getOrNull(); | |
| 362 | const value3 = await value3_promise; | |
| 363 | assert(value3 == null); | |
| 364 | ||
| 365 | const last_put = try async testPut(channel, 4444); | |
| 366 | const value4 = await try async channel.getOrNull(); | |
| 367 | assert(value4.? == 4444); | |
| 368 | await last_put; | |
| 233 | 369 | } |
| 234 | 370 | |
| 235 | 371 | async fn testChannelPutter(channel: *Channel(i32)) void { |
| ... | ... | @@ -237,3 +373,6 @@ async fn testChannelPutter(channel: *Channel(i32)) void { |
| 237 | 373 | await (async channel.put(4567) catch @panic("out of memory")); |
| 238 | 374 | } |
| 239 | 375 | |
| 376 | async fn testPut(channel: *Channel(i32), value: i32) void { | |
| 377 | await (async channel.put(value) catch @panic("out of memory")); | |
| 378 | } |
std/event/fs.zig+11| ... | ... | @@ -99,6 +99,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | 101 | var req_node = RequestNode{ |
| 102 | .prev = undefined, | |
| 102 | 103 | .next = undefined, |
| 103 | 104 | .data = Request{ |
| 104 | 105 | .msg = Request.Msg{ |
| ... | ... | @@ -111,6 +112,7 @@ pub async fn pwritev(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: |
| 111 | 112 | }, |
| 112 | 113 | .finish = Request.Finish{ |
| 113 | 114 | .TickNode = event.Loop.NextTickNode{ |
| 115 | .prev = undefined, | |
| 114 | 116 | .next = undefined, |
| 115 | 117 | .data = my_handle, |
| 116 | 118 | }, |
| ... | ... | @@ -148,6 +150,7 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [ |
| 148 | 150 | } |
| 149 | 151 | |
| 150 | 152 | var req_node = RequestNode{ |
| 153 | .prev = undefined, | |
| 151 | 154 | .next = undefined, |
| 152 | 155 | .data = Request{ |
| 153 | 156 | .msg = Request.Msg{ |
| ... | ... | @@ -160,6 +163,7 @@ pub async fn preadv(loop: *event.Loop, fd: os.FileHandle, offset: usize, data: [ |
| 160 | 163 | }, |
| 161 | 164 | .finish = Request.Finish{ |
| 162 | 165 | .TickNode = event.Loop.NextTickNode{ |
| 166 | .prev = undefined, | |
| 163 | 167 | .next = undefined, |
| 164 | 168 | .data = my_handle, |
| 165 | 169 | }, |
| ... | ... | @@ -186,6 +190,7 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os. |
| 186 | 190 | defer loop.allocator.free(path_with_null); |
| 187 | 191 | |
| 188 | 192 | var req_node = RequestNode{ |
| 193 | .prev = undefined, | |
| 189 | 194 | .next = undefined, |
| 190 | 195 | .data = Request{ |
| 191 | 196 | .msg = Request.Msg{ |
| ... | ... | @@ -196,6 +201,7 @@ pub async fn openRead(loop: *event.Loop, path: []const u8) os.File.OpenError!os. |
| 196 | 201 | }, |
| 197 | 202 | .finish = Request.Finish{ |
| 198 | 203 | .TickNode = event.Loop.NextTickNode{ |
| 204 | .prev = undefined, | |
| 199 | 205 | .next = undefined, |
| 200 | 206 | .data = my_handle, |
| 201 | 207 | }, |
| ... | ... | @@ -227,6 +233,7 @@ pub async fn openReadWrite( |
| 227 | 233 | defer loop.allocator.free(path_with_null); |
| 228 | 234 | |
| 229 | 235 | var req_node = RequestNode{ |
| 236 | .prev = undefined, | |
| 230 | 237 | .next = undefined, |
| 231 | 238 | .data = Request{ |
| 232 | 239 | .msg = Request.Msg{ |
| ... | ... | @@ -238,6 +245,7 @@ pub async fn openReadWrite( |
| 238 | 245 | }, |
| 239 | 246 | .finish = Request.Finish{ |
| 240 | 247 | .TickNode = event.Loop.NextTickNode{ |
| 248 | .prev = undefined, | |
| 241 | 249 | .next = undefined, |
| 242 | 250 | .data = my_handle, |
| 243 | 251 | }, |
| ... | ... | @@ -267,6 +275,7 @@ pub const CloseOperation = struct { |
| 267 | 275 | .loop = loop, |
| 268 | 276 | .have_fd = false, |
| 269 | 277 | .close_req_node = RequestNode{ |
| 278 | .prev = undefined, | |
| 270 | 279 | .next = undefined, |
| 271 | 280 | .data = Request{ |
| 272 | 281 | .msg = Request.Msg{ |
| ... | ... | @@ -312,6 +321,7 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons |
| 312 | 321 | defer loop.allocator.free(path_with_null); |
| 313 | 322 | |
| 314 | 323 | var req_node = RequestNode{ |
| 324 | .prev = undefined, | |
| 315 | 325 | .next = undefined, |
| 316 | 326 | .data = Request{ |
| 317 | 327 | .msg = Request.Msg{ |
| ... | ... | @@ -324,6 +334,7 @@ pub async fn writeFileMode(loop: *event.Loop, path: []const u8, contents: []cons |
| 324 | 334 | }, |
| 325 | 335 | .finish = Request.Finish{ |
| 326 | 336 | .TickNode = event.Loop.NextTickNode{ |
| 337 | .prev = undefined, | |
| 327 | 338 | .next = undefined, |
| 328 | 339 | .data = my_handle, |
| 329 | 340 | }, |
std/event/lock.zig+12-6| ... | ... | @@ -91,13 +91,16 @@ pub const Lock = struct { |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | 93 | pub async fn acquire(self: *Lock) Held { |
| 94 | suspend |handle| { | |
| 95 | // TODO explicitly put this memory in the coroutine frame #1194 | |
| 96 | var my_tick_node = Loop.NextTickNode{ | |
| 97 | .data = handle, | |
| 98 | .next = undefined, | |
| 99 | }; | |
| 94 | // TODO explicitly put this memory in the coroutine frame #1194 | |
| 95 | var my_handle: promise = undefined; | |
| 96 | suspend |p| { | |
| 97 | my_handle = p; | |
| 98 | resume p; | |
| 99 | } | |
| 100 | var my_tick_node = Loop.NextTickNode.init(my_handle); | |
| 100 | 101 | |
| 102 | errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire | |
| 103 | suspend |_| { | |
| 101 | 104 | self.queue.put(&my_tick_node); |
| 102 | 105 | |
| 103 | 106 | // At this point, we are in the queue, so we might have already been resumed and this coroutine |
| ... | ... | @@ -170,6 +173,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void { |
| 170 | 173 | } |
| 171 | 174 | const handle1 = async lockRunner(lock) catch @panic("out of memory"); |
| 172 | 175 | var tick_node1 = Loop.NextTickNode{ |
| 176 | .prev = undefined, | |
| 173 | 177 | .next = undefined, |
| 174 | 178 | .data = handle1, |
| 175 | 179 | }; |
| ... | ... | @@ -177,6 +181,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void { |
| 177 | 181 | |
| 178 | 182 | const handle2 = async lockRunner(lock) catch @panic("out of memory"); |
| 179 | 183 | var tick_node2 = Loop.NextTickNode{ |
| 184 | .prev = undefined, | |
| 180 | 185 | .next = undefined, |
| 181 | 186 | .data = handle2, |
| 182 | 187 | }; |
| ... | ... | @@ -184,6 +189,7 @@ async fn testLock(loop: *Loop, lock: *Lock) void { |
| 184 | 189 | |
| 185 | 190 | const handle3 = async lockRunner(lock) catch @panic("out of memory"); |
| 186 | 191 | var tick_node3 = Loop.NextTickNode{ |
| 192 | .prev = undefined, | |
| 187 | 193 | .next = undefined, |
| 188 | 194 | .data = handle3, |
| 189 | 195 | }; |
std/event/loop.zig+11| ... | ... | @@ -120,6 +120,7 @@ pub const Loop = struct { |
| 120 | 120 | // we need another thread for the file system because Linux does not have an async |
| 121 | 121 | // file system I/O API. |
| 122 | 122 | self.os_data.fs_end_request = fs.RequestNode{ |
| 123 | .prev = undefined, | |
| 123 | 124 | .next = undefined, |
| 124 | 125 | .data = fs.Request{ |
| 125 | 126 | .msg = fs.Request.Msg.End, |
| ... | ... | @@ -206,6 +207,7 @@ pub const Loop = struct { |
| 206 | 207 | .udata = @ptrToInt(&eventfd_node.data.base), |
| 207 | 208 | }, |
| 208 | 209 | }, |
| 210 | .prev = undefined, | |
| 209 | 211 | .next = undefined, |
| 210 | 212 | }; |
| 211 | 213 | self.available_eventfd_resume_nodes.push(eventfd_node); |
| ... | ... | @@ -270,6 +272,7 @@ pub const Loop = struct { |
| 270 | 272 | // this one is for sending events |
| 271 | 273 | .completion_key = @ptrToInt(&eventfd_node.data.base), |
| 272 | 274 | }, |
| 275 | .prev = undefined, | |
| 273 | 276 | .next = undefined, |
| 274 | 277 | }; |
| 275 | 278 | self.available_eventfd_resume_nodes.push(eventfd_node); |
| ... | ... | @@ -422,6 +425,12 @@ pub const Loop = struct { |
| 422 | 425 | self.dispatch(); |
| 423 | 426 | } |
| 424 | 427 | |
| 428 | pub fn cancelOnNextTick(self: *Loop, node: *NextTickNode) void { | |
| 429 | if (self.next_tick_queue.remove(node)) { | |
| 430 | self.finishOneEvent(); | |
| 431 | } | |
| 432 | } | |
| 433 | ||
| 425 | 434 | pub fn run(self: *Loop) void { |
| 426 | 435 | self.finishOneEvent(); // the reference we start with |
| 427 | 436 | |
| ... | ... | @@ -443,6 +452,7 @@ pub const Loop = struct { |
| 443 | 452 | suspend |p| { |
| 444 | 453 | handle.* = p; |
| 445 | 454 | var my_tick_node = Loop.NextTickNode{ |
| 455 | .prev = undefined, | |
| 446 | 456 | .next = undefined, |
| 447 | 457 | .data = p, |
| 448 | 458 | }; |
| ... | ... | @@ -464,6 +474,7 @@ pub const Loop = struct { |
| 464 | 474 | pub async fn yield(self: *Loop) void { |
| 465 | 475 | suspend |p| { |
| 466 | 476 | var my_tick_node = Loop.NextTickNode{ |
| 477 | .prev = undefined, | |
| 467 | 478 | .next = undefined, |
| 468 | 479 | .data = p, |
| 469 | 480 | }; |
std/event/rwlock.zig+2| ... | ... | @@ -101,6 +101,7 @@ pub const RwLock = struct { |
| 101 | 101 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 102 | 102 | var my_tick_node = Loop.NextTickNode{ |
| 103 | 103 | .data = handle, |
| 104 | .prev = undefined, | |
| 104 | 105 | .next = undefined, |
| 105 | 106 | }; |
| 106 | 107 | |
| ... | ... | @@ -133,6 +134,7 @@ pub const RwLock = struct { |
| 133 | 134 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 134 | 135 | var my_tick_node = Loop.NextTickNode{ |
| 135 | 136 | .data = handle, |
| 137 | .prev = undefined, | |
| 136 | 138 | .next = undefined, |
| 137 | 139 | }; |
| 138 | 140 |
std/index.zig-1| ... | ... | @@ -6,7 +6,6 @@ pub const Buffer = @import("buffer.zig").Buffer; |
| 6 | 6 | pub const BufferOutStream = @import("buffer.zig").BufferOutStream; |
| 7 | 7 | pub const HashMap = @import("hash_map.zig").HashMap; |
| 8 | 8 | pub const LinkedList = @import("linked_list.zig").LinkedList; |
| 9 | pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList; | |
| 10 | 9 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; |
| 11 | 10 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 12 | 11 | pub const Mutex = @import("mutex.zig").Mutex; |
std/linked_list.zig+4-97| ... | ... | @@ -4,18 +4,8 @@ const assert = debug.assert; |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | const Allocator = mem.Allocator; |
| 6 | 6 | |
| 7 | /// Generic non-intrusive doubly linked list. | |
| 8 | pub fn LinkedList(comptime T: type) type { | |
| 9 | return BaseLinkedList(T, void, ""); | |
| 10 | } | |
| 11 | ||
| 12 | /// Generic intrusive doubly linked list. | |
| 13 | pub fn IntrusiveLinkedList(comptime ParentType: type, comptime field_name: []const u8) type { | |
| 14 | return BaseLinkedList(void, ParentType, field_name); | |
| 15 | } | |
| 16 | ||
| 17 | 7 | /// Generic doubly linked list. |
| 18 | fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_name: []const u8) type { | |
| 8 | pub fn LinkedList(comptime T: type) type { | |
| 19 | 9 | return struct { |
| 20 | 10 | const Self = this; |
| 21 | 11 | |
| ... | ... | @@ -25,23 +15,13 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na |
| 25 | 15 | next: ?*Node, |
| 26 | 16 | data: T, |
| 27 | 17 | |
| 28 | pub fn init(value: *const T) Node { | |
| 18 | pub fn init(data: T) Node { | |
| 29 | 19 | return Node{ |
| 30 | 20 | .prev = null, |
| 31 | 21 | .next = null, |
| 32 | .data = value.*, | |
| 22 | .data = data, | |
| 33 | 23 | }; |
| 34 | 24 | } |
| 35 | ||
| 36 | pub fn initIntrusive() Node { | |
| 37 | // TODO: when #678 is solved this can become `init`. | |
| 38 | return Node.init({}); | |
| 39 | } | |
| 40 | ||
| 41 | pub fn toData(node: *Node) *ParentType { | |
| 42 | comptime assert(isIntrusive()); | |
| 43 | return @fieldParentPtr(ParentType, field_name, node); | |
| 44 | } | |
| 45 | 25 | }; |
| 46 | 26 | |
| 47 | 27 | first: ?*Node, |
| ... | ... | @@ -60,10 +40,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na |
| 60 | 40 | }; |
| 61 | 41 | } |
| 62 | 42 | |
| 63 | fn isIntrusive() bool { | |
| 64 | return ParentType != void or field_name.len != 0; | |
| 65 | } | |
| 66 | ||
| 67 | 43 | /// Insert a new node after an existing one. |
| 68 | 44 | /// |
| 69 | 45 | /// Arguments: |
| ... | ... | @@ -192,7 +168,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na |
| 192 | 168 | /// Returns: |
| 193 | 169 | /// A pointer to the new node. |
| 194 | 170 | pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node { |
| 195 | comptime assert(!isIntrusive()); | |
| 196 | 171 | return allocator.create(Node(undefined)); |
| 197 | 172 | } |
| 198 | 173 | |
| ... | ... | @@ -202,7 +177,6 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na |
| 202 | 177 | /// node: Pointer to the node to deallocate. |
| 203 | 178 | /// allocator: Dynamic memory allocator. |
| 204 | 179 | pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void { |
| 205 | comptime assert(!isIntrusive()); | |
| 206 | 180 | allocator.destroy(node); |
| 207 | 181 | } |
| 208 | 182 | |
| ... | ... | @@ -214,8 +188,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na |
| 214 | 188 | /// |
| 215 | 189 | /// Returns: |
| 216 | 190 | /// A pointer to the new node. |
| 217 | pub fn createNode(list: *Self, data: *const T, allocator: *Allocator) !*Node { | |
| 218 | comptime assert(!isIntrusive()); | |
| 191 | pub fn createNode(list: *Self, data: T, allocator: *Allocator) !*Node { | |
| 219 | 192 | var node = try list.allocateNode(allocator); |
| 220 | 193 | node.* = Node.init(data); |
| 221 | 194 | return node; |
| ... | ... | @@ -274,69 +247,3 @@ test "basic linked list test" { |
| 274 | 247 | assert(list.last.?.data == 4); |
| 275 | 248 | assert(list.len == 2); |
| 276 | 249 | } |
| 277 | ||
| 278 | const ElementList = IntrusiveLinkedList(Element, "link"); | |
| 279 | const Element = struct { | |
| 280 | value: u32, | |
| 281 | link: IntrusiveLinkedList(Element, "link").Node, | |
| 282 | }; | |
| 283 | ||
| 284 | test "basic intrusive linked list test" { | |
| 285 | const allocator = debug.global_allocator; | |
| 286 | var list = ElementList.init(); | |
| 287 | ||
| 288 | var one = Element{ | |
| 289 | .value = 1, | |
| 290 | .link = ElementList.Node.initIntrusive(), | |
| 291 | }; | |
| 292 | var two = Element{ | |
| 293 | .value = 2, | |
| 294 | .link = ElementList.Node.initIntrusive(), | |
| 295 | }; | |
| 296 | var three = Element{ | |
| 297 | .value = 3, | |
| 298 | .link = ElementList.Node.initIntrusive(), | |
| 299 | }; | |
| 300 | var four = Element{ | |
| 301 | .value = 4, | |
| 302 | .link = ElementList.Node.initIntrusive(), | |
| 303 | }; | |
| 304 | var five = Element{ | |
| 305 | .value = 5, | |
| 306 | .link = ElementList.Node.initIntrusive(), | |
| 307 | }; | |
| 308 | ||
| 309 | list.append(&two.link); // {2} | |
| 310 | list.append(&five.link); // {2, 5} | |
| 311 | list.prepend(&one.link); // {1, 2, 5} | |
| 312 | list.insertBefore(&five.link, &four.link); // {1, 2, 4, 5} | |
| 313 | list.insertAfter(&two.link, &three.link); // {1, 2, 3, 4, 5} | |
| 314 | ||
| 315 | // Traverse forwards. | |
| 316 | { | |
| 317 | var it = list.first; | |
| 318 | var index: u32 = 1; | |
| 319 | while (it) |node| : (it = node.next) { | |
| 320 | assert(node.toData().value == index); | |
| 321 | index += 1; | |
| 322 | } | |
| 323 | } | |
| 324 | ||
| 325 | // Traverse backwards. | |
| 326 | { | |
| 327 | var it = list.last; | |
| 328 | var index: u32 = 1; | |
| 329 | while (it) |node| : (it = node.prev) { | |
| 330 | assert(node.toData().value == (6 - index)); | |
| 331 | index += 1; | |
| 332 | } | |
| 333 | } | |
| 334 | ||
| 335 | var first = list.popFirst(); // {2, 3, 4, 5} | |
| 336 | var last = list.pop(); // {2, 3, 4} | |
| 337 | list.remove(&three.link); // {2, 4} | |
| 338 | ||
| 339 | assert(list.first.?.toData().value == 2); | |
| 340 | assert(list.last.?.toData().value == 4); | |
| 341 | assert(list.len == 2); | |
| 342 | } |