Commit 29c32b3dc5

jagt <jagt@live.com>
2022-03-25 15:53:58
`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
1 parent 23507f2
Changed files (2)
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();
 
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]),