From 29c32b3dc50218d15f779201e154d425fffcefeb Mon Sep 17 00:00:00 2001 From: jagt Date: Fri, 25 Mar 2022 22:53:58 +0800 Subject: [PATCH] `Namespace.decls` use context to save memory change `Module.Namespace.decls` from `AutoArrayHashMapUnmanaged` to `ArrayHashMapUnmanaged(*Decl, void)` with custom context to eliminate duplicated decl name strings. Also see: https://zig.news/andrewrk/how-to-use-hash-map-contexts-to-save-memory-when-doing-a-string-table-3l33 --- src/Module.zig | 47 ++++++++++++++++++++++++++++++++++++----------- src/Sema.zig | 9 ++++----- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 71e2bd8d7c..e74d5f1dd8 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1591,6 +1591,19 @@ pub const Var = struct { } }; +pub const DeclAdapter = struct { + pub fn hash(self: @This(), s: []const u8) u32 { + _ = self; + return @truncate(u32, std.hash.Wyhash.hash(0, s)); + } + + pub fn eql(self: @This(), a: []const u8, b_decl: *Decl, b_index: usize) bool { + _ = self; + _ = b_index; + return mem.eql(u8, a, mem.sliceTo(b_decl.name, 0)); + } +}; + /// The container that structs, enums, unions, and opaques have. pub const Namespace = struct { parent: ?*Namespace, @@ -1601,9 +1614,8 @@ pub const Namespace = struct { /// which decls have been added/removed from source. /// Declaration order is preserved via entry order. /// Key memory is owned by `decl.name`. - /// TODO save memory with https://github.com/ziglang/zig/issues/8619. /// Anonymous decls are not stored here; they are kept in `anon_decls` instead. - decls: std.StringArrayHashMapUnmanaged(*Decl) = .{}, + decls: std.ArrayHashMapUnmanaged(*Decl, void, DeclContext, true) = .{}, anon_decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, @@ -1612,6 +1624,19 @@ pub const Namespace = struct { /// Value is whether the usingnamespace decl is marked `pub`. usingnamespace_set: std.AutoHashMapUnmanaged(*Decl, bool) = .{}, + const DeclContext = struct { + pub fn hash(self: @This(), decl: *Decl) u32 { + _ = self; + return @truncate(u32, std.hash.Wyhash.hash(0, mem.sliceTo(decl.name, 0))); + } + + pub fn eql(self: @This(), a: *Decl, b: *Decl, b_index: usize) bool { + _ = self; + _ = b_index; + return mem.eql(u8, mem.sliceTo(a.name, 0), mem.sliceTo(b.name, 0)); + } + }; + pub fn deinit(ns: *Namespace, mod: *Module) void { ns.destroyDecls(mod); ns.* = undefined; @@ -1628,8 +1653,8 @@ pub const Namespace = struct { var anon_decls = ns.anon_decls; ns.anon_decls = .{}; - for (decls.values()) |value| { - value.destroy(mod); + for (decls.keys()) |decl| { + decl.destroy(mod); } decls.deinit(gpa); @@ -1658,7 +1683,7 @@ pub const Namespace = struct { // TODO rework this code to not panic on OOM. // (might want to coordinate with the clearDecl function) - for (decls.values()) |child_decl| { + for (decls.keys()) |child_decl| { mod.clearDecl(child_decl, outdated_decls) catch @panic("out of memory"); child_decl.destroy(mod); } @@ -3325,7 +3350,7 @@ fn updateZirRefs(gpa: Allocator, file: *File, old_zir: Zir) !void { } if (decl.getInnerNamespace()) |namespace| { - for (namespace.decls.values()) |sub_decl| { + for (namespace.decls.keys()) |sub_decl| { try decl_stack.append(gpa, sub_decl); } for (namespace.anon_decls.keys()) |sub_decl| { @@ -4420,7 +4445,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi if (is_usingnamespace) try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1); // We create a Decl for it regardless of analysis status. - const gop = try namespace.decls.getOrPut(gpa, decl_name); + const gop = try namespace.decls.getOrPutAdapted(gpa, @as([]const u8, mem.sliceTo(decl_name, 0)), DeclAdapter{}); if (!gop.found_existing) { const new_decl = try mod.allocateNewDecl(decl_name, namespace, decl_node, iter.parent_decl.src_scope); if (is_usingnamespace) { @@ -4428,7 +4453,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi } log.debug("scan new {*} ({s}) into {*}", .{ new_decl, decl_name, namespace }); new_decl.src_line = line; - gop.value_ptr.* = new_decl; + gop.key_ptr.* = new_decl; // Exported decls, comptime decls, usingnamespace decls, and // test decls if in test mode, get analyzed. const decl_pkg = namespace.file_scope.pkg; @@ -4464,7 +4489,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi return; } gpa.free(decl_name); - const decl = gop.value_ptr.*; + const decl = gop.key_ptr.*; log.debug("scan existing {*} ({s}) of {*}", .{ decl, decl.name, namespace }); // Update the AST node of the decl; even if its contents are unchanged, it may // have been re-ordered. @@ -5333,7 +5358,7 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void { // Remove from the namespace it resides in, preserving declaration order. assert(decl.zir_decl_index != 0); - _ = decl.src_namespace.decls.orderedRemove(mem.sliceTo(decl.name, 0)); + _ = decl.src_namespace.decls.orderedRemoveAdapted(@as([]const u8, mem.sliceTo(decl.name, 0)), DeclAdapter{}); try mod.clearDecl(decl, &outdated_decls); decl.destroy(mod); @@ -5400,7 +5425,7 @@ pub fn populateTestFunctions(mod: *Module) !void { const builtin_pkg = mod.main_pkg.table.get("builtin").?; const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file; const builtin_namespace = builtin_file.root_decl.?.src_namespace; - const decl = builtin_namespace.decls.get("test_functions").?; + const decl = builtin_namespace.decls.getKeyAdapted(@as([]const u8, "test_functions"), DeclAdapter{}).?; var buf: Type.SlicePtrFieldTypeBuffer = undefined; const tmp_test_fn_ty = decl.ty.slicePtrFieldType(&buf).elemType(); diff --git a/src/Sema.zig b/src/Sema.zig index b7580511cc..121a5a20ac 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4516,7 +4516,7 @@ fn lookupInNamespace( while (check_i < checked_namespaces.count()) : (check_i += 1) { const check_ns = checked_namespaces.keys()[check_i]; - if (check_ns.decls.get(ident_name)) |decl| { + if (check_ns.decls.getKeyAdapted(ident_name, Module.DeclAdapter{})) |decl| { // Skip decls which are not marked pub, which are in a different // file than the `a.b`/`@hasDecl` syntax. if (decl.is_pub or src_file == decl.getFileScope()) { @@ -4559,7 +4559,7 @@ fn lookupInNamespace( return sema.failWithOwnedErrorMsg(block, msg); }, } - } else if (namespace.decls.get(ident_name)) |decl| { + } else if (namespace.decls.getKeyAdapted(ident_name, Module.DeclAdapter{})) |decl| { try mod.declareDeclDependency(sema.owner_decl, decl); return decl; } @@ -11502,12 +11502,11 @@ fn typeInfoDecls( const decls_len = if (opt_namespace) |ns| ns.decls.count() else 0; const decls_vals = try decls_anon_decl.arena().alloc(Value, decls_len); for (decls_vals) |*decls_val, i| { - const decl = opt_namespace.?.decls.values()[i]; - const name = opt_namespace.?.decls.keys()[i]; + const decl = opt_namespace.?.decls.keys()[i]; const name_val = v: { var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const bytes = try anon_decl.arena().dupeZ(u8, name); + const bytes = try anon_decl.arena().dupeZ(u8, mem.sliceTo(decl.name, 0)); const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),