From 50cf6e70660dbd0e8ff52ef59c3f8bea5eb867cf Mon Sep 17 00:00:00 2001 From: seanthegleaming Date: Mon, 27 Oct 2025 19:53:12 -0400 Subject: [PATCH 1/4] Refactor `auto_hash.zig` * Applied Zig-style naming conventions to functions and values * Remove redundant namespacing * `std.hash.autoHash` -> `std.hash.auto` * `std.hash.autoHashStrat` -> `std.hash.autoStrat` * `std.hash.HashStrategy` -> `std.hash.Strategy` * Correct capitalization * `std.hash.HashStrategy.Shallow` -> `std.hash.Strategy.Shallow` * `std.hash.HashStrategy.Deep` -> `std.hash.Strategy.Deep` * `std.hash.HashStrategy.DeepRecursive` -> `std.hash.Strategy.deep_recursive` * All of the old identifiers are still available as deprecated aliases. * Bug fix: Slices are now detected when nested within error unions, optionals, and arrays. As a consequence of this, `std.hash.auto` may result in a compile error in places where it previously did not. The previous behavior was a bug, but this change is still technically breaking. * Optimization: In general, `auto_hash.zig` avoids copying large values, preferring to hash them in place. Moreover, `auto_hash.zig` is generally smarter about directly using calling `hasher.update` on values which have a unique representation. For instance, slices and arrays of values with unique representations will undergo a direct `@ptrCast` into a slice of bytes and hash all elements at once rather than doing this individually for every element in the span. * Cleaned up the implementation and tests, applying more current style and language features where appropriate --- lib/compiler/aro/backend/Interner.zig | 17 +- lib/std/Build/Watch.zig | 4 +- lib/std/array_hash_map.zig | 9 +- lib/std/hash.zig | 17 +- lib/std/hash/auto_hash.zig | 749 +++++++++++++++++--------- lib/std/hash_map.zig | 9 +- src/Builtin.zig | 22 +- src/Compilation.zig | 6 +- src/InternPool.zig | 101 ++-- src/Sema.zig | 30 +- tools/update-linux-headers.zig | 4 +- 11 files changed, 598 insertions(+), 370 deletions(-) diff --git a/lib/compiler/aro/backend/Interner.zig b/lib/compiler/aro/backend/Interner.zig index 683b18d7f5c6..39b95000e8b3 100644 --- a/lib/compiler/aro/backend/Interner.zig +++ b/lib/compiler/aro/backend/Interner.zig @@ -89,24 +89,22 @@ pub const Key = union(enum) { }; pub fn hash(key: Key) u32 { - var hasher = Hash.init(0); + var hasher: Hash = .init(0); const tag = std.meta.activeTag(key); - std.hash.autoHash(&hasher, tag); + std.hash.auto(&hasher, tag); switch (key) { .bytes => |bytes| { hasher.update(bytes); }, - .record_ty => |elems| for (elems) |elem| { - std.hash.autoHash(&hasher, elem); - }, + .record_ty => |elems| std.hash.autoStrat(&hasher, elems, .deep), .float => |repr| switch (repr) { - inline else => |data| std.hash.autoHash( + inline else => |data| std.hash.auto( &hasher, @as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(data))), @bitCast(data)), ), }, .complex => |repr| switch (repr) { - inline else => |data| std.hash.autoHash( + inline else => |data| std.hash.auto( &hasher, @as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(data))), @bitCast(data)), ), @@ -114,11 +112,10 @@ pub const Key = union(enum) { .int => |repr| { var space: Tag.Int.BigIntSpace = undefined; const big = repr.toBigInt(&space); - std.hash.autoHash(&hasher, big.positive); - for (big.limbs) |limb| std.hash.autoHash(&hasher, limb); + std.hash.autoStrat(&hasher, big, .deep); }, inline else => |info| { - std.hash.autoHash(&hasher, info); + std.hash.auto(&hasher, info); }, } return @truncate(hasher.final()); diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index 9f393ca26800..39f773ed4550 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -868,8 +868,8 @@ pub const Match = struct { pub const Context = struct { pub fn hash(self: Context, a: Match) u32 { _ = self; - var hasher = Hash.init(0); - std.hash.autoHash(&hasher, a.step); + var hasher: Hash = .init(0); + std.hash.auto(&hasher, a.step); hasher.update(a.basename); return @truncate(hasher.final()); } diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index 2550e0aebec9..d1d046b6fa2f 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -4,7 +4,6 @@ const assert = debug.assert; const testing = std.testing; const math = std.math; const mem = std.mem; -const autoHash = std.hash.autoHash; const Wyhash = std.hash.Wyhash; const Allocator = mem.Allocator; const hash_map = @This(); @@ -2641,8 +2640,8 @@ pub fn getAutoHashFn(comptime K: type, comptime Context: type) (fn (Context, K) if (std.meta.hasUniqueRepresentation(K)) { return @truncate(Wyhash.hash(0, std.mem.asBytes(&key))); } else { - var hasher = Wyhash.init(0); - autoHash(&hasher, key); + var hasher: Wyhash = .init(0); + std.hash.auto(&hasher, key); return @truncate(hasher.final()); } } @@ -2681,8 +2680,8 @@ pub fn getAutoHashStratFn(comptime K: type, comptime Context: type, comptime str return struct { fn hash(ctx: Context, key: K) u32 { _ = ctx; - var hasher = Wyhash.init(0); - std.hash.autoHashStrat(&hasher, key, strategy); + var hasher: Wyhash = .init(0); + std.hash.autoStrat(&hasher, key, strategy); return @as(u32, @truncate(hasher.final())); } }.hash; diff --git a/lib/std/hash.zig b/lib/std/hash.zig index 781971bd1338..83c57a0a097f 100644 --- a/lib/std/hash.zig +++ b/lib/std/hash.zig @@ -1,9 +1,18 @@ pub const Adler32 = @import("hash/Adler32.zig"); const auto_hash = @import("hash/auto_hash.zig"); -pub const autoHash = auto_hash.autoHash; -pub const autoHashStrat = auto_hash.hash; -pub const Strategy = auto_hash.HashStrategy; +pub const auto = auto_hash.auto; +pub const autoStrat = auto_hash.autoStrat; +pub const Strategy = auto_hash.Strategy; + +/// Deprecated alias for `Strategy` +pub const HashStrategy = Strategy; + +/// Deprecated alias for `auto` +pub const autoHash = auto; + +/// Deprecated alias for `autoStrat` +pub const autoHashStrat = autoStrat; // pub for polynomials + generic crc32 construction pub const crc = @import("hash/crc.zig"); @@ -44,7 +53,7 @@ pub fn int(input: anytype) @TypeOf(input) { // Convert input to unsigned integer (easier to deal with) const Uint = @Type(.{ .int = .{ .bits = bits, .signedness = .unsigned } }); const u_input: Uint = @bitCast(input); - if (bits > 256) @compileError("bit widths > 256 are unsupported, use std.hash.autoHash functionality."); + if (bits > 256) @compileError("bit widths > 256 are unsupported, use std.hash.auto functionality."); // For bit widths that don't have a dedicated function, use a heuristic // construction with a multiplier suited to diffusion - // a mod 2^bits where a^2 - 46 * a + 1 = 0 mod 2^(bits + 4), diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 59099c1a7d3e..8e39773c8f7b 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -1,76 +1,229 @@ const std = @import("std"); -const assert = std.debug.assert; const mem = std.mem; +const assert = std.debug.assert; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const Wyhash = std.hash.Wyhash; /// Describes how pointer types should be hashed. -pub const HashStrategy = enum { +pub const Strategy = enum { /// Do not follow pointers, only hash their value. - Shallow, + shallow, - /// Follow pointers, hash the pointee content. - /// Only dereferences one level, ie. it is changed into .Shallow when a + /// Follow pointers once, hash the pointee content. + /// Only dereferences one level, ie. it is changed into `shallow` when a /// pointer type is encountered. - Deep, + deep, - /// Follow pointers, hash the pointee content. + /// Follow pointers recursively, hash the pointee content. /// Dereferences all pointers encountered. /// Assumes no cycle. - DeepRecursive, + deep_recursive, + + /// Deprecated alias for `shallow` + pub const Shallow: Strategy = .shallow; + + /// Deprecated alias for `deep` + pub const Deep: Strategy = .deep; + + /// Deprecated alias for `deep_recursive` + pub const DeepRecursive: Strategy = .deep_recursive; + + /// When hashing a pointer with `strat`, + /// this returns the strategy used to hash + /// the data at the pointer. If this returns + /// `null`, then the pointer itself should + /// be hashed. + fn deref(strat: Strategy) ?Strategy { + return switch (strat) { + .shallow => null, + .deep => .shallow, + .deep_recursive => .deep_recursive, + }; + } + + /// Returns a `Strategy` that is equivalent to `strat` for + /// use in hashing values of type `T`. Used for preventing + /// the explosion of generic instantiation. + fn deduplicate(comptime strat: Strategy, comptime T: type) ?Strategy { + if (strat != .shallow and !typeContains(T, .pointer)) { + return .shallow; + } + return null; + } }; +/// Whether the pointer type `P` points to directly hashable memory. +/// This means non-volatile, non-nullable, and default address space. +fn isMemoryHashable(comptime Pointer: type) bool { + // Instead of checking the is_volatile, address_space, and is_allowzero + // fields directly, we modify the fields we don't care about + // and check if reifying the info results in []const u8. + // This allows us to handle pointers to packed struct fields, + // which cannot be observed from @typeInfo. + comptime var info = @typeInfo(Pointer).pointer; + if (info.size == .c) { + // C pointers are nullable + return false; + } + info.child = u8; + info.size = .slice; + info.is_const = true; + info.alignment = 1; + info.sentinel_ptr = null; + return @Type(.{ .pointer = info }) == []const u8; +} + +/// Whether `T` can be safely hashed by hashing its raw bytes +fn hashByBytes(comptime T: type, strat: Strategy) bool { + if (strat == .shallow or !typeContains(T, .pointer)) { + // Don't include sentinels in the hashes + return std.meta.hasUniqueRepresentation(T) and !typeContains(T, .sentinel); + } else { + return false; + } +} + +/// Whether an effort should be made to avoid copying values +/// of type `T`. When this returns true, +fn preferInPlaceHash(comptime T: type) bool { + return @sizeOf(T) > @sizeOf(usize); +} + /// Helper function to hash a pointer and mutate the strategy if needed. -pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { - const info = @typeInfo(@TypeOf(key)); - - switch (info.pointer.size) { - .one => switch (strat) { - .Shallow => hash(hasher, @intFromPtr(key), .Shallow), - .Deep => hash(hasher, key.*, .Shallow), - .DeepRecursive => hash(hasher, key.*, .DeepRecursive), +fn hashInPlace(hasher: anytype, key: anytype, comptime strat: Strategy) void { + const KeyPtr = @TypeOf(key); + const info = @typeInfo(KeyPtr).pointer; + + const is_span = switch (info.size) { + .one => @typeInfo(info.child) == .array, + .slice => true, + .many, .c => if (strat != .shallow) @compileError( + \\ unknown-length pointers and C pointers cannot be hashed deeply. + \\ Consider providing your own hash function. + ), + }; + + if (comptime is_span) { + // hashArray already has logic to hash slices and + // array pointers optimally + return hashArray(hasher, key, strat); + } else if (comptime strat.deduplicate(info.child)) |new_strat| { + // Here we prevent the explosion of generic instantiation + // for all of the following logic. We do this after the + // is_span check because hashArray also does this. + return hashInPlace(hasher, key, new_strat); + } else if (comptime isMemoryHashable(KeyPtr) and hashByBytes(info.child, strat)) { + // If the memory is non-nullable, non-volatile, + // and byte-aligned, then we can try optimizing + // to hash the key in place. This is also attempted + // in autoStrat, but by doing it here, we can avoid + // a redundant copy of the data + const bytes: []const u8 = @ptrCast(key); + return hasher.update(bytes); + } else { + // In the general case, just dereference the key + // and pass it on to autoStrat + return autoStrat(hasher, key.*, strat); + } +} + +/// Helper function to hash a set of contiguous objects, from an array or slice. +fn hashArray(hasher: anytype, key: anytype, comptime strat: Strategy) void { + // Exclude the sentinel in all of the following code + const no_sent = key[0..key.len]; + const NoSent = @TypeOf(no_sent); + + const info = @typeInfo(NoSent).pointer; + + if (comptime strat.deduplicate(info.child)) |new_strat| { + // Prevent the explosion of generic instantiation + // by demoting strat to shallow when there are no + // pointers within the key + return hashArray(hasher, no_sent, new_strat); + } + + const Child = std.meta.Elem(NoSent); + + switch (@typeInfo(Child)) { + .array => |arr| if (arr.sentinel_ptr != null) { + // Attempt to flatten arrays of arrays when possible + comptime var flat_info = info; + flat_info.child = switch (info.size) { + .slice => arr.child, + else => [arr.len * no_sent.len]arr.child, + }; + const Flat = @Type(.{ .pointer = flat_info }); + const flat: Flat = @ptrCast(no_sent); + return hashArray(hasher, flat, strat); }, + else => {}, + } - .slice => { - switch (strat) { - .Shallow => { - hashPointer(hasher, key.ptr, .Shallow); - }, - .Deep => hashArray(hasher, key, .Shallow), - .DeepRecursive => hashArray(hasher, key, .DeepRecursive), + // If the memory is non-nullable, non-volatile, + // and byte-aligned, then we can try optimizing + // to hash the key in place + if (comptime isMemoryHashable(NoSent)) { + if (comptime preferInPlaceHash(Child)) { + // Otherwise, if the data is sufficiently large, + // we attempt to hash as much as possible in place + // by passing the elements to hashAtPointer + for (no_sent) |*element| { + hashInPlace(hasher, element, strat); } - hash(hasher, key.len, .Shallow); - }, + return; + } + } - .many, - .c, - => switch (strat) { - .Shallow => hash(hasher, @intFromPtr(key), .Shallow), - else => @compileError( - \\ unknown-length pointers and C pointers cannot be hashed deeply. - \\ Consider providing your own hash function. - ), - }, + // In the general case, we use a regular, by value for loop + for (no_sent) |element| { + autoStrat(hasher, element, strat); } + return; } -/// Helper function to hash a set of contiguous objects, from an array or slice. -pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { - for (key) |element| { - hash(hasher, element, strat); +fn validateTypeInfo(comptime T: type) void { + switch (@typeInfo(T)) { + .noreturn, + .@"opaque", + .undefined, + .null, + .comptime_float, + .comptime_int, + .type, + .enum_literal, + .frame, + => @compileError("unable to hash type " ++ @typeName(T)), + + .@"union" => |info| if (info.tag_type == null) + @compileError("cannot hash untagged union type: " ++ @typeName(T) ++ ", provide your own hash function"), + + else => {}, } } /// Provides generic hashing for any eligible type. /// Strategy is provided to determine if pointers should be followed or not. -pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { +pub fn autoStrat(hasher: anytype, key: anytype, comptime strat: Strategy) void { const Key = @TypeOf(key); - const Hasher = switch (@typeInfo(@TypeOf(hasher))) { - .pointer => |ptr| ptr.child, - else => @TypeOf(hasher), - }; + validateTypeInfo(Key); + + if (comptime strat.deduplicate(Key)) |new_strat| { + // Prevent the explosion of generic instantiation + // by demoting strat to shallow when there are no + // pointers within the key + return autoStrat(hasher, key, new_strat); + } - if (strat == .Shallow and std.meta.hasUniqueRepresentation(Key)) { - @call(.always_inline, Hasher.update, .{ hasher, mem.asBytes(&key) }); - return; + if (comptime hashByBytes(Key, strat)) { + // If it is safe to do so, we skip all of the + // field-by-field hashing logic and just directly + // hash the bytes of the value. It's generally better + // for this to happen inside hashInPlace so we aren't + // making unnecessary copies of the key, but it should + // be checked here too + const bytes: []const u8 = @ptrCast(&key); + return hasher.update(bytes); } switch (@typeInfo(Key)) { @@ -83,218 +236,257 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { .type, .enum_literal, .frame, - .float, - => @compileError("unable to hash type " ++ @typeName(Key)), + => comptime unreachable, .void => return, - // Help the optimizer see that hashing an int is easy by inlining! - // TODO Check if the situation is better after #561 is resolved. - .int => |int| switch (int.signedness) { - .signed => hash(hasher, @as(@Type(.{ .int = .{ - .bits = int.bits, - .signedness = .unsigned, - } }), @bitCast(key)), strat), - .unsigned => { - if (std.meta.hasUniqueRepresentation(Key)) { - @call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key) }); - } else { - // Take only the part containing the key value, the remaining - // bytes are undefined and must not be hashed! - const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable; - @call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key)[0..byte_size] }); - } - }, + .int => |int| { + const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable; + const ByteAligned = std.meta.Int(int.signedness, byte_size * 8); + const byte_aligned: ByteAligned = key; + const bytes: [byte_size]u8 = @bitCast(byte_aligned); + return hasher.update(&bytes); }, - .bool => hash(hasher, @intFromBool(key), strat), - .@"enum" => hash(hasher, @intFromEnum(key), strat), - .error_set => hash(hasher, @intFromError(key), strat), - .@"anyframe", .@"fn" => hash(hasher, @intFromPtr(key), strat), + .float => |float| { + const AsInt = std.meta.Int(.unsigned, float.bits); + const as_int: AsInt = @bitCast(key); + return autoStrat(hasher, as_int, .shallow); + }, - .pointer => @call(.always_inline, hashPointer, .{ hasher, key, strat }), + .bool => return autoStrat(hasher, @intFromBool(key), strat), + .@"enum" => return autoStrat(hasher, @intFromEnum(key), strat), + .error_set => return autoStrat(hasher, @intFromError(key), strat), + .@"anyframe" => return autoStrat(hasher, @intFromPtr(key), strat), + .@"fn" => return autoStrat(hasher, @intFromPtr(&key), strat), - .optional => if (key) |k| hash(hasher, k, strat), + .array => return hashArray(hasher, key[0..key.len], strat), - .array => hashArray(hasher, key, strat), + .pointer => |info| { + if (comptime strat.deref()) |deref_strat| { + return hashInPlace(hasher, key, deref_strat); + } else { + switch (info.size) { + .one, .many, .c => return autoStrat(hasher, @intFromPtr(key), .shallow), + .slice => { + const data: [2]usize = .{ + @intFromPtr(key.ptr), + key.len, + }; + return autoStrat(hasher, data, .shallow); + }, + } + } + }, - .vector => |info| { - if (std.meta.hasUniqueRepresentation(Key)) { - hasher.update(mem.asBytes(&key)); + .optional => |info| { + if (comptime preferInPlaceHash(info.child)) { + if (key) |*k| { + return hashInPlace(hasher, k, strat); + } } else { - comptime var i = 0; - inline while (i < info.len) : (i += 1) { - hash(hasher, key[i], strat); + if (key) |k| { + return autoStrat(hasher, k, strat); } } + return; }, - .@"struct" => |info| { - inline for (info.fields) |field| { - // We reuse the hash of the previous field as the seed for the - // next one so that they're dependant. - hash(hasher, @field(key, field.name), strat); + .vector => |info| { + inline for (0..info.len) |i| { + autoStrat(hasher, key[i], strat); } + return; }, - .@"union" => |info| blk: { - if (info.tag_type) |tag_type| { - const tag = std.meta.activeTag(key); - hash(hasher, tag, strat); - inline for (info.fields) |field| { - if (@field(tag_type, field.name) == tag) { - if (field.type != void) { - hash(hasher, @field(key, field.name), strat); - } - break :blk; - } + .@"struct" => |info| { + inline for (info.fields) |field| { + if (field.is_comptime) continue; + + if (comptime info.layout != .@"packed" and preferInPlaceHash(field.type)) { + hashInPlace(hasher, &@field(key, field.name), strat); + } else { + autoStrat(hasher, @field(key, field.name), strat); } - unreachable; - } else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function"); + } + return; }, - .error_union => blk: { - const payload = key catch |err| { - hash(hasher, err, strat); - break :blk; - }; - hash(hasher, payload, strat); + .@"union" => |info| { + const tag: info.tag_type.? = key; + autoStrat(hasher, tag, .shallow); + switch (key) { + inline else => |*payload| { + const Field = @TypeOf(payload.*); + if (comptime preferInPlaceHash(Field)) { + return hashInPlace(hasher, payload, strat); + } else { + return autoStrat(hasher, payload.*, strat); + } + }, + } }, - } -} - -inline fn typeContainsSlice(comptime K: type) bool { - return switch (@typeInfo(K)) { - .pointer => |info| info.size == .slice, - inline .@"struct", .@"union" => |info| { - inline for (info.fields) |field| { - if (typeContainsSlice(field.type)) { - return true; + .error_union => |info| { + if (key) |*payload| { + if (comptime preferInPlaceHash(info.payload)) { + return hashInPlace(hasher, payload, strat); + } else { + return autoStrat(hasher, payload.*, strat); } + } else |err| { + return autoStrat(hasher, err, strat); } - return false; }, - - else => false, - }; + } } /// Provides generic hashing for any eligible type. /// Only hashes `key` itself, pointers are not followed. /// Slices as well as unions and structs containing slices are rejected to avoid /// ambiguity on the user's intention. -pub fn autoHash(hasher: anytype, key: anytype) void { +pub fn auto(hasher: anytype, key: anytype) void { const Key = @TypeOf(key); - if (comptime typeContainsSlice(Key)) { - @compileError("std.hash.autoHash does not allow slices as well as unions and structs containing slices here (" ++ @typeName(Key) ++ - ") because the intent is unclear. Consider using std.hash.autoHashStrat or providing your own hash function instead."); + if (comptime typeContains(Key, .slice)) { + @compileError("std.hash.auto does not allow slices as well as unions and structs containing slices here (" ++ @typeName(Key) ++ + ") because the intent is unclear. Consider using std.hash.autoStrat or providing your own hash function instead."); } - hash(hasher, key, .Shallow); + autoStrat(hasher, key, .shallow); } -const testing = std.testing; -const Wyhash = std.hash.Wyhash; +inline fn typeContains(comptime K: type, comptime what: enum { slice, pointer, sentinel }) bool { + return switch (@typeInfo(K)) { + .pointer => |info| switch (what) { + .pointer => true, + .slice => info.size == .slice, + .sentinel => false, + }, + + .@"struct" => |info| inline for (info.fields) |field| { + if (!field.is_comptime) { + if (typeContains(field.type, what)) break true; + } + } else false, + + .@"union" => |info| inline for (info.fields) |field| { + if (typeContains(field.type, what)) break true; + } else false, + + .array => |info| switch (what) { + .sentinel => info.sentinel_ptr != null, + .pointer, .slice => false, + } or (info.len > 0 and typeContains(info.child, what)), + + .vector => |info| info.len > 0 and typeContains(info.child, what), + + .optional => |info| typeContains(info.child, what), + .error_union => |info| typeContains(info.payload, what), + + else => false, + }; +} + +test typeContains { + try comptime expect(!typeContains(std.builtin.TypeId, .slice)); + try comptime expect(typeContains([]const u8, .slice)); + try comptime expect(!typeContains(u8, .slice)); + + const A = struct { x: []const u8 }; + const B = struct { a: A }; + const C = struct { b: B }; + const D = struct { x: u8, y: [40:0]u16 }; + + try comptime expect(typeContains(A, .slice)); + try comptime expect(typeContains(B, .slice)); + try comptime expect(typeContains(C, .slice)); + try comptime expect(!typeContains(D, .slice)); + try comptime expect(typeContains(D, .sentinel)); +} fn testHash(key: anytype) u64 { - // Any hash could be used here, for testing autoHash. - var hasher = Wyhash.init(0); - hash(&hasher, key, .Shallow); + // Any hash could be used here, for testing autoStrat. + var hasher: Wyhash = .init(0); + auto(&hasher, key); return hasher.final(); } fn testHashShallow(key: anytype) u64 { - // Any hash could be used here, for testing autoHash. - var hasher = Wyhash.init(0); - hash(&hasher, key, .Shallow); + // Any hash could be used here, for testing autoStrat. + var hasher: Wyhash = .init(0); + autoStrat(&hasher, key, .shallow); return hasher.final(); } fn testHashDeep(key: anytype) u64 { - // Any hash could be used here, for testing autoHash. - var hasher = Wyhash.init(0); - hash(&hasher, key, .Deep); + // Any hash could be used here, for testing autoStrat. + var hasher: Wyhash = .init(0); + autoStrat(&hasher, key, .deep); return hasher.final(); } fn testHashDeepRecursive(key: anytype) u64 { - // Any hash could be used here, for testing autoHash. - var hasher = Wyhash.init(0); - hash(&hasher, key, .DeepRecursive); + // Any hash could be used here, for testing auto. + var hasher: Wyhash = .init(0); + autoStrat(&hasher, key, .deep_recursive); return hasher.final(); } -test "typeContainsSlice" { - comptime { - try testing.expect(!typeContainsSlice(std.meta.Tag(std.builtin.Type))); - - try testing.expect(typeContainsSlice([]const u8)); - try testing.expect(!typeContainsSlice(u8)); - const A = struct { x: []const u8 }; - const B = struct { a: A }; - const C = struct { b: B }; - const D = struct { x: u8 }; - try testing.expect(typeContainsSlice(A)); - try testing.expect(typeContainsSlice(B)); - try testing.expect(typeContainsSlice(C)); - try testing.expect(!typeContainsSlice(D)); - } -} - test "hash pointer" { - const array = [_]u32{ 123, 123, 123 }; + const array: [3]u32 = .{ 123, 123, 123 }; const a = &array[0]; const b = &array[1]; const c = &array[2]; const d = a; - try testing.expect(testHashShallow(a) == testHashShallow(d)); - try testing.expect(testHashShallow(a) != testHashShallow(c)); - try testing.expect(testHashShallow(a) != testHashShallow(b)); + try expectEqual(testHashShallow(a), testHashShallow(d)); + try expect(testHashShallow(a) != testHashShallow(c)); + try expect(testHashShallow(a) != testHashShallow(b)); - try testing.expect(testHashDeep(a) == testHashDeep(a)); - try testing.expect(testHashDeep(a) == testHashDeep(c)); - try testing.expect(testHashDeep(a) == testHashDeep(b)); + try expectEqual(testHashDeep(a), testHashDeep(a)); + try expectEqual(testHashDeep(a), testHashDeep(c)); + try expectEqual(testHashDeep(a), testHashDeep(b)); - try testing.expect(testHashDeepRecursive(a) == testHashDeepRecursive(a)); - try testing.expect(testHashDeepRecursive(a) == testHashDeepRecursive(c)); - try testing.expect(testHashDeepRecursive(a) == testHashDeepRecursive(b)); + try expectEqual(testHashDeepRecursive(a), testHashDeepRecursive(a)); + try expectEqual(testHashDeepRecursive(a), testHashDeepRecursive(c)); + try expectEqual(testHashDeepRecursive(a), testHashDeepRecursive(b)); } test "hash slice shallow" { - // Allocate one array dynamically so that we're assured it is not merged - // with the other by the optimization passes. - const array1 = try std.testing.allocator.create([6]u32); - defer std.testing.allocator.destroy(array1); - array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 }; - const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 }; - // TODO audit deep/shallow - maybe it has the wrong behavior with respect to array pointers and slices + var array1: [6]u32 = undefined; + std.mem.doNotOptimizeAway(array1); + array1 = .{ 1, 2, 3, 4, 5, 6 }; + + const array2: [6]u32 = .{ 1, 2, 3, 4, 5, 6 }; + var runtime_zero: usize = 0; - _ = &runtime_zero; + std.mem.doNotOptimizeAway(&runtime_zero); + const a = array1[runtime_zero..]; const b = array2[runtime_zero..]; const c = array1[runtime_zero..3]; - try testing.expect(testHashShallow(a) == testHashShallow(a)); - try testing.expect(testHashShallow(a) != testHashShallow(array1)); - try testing.expect(testHashShallow(a) != testHashShallow(b)); - try testing.expect(testHashShallow(a) != testHashShallow(c)); + + try expectEqual(testHashShallow(a), testHashShallow(a)); + try expect(testHashShallow(a) != testHashShallow(array1)); + try expect(testHashShallow(a) != testHashShallow(b)); + try expect(testHashShallow(a) != testHashShallow(c)); } test "hash slice deep" { - // Allocate one array dynamically so that we're assured it is not merged - // with the other by the optimization passes. - const array1 = try std.testing.allocator.create([6]u32); - defer std.testing.allocator.destroy(array1); - array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 }; - const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 }; + var array1: [6]u32 = undefined; + std.mem.doNotOptimizeAway(array1); + array1 = .{ 1, 2, 3, 4, 5, 6 }; + + const array2: [6]u32 = .{ 1, 2, 3, 4, 5, 6 }; + const a = array1[0..]; const b = array2[0..]; const c = array1[0..3]; - try testing.expect(testHashDeep(a) == testHashDeep(a)); - try testing.expect(testHashDeep(a) == testHashDeep(array1)); - try testing.expect(testHashDeep(a) == testHashDeep(b)); - try testing.expect(testHashDeep(a) != testHashDeep(c)); + + try expectEqual(testHashDeep(a), testHashDeep(a)); + try expectEqual(testHashDeep(a), testHashDeep(array1)); + try expectEqual(testHashDeep(a), testHashDeep(b)); + try expect(testHashDeep(a) != testHashDeep(c)); } test "hash struct deep" { @@ -303,124 +495,149 @@ test "hash struct deep" { b: u16, c: *bool, - const Self = @This(); + const Foo = @This(); - pub fn init(allocator: mem.Allocator, a_: u32, b_: u16, c_: bool) !Self { + fn init(allocator: mem.Allocator, a: u32, b: u16, c: bool) mem.Allocator.Error!Foo { const ptr = try allocator.create(bool); - ptr.* = c_; - return Self{ .a = a_, .b = b_, .c = ptr }; + ptr.* = c; + return .{ + .a = a, + .b = b, + .c = ptr, + }; + } + + fn deinit(self: Foo, allocator: mem.Allocator) void { + allocator.destroy(self.c); } }; const allocator = std.testing.allocator; - const foo = try Foo.init(allocator, 123, 10, true); - const bar = try Foo.init(allocator, 123, 10, true); - const baz = try Foo.init(allocator, 123, 10, false); - defer allocator.destroy(foo.c); - defer allocator.destroy(bar.c); - defer allocator.destroy(baz.c); - - try testing.expect(testHashDeep(foo) == testHashDeep(bar)); - try testing.expect(testHashDeep(foo) != testHashDeep(baz)); - try testing.expect(testHashDeep(bar) != testHashDeep(baz)); - - var hasher = Wyhash.init(0); + + const foo: Foo = try .init(allocator, 123, 10, true); + defer foo.deinit(allocator); + + const bar: Foo = try .init(allocator, 123, 10, true); + defer bar.deinit(allocator); + + const baz: Foo = try .init(allocator, 123, 10, false); + defer baz.deinit(allocator); + + try expectEqual(testHashDeep(foo), testHashDeep(bar)); + try expect(testHashDeep(foo) != testHashDeep(baz)); + try expect(testHashDeep(bar) != testHashDeep(baz)); + + var hasher: Wyhash = .init(0); const h = testHashDeep(foo); - autoHash(&hasher, foo.a); - autoHash(&hasher, foo.b); - autoHash(&hasher, foo.c.*); - try testing.expectEqual(h, hasher.final()); + auto(&hasher, foo.a); + auto(&hasher, foo.b); + auto(&hasher, foo.c.*); + try expectEqual(h, hasher.final()); const h2 = testHashDeepRecursive(&foo); - try testing.expect(h2 != testHashDeep(&foo)); - try testing.expect(h2 == testHashDeep(foo)); + try expect(h2 != testHashDeep(&foo)); + try expectEqual(h2, testHashDeep(foo)); } -test "testHash optional" { +test "hash optional" { const a: ?u32 = 123; const b: ?u32 = null; - try testing.expectEqual(testHash(a), testHash(@as(u32, 123))); - try testing.expect(testHash(a) != testHash(b)); - try testing.expectEqual(testHash(b), 0x409638ee2bde459); // wyhash empty input hash + try expectEqual(testHash(a), testHash(@as(u32, 123))); + try expect(testHash(a) != testHash(b)); + try expectEqual(testHash(b), 0x409638ee2bde459); // wyhash empty input hash } -test "testHash array" { - const a = [_]u32{ 1, 2, 3 }; +test "hash array" { + const a: [3]u32 = .{ 1, 2, 3 }; const h = testHash(a); - var hasher = Wyhash.init(0); - autoHash(&hasher, @as(u32, 1)); - autoHash(&hasher, @as(u32, 2)); - autoHash(&hasher, @as(u32, 3)); - try testing.expectEqual(h, hasher.final()); + var hasher: Wyhash = .init(0); + auto(&hasher, @as(u32, 1)); + auto(&hasher, @as(u32, 2)); + auto(&hasher, @as(u32, 3)); + try expectEqual(h, hasher.final()); } -test "testHash multi-dimensional array" { - const a = [_][]const u32{ &.{ 1, 2, 3 }, &.{ 4, 5 } }; - const b = [_][]const u32{ &.{ 1, 2 }, &.{ 3, 4, 5 } }; - try testing.expect(testHash(a) != testHash(b)); +test "hash array of slices" { + const a: [2][]const u32 = .{ &.{ 1, 2, 3 }, &.{ 4, 5 } }; + const b: [2][]const u32 = .{ &.{ 1, 2 }, &.{ 3, 4, 5 } }; + try expectEqual(testHashDeep(a), testHashDeep(b)); + try expect(testHashShallow(a) != testHashShallow(b)); } -test "testHash struct" { +test "hash struct" { const Foo = struct { - a: u32 = 1, - b: u32 = 2, - c: u32 = 3, + a: u32, + b: u32, + c: u32, + }; + + const f: Foo = .{ + .a = 1, + .b = 2, + .c = 3, }; - const f = Foo{}; + const h = testHash(f); - var hasher = Wyhash.init(0); - autoHash(&hasher, @as(u32, 1)); - autoHash(&hasher, @as(u32, 2)); - autoHash(&hasher, @as(u32, 3)); - try testing.expectEqual(h, hasher.final()); + var hasher: Wyhash = .init(0); + auto(&hasher, @as(u32, 1)); + auto(&hasher, @as(u32, 2)); + auto(&hasher, @as(u32, 3)); + try expectEqual(h, hasher.final()); } -test "testHash union" { +test "hash union" { const Foo = union(enum) { - A: u32, - B: bool, - C: u32, - D: void, + a: u32, + b: bool, + c: u32, + d: void, }; - const a = Foo{ .A = 18 }; - var b = Foo{ .B = true }; - const c = Foo{ .C = 18 }; - const d: Foo = .D; - try testing.expect(testHash(a) == testHash(a)); - try testing.expect(testHash(a) != testHash(b)); - try testing.expect(testHash(a) != testHash(c)); - try testing.expect(testHash(a) != testHash(d)); + const a: Foo = .{ .a = 18 }; + var b: Foo = .{ .b = true }; + const c: Foo = .{ .c = 18 }; + const d: Foo = .d; + try expectEqual(testHash(a), testHash(a)); + try expect(testHash(a) != testHash(b)); + try expect(testHash(a) != testHash(c)); + try expect(testHash(a) != testHash(d)); - b = Foo{ .A = 18 }; - try testing.expect(testHash(a) == testHash(b)); + b = .{ .a = 18 }; + try expectEqual(testHash(a), testHash(b)); - b = .D; - try testing.expect(testHash(d) == testHash(b)); + b = .d; + try expectEqual(testHash(d), testHash(b)); } -test "testHash vector" { - const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 }; - const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 }; - try testing.expect(testHash(a) == testHash(a)); - try testing.expect(testHash(a) != testHash(b)); +test "hash vector" { + const a: @Vector(4, u32) = .{ 1, 2, 3, 4 }; + const b: @Vector(4, u32) = .{ 1, 2, 3, 5 }; + try expectEqual(testHash(a), testHash(a)); + try expect(testHash(a) != testHash(b)); - const c: @Vector(4, u31) = [_]u31{ 1, 2, 3, 4 }; - const d: @Vector(4, u31) = [_]u31{ 1, 2, 3, 5 }; - try testing.expect(testHash(c) == testHash(c)); - try testing.expect(testHash(c) != testHash(d)); + const c: @Vector(4, u31) = .{ 1, 2, 3, 4 }; + const d: @Vector(4, u31) = .{ 1, 2, 3, 5 }; + try expectEqual(testHash(c), testHash(c)); + try expect(testHash(c) != testHash(d)); } -test "testHash error union" { +test "hash error union" { const Errors = error{Test}; + const Foo = struct { - a: u32 = 1, - b: u32 = 2, - c: u32 = 3, + a: u32, + b: u32, + c: u32, }; - const f = Foo{}; - const g: Errors!Foo = Errors.Test; - try testing.expect(testHash(f) != testHash(g)); - try testing.expect(testHash(f) == testHash(Foo{})); - try testing.expect(testHash(g) == testHash(Errors.Test)); + const f: Foo = .{ + .a = 1, + .b = 2, + .c = 3, + }; + + const g: Errors!Foo = error.Test; + + try expect(testHash(f) != testHash(g)); + try expectEqual(testHash(f), testHash(Foo{ .a = 1, .b = 2, .c = 3 })); + try expectEqual(testHash(g), testHash(Errors.Test)); } diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 85424fe45e7e..7fc4588358ad 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -1,7 +1,6 @@ const std = @import("std.zig"); const builtin = @import("builtin"); const assert = std.debug.assert; -const autoHash = std.hash.autoHash; const math = std.math; const mem = std.mem; const Allocator = mem.Allocator; @@ -12,11 +11,11 @@ pub fn getAutoHashFn(comptime K: type, comptime Context: type) (fn (Context, K) comptime { assert(@hasDecl(std, "StringHashMap")); // detect when the following message needs updated if (K == []const u8) { - @compileError("std.hash.autoHash does not allow slices here (" ++ + @compileError("std.hash.auto does not allow slices here (" ++ @typeName(K) ++ ") because the intent is unclear. " ++ "Consider using std.StringHashMap for hashing the contents of []const u8. " ++ - "Alternatively, consider using std.hash.autoHashStrat or providing your own hash function instead."); + "Alternatively, consider using std.hash.autoStrat or providing your own hash function instead."); } } @@ -26,8 +25,8 @@ pub fn getAutoHashFn(comptime K: type, comptime Context: type) (fn (Context, K) if (std.meta.hasUniqueRepresentation(K)) { return Wyhash.hash(0, std.mem.asBytes(&key)); } else { - var hasher = Wyhash.init(0); - autoHash(&hasher, key); + var hasher: Wyhash = .init(0); + std.hash.auto(&hasher, key); return hasher.final(); } } diff --git a/src/Builtin.zig b/src/Builtin.zig index 7680d495d700..c4746201a9ae 100644 --- a/src/Builtin.zig +++ b/src/Builtin.zig @@ -26,14 +26,22 @@ pub fn hash(opts: @This()) [std.Build.Cache.bin_digest_len]u8 { inline for (@typeInfo(@This()).@"struct".fields) |f| { if (comptime std.mem.eql(u8, f.name, "target")) { // This needs special handling. - std.hash.autoHash(&h, opts.target.cpu); - std.hash.autoHash(&h, opts.target.os.tag); - std.hash.autoHash(&h, opts.target.os.versionRange()); - std.hash.autoHash(&h, opts.target.abi); - std.hash.autoHash(&h, opts.target.ofmt); - std.hash.autoHash(&h, opts.target.dynamic_linker); + std.hash.auto(&h, opts.target.cpu); + std.hash.auto(&h, opts.target.os.tag); + switch (opts.target.os.versionRange()) { + // Hash the OS version deeply to include + // the prerelease/build components. + // Currently, those components should always be null, + // but if a prerelease/build component were + // for some reason present, this would be + // the more correct behavior. + inline else => |ver| std.hash.autoStrat(&h, ver, .deep), + } + std.hash.auto(&h, opts.target.abi); + std.hash.auto(&h, opts.target.ofmt); + h.update(opts.target.dynamic_linker.get() orelse &.{}); } else { - std.hash.autoHash(&h, @field(opts, f.name)); + std.hash.auto(&h, @field(opts, f.name)); } } return h.finalResult(); diff --git a/src/Compilation.zig b/src/Compilation.zig index 760c8f8b110e..ff7465979e1d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4315,9 +4315,9 @@ pub const ErrorNoteHashContext = struct { if (key.src_loc != .none) { const src = eb.getSourceLocation(key.src_loc); hasher.update(eb.nullTerminatedString(src.src_path)); - std.hash.autoHash(&hasher, src.line); - std.hash.autoHash(&hasher, src.column); - std.hash.autoHash(&hasher, src.span_main); + std.hash.auto(&hasher, src.line); + std.hash.auto(&hasher, src.column); + std.hash.auto(&hasher, src.span_main); } return @as(u32, @truncate(hasher.final())); diff --git a/src/InternPool.zig b/src/InternPool.zig index a595fa502c0b..e68ab25d890c 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -2235,15 +2235,15 @@ pub const Key = union(enum) { pub fn hash(self: FuncType, hasher: *Hash, ip: *const InternPool) void { for (self.param_types.get(ip)) |param_type| { - std.hash.autoHash(hasher, param_type); + std.hash.auto(hasher, param_type); } - std.hash.autoHash(hasher, self.return_type); - std.hash.autoHash(hasher, self.comptime_bits); - std.hash.autoHash(hasher, self.noalias_bits); - std.hash.autoHash(hasher, self.cc); - std.hash.autoHash(hasher, self.is_var_args); - std.hash.autoHash(hasher, self.is_generic); - std.hash.autoHash(hasher, self.is_noinline); + std.hash.auto(hasher, self.return_type); + std.hash.auto(hasher, self.comptime_bits); + std.hash.auto(hasher, self.noalias_bits); + std.hash.auto(hasher, self.cc); + std.hash.auto(hasher, self.is_var_args); + std.hash.auto(hasher, self.is_generic); + std.hash.auto(hasher, self.is_noinline); } }; @@ -2650,7 +2650,6 @@ pub const Key = union(enum) { const KeyTag = @typeInfo(Key).@"union".tag_type.?; const seed = @intFromEnum(@as(KeyTag, key)); return switch (key) { - // TODO: assert no padding in these types inline .ptr_type, .array_type, .vector_type, @@ -2667,7 +2666,10 @@ pub const Key = union(enum) { .empty_enum_value, .inferred_error_set_type, .un, - => |x| Hash.hash(seed, asBytes(&x)), + => |x| raw_bytes: { + comptime assert(std.meta.hasUniqueRepresentation(@TypeOf(x))); + break :raw_bytes Hash.hash(seed, asBytes(&x)); + }, .int_type => |x| Hash.hash(seed + @intFromEnum(x.signedness), asBytes(&x.bits)), @@ -2683,58 +2685,56 @@ pub const Key = union(enum) { .union_type, .struct_type, => |namespace_type| { - var hasher = Hash.init(seed); - std.hash.autoHash(&hasher, std.meta.activeTag(namespace_type)); + var hasher: Hash = .init(seed); + std.hash.auto(&hasher, std.meta.activeTag(namespace_type)); switch (namespace_type) { .declared => |declared| { - std.hash.autoHash(&hasher, declared.zir_index); + std.hash.auto(&hasher, declared.zir_index); const captures = switch (declared.captures) { .owned => |cvs| cvs.get(ip), .external => |cvs| cvs, }; - for (captures) |cv| { - std.hash.autoHash(&hasher, cv); - } + std.hash.autoStrat(&hasher, captures, .deep); }, .generated_tag => |generated_tag| { - std.hash.autoHash(&hasher, generated_tag.union_type); + std.hash.auto(&hasher, generated_tag.union_type); }, .reified => |reified| { - std.hash.autoHash(&hasher, reified.zir_index); - std.hash.autoHash(&hasher, reified.type_hash); + std.hash.auto(&hasher, reified.zir_index); + std.hash.auto(&hasher, reified.type_hash); }, } return hasher.final(); }, .int => |int| { - var hasher = Hash.init(seed); + var hasher: Hash = .init(seed); // Canonicalize all integers by converting them to BigIntConst. switch (int.storage) { .u64, .i64, .big_int => { var buffer: Key.Int.Storage.BigIntSpace = undefined; const big_int = int.storage.toBigInt(&buffer); - std.hash.autoHash(&hasher, int.ty); - std.hash.autoHash(&hasher, big_int.positive); - for (big_int.limbs) |limb| std.hash.autoHash(&hasher, limb); + std.hash.auto(&hasher, int.ty); + std.hash.auto(&hasher, big_int.positive); + for (big_int.limbs) |limb| std.hash.auto(&hasher, limb); }, .lazy_align, .lazy_size => |lazy_ty| { - std.hash.autoHash( + std.hash.auto( &hasher, @as(@typeInfo(Key.Int.Storage).@"union".tag_type.?, int.storage), ); - std.hash.autoHash(&hasher, lazy_ty); + std.hash.auto(&hasher, lazy_ty); }, } return hasher.final(); }, .float => |float| { - var hasher = Hash.init(seed); - std.hash.autoHash(&hasher, float.ty); + var hasher: Hash = .init(seed); + std.hash.auto(&hasher, float.ty); switch (float.storage) { - inline else => |val| std.hash.autoHash( + inline else => |val| std.hash.auto( &hasher, @as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(val))), @bitCast(val)), ), @@ -2769,8 +2769,8 @@ pub const Key = union(enum) { }, .aggregate => |aggregate| { - var hasher = Hash.init(seed); - std.hash.autoHash(&hasher, aggregate.ty); + var hasher: Hash = .init(seed); + std.hash.auto(&hasher, aggregate.ty); const len = ip.aggregateTypeLen(aggregate.ty); const child = switch (ip.indexToKey(aggregate.ty)) { .array_type => |array_type| array_type.child, @@ -2782,15 +2782,15 @@ pub const Key = union(enum) { if (child == .u8_type) { switch (aggregate.storage) { .bytes => |bytes| for (bytes.toSlice(len, ip)) |byte| { - std.hash.autoHash(&hasher, KeyTag.int); - std.hash.autoHash(&hasher, byte); + std.hash.auto(&hasher, KeyTag.int); + std.hash.auto(&hasher, byte); }, .elems => |elems| for (elems[0..@intCast(len)]) |elem| { const elem_key = ip.indexToKey(elem); - std.hash.autoHash(&hasher, @as(KeyTag, elem_key)); + std.hash.auto(&hasher, @as(KeyTag, elem_key)); switch (elem_key) { .undef => {}, - .int => |int| std.hash.autoHash( + .int => |int| std.hash.auto( &hasher, @as(u8, @intCast(int.storage.u64)), ), @@ -2801,10 +2801,10 @@ pub const Key = union(enum) { const elem_key = ip.indexToKey(elem); var remaining = len; while (remaining > 0) : (remaining -= 1) { - std.hash.autoHash(&hasher, @as(KeyTag, elem_key)); + std.hash.auto(&hasher, @as(KeyTag, elem_key)); switch (elem_key) { .undef => {}, - .int => |int| std.hash.autoHash( + .int => |int| std.hash.auto( &hasher, @as(u8, @intCast(int.storage.u64)), ), @@ -2818,11 +2818,10 @@ pub const Key = union(enum) { switch (aggregate.storage) { .bytes => unreachable, - .elems => |elems| for (elems[0..@intCast(len)]) |elem| - std.hash.autoHash(&hasher, elem), + .elems => |elems| std.hash.autoStrat(&hasher, elems, .deep), .repeated_elem => |elem| { var remaining = len; - while (remaining > 0) : (remaining -= 1) std.hash.autoHash(&hasher, elem); + while (remaining > 0) : (remaining -= 1) std.hash.auto(&hasher, elem); }, } return hasher.final(); @@ -2831,22 +2830,22 @@ pub const Key = union(enum) { .error_set_type => |x| Hash.hash(seed, std.mem.sliceAsBytes(x.names.get(ip))), .tuple_type => |tuple_type| { - var hasher = Hash.init(seed); - for (tuple_type.types.get(ip)) |elem| std.hash.autoHash(&hasher, elem); - for (tuple_type.values.get(ip)) |elem| std.hash.autoHash(&hasher, elem); + var hasher: Hash = .init(seed); + std.hash.autoStrat(&hasher, tuple_type.types.get(ip), .deep); + std.hash.autoStrat(&hasher, tuple_type.values.get(ip), .deep); return hasher.final(); }, .func_type => |func_type| { - var hasher = Hash.init(seed); + var hasher: Hash = .init(seed); func_type.hash(&hasher, ip); return hasher.final(); }, .memoized_call => |memoized_call| { - var hasher = Hash.init(seed); - std.hash.autoHash(&hasher, memoized_call.func); - for (memoized_call.arg_values) |arg| std.hash.autoHash(&hasher, arg); + var hasher: Hash = .init(seed); + std.hash.auto(&hasher, memoized_call.func); + std.hash.autoStrat(&hasher, memoized_call.arg_values, .deep); return hasher.final(); }, @@ -2862,12 +2861,12 @@ pub const Key = union(enum) { return Hash.hash(seed, bytes); } - var hasher = Hash.init(seed); - std.hash.autoHash(&hasher, func.generic_owner); - std.hash.autoHash(&hasher, func.uncoerced_ty == func.ty); - for (func.comptime_args.get(ip)) |arg| std.hash.autoHash(&hasher, arg); + var hasher: Hash = .init(seed); + std.hash.auto(&hasher, func.generic_owner); + std.hash.auto(&hasher, func.uncoerced_ty == func.ty); + std.hash.autoStrat(&hasher, func.comptime_args.get(ip), .deep); if (func.resolved_error_set_extra_index == 0) { - std.hash.autoHash(&hasher, func.ty); + std.hash.auto(&hasher, func.ty); } else { var ty_info = ip.indexToFuncType(func.ty).?; ty_info.return_type = ip.errorUnionPayload(ty_info.return_type); diff --git a/src/Sema.zig b/src/Sema.zig index b94e172b45ac..833bdb7ac709 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21076,10 +21076,10 @@ fn reifyEnum( // For deduplication purposes, we must create a hash including all details of this type. // TODO: use a longer hash! - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, tag_ty.toIntern()); - std.hash.autoHash(&hasher, is_exhaustive); - std.hash.autoHash(&hasher, fields_len); + var hasher: std.hash.Wyhash = .init(0); + std.hash.auto(&hasher, tag_ty.toIntern()); + std.hash.auto(&hasher, is_exhaustive); + std.hash.auto(&hasher, fields_len); for (0..fields_len) |field_idx| { const field_info = try fields_val.elemValue(pt, field_idx); @@ -21089,7 +21089,7 @@ fn reifyEnum( const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .enum_field_name }); - std.hash.autoHash(&hasher, .{ + std.hash.auto(&hasher, .{ field_name, field_value_val.toIntern(), }); @@ -21221,10 +21221,10 @@ fn reifyUnion( // For deduplication purposes, we must create a hash including all details of this type. // TODO: use a longer hash! - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, layout); - std.hash.autoHash(&hasher, opt_tag_type_val.toIntern()); - std.hash.autoHash(&hasher, fields_len); + var hasher: std.hash.Wyhash = .init(0); + std.hash.auto(&hasher, layout); + std.hash.auto(&hasher, opt_tag_type_val.toIntern()); + std.hash.auto(&hasher, fields_len); for (0..fields_len) |field_idx| { const field_info = try fields_val.elemValue(pt, field_idx); @@ -21234,7 +21234,7 @@ fn reifyUnion( const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(pt, 2)); const field_name = try sema.sliceToIpString(block, src, field_name_val, .{ .simple = .union_field_name }); - std.hash.autoHash(&hasher, .{ + std.hash.auto(&hasher, .{ field_name, field_type_val.toIntern(), field_align_val.toIntern(), @@ -21553,10 +21553,10 @@ fn reifyStruct( // For deduplication purposes, we must create a hash including all details of this type. // TODO: use a longer hash! - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, layout); - std.hash.autoHash(&hasher, opt_backing_int_val.toIntern()); - std.hash.autoHash(&hasher, fields_len); + var hasher: std.hash.Wyhash = .init(0); + std.hash.auto(&hasher, layout); + std.hash.auto(&hasher, opt_backing_int_val.toIntern()); + std.hash.auto(&hasher, fields_len); var any_comptime_fields = false; var any_default_inits = false; @@ -21587,7 +21587,7 @@ fn reifyStruct( break :d (try sema.resolveLazyValue(val)).toIntern(); } else .none; - std.hash.autoHash(&hasher, .{ + std.hash.auto(&hasher, .{ field_name, field_type_val.toIntern(), field_default_value, diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index bf9edf0753c2..80ed00ffaaa0 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -110,8 +110,8 @@ const DestTarget = struct { const HashContext = struct { pub fn hash(self: @This(), a: DestTarget) u32 { _ = self; - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, a.arch); + var hasher: std.hash.Wyhash = .init(0); + std.hash.auto(&hasher, a.arch); return @as(u32, @truncate(hasher.final())); } From 241cb6d54c33fe720b9c927bf195ca19d5de4e02 Mon Sep 17 00:00:00 2001 From: seanthegleaming Date: Mon, 27 Oct 2025 20:58:26 -0400 Subject: [PATCH 2/4] Fix improper use of hash.auto in src/libs/glibc This file made use of an auto hash map of SemanticVersions. Due to the recent fixes in `auto_hash.zig`, this is now a compile error, as `SemanticVersion` contains slices. This oversight previously went undetected, and simply hashed the slice slices by value if they were present. With this fix, the slices are explicitly hashed deeply. --- src/libs/glibc.zig | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index 8281bee303d7..492e1b5544f3 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -778,6 +778,18 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye var versions_buffer: [32]u8 = undefined; var versions_len: usize = undefined; + const VersionHashContext = struct { + pub fn hash(_: @This(), version: Version) u32 { + var hasher: std.hash.Wyhash = .init(0); + std.hash.autoStrat(&hasher, version, .deep); + return @truncate(hasher.final()); + } + + pub fn eql(_: @This(), lhs: Version, rhs: Version) bool { + return std.meta.eql(lhs, rhs); + } + }; + // There can be situations where there are multiple inclusions for the same symbol with // partially overlapping versions, due to different target lists. For example: // @@ -792,7 +804,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye // // If we don't handle this, we end up writing the default `lgammal` symbol for version 2.33 // twice, which causes a "duplicate symbol" assembler error. - var versions_written = std.AutoArrayHashMap(Version, void).init(arena); + var versions_written: std.ArrayHashMapUnmanaged(Version, void, VersionHashContext, true) = .empty; var inc_reader: std.Io.Reader = .fixed(metadata.inclusions); @@ -856,7 +868,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye } versions_written.clearRetainingCapacity(); - try versions_written.ensureTotalCapacity(versions_len); + try versions_written.ensureTotalCapacity(arena, versions_len); { var ver_buf_i: u8 = 0; @@ -870,7 +882,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const ver_index = versions_buffer[ver_buf_i]; const ver = metadata.all_versions[ver_index]; - if (versions_written.getOrPutAssumeCapacity(ver).found_existing) continue; + if (versions_written.getOrPutAssumeCapacity(arena, ver).found_existing) continue; // Default symbol version definition vs normal symbol version definition const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index; @@ -1032,7 +1044,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye } versions_written.clearRetainingCapacity(); - try versions_written.ensureTotalCapacity(versions_len); + try versions_written.ensureTotalCapacity(arena, versions_len); { var ver_buf_i: u8 = 0; @@ -1047,7 +1059,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const ver_index = versions_buffer[ver_buf_i]; const ver = metadata.all_versions[ver_index]; - if (versions_written.getOrPutAssumeCapacity(ver).found_existing) continue; + if (versions_written.getOrPutAssumeCapacity(arena, ver).found_existing) continue; // Default symbol version definition vs normal symbol version definition const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index; From 56cc5f67e162fa3d81aa71c1f47a1725ca8f2183 Mon Sep 17 00:00:00 2001 From: seanthegleaming Date: Mon, 27 Oct 2025 21:08:17 -0400 Subject: [PATCH 3/4] Fix typo in glibc AutoHashMap fix --- src/libs/glibc.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index 492e1b5544f3..886ae076ebf2 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -882,7 +882,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const ver_index = versions_buffer[ver_buf_i]; const ver = metadata.all_versions[ver_index]; - if (versions_written.getOrPutAssumeCapacity(arena, ver).found_existing) continue; + if (versions_written.getOrPutAssumeCapacity(ver).found_existing) continue; // Default symbol version definition vs normal symbol version definition const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index; @@ -1059,7 +1059,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye const ver_index = versions_buffer[ver_buf_i]; const ver = metadata.all_versions[ver_index]; - if (versions_written.getOrPutAssumeCapacity(arena, ver).found_existing) continue; + if (versions_written.getOrPutAssumeCapacity(ver).found_existing) continue; // Default symbol version definition vs normal symbol version definition const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index; From ccccf12ba28ff930bb753999c1e3e86c0d31acea Mon Sep 17 00:00:00 2001 From: seanthegleaming Date: Mon, 27 Oct 2025 21:16:24 -0400 Subject: [PATCH 4/4] Add index parameter to glibc.zig VersionSet Apologies for not not testing these changes locally, I have been unable to build Zig with LLVM, and thus have to rely on the CI to test building the compiler with LLVM enabled. --- src/libs/glibc.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index 886ae076ebf2..e5bac86d1b4c 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -785,7 +785,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye return @truncate(hasher.final()); } - pub fn eql(_: @This(), lhs: Version, rhs: Version) bool { + pub fn eql(_: @This(), lhs: Version, rhs: Version, _: usize) bool { return std.meta.eql(lhs, rhs); } }; @@ -804,7 +804,8 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye // // If we don't handle this, we end up writing the default `lgammal` symbol for version 2.33 // twice, which causes a "duplicate symbol" assembler error. - var versions_written: std.ArrayHashMapUnmanaged(Version, void, VersionHashContext, true) = .empty; + const VersionSet = std.ArrayHashMapUnmanaged(Version, void, VersionHashContext, true); + var versions_written: VersionSet = .empty; var inc_reader: std.Io.Reader = .fixed(metadata.inclusions);