diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-01-25 04:48:16 +0000 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2025-01-25 06:07:08 +0000 |
| commit | f47b8de2ad706616b648c52f5036102cb804e65d (patch) | |
| tree | 20246773622e31440107d87455d5a01ac1cd8dcf /src/Zcu/PerThread.zig | |
| parent | 5202c977d95bf65775e733264b7d37ed940a2997 (diff) | |
| download | zig-f47b8de2ad706616b648c52f5036102cb804e65d.tar.gz zig-f47b8de2ad706616b648c52f5036102cb804e65d.zip | |
incremental: handle `@embedFile`
Uses of `@embedFile` register dependencies on the corresponding
`Zcu.EmbedFile`. At the start of every update, we iterate all embedded
files and update them if necessary, and invalidate the dependencies if
they changed.
In order to properly integrate with the lazy analysis model, failed
embed files are now reported by the `AnalUnit` which actually used
`@embedFile`; the filesystem error is stored in the `Zcu.EmbedFile`.
An incremental test is added covering incremental updates to embedded
files, and I have verified locally that dependency invalidation is
working correctly.
Diffstat (limited to 'src/Zcu/PerThread.zig')
| -rw-r--r-- | src/Zcu/PerThread.zig | 233 |
1 files changed, 143 insertions, 90 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index c6a16ee00c..9e21b655d1 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2117,32 +2117,32 @@ pub fn embedFile( pt: Zcu.PerThread, cur_file: *Zcu.File, import_string: []const u8, - src_loc: Zcu.LazySrcLoc, -) !InternPool.Index { +) error{ + OutOfMemory, + ImportOutsideModulePath, + CurrentWorkingDirectoryUnlinked, +}!Zcu.EmbedFile.Index { const zcu = pt.zcu; const gpa = zcu.gpa; - if (cur_file.mod.deps.get(import_string)) |pkg| { + if (cur_file.mod.deps.get(import_string)) |mod| { const resolved_path = try std.fs.path.resolve(gpa, &.{ - pkg.root.root_dir.path orelse ".", - pkg.root.sub_path, - pkg.root_src_path, + mod.root.root_dir.path orelse ".", + mod.root.sub_path, + mod.root_src_path, }); - var keep_resolved_path = false; - defer if (!keep_resolved_path) gpa.free(resolved_path); + errdefer gpa.free(resolved_path); const gop = try zcu.embed_table.getOrPut(gpa, resolved_path); - errdefer { - assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); - keep_resolved_path = false; - } - if (gop.found_existing) return gop.value_ptr.*.val; - keep_resolved_path = true; + errdefer assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); - const sub_file_path = try gpa.dupe(u8, pkg.root_src_path); - errdefer gpa.free(sub_file_path); + if (gop.found_existing) { + gpa.free(resolved_path); // we're not using this key + return @enumFromInt(gop.index); + } - return pt.newEmbedFile(pkg, sub_file_path, resolved_path, gop.value_ptr, src_loc); + gop.value_ptr.* = try pt.newEmbedFile(mod, mod.root_src_path, resolved_path); + return @enumFromInt(gop.index); } // The resolved path is used as the key in the table, to detect if a file @@ -2154,17 +2154,15 @@ pub fn embedFile( "..", import_string, }); - - var keep_resolved_path = false; - defer if (!keep_resolved_path) gpa.free(resolved_path); + errdefer gpa.free(resolved_path); const gop = try zcu.embed_table.getOrPut(gpa, resolved_path); - errdefer { - assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); - keep_resolved_path = false; + errdefer assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); + + if (gop.found_existing) { + gpa.free(resolved_path); // we're not using this key + return @enumFromInt(gop.index); } - if (gop.found_existing) return gop.value_ptr.*.val; - keep_resolved_path = true; const resolved_root_path = try std.fs.path.resolve(gpa, &.{ cur_file.mod.root.root_dir.path orelse ".", @@ -2172,101 +2170,156 @@ pub fn embedFile( }); defer gpa.free(resolved_root_path); - const sub_file_path = p: { - const relative = try std.fs.path.relative(gpa, resolved_root_path, resolved_path); - errdefer gpa.free(relative); - - if (!isUpDir(relative) and !std.fs.path.isAbsolute(relative)) { - break :p relative; - } - return error.ImportOutsideModulePath; + const sub_file_path = std.fs.path.relative(gpa, resolved_root_path, resolved_path) catch |err| switch (err) { + error.Unexpected => unreachable, + else => |e| return e, }; defer gpa.free(sub_file_path); - return pt.newEmbedFile(cur_file.mod, sub_file_path, resolved_path, gop.value_ptr, src_loc); + if (isUpDir(sub_file_path) or std.fs.path.isAbsolute(sub_file_path)) { + return error.ImportOutsideModulePath; + } + + gop.value_ptr.* = try pt.newEmbedFile(cur_file.mod, sub_file_path, resolved_path); + return @enumFromInt(gop.index); } -/// https://github.com/ziglang/zig/issues/14307 -fn newEmbedFile( +pub fn updateEmbedFile( pt: Zcu.PerThread, - pkg: *Module, - sub_file_path: []const u8, - resolved_path: []const u8, - result: **Zcu.EmbedFile, - src_loc: Zcu.LazySrcLoc, -) !InternPool.Index { + ef: *Zcu.EmbedFile, + /// If not `null`, the interned file data is stored here, if it was loaded. + /// `newEmbedFile` uses this to add the file to the `whole` cache manifest. + ip_str_out: ?*?InternPool.String, +) Allocator.Error!void { + pt.updateEmbedFileInner(ef, ip_str_out) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| { + ef.val = .none; + ef.err = e; + ef.stat = undefined; + }, + }; +} + +fn updateEmbedFileInner( + pt: Zcu.PerThread, + ef: *Zcu.EmbedFile, + ip_str_out: ?*?InternPool.String, +) !void { + const tid = pt.tid; const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const new_file = try gpa.create(Zcu.EmbedFile); - errdefer gpa.destroy(new_file); - - var file = try pkg.root.openFile(sub_file_path, .{}); + var file = try ef.owner.root.openFile(ef.sub_file_path.toSlice(ip), .{}); defer file.close(); - const actual_stat = try file.stat(); - const stat: Cache.File.Stat = .{ - .size = actual_stat.size, - .inode = actual_stat.inode, - .mtime = actual_stat.mtime, - }; - const size = std.math.cast(usize, actual_stat.size) orelse return error.Overflow; - - const strings = ip.getLocal(pt.tid).getMutableStrings(gpa); - const bytes = try strings.addManyAsSlice(try std.math.add(usize, size, 1)); - const actual_read = try file.readAll(bytes[0][0..size]); - if (actual_read != size) return error.UnexpectedEndOfFile; - bytes[0][size] = 0; + const stat: Cache.File.Stat = .fromFs(try file.stat()); - const comp = zcu.comp; - switch (comp.cache_use) { - .whole => |whole| if (whole.cache_manifest) |man| { - const copied_resolved_path = try gpa.dupe(u8, resolved_path); - errdefer gpa.free(copied_resolved_path); - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - try man.addFilePostContents(copied_resolved_path, bytes[0][0..size], stat); - }, - .incremental => {}, + if (ef.val != .none) { + const old_stat = ef.stat; + const unchanged_metadata = + stat.size == old_stat.size and + stat.mtime == old_stat.mtime and + stat.inode == old_stat.inode; + if (unchanged_metadata) return; } - const array_ty = try pt.intern(.{ .array_type = .{ + const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; + const size_plus_one = std.math.add(usize, size, 1) catch return error.FileTooBig; + + // The loaded bytes of the file, including a sentinel 0 byte. + const ip_str: InternPool.String = str: { + const strings = ip.getLocal(tid).getMutableStrings(gpa); + const old_len = strings.mutate.len; + errdefer strings.shrinkRetainingCapacity(old_len); + const bytes = (try strings.addManyAsSlice(size_plus_one))[0]; + const actual_read = try file.readAll(bytes[0..size]); + if (actual_read != size) return error.UnexpectedEof; + bytes[size] = 0; + break :str try ip.getOrPutTrailingString(gpa, tid, @intCast(bytes.len), .maybe_embedded_nulls); + }; + if (ip_str_out) |p| p.* = ip_str; + + const array_ty = try pt.arrayType(.{ .len = size, .sentinel = .zero_u8, .child = .u8_type, - } }); + }); + const ptr_ty = try pt.singleConstPtrType(array_ty); + const array_val = try pt.intern(.{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, @intCast(bytes[0].len), .maybe_embedded_nulls) }, + .ty = array_ty.toIntern(), + .storage = .{ .bytes = ip_str }, } }); - - const ptr_ty = (try pt.ptrType(.{ - .child = array_ty, - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - })).toIntern(); const ptr_val = try pt.intern(.{ .ptr = .{ - .ty = ptr_ty, + .ty = ptr_ty.toIntern(), .base_addr = .{ .uav = .{ .val = array_val, - .orig_ty = ptr_ty, + .orig_ty = ptr_ty.toIntern(), } }, .byte_offset = 0, } }); - result.* = new_file; + ef.val = ptr_val; + ef.err = null; + ef.stat = stat; +} + +fn newEmbedFile( + pt: Zcu.PerThread, + mod: *Module, + /// The path of the file to embed relative to the root of `mod`. + sub_file_path: []const u8, + /// The resolved path of the file to embed. + resolved_path: []const u8, +) !*Zcu.EmbedFile { + const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + if (comp.file_system_inputs) |fsi| + try comp.appendFileSystemInput(fsi, mod.root, sub_file_path); + + const new_file = try gpa.create(Zcu.EmbedFile); + errdefer gpa.destroy(new_file); + new_file.* = .{ + .owner = mod, .sub_file_path = try ip.getOrPutString(gpa, pt.tid, sub_file_path, .no_embedded_nulls), - .owner = pkg, - .stat = stat, - .val = ptr_val, - .src_loc = src_loc, + .val = .none, + .err = null, + .stat = undefined, }; - return ptr_val; + + var opt_ip_str: ?InternPool.String = null; + try pt.updateEmbedFile(new_file, &opt_ip_str); + + // Add the file contents to the `whole` cache manifest if necessary. + cache: { + const whole = switch (zcu.comp.cache_use) { + .whole => |whole| whole, + .incremental => break :cache, + }; + const man = whole.cache_manifest orelse break :cache; + const ip_str = opt_ip_str orelse break :cache; + + const copied_resolved_path = try gpa.dupe(u8, resolved_path); + errdefer gpa.free(copied_resolved_path); + + const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu); + + whole.cache_manifest_mutex.lock(); + defer whole.cache_manifest_mutex.unlock(); + + man.addFilePostContents(copied_resolved_path, ip_str.toSlice(array_len, ip), new_file.stat) catch |err| switch (err) { + error.Unexpected => unreachable, + else => |e| return e, + }; + } + + return new_file; } pub fn scanNamespace( |
