| author | |
| committer | |
| log | 5dfcd09e496160054d4f333500d084e35f2fbdd2 |
| tree | 08f4177b091848c1b78ec8e7c1d7973ee230391a |
| parent | 7f6e97cb26ffabbc192e7ccdf44aebbbc3be751d |
15 files changed, 756 insertions(+), 330 deletions(-)
src-self-hosted/compilation.zig+112-46| ... | ... | @@ -230,6 +230,8 @@ pub const Compilation = struct { |
| 230 | 230 | |
| 231 | 231 | c_int_types: [CInt.list.len]*Type.Int, |
| 232 | 232 | |
| 233 | fs_watch: *fs.Watch(*Scope.Root), | |
| 234 | ||
| 233 | 235 | const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); |
| 234 | 236 | const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); |
| 235 | 237 | const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); |
| ... | ... | @@ -285,6 +287,7 @@ pub const Compilation = struct { |
| 285 | 287 | LibCMissingDynamicLinker, |
| 286 | 288 | InvalidDarwinVersionString, |
| 287 | 289 | UnsupportedLinkArchitecture, |
| 290 | UserResourceLimitReached, | |
| 288 | 291 | }; |
| 289 | 292 | |
| 290 | 293 | pub const Event = union(enum) { |
| ... | ... | @@ -331,7 +334,8 @@ pub const Compilation = struct { |
| 331 | 334 | zig_lib_dir: []const u8, |
| 332 | 335 | ) !*Compilation { |
| 333 | 336 | const loop = event_loop_local.loop; |
| 334 | const comp = try event_loop_local.loop.allocator.create(Compilation{ | |
| 337 | const comp = try event_loop_local.loop.allocator.createOne(Compilation); | |
| 338 | comp.* = Compilation{ | |
| 335 | 339 | .loop = loop, |
| 336 | 340 | .arena_allocator = std.heap.ArenaAllocator.init(loop.allocator), |
| 337 | 341 | .event_loop_local = event_loop_local, |
| ... | ... | @@ -376,7 +380,7 @@ pub const Compilation = struct { |
| 376 | 380 | .fn_link_set = event.Locked(FnLinkSet).init(loop, FnLinkSet.init()), |
| 377 | 381 | .windows_subsystem_windows = false, |
| 378 | 382 | .windows_subsystem_console = false, |
| 379 | .link_libs_list = undefined, | |
| 383 | .link_libs_list = ArrayList(*LinkLib).init(comp.arena()), | |
| 380 | 384 | .libc_link_lib = null, |
| 381 | 385 | .err_color = errmsg.Color.Auto, |
| 382 | 386 | .darwin_frameworks = [][]const u8{}, |
| ... | ... | @@ -417,8 +421,10 @@ pub const Compilation = struct { |
| 417 | 421 | .override_libc = null, |
| 418 | 422 | .destroy_handle = undefined, |
| 419 | 423 | .have_err_ret_tracing = false, |
| 420 | .primitive_type_table = undefined, | |
| 421 | }); | |
| 424 | .primitive_type_table = TypeTable.init(comp.arena()), | |
| 425 | ||
| 426 | .fs_watch = undefined, | |
| 427 | }; | |
| 422 | 428 | errdefer { |
| 423 | 429 | comp.int_type_table.private_data.deinit(); |
| 424 | 430 | comp.array_type_table.private_data.deinit(); |
| ... | ... | @@ -431,9 +437,7 @@ pub const Compilation = struct { |
| 431 | 437 | comp.name = try Buffer.init(comp.arena(), name); |
| 432 | 438 | comp.llvm_triple = try target.getTriple(comp.arena()); |
| 433 | 439 | comp.llvm_target = try Target.llvmTargetFromTriple(comp.llvm_triple); |
| 434 | comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); | |
| 435 | 440 | comp.zig_std_dir = try std.os.path.join(comp.arena(), zig_lib_dir, "std"); |
| 436 | comp.primitive_type_table = TypeTable.init(comp.arena()); | |
| 437 | 441 | |
| 438 | 442 | const opt_level = switch (build_mode) { |
| 439 | 443 | builtin.Mode.Debug => llvm.CodeGenLevelNone, |
| ... | ... | @@ -485,6 +489,9 @@ pub const Compilation = struct { |
| 485 | 489 | comp.root_package = try Package.create(comp.arena(), ".", ""); |
| 486 | 490 | } |
| 487 | 491 | |
| 492 | comp.fs_watch = try fs.Watch(*Scope.Root).create(loop, 16); | |
| 493 | errdefer comp.fs_watch.destroy(); | |
| 494 | ||
| 488 | 495 | try comp.initTypes(); |
| 489 | 496 | |
| 490 | 497 | comp.destroy_handle = try async<loop.allocator> comp.internalDeinit(); |
| ... | ... | @@ -686,6 +693,7 @@ pub const Compilation = struct { |
| 686 | 693 | os.deleteTree(self.arena(), tmp_dir) catch {}; |
| 687 | 694 | } else |_| {}; |
| 688 | 695 | |
| 696 | self.fs_watch.destroy(); | |
| 689 | 697 | self.events.destroy(); |
| 690 | 698 | |
| 691 | 699 | llvm.DisposeMessage(self.target_layout_str); |
| ... | ... | @@ -720,7 +728,9 @@ pub const Compilation = struct { |
| 720 | 728 | var build_result = await (async self.initialCompile() catch unreachable); |
| 721 | 729 | |
| 722 | 730 | while (true) { |
| 723 | const link_result = if (build_result) self.maybeLink() else |err| err; | |
| 731 | const link_result = if (build_result) blk: { | |
| 732 | break :blk await (async self.maybeLink() catch unreachable); | |
| 733 | } else |err| err; | |
| 724 | 734 | // this makes a handy error return trace and stack trace in debug mode |
| 725 | 735 | if (std.debug.runtime_safety) { |
| 726 | 736 | link_result catch unreachable; |
| ... | ... | @@ -745,9 +755,35 @@ pub const Compilation = struct { |
| 745 | 755 | await (async self.events.put(Event{ .Error = err }) catch unreachable); |
| 746 | 756 | } |
| 747 | 757 | |
| 758 | // First, get an item from the watch channel, waiting on the channel. | |
| 748 | 759 | 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); | |
| 760 | { | |
| 761 | const ev = await (async self.fs_watch.channel.get() catch unreachable); | |
| 762 | const root_scope = switch (ev) { | |
| 763 | fs.Watch(*Scope.Root).Event.CloseWrite => |x| x, | |
| 764 | fs.Watch(*Scope.Root).Event.Err => |err| { | |
| 765 | build_result = err; | |
| 766 | continue; | |
| 767 | }, | |
| 768 | }; | |
| 769 | group.call(rebuildFile, self, root_scope) catch |err| { | |
| 770 | build_result = err; | |
| 771 | continue; | |
| 772 | }; | |
| 773 | } | |
| 774 | // Next, get all the items from the channel that are buffered up. | |
| 775 | while (await (async self.fs_watch.channel.getOrNull() catch unreachable)) |ev| { | |
| 776 | const root_scope = switch (ev) { | |
| 777 | fs.Watch(*Scope.Root).Event.CloseWrite => |x| x, | |
| 778 | fs.Watch(*Scope.Root).Event.Err => |err| { | |
| 779 | build_result = err; | |
| 780 | continue; | |
| 781 | }, | |
| 782 | }; | |
| 783 | group.call(rebuildFile, self, root_scope) catch |err| { | |
| 784 | build_result = err; | |
| 785 | continue; | |
| 786 | }; | |
| 751 | 787 | } |
| 752 | 788 | build_result = await (async group.wait() catch unreachable); |
| 753 | 789 | } |
| ... | ... | @@ -757,11 +793,11 @@ pub const Compilation = struct { |
| 757 | 793 | const tree_scope = blk: { |
| 758 | 794 | const source_code = (await (async fs.readFile( |
| 759 | 795 | self.loop, |
| 760 | root_src_real_path, | |
| 796 | root_scope.realpath, | |
| 761 | 797 | max_src_size, |
| 762 | 798 | ) catch unreachable)) catch |err| { |
| 763 | try printError("unable to open '{}': {}", root_src_real_path, err); | |
| 764 | return err; | |
| 799 | try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", @errorName(err)); | |
| 800 | return; | |
| 765 | 801 | }; |
| 766 | 802 | errdefer self.gpa().free(source_code); |
| 767 | 803 | |
| ... | ... | @@ -793,9 +829,9 @@ pub const Compilation = struct { |
| 793 | 829 | var decl_group = event.Group(BuildError!void).init(self.loop); |
| 794 | 830 | defer decl_group.deinit(); |
| 795 | 831 | |
| 796 | try self.rebuildChangedDecls( | |
| 832 | try await try async self.rebuildChangedDecls( | |
| 797 | 833 | &decl_group, |
| 798 | locked_table, | |
| 834 | locked_table.value, | |
| 799 | 835 | root_scope.decls, |
| 800 | 836 | &tree_scope.tree.root_node.decls, |
| 801 | 837 | tree_scope, |
| ... | ... | @@ -809,7 +845,7 @@ pub const Compilation = struct { |
| 809 | 845 | group: *event.Group(BuildError!void), |
| 810 | 846 | locked_table: *Decl.Table, |
| 811 | 847 | decl_scope: *Scope.Decls, |
| 812 | ast_decls: &ast.Node.Root.DeclList, | |
| 848 | ast_decls: *ast.Node.Root.DeclList, | |
| 813 | 849 | tree_scope: *Scope.AstTree, |
| 814 | 850 | ) !void { |
| 815 | 851 | var existing_decls = try locked_table.clone(); |
| ... | ... | @@ -824,14 +860,14 @@ pub const Compilation = struct { |
| 824 | 860 | |
| 825 | 861 | // TODO connect existing comptime decls to updated source files |
| 826 | 862 | |
| 827 | try self.prelink_group.call(addCompTimeBlock, self, &decl_scope.base, comptime_node); | |
| 863 | try self.prelink_group.call(addCompTimeBlock, self, tree_scope, &decl_scope.base, comptime_node); | |
| 828 | 864 | }, |
| 829 | 865 | ast.Node.Id.VarDecl => @panic("TODO"), |
| 830 | 866 | ast.Node.Id.FnProto => { |
| 831 | 867 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); |
| 832 | 868 | |
| 833 | 869 | const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else { |
| 834 | try self.addCompileError(root_scope, Span{ | |
| 870 | try self.addCompileError(tree_scope, Span{ | |
| 835 | 871 | .first = fn_proto.fn_token, |
| 836 | 872 | .last = fn_proto.fn_token + 1, |
| 837 | 873 | }, "missing function name"); |
| ... | ... | @@ -856,10 +892,12 @@ pub const Compilation = struct { |
| 856 | 892 | .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token), |
| 857 | 893 | .resolution = event.Future(BuildError!void).init(self.loop), |
| 858 | 894 | .parent_scope = &decl_scope.base, |
| 895 | .tree_scope = tree_scope, | |
| 859 | 896 | }, |
| 860 | 897 | .value = Decl.Fn.Val{ .Unresolved = {} }, |
| 861 | 898 | .fn_proto = fn_proto, |
| 862 | 899 | }); |
| 900 | tree_scope.base.ref(); | |
| 863 | 901 | errdefer self.gpa().destroy(fn_decl); |
| 864 | 902 | |
| 865 | 903 | try group.call(addTopLevelDecl, self, &fn_decl.base, locked_table); |
| ... | ... | @@ -883,8 +921,8 @@ pub const Compilation = struct { |
| 883 | 921 | const root_scope = blk: { |
| 884 | 922 | // TODO async/await os.path.real |
| 885 | 923 | 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; | |
| 924 | try self.addCompileErrorCli(root_src_path, "unable to open: {}", @errorName(err)); | |
| 925 | return; | |
| 888 | 926 | }; |
| 889 | 927 | errdefer self.gpa().free(root_src_real_path); |
| 890 | 928 | |
| ... | ... | @@ -892,7 +930,8 @@ pub const Compilation = struct { |
| 892 | 930 | }; |
| 893 | 931 | defer root_scope.base.deref(self); |
| 894 | 932 | |
| 895 | try self.rebuildFile(root_scope); | |
| 933 | assert((try await try async self.fs_watch.addFile(root_scope.realpath, root_scope)) == null); | |
| 934 | try await try async self.rebuildFile(root_scope); | |
| 896 | 935 | } |
| 897 | 936 | } |
| 898 | 937 | |
| ... | ... | @@ -917,6 +956,7 @@ pub const Compilation = struct { |
| 917 | 956 | /// caller takes ownership of resulting Code |
| 918 | 957 | async fn genAndAnalyzeCode( |
| 919 | 958 | comp: *Compilation, |
| 959 | tree_scope: *Scope.AstTree, | |
| 920 | 960 | scope: *Scope, |
| 921 | 961 | node: *ast.Node, |
| 922 | 962 | expected_type: ?*Type, |
| ... | ... | @@ -924,6 +964,7 @@ pub const Compilation = struct { |
| 924 | 964 | const unanalyzed_code = try await (async ir.gen( |
| 925 | 965 | comp, |
| 926 | 966 | node, |
| 967 | tree_scope, | |
| 927 | 968 | scope, |
| 928 | 969 | ) catch unreachable); |
| 929 | 970 | defer unanalyzed_code.destroy(comp.gpa()); |
| ... | ... | @@ -950,6 +991,7 @@ pub const Compilation = struct { |
| 950 | 991 | |
| 951 | 992 | async fn addCompTimeBlock( |
| 952 | 993 | comp: *Compilation, |
| 994 | tree_scope: *Scope.AstTree, | |
| 953 | 995 | scope: *Scope, |
| 954 | 996 | comptime_node: *ast.Node.Comptime, |
| 955 | 997 | ) !void { |
| ... | ... | @@ -958,6 +1000,7 @@ pub const Compilation = struct { |
| 958 | 1000 | |
| 959 | 1001 | const analyzed_code = (await (async genAndAnalyzeCode( |
| 960 | 1002 | comp, |
| 1003 | tree_scope, | |
| 961 | 1004 | scope, |
| 962 | 1005 | comptime_node.expr, |
| 963 | 1006 | &void_type.base, |
| ... | ... | @@ -975,25 +1018,37 @@ pub const Compilation = struct { |
| 975 | 1018 | decl: *Decl, |
| 976 | 1019 | locked_table: *Decl.Table, |
| 977 | 1020 | ) !void { |
| 978 | const tree = decl.findRootScope().tree; | |
| 979 | const is_export = decl.isExported(tree); | |
| 1021 | const is_export = decl.isExported(decl.tree_scope.tree); | |
| 980 | 1022 | |
| 981 | 1023 | if (is_export) { |
| 982 | 1024 | try self.prelink_group.call(verifyUniqueSymbol, self, decl); |
| 983 | 1025 | try self.prelink_group.call(resolveDecl, self, decl); |
| 984 | 1026 | } |
| 985 | 1027 | |
| 986 | if (try locked_table.put(decl.name, decl)) |other_decl| { | |
| 987 | try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name); | |
| 1028 | const gop = try locked_table.getOrPut(decl.name); | |
| 1029 | if (gop.found_existing) { | |
| 1030 | try self.addCompileError(decl.tree_scope, decl.getSpan(), "redefinition of '{}'", decl.name); | |
| 988 | 1031 | // TODO note: other definition here |
| 1032 | } else { | |
| 1033 | gop.kv.value = decl; | |
| 989 | 1034 | } |
| 990 | 1035 | } |
| 991 | 1036 | |
| 992 | fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void { | |
| 1037 | fn addCompileError(self: *Compilation, tree_scope: *Scope.AstTree, span: Span, comptime fmt: []const u8, args: ...) !void { | |
| 1038 | const text = try std.fmt.allocPrint(self.gpa(), fmt, args); | |
| 1039 | errdefer self.gpa().free(text); | |
| 1040 | ||
| 1041 | const msg = try Msg.createFromScope(self, tree_scope, span, text); | |
| 1042 | errdefer msg.destroy(); | |
| 1043 | ||
| 1044 | try self.prelink_group.call(addCompileErrorAsync, self, msg); | |
| 1045 | } | |
| 1046 | ||
| 1047 | fn addCompileErrorCli(self: *Compilation, realpath: []const u8, comptime fmt: []const u8, args: ...) !void { | |
| 993 | 1048 | const text = try std.fmt.allocPrint(self.gpa(), fmt, args); |
| 994 | 1049 | errdefer self.gpa().free(text); |
| 995 | 1050 | |
| 996 | const msg = try Msg.createFromScope(self, root, span, text); | |
| 1051 | const msg = try Msg.createFromCli(self, realpath, text); | |
| 997 | 1052 | errdefer msg.destroy(); |
| 998 | 1053 | |
| 999 | 1054 | try self.prelink_group.call(addCompileErrorAsync, self, msg); |
| ... | ... | @@ -1017,7 +1072,7 @@ pub const Compilation = struct { |
| 1017 | 1072 | |
| 1018 | 1073 | if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { |
| 1019 | 1074 | try self.addCompileError( |
| 1020 | decl.findRootScope(), | |
| 1075 | decl.tree_scope, | |
| 1021 | 1076 | decl.getSpan(), |
| 1022 | 1077 | "exported symbol collision: '{}'", |
| 1023 | 1078 | decl.name, |
| ... | ... | @@ -1141,18 +1196,24 @@ pub const Compilation = struct { |
| 1141 | 1196 | } |
| 1142 | 1197 | |
| 1143 | 1198 | /// Returns a value which has been ref()'d once |
| 1144 | async fn analyzeConstValue(comp: *Compilation, scope: *Scope, node: *ast.Node, expected_type: *Type) !*Value { | |
| 1145 | const analyzed_code = try await (async comp.genAndAnalyzeCode(scope, node, expected_type) catch unreachable); | |
| 1199 | async fn analyzeConstValue( | |
| 1200 | comp: *Compilation, | |
| 1201 | tree_scope: *Scope.AstTree, | |
| 1202 | scope: *Scope, | |
| 1203 | node: *ast.Node, | |
| 1204 | expected_type: *Type, | |
| 1205 | ) !*Value { | |
| 1206 | const analyzed_code = try await (async comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type) catch unreachable); | |
| 1146 | 1207 | defer analyzed_code.destroy(comp.gpa()); |
| 1147 | 1208 | |
| 1148 | 1209 | return analyzed_code.getCompTimeResult(comp); |
| 1149 | 1210 | } |
| 1150 | 1211 | |
| 1151 | async fn analyzeTypeExpr(comp: *Compilation, scope: *Scope, node: *ast.Node) !*Type { | |
| 1212 | async fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type { | |
| 1152 | 1213 | const meta_type = &Type.MetaType.get(comp).base; |
| 1153 | 1214 | defer meta_type.base.deref(comp); |
| 1154 | 1215 | |
| 1155 | const result_val = try await (async comp.analyzeConstValue(scope, node, meta_type) catch unreachable); | |
| 1216 | const result_val = try await (async comp.analyzeConstValue(tree_scope, scope, node, meta_type) catch unreachable); | |
| 1156 | 1217 | errdefer result_val.base.deref(comp); |
| 1157 | 1218 | |
| 1158 | 1219 | return result_val.cast(Type).?; |
| ... | ... | @@ -1168,13 +1229,6 @@ pub const Compilation = struct { |
| 1168 | 1229 | } |
| 1169 | 1230 | }; |
| 1170 | 1231 | |
| 1171 | fn printError(comptime format: []const u8, args: ...) !void { | |
| 1172 | var stderr_file = try std.io.getStdErr(); | |
| 1173 | var stderr_file_out_stream = std.io.FileOutStream.init(&stderr_file); | |
| 1174 | const out_stream = &stderr_file_out_stream.stream; | |
| 1175 | try out_stream.print(format, args); | |
| 1176 | } | |
| 1177 | ||
| 1178 | 1232 | fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib { |
| 1179 | 1233 | if (optional_token_index) |token_index| { |
| 1180 | 1234 | const token = tree.tokens.at(token_index); |
| ... | ... | @@ -1198,12 +1252,14 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void { |
| 1198 | 1252 | } |
| 1199 | 1253 | |
| 1200 | 1254 | async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { |
| 1255 | const tree_scope = fn_decl.base.tree_scope; | |
| 1256 | ||
| 1201 | 1257 | const body_node = fn_decl.fn_proto.body_node orelse return await (async generateDeclFnProto(comp, fn_decl) catch unreachable); |
| 1202 | 1258 | |
| 1203 | 1259 | const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); |
| 1204 | 1260 | defer fndef_scope.base.deref(comp); |
| 1205 | 1261 | |
| 1206 | const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); | |
| 1262 | const fn_type = try await (async analyzeFnType(comp, tree_scope, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); | |
| 1207 | 1263 | defer fn_type.base.base.deref(comp); |
| 1208 | 1264 | |
| 1209 | 1265 | var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); |
| ... | ... | @@ -1216,18 +1272,17 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { |
| 1216 | 1272 | symbol_name_consumed = true; |
| 1217 | 1273 | |
| 1218 | 1274 | // Define local parameter variables |
| 1219 | const root_scope = fn_decl.base.findRootScope(); | |
| 1220 | 1275 | for (fn_type.key.data.Normal.params) |param, i| { |
| 1221 | 1276 | //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); |
| 1222 | 1277 | const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*); |
| 1223 | 1278 | const name_token = param_decl.name_token orelse { |
| 1224 | try comp.addCompileError(root_scope, Span{ | |
| 1279 | try comp.addCompileError(tree_scope, Span{ | |
| 1225 | 1280 | .first = param_decl.firstToken(), |
| 1226 | 1281 | .last = param_decl.type_node.firstToken(), |
| 1227 | 1282 | }, "missing parameter name"); |
| 1228 | 1283 | return error.SemanticAnalysisFailed; |
| 1229 | 1284 | }; |
| 1230 | const param_name = root_scope.tree.tokenSlice(name_token); | |
| 1285 | const param_name = tree_scope.tree.tokenSlice(name_token); | |
| 1231 | 1286 | |
| 1232 | 1287 | // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { |
| 1233 | 1288 | // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); |
| ... | ... | @@ -1249,6 +1304,7 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { |
| 1249 | 1304 | } |
| 1250 | 1305 | |
| 1251 | 1306 | const analyzed_code = try await (async comp.genAndAnalyzeCode( |
| 1307 | tree_scope, | |
| 1252 | 1308 | fn_val.child_scope, |
| 1253 | 1309 | body_node, |
| 1254 | 1310 | fn_type.key.data.Normal.return_type, |
| ... | ... | @@ -1279,12 +1335,17 @@ fn getZigDir(allocator: *mem.Allocator) ![]u8 { |
| 1279 | 1335 | return os.getAppDataDir(allocator, "zig"); |
| 1280 | 1336 | } |
| 1281 | 1337 | |
| 1282 | async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn { | |
| 1338 | async fn analyzeFnType( | |
| 1339 | comp: *Compilation, | |
| 1340 | tree_scope: *Scope.AstTree, | |
| 1341 | scope: *Scope, | |
| 1342 | fn_proto: *ast.Node.FnProto, | |
| 1343 | ) !*Type.Fn { | |
| 1283 | 1344 | const return_type_node = switch (fn_proto.return_type) { |
| 1284 | 1345 | ast.Node.FnProto.ReturnType.Explicit => |n| n, |
| 1285 | 1346 | ast.Node.FnProto.ReturnType.InferErrorSet => |n| n, |
| 1286 | 1347 | }; |
| 1287 | const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable); | |
| 1348 | const return_type = try await (async comp.analyzeTypeExpr(tree_scope, scope, return_type_node) catch unreachable); | |
| 1288 | 1349 | return_type.base.deref(comp); |
| 1289 | 1350 | |
| 1290 | 1351 | var params = ArrayList(Type.Fn.Param).init(comp.gpa()); |
| ... | ... | @@ -1300,7 +1361,7 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn |
| 1300 | 1361 | var it = fn_proto.params.iterator(0); |
| 1301 | 1362 | while (it.next()) |param_node_ptr| { |
| 1302 | 1363 | const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; |
| 1303 | const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable); | |
| 1364 | const param_type = try await (async comp.analyzeTypeExpr(tree_scope, scope, param_node.type_node) catch unreachable); | |
| 1304 | 1365 | errdefer param_type.base.deref(comp); |
| 1305 | 1366 | try params.append(Type.Fn.Param{ |
| 1306 | 1367 | .typ = param_type, |
| ... | ... | @@ -1337,7 +1398,12 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn |
| 1337 | 1398 | } |
| 1338 | 1399 | |
| 1339 | 1400 | async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { |
| 1340 | const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable); | |
| 1401 | const fn_type = try await (async analyzeFnType( | |
| 1402 | comp, | |
| 1403 | fn_decl.base.tree_scope, | |
| 1404 | fn_decl.base.parent_scope, | |
| 1405 | fn_decl.fn_proto, | |
| 1406 | ) catch unreachable); | |
| 1341 | 1407 | defer fn_type.base.base.deref(comp); |
| 1342 | 1408 | |
| 1343 | 1409 | var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name); |
src-self-hosted/decl.zig+2| ... | ... | @@ -16,6 +16,8 @@ pub const Decl = struct { |
| 16 | 16 | visib: Visib, |
| 17 | 17 | resolution: event.Future(Compilation.BuildError!void), |
| 18 | 18 | parent_scope: *Scope, |
| 19 | ||
| 20 | // TODO when we destroy the decl, deref the tree scope | |
| 19 | 21 | tree_scope: *Scope.AstTree, |
| 20 | 22 | |
| 21 | 23 | pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); |
src-self-hosted/errmsg.zig+75-28| ... | ... | @@ -33,35 +33,48 @@ pub const Span = struct { |
| 33 | 33 | }; |
| 34 | 34 | |
| 35 | 35 | pub const Msg = struct { |
| 36 | span: Span, | |
| 37 | 36 | text: []u8, |
| 37 | realpath: []u8, | |
| 38 | 38 | data: Data, |
| 39 | 39 | |
| 40 | 40 | const Data = union(enum) { |
| 41 | Cli: Cli, | |
| 41 | 42 | PathAndTree: PathAndTree, |
| 42 | 43 | ScopeAndComp: ScopeAndComp, |
| 43 | 44 | }; |
| 44 | 45 | |
| 45 | 46 | const PathAndTree = struct { |
| 46 | realpath: []const u8, | |
| 47 | span: Span, | |
| 47 | 48 | tree: *ast.Tree, |
| 48 | 49 | allocator: *mem.Allocator, |
| 49 | 50 | }; |
| 50 | 51 | |
| 51 | 52 | const ScopeAndComp = struct { |
| 53 | span: Span, | |
| 52 | 54 | tree_scope: *Scope.AstTree, |
| 53 | 55 | compilation: *Compilation, |
| 54 | 56 | }; |
| 55 | 57 | |
| 58 | const Cli = struct { | |
| 59 | allocator: *mem.Allocator, | |
| 60 | }; | |
| 61 | ||
| 56 | 62 | pub fn destroy(self: *Msg) void { |
| 57 | 63 | switch (self.data) { |
| 64 | Data.Cli => |cli| { | |
| 65 | cli.allocator.free(self.text); | |
| 66 | cli.allocator.free(self.realpath); | |
| 67 | cli.allocator.destroy(self); | |
| 68 | }, | |
| 58 | 69 | Data.PathAndTree => |path_and_tree| { |
| 59 | 70 | path_and_tree.allocator.free(self.text); |
| 71 | path_and_tree.allocator.free(self.realpath); | |
| 60 | 72 | path_and_tree.allocator.destroy(self); |
| 61 | 73 | }, |
| 62 | 74 | Data.ScopeAndComp => |scope_and_comp| { |
| 63 | 75 | scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation); |
| 64 | 76 | scope_and_comp.compilation.gpa().free(self.text); |
| 77 | scope_and_comp.compilation.gpa().free(self.realpath); | |
| 65 | 78 | scope_and_comp.compilation.gpa().destroy(self); |
| 66 | 79 | }, |
| 67 | 80 | } |
| ... | ... | @@ -69,6 +82,7 @@ pub const Msg = struct { |
| 69 | 82 | |
| 70 | 83 | fn getAllocator(self: *const Msg) *mem.Allocator { |
| 71 | 84 | switch (self.data) { |
| 85 | Data.Cli => |cli| return cli.allocator, | |
| 72 | 86 | Data.PathAndTree => |path_and_tree| { |
| 73 | 87 | return path_and_tree.allocator; |
| 74 | 88 | }, |
| ... | ... | @@ -78,19 +92,9 @@ pub const Msg = struct { |
| 78 | 92 | } |
| 79 | 93 | } |
| 80 | 94 | |
| 81 | pub fn getRealPath(self: *const Msg) []const u8 { | |
| 82 | switch (self.data) { | |
| 83 | Data.PathAndTree => |path_and_tree| { | |
| 84 | return path_and_tree.realpath; | |
| 85 | }, | |
| 86 | Data.ScopeAndComp => |scope_and_comp| { | |
| 87 | return scope_and_comp.tree_scope.root().realpath; | |
| 88 | }, | |
| 89 | } | |
| 90 | } | |
| 91 | ||
| 92 | 95 | pub fn getTree(self: *const Msg) *ast.Tree { |
| 93 | 96 | switch (self.data) { |
| 97 | Data.Cli => unreachable, | |
| 94 | 98 | Data.PathAndTree => |path_and_tree| { |
| 95 | 99 | return path_and_tree.tree; |
| 96 | 100 | }, |
| ... | ... | @@ -100,16 +104,28 @@ pub const Msg = struct { |
| 100 | 104 | } |
| 101 | 105 | } |
| 102 | 106 | |
| 107 | pub fn getSpan(self: *const Msg) Span { | |
| 108 | return switch (self.data) { | |
| 109 | Data.Cli => unreachable, | |
| 110 | Data.PathAndTree => |path_and_tree| path_and_tree.span, | |
| 111 | Data.ScopeAndComp => |scope_and_comp| scope_and_comp.span, | |
| 112 | }; | |
| 113 | } | |
| 114 | ||
| 103 | 115 | /// Takes ownership of text |
| 104 | 116 | /// References tree_scope, and derefs when the msg is freed |
| 105 | 117 | pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg { |
| 118 | const realpath = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); | |
| 119 | errdefer comp.gpa().free(realpath); | |
| 120 | ||
| 106 | 121 | const msg = try comp.gpa().create(Msg{ |
| 107 | 122 | .text = text, |
| 108 | .span = span, | |
| 123 | .realpath = realpath, | |
| 109 | 124 | .data = Data{ |
| 110 | 125 | .ScopeAndComp = ScopeAndComp{ |
| 111 | 126 | .tree_scope = tree_scope, |
| 112 | 127 | .compilation = comp, |
| 128 | .span = span, | |
| 113 | 129 | }, |
| 114 | 130 | }, |
| 115 | 131 | }); |
| ... | ... | @@ -117,6 +133,22 @@ pub const Msg = struct { |
| 117 | 133 | return msg; |
| 118 | 134 | } |
| 119 | 135 | |
| 136 | /// Caller owns returned Msg and must free with `allocator` | |
| 137 | /// allocator will additionally be used for printing messages later. | |
| 138 | pub fn createFromCli(comp: *Compilation, realpath: []const u8, text: []u8) !*Msg { | |
| 139 | const realpath_copy = try mem.dupe(comp.gpa(), u8, realpath); | |
| 140 | errdefer comp.gpa().free(realpath_copy); | |
| 141 | ||
| 142 | const msg = try comp.gpa().create(Msg{ | |
| 143 | .text = text, | |
| 144 | .realpath = realpath_copy, | |
| 145 | .data = Data{ | |
| 146 | .Cli = Cli{ .allocator = comp.gpa() }, | |
| 147 | }, | |
| 148 | }); | |
| 149 | return msg; | |
| 150 | } | |
| 151 | ||
| 120 | 152 | pub fn createFromParseErrorAndScope( |
| 121 | 153 | comp: *Compilation, |
| 122 | 154 | tree_scope: *Scope.AstTree, |
| ... | ... | @@ -126,19 +158,23 @@ pub const Msg = struct { |
| 126 | 158 | var text_buf = try std.Buffer.initSize(comp.gpa(), 0); |
| 127 | 159 | defer text_buf.deinit(); |
| 128 | 160 | |
| 161 | const realpath_copy = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); | |
| 162 | errdefer comp.gpa().free(realpath_copy); | |
| 163 | ||
| 129 | 164 | var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; |
| 130 | 165 | try parse_error.render(&tree_scope.tree.tokens, out_stream); |
| 131 | 166 | |
| 132 | 167 | const msg = try comp.gpa().create(Msg{ |
| 133 | 168 | .text = undefined, |
| 134 | .span = Span{ | |
| 135 | .first = loc_token, | |
| 136 | .last = loc_token, | |
| 137 | }, | |
| 169 | .realpath = realpath_copy, | |
| 138 | 170 | .data = Data{ |
| 139 | 171 | .ScopeAndComp = ScopeAndComp{ |
| 140 | 172 | .tree_scope = tree_scope, |
| 141 | 173 | .compilation = comp, |
| 174 | .span = Span{ | |
| 175 | .first = loc_token, | |
| 176 | .last = loc_token, | |
| 177 | }, | |
| 142 | 178 | }, |
| 143 | 179 | }, |
| 144 | 180 | }); |
| ... | ... | @@ -161,22 +197,25 @@ pub const Msg = struct { |
| 161 | 197 | var text_buf = try std.Buffer.initSize(allocator, 0); |
| 162 | 198 | defer text_buf.deinit(); |
| 163 | 199 | |
| 200 | const realpath_copy = try mem.dupe(allocator, u8, realpath); | |
| 201 | errdefer allocator.free(realpath_copy); | |
| 202 | ||
| 164 | 203 | var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; |
| 165 | 204 | try parse_error.render(&tree.tokens, out_stream); |
| 166 | 205 | |
| 167 | 206 | const msg = try allocator.create(Msg{ |
| 168 | 207 | .text = undefined, |
| 208 | .realpath = realpath_copy, | |
| 169 | 209 | .data = Data{ |
| 170 | 210 | .PathAndTree = PathAndTree{ |
| 171 | 211 | .allocator = allocator, |
| 172 | .realpath = realpath, | |
| 173 | 212 | .tree = tree, |
| 213 | .span = Span{ | |
| 214 | .first = loc_token, | |
| 215 | .last = loc_token, | |
| 216 | }, | |
| 174 | 217 | }, |
| 175 | 218 | }, |
| 176 | .span = Span{ | |
| 177 | .first = loc_token, | |
| 178 | .last = loc_token, | |
| 179 | }, | |
| 180 | 219 | }); |
| 181 | 220 | msg.text = text_buf.toOwnedSlice(); |
| 182 | 221 | errdefer allocator.destroy(msg); |
| ... | ... | @@ -185,20 +224,28 @@ pub const Msg = struct { |
| 185 | 224 | } |
| 186 | 225 | |
| 187 | 226 | pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void { |
| 227 | switch (msg.data) { | |
| 228 | Data.Cli => { | |
| 229 | try stream.print("{}:-:-: error: {}\n", msg.realpath, msg.text); | |
| 230 | return; | |
| 231 | }, | |
| 232 | else => {}, | |
| 233 | } | |
| 234 | ||
| 188 | 235 | const allocator = msg.getAllocator(); |
| 189 | const realpath = msg.getRealPath(); | |
| 190 | 236 | const tree = msg.getTree(); |
| 191 | 237 | |
| 192 | 238 | const cwd = try os.getCwd(allocator); |
| 193 | 239 | defer allocator.free(cwd); |
| 194 | 240 | |
| 195 | const relpath = try os.path.relative(allocator, cwd, realpath); | |
| 241 | const relpath = try os.path.relative(allocator, cwd, msg.realpath); | |
| 196 | 242 | defer allocator.free(relpath); |
| 197 | 243 | |
| 198 | const path = if (relpath.len < realpath.len) relpath else realpath; | |
| 244 | const path = if (relpath.len < msg.realpath.len) relpath else msg.realpath; | |
| 245 | const span = msg.getSpan(); | |
| 199 | 246 | |
| 200 | const first_token = tree.tokens.at(msg.span.first); | |
| 201 | const last_token = tree.tokens.at(msg.span.last); | |
| 247 | const first_token = tree.tokens.at(span.first); | |
| 248 | const last_token = tree.tokens.at(span.last); | |
| 202 | 249 | const start_loc = tree.tokenLocationPtr(0, first_token); |
| 203 | 250 | const end_loc = tree.tokenLocationPtr(first_token.end, last_token); |
| 204 | 251 | if (!color_on) { |
src-self-hosted/ir.zig+23-20| ... | ... | @@ -961,6 +961,7 @@ pub const Code = struct { |
| 961 | 961 | basic_block_list: std.ArrayList(*BasicBlock), |
| 962 | 962 | arena: std.heap.ArenaAllocator, |
| 963 | 963 | return_type: ?*Type, |
| 964 | tree_scope: *Scope.AstTree, | |
| 964 | 965 | |
| 965 | 966 | /// allocator is comp.gpa() |
| 966 | 967 | pub fn destroy(self: *Code, allocator: *Allocator) void { |
| ... | ... | @@ -990,14 +991,14 @@ pub const Code = struct { |
| 990 | 991 | return ret_value.val.KnownValue.getRef(); |
| 991 | 992 | } |
| 992 | 993 | try comp.addCompileError( |
| 993 | ret_value.scope.findRoot(), | |
| 994 | self.tree_scope, | |
| 994 | 995 | ret_value.span, |
| 995 | 996 | "unable to evaluate constant expression", |
| 996 | 997 | ); |
| 997 | 998 | return error.SemanticAnalysisFailed; |
| 998 | 999 | } else if (inst.hasSideEffects()) { |
| 999 | 1000 | try comp.addCompileError( |
| 1000 | inst.scope.findRoot(), | |
| 1001 | self.tree_scope, | |
| 1001 | 1002 | inst.span, |
| 1002 | 1003 | "unable to evaluate constant expression", |
| 1003 | 1004 | ); |
| ... | ... | @@ -1013,25 +1014,24 @@ pub const Builder = struct { |
| 1013 | 1014 | code: *Code, |
| 1014 | 1015 | current_basic_block: *BasicBlock, |
| 1015 | 1016 | next_debug_id: usize, |
| 1016 | root_scope: *Scope.Root, | |
| 1017 | 1017 | is_comptime: bool, |
| 1018 | 1018 | is_async: bool, |
| 1019 | 1019 | begin_scope: ?*Scope, |
| 1020 | 1020 | |
| 1021 | 1021 | pub const Error = Analyze.Error; |
| 1022 | 1022 | |
| 1023 | pub fn init(comp: *Compilation, root_scope: *Scope.Root, begin_scope: ?*Scope) !Builder { | |
| 1023 | pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, begin_scope: ?*Scope) !Builder { | |
| 1024 | 1024 | const code = try comp.gpa().create(Code{ |
| 1025 | 1025 | .basic_block_list = undefined, |
| 1026 | 1026 | .arena = std.heap.ArenaAllocator.init(comp.gpa()), |
| 1027 | 1027 | .return_type = null, |
| 1028 | .tree_scope = tree_scope, | |
| 1028 | 1029 | }); |
| 1029 | 1030 | code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); |
| 1030 | 1031 | errdefer code.destroy(comp.gpa()); |
| 1031 | 1032 | |
| 1032 | 1033 | return Builder{ |
| 1033 | 1034 | .comp = comp, |
| 1034 | .root_scope = root_scope, | |
| 1035 | 1035 | .current_basic_block = undefined, |
| 1036 | 1036 | .code = code, |
| 1037 | 1037 | .next_debug_id = 0, |
| ... | ... | @@ -1292,6 +1292,7 @@ pub const Builder = struct { |
| 1292 | 1292 | Scope.Id.FnDef => return false, |
| 1293 | 1293 | Scope.Id.Decls => unreachable, |
| 1294 | 1294 | Scope.Id.Root => unreachable, |
| 1295 | Scope.Id.AstTree => unreachable, | |
| 1295 | 1296 | Scope.Id.Block, |
| 1296 | 1297 | Scope.Id.Defer, |
| 1297 | 1298 | Scope.Id.DeferExpr, |
| ... | ... | @@ -1302,7 +1303,7 @@ pub const Builder = struct { |
| 1302 | 1303 | } |
| 1303 | 1304 | |
| 1304 | 1305 | pub fn genIntLit(irb: *Builder, int_lit: *ast.Node.IntegerLiteral, scope: *Scope) !*Inst { |
| 1305 | const int_token = irb.root_scope.tree.tokenSlice(int_lit.token); | |
| 1306 | const int_token = irb.code.tree_scope.tree.tokenSlice(int_lit.token); | |
| 1306 | 1307 | |
| 1307 | 1308 | var base: u8 = undefined; |
| 1308 | 1309 | var rest: []const u8 = undefined; |
| ... | ... | @@ -1341,7 +1342,7 @@ pub const Builder = struct { |
| 1341 | 1342 | } |
| 1342 | 1343 | |
| 1343 | 1344 | pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { |
| 1344 | const str_token = irb.root_scope.tree.tokenSlice(str_lit.token); | |
| 1345 | const str_token = irb.code.tree_scope.tree.tokenSlice(str_lit.token); | |
| 1345 | 1346 | const src_span = Span.token(str_lit.token); |
| 1346 | 1347 | |
| 1347 | 1348 | var bad_index: usize = undefined; |
| ... | ... | @@ -1349,7 +1350,7 @@ pub const Builder = struct { |
| 1349 | 1350 | error.OutOfMemory => return error.OutOfMemory, |
| 1350 | 1351 | error.InvalidCharacter => { |
| 1351 | 1352 | try irb.comp.addCompileError( |
| 1352 | irb.root_scope, | |
| 1353 | irb.code.tree_scope, | |
| 1353 | 1354 | src_span, |
| 1354 | 1355 | "invalid character in string literal: '{c}'", |
| 1355 | 1356 | str_token[bad_index], |
| ... | ... | @@ -1427,7 +1428,7 @@ pub const Builder = struct { |
| 1427 | 1428 | |
| 1428 | 1429 | if (statement_node.cast(ast.Node.Defer)) |defer_node| { |
| 1429 | 1430 | // defer starts a new scope |
| 1430 | const defer_token = irb.root_scope.tree.tokens.at(defer_node.defer_token); | |
| 1431 | const defer_token = irb.code.tree_scope.tree.tokens.at(defer_node.defer_token); | |
| 1431 | 1432 | const kind = switch (defer_token.id) { |
| 1432 | 1433 | Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit, |
| 1433 | 1434 | Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit, |
| ... | ... | @@ -1513,7 +1514,7 @@ pub const Builder = struct { |
| 1513 | 1514 | const src_span = Span.token(control_flow_expr.ltoken); |
| 1514 | 1515 | if (scope.findFnDef() == null) { |
| 1515 | 1516 | try irb.comp.addCompileError( |
| 1516 | irb.root_scope, | |
| 1517 | irb.code.tree_scope, | |
| 1517 | 1518 | src_span, |
| 1518 | 1519 | "return expression outside function definition", |
| 1519 | 1520 | ); |
| ... | ... | @@ -1523,7 +1524,7 @@ pub const Builder = struct { |
| 1523 | 1524 | if (scope.findDeferExpr()) |scope_defer_expr| { |
| 1524 | 1525 | if (!scope_defer_expr.reported_err) { |
| 1525 | 1526 | try irb.comp.addCompileError( |
| 1526 | irb.root_scope, | |
| 1527 | irb.code.tree_scope, | |
| 1527 | 1528 | src_span, |
| 1528 | 1529 | "cannot return from defer expression", |
| 1529 | 1530 | ); |
| ... | ... | @@ -1599,7 +1600,7 @@ pub const Builder = struct { |
| 1599 | 1600 | |
| 1600 | 1601 | pub async fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst { |
| 1601 | 1602 | const src_span = Span.token(identifier.token); |
| 1602 | const name = irb.root_scope.tree.tokenSlice(identifier.token); | |
| 1603 | const name = irb.code.tree_scope.tree.tokenSlice(identifier.token); | |
| 1603 | 1604 | |
| 1604 | 1605 | //if (buf_eql_str(variable_name, "_") && lval == LValPtr) { |
| 1605 | 1606 | // IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, node); |
| ... | ... | @@ -1622,7 +1623,7 @@ pub const Builder = struct { |
| 1622 | 1623 | } |
| 1623 | 1624 | } else |err| switch (err) { |
| 1624 | 1625 | error.Overflow => { |
| 1625 | try irb.comp.addCompileError(irb.root_scope, src_span, "integer too large"); | |
| 1626 | try irb.comp.addCompileError(irb.code.tree_scope, src_span, "integer too large"); | |
| 1626 | 1627 | return error.SemanticAnalysisFailed; |
| 1627 | 1628 | }, |
| 1628 | 1629 | error.OutOfMemory => return error.OutOfMemory, |
| ... | ... | @@ -1656,7 +1657,7 @@ pub const Builder = struct { |
| 1656 | 1657 | // TODO put a variable of same name with invalid type in global scope |
| 1657 | 1658 | // so that future references to this same name will find a variable with an invalid type |
| 1658 | 1659 | |
| 1659 | try irb.comp.addCompileError(irb.root_scope, src_span, "unknown identifier '{}'", name); | |
| 1660 | try irb.comp.addCompileError(irb.code.tree_scope, src_span, "unknown identifier '{}'", name); | |
| 1660 | 1661 | return error.SemanticAnalysisFailed; |
| 1661 | 1662 | } |
| 1662 | 1663 | |
| ... | ... | @@ -1689,6 +1690,7 @@ pub const Builder = struct { |
| 1689 | 1690 | => scope = scope.parent orelse break, |
| 1690 | 1691 | |
| 1691 | 1692 | Scope.Id.DeferExpr => unreachable, |
| 1693 | Scope.Id.AstTree => unreachable, | |
| 1692 | 1694 | } |
| 1693 | 1695 | } |
| 1694 | 1696 | return result; |
| ... | ... | @@ -1740,6 +1742,7 @@ pub const Builder = struct { |
| 1740 | 1742 | => scope = scope.parent orelse return is_noreturn, |
| 1741 | 1743 | |
| 1742 | 1744 | Scope.Id.DeferExpr => unreachable, |
| 1745 | Scope.Id.AstTree => unreachable, | |
| 1743 | 1746 | } |
| 1744 | 1747 | } |
| 1745 | 1748 | } |
| ... | ... | @@ -1968,8 +1971,8 @@ const Analyze = struct { |
| 1968 | 1971 | OutOfMemory, |
| 1969 | 1972 | }; |
| 1970 | 1973 | |
| 1971 | pub fn init(comp: *Compilation, root_scope: *Scope.Root, explicit_return_type: ?*Type) !Analyze { | |
| 1972 | var irb = try Builder.init(comp, root_scope, null); | |
| 1974 | pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, explicit_return_type: ?*Type) !Analyze { | |
| 1975 | var irb = try Builder.init(comp, tree_scope, null); | |
| 1973 | 1976 | errdefer irb.abort(); |
| 1974 | 1977 | |
| 1975 | 1978 | return Analyze{ |
| ... | ... | @@ -2047,7 +2050,7 @@ const Analyze = struct { |
| 2047 | 2050 | } |
| 2048 | 2051 | |
| 2049 | 2052 | fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void { |
| 2050 | return self.irb.comp.addCompileError(self.irb.root_scope, span, fmt, args); | |
| 2053 | return self.irb.comp.addCompileError(self.irb.code.tree_scope, span, fmt, args); | |
| 2051 | 2054 | } |
| 2052 | 2055 | |
| 2053 | 2056 | fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type { |
| ... | ... | @@ -2535,9 +2538,10 @@ const Analyze = struct { |
| 2535 | 2538 | pub async fn gen( |
| 2536 | 2539 | comp: *Compilation, |
| 2537 | 2540 | body_node: *ast.Node, |
| 2541 | tree_scope: *Scope.AstTree, | |
| 2538 | 2542 | scope: *Scope, |
| 2539 | 2543 | ) !*Code { |
| 2540 | var irb = try Builder.init(comp, scope.findRoot(), scope); | |
| 2544 | var irb = try Builder.init(comp, tree_scope, scope); | |
| 2541 | 2545 | errdefer irb.abort(); |
| 2542 | 2546 | |
| 2543 | 2547 | const entry_block = try irb.createBasicBlock(scope, c"Entry"); |
| ... | ... | @@ -2555,9 +2559,8 @@ pub async fn gen( |
| 2555 | 2559 | |
| 2556 | 2560 | pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { |
| 2557 | 2561 | const old_entry_bb = old_code.basic_block_list.at(0); |
| 2558 | const root_scope = old_entry_bb.scope.findRoot(); | |
| 2559 | 2562 | |
| 2560 | var ira = try Analyze.init(comp, root_scope, expected_type); | |
| 2563 | var ira = try Analyze.init(comp, old_code.tree_scope, expected_type); | |
| 2561 | 2564 | errdefer ira.abort(); |
| 2562 | 2565 | |
| 2563 | 2566 | const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null); |
src-self-hosted/main.zig+33-27| ... | ... | @@ -24,6 +24,8 @@ var stderr_file: os.File = undefined; |
| 24 | 24 | var stderr: *io.OutStream(io.FileOutStream.Error) = undefined; |
| 25 | 25 | var stdout: *io.OutStream(io.FileOutStream.Error) = undefined; |
| 26 | 26 | |
| 27 | const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB | |
| 28 | ||
| 27 | 29 | const usage = |
| 28 | 30 | \\usage: zig [command] [options] |
| 29 | 31 | \\ |
| ... | ... | @@ -71,26 +73,26 @@ pub fn main() !void { |
| 71 | 73 | } |
| 72 | 74 | |
| 73 | 75 | 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 | //}, | |
| 76 | Command{ | |
| 77 | .name = "build-exe", | |
| 78 | .exec = cmdBuildExe, | |
| 79 | }, | |
| 80 | Command{ | |
| 81 | .name = "build-lib", | |
| 82 | .exec = cmdBuildLib, | |
| 83 | }, | |
| 84 | Command{ | |
| 85 | .name = "build-obj", | |
| 86 | .exec = cmdBuildObj, | |
| 87 | }, | |
| 86 | 88 | Command{ |
| 87 | 89 | .name = "fmt", |
| 88 | 90 | .exec = cmdFmt, |
| 89 | 91 | }, |
| 90 | //Command{ | |
| 91 | // .name = "libc", | |
| 92 | // .exec = cmdLibC, | |
| 93 | //}, | |
| 92 | Command{ | |
| 93 | .name = "libc", | |
| 94 | .exec = cmdLibC, | |
| 95 | }, | |
| 94 | 96 | Command{ |
| 95 | 97 | .name = "targets", |
| 96 | 98 | .exec = cmdTargets, |
| ... | ... | @@ -472,16 +474,21 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 472 | 474 | } |
| 473 | 475 | |
| 474 | 476 | async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { |
| 477 | var count: usize = 0; | |
| 475 | 478 | while (true) { |
| 476 | 479 | // TODO directly awaiting async should guarantee memory allocation elision |
| 477 | 480 | const build_event = await (async comp.events.get() catch unreachable); |
| 481 | count += 1; | |
| 478 | 482 | |
| 479 | 483 | switch (build_event) { |
| 480 | Compilation.Event.Ok => {}, | |
| 484 | Compilation.Event.Ok => { | |
| 485 | stderr.print("Build {} succeeded\n", count) catch os.exit(1); | |
| 486 | }, | |
| 481 | 487 | Compilation.Event.Error => |err| { |
| 482 | stderr.print("build failed: {}\n", @errorName(err)) catch os.exit(1); | |
| 488 | stderr.print("Build {} failed: {}\n", count, @errorName(err)) catch os.exit(1); | |
| 483 | 489 | }, |
| 484 | 490 | Compilation.Event.Fail => |msgs| { |
| 491 | stderr.print("Build {} compile errors:\n", count) catch os.exit(1); | |
| 485 | 492 | for (msgs) |msg| { |
| 486 | 493 | defer msg.destroy(); |
| 487 | 494 | msg.printToFile(&stderr_file, color) catch os.exit(1); |
| ... | ... | @@ -614,7 +621,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 614 | 621 | var stdin_file = try io.getStdIn(); |
| 615 | 622 | var stdin = io.FileInStream.init(&stdin_file); |
| 616 | 623 | |
| 617 | const source_code = try stdin.stream.readAllAlloc(allocator, @maxValue(usize)); | |
| 624 | const source_code = try stdin.stream.readAllAlloc(allocator, max_src_size); | |
| 618 | 625 | defer allocator.free(source_code); |
| 619 | 626 | |
| 620 | 627 | var tree = std.zig.parse(allocator, source_code) catch |err| { |
| ... | ... | @@ -697,12 +704,6 @@ async fn asyncFmtMain( |
| 697 | 704 | suspend { |
| 698 | 705 | resume @handle(); |
| 699 | 706 | } |
| 700 | // Things we need to make event-based: | |
| 701 | // * opening the file in the first place - the open() | |
| 702 | // * read() | |
| 703 | // * readdir() | |
| 704 | // * the actual parsing and rendering | |
| 705 | // * rename() | |
| 706 | 707 | var fmt = Fmt{ |
| 707 | 708 | .seen = event.Locked(Fmt.SeenMap).init(loop, Fmt.SeenMap.init(loop.allocator)), |
| 708 | 709 | .any_error = false, |
| ... | ... | @@ -714,7 +715,10 @@ async fn asyncFmtMain( |
| 714 | 715 | for (flags.positionals.toSliceConst()) |file_path| { |
| 715 | 716 | try group.call(fmtPath, &fmt, file_path); |
| 716 | 717 | } |
| 717 | return await (async group.wait() catch unreachable); | |
| 718 | try await (async group.wait() catch unreachable); | |
| 719 | if (fmt.any_error) { | |
| 720 | os.exit(1); | |
| 721 | } | |
| 718 | 722 | } |
| 719 | 723 | |
| 720 | 724 | async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8) FmtError!void { |
| ... | ... | @@ -731,9 +735,10 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8) FmtError!void { |
| 731 | 735 | const source_code = (await try async event.fs.readFile( |
| 732 | 736 | fmt.loop, |
| 733 | 737 | file_path, |
| 734 | 2 * 1024 * 1024 * 1024, | |
| 738 | max_src_size, | |
| 735 | 739 | )) catch |err| switch (err) { |
| 736 | 740 | error.IsDir => { |
| 741 | // TODO make event based (and dir.next()) | |
| 737 | 742 | var dir = try std.os.Dir.open(fmt.loop.allocator, file_path); |
| 738 | 743 | defer dir.close(); |
| 739 | 744 | |
| ... | ... | @@ -774,6 +779,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8) FmtError!void { |
| 774 | 779 | return; |
| 775 | 780 | } |
| 776 | 781 | |
| 782 | // TODO make this evented | |
| 777 | 783 | const baf = try io.BufferedAtomicFile.create(fmt.loop.allocator, file_path); |
| 778 | 784 | defer baf.destroy(); |
| 779 | 785 |
src-self-hosted/scope.zig+8-4| ... | ... | @@ -63,6 +63,8 @@ pub const Scope = struct { |
| 63 | 63 | Id.CompTime, |
| 64 | 64 | Id.Var, |
| 65 | 65 | => scope = scope.parent.?, |
| 66 | ||
| 67 | Id.AstTree => unreachable, | |
| 66 | 68 | } |
| 67 | 69 | } |
| 68 | 70 | } |
| ... | ... | @@ -83,6 +85,8 @@ pub const Scope = struct { |
| 83 | 85 | Id.Root, |
| 84 | 86 | Id.Var, |
| 85 | 87 | => scope = scope.parent orelse return null, |
| 88 | ||
| 89 | Id.AstTree => unreachable, | |
| 86 | 90 | } |
| 87 | 91 | } |
| 88 | 92 | } |
| ... | ... | @@ -132,6 +136,7 @@ pub const Scope = struct { |
| 132 | 136 | } |
| 133 | 137 | |
| 134 | 138 | pub fn destroy(self: *Root, comp: *Compilation) void { |
| 139 | // TODO comp.fs_watch.removeFile(self.realpath); | |
| 135 | 140 | self.decls.base.deref(comp); |
| 136 | 141 | comp.gpa().free(self.realpath); |
| 137 | 142 | comp.gpa().destroy(self); |
| ... | ... | @@ -144,13 +149,13 @@ pub const Scope = struct { |
| 144 | 149 | |
| 145 | 150 | /// Creates a scope with 1 reference |
| 146 | 151 | /// 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); | |
| 152 | pub fn create(comp: *Compilation, tree: *ast.Tree, root_scope: *Root) !*AstTree { | |
| 153 | const self = try comp.gpa().createOne(AstTree); | |
| 149 | 154 | self.* = AstTree{ |
| 150 | 155 | .base = undefined, |
| 151 | 156 | .tree = tree, |
| 152 | 157 | }; |
| 153 | self.base.init(Id.AstTree, &root.base); | |
| 158 | self.base.init(Id.AstTree, &root_scope.base); | |
| 154 | 159 | |
| 155 | 160 | return self; |
| 156 | 161 | } |
| ... | ... | @@ -181,7 +186,6 @@ pub const Scope = struct { |
| 181 | 186 | self.* = Decls{ |
| 182 | 187 | .base = undefined, |
| 183 | 188 | .table = event.RwLocked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), |
| 184 | .name_future = event.Future(void).init(comp.loop), | |
| 185 | 189 | }; |
| 186 | 190 | self.base.init(Id.Decls, parent); |
| 187 | 191 | return self; |
src-self-hosted/test.zig+4-3| ... | ... | @@ -212,9 +212,10 @@ pub const TestContext = struct { |
| 212 | 212 | Compilation.Event.Fail => |msgs| { |
| 213 | 213 | assertOrPanic(msgs.len != 0); |
| 214 | 214 | for (msgs) |msg| { |
| 215 | if (mem.endsWith(u8, msg.getRealPath(), path) and mem.eql(u8, msg.text, text)) { | |
| 216 | const first_token = msg.getTree().tokens.at(msg.span.first); | |
| 217 | const last_token = msg.getTree().tokens.at(msg.span.first); | |
| 215 | if (mem.endsWith(u8, msg.realpath, path) and mem.eql(u8, msg.text, text)) { | |
| 216 | const span = msg.getSpan(); | |
| 217 | const first_token = msg.getTree().tokens.at(span.first); | |
| 218 | const last_token = msg.getTree().tokens.at(span.first); | |
| 218 | 219 | const start_loc = msg.getTree().tokenLocationPtr(0, first_token); |
| 219 | 220 | if (start_loc.line + 1 == line and start_loc.column + 1 == column) { |
| 220 | 221 | return; |
src/ir.cpp+10-1| ... | ... | @@ -9614,6 +9614,9 @@ static ConstExprValue *ir_resolve_const(IrAnalyze *ira, IrInstruction *value, Un |
| 9614 | 9614 | case ConstValSpecialStatic: |
| 9615 | 9615 | return &value->value; |
| 9616 | 9616 | case ConstValSpecialRuntime: |
| 9617 | if (!type_has_bits(value->value.type)) { | |
| 9618 | return &value->value; | |
| 9619 | } | |
| 9617 | 9620 | ir_add_error(ira, value, buf_sprintf("unable to evaluate constant expression")); |
| 9618 | 9621 | return nullptr; |
| 9619 | 9622 | case ConstValSpecialUndef: |
| ... | ... | @@ -16115,8 +16118,14 @@ static TypeTableEntry *ir_analyze_container_init_fields_union(IrAnalyze *ira, Ir |
| 16115 | 16118 | if (casted_field_value == ira->codegen->invalid_instruction) |
| 16116 | 16119 | return ira->codegen->builtin_types.entry_invalid; |
| 16117 | 16120 | |
| 16121 | type_ensure_zero_bits_known(ira->codegen, casted_field_value->value.type); | |
| 16122 | if (type_is_invalid(casted_field_value->value.type)) | |
| 16123 | return ira->codegen->builtin_types.entry_invalid; | |
| 16124 | ||
| 16118 | 16125 | bool is_comptime = ir_should_inline(ira->new_irb.exec, instruction->scope); |
| 16119 | if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime) { | |
| 16126 | if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime || | |
| 16127 | !type_has_bits(casted_field_value->value.type)) | |
| 16128 | { | |
| 16120 | 16129 | ConstExprValue *field_val = ir_resolve_const(ira, casted_field_value, UndefOk); |
| 16121 | 16130 | if (!field_val) |
| 16122 | 16131 | return ira->codegen->builtin_types.entry_invalid; |
std/build.zig+59-50| ... | ... | @@ -424,60 +424,69 @@ pub const Builder = struct { |
| 424 | 424 | return mode; |
| 425 | 425 | } |
| 426 | 426 | |
| 427 | pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) bool { | |
| 428 | if (self.user_input_options.put(name, UserInputOption{ | |
| 429 | .name = name, | |
| 430 | .value = UserValue{ .Scalar = value }, | |
| 431 | .used = false, | |
| 432 | }) catch unreachable) |*prev_value| { | |
| 433 | // option already exists | |
| 434 | switch (prev_value.value) { | |
| 435 | UserValue.Scalar => |s| { | |
| 436 | // turn it into a list | |
| 437 | var list = ArrayList([]const u8).init(self.allocator); | |
| 438 | list.append(s) catch unreachable; | |
| 439 | list.append(value) catch unreachable; | |
| 440 | _ = self.user_input_options.put(name, UserInputOption{ | |
| 441 | .name = name, | |
| 442 | .value = UserValue{ .List = list }, | |
| 443 | .used = false, | |
| 444 | }) catch unreachable; | |
| 445 | }, | |
| 446 | UserValue.List => |*list| { | |
| 447 | // append to the list | |
| 448 | list.append(value) catch unreachable; | |
| 449 | _ = self.user_input_options.put(name, UserInputOption{ | |
| 450 | .name = name, | |
| 451 | .value = UserValue{ .List = list.* }, | |
| 452 | .used = false, | |
| 453 | }) catch unreachable; | |
| 454 | }, | |
| 455 | UserValue.Flag => { | |
| 456 | warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name); | |
| 457 | return true; | |
| 458 | }, | |
| 459 | } | |
| 427 | pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool { | |
| 428 | const gop = try self.user_input_options.getOrPut(name); | |
| 429 | if (!gop.found_existing) { | |
| 430 | gop.kv.value = UserInputOption{ | |
| 431 | .name = name, | |
| 432 | .value = UserValue{ .Scalar = value }, | |
| 433 | .used = false, | |
| 434 | }; | |
| 435 | return false; | |
| 436 | } | |
| 437 | ||
| 438 | // option already exists | |
| 439 | switch (gop.kv.value.value) { | |
| 440 | UserValue.Scalar => |s| { | |
| 441 | // turn it into a list | |
| 442 | var list = ArrayList([]const u8).init(self.allocator); | |
| 443 | list.append(s) catch unreachable; | |
| 444 | list.append(value) catch unreachable; | |
| 445 | _ = self.user_input_options.put(name, UserInputOption{ | |
| 446 | .name = name, | |
| 447 | .value = UserValue{ .List = list }, | |
| 448 | .used = false, | |
| 449 | }) catch unreachable; | |
| 450 | }, | |
| 451 | UserValue.List => |*list| { | |
| 452 | // append to the list | |
| 453 | list.append(value) catch unreachable; | |
| 454 | _ = self.user_input_options.put(name, UserInputOption{ | |
| 455 | .name = name, | |
| 456 | .value = UserValue{ .List = list.* }, | |
| 457 | .used = false, | |
| 458 | }) catch unreachable; | |
| 459 | }, | |
| 460 | UserValue.Flag => { | |
| 461 | warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name); | |
| 462 | return true; | |
| 463 | }, | |
| 460 | 464 | } |
| 461 | 465 | return false; |
| 462 | 466 | } |
| 463 | 467 | |
| 464 | pub fn addUserInputFlag(self: *Builder, name: []const u8) bool { | |
| 465 | if (self.user_input_options.put(name, UserInputOption{ | |
| 466 | .name = name, | |
| 467 | .value = UserValue{ .Flag = {} }, | |
| 468 | .used = false, | |
| 469 | }) catch unreachable) |*prev_value| { | |
| 470 | switch (prev_value.value) { | |
| 471 | UserValue.Scalar => |s| { | |
| 472 | warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s); | |
| 473 | return true; | |
| 474 | }, | |
| 475 | UserValue.List => { | |
| 476 | warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name); | |
| 477 | return true; | |
| 478 | }, | |
| 479 | UserValue.Flag => {}, | |
| 480 | } | |
| 468 | pub fn addUserInputFlag(self: *Builder, name: []const u8) !bool { | |
| 469 | const gop = try self.user_input_options.getOrPut(name); | |
| 470 | if (!gop.found_existing) { | |
| 471 | gop.kv.value = UserInputOption{ | |
| 472 | .name = name, | |
| 473 | .value = UserValue{ .Flag = {} }, | |
| 474 | .used = false, | |
| 475 | }; | |
| 476 | return false; | |
| 477 | } | |
| 478 | ||
| 479 | // option already exists | |
| 480 | switch (gop.kv.value.value) { | |
| 481 | UserValue.Scalar => |s| { | |
| 482 | warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s); | |
| 483 | return true; | |
| 484 | }, | |
| 485 | UserValue.List => { | |
| 486 | warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name); | |
| 487 | return true; | |
| 488 | }, | |
| 489 | UserValue.Flag => {}, | |
| 481 | 490 | } |
| 482 | 491 | return false; |
| 483 | 492 | } |
std/event/fs.zig+180-94| ... | ... | @@ -367,109 +367,193 @@ pub async fn readFile(loop: *event.Loop, file_path: []const u8, max_size: usize) |
| 367 | 367 | } |
| 368 | 368 | } |
| 369 | 369 | |
| 370 | pub const Watch = struct { | |
| 371 | channel: *event.Channel(Event), | |
| 372 | putter: promise, | |
| 373 | ||
| 374 | pub const Event = union(enum) { | |
| 375 | CloseWrite, | |
| 376 | Err: Error, | |
| 377 | }; | |
| 378 | ||
| 379 | pub const Error = error{ | |
| 380 | UserResourceLimitReached, | |
| 381 | SystemResources, | |
| 382 | }; | |
| 383 | ||
| 384 | pub fn destroy(self: *Watch) void { | |
| 385 | // TODO https://github.com/ziglang/zig/issues/1261 | |
| 386 | cancel self.putter; | |
| 387 | } | |
| 388 | }; | |
| 389 | ||
| 390 | pub fn watchFile(loop: *event.Loop, file_path: []const u8) !*Watch { | |
| 391 | const path_with_null = try std.cstr.addNullByte(loop.allocator, file_path); | |
| 392 | defer loop.allocator.free(path_with_null); | |
| 370 | pub fn Watch(comptime V: type) type { | |
| 371 | return struct { | |
| 372 | channel: *event.Channel(Event), | |
| 373 | putter: promise, | |
| 374 | wd_table: WdTable, | |
| 375 | table_lock: event.Lock, | |
| 376 | inotify_fd: i32, | |
| 377 | ||
| 378 | const WdTable = std.AutoHashMap(i32, Dir); | |
| 379 | const FileTable = std.AutoHashMap([]const u8, V); | |
| 380 | ||
| 381 | const Self = this; | |
| 382 | ||
| 383 | const Dir = struct { | |
| 384 | dirname: []const u8, | |
| 385 | file_table: FileTable, | |
| 386 | }; | |
| 393 | 387 | |
| 394 | const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); | |
| 395 | errdefer os.close(inotify_fd); | |
| 388 | pub const Event = union(enum) { | |
| 389 | CloseWrite: V, | |
| 390 | Err: Error, | |
| 396 | 391 | |
| 397 | const wd = try os.linuxINotifyAddWatchC(inotify_fd, path_with_null.ptr, os.linux.IN_CLOSE_WRITE); | |
| 398 | errdefer os.close(wd); | |
| 392 | pub const Error = error{ | |
| 393 | UserResourceLimitReached, | |
| 394 | SystemResources, | |
| 395 | }; | |
| 396 | }; | |
| 399 | 397 | |
| 400 | const channel = try event.Channel(Watch.Event).create(loop, 0); | |
| 401 | errdefer channel.destroy(); | |
| 398 | pub fn create(loop: *event.Loop, event_buf_count: usize) !*Self { | |
| 399 | const inotify_fd = try os.linuxINotifyInit1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); | |
| 400 | errdefer os.close(inotify_fd); | |
| 402 | 401 | |
| 403 | var result: *Watch = undefined; | |
| 404 | _ = try async<loop.allocator> watchEventPutter(inotify_fd, wd, channel, &result); | |
| 405 | return result; | |
| 406 | } | |
| 407 | ||
| 408 | async fn watchEventPutter(inotify_fd: i32, wd: i32, channel: *event.Channel(Watch.Event), out_watch: **Watch) void { | |
| 409 | // TODO https://github.com/ziglang/zig/issues/1194 | |
| 410 | suspend { | |
| 411 | resume @handle(); | |
| 412 | } | |
| 402 | const channel = try event.Channel(Self.Event).create(loop, event_buf_count); | |
| 403 | errdefer channel.destroy(); | |
| 413 | 404 | |
| 414 | var watch = Watch{ | |
| 415 | .putter = @handle(), | |
| 416 | .channel = channel, | |
| 417 | }; | |
| 418 | out_watch.* = &watch; | |
| 405 | var result: *Self = undefined; | |
| 406 | _ = try async<loop.allocator> eventPutter(inotify_fd, channel, &result); | |
| 407 | return result; | |
| 408 | } | |
| 419 | 409 | |
| 420 | const loop = channel.loop; | |
| 421 | loop.beginOneEvent(); | |
| 410 | pub fn destroy(self: *Self) void { | |
| 411 | cancel self.putter; | |
| 412 | } | |
| 422 | 413 | |
| 423 | defer { | |
| 424 | channel.destroy(); | |
| 425 | os.close(wd); | |
| 426 | os.close(inotify_fd); | |
| 427 | loop.finishOneEvent(); | |
| 428 | } | |
| 414 | pub async fn addFile(self: *Self, file_path: []const u8, value: V) !?V { | |
| 415 | const dirname = os.path.dirname(file_path) orelse "."; | |
| 416 | const dirname_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, dirname); | |
| 417 | var dirname_with_null_consumed = false; | |
| 418 | defer if (!dirname_with_null_consumed) self.channel.loop.allocator.free(dirname_with_null); | |
| 419 | ||
| 420 | const basename = os.path.basename(file_path); | |
| 421 | const basename_with_null = try std.cstr.addNullByte(self.channel.loop.allocator, basename); | |
| 422 | var basename_with_null_consumed = false; | |
| 423 | defer if (!basename_with_null_consumed) self.channel.loop.allocator.free(basename_with_null); | |
| 424 | ||
| 425 | const wd = try os.linuxINotifyAddWatchC( | |
| 426 | self.inotify_fd, | |
| 427 | dirname_with_null.ptr, | |
| 428 | os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, | |
| 429 | ); | |
| 430 | // wd is either a newly created watch or an existing one. | |
| 431 | ||
| 432 | const held = await (async self.table_lock.acquire() catch unreachable); | |
| 433 | defer held.release(); | |
| 434 | ||
| 435 | const gop = try self.wd_table.getOrPut(wd); | |
| 436 | if (!gop.found_existing) { | |
| 437 | gop.kv.value = Dir{ | |
| 438 | .dirname = dirname_with_null, | |
| 439 | .file_table = FileTable.init(self.channel.loop.allocator), | |
| 440 | }; | |
| 441 | dirname_with_null_consumed = true; | |
| 442 | } | |
| 443 | const dir = &gop.kv.value; | |
| 444 | ||
| 445 | const file_table_gop = try dir.file_table.getOrPut(basename_with_null); | |
| 446 | if (file_table_gop.found_existing) { | |
| 447 | const prev_value = file_table_gop.kv.value; | |
| 448 | file_table_gop.kv.value = value; | |
| 449 | return prev_value; | |
| 450 | } else { | |
| 451 | file_table_gop.kv.value = value; | |
| 452 | basename_with_null_consumed = true; | |
| 453 | return null; | |
| 454 | } | |
| 455 | } | |
| 429 | 456 | |
| 430 | var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; | |
| 457 | pub async fn removeFile(self: *Self, file_path: []const u8) ?V { | |
| 458 | @panic("TODO"); | |
| 459 | } | |
| 431 | 460 | |
| 432 | while (true) { | |
| 433 | const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len); | |
| 434 | const errno = os.linux.getErrno(rc); | |
| 435 | switch (errno) { | |
| 436 | 0 => { | |
| 437 | // can't use @bytesToSlice because of the special variable length name field | |
| 438 | var ptr = event_buf[0..].ptr; | |
| 439 | const end_ptr = ptr + event_buf.len; | |
| 440 | var ev: *os.linux.inotify_event = undefined; | |
| 441 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) { | |
| 442 | ev = @ptrCast(*os.linux.inotify_event, ptr); | |
| 443 | if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { | |
| 444 | await (async channel.put(Watch.Event.CloseWrite) catch unreachable); | |
| 461 | async fn eventPutter(inotify_fd: i32, channel: *event.Channel(Event), out_watch: **Self) void { | |
| 462 | // TODO https://github.com/ziglang/zig/issues/1194 | |
| 463 | suspend { | |
| 464 | resume @handle(); | |
| 465 | } | |
| 466 | ||
| 467 | const loop = channel.loop; | |
| 468 | ||
| 469 | var watch = Self{ | |
| 470 | .putter = @handle(), | |
| 471 | .channel = channel, | |
| 472 | .wd_table = WdTable.init(loop.allocator), | |
| 473 | .table_lock = event.Lock.init(loop), | |
| 474 | .inotify_fd = inotify_fd, | |
| 475 | }; | |
| 476 | out_watch.* = &watch; | |
| 477 | ||
| 478 | loop.beginOneEvent(); | |
| 479 | ||
| 480 | defer { | |
| 481 | watch.table_lock.deinit(); | |
| 482 | { | |
| 483 | var wd_it = watch.wd_table.iterator(); | |
| 484 | while (wd_it.next()) |wd_entry| { | |
| 485 | var file_it = wd_entry.value.file_table.iterator(); | |
| 486 | while (file_it.next()) |file_entry| { | |
| 487 | loop.allocator.free(file_entry.key); | |
| 488 | } | |
| 489 | loop.allocator.free(wd_entry.value.dirname); | |
| 445 | 490 | } |
| 446 | 491 | } |
| 447 | }, | |
| 448 | os.linux.EINTR => continue, | |
| 449 | os.linux.EINVAL => unreachable, | |
| 450 | os.linux.EFAULT => unreachable, | |
| 451 | os.linux.EAGAIN => { | |
| 452 | (await (async loop.linuxWaitFd( | |
| 453 | inotify_fd, | |
| 454 | os.linux.EPOLLET | os.linux.EPOLLIN, | |
| 455 | ) catch unreachable)) catch |err| { | |
| 456 | const transformed_err = switch (err) { | |
| 457 | error.InvalidFileDescriptor => unreachable, | |
| 458 | error.FileDescriptorAlreadyPresentInSet => unreachable, | |
| 459 | error.InvalidSyscall => unreachable, | |
| 460 | error.OperationCausesCircularLoop => unreachable, | |
| 461 | error.FileDescriptorNotRegistered => unreachable, | |
| 462 | error.SystemResources => error.SystemResources, | |
| 463 | error.UserResourceLimitReached => error.UserResourceLimitReached, | |
| 464 | error.FileDescriptorIncompatibleWithEpoll => unreachable, | |
| 465 | error.Unexpected => unreachable, | |
| 466 | }; | |
| 467 | await (async channel.put(Watch.Event{ .Err = transformed_err }) catch unreachable); | |
| 468 | }; | |
| 469 | }, | |
| 470 | else => unreachable, | |
| 492 | loop.finishOneEvent(); | |
| 493 | os.close(inotify_fd); | |
| 494 | channel.destroy(); | |
| 495 | } | |
| 496 | ||
| 497 | var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; | |
| 498 | ||
| 499 | while (true) { | |
| 500 | const rc = os.linux.read(inotify_fd, &event_buf, event_buf.len); | |
| 501 | const errno = os.linux.getErrno(rc); | |
| 502 | switch (errno) { | |
| 503 | 0 => { | |
| 504 | // can't use @bytesToSlice because of the special variable length name field | |
| 505 | var ptr = event_buf[0..].ptr; | |
| 506 | const end_ptr = ptr + event_buf.len; | |
| 507 | var ev: *os.linux.inotify_event = undefined; | |
| 508 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += @sizeOf(os.linux.inotify_event) + ev.len) { | |
| 509 | ev = @ptrCast(*os.linux.inotify_event, ptr); | |
| 510 | if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { | |
| 511 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); | |
| 512 | const basename_with_null = basename_ptr[0 .. std.cstr.len(basename_ptr) + 1]; | |
| 513 | const user_value = blk: { | |
| 514 | const held = await (async watch.table_lock.acquire() catch unreachable); | |
| 515 | defer held.release(); | |
| 516 | ||
| 517 | const dir = &watch.wd_table.get(ev.wd).?.value; | |
| 518 | if (dir.file_table.get(basename_with_null)) |entry| { | |
| 519 | break :blk entry.value; | |
| 520 | } else { | |
| 521 | break :blk null; | |
| 522 | } | |
| 523 | }; | |
| 524 | if (user_value) |v| { | |
| 525 | await (async channel.put(Self.Event{ .CloseWrite = v }) catch unreachable); | |
| 526 | } | |
| 527 | } | |
| 528 | } | |
| 529 | }, | |
| 530 | os.linux.EINTR => continue, | |
| 531 | os.linux.EINVAL => unreachable, | |
| 532 | os.linux.EFAULT => unreachable, | |
| 533 | os.linux.EAGAIN => { | |
| 534 | (await (async loop.linuxWaitFd( | |
| 535 | inotify_fd, | |
| 536 | os.linux.EPOLLET | os.linux.EPOLLIN, | |
| 537 | ) catch unreachable)) catch |err| { | |
| 538 | const transformed_err = switch (err) { | |
| 539 | error.InvalidFileDescriptor => unreachable, | |
| 540 | error.FileDescriptorAlreadyPresentInSet => unreachable, | |
| 541 | error.InvalidSyscall => unreachable, | |
| 542 | error.OperationCausesCircularLoop => unreachable, | |
| 543 | error.FileDescriptorNotRegistered => unreachable, | |
| 544 | error.SystemResources => error.SystemResources, | |
| 545 | error.UserResourceLimitReached => error.UserResourceLimitReached, | |
| 546 | error.FileDescriptorIncompatibleWithEpoll => unreachable, | |
| 547 | error.Unexpected => unreachable, | |
| 548 | }; | |
| 549 | await (async channel.put(Self.Event{ .Err = transformed_err }) catch unreachable); | |
| 550 | }; | |
| 551 | }, | |
| 552 | else => unreachable, | |
| 553 | } | |
| 554 | } | |
| 471 | 555 | } |
| 472 | } | |
| 556 | }; | |
| 473 | 557 | } |
| 474 | 558 | |
| 475 | 559 | const test_tmp_dir = "std_event_fs_test"; |
| ... | ... | @@ -517,9 +601,11 @@ async fn testFsWatch(loop: *event.Loop) !void { |
| 517 | 601 | assert(mem.eql(u8, read_contents, contents)); |
| 518 | 602 | |
| 519 | 603 | // now watch the file |
| 520 | var watch = try watchFile(loop, file_path); | |
| 604 | var watch = try Watch(void).create(loop, 0); | |
| 521 | 605 | defer watch.destroy(); |
| 522 | 606 | |
| 607 | assert((try await try async watch.addFile(file_path, {})) == null); | |
| 608 | ||
| 523 | 609 | const ev = try async watch.channel.get(); |
| 524 | 610 | var ev_consumed = false; |
| 525 | 611 | defer if (!ev_consumed) cancel ev; |
| ... | ... | @@ -534,8 +620,8 @@ async fn testFsWatch(loop: *event.Loop) !void { |
| 534 | 620 | |
| 535 | 621 | ev_consumed = true; |
| 536 | 622 | switch (await ev) { |
| 537 | Watch.Event.CloseWrite => {}, | |
| 538 | Watch.Event.Err => |err| return err, | |
| 623 | Watch(void).Event.CloseWrite => {}, | |
| 624 | Watch(void).Event.Err => |err| return err, | |
| 539 | 625 | } |
| 540 | 626 | |
| 541 | 627 | const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); |
std/event/rwlock.zig+2| ... | ... | @@ -10,6 +10,8 @@ const Loop = std.event.Loop; |
| 10 | 10 | /// Does not make any syscalls - coroutines which are waiting for the lock are suspended, and |
| 11 | 11 | /// are resumed when the lock is released, in order. |
| 12 | 12 | /// Many readers can hold the lock at the same time; however locking for writing is exclusive. |
| 13 | /// When a read lock is held, it will not be released until the reader queue is empty. | |
| 14 | /// When a write lock is held, it will not be released until the writer queue is empty. | |
| 13 | 15 | pub const RwLock = struct { |
| 14 | 16 | loop: *Loop, |
| 15 | 17 | shared_state: u8, // TODO make this an enum |
std/hash_map.zig+244-54| ... | ... | @@ -9,6 +9,10 @@ const builtin = @import("builtin"); |
| 9 | 9 | const want_modification_safety = builtin.mode != builtin.Mode.ReleaseFast; |
| 10 | 10 | const debug_u32 = if (want_modification_safety) u32 else void; |
| 11 | 11 | |
| 12 | pub fn AutoHashMap(comptime K: type, comptime V: type) type { | |
| 13 | return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K)); | |
| 14 | } | |
| 15 | ||
| 12 | 16 | pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u32, comptime eql: fn (a: K, b: K) bool) type { |
| 13 | 17 | return struct { |
| 14 | 18 | entries: []Entry, |
| ... | ... | @@ -20,13 +24,22 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 20 | 24 | |
| 21 | 25 | const Self = this; |
| 22 | 26 | |
| 23 | pub const Entry = struct { | |
| 24 | used: bool, | |
| 25 | distance_from_start_index: usize, | |
| 27 | pub const KV = struct { | |
| 26 | 28 | key: K, |
| 27 | 29 | value: V, |
| 28 | 30 | }; |
| 29 | 31 | |
| 32 | const Entry = struct { | |
| 33 | used: bool, | |
| 34 | distance_from_start_index: usize, | |
| 35 | kv: KV, | |
| 36 | }; | |
| 37 | ||
| 38 | pub const GetOrPutResult = struct { | |
| 39 | kv: *KV, | |
| 40 | found_existing: bool, | |
| 41 | }; | |
| 42 | ||
| 30 | 43 | pub const Iterator = struct { |
| 31 | 44 | hm: *const Self, |
| 32 | 45 | // how many items have we returned |
| ... | ... | @@ -36,7 +49,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 36 | 49 | // used to detect concurrent modification |
| 37 | 50 | initial_modification_count: debug_u32, |
| 38 | 51 | |
| 39 | pub fn next(it: *Iterator) ?*Entry { | |
| 52 | pub fn next(it: *Iterator) ?*KV { | |
| 40 | 53 | if (want_modification_safety) { |
| 41 | 54 | assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification |
| 42 | 55 | } |
| ... | ... | @@ -46,7 +59,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 46 | 59 | if (entry.used) { |
| 47 | 60 | it.index += 1; |
| 48 | 61 | it.count += 1; |
| 49 | return entry; | |
| 62 | return &entry.kv; | |
| 50 | 63 | } |
| 51 | 64 | } |
| 52 | 65 | unreachable; // no next item |
| ... | ... | @@ -71,7 +84,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 71 | 84 | }; |
| 72 | 85 | } |
| 73 | 86 | |
| 74 | pub fn deinit(hm: *const Self) void { | |
| 87 | pub fn deinit(hm: Self) void { | |
| 75 | 88 | hm.allocator.free(hm.entries); |
| 76 | 89 | } |
| 77 | 90 | |
| ... | ... | @@ -84,34 +97,65 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 84 | 97 | hm.incrementModificationCount(); |
| 85 | 98 | } |
| 86 | 99 | |
| 87 | pub fn count(hm: *const Self) usize { | |
| 88 | return hm.size; | |
| 100 | pub fn count(self: Self) usize { | |
| 101 | return self.size; | |
| 89 | 102 | } |
| 90 | 103 | |
| 91 | /// Returns the value that was already there. | |
| 92 | pub fn put(hm: *Self, key: K, value: *const V) !?V { | |
| 93 | if (hm.entries.len == 0) { | |
| 94 | try hm.initCapacity(16); | |
| 104 | /// If key exists this function cannot fail. | |
| 105 | /// If there is an existing item with `key`, then the result | |
| 106 | /// kv pointer points to it, and found_existing is true. | |
| 107 | /// Otherwise, puts a new item with undefined value, and | |
| 108 | /// the kv pointer points to it. Caller should then initialize | |
| 109 | /// the data. | |
| 110 | pub fn getOrPut(self: *Self, key: K) !GetOrPutResult { | |
| 111 | // TODO this implementation can be improved - we should only | |
| 112 | // have to hash once and find the entry once. | |
| 113 | if (self.get(key)) |kv| { | |
| 114 | return GetOrPutResult{ | |
| 115 | .kv = kv, | |
| 116 | .found_existing = true, | |
| 117 | }; | |
| 118 | } | |
| 119 | self.incrementModificationCount(); | |
| 120 | try self.ensureCapacity(); | |
| 121 | const put_result = self.internalPut(key); | |
| 122 | assert(put_result.old_kv == null); | |
| 123 | return GetOrPutResult{ | |
| 124 | .kv = &put_result.new_entry.kv, | |
| 125 | .found_existing = false, | |
| 126 | }; | |
| 127 | } | |
| 128 | ||
| 129 | fn ensureCapacity(self: *Self) !void { | |
| 130 | if (self.entries.len == 0) { | |
| 131 | return self.initCapacity(16); | |
| 95 | 132 | } |
| 96 | hm.incrementModificationCount(); | |
| 97 | 133 | |
| 98 | 134 | // if we get too full (60%), double the capacity |
| 99 | if (hm.size * 5 >= hm.entries.len * 3) { | |
| 100 | const old_entries = hm.entries; | |
| 101 | try hm.initCapacity(hm.entries.len * 2); | |
| 135 | if (self.size * 5 >= self.entries.len * 3) { | |
| 136 | const old_entries = self.entries; | |
| 137 | try self.initCapacity(self.entries.len * 2); | |
| 102 | 138 | // dump all of the old elements into the new table |
| 103 | 139 | for (old_entries) |*old_entry| { |
| 104 | 140 | if (old_entry.used) { |
| 105 | _ = hm.internalPut(old_entry.key, old_entry.value); | |
| 141 | self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value; | |
| 106 | 142 | } |
| 107 | 143 | } |
| 108 | hm.allocator.free(old_entries); | |
| 144 | self.allocator.free(old_entries); | |
| 109 | 145 | } |
| 146 | } | |
| 110 | 147 | |
| 111 | return hm.internalPut(key, value); | |
| 148 | /// Returns the kv pair that was already there. | |
| 149 | pub fn put(self: *Self, key: K, value: V) !?KV { | |
| 150 | self.incrementModificationCount(); | |
| 151 | try self.ensureCapacity(); | |
| 152 | ||
| 153 | const put_result = self.internalPut(key); | |
| 154 | put_result.new_entry.kv.value = value; | |
| 155 | return put_result.old_kv; | |
| 112 | 156 | } |
| 113 | 157 | |
| 114 | pub fn get(hm: *const Self, key: K) ?*Entry { | |
| 158 | pub fn get(hm: *const Self, key: K) ?*KV { | |
| 115 | 159 | if (hm.entries.len == 0) { |
| 116 | 160 | return null; |
| 117 | 161 | } |
| ... | ... | @@ -122,7 +166,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 122 | 166 | return hm.get(key) != null; |
| 123 | 167 | } |
| 124 | 168 | |
| 125 | pub fn remove(hm: *Self, key: K) ?*Entry { | |
| 169 | pub fn remove(hm: *Self, key: K) ?*KV { | |
| 126 | 170 | if (hm.entries.len == 0) return null; |
| 127 | 171 | hm.incrementModificationCount(); |
| 128 | 172 | const start_index = hm.keyToIndex(key); |
| ... | ... | @@ -134,7 +178,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 134 | 178 | |
| 135 | 179 | if (!entry.used) return null; |
| 136 | 180 | |
| 137 | if (!eql(entry.key, key)) continue; | |
| 181 | if (!eql(entry.kv.key, key)) continue; | |
| 138 | 182 | |
| 139 | 183 | while (roll_over < hm.entries.len) : (roll_over += 1) { |
| 140 | 184 | const next_index = (start_index + roll_over + 1) % hm.entries.len; |
| ... | ... | @@ -142,7 +186,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 142 | 186 | if (!next_entry.used or next_entry.distance_from_start_index == 0) { |
| 143 | 187 | entry.used = false; |
| 144 | 188 | hm.size -= 1; |
| 145 | return entry; | |
| 189 | return &entry.kv; | |
| 146 | 190 | } |
| 147 | 191 | entry.* = next_entry.*; |
| 148 | 192 | entry.distance_from_start_index -= 1; |
| ... | ... | @@ -168,7 +212,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 168 | 212 | try other.initCapacity(self.entries.len); |
| 169 | 213 | var it = self.iterator(); |
| 170 | 214 | while (it.next()) |entry| { |
| 171 | try other.put(entry.key, entry.value); | |
| 215 | assert((try other.put(entry.key, entry.value)) == null); | |
| 172 | 216 | } |
| 173 | 217 | return other; |
| 174 | 218 | } |
| ... | ... | @@ -188,60 +232,81 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 188 | 232 | } |
| 189 | 233 | } |
| 190 | 234 | |
| 191 | /// Returns the value that was already there. | |
| 192 | fn internalPut(hm: *Self, orig_key: K, orig_value: *const V) ?V { | |
| 235 | const InternalPutResult = struct { | |
| 236 | new_entry: *Entry, | |
| 237 | old_kv: ?KV, | |
| 238 | }; | |
| 239 | ||
| 240 | /// Returns a pointer to the new entry. | |
| 241 | /// Asserts that there is enough space for the new item. | |
| 242 | fn internalPut(self: *Self, orig_key: K) InternalPutResult { | |
| 193 | 243 | var key = orig_key; |
| 194 | var value = orig_value.*; | |
| 195 | const start_index = hm.keyToIndex(key); | |
| 244 | var value: V = undefined; | |
| 245 | const start_index = self.keyToIndex(key); | |
| 196 | 246 | var roll_over: usize = 0; |
| 197 | 247 | var distance_from_start_index: usize = 0; |
| 198 | while (roll_over < hm.entries.len) : ({ | |
| 248 | var got_result_entry = false; | |
| 249 | var result = InternalPutResult{ | |
| 250 | .new_entry = undefined, | |
| 251 | .old_kv = null, | |
| 252 | }; | |
| 253 | while (roll_over < self.entries.len) : ({ | |
| 199 | 254 | roll_over += 1; |
| 200 | 255 | distance_from_start_index += 1; |
| 201 | 256 | }) { |
| 202 | const index = (start_index + roll_over) % hm.entries.len; | |
| 203 | const entry = &hm.entries[index]; | |
| 257 | const index = (start_index + roll_over) % self.entries.len; | |
| 258 | const entry = &self.entries[index]; | |
| 204 | 259 | |
| 205 | if (entry.used and !eql(entry.key, key)) { | |
| 260 | if (entry.used and !eql(entry.kv.key, key)) { | |
| 206 | 261 | if (entry.distance_from_start_index < distance_from_start_index) { |
| 207 | 262 | // robin hood to the rescue |
| 208 | 263 | const tmp = entry.*; |
| 209 | hm.max_distance_from_start_index = math.max(hm.max_distance_from_start_index, distance_from_start_index); | |
| 264 | self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index); | |
| 265 | if (!got_result_entry) { | |
| 266 | got_result_entry = true; | |
| 267 | result.new_entry = entry; | |
| 268 | } | |
| 210 | 269 | entry.* = Entry{ |
| 211 | 270 | .used = true, |
| 212 | 271 | .distance_from_start_index = distance_from_start_index, |
| 213 | .key = key, | |
| 214 | .value = value, | |
| 272 | .kv = KV{ | |
| 273 | .key = key, | |
| 274 | .value = value, | |
| 275 | }, | |
| 215 | 276 | }; |
| 216 | key = tmp.key; | |
| 217 | value = tmp.value; | |
| 277 | key = tmp.kv.key; | |
| 278 | value = tmp.kv.value; | |
| 218 | 279 | distance_from_start_index = tmp.distance_from_start_index; |
| 219 | 280 | } |
| 220 | 281 | continue; |
| 221 | 282 | } |
| 222 | 283 | |
| 223 | var result: ?V = null; | |
| 224 | 284 | if (entry.used) { |
| 225 | result = entry.value; | |
| 285 | result.old_kv = entry.kv; | |
| 226 | 286 | } else { |
| 227 | 287 | // adding an entry. otherwise overwriting old value with |
| 228 | 288 | // same key |
| 229 | hm.size += 1; | |
| 289 | self.size += 1; | |
| 230 | 290 | } |
| 231 | 291 | |
| 232 | hm.max_distance_from_start_index = math.max(distance_from_start_index, hm.max_distance_from_start_index); | |
| 292 | self.max_distance_from_start_index = math.max(distance_from_start_index, self.max_distance_from_start_index); | |
| 293 | if (!got_result_entry) { | |
| 294 | result.new_entry = entry; | |
| 295 | } | |
| 233 | 296 | entry.* = Entry{ |
| 234 | 297 | .used = true, |
| 235 | 298 | .distance_from_start_index = distance_from_start_index, |
| 236 | .key = key, | |
| 237 | .value = value, | |
| 299 | .kv = KV{ | |
| 300 | .key = key, | |
| 301 | .value = value, | |
| 302 | }, | |
| 238 | 303 | }; |
| 239 | 304 | return result; |
| 240 | 305 | } |
| 241 | 306 | unreachable; // put into a full map |
| 242 | 307 | } |
| 243 | 308 | |
| 244 | fn internalGet(hm: *const Self, key: K) ?*Entry { | |
| 309 | fn internalGet(hm: Self, key: K) ?*KV { | |
| 245 | 310 | const start_index = hm.keyToIndex(key); |
| 246 | 311 | { |
| 247 | 312 | var roll_over: usize = 0; |
| ... | ... | @@ -250,13 +315,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 250 | 315 | const entry = &hm.entries[index]; |
| 251 | 316 | |
| 252 | 317 | if (!entry.used) return null; |
| 253 | if (eql(entry.key, key)) return entry; | |
| 318 | if (eql(entry.kv.key, key)) return &entry.kv; | |
| 254 | 319 | } |
| 255 | 320 | } |
| 256 | 321 | return null; |
| 257 | 322 | } |
| 258 | 323 | |
| 259 | fn keyToIndex(hm: *const Self, key: K) usize { | |
| 324 | fn keyToIndex(hm: Self, key: K) usize { | |
| 260 | 325 | return usize(hash(key)) % hm.entries.len; |
| 261 | 326 | } |
| 262 | 327 | }; |
| ... | ... | @@ -266,7 +331,7 @@ test "basic hash map usage" { |
| 266 | 331 | var direct_allocator = std.heap.DirectAllocator.init(); |
| 267 | 332 | defer direct_allocator.deinit(); |
| 268 | 333 | |
| 269 | var map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator); | |
| 334 | var map = AutoHashMap(i32, i32).init(&direct_allocator.allocator); | |
| 270 | 335 | defer map.deinit(); |
| 271 | 336 | |
| 272 | 337 | assert((try map.put(1, 11)) == null); |
| ... | ... | @@ -275,8 +340,19 @@ test "basic hash map usage" { |
| 275 | 340 | assert((try map.put(4, 44)) == null); |
| 276 | 341 | assert((try map.put(5, 55)) == null); |
| 277 | 342 | |
| 278 | assert((try map.put(5, 66)).? == 55); | |
| 279 | assert((try map.put(5, 55)).? == 66); | |
| 343 | assert((try map.put(5, 66)).?.value == 55); | |
| 344 | assert((try map.put(5, 55)).?.value == 66); | |
| 345 | ||
| 346 | const gop1 = try map.getOrPut(5); | |
| 347 | assert(gop1.found_existing == true); | |
| 348 | assert(gop1.kv.value == 55); | |
| 349 | gop1.kv.value = 77; | |
| 350 | assert(map.get(5).?.value == 77); | |
| 351 | ||
| 352 | const gop2 = try map.getOrPut(99); | |
| 353 | assert(gop2.found_existing == false); | |
| 354 | gop2.kv.value = 42; | |
| 355 | assert(map.get(99).?.value == 42); | |
| 280 | 356 | |
| 281 | 357 | assert(map.contains(2)); |
| 282 | 358 | assert(map.get(2).?.value == 22); |
| ... | ... | @@ -289,7 +365,7 @@ test "iterator hash map" { |
| 289 | 365 | var direct_allocator = std.heap.DirectAllocator.init(); |
| 290 | 366 | defer direct_allocator.deinit(); |
| 291 | 367 | |
| 292 | var reset_map = HashMap(i32, i32, hash_i32, eql_i32).init(&direct_allocator.allocator); | |
| 368 | var reset_map = AutoHashMap(i32, i32).init(&direct_allocator.allocator); | |
| 293 | 369 | defer reset_map.deinit(); |
| 294 | 370 | |
| 295 | 371 | assert((try reset_map.put(1, 11)) == null); |
| ... | ... | @@ -332,10 +408,124 @@ test "iterator hash map" { |
| 332 | 408 | assert(entry.value == values[0]); |
| 333 | 409 | } |
| 334 | 410 | |
| 335 | fn hash_i32(x: i32) u32 { | |
| 336 | return @bitCast(u32, x); | |
| 411 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | |
| 412 | return struct { | |
| 413 | fn hash(key: K) u32 { | |
| 414 | comptime var rng = comptime std.rand.DefaultPrng.init(0); | |
| 415 | return autoHash(key, &rng.random, u32); | |
| 416 | } | |
| 417 | }.hash; | |
| 418 | } | |
| 419 | ||
| 420 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 421 | return struct { | |
| 422 | fn eql(a: K, b: K) bool { | |
| 423 | return autoEql(a, b); | |
| 424 | } | |
| 425 | }.eql; | |
| 426 | } | |
| 427 | ||
| 428 | // TODO improve these hash functions | |
| 429 | pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt { | |
| 430 | switch (@typeInfo(@typeOf(key))) { | |
| 431 | builtin.TypeId.NoReturn, | |
| 432 | builtin.TypeId.Opaque, | |
| 433 | builtin.TypeId.Undefined, | |
| 434 | builtin.TypeId.ArgTuple, | |
| 435 | => @compileError("cannot hash this type"), | |
| 436 | ||
| 437 | builtin.TypeId.Void, | |
| 438 | builtin.TypeId.Null, | |
| 439 | => return 0, | |
| 440 | ||
| 441 | builtin.TypeId.Int => |info| { | |
| 442 | const unsigned_x = @bitCast(@IntType(false, info.bits), key); | |
| 443 | if (info.bits <= HashInt.bit_count) { | |
| 444 | return HashInt(unsigned_x) *% comptime rng.scalar(HashInt); | |
| 445 | } else { | |
| 446 | return @truncate(HashInt, unsigned_x *% comptime rng.scalar(@typeOf(unsigned_x))); | |
| 447 | } | |
| 448 | }, | |
| 449 | ||
| 450 | builtin.TypeId.Float => |info| { | |
| 451 | return autoHash(@bitCast(@IntType(false, info.bits), key), rng); | |
| 452 | }, | |
| 453 | builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng), | |
| 454 | builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng), | |
| 455 | builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng), | |
| 456 | builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng), | |
| 457 | ||
| 458 | builtin.TypeId.Namespace, | |
| 459 | builtin.TypeId.Block, | |
| 460 | builtin.TypeId.BoundFn, | |
| 461 | builtin.TypeId.ComptimeFloat, | |
| 462 | builtin.TypeId.ComptimeInt, | |
| 463 | builtin.TypeId.Type, | |
| 464 | => return 0, | |
| 465 | ||
| 466 | builtin.TypeId.Pointer => |info| switch (info.size) { | |
| 467 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"), | |
| 468 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"), | |
| 469 | builtin.TypeInfo.Pointer.Size.Slice => { | |
| 470 | const interval = std.math.max(1, key.len / 256); | |
| 471 | var i: usize = 0; | |
| 472 | var h = comptime rng.scalar(HashInt); | |
| 473 | while (i < key.len) : (i += interval) { | |
| 474 | h ^= autoHash(key[i], rng, HashInt); | |
| 475 | } | |
| 476 | return h; | |
| 477 | }, | |
| 478 | }, | |
| 479 | ||
| 480 | builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"), | |
| 481 | builtin.TypeId.Array => @compileError("TODO auto hash for arrays"), | |
| 482 | builtin.TypeId.Struct => @compileError("TODO auto hash for structs"), | |
| 483 | builtin.TypeId.Union => @compileError("TODO auto hash for unions"), | |
| 484 | builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"), | |
| 485 | } | |
| 337 | 486 | } |
| 338 | 487 | |
| 339 | fn eql_i32(a: i32, b: i32) bool { | |
| 340 | return a == b; | |
| 488 | pub fn autoEql(a: var, b: @typeOf(a)) bool { | |
| 489 | switch (@typeInfo(@typeOf(a))) { | |
| 490 | builtin.TypeId.NoReturn, | |
| 491 | builtin.TypeId.Opaque, | |
| 492 | builtin.TypeId.Undefined, | |
| 493 | builtin.TypeId.ArgTuple, | |
| 494 | => @compileError("cannot test equality of this type"), | |
| 495 | builtin.TypeId.Void, | |
| 496 | builtin.TypeId.Null, | |
| 497 | => return true, | |
| 498 | builtin.TypeId.Bool, | |
| 499 | builtin.TypeId.Int, | |
| 500 | builtin.TypeId.Float, | |
| 501 | builtin.TypeId.ComptimeFloat, | |
| 502 | builtin.TypeId.ComptimeInt, | |
| 503 | builtin.TypeId.Namespace, | |
| 504 | builtin.TypeId.Block, | |
| 505 | builtin.TypeId.Promise, | |
| 506 | builtin.TypeId.Enum, | |
| 507 | builtin.TypeId.BoundFn, | |
| 508 | builtin.TypeId.Fn, | |
| 509 | builtin.TypeId.ErrorSet, | |
| 510 | builtin.TypeId.Type, | |
| 511 | => return a == b, | |
| 512 | ||
| 513 | builtin.TypeId.Pointer => |info| switch (info.size) { | |
| 514 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"), | |
| 515 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"), | |
| 516 | builtin.TypeInfo.Pointer.Size.Slice => { | |
| 517 | if (a.len != b.len) return false; | |
| 518 | for (a) |a_item, i| { | |
| 519 | if (!autoEql(a_item, b[i])) return false; | |
| 520 | } | |
| 521 | return true; | |
| 522 | }, | |
| 523 | }, | |
| 524 | ||
| 525 | builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"), | |
| 526 | builtin.TypeId.Array => @compileError("TODO auto eql for arrays"), | |
| 527 | builtin.TypeId.Struct => @compileError("TODO auto eql for structs"), | |
| 528 | builtin.TypeId.Union => @compileError("TODO auto eql for unions"), | |
| 529 | builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"), | |
| 530 | } | |
| 341 | 531 | } |
std/index.zig+1| ... | ... | @@ -5,6 +5,7 @@ pub const BufSet = @import("buf_set.zig").BufSet; |
| 5 | 5 | 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 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; | |
| 8 | 9 | pub const LinkedList = @import("linked_list.zig").LinkedList; |
| 9 | 10 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; |
| 10 | 11 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
std/json.zig+1-1| ... | ... | @@ -1318,7 +1318,7 @@ pub const Parser = struct { |
| 1318 | 1318 | _ = p.stack.pop(); |
| 1319 | 1319 | |
| 1320 | 1320 | var object = &p.stack.items[p.stack.len - 1].Object; |
| 1321 | _ = try object.put(key, value); | |
| 1321 | _ = try object.put(key, value.*); | |
| 1322 | 1322 | p.state = State.ObjectKey; |
| 1323 | 1323 | }, |
| 1324 | 1324 | // Array Parent -> [ ..., <array>, value ] |
std/special/build_runner.zig+2-2| ... | ... | @@ -72,10 +72,10 @@ pub fn main() !void { |
| 72 | 72 | if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| { |
| 73 | 73 | const option_name = option_contents[0..name_end]; |
| 74 | 74 | const option_value = option_contents[name_end + 1 ..]; |
| 75 | if (builder.addUserInputOption(option_name, option_value)) | |
| 75 | if (try builder.addUserInputOption(option_name, option_value)) | |
| 76 | 76 | return usageAndErr(&builder, false, try stderr_stream); |
| 77 | 77 | } else { |
| 78 | if (builder.addUserInputFlag(option_contents)) | |
| 78 | if (try builder.addUserInputFlag(option_contents)) | |
| 79 | 79 | return usageAndErr(&builder, false, try stderr_stream); |
| 80 | 80 | } |
| 81 | 81 | } else if (mem.startsWith(u8, arg, "-")) { |