authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-03 18:34:48-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-04 17:42:22-07:00
log61e2e70b1e8c3e3ee9b6f524187c4453308f98f5
tree98de65fc77c26c2bc1ea4f77a9700e33a8b337eb
parentcef0857634686c0b4968d9d5a90991f0feeef839

std.Build.Cache: get it compiling again


4 files changed, 217 insertions(+), 162 deletions(-)

lib/compiler/Maker.zig+2-1
......@@ -1498,7 +1498,8 @@ fn configure(graph: *Graph, options: ConfigureOptions) !ScannedConfig {
14981498
14991499 if (config_man) |man| for (configuration.path_deps) |path_dep| {
15001500 const path = try confPathDepToCachePath(arena, graph, &configuration, path_dep);
1501 try man.addPathPost(path, .{
1501 try man.addPathPost(.{
1502 .path = .{ .unresolved = path },
15021503 .handle = if (path_dep.flags.is_directory) .{ .dir = null } else .{ .file = null },
15031504 .metadata_only = path_dep.flags.metadata_only,
15041505 });
lib/compiler/Maker/Step.zig+1-1
......@@ -787,7 +787,7 @@ pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !voi
787787}
788788
789789pub fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
790 return setWatchInputsFromManifestFiles(s, maker, &man.files, man.cache.prefixes());
790 return setWatchInputsFromManifestFiles(s, maker, &man.borrowFiles(), man.cache.prefixes());
791791}
792792
793793pub fn setWatchInputsFromManifestFiles(
lib/std/Build/Cache.zig+208-154
......@@ -56,71 +56,60 @@ pub fn prefixes(cache: *const Cache) []const Directory {
5656 return cache.prefixes_buffer[0..cache.prefixes_len];
5757}
5858
59pub const PrefixedPath = struct {
60 prefix: u8,
61 sub_path: []const u8,
62
63 fn eql(a: PrefixedPath, b: PrefixedPath) bool {
64 return a.prefix == b.prefix and mem.eql(u8, a.sub_path, b.sub_path);
65 }
59const PrefixIndex = u6;
6660
67 fn hash(pp: PrefixedPath) u32 {
68 return @truncate(std.hash.Wyhash.hash(pp.prefix, pp.sub_path));
69 }
61const PrefixedPath = struct {
62 prefix: PrefixIndex,
63 sub_path: []const u8,
7064};
7165
72fn findPrefixPath(cache: *const Cache, path: Path) !PrefixedPath {
73 const gpa = cache.gpa;
74 const resolved_path = try std.fs.path.resolve(gpa, &.{
75 cache.cwd, path.root_dir.path orelse ".", path.subPathOrDot(),
76 });
77 errdefer gpa.free(resolved_path);
78 return findPrefixResolved(cache, resolved_path);
79}
80
81fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
82 const gpa = cache.gpa;
83 const resolved_path = try std.fs.path.resolve(gpa, &.{file_path});
84 errdefer gpa.free(resolved_path);
85 return findPrefixResolved(cache, resolved_path);
66fn appendPrefixedPath(cache: *const Cache, contents: *std.ArrayList(u8), prefixed_path: PrefixedPath) !PrefixIndex {
67 const end = contents.items.len + prefixed_path.sub_path.len;
68 const needed_alignment = @alignOf(Manifest.File) - (end % @alignOf(Manifest.File));
69 assert(needed_alignment >= 1); // Always need at least a null byte.
70 try contents.ensureTotalCapacity(cache.gpa, end + needed_alignment);
71 contents.appendSliceAssumeCapacity(prefixed_path.sub_path);
72 contents.appendNTimesAssumeCapacity(0, needed_alignment);
73 return prefixed_path.prefix;
8674}
8775
88/// Takes ownership of `resolved_path` on success.
89fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
76fn resolveAppendPath(cache: *const Cache, contents: *std.ArrayList(u8), path: Path) !PrefixIndex {
9077 const gpa = cache.gpa;
9178 const cwd = cache.cwd;
79 const path_start = contents.items.len;
80
81 const resolved_path = try std.fs.path.resolveAlloc(gpa, &.{
82 path.root_dir.path orelse cwd,
83 path.subPathOrDot(),
84 });
85 defer gpa.free(resolved_path);
86
9287 for (cache.prefixes(), 0..) |prefix, i| {
93 const p = prefix.path orelse continue;
94 const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {
95 error.NotASubPath => continue,
96 else => |e| return e,
97 };
98 // Free the resolved path since we're not going to return it
99 gpa.free(resolved_path);
100 return .{
101 .prefix = @intCast(i),
102 .sub_path = sub_path,
103 };
104 }
88 const pp = prefix.path orelse continue;
89 contents.shrinkRetainingCapacity(path_start);
90 try std.fs.path.relativeAppend(gpa, contents, cwd, null, pp, resolved_path);
91 const relative = contents.items[path_start..];
10592
106 return .{
107 .prefix = 0,
108 .sub_path = resolved_path,
109 };
110}
93 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
94 if (component_iterator.root() != null) continue;
95 const first_component = component_iterator.first();
96 if (first_component != null and mem.eql(u8, first_component.?.name, "..")) continue;
11197
112fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {
113 const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);
114 errdefer gpa.free(relative);
115 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
116 if (component_iterator.root() != null) {
117 return error.NotASubPath;
118 }
119 const first_component = component_iterator.first();
120 if (first_component != null and mem.eql(u8, first_component.?.name, "..")) {
121 return error.NotASubPath;
98 const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File));
99 assert(needed_alignment >= 1); // Always need at least a null byte.
100 try contents.appendNTimes(gpa, 0, needed_alignment);
101
102 return @intCast(i);
122103 }
123 return relative;
104
105 contents.shrinkRetainingCapacity(path_start);
106 try contents.appendSlice(gpa, resolved_path);
107
108 const needed_alignment = @alignOf(Manifest.File) - (contents.items.len % @alignOf(Manifest.File));
109 assert(needed_alignment >= 1); // Always need at least a null byte.
110 try contents.appendNTimes(gpa, 0, needed_alignment);
111
112 return 0;
124113}
125114
126115/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6
......@@ -359,7 +348,10 @@ pub const Manifest = struct {
359348 _,
360349 },
361350 /// `have_handle` determines whether this is populated.
362 handle: Io.File,
351 handle: union {
352 file: Io.File,
353 dir: Io.Dir,
354 },
363355
364356 /// Index into `Manifest.input_paths`.
365357 pub const Index = enum(u32) {
......@@ -383,7 +375,7 @@ pub const Manifest = struct {
383375 pub const Flags = packed struct(u8) {
384376 is_directory: bool,
385377 metadata_only: bool,
386 prefix: u6,
378 prefix: PrefixIndex,
387379 };
388380
389381 /// Prefixes path names in encoded directory contents. Starts numbering
......@@ -408,40 +400,49 @@ pub const Manifest = struct {
408400 _,
409401
410402 pub fn get(offset: Offset, contents: []u8) *File {
411 return @ptrCast(@alignCast(contents.items[@backingInt(offset)..][0..@sizeOf(File)]));
403 return @constCast(getConst(offset, contents));
404 }
405
406 pub fn getConst(offset: Offset, contents: []const u8) *const File {
407 return @ptrCast(@alignCast(contents[@backingInt(offset)..][0..@sizeOf(File)]));
412408 }
413409
414410 pub fn getFallible(offset: Offset, contents: []u8) error{InvalidFormat}!*File {
415 if (@backingInt(offset) + @sizeOf(File) >= contents.items.len) return error.InvalidFormat;
411 if (@backingInt(offset) + @sizeOf(File) >= contents.len) return error.InvalidFormat;
416412 return get(offset, contents);
417413 }
418414 };
419415
416 /// Intentionally matches if the files are different only by flags other than prefix.
420417 pub const HashContext = struct {
421 manifest: *const Manifest,
418 contents: []const u8,
422419
423420 pub fn hash(this: @This(), off: Offset) u32 {
424 const file = off.get(this.manifest);
425 return @truncate(std.hash.Wyhash.hash(file.prefix, file.path()));
421 const file_prefix = off.getConst(this.contents).flags.prefix;
422 const file_path = filePath(this.contents, off);
423 return @truncate(std.hash.Wyhash.hash(file_prefix, file_path));
426424 }
427425
428426 pub fn eql(this: @This(), a_off: Offset, b_off: Offset, b_index: usize) bool {
429427 _ = b_index;
430 const a = a_off.get(this.manifest);
431 const b = b_off.get(this.manifest);
432 return a.prefix == b.prefix and mem.eql(u8, a.path(), b.path());
428 const a_prefix = a_off.getConst(this.contents).flags.prefix;
429 const b_prefix = b_off.getConst(this.contents).flags.prefix;
430 if (a_prefix != b_prefix) return false;
431 const a_path = filePath(this.contents, a_off);
432 const b_path = filePath(this.contents, b_off);
433 return mem.eql(u8, a_path, b_path);
433434 }
434435 };
435436
436437 fn setStat(file: *File, m: *Manifest, stat: Stat) Io.Cancelable!void {
437438 file.size = stat.size;
438439 file.inode = stat.inode;
439 file.mtime = stat.mtime;
440 file.mtime = @intCast(stat.mtime.toNanoseconds());
440441
441442 if (try m.isProblematicTimestamp(stat.mtime)) {
442443 // The actual file has an unreliable timestamp; force it to be hashed.
443 file.stat.mtime = 0;
444 file.stat.inode = 0;
444 file.mtime = 0;
445 file.inode = 0;
445446 }
446447 }
447448
......@@ -453,7 +454,7 @@ pub const Manifest = struct {
453454 {
454455 return false;
455456 } else {
456 setStat(file, m, stat);
457 try setStat(file, m, stat);
457458 return true;
458459 }
459460 }
......@@ -489,12 +490,31 @@ pub const Manifest = struct {
489490 size: u64,
490491 inode: Io.File.INode,
491492 mtime: Io.Timestamp,
493
494 pub fn init(other: Io.File.Stat) Stat {
495 return .{
496 .size = other.size,
497 .inode = other.inode,
498 .mtime = other.mtime,
499 };
500 }
492501 };
493502
494503 pub const PathHandle = union(enum) {
495504 file: ?Io.File,
496505 /// If provided, this handle must be opened with iteration capability.
497506 dir: ?Io.Dir,
507
508 pub fn isDirectory(this: @This()) bool {
509 return this == .dir;
510 }
511
512 pub fn have(this: @This()) bool {
513 return switch (this) {
514 .file => |opt_file| opt_file != null,
515 .dir => |opt_dir| opt_dir != null,
516 };
517 }
498518 };
499519
500520 pub const AddInputPathOptions = struct {
......@@ -524,59 +544,73 @@ pub const Manifest = struct {
524544 /// See also:
525545 /// * `addPathPost`
526546 pub fn addInputPath(m: *Manifest, path: Path, options: AddInputPathOptions) Allocator.Error!InputPath.Index {
527 const gpa = m.cache.gpa;
528 try m.files.ensureUnusedCapacity(gpa, 1);
547 const cache = m.cache;
548 const gpa = cache.gpa;
549 try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items });
529550 try m.input_paths.ensureUnusedCapacity(gpa, 1);
530551
531552 const prev_contents_len = m.contents.items.len;
532 const header: *File = @ptrCast(try m.contents.addManyAsSlice(gpa, @sizeOf(File)));
553 const header: *File = @ptrCast(@alignCast(try m.contents.addManyAsSlice(gpa, @sizeOf(File))));
533554 errdefer m.contents.shrinkRetainingCapacity(prev_contents_len);
534555
535556 header.* = .{
536557 .flags = .{
537 .prefix = try m.cache.findAppendPrefixedPath(&m.contents, path),
538 .is_directory = options.is_directory,
558 .prefix = try cache.resolveAppendPath(&m.contents, path),
559 .is_directory = options.handle.isDirectory(),
539560 .metadata_only = options.metadata_only,
540561 },
541562 .size = undefined,
542563 .inode = undefined,
543564 .mtime = undefined,
544565 .digest = undefined,
566 .path_start = .{},
545567 };
546 assert(m.contents.items.len % @alignOf(File) == 0);
568 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
547569
548 const gop = try m.files.getOrPutAssumeCapacityContext(@fromBackingInt(prev_contents_len), .{
549 .manifest = m,
570 const gop = m.files.getOrPutAssumeCapacityContext(@fromBackingInt(@intCast(prev_contents_len)), .{
571 .contents = m.contents.items,
550572 });
573 m.files.lockPointers();
574 defer m.files.unlockPointers();
575
551576 if (gop.found_existing) {
552577 m.contents.shrinkRetainingCapacity(prev_contents_len);
553578 const existing_input_file = &m.input_paths.items[gop.index];
554 if (options.handle) |handle| {
555 existing_input_file.handle = handle;
556 existing_input_file.have_handle = true;
579 switch (options.handle) {
580 .file => |opt_file| if (opt_file) |file| {
581 existing_input_file.handle = .{ .file = file };
582 existing_input_file.have_handle = true;
583 },
584 .dir => |opt_dir| if (opt_dir) |dir| {
585 existing_input_file.handle = .{ .dir = dir };
586 existing_input_file.have_handle = true;
587 },
557588 }
558589 if (options.request_contents) switch (existing_input_file.contents) {
559590 .requested, .not_requested => existing_input_file.contents = .requested,
560591 _ => {},
561592 };
562 const existing_header = &m.files.keys()[gop.index];
593 const existing_header = m.files.keys()[gop.index].get(m.contents.items);
563594 if (options.stat) |stat| {
564595 existing_input_file.have_stat = true;
565596 existing_header.size = stat.size;
566597 existing_header.inode = stat.inode;
567 existing_header.mtime = stat.mtime;
598 existing_header.mtime = @intCast(stat.mtime.toNanoseconds());
568599 }
569600 // If it trips, the same file path has been added to the cache
570601 // manifest both as a directory and as a normal file, making the
571602 // intended caching behavior ambiguous.
572 assert(existing_header.flags.is_directory == options.is_directory);
603 assert(existing_header.flags.is_directory == options.handle.isDirectory());
573604 if (!options.metadata_only)
574605 existing_header.flags.metadata_only = false;
575606 } else {
576607 m.input_paths.appendAssumeCapacity(.{
577608 .request_handle = options.request_handle,
578 .have_handle = options.handle != null,
579 .handle = if (options.handle) |handle| handle else undefined,
609 .have_handle = options.handle.have(),
610 .handle = switch (options.handle) {
611 .file => |opt_file| if (opt_file) |file| .{ .file = file } else undefined,
612 .dir => |opt_dir| if (opt_dir) |dir| .{ .dir = dir } else undefined,
613 },
580614 .contents = if (options.request_contents) .requested else .not_requested,
581615 .have_digest = false,
582616 .have_stat = options.stat != null,
......@@ -585,10 +619,10 @@ pub const Manifest = struct {
585619 if (options.stat) |stat| {
586620 header.size = stat.size;
587621 header.inode = stat.inode;
588 header.mtime = stat.mtime;
622 header.mtime = @intCast(stat.mtime.toNanoseconds());
589623 }
590624 }
591 return @fromBackingInt(gop.index);
625 return @fromBackingInt(@intCast(gop.index));
592626 }
593627
594628 pub fn addInputFileOptional(m: *Manifest, opt_path: ?Path, options: AddInputPathOptions) Allocator.Error!void {
......@@ -759,8 +793,8 @@ pub const Manifest = struct {
759793 if (m.files.count() <= m.input_paths.items.len) return;
760794 const off = m.files.keys()[m.input_paths.items.len];
761795 m.contents.shrinkRetainingCapacity(@backingInt(off));
762 assert(m.contents.items.len % @alignOf(File) == 0);
763 m.files.shrinkRetainingCapacity(m.input_paths.items.len);
796 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
797 m.files.shrinkRetainingCapacityContext(m.input_paths.items.len, .{ .contents = m.contents.items });
764798 }
765799
766800 /// Assumes that `self.hash.hasher` has been updated only with the original digest and that
......@@ -806,11 +840,13 @@ pub const Manifest = struct {
806840
807841 // Guess number of files based on manifest contents len to reduce allocations.
808842 // This is not an upper bound; subsequent insertions may potentially allocate.
809 try m.files.ensureUnusedCapacity(gpa, contents.len / (@sizeOf(File) + 32));
843 try m.files.ensureUnusedCapacityContext(gpa, contents.len / (@sizeOf(File) + 32), .{
844 .contents = contents,
845 });
810846
811847 // This group we would like to cancel as soon as a cache miss is discovered.
812848 const PostResult = union(enum) {
813 checkFile: Check.Status,
849 checkFile: CheckFileError!Check.Status,
814850 };
815851 var post_select_buffer: [10]PostResult = undefined;
816852 var post_select: Io.Select(PostResult) = .init(io, &post_select_buffer);
......@@ -819,12 +855,12 @@ pub const Manifest = struct {
819855
820856 while (off + 1 < contents.len) {
821857 const file_off: File.Offset = @fromBackingInt(off);
822 const file = try file_off.getFallible(m);
858 const file = try file_off.getFallible(contents);
823859 if (file.flags.prefix >= m.cache.prefixes_len) return error.InvalidFormat;
824860 const path = try filePathFallible(contents, file_off);
825861 if (path.len == 0) return error.InvalidFormat;
826862
827 try m.files.put(gpa, file_off, {});
863 try m.files.putContext(gpa, file_off, {}, .{ .contents = contents });
828864
829865 post_select.async(.checkFile, checkFile, .{ m, &c, file_off, path });
830866 post_select_remaining += 1;
......@@ -867,19 +903,19 @@ pub const Manifest = struct {
867903 try input_group.await(io);
868904 return .miss;
869905 },
870 .fail => |diagnostic| {
871 m.diagnostic = diagnostic;
872 return error.CacheCheckFailed;
873 },
874906 },
875907 };
876908 }
877909
878910 try input_group.await(io);
879911 if (c.status == .miss) return .miss;
912 if (m.diagnostic != .none) return error.CacheCheckFailed;
913
914 // Needed due to the length mutation above.
915 const refreshed_contents = m.contents.items;
880916
881917 for (m.files.keys()) |file_off| {
882 m.hash.hasher.update(&file_off.get(m).digest);
918 m.hash.hasher.update(&file_off.get(refreshed_contents).digest);
883919 }
884920
885921 return .hit;
......@@ -896,20 +932,25 @@ pub const Manifest = struct {
896932 if (input_path.have_stat) @panic("TODO");
897933 if (input_path.contents != .not_requested) @panic("TODO");
898934 if (input_path.request_handle) @panic("TODO");
899 switch (try checkFile(m, c, file_off, file_path)) {
935 if (checkFile(m, c, file_off, file_path)) |status| switch (status) {
900936 .hit => return,
901937 .miss => @atomicStore(Check.Status, &c.status, .miss, .unordered),
938 } else |err| switch (err) {
939 error.CacheCheckFailed => assert(m.diagnostic != .none),
940 else => |e| return e,
902941 }
903942 }
904943
944 const CheckFileError = error{ Canceled, CacheCheckFailed };
945
905946 /// Runs concurrently with other `checkFile`.
906947 fn checkFile(
907948 m: *Manifest,
908949 c: *Check,
909950 file_off: File.Offset,
910951 file_path: [:0]const u8,
911 ) error{ Canceled, CacheCheckFailed }!Check.Status {
912 const file = file_off.get(m);
952 ) CheckFileError!Check.Status {
953 const file = file_off.get(m.contents.items);
913954 const cache = m.cache;
914955 const gpa = cache.gpa;
915956 const io = cache.io;
......@@ -928,7 +969,7 @@ pub const Manifest = struct {
928969 const actual_is_directory = actual_stat.kind == .directory;
929970 if (actual_is_directory != file.flags.is_directory) return .miss;
930971
931 if (try file.setStatChanged(m, actual_stat)) return .miss;
972 if (try file.setStatChanged(m, .init(actual_stat))) return .miss;
932973
933974 return .hit;
934975 }
......@@ -954,7 +995,7 @@ pub const Manifest = struct {
954995 .err = e,
955996 } }),
956997 };
957 if (try file.setStatChanged(m, actual_stat)) {
998 if (try file.setStatChanged(m, .init(actual_stat))) {
958999 const prev_digest: BinDigest = file.digest;
9591000 var contents: std.ArrayList(u8) = .empty;
9601001 defer contents.deinit(gpa);
......@@ -989,7 +1030,7 @@ pub const Manifest = struct {
9891030 } }),
9901031 };
9911032
992 if (try file.setStatChanged(m, actual_stat)) {
1033 if (try file.setStatChanged(m, .init(actual_stat))) {
9931034 const prev_digest: BinDigest = file.digest;
9941035 hashFile(io, opened_file, &file.digest) catch |err| switch (err) {
9951036 error.Canceled => |e| return e,
......@@ -1015,8 +1056,9 @@ pub const Manifest = struct {
10151056 man.hash.hasher = hasher_init;
10161057 man.hash.hasher.update(bin_digest);
10171058 man.shrinkFilesToInput();
1059 const contents = man.contents.items;
10181060 for (man.files.keys()) |off| {
1019 const file = off.get(man);
1061 const file = off.get(contents);
10201062 man.hash.hasher.update(&file.digest);
10211063 }
10221064 }
......@@ -1065,6 +1107,10 @@ pub const Manifest = struct {
10651107 }
10661108
10671109 pub const AddPathPostOptions = struct {
1110 path: union(enum) {
1111 unresolved: Path,
1112 prefixed: PrefixedPath,
1113 },
10681114 handle: PathHandle = .{ .file = null },
10691115 stat: ?Stat = null,
10701116 /// If it is a directory, there is a special encoding required for contents, which
......@@ -1073,29 +1119,30 @@ pub const Manifest = struct {
10731119 metadata_only: bool = false,
10741120 };
10751121
1076 pub const AddPathPostError = Io.Cancelable || Allocator.Error;
1077
10781122 /// Add a file as a dependency of process being cached, after cache miss
10791123 /// occurs.
10801124 ///
10811125 /// See also:
10821126 /// * `addInputPath`
1083 pub fn addPathPost(m: *Manifest, path: Path, options: AddPathPostOptions) AddPathPostError!void {
1127 pub fn addPathPost(m: *Manifest, options: AddPathPostOptions) !void {
10841128 assert(m.manifest_file != null);
10851129 const cache = m.cache;
10861130 const gpa = cache.gpa;
10871131 const io = cache.io;
10881132 const is_directory = options.handle == .dir;
10891133
1090 try m.files.ensureUnusedCapacity(gpa, 1);
1134 try m.files.ensureUnusedCapacityContext(gpa, 1, .{ .contents = m.contents.items });
10911135
1092 const prev_contents_len = m.contents.items.len;
1093 const new_header: *File = @ptrCast(try m.contents.addManyAsSlice(gpa, @sizeOf(File)));
1094 errdefer m.contents.shrinkRetainingCapacity(prev_contents_len);
1136 const new_file_offset: File.Offset = @fromBackingInt(@intCast(m.contents.items.len));
1137 const new_header: *File = @ptrCast(@alignCast(try m.contents.addManyAsSlice(gpa, @sizeOf(File))));
1138 errdefer m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
10951139
10961140 new_header.* = .{
10971141 .flags = .{
1098 .prefix = try cache.findAppendPrefixedPath(&m.contents, path),
1142 .prefix = switch (options.path) {
1143 .unresolved => |unresolved| try cache.resolveAppendPath(&m.contents, unresolved),
1144 .prefixed => |prefixed| try cache.appendPrefixedPath(&m.contents, prefixed),
1145 },
10991146 .is_directory = is_directory,
11001147 .metadata_only = options.metadata_only,
11011148 },
......@@ -1103,31 +1150,32 @@ pub const Manifest = struct {
11031150 .inode = undefined,
11041151 .mtime = undefined,
11051152 .digest = @splat(0),
1153 .path_start = .{},
11061154 };
1107 assert(m.contents.items.len % @alignOf(File) == 0);
1155 assert(mem.isAligned(m.contents.items.len, @alignOf(File)));
11081156
1109 const gop = m.files.getOrPutAssumeCapacity(@fromBackingInt(prev_contents_len), .{
1110 .manifest = m,
1157 const gop = m.files.getOrPutAssumeCapacityContext(new_file_offset, .{
1158 .contents = m.contents.items,
11111159 });
11121160 m.files.lockPointers();
11131161 defer m.files.unlockPointers();
11141162
1115 const header = if (gop.found_existing) h: {
1116 m.contents.shrinkRetainingCapacity(prev_contents_len);
1163 const header, const file_offset = if (gop.found_existing) h: {
1164 m.contents.shrinkRetainingCapacity(@backingInt(new_file_offset));
11171165 const existing_off = gop.key_ptr.*;
1118 const header = existing_off.get(m);
1166 const header = existing_off.get(m.contents.items);
11191167 // If it trips, the same file path has been added to the cache
11201168 // manifest both as a directory and as a normal file, making the
11211169 // intended caching behavior ambiguous.
11221170 assert(header.flags.is_directory == is_directory);
11231171 if (!options.metadata_only)
11241172 header.flags.metadata_only = false;
1125 break :h header;
1126 } else new_header;
1173 break :h .{ header, existing_off };
1174 } else .{ new_header, new_file_offset };
11271175
11281176 if (options.stat) |stat| {
11291177 try header.setStat(m, stat);
1130 if (header.metadata_only) {
1178 if (header.flags.metadata_only) {
11311179 return;
11321180 } else if (options.contents) |contents| {
11331181 var hasher = hasher_init;
......@@ -1138,27 +1186,31 @@ pub const Manifest = struct {
11381186 }
11391187
11401188 const need_stat = options.stat == null;
1189 const metadata_only = header.flags.metadata_only;
1190 const prefix = header.flags.prefix;
11411191
11421192 switch (options.handle) {
11431193 .dir => |opt_handle| if (opt_handle) |handle| {
1144 try populateDirectory(m, header, need_stat, handle, options.contents, header.metadata_only);
1194 try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only);
11451195 } else {
1146 const dir = cache.prefixes()[header.flags.prefix].handle;
1147 const handle = try dir.openDir(io, header.path(), .{
1196 const dir = cache.prefixes()[prefix].handle;
1197 const sub_path = filePath(m.contents.items, file_offset);
1198 const handle = try dir.openDir(io, sub_path, .{
11481199 .access_sub_paths = false,
11491200 .iterate = true,
11501201 });
11511202 defer handle.close(io);
1152 try populateDirectory(m, header, need_stat, handle, options.contents, header.metadata_only);
1203 try populateDirectory(m, header, need_stat, handle, options.contents, metadata_only);
11531204 },
11541205
11551206 .file => |opt_handle| if (opt_handle) |handle| {
1156 try populateFile(m, header, need_stat, handle, options.contents, header.metadata_only);
1207 try populateFile(m, header, need_stat, handle, options.contents, metadata_only);
11571208 } else {
1158 const dir = cache.prefixes()[header.flags.prefix].handle;
1159 const handle = try dir.openFile(io, header.path(), .{ .mode = .read_only });
1209 const dir = cache.prefixes()[prefix].handle;
1210 const sub_path = filePath(m.contents.items, file_offset);
1211 const handle = try dir.openFile(io, sub_path, .{ .mode = .read_only });
11601212 defer handle.close(io);
1161 try populateFile(m, header, need_stat, handle, options.contents, header.metadata_only);
1213 try populateFile(m, header, need_stat, handle, options.contents, metadata_only);
11621214 },
11631215 }
11641216 }
......@@ -1175,7 +1227,7 @@ pub const Manifest = struct {
11751227
11761228 if (need_stat) {
11771229 const stat = try handle.stat(io);
1178 try file.setStat(m, stat);
1230 try file.setStat(m, .init(stat));
11791231 }
11801232 if (metadata_only) return;
11811233 if (contents) |bytes| {
......@@ -1201,7 +1253,7 @@ pub const Manifest = struct {
12011253
12021254 if (need_stat) {
12031255 const stat = try handle.stat(io);
1204 try file.setStat(m, stat);
1256 try file.setStat(m, .init(stat));
12051257 }
12061258 if (metadata_only) return;
12071259 if (contents) |bytes| {
......@@ -1238,28 +1290,26 @@ pub const Manifest = struct {
12381290 defer resolve_buf.deinit(gpa);
12391291
12401292 var it: DepTokenizer = .{ .bytes = dep_file_contents };
1241 while (it.next()) |token| {
1242 switch (token) {
1243 // We don't care about targets, we only want the prereqs
1244 // Clang is invoked in single-source mode but other programs may not
1245 .target, .target_must_resolve => {},
1246 .prereq => |file_path| if (self.manifest_file == null) {
1247 _ = try self.addInputPath(.initCwd(file_path), .{});
1248 } else try self.addPathPost(file_path),
1249 .prereq_must_resolve => {
1250 resolve_buf.clearRetainingCapacity();
1251 try token.resolve(gpa, &resolve_buf);
1252 if (self.manifest_file == null) {
1253 _ = try self.addInputPath(.initCwd(resolve_buf.items), .{});
1254 } else try self.addPathPost(resolve_buf.items);
1255 },
1256 else => |err| {
1257 try err.printError(gpa, &error_buf);
1258 log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items });
1259 return error.InvalidDepFile;
1260 },
1261 }
1262 }
1293 while (it.next()) |token| switch (token) {
1294 // We don't care about targets, we only want the prereqs
1295 // Clang is invoked in single-source mode but other programs may not
1296 .target, .target_must_resolve => {},
1297 .prereq => |file_path| if (self.manifest_file == null) {
1298 _ = try self.addInputPath(.initCwd(file_path), .{});
1299 } else try self.addPathPost(.{ .path = .{ .unresolved = .initCwd(file_path) } }),
1300 .prereq_must_resolve => {
1301 resolve_buf.clearRetainingCapacity();
1302 try token.resolve(gpa, &resolve_buf);
1303 if (self.manifest_file == null) {
1304 _ = try self.addInputPath(.initCwd(resolve_buf.items), .{});
1305 } else try self.addPathPost(.{ .path = .{ .unresolved = .initCwd(resolve_buf.items) } });
1306 },
1307 else => |err| {
1308 try err.printError(gpa, &error_buf);
1309 log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items });
1310 return error.InvalidDepFile;
1311 },
1312 };
12631313 }
12641314
12651315 /// Returns a binary hash of the inputs.
......@@ -1368,6 +1418,10 @@ pub const Manifest = struct {
13681418 pub fn takeFiles(m: *Manifest) SelfContainedFiles {
13691419 defer m.files = .empty;
13701420 defer m.contents = .empty;
1421 return borrowFiles(m);
1422 }
1423
1424 pub fn borrowFiles(m: *const Manifest) SelfContainedFiles {
13711425 return .{
13721426 .files = m.files,
13731427 .contents = m.contents,
......@@ -1488,7 +1542,7 @@ pub const Manifest = struct {
14881542 while (true) {
14891543 const entries = entry_buffer[0..try reader.read(io, &entry_buffer)];
14901544 for (try entries_list.addManyAsSlice(gpa, entries.len), entries) |*off, entry| {
1491 off.* = contents.items.len;
1545 off.* = @intCast(contents.items.len);
14921546 // As an optimization, make the reservation also count the duplication
14931547 // of the contents buffer that will be required after sorting.
14941548 try contents.ensureUnusedCapacity(gpa, (contents.items.len + entry.name.len + 2 - contents_start) * 2);
lib/std/zig.zig+6-6
......@@ -1837,12 +1837,12 @@ pub fn buildExeSubprocess(
18371837 var it = mem.splitScalar(u8, body, 0);
18381838 while (it.next()) |prefixed_path| {
18391839 const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));
1840 const sub_path = try gpa.dupe(u8, prefixed_path[1..]);
1841 var keep = false;
1842 defer if (!keep) gpa.free(sub_path);
1843 keep = man.addPrefixedPathPost(.{
1844 .prefix = @backingInt(prefix),
1845 .sub_path = sub_path,
1840 const sub_path = prefixed_path[1..];
1841 man.addPathPost(.{
1842 .path = .{ .prefixed = .{
1843 .prefix = @intCast(@backingInt(prefix)),
1844 .sub_path = sub_path,
1845 } },
18461846 }) catch |err| switch (err) {
18471847 error.Canceled, error.OutOfMemory => |e| return e,
18481848 else => |e| {