Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incremental: handle @embedFile #22602

Merged
merged 4 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion lib/std/fs/path.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const debug = std.debug;
const assert = debug.assert;
const testing = std.testing;
const mem = std.mem;
const fmt = std.fmt;
const ascii = std.ascii;
const Allocator = mem.Allocator;
const math = std.math;
Expand Down Expand Up @@ -147,6 +146,36 @@ pub fn joinZ(allocator: Allocator, paths: []const []const u8) ![:0]u8 {
return out[0 .. out.len - 1 :0];
}

pub fn fmtJoin(paths: []const []const u8) std.fmt.Formatter(formatJoin) {
return .{ .data = paths };
}

fn formatJoin(paths: []const []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, w: anytype) !void {
_ = fmt;
_ = options;

const first_path_idx = for (paths, 0..) |p, idx| {
if (p.len != 0) break idx;
} else return;

try w.writeAll(paths[first_path_idx]); // first component
var prev_path = paths[first_path_idx];
for (paths[first_path_idx + 1 ..]) |this_path| {
if (this_path.len == 0) continue; // skip empty components
const prev_sep = isSep(prev_path[prev_path.len - 1]);
const this_sep = isSep(this_path[0]);
if (!prev_sep and !this_sep) {
try w.writeByte(sep);
}
if (prev_sep and this_sep) {
try w.writeAll(this_path[1..]); // skip redundant separator
} else {
try w.writeAll(this_path);
}
prev_path = this_path;
}
}

fn testJoinMaybeZUefi(paths: []const []const u8, expected: []const u8, zero: bool) !void {
const uefiIsSep = struct {
fn isSep(byte: u8) bool {
Expand Down
83 changes: 23 additions & 60 deletions src/Compilation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,6 @@ win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.fifo.Linea
/// since the last compilation, as well as scan for `@import` and queue up
/// additional jobs corresponding to those new files.
astgen_work_queue: std.fifo.LinearFifo(Zcu.File.Index, .Dynamic),
/// These jobs are to inspect the file system stat() and if the embedded file has changed
/// on disk, mark the corresponding Decl outdated and queue up an `analyze_decl`
/// task for it.
embed_file_work_queue: std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic),

/// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator.
/// This data is accessed by multiple threads and is protected by `mutex`.
Expand Down Expand Up @@ -1465,7 +1461,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
.win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{},
.astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa),
.embed_file_work_queue = std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic).init(gpa),
.c_source_files = options.c_source_files,
.rc_source_files = options.rc_source_files,
.cache_parent = cache,
Expand Down Expand Up @@ -1932,7 +1927,6 @@ pub fn destroy(comp: *Compilation) void {
comp.c_object_work_queue.deinit();
comp.win32_resource_work_queue.deinit();
comp.astgen_work_queue.deinit();
comp.embed_file_work_queue.deinit();

comp.windows_libs.deinit(gpa);

Expand Down Expand Up @@ -2247,11 +2241,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
}
}

