authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-18 12:35:36-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-18 12:35:36-07:00
logab8f8465a301ded54d2a2504ca3394ec1425cacb
tree60c94fd3f2695db767fc72bdf7e9528d788428cc
parent4187008b5ed3118d9cf58dbdff8bad2974a7910d

stage2: fix deletion of Decls that get re-referenced

When scanDecls happens, we create stub Decl objects that have not been semantically analyzed. When they get referenced, they get semantically analyzed. Before this commit, when they got unreferenced, they were completely deleted, including deleted from the containing Namespace. However, if the update did not cause the containing Namespace to get deleted, for example, if `std.builtin.ExportOptions` is no longer referenced, but `std.builtin` is still referenced, and then `ExportOptions` gets referenced again, the Namespace would be incorrectly missing the Decl, so we get an incorrect "no such member" error. The solution is to, when dealing with a no longer referenced Decl objects during an update, clear them to the state they would be in on a fresh scanDecl, rather than completely deleting them.

3 files changed, 99 insertions(+), 64 deletions(-)

src/Compilation.zig+10-2
......@@ -1620,13 +1620,21 @@ pub fn update(self: *Compilation) !void {
16201620 if (!use_stage1) {
16211621 if (self.bin_file.options.module) |module| {
16221622 // Process the deletion set. We use a while loop here because the
1623 // deletion set may grow as we call `deleteDecl` within this loop,
1623 // deletion set may grow as we call `clearDecl` within this loop,
16241624 // and more unreferenced Decls are revealed.
16251625 while (module.deletion_set.entries.items.len != 0) {
16261626 const decl = module.deletion_set.entries.items[0].key;
16271627 assert(decl.deletion_flag);
16281628 assert(decl.dependants.count() == 0);
1629 try module.deleteDecl(decl, null);
1629 const is_anon = if (decl.zir_decl_index == 0) blk: {
1630 break :blk decl.namespace.anon_decls.swapRemove(decl) != null;
1631 } else false;
1632
1633 try module.clearDecl(decl, null);
1634
1635 if (is_anon) {
1636 decl.destroy(module);
1637 }
16301638 }
16311639
16321640 try module.processExports();
src/Module.zig+69-39
......@@ -291,7 +291,7 @@ pub const Decl = struct {
291291 }
292292 if (decl.has_tv) {
293293 if (decl.getInnerNamespace()) |namespace| {
294 namespace.clearDecls(module);
294 namespace.destroyDecls(module);
295295 }
296296 decl.clearValues(gpa);
297297 }
......@@ -880,14 +880,14 @@ pub const Scope = struct {
880880 anon_decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
881881
882882 pub fn deinit(ns: *Namespace, mod: *Module) void {
883 ns.clearDecls(mod);
883 ns.destroyDecls(mod);
884884 ns.* = undefined;
885885 }
886886
887 pub fn clearDecls(ns: *Namespace, mod: *Module) void {
887 pub fn destroyDecls(ns: *Namespace, mod: *Module) void {
888888 const gpa = mod.gpa;
889889
890 log.debug("clearDecls {*}", .{ns});
890 log.debug("destroyDecls {*}", .{ns});
891891
892892 var decls = ns.decls;
893893 ns.decls = .{};
......@@ -915,30 +915,28 @@ pub const Scope = struct {
915915
916916 log.debug("deleteAllDecls {*}", .{ns});
917917
918 while (ns.decls.count() != 0) {
919 const last_entry = ns.decls.entries.items[ns.decls.entries.items.len - 1];
920 const child_decl = last_entry.value;
921 try mod.deleteDecl(child_decl, outdated_decls);
922 }
923 ns.decls.deinit(gpa);
918 var decls = ns.decls;
924919 ns.decls = .{};
925920
926 while (ns.anon_decls.count() != 0) {
927 const last_entry = ns.anon_decls.entries.items[ns.anon_decls.entries.items.len - 1];
928 const child_decl = last_entry.key;
929 try mod.deleteDecl(child_decl, outdated_decls);
930 }
931 ns.anon_decls.deinit(gpa);
921 var anon_decls = ns.anon_decls;
932922 ns.anon_decls = .{};
933 }
934923
935 pub fn removeDecl(ns: *Namespace, child: *Decl) void {
936 if (child.zir_decl_index == 0) {
937 _ = ns.anon_decls.swapRemove(child);
938 } else {
939 // Preserve declaration order.
940 _ = ns.decls.orderedRemove(mem.spanZ(child.name));
924 // TODO rework this code to not panic on OOM.
925 // (might want to coordinate with the clearDecl function)
926
927 for (decls.items()) |entry| {
928 const child_decl = entry.value;
929 mod.clearDecl(child_decl, outdated_decls) catch @panic("out of memory");
930 child_decl.destroy(mod);
931 }
932 decls.deinit(gpa);
933
934 for (anon_decls.items()) |entry| {
935 const child_decl = entry.key;
936 mod.clearDecl(child_decl, outdated_decls) catch @panic("out of memory");
937 child_decl.destroy(mod);
941938 }
939 anon_decls.deinit(gpa);
942940 }
943941
944942 // This renders e.g. "std.fs.Dir.OpenOptions"
......@@ -2122,6 +2120,14 @@ pub const InnerError = error{ OutOfMemory, AnalysisFail };
21222120pub fn deinit(mod: *Module) void {
21232121 const gpa = mod.gpa;
21242122
2123 for (mod.import_table.items()) |entry| {
2124 gpa.free(entry.key);
2125 entry.value.destroy(mod);
2126 }
2127 mod.import_table.deinit(gpa);
2128
2129 mod.deletion_set.deinit(gpa);
2130
21252131 // The callsite of `Compilation.create` owns the `root_pkg`, however
21262132 // Module owns the builtin and std packages that it adds.
21272133 if (mod.root_pkg.table.remove("builtin")) |entry| {
......@@ -2142,8 +2148,6 @@ pub fn deinit(mod: *Module) void {
21422148 mod.local_zir_cache.handle.close();
21432149 mod.global_zir_cache.handle.close();
21442150
2145 mod.deletion_set.deinit(gpa);
2146
21472151 for (mod.failed_decls.items()) |entry| {
21482152 entry.value.destroy(gpa);
21492153 }
......@@ -2188,12 +2192,6 @@ pub fn deinit(mod: *Module) void {
21882192 mod.global_error_set.deinit(gpa);
21892193
21902194 mod.error_name_list.deinit(gpa);
2191
2192 for (mod.import_table.items()) |entry| {
2193 gpa.free(entry.key);
2194 entry.value.destroy(mod);
2195 }
2196 mod.import_table.deinit(gpa);
21972195}
21982196
21992197fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
......@@ -3420,7 +3418,8 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) InnerError!vo
34203418 }
34213419}
34223420
3423pub fn deleteDecl(
3421/// Make it as if the semantic analysis for this Decl never happened.
3422pub fn clearDecl(
34243423 mod: *Module,
34253424 decl: *Decl,
34263425 outdated_decls: ?*std.AutoArrayHashMap(*Decl, void),
......@@ -3428,7 +3427,7 @@ pub fn deleteDecl(
34283427 const tracy = trace(@src());
34293428 defer tracy.end();
34303429
3431 log.debug("deleting {*} ({s})", .{ decl, decl.name });
3430 log.debug("clearing {*} ({s})", .{ decl, decl.name });
34323431
34333432 const gpa = mod.gpa;
34343433 try mod.deletion_set.ensureUnusedCapacity(gpa, decl.dependencies.count());
......@@ -3438,10 +3437,7 @@ pub fn deleteDecl(
34383437 try map.ensureUnusedCapacity(decl.dependants.count());
34393438 }
34403439
3441 // Remove from the namespace it resides in.
3442 decl.namespace.removeDecl(decl);
3443
3444 // Remove itself from its dependencies, because we are about to destroy the decl pointer.
3440 // Remove itself from its dependencies.
34453441 for (decl.dependencies.items()) |entry| {
34463442 const dep = entry.key;
34473443 dep.removeDependant(decl);
......@@ -3452,6 +3448,8 @@ pub fn deleteDecl(
34523448 mod.deletion_set.putAssumeCapacity(dep, {});
34533449 }
34543450 }
3451 decl.dependencies.clearRetainingCapacity();
3452
34553453 // Anything that depends on this deleted decl needs to be re-analyzed.
34563454 for (decl.dependants.items()) |entry| {
34573455 const dep = entry.key;
......@@ -3467,6 +3465,8 @@ pub fn deleteDecl(
34673465 assert(mod.deletion_set.contains(dep));
34683466 }
34693467 }
3468 decl.dependants.clearRetainingCapacity();
3469
34703470 if (mod.failed_decls.swapRemove(decl)) |entry| {
34713471 entry.value.destroy(gpa);
34723472 }
......@@ -3482,6 +3482,25 @@ pub fn deleteDecl(
34823482 if (decl.has_tv) {
34833483 if (decl.ty.hasCodeGenBits()) {
34843484 mod.comp.bin_file.freeDecl(decl);
3485
3486 // TODO instead of a union, put this memory trailing Decl objects,
3487 // and allow it to be variably sized.
3488 decl.link = switch (mod.comp.bin_file.tag) {
3489 .coff => .{ .coff = link.File.Coff.TextBlock.empty },
3490 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
3491 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
3492 .c => .{ .c = link.File.C.DeclBlock.empty },
3493 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
3494 .spirv => .{ .spirv = {} },
3495 };
3496 decl.fn_link = switch (mod.comp.bin_file.tag) {
3497 .coff => .{ .coff = {} },
3498 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
3499 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
3500 .c => .{ .c = link.File.C.FnBlock.empty },
3501 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
3502 .spirv => .{ .spirv = .{} },
3503 };
34853504 }
34863505 if (decl.getInnerNamespace()) |namespace| {
34873506 try namespace.deleteAllDecls(mod, outdated_decls);
......@@ -3489,7 +3508,12 @@ pub fn deleteDecl(
34893508 decl.clearValues(gpa);
34903509 }
34913510
3492 decl.destroy(mod);
3511 if (decl.deletion_flag) {
3512 decl.deletion_flag = false;
3513 mod.deletion_set.swapRemoveAssertDiscard(decl);
3514 }
3515
3516 decl.analysis = .unreferenced;
34933517}
34943518
34953519/// Delete all the Export objects that are caused by this Decl. Re-analysis of
......@@ -4836,7 +4860,13 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
48364860 // deletion set at this time.
48374861 for (file.deleted_decls.items) |decl| {
48384862 log.debug("deleted from source: {*} ({s})", .{ decl, decl.name });
4839 try mod.deleteDecl(decl, &outdated_decls);
4863
4864 // Remove from the namespace it resides in, preserving declaration order.
4865 assert(decl.zir_decl_index != 0);
4866 _ = decl.namespace.decls.orderedRemove(mem.spanZ(decl.name));
4867
4868 try mod.clearDecl(decl, &outdated_decls);
4869 decl.destroy(mod);
48404870 }
48414871 file.deleted_decls.clearRetainingCapacity();
48424872 }
test/stage2/test.zig+20-23
......@@ -66,12 +66,10 @@ pub fn addCases(ctx: *TestContext) !void {
6666 "Hello, World!\n",
6767 );
6868
69 // Now change the message only
69 // Convert to pub fn main
7070 case.addCompareOutput(
71 \\pub export fn _start() noreturn {
71 \\pub fn main() void {
7272 \\ print();
73 \\
74 \\ exit();
7573 \\}
7674 \\
7775 \\fn print() void {
......@@ -79,32 +77,41 @@ pub fn addCases(ctx: *TestContext) !void {
7977 \\ :
8078 \\ : [number] "{rax}" (1),
8179 \\ [arg1] "{rdi}" (1),
82 \\ [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
83 \\ [arg3] "{rdx}" (104)
80 \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
81 \\ [arg3] "{rdx}" (14)
8482 \\ : "rcx", "r11", "memory"
8583 \\ );
8684 \\ return;
8785 \\}
86 ,
87 "Hello, World!\n",
88 );
89
90 // Now change the message only
91 case.addCompareOutput(
92 \\pub fn main() void {
93 \\ print();
94 \\}
8895 \\
89 \\fn exit() noreturn {
96 \\fn print() void {
9097 \\ asm volatile ("syscall"
9198 \\ :
92 \\ : [number] "{rax}" (231),
93 \\ [arg1] "{rdi}" (0)
99 \\ : [number] "{rax}" (1),
100 \\ [arg1] "{rdi}" (1),
101 \\ [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")),
102 \\ [arg3] "{rdx}" (104)
94103 \\ : "rcx", "r11", "memory"
95104 \\ );
96 \\ unreachable;
105 \\ return;
97106 \\}
98107 ,
99108 "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n",
100109 );
101110 // Now we print it twice.
102111 case.addCompareOutput(
103 \\pub export fn _start() noreturn {
112 \\pub fn main() void {
104113 \\ print();
105114 \\ print();
106 \\
107 \\ exit();
108115 \\}
109116 \\
110117 \\fn print() void {
......@@ -118,16 +125,6 @@ pub fn addCases(ctx: *TestContext) !void {
118125 \\ );
119126 \\ return;
120127 \\}
121 \\
122 \\fn exit() noreturn {
123 \\ asm volatile ("syscall"
124 \\ :
125 \\ : [number] "{rax}" (231),
126 \\ [arg1] "{rdi}" (0)
127 \\ : "rcx", "r11", "memory"
128 \\ );
129 \\ unreachable;
130 \\}
131128 ,
132129 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.
133130 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.