authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-12 15:45:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-12 23:21:21-05:00
log91b897ef581aec65dd7a080754ab09d2e176ed8f
tree6c6a7a2a3776d579619c6ed1cda49f5392f63640
parent53500a57684665e08a2e18e7a736aacfa6548062

rework memory management of Module.Namespace hash maps

The motivating problem here was a memory leak in the hash maps of Module.Namespace. The commit deletes more of the legacy incremental compilation implementation. It had things like use of orderedRemove and trying to do too much OOP-style creation and deletion of objects. Instead, this commit iterates over all the namespaces on Module deinit and calls deinit on the hash map fields. This logic is much simpler to reason about. Similarly, change global inline assembly to an array hash map since iterating over the values is a primary use of it, and clean up the remaining values on Module deinit, solving another memory leak. After this there are no more memory leaks remaining when using the x86 backend in a libc-less compiler.

5 files changed, 25 insertions(+), 194 deletions(-)

src/Compilation.zig-22
...@@ -2522,18 +2522,6 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void...@@ -2522,18 +2522,6 @@ pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void
2522 try module.populateTestFunctions(main_progress_node);2522 try module.populateTestFunctions(main_progress_node);
2523 }2523 }
25242524
2525 // Process the deletion set. We use a while loop here because the
2526 // deletion set may grow as we call `clearDecl` within this loop,
2527 // and more unreferenced Decls are revealed.
2528 while (module.deletion_set.count() != 0) {
2529 const decl_index = module.deletion_set.keys()[0];
2530 const decl = module.declPtr(decl_index);
2531 assert(decl.deletion_flag);
2532 assert(decl.zir_decl_index != .none);
2533
2534 try module.clearDecl(decl_index, null);
2535 }
2536
2537 try module.processExports();2525 try module.processExports();
2538 }2526 }
25392527
...@@ -3686,16 +3674,6 @@ pub fn performAllTheWork(...@@ -3686,16 +3674,6 @@ pub fn performAllTheWork(
3686 try reportMultiModuleErrors(mod);3674 try reportMultiModuleErrors(mod);
3687 }3675 }
36883676
3689 {
3690 const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");
3691 defer outdated_and_deleted_decls_frame.end();
3692
3693 // Iterate over all the files and look for outdated and deleted declarations.
3694 if (comp.bin_file.options.module) |mod| {
3695 try mod.processOutdatedAndDeletedDecls();
3696 }
3697 }
3698
3699 if (comp.bin_file.options.module) |mod| {3677 if (comp.bin_file.options.module) |mod| {
3700 mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);3678 mod.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
3701 mod.sema_prog_node.activate();3679 mod.sema_prog_node.activate();
src/InternPool.zig+5-2
...@@ -6151,7 +6151,6 @@ fn finishFuncInstance(...@@ -6151,7 +6151,6 @@ fn finishFuncInstance(
6151 .@"linksection" = section,6151 .@"linksection" = section,
6152 .@"addrspace" = fn_owner_decl.@"addrspace",6152 .@"addrspace" = fn_owner_decl.@"addrspace",
6153 .analysis = .complete,6153 .analysis = .complete,
6154 .deletion_flag = false,
6155 .zir_decl_index = fn_owner_decl.zir_decl_index,6154 .zir_decl_index = fn_owner_decl.zir_decl_index,
6156 .src_scope = fn_owner_decl.src_scope,6155 .src_scope = fn_owner_decl.src_scope,
6157 .generation = generation,6156 .generation = generation,
...@@ -7617,7 +7616,11 @@ pub fn createNamespace(...@@ -7617,7 +7616,11 @@ pub fn createNamespace(
7617}7616}
76187617
7619pub fn destroyNamespace(ip: *InternPool, gpa: Allocator, index: Module.Namespace.Index) void {7618pub fn destroyNamespace(ip: *InternPool, gpa: Allocator, index: Module.Namespace.Index) void {
7620 ip.namespacePtr(index).* = undefined;7619 ip.namespacePtr(index).* = .{
7620 .parent = undefined,
7621 .file_scope = undefined,
7622 .ty = undefined,
7623 };
7621 ip.namespaces_free_list.append(gpa, index) catch {7624 ip.namespaces_free_list.append(gpa, index) catch {
7622 // In order to keep `destroyNamespace` a non-fallible function, we ignore memory7625 // In order to keep `destroyNamespace` a non-fallible function, we ignore memory
7623 // allocation failures here, instead leaking the Namespace until garbage collection.7626 // allocation failures here, instead leaking the Namespace until garbage collection.
src/Module.zig+14-166
...@@ -132,10 +132,6 @@ failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},...@@ -132,10 +132,6 @@ failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
132/// are stored here.132/// are stored here.
133cimport_errors: std.AutoArrayHashMapUnmanaged(Decl.Index, std.zig.ErrorBundle) = .{},133cimport_errors: std.AutoArrayHashMapUnmanaged(Decl.Index, std.zig.ErrorBundle) = .{},
134134
135/// Candidates for deletion. After a semantic analysis update completes, this list
136/// contains Decls that need to be deleted if they end up having no references to them.
137deletion_set: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},
138
139/// Key is the error name, index is the error tag value. Index 0 has a length-0 string.135/// Key is the error name, index is the error tag value. Index 0 has a length-0 string.
140global_error_set: GlobalErrorSet = .{},136global_error_set: GlobalErrorSet = .{},
141137
...@@ -165,7 +161,7 @@ emit_h: ?*GlobalEmitH,...@@ -165,7 +161,7 @@ emit_h: ?*GlobalEmitH,
165161
166test_functions: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},162test_functions: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},
167163
168global_assembly: std.AutoHashMapUnmanaged(Decl.Index, []u8) = .{},164global_assembly: std.AutoArrayHashMapUnmanaged(Decl.Index, []u8) = .{},
169165
170reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct {166reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct {
171 referencer: Decl.Index,167 referencer: Decl.Index,
...@@ -438,9 +434,6 @@ pub const Decl = struct {...@@ -438,9 +434,6 @@ pub const Decl = struct {
438 /// with it. That means when `Decl` is destroyed, the cleanup code should additionally434 /// with it. That means when `Decl` is destroyed, the cleanup code should additionally
439 /// check if the value owns a `Namespace`, and destroy that too.435 /// check if the value owns a `Namespace`, and destroy that too.
440 owns_tv: bool,436 owns_tv: bool,
441 /// This flag is set when this Decl is added to `Module.deletion_set`, and cleared
442 /// when removed.
443 deletion_flag: bool,
444 /// Whether the corresponding AST decl has a `pub` keyword.437 /// Whether the corresponding AST decl has a `pub` keyword.
445 is_pub: bool,438 is_pub: bool,
446 /// Whether the corresponding AST decl has a `export` keyword.439 /// Whether the corresponding AST decl has a `export` keyword.
...@@ -873,47 +866,6 @@ pub const Namespace = struct {...@@ -873,47 +866,6 @@ pub const Namespace = struct {
873 }866 }
874 };867 };
875868
876 pub fn deinit(ns: *Namespace, mod: *Module) void {
877 ns.destroyDecls(mod);
878 ns.* = undefined;
879 }
880
881 pub fn destroyDecls(ns: *Namespace, mod: *Module) void {
882 const gpa = mod.gpa;
883
884 var decls = ns.decls;
885 ns.decls = .{};
886
887 for (decls.keys()) |decl_index| {
888 mod.destroyDecl(decl_index);
889 }
890 decls.deinit(gpa);
891
892 ns.usingnamespace_set.deinit(gpa);
893 }
894
895 pub fn deleteAllDecls(
896 ns: *Namespace,
897 mod: *Module,
898 outdated_decls: ?*std.AutoArrayHashMap(Decl.Index, void),
899 ) !void {
900 const gpa = mod.gpa;
901
902 var decls = ns.decls;
903 ns.decls = .{};
904
905 // TODO rework this code to not panic on OOM.
906 // (might want to coordinate with the clearDecl function)
907
908 for (decls.keys()) |child_decl| {
909 mod.clearDecl(child_decl, outdated_decls) catch @panic("out of memory");
910 mod.destroyDecl(child_decl);
911 }
912 decls.deinit(gpa);
913
914 ns.usingnamespace_set.deinit(gpa);
915 }
916
917 // This renders e.g. "std.fs.Dir.OpenOptions"869 // This renders e.g. "std.fs.Dir.OpenOptions"
918 pub fn renderFullyQualifiedName(870 pub fn renderFullyQualifiedName(
919 ns: Namespace,871 ns: Namespace,
...@@ -2527,7 +2479,6 @@ pub fn deinit(mod: *Module) void {...@@ -2527,7 +2479,6 @@ pub fn deinit(mod: *Module) void {
2527 mod.embed_table.deinit(gpa);2479 mod.embed_table.deinit(gpa);
2528 }2480 }
25292481
2530 mod.deletion_set.deinit(gpa);
2531 mod.compile_log_text.deinit(gpa);2482 mod.compile_log_text.deinit(gpa);
25322483
2533 mod.zig_cache_artifact_directory.handle.close();2484 mod.zig_cache_artifact_directory.handle.close();
...@@ -2590,9 +2541,21 @@ pub fn deinit(mod: *Module) void {...@@ -2590,9 +2541,21 @@ pub fn deinit(mod: *Module) void {
25902541
2591 mod.test_functions.deinit(gpa);2542 mod.test_functions.deinit(gpa);
25922543
2544 for (mod.global_assembly.values()) |s| {
2545 gpa.free(s);
2546 }
2593 mod.global_assembly.deinit(gpa);2547 mod.global_assembly.deinit(gpa);
2548
2594 mod.reference_table.deinit(gpa);2549 mod.reference_table.deinit(gpa);
25952550
2551 {
2552 var it = mod.intern_pool.allocated_namespaces.iterator(0);
2553 while (it.next()) |namespace| {
2554 namespace.decls.deinit(gpa);
2555 namespace.usingnamespace_set.deinit(gpa);
2556 }
2557 }
2558
2596 mod.intern_pool.deinit(gpa);2559 mod.intern_pool.deinit(gpa);
2597 mod.tmp_hack_arena.deinit();2560 mod.tmp_hack_arena.deinit();
25982561
...@@ -2606,20 +2569,10 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {...@@ -2606,20 +2569,10 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
2606 const ip = &mod.intern_pool;2569 const ip = &mod.intern_pool;
26072570
2608 {2571 {
2609 const decl = mod.declPtr(decl_index);
2610 _ = mod.test_functions.swapRemove(decl_index);2572 _ = mod.test_functions.swapRemove(decl_index);
2611 if (decl.deletion_flag) {2573 if (mod.global_assembly.fetchSwapRemove(decl_index)) |kv| {
2612 assert(mod.deletion_set.swapRemove(decl_index));
2613 }
2614 if (mod.global_assembly.fetchRemove(decl_index)) |kv| {
2615 gpa.free(kv.value);2574 gpa.free(kv.value);
2616 }2575 }
2617 if (decl.has_tv) {
2618 if (decl.getOwnedInnerNamespaceIndex(mod).unwrap()) |i| {
2619 mod.namespacePtr(i).destroyDecls(mod);
2620 mod.destroyNamespace(i);
2621 }
2622 }
2623 }2576 }
26242577
2625 ip.destroyDecl(gpa, decl_index);2578 ip.destroyDecl(gpa, decl_index);
...@@ -4422,56 +4375,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err...@@ -4422,56 +4375,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
4422 }4375 }
4423}4376}
44244377
4425/// Make it as if the semantic analysis for this Decl never happened.
4426pub fn clearDecl(
4427 mod: *Module,
4428 decl_index: Decl.Index,
4429 outdated_decls: ?*std.AutoArrayHashMap(Decl.Index, void),
4430) Allocator.Error!void {
4431 const tracy = trace(@src());
4432 defer tracy.end();
4433
4434 const decl = mod.declPtr(decl_index);
4435
4436 const gpa = mod.gpa;
4437
4438 if (outdated_decls) |map| {
4439 _ = map.swapRemove(decl_index);
4440 }
4441
4442 if (mod.failed_decls.fetchSwapRemove(decl_index)) |kv| {
4443 kv.value.destroy(gpa);
4444 }
4445 if (mod.cimport_errors.fetchSwapRemove(decl_index)) |kv| {
4446 var errors = kv.value;
4447 errors.deinit(gpa);
4448 }
4449 if (mod.emit_h) |emit_h| {
4450 if (emit_h.failed_decls.fetchSwapRemove(decl_index)) |kv| {
4451 kv.value.destroy(gpa);
4452 }
4453 assert(emit_h.decl_table.swapRemove(decl_index));
4454 }
4455 _ = mod.compile_log_decls.swapRemove(decl_index);
4456 try mod.deleteDeclExports(decl_index);
4457
4458 if (decl.has_tv) {
4459 if (decl.ty.isFnOrHasRuntimeBits(mod)) {
4460 mod.comp.bin_file.freeDecl(decl_index);
4461 }
4462 if (decl.getOwnedInnerNamespace(mod)) |namespace| {
4463 try namespace.deleteAllDecls(mod, outdated_decls);
4464 }
4465 }
4466
4467 if (decl.deletion_flag) {
4468 decl.deletion_flag = false;
4469 assert(mod.deletion_set.swapRemove(decl_index));
4470 }
4471
4472 decl.analysis = .unreferenced;
4473}
4474
4475/// This function is exclusively called for anonymous decls.4378/// This function is exclusively called for anonymous decls.
4476/// All resources referenced by anonymous decls are owned by InternPool4379/// All resources referenced by anonymous decls are owned by InternPool
4477/// so there is no cleanup to do here.4380/// so there is no cleanup to do here.
...@@ -4488,14 +4391,6 @@ pub fn deleteUnusedDecl(mod: *Module, decl_index: Decl.Index) void {...@@ -4488,14 +4391,6 @@ pub fn deleteUnusedDecl(mod: *Module, decl_index: Decl.Index) void {
4488 }4391 }
4489}4392}
44904393
4491/// We don't perform a deletion here, because this Decl or another one
4492/// may end up referencing it before the update is complete.
4493fn markDeclForDeletion(mod: *Module, decl_index: Decl.Index) !void {
4494 const decl = mod.declPtr(decl_index);
4495 decl.deletion_flag = true;
4496 try mod.deletion_set.put(mod.gpa, decl_index, {});
4497}
4498
4499/// Cancel the creation of an anon decl and delete any references to it.4394/// Cancel the creation of an anon decl and delete any references to it.
4500/// If other decls depend on this decl, they must be aborted first.4395/// If other decls depend on this decl, they must be aborted first.
4501pub fn abortAnonDecl(mod: *Module, decl_index: Decl.Index) void {4396pub fn abortAnonDecl(mod: *Module, decl_index: Decl.Index) void {
...@@ -4868,7 +4763,6 @@ pub fn allocateNewDecl(...@@ -4868,7 +4763,6 @@ pub fn allocateNewDecl(
4868 .@"linksection" = .none,4763 .@"linksection" = .none,
4869 .@"addrspace" = .generic,4764 .@"addrspace" = .generic,
4870 .analysis = .unreferenced,4765 .analysis = .unreferenced,
4871 .deletion_flag = false,
4872 .zir_decl_index = .none,4766 .zir_decl_index = .none,
4873 .src_scope = src_scope,4767 .src_scope = src_scope,
4874 .generation = 0,4768 .generation = 0,
...@@ -5366,52 +5260,6 @@ pub fn optionsSrc(mod: *Module, decl: *Decl, base_src: LazySrcLoc, wanted: []con...@@ -5366,52 +5260,6 @@ pub fn optionsSrc(mod: *Module, decl: *Decl, base_src: LazySrcLoc, wanted: []con
5366 return base_src;5260 return base_src;
5367}5261}
53685262
5369/// Called from `performAllTheWork`, after all AstGen workers have finished,
5370/// and before the main semantic analysis loop begins.
5371pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
5372 // Ultimately, the goal is to queue up `analyze_decl` tasks in the work queue
5373 // for the outdated decls, but we cannot queue up the tasks until after
5374 // we find out which ones have been deleted, otherwise there would be
5375 // deleted Decl pointers in the work queue.
5376 var outdated_decls = std.AutoArrayHashMap(Decl.Index, void).init(mod.gpa);
5377 defer outdated_decls.deinit();
5378 for (mod.import_table.values()) |file| {
5379 try outdated_decls.ensureUnusedCapacity(file.outdated_decls.items.len);
5380 for (file.outdated_decls.items) |decl_index| {
5381 outdated_decls.putAssumeCapacity(decl_index, {});
5382 }
5383 file.outdated_decls.clearRetainingCapacity();
5384
5385 // Handle explicitly deleted decls from the source code. This is one of two
5386 // places that Decl deletions happen. The other is in `Compilation`, after
5387 // `performAllTheWork`, where we iterate over `Module.deletion_set` and
5388 // delete Decls which are no longer referenced.
5389 // If a Decl is explicitly deleted from source, and also no longer referenced,
5390 // it may be both in this `deleted_decls` set, as well as in the
5391 // `Module.deletion_set`. To avoid deleting it twice, we remove it from the
5392 // deletion set at this time.
5393 for (file.deleted_decls.items) |decl_index| {
5394 const decl = mod.declPtr(decl_index);
5395
5396 // Remove from the namespace it resides in, preserving declaration order.
5397 assert(decl.zir_decl_index != .none);
5398 _ = mod.namespacePtr(decl.src_namespace).decls.orderedRemoveAdapted(
5399 decl.name,
5400 DeclAdapter{ .mod = mod },
5401 );
5402
5403 try mod.clearDecl(decl_index, &outdated_decls);
5404 mod.destroyDecl(decl_index);
5405 }
5406 file.deleted_decls.clearRetainingCapacity();
5407 }
5408 // Finally we can queue up re-analysis tasks after we have processed
5409 // the deleted decls.
5410 for (outdated_decls.keys()) |key| {
5411 try mod.markOutdatedDecl(key);
5412 }
5413}
5414
5415/// Called from `Compilation.update`, after everything is done, just before5263/// Called from `Compilation.update`, after everything is done, just before
5416/// reporting compile errors. In this function we emit exported symbol collision5264/// reporting compile errors. In this function we emit exported symbol collision
5417/// errors and communicate exported symbols to the linker backend.5265/// errors and communicate exported symbols to the linker backend.
src/codegen/c.zig+3-2
...@@ -2506,8 +2506,9 @@ pub fn genTypeDecl(...@@ -2506,8 +2506,9 @@ pub fn genTypeDecl(
2506}2506}
25072507
2508pub fn genGlobalAsm(mod: *Module, writer: anytype) !void {2508pub fn genGlobalAsm(mod: *Module, writer: anytype) !void {
2509 var it = mod.global_assembly.valueIterator();2509 for (mod.global_assembly.values()) |asm_source| {
2510 while (it.next()) |asm_source| try writer.print("__asm({s});\n", .{fmtStringLiteral(asm_source.*, null)});2510 try writer.print("__asm({s});\n", .{fmtStringLiteral(asm_source, null)});
2511 }
2511}2512}
25122513
2513pub fn genErrDecls(o: *Object) !void {2514pub fn genErrDecls(o: *Object) !void {
src/codegen/llvm.zig+3-2
...@@ -1121,8 +1121,9 @@ pub const Object = struct {...@@ -1121,8 +1121,9 @@ pub const Object = struct {
1121 const mod = object.module;1121 const mod = object.module;
11221122
1123 const writer = object.builder.setModuleAsm();1123 const writer = object.builder.setModuleAsm();
1124 var it = mod.global_assembly.valueIterator();1124 for (mod.global_assembly.values()) |assembly| {
1125 while (it.next()) |assembly| try writer.print("{s}\n", .{assembly.*});1125 try writer.print("{s}\n", .{assembly});
1126 }
1126 try object.builder.finishModuleAsm();1127 try object.builder.finishModuleAsm();
1127 }1128 }
11281129