// Put a work item in for checking if any files used with `@embedFile` changed.
try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count());
for (zcu.embed_table.values()) |embed_file| {
comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file);
}
if (comp.file_system_inputs) |fsi| {
const ip = &zcu.intern_pool;
for (zcu.embed_table.values()) |embed_file| {
Expand Down Expand Up @@ -3235,9 +3224,6 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
try addZirErrorMessages(&bundle, file);
}
}
for (zcu.failed_embed_files.values()) |error_msg| {
try addModuleErrorMsg(zcu, &bundle, error_msg.*);
}
var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: {
const SortOrder = struct {
zcu: *Zcu,
Expand Down Expand Up @@ -3812,9 +3798,10 @@ fn performAllTheWorkInner(
}
}

while (comp.embed_file_work_queue.readItem()) |embed_file| {
comp.thread_pool.spawnWg(&astgen_wait_group, workerCheckEmbedFile, .{
comp, embed_file,
for (0.., zcu.embed_table.values()) |ef_index_usize, ef| {
const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize);
comp.thread_pool.spawnWgId(&astgen_wait_group, workerCheckEmbedFile, .{
comp, ef_index, ef,
});
}
}
Expand Down Expand Up @@ -4377,33 +4364,33 @@ fn workerUpdateBuiltinZigFile(
};
}

fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Zcu.EmbedFile) void {
comp.detectEmbedFileUpdate(embed_file) catch |err| {
comp.reportRetryableEmbedFileError(embed_file, err) catch |oom| switch (oom) {
// Swallowing this error is OK because it's implied to be OOM when
// there is a missing `failed_embed_files` error message.
error.OutOfMemory => {},
};
return;
fn workerCheckEmbedFile(tid: usize, comp: *Compilation, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) void {
comp.detectEmbedFileUpdate(@enumFromInt(tid), ef_index, ef) catch |err| switch (err) {
error.OutOfMemory => {
comp.mutex.lock();
defer comp.mutex.unlock();
comp.setAllocFailure();
},
};
}

fn detectEmbedFileUpdate(comp: *Compilation, embed_file: *Zcu.EmbedFile) !void {
fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) !void {
const zcu = comp.zcu.?;
const ip = &zcu.intern_pool;
var file = try embed_file.owner.root.openFile(embed_file.sub_file_path.toSlice(ip), .{});
defer file.close();
const pt: Zcu.PerThread = .activate(zcu, tid);
defer pt.deactivate();

const old_val = ef.val;
const old_err = ef.err;

const stat = try file.stat();
try pt.updateEmbedFile(ef, null);

const unchanged_metadata =
stat.size == embed_file.stat.size and
stat.mtime == embed_file.stat.mtime and
stat.inode == embed_file.stat.inode;
if (ef.val != .none and ef.val == old_val) return; // success, value unchanged
if (ef.val == .none and old_val == .none and ef.err == old_err) return; // failure, error unchanged

if (unchanged_metadata) return;
comp.mutex.lock();
defer comp.mutex.unlock();

@panic("TODO: handle embed file incremental update");
try zcu.markDependeeOutdated(.not_marked_po, .{ .embed_file = ef_index });
}

pub fn obtainCObjectCacheManifest(
Expand Down Expand Up @@ -4802,30 +4789,6 @@ fn reportRetryableWin32ResourceError(
}
}

fn reportRetryableEmbedFileError(
comp: *Compilation,
embed_file: *Zcu.EmbedFile,
err: anyerror,
) error{OutOfMemory}!void {
const zcu = comp.zcu.?;
const gpa = zcu.gpa;
const src_loc = embed_file.src_loc;
const ip = &zcu.intern_pool;
const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{
embed_file.owner.root,
embed_file.sub_file_path.toSlice(ip),
@errorName(err),
});

errdefer err_msg.destroy(gpa);

{
comp.mutex.lock();
defer comp.mutex.unlock();
try zcu.failed_embed_files.putNoClobber(gpa, embed_file, err_msg);
}
}

fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Progress.Node) !void {
if (comp.config.c_frontend == .aro) {
return comp.failCObj(c_object, "aro does not support compiling C objects yet", .{});
Expand Down
9 changes: 9 additions & 0 deletions src/InternPool.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ nav_ty_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index),
/// * a container type requiring resolution (invalidated when the type must be recreated at a new index)
/// Value is index into `dep_entries` of the first dependency on this interned value.
interned_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index),
/// Dependencies on an embedded file.
/// Introduced by `@embedFile`; invalidated when the file changes.
/// Value is index into `dep_entries` of the first dependency on this `Zcu.EmbedFile`.
embed_file_deps: std.AutoArrayHashMapUnmanaged(Zcu.EmbedFile.Index, DepEntry.Index),
/// Dependencies on the full set of names in a ZIR namespace.
/// Key refers to a `struct_decl`, `union_decl`, etc.
/// Value is index into `dep_entries` of the first dependency on this namespace.
Expand Down Expand Up @@ -90,6 +94,7 @@ pub const empty: InternPool = .{
.nav_val_deps = .empty,
.nav_ty_deps = .empty,
.interned_deps = .empty,
.embed_file_deps = .empty,
.namespace_deps = .empty,
.namespace_name_deps = .empty,
.memoized_state_main_deps = .none,
Expand Down Expand Up @@ -824,6 +829,7 @@ pub const Dependee = union(enum) {
nav_val: Nav.Index,
nav_ty: Nav.Index,
interned: Index,
embed_file: Zcu.EmbedFile.Index,
namespace: TrackedInst.Index,
namespace_name: NamespaceNameKey,
memoized_state: MemoizedStateStage,
Expand Down Expand Up @@ -875,6 +881,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
.nav_val => |x| ip.nav_val_deps.get(x),
.nav_ty => |x| ip.nav_ty_deps.get(x),
.interned => |x| ip.interned_deps.get(x),
.embed_file => |x| ip.embed_file_deps.get(x),
.namespace => |x| ip.namespace_deps.get(x),
.namespace_name => |x| ip.namespace_name_deps.get(x),
.memoized_state => |stage| switch (stage) {
Expand Down Expand Up @@ -945,6 +952,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
.nav_val => ip.nav_val_deps,
.nav_ty => ip.nav_ty_deps,
.interned => ip.interned_deps,
.embed_file => ip.embed_file_deps,
.namespace => ip.namespace_deps,
.namespace_name => ip.namespace_name_deps,
.memoized_state => comptime unreachable,
Expand Down Expand Up @@ -6612,6 +6620,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
ip.nav_val_deps.deinit(gpa);
ip.nav_ty_deps.deinit(gpa);
ip.interned_deps.deinit(gpa);
ip.embed_file_deps.deinit(gpa);
ip.namespace_deps.deinit(gpa);
ip.namespace_name_deps.deinit(gpa);

Expand Down
20 changes: 14 additions & 6 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13949,6 +13949,8 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
defer tracy.end();

const pt = sema.pt;
const zcu = pt.zcu;

const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const name = try sema.resolveConstString(block, operand_src, inst_data.operand, .{ .simple = .operand_embedFile });
Expand All @@ -13957,18 +13959,24 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
return sema.fail(block, operand_src, "file path name cannot be empty", .{});
}

const val = pt.embedFile(block.getFileScope(pt.zcu), name, operand_src) catch |err| switch (err) {
const ef_idx = pt.embedFile(block.getFileScope(zcu), name) catch |err| switch (err) {
error.ImportOutsideModulePath => {
return sema.fail(block, operand_src, "embed of file outside package path: '{s}'", .{name});
},
else => {
// TODO: these errors are file system errors; make sure an update() will
// retry this and not cache the file system error, which may be transient.
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(err) });
error.CurrentWorkingDirectoryUnlinked => {
// TODO: this should be some kind of retryable failure, in case the cwd is put back
return sema.fail(block, operand_src, "unable to resolve '{s}': working directory has been unlinked", .{name});
},
error.OutOfMemory => |e| return e,
};
try sema.declareDependency(.{ .embed_file = ef_idx });

const result = ef_idx.get(zcu);
if (result.val == .none) {
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(result.err.?) });
}

return Air.internedToRef(val);
return Air.internedToRef(result.val);
}

fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
Expand Down
33 changes: 22 additions & 11 deletions src/Zcu.zig
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ compile_log_sources: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
/// Using a map here for consistency with the other fields here.
/// The ErrorMsg memory is owned by the `File`, using Module's general purpose allocator.
failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .empty,
/// The ErrorMsg memory is owned by the `EmbedFile`, using Module's general purpose allocator.
failed_embed_files: std.AutoArrayHashMapUnmanaged(*EmbedFile, *ErrorMsg) = .empty,
failed_exports: std.AutoArrayHashMapUnmanaged(Export.Index, *ErrorMsg) = .empty,
/// If analysis failed due to a cimport error, the corresponding Clang errors
/// are stored here.
Expand Down Expand Up @@ -893,13 +891,23 @@ pub const File = struct {
};

pub const EmbedFile = struct {
/// Relative to the owning module's root directory.
sub_file_path: InternPool.NullTerminatedString,
/// Module that this file is a part of, managed externally.
owner: *Package.Module,
stat: Cache.File.Stat,
/// Relative to the owning module's root directory.
sub_file_path: InternPool.NullTerminatedString,

/// `.none` means the file was not loaded, so `stat` is undefined.
val: InternPool.Index,
src_loc: LazySrcLoc,
/// If this is `null` and `val` is `.none`, the file has never been loaded.
err: ?(std.fs.File.OpenError || std.fs.File.StatError || std.fs.File.ReadError || error{UnexpectedEof}),
stat: Cache.File.Stat,

pub const Index = enum(u32) {
_,
pub fn get(idx: Index, zcu: *const Zcu) *EmbedFile {
return zcu.embed_table.values()[@intFromEnum(idx)];
}
};
};

/// This struct holds data necessary to construct API-facing `AllErrors.Message`.
Expand Down Expand Up @@ -2459,11 +2467,6 @@ pub fn deinit(zcu: *Zcu) void {
}
zcu.failed_files.deinit(gpa);

for (zcu.failed_embed_files.values()) |msg| {
msg.destroy(gpa);
}
zcu.failed_embed_files.deinit(gpa);

for (zcu.failed_exports.values()) |value| {
value.destroy(gpa);
}
Expand Down Expand Up @@ -3882,6 +3885,14 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com
.func => |f| return writer.print("ies('{}')", .{ip.getNav(f.owner_nav).fqn.fmt(ip)}),
else => unreachable,
},
.embed_file => |ef_idx| {
const ef = ef_idx.get(zcu);
return writer.print("embed_file('{s}')", .{std.fs.path.fmtJoin(&.{
ef.owner.root.root_dir.path orelse "",
ef.owner.root.sub_path,
ef.sub_file_path.toSlice(ip),
})});
},
.namespace => |ti| {
const info = ti.resolveFull(ip) orelse {
return writer.writeAll("namespace(<lost>)");
Expand Down
Loading
Loading