From 040c6eaaa03bbcfcdeadbe835c1c2f209e9f401e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 19:30:37 -0700 Subject: stage2: garbage collect unused anon decls After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now. --- src/link/Plan9.zig | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src/link') diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 135b59f82b..3b2aae85bc 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -224,7 +224,9 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count()); + // TODO I changed this assert from == to >= but this code all needs to be audited; see + // the comment in `freeDecl`. + assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count()); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); @@ -358,11 +360,18 @@ fn addDeclExports( } pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { + // TODO this is not the correct check for being function body, + // it could just be a function pointer. + // TODO audit the lifetimes of decls table entries. It's possible to get + // allocateDeclIndexes and then freeDecl without any updateDecl in between. + // However that is planned to change, see the TODO comment in Module.zig + // in the deleteUnusedDecl function. const is_fn = (decl.ty.zigTypeTag() == .Fn); - if (is_fn) - assert(self.fn_decl_table.swapRemove(decl)) - else - assert(self.data_decl_table.swapRemove(decl)); + if (is_fn) { + _ = self.fn_decl_table.swapRemove(decl); + } else { + _ = self.data_decl_table.swapRemove(decl); + } } pub fn updateDeclExports( -- cgit v1.2.3