authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-13 18:18:47-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-16 13:12:47-07:00
log228178c151e078dc1bef38c2e20038cebe24a887
treef4a7927fc97f90dd6fa60005c93539bdc9126f4f
parent43397f22513c5769213c74a62822366f4d548364

std.Build.Cache: simplify hashing logic


1 files changed, 37 insertions(+), 42 deletions(-)

lib/std/Build/Cache.zig+37-42
...@@ -873,9 +873,6 @@ pub const Manifest = struct {...@@ -873,9 +873,6 @@ pub const Manifest = struct {
873 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user873 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user
874 // can use `final`, and will at some point `finalize` the file list to disk.874 // can use `final`, and will at some point `finalize` the file list to disk.
875875
876 man.hash.hasher = hasher_init;
877 man.hash.hasher.update(&input_digest);
878
879 hit: {876 hit: {
880 const miss_result = miss: {877 const miss_result = miss: {
881 const result = try man.checkLocked();878 const result = try man.checkLocked();
...@@ -884,12 +881,8 @@ pub const Manifest = struct {...@@ -884,12 +881,8 @@ pub const Manifest = struct {
884 } else if (!try man.upgradeToExclusiveLock()) {881 } else if (!try man.upgradeToExclusiveLock()) {
885 break :miss result;882 break :miss result;
886 }883 }
887 // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone884 // Missed with the shared lock, and upgraded to an exclusive lock. However, another process may have
888 // else might have modified the digest, so we need to check again before deciding to miss.885 // modified the cache directory, so we need to check again before deciding to miss.
889 // Before trying again, we must reset `man.hash.hasher` and `man.files`.
890 // This is basically just the first half of `undoCheck`.
891 man.hash.hasher = hasher_init;
892 man.hash.hasher.update(&input_digest);
893 man.shrinkFilesToInput();886 man.shrinkFilesToInput();
894 const refreshed_result = try man.checkLocked();887 const refreshed_result = try man.checkLocked();
895 if (refreshed_result == .hit) break :hit;888 if (refreshed_result == .hit) break :hit;
...@@ -899,7 +892,8 @@ pub const Manifest = struct {...@@ -899,7 +892,8 @@ pub const Manifest = struct {
899 // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a892 // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a
900 // cache miss, which is needed because they will be used in the manifest digest.893 // cache miss, which is needed because they will be used in the manifest digest.
901 man.dirty = true;894 man.dirty = true;
902 undoCheck(man, &input_digest);895 man.shrinkFilesToInput();
896 hashFiles(man, &input_digest);
903 return miss_result;897 return miss_result;
904 }898 }
905899
...@@ -910,9 +904,23 @@ pub const Manifest = struct {...@@ -910,9 +904,23 @@ pub const Manifest = struct {
910 };904 };
911 }905 }
912906
907 hashFiles(man, &input_digest);
913 return .hit;908 return .hit;
914 }909 }
915910
911 fn hashFiles(m: *Manifest, input_digest: *BinDigest) void {
912 const contents = m.contents.items;
913
914 m.hash.hasher = hasher_init;
915 m.hash.hasher.update(input_digest);
916
917 for (m.files.keys(), 0..) |off, i| {
918 const file = off.get(contents);
919 log.warn("hashing {d} {s} = {x}", .{ i, off.path(contents), &file.digest });
920 m.hash.hasher.update(&file.digest);
921 }
922 }
923
916 fn shrinkFilesToInput(m: *Manifest) void {924 fn shrinkFilesToInput(m: *Manifest) void {
917 if (m.files.count() <= m.input_paths.items.len) return;925 if (m.files.count() <= m.input_paths.items.len) return;
918 // Reads from files hash map whose data is destroyed on the next line.926 // Reads from files hash map whose data is destroyed on the next line.
...@@ -923,9 +931,9 @@ pub const Manifest = struct {...@@ -923,9 +931,9 @@ pub const Manifest = struct {
923 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));931 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
924 }932 }
925933
926 /// Assumes that `self.hash.hasher` has been updated only with the original digest and that934 /// Does not observe or modify `self.hash.hasher`. Asserts `self.files` contains only the original input files.
927 /// `self.files` contains only the original input files.
928 fn checkLocked(m: *Manifest) CheckError!CheckResult {935 fn checkLocked(m: *Manifest) CheckError!CheckResult {
936 assert(m.files.count() == m.input_paths.items.len);
929 const gpa = m.cache.gpa;937 const gpa = m.cache.gpa;
930 const io = m.cache.io;938 const io = m.cache.io;
931 const manifest_file = m.manifest_file.?;939 const manifest_file = m.manifest_file.?;
...@@ -938,7 +946,7 @@ pub const Manifest = struct {...@@ -938,7 +946,7 @@ pub const Manifest = struct {
938 if (manifest_size == 0 or manifest_size < m.contents.items.len) {946 if (manifest_size == 0 or manifest_size < m.contents.items.len) {
939 // Manifest file was never finalized.947 // Manifest file was never finalized.
940 try m.contents.ensureUnusedCapacity(gpa, 1);948 try m.contents.ensureUnusedCapacity(gpa, 1);
941 return missInput(m, 0, m.contents.items.len, manifest_size, .incomplete_manifest);949 return populateMissingInputFileHashes(m, 0, .incomplete_manifest);
942 }950 }
943951
944 // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest952 // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest
...@@ -957,7 +965,7 @@ pub const Manifest = struct {...@@ -957,7 +965,7 @@ pub const Manifest = struct {
957 error.Canceled => |e| return e,965 error.Canceled => |e| return e,
958 else => |e| return fail(&m.diagnostic, .{ .manifest_read = e }),966 else => |e| return fail(&m.diagnostic, .{ .manifest_read = e }),
959 };967 };
960 if (n != manifest_size) return checkLockedMiss(m, input_contents_len, .incomplete_manifest);968 if (n != manifest_size) return missInput(m, 0, input_contents_len, manifest_size, .incomplete_manifest);
961 }969 }
962 const disk_contents = m.contents.items[0..manifest_size];970 const disk_contents = m.contents.items[0..manifest_size];
963 const input_contents = m.contents.items[manifest_size..][0..input_contents_len];971 const input_contents = m.contents.items[manifest_size..][0..input_contents_len];
...@@ -1004,8 +1012,6 @@ pub const Manifest = struct {...@@ -1004,8 +1012,6 @@ pub const Manifest = struct {
1004 const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0;1012 const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0;
1005 if (!file_valid) return checkLockedMiss(m, input_contents_len, .incomplete_manifest);1013 if (!file_valid) return checkLockedMiss(m, input_contents_len, .incomplete_manifest);
10061014
1007 for (m.files.keys()) |file_off| m.hash.hasher.update(&file_off.get(disk_contents).digest);
1008
1009 // Since it's a cache hit, we accept the input file contents from disk and discard the other copy.1015 // Since it's a cache hit, we accept the input file contents from disk and discard the other copy.
1010 // Furthermore, don't track the trailing zero byte in contents.1016 // Furthermore, don't track the trailing zero byte in contents.
1011 m.contents.shrinkRetainingCapacity(manifest_size - 1);1017 m.contents.shrinkRetainingCapacity(manifest_size - 1);
...@@ -1035,7 +1041,15 @@ pub const Manifest = struct {...@@ -1035,7 +1041,15 @@ pub const Manifest = struct {
1035 m.contents.items[manifest_size + off ..][0..copy_len],1041 m.contents.items[manifest_size + off ..][0..copy_len],
1036 );1042 );
1037 m.contents.shrinkRetainingCapacity(input_contents_len);1043 m.contents.shrinkRetainingCapacity(input_contents_len);
1038 // And now iterate over remaining input files and populate the missing input file hashes.1044 return populateMissingInputFileHashes(m, next_file_index, result);
1045 }
1046
1047 fn populateMissingInputFileHashes(
1048 m: *Manifest,
1049 next_file_index: usize,
1050 result: CheckResult,
1051 ) CheckError!CheckResult {
1052 const file_offs = m.files.keys();
1039 const contents = m.contents.items;1053 const contents = m.contents.items;
1040 for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| {1054 for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| {
1041 try populateInputPath(m, input_file_off, input_path, contents);1055 try populateInputPath(m, input_file_off, input_path, contents);
...@@ -1515,23 +1529,6 @@ pub const Manifest = struct {...@@ -1515,23 +1529,6 @@ pub const Manifest = struct {
1515 return error.CacheCheckFailed;1529 return error.CacheCheckFailed;
1516 }1530 }
15171531
1518 /// Reset `man.hash.hasher` to the state it should be in after `check` returns `CheckResult.miss`.
1519 /// The hasher contains the original input digest, and all original input file digests (i.e.
1520 /// not including discovered files).
1521 ///
1522 /// Assumes that `digest` is populated for all input files.
1523 pub fn undoCheck(man: *Manifest, input_digest: *const BinDigest) void {
1524 // Reset the hash.
1525 man.hash.hasher = hasher_init;
1526 man.hash.hasher.update(input_digest);
1527 man.shrinkFilesToInput();
1528 const contents = man.contents.items;
1529 for (man.files.keys()) |off| {
1530 const file = off.get(contents);
1531 man.hash.hasher.update(&file.digest);
1532 }
1533 }
1534
1535 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {1532 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {
1536 const io = man.cache.io;1533 const io = man.cache.io;
15371534
...@@ -1844,22 +1841,20 @@ pub const Manifest = struct {...@@ -1844,22 +1841,20 @@ pub const Manifest = struct {
1844 };1841 };
1845 }1842 }
18461843
1847 /// Returns a binary hash of the inputs.1844 /// Returns a binary hash of the inputs, input file contents, and discovered file contents.
1848 pub fn finalBin(self: *Manifest) BinDigest {1845 pub fn finalBin(self: *Manifest) BinDigest {
1849 assert(self.manifest_file != null);1846 assert(self.manifest_file != null);
18501847
1851 // We don't close the manifest file yet, because we want to1848 // We don't close the manifest file yet, because we want to keep it locked until the API user is done using it.
1852 // keep it locked until the API user is done using it.1849 // We also don't write out the manifest yet, because until `finalize` is called we still might be working on
1853 // We also don't write out the manifest yet, because until1850 // creating the artifacts to cache.
1854 // cache_release is called we still might be working on creating
1855 // the artifacts to cache.
18561851
1857 var bin_digest: BinDigest = undefined;1852 var bin_digest: BinDigest = undefined;
1858 self.hash.hasher.final(&bin_digest);1853 self.hash.hasher.final(&bin_digest);
1859 return bin_digest;1854 return bin_digest;
1860 }1855 }
18611856
1862 /// Returns a hex encoded hash of the inputs.1857 /// Returns a hex encoded hash of the inputs, input file contents, and discovered file contents.
1863 pub fn final(self: *Manifest) HexDigest {1858 pub fn final(self: *Manifest) HexDigest {
1864 const bin_digest = self.finalBin();1859 const bin_digest = self.finalBin();
1865 return binToHex(bin_digest);1860 return binToHex(bin_digest);
...@@ -2327,7 +2322,7 @@ test "check that changing a file causes cache miss" {...@@ -2327,7 +2322,7 @@ test "check that changing a file causes cache miss" {
2327 try man.finalize();2322 try man.finalize();
2328 }2323 }
23292324
2330 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));2325 try testing.expect(!mem.eql(u8, &digest1, &digest2));
2331 }2326 }
2332}2327}
23332328