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 {...@@ -1620,13 +1620,21 @@ pub fn update(self: *Compilation) !void {
1620 if (!use_stage1) {1620 if (!use_stage1) {
1621 if (self.bin_file.options.module) |module| {1621 if (self.bin_file.options.module) |module| {
1622 // Process the deletion set. We use a while loop here because the1622 // 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,
1624 // and more unreferenced Decls are revealed.1624 // and more unreferenced Decls are revealed.
1625 while (module.deletion_set.entries.items.len != 0) {1625 while (module.deletion_set.entries.items.len != 0) {
1626 const decl = module.deletion_set.entries.items[0].key;1626 const decl = module.deletion_set.entries.items[0].key;
1627 assert(decl.deletion_flag);1627 assert(decl.deletion_flag);
1628 assert(decl.dependants.count() == 0);1628 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 }
1630 }1638 }
16311639
1632 try module.processExports();1640 try module.processExports();
src/Module.zig+69-39
...@@ -291,7 +291,7 @@ pub const Decl = struct {...@@ -291,7 +291,7 @@ pub const Decl = struct {
291 }291 }
292 if (decl.has_tv) {292 if (decl.has_tv) {
293 if (decl.getInnerNamespace()) |namespace| {293 if (decl.getInnerNamespace()) |namespace| {
294 namespace.clearDecls(module);294 namespace.destroyDecls(module);
295 }295 }
296 decl.clearValues(gpa);296 decl.clearValues(gpa);
297 }297 }
...@@ -880,14 +880,14 @@ pub const Scope = struct {...@@ -880,14 +880,14 @@ pub const Scope = struct {
880 anon_decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},880 anon_decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
881881
882 pub fn deinit(ns: *Namespace, mod: *Module) void {882 pub fn deinit(ns: *Namespace, mod: *Module) void {
883 ns.clearDecls(mod);883 ns.destroyDecls(mod);
884 ns.* = undefined;884 ns.* = undefined;
885 }885 }
886886
887 pub fn clearDecls(ns: *Namespace, mod: *Module) void {887 pub fn destroyDecls(ns: *Namespace, mod: *Module) void {
888 const gpa = mod.gpa;888 const gpa = mod.gpa;
889889
890 log.debug("clearDecls {*}", .{ns});890 log.debug("destroyDecls {*}", .{ns});
891891
892 var decls = ns.decls;892 var decls = ns.decls;
893 ns.decls = .{};893 ns.decls = .{};
...@@ -915,30 +915,28 @@ pub const Scope = struct {...@@ -915,30 +915,28 @@ pub const Scope = struct {
915915
916 log.debug("deleteAllDecls {*}", .{ns});916 log.debug("deleteAllDecls {*}", .{ns});
917917
918 while (ns.decls.count() != 0) {918 var decls = ns.decls;
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);
924 ns.decls = .{};919 ns.decls = .{};
925920
926 while (ns.anon_decls.count() != 0) {921 var anon_decls = ns.anon_decls;
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);
932 ns.anon_decls = .{};922 ns.anon_decls = .{};
933 }
934923
935 pub fn removeDecl(ns: *Namespace, child: *Decl) void {924 // TODO rework this code to not panic on OOM.
936 if (child.zir_decl_index == 0) {925 // (might want to coordinate with the clearDecl function)
937 _ = ns.anon_decls.swapRemove(child);926
938 } else {927 for (decls.items()) |entry| {
939 // Preserve declaration order.928 const child_decl = entry.value;
940 _ = ns.decls.orderedRemove(mem.spanZ(child.name));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);
941 }938 }
939 anon_decls.deinit(gpa);
942 }940 }
943941
944 // This renders e.g. "std.fs.Dir.OpenOptions"942 // This renders e.g. "std.fs.Dir.OpenOptions"
...@@ -2122,6 +2120,14 @@ pub const InnerError = error{ OutOfMemory, AnalysisFail };...@@ -2122,6 +2120,14 @@ pub const InnerError = error{ OutOfMemory, AnalysisFail };
2122pub fn deinit(mod: *Module) void {2120pub fn deinit(mod: *Module) void {
2123 const gpa = mod.gpa;2121 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
2125 // The callsite of `Compilation.create` owns the `root_pkg`, however2131 // The callsite of `Compilation.create` owns the `root_pkg`, however
2126 // Module owns the builtin and std packages that it adds.2132 // Module owns the builtin and std packages that it adds.
2127 if (mod.root_pkg.table.remove("builtin")) |entry| {2133 if (mod.root_pkg.table.remove("builtin")) |entry| {
...@@ -2142,8 +2148,6 @@ pub fn deinit(mod: *Module) void {...@@ -2142,8 +2148,6 @@ pub fn deinit(mod: *Module) void {
2142 mod.local_zir_cache.handle.close();2148 mod.local_zir_cache.handle.close();
2143 mod.global_zir_cache.handle.close();2149 mod.global_zir_cache.handle.close();
21442150
2145 mod.deletion_set.deinit(gpa);
2146
2147 for (mod.failed_decls.items()) |entry| {2151 for (mod.failed_decls.items()) |entry| {
2148 entry.value.destroy(gpa);2152 entry.value.destroy(gpa);
2149 }2153 }
...@@ -2188,12 +2192,6 @@ pub fn deinit(mod: *Module) void {...@@ -2188,12 +2192,6 @@ pub fn deinit(mod: *Module) void {
2188 mod.global_error_set.deinit(gpa);2192 mod.global_error_set.deinit(gpa);
21892193
2190 mod.error_name_list.deinit(gpa);2194 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);
2197}2195}
21982196
2199fn freeExportList(gpa: *Allocator, export_list: []*Export) void {2197fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
...@@ -3420,7 +3418,8 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) InnerError!vo...@@ -3420,7 +3418,8 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) InnerError!vo
3420 }3418 }
3421}3419}
34223420
3423pub fn deleteDecl(3421/// Make it as if the semantic analysis for this Decl never happened.
3422pub fn clearDecl(
3424 mod: *Module,3423 mod: *Module,
3425 decl: *Decl,3424 decl: *Decl,
3426 outdated_decls: ?*std.AutoArrayHashMap(*Decl, void),3425 outdated_decls: ?*std.AutoArrayHashMap(*Decl, void),
...@@ -3428,7 +3427,7 @@ pub fn deleteDecl(...@@ -3428,7 +3427,7 @@ pub fn deleteDecl(
3428 const tracy = trace(@src());3427 const tracy = trace(@src());
3429 defer tracy.end();3428 defer tracy.end();
34303429
3431 log.debug("deleting {*} ({s})", .{ decl, decl.name });3430 log.debug("clearing {*} ({s})", .{ decl, decl.name });
34323431
3433 const gpa = mod.gpa;3432 const gpa = mod.gpa;
3434 try mod.deletion_set.ensureUnusedCapacity(gpa, decl.dependencies.count());3433 try mod.deletion_set.ensureUnusedCapacity(gpa, decl.dependencies.count());
...@@ -3438,10 +3437,7 @@ pub fn deleteDecl(...@@ -3438,10 +3437,7 @@ pub fn deleteDecl(
3438 try map.ensureUnusedCapacity(decl.dependants.count());3437 try map.ensureUnusedCapacity(decl.dependants.count());
3439 }3438 }
34403439
3441 // Remove from the namespace it resides in.3440 // Remove itself from its dependencies.
3442 decl.namespace.removeDecl(decl);
3443
3444 // Remove itself from its dependencies, because we are about to destroy the decl pointer.
3445 for (decl.dependencies.items()) |entry| {3441 for (decl.dependencies.items()) |entry| {
3446 const dep = entry.key;3442 const dep = entry.key;
3447 dep.removeDependant(decl);3443 dep.removeDependant(decl);
...@@ -3452,6 +3448,8 @@ pub fn deleteDecl(...@@ -3452,6 +3448,8 @@ pub fn deleteDecl(
3452 mod.deletion_set.putAssumeCapacity(dep, {});3448 mod.deletion_set.putAssumeCapacity(dep, {});
3453 }3449 }
3454 }3450 }
3451 decl.dependencies.clearRetainingCapacity();
3452
3455 // Anything that depends on this deleted decl needs to be re-analyzed.3453 // Anything that depends on this deleted decl needs to be re-analyzed.
3456 for (decl.dependants.items()) |entry| {3454 for (decl.dependants.items()) |entry| {
3457 const dep = entry.key;3455 const dep = entry.key;
...@@ -3467,6 +3465,8 @@ pub fn deleteDecl(...@@ -3467,6 +3465,8 @@ pub fn deleteDecl(
3467 assert(mod.deletion_set.contains(dep));3465 assert(mod.deletion_set.contains(dep));
3468 }3466 }
3469 }3467 }
3468 decl.dependants.clearRetainingCapacity();
3469
3470 if (mod.failed_decls.swapRemove(decl)) |entry| {3470 if (mod.failed_decls.swapRemove(decl)) |entry| {
3471 entry.value.destroy(gpa);3471 entry.value.destroy(gpa);
3472 }3472 }
...@@ -3482,6 +3482,25 @@ pub fn deleteDecl(...@@ -3482,6 +3482,25 @@ pub fn deleteDecl(
3482 if (decl.has_tv) {3482 if (decl.has_tv) {
3483 if (decl.ty.hasCodeGenBits()) {3483 if (decl.ty.hasCodeGenBits()) {
3484 mod.comp.bin_file.freeDecl(decl);3484 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 };
3485 }3504 }
3486 if (decl.getInnerNamespace()) |namespace| {3505 if (decl.getInnerNamespace()) |namespace| {
3487 try namespace.deleteAllDecls(mod, outdated_decls);3506 try namespace.deleteAllDecls(mod, outdated_decls);
...@@ -3489,7 +3508,12 @@ pub fn deleteDecl(...@@ -3489,7 +3508,12 @@ pub fn deleteDecl(
3489 decl.clearValues(gpa);3508 decl.clearValues(gpa);
3490 }3509 }
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;
3493}3517}
34943518
3495/// Delete all the Export objects that are caused by this Decl. Re-analysis of3519/// Delete all the Export objects that are caused by this Decl. Re-analysis of
...@@ -4836,7 +4860,13 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {...@@ -4836,7 +4860,13 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
4836 // deletion set at this time.4860 // deletion set at this time.
4837 for (file.deleted_decls.items) |decl| {4861 for (file.deleted_decls.items) |decl| {
4838 log.debug("deleted from source: {*} ({s})", .{ decl, decl.name });4862 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);
4840 }4870 }
4841 file.deleted_decls.clearRetainingCapacity();4871 file.deleted_decls.clearRetainingCapacity();
4842 }4872 }
test/stage2/test.zig+20-23
...@@ -66,12 +66,10 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -66,12 +66,10 @@ pub fn addCases(ctx: *TestContext) !void {
66 "Hello, World!\n",66 "Hello, World!\n",
67 );67 );
6868
69 // Now change the message only69 // Convert to pub fn main
70 case.addCompareOutput(70 case.addCompareOutput(
71 \\pub export fn _start() noreturn {71 \\pub fn main() void {
72 \\ print();72 \\ print();
73 \\
74 \\ exit();
75 \\}73 \\}
76 \\74 \\
77 \\fn print() void {75 \\fn print() void {
...@@ -79,32 +77,41 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -79,32 +77,41 @@ pub fn addCases(ctx: *TestContext) !void {
79 \\ :77 \\ :
80 \\ : [number] "{rax}" (1),78 \\ : [number] "{rax}" (1),
81 \\ [arg1] "{rdi}" (1),79 \\ [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")),80 \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
83 \\ [arg3] "{rdx}" (104)81 \\ [arg3] "{rdx}" (14)
84 \\ : "rcx", "r11", "memory"82 \\ : "rcx", "r11", "memory"
85 \\ );83 \\ );
86 \\ return;84 \\ return;
87 \\}85 \\}
86 ,
87 "Hello, World!\n",
88 );
89
90 // Now change the message only
91 case.addCompareOutput(
92 \\pub fn main() void {
93 \\ print();
94 \\}
88 \\95 \\
89 \\fn exit() noreturn {96 \\fn print() void {
90 \\ asm volatile ("syscall"97 \\ asm volatile ("syscall"
91 \\ :98 \\ :
92 \\ : [number] "{rax}" (231),99 \\ : [number] "{rax}" (1),
93 \\ [arg1] "{rdi}" (0)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)
94 \\ : "rcx", "r11", "memory"103 \\ : "rcx", "r11", "memory"
95 \\ );104 \\ );
96 \\ unreachable;105 \\ return;
97 \\}106 \\}
98 ,107 ,
99 "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n",108 "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n",
100 );109 );
101 // Now we print it twice.110 // Now we print it twice.
102 case.addCompareOutput(111 case.addCompareOutput(
103 \\pub export fn _start() noreturn {112 \\pub fn main() void {
104 \\ print();113 \\ print();
105 \\ print();114 \\ print();
106 \\
107 \\ exit();
108 \\}115 \\}
109 \\116 \\
110 \\fn print() void {117 \\fn print() void {
...@@ -118,16 +125,6 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -118,16 +125,6 @@ pub fn addCases(ctx: *TestContext) !void {
118 \\ );125 \\ );
119 \\ return;126 \\ return;
120 \\}127 \\}
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 \\}
131 ,128 ,
132 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.129 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.
133 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.130 \\What is up? This is a longer message that will force the data to be relocated in virtual address space.