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 {
873873 // through calls to `addDiscoveredPath` etc, which will update the hasher. After all files are added, the user
874874 // 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
879876 hit: {
880877 const miss_result = miss: {
881878 const result = try man.checkLocked();
......@@ -884,12 +881,8 @@ pub const Manifest = struct {
884881 } else if (!try man.upgradeToExclusiveLock()) {
885882 break :miss result;
886883 }
887 // We've just had a miss with the shared lock, and upgraded to an exclusive lock. Someone
888 // else might have modified the digest, 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);
884 // Missed with the shared lock, and upgraded to an exclusive lock. However, another process may have
885 // modified the cache directory, so we need to check again before deciding to miss.
893886 man.shrinkFilesToInput();
894887 const refreshed_result = try man.checkLocked();
895888 if (refreshed_result == .hit) break :hit;
......@@ -899,7 +892,8 @@ pub const Manifest = struct {
899892 // Cache miss, but `checkLocked` guarantees that all input files have their digests computed, even on a
900893 // cache miss, which is needed because they will be used in the manifest digest.
901894 man.dirty = true;
902 undoCheck(man, &input_digest);
895 man.shrinkFilesToInput();
896 hashFiles(man, &input_digest);
903897 return miss_result;
904898 }
905899
......@@ -910,9 +904,23 @@ pub const Manifest = struct {
910904 };
911905 }
912906
907 hashFiles(man, &input_digest);
913908 return .hit;
914909 }
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
916924 fn shrinkFilesToInput(m: *Manifest) void {
917925 if (m.files.count() <= m.input_paths.items.len) return;
918926 // Reads from files hash map whose data is destroyed on the next line.
......@@ -923,9 +931,9 @@ pub const Manifest = struct {
923931 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
924932 }
925933
926 /// Assumes that `self.hash.hasher` has been updated only with the original digest and that
927 /// `self.files` contains only the original input files.
934 /// Does not observe or modify `self.hash.hasher`. Asserts `self.files` contains only the original input files.
928935 fn checkLocked(m: *Manifest) CheckError!CheckResult {
936 assert(m.files.count() == m.input_paths.items.len);
929937 const gpa = m.cache.gpa;
930938 const io = m.cache.io;
931939 const manifest_file = m.manifest_file.?;
......@@ -938,7 +946,7 @@ pub const Manifest = struct {
938946 if (manifest_size == 0 or manifest_size < m.contents.items.len) {
939947 // Manifest file was never finalized.
940948 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);
942950 }
943951
944952 // We must not clobber existing `Manifest.contents` because it possibly contains prepopulated stat and digest
......@@ -957,7 +965,7 @@ pub const Manifest = struct {
957965 error.Canceled => |e| return e,
958966 else => |e| return fail(&m.diagnostic, .{ .manifest_read = e }),
959967 };
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);
961969 }
962970 const disk_contents = m.contents.items[0..manifest_size];
963971 const input_contents = m.contents.items[manifest_size..][0..input_contents_len];
......@@ -1004,8 +1012,6 @@ pub const Manifest = struct {
10041012 const file_valid = off + 1 == disk_contents.len and disk_contents[off] == 0;
10051013 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
10091015 // Since it's a cache hit, we accept the input file contents from disk and discard the other copy.
10101016 // Furthermore, don't track the trailing zero byte in contents.
10111017 m.contents.shrinkRetainingCapacity(manifest_size - 1);
......@@ -1035,7 +1041,15 @@ pub const Manifest = struct {
10351041 m.contents.items[manifest_size + off ..][0..copy_len],
10361042 );
10371043 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();
10391053 const contents = m.contents.items;
10401054 for (file_offs[next_file_index..], m.input_paths.items[next_file_index..]) |input_file_off, *input_path| {
10411055 try populateInputPath(m, input_file_off, input_path, contents);
......@@ -1515,23 +1529,6 @@ pub const Manifest = struct {
15151529 return error.CacheCheckFailed;
15161530 }
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
15351532 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) error{Canceled}!bool {
15361533 const io = man.cache.io;
15371534
......@@ -1844,22 +1841,20 @@ pub const Manifest = struct {
18441841 };
18451842 }
18461843
1847 /// Returns a binary hash of the inputs.
1844 /// Returns a binary hash of the inputs, input file contents, and discovered file contents.
18481845 pub fn finalBin(self: *Manifest) BinDigest {
18491846 assert(self.manifest_file != null);
18501847
1851 // We don't close the manifest file yet, because we want to
1852 // keep it locked until the API user is done using it.
1853 // We also don't write out the manifest yet, because until
1854 // cache_release is called we still might be working on creating
1855 // the artifacts to cache.
1848 // We don't close the manifest file yet, because we want to 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
1850 // creating the artifacts to cache.
18561851
18571852 var bin_digest: BinDigest = undefined;
18581853 self.hash.hasher.final(&bin_digest);
18591854 return bin_digest;
18601855 }
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.
18631858 pub fn final(self: *Manifest) HexDigest {
18641859 const bin_digest = self.finalBin();
18651860 return binToHex(bin_digest);
......@@ -2327,7 +2322,7 @@ test "check that changing a file causes cache miss" {
23272322 try man.finalize();
23282323 }
23292324
2330 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
2325 try testing.expect(!mem.eql(u8, &digest1, &digest2));
23312326 }
23322327}
23332328