aboutsummaryrefslogtreecommitdiff
path: root/src/Module.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2024-01-07 00:42:30 +0000
committermlugg <mlugg@mlugg.co.uk>2024-01-23 21:19:53 +0000
commit06d8bb32e3edfb4a26c6d3ecdf198574f4bd3f87 (patch)
treec71cc2ab5af1f24f224031f947c9ac0396df2562 /src/Module.zig
parentae845a33c04fb287ae5a7445743c2b570e40ca1f (diff)
downloadzig-06d8bb32e3edfb4a26c6d3ecdf198574f4bd3f87.tar.gz
zig-06d8bb32e3edfb4a26c6d3ecdf198574f4bd3f87.zip
InternPool: introduce TrackedInst
It is problematic for the cached `InternPool` state to directly reference ZIR instruction indices, as these are not stable across incremental updates. The existing ZIR mapping logic attempts to handle this by iterating the existing Decl graph for a file after `AstGen` and update ZIR indices on `Decl`s, struct types, etc. However, this is unreliable due to generic instantiations, and relies on specialized logic for everything which may refer to a ZIR instruction (e.g. a struct's owner decl). I therefore determined that a prerequisite change for incremental compilation would be to rework how we store these indices. This commit introduces a `TrackedInst` type which provides a stable index (`TrackedInst.Index`) for a single ZIR instruction in the compilation. The `InternPool` now stores these values in place of ZIR instruction indices. This makes the ZIR mapping logic relatively trivial: after `AstGen` completes, we simply iterate all `TrackedInst` values and update those indices which have changed. In future, if the corresponding ZIR instruction has been removed, we must also invalidate any dependencies on this instruction to trigger any required re-analysis, however the dependency system does not yet exist.
Diffstat (limited to 'src/Module.zig')
-rw-r--r--src/Module.zig120
1 files changed, 34 insertions, 86 deletions
diff --git a/src/Module.zig b/src/Module.zig
index 02df2dac67..499eb02f1e 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -834,6 +834,9 @@ pub const File = struct {
multi_pkg: bool = false,
/// List of references to this file, used for multi-package errors.
references: std.ArrayListUnmanaged(Reference) = .{},
+ /// The hash of the path to this file, used to store `InternPool.TrackedInst`.
+ /// undefined until `zir_loaded == true`.
+ path_digest: Cache.BinDigest = undefined,
/// Used by change detection algorithm, after astgen, contains the
/// set of decls that existed in the previous ZIR but not in the new one.
@@ -2594,7 +2597,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
const stat = try source_file.stat();
const want_local_cache = file.mod == mod.main_mod;
- const digest = hash: {
+ const bin_digest = hash: {
var path_hash: Cache.HashHelper = .{};
path_hash.addBytes(build_options.version);
path_hash.add(builtin.zig_backend);
@@ -2603,7 +2606,19 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
path_hash.addBytes(file.mod.root.sub_path);
}
path_hash.addBytes(file.sub_file_path);
- break :hash path_hash.final();
+ var bin: Cache.BinDigest = undefined;
+ path_hash.hasher.final(&bin);
+ break :hash bin;
+ };
+ file.path_digest = bin_digest;
+ const hex_digest = hex: {
+ var hex: Cache.HexDigest = undefined;
+ _ = std.fmt.bufPrint(
+ &hex,
+ "{s}",
+ .{std.fmt.fmtSliceHexLower(&bin_digest)},
+ ) catch unreachable;
+ break :hex hex;
};
const cache_directory = if (want_local_cache) mod.local_zir_cache else mod.global_zir_cache;
const zir_dir = cache_directory.handle;
@@ -2613,7 +2628,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
.never_loaded, .retryable_failure => lock: {
// First, load the cached ZIR code, if any.
log.debug("AstGen checking cache: {s} (local={}, digest={s})", .{
- file.sub_file_path, want_local_cache, &digest,
+ file.sub_file_path, want_local_cache, &hex_digest,
});
break :lock .shared;
@@ -2640,7 +2655,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
// version. Likewise if we're working on AstGen and another process asks for
// the cached file, they'll get it.
const cache_file = while (true) {
- break zir_dir.createFile(&digest, .{
+ break zir_dir.createFile(&hex_digest, .{
.read = true,
.truncate = false,
.lock = lock,
@@ -2826,7 +2841,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
};
cache_file.writevAll(&iovecs) catch |err| {
log.warn("unable to write cached ZIR code for {}{s} to {}{s}: {s}", .{
- file.mod.root, file.sub_file_path, cache_directory, &digest, @errorName(err),
+ file.mod.root, file.sub_file_path, cache_directory, &hex_digest, @errorName(err),
});
};
@@ -2935,89 +2950,22 @@ fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_file: std.fs.File)
return zir;
}
-/// Patch ups:
-/// * Struct.zir_index
-/// * Decl.zir_index
-/// * Fn.zir_body_inst
-/// * Decl.zir_decl_index
-fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
- const gpa = mod.gpa;
- const new_zir = file.zir;
-
- // The root decl will be null if the previous ZIR had AST errors.
- const root_decl = file.root_decl.unwrap() orelse return;
+fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {
+ const gpa = zcu.gpa;
- // Maps from old ZIR to new ZIR, declaration, struct_decl, enum_decl, etc. Any instruction which
- // creates a namespace, and any `declaration` instruction, gets mapped from old to new here.
var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
defer inst_map.deinit(gpa);
- try mapOldZirToNew(gpa, old_zir, new_zir, &inst_map);
-
- // Walk the Decl graph, updating ZIR indexes, strings, and populating
- // the deleted and outdated lists.
-
- var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};
- defer decl_stack.deinit(gpa);
-
- try decl_stack.append(gpa, root_decl);
-
- file.deleted_decls.clearRetainingCapacity();
- file.outdated_decls.clearRetainingCapacity();
-
- // The root decl is always outdated; otherwise we would not have had
- // to re-generate ZIR for the File.
- try file.outdated_decls.append(gpa, root_decl);
-
- const ip = &mod.intern_pool;
-
- while (decl_stack.popOrNull()) |decl_index| {
- const decl = mod.declPtr(decl_index);
- // Anonymous decls and the root decl have this set to 0. We still need
- // to walk them but we do not need to modify this value.
- // Anonymous decls should not be marked outdated. They will be re-generated
- // if their owner decl is marked outdated.
- if (decl.zir_decl_index.unwrap()) |old_zir_decl_index| {
- const new_zir_decl_index = inst_map.get(old_zir_decl_index) orelse {
- try file.deleted_decls.append(gpa, decl_index);
- continue;
- };
- const old_hash = decl.contentsHashZir(old_zir);
- decl.zir_decl_index = new_zir_decl_index.toOptional();
- const new_hash = decl.contentsHashZir(new_zir);
- if (!std.zig.srcHashEql(old_hash, new_hash)) {
- try file.outdated_decls.append(gpa, decl_index);
- }
- }
+ try mapOldZirToNew(gpa, old_zir, file.zir, &inst_map);
- if (!decl.owns_tv) continue;
-
- if (decl.getOwnedStruct(mod)) |struct_type| {
- struct_type.setZirIndex(ip, inst_map.get(struct_type.zir_index) orelse {
- try file.deleted_decls.append(gpa, decl_index);
- continue;
- });
- }
-
- if (decl.getOwnedUnion(mod)) |union_type| {
- union_type.setZirIndex(ip, inst_map.get(union_type.zir_index) orelse {
- try file.deleted_decls.append(gpa, decl_index);
- continue;
- });
- }
-
- if (decl.getOwnedFunction(mod)) |func| {
- func.zirBodyInst(ip).* = inst_map.get(func.zir_body_inst) orelse {
- try file.deleted_decls.append(gpa, decl_index);
- continue;
- };
- }
-
- if (decl.getOwnedInnerNamespace(mod)) |namespace| {
- for (namespace.decls.keys()) |sub_decl| {
- try decl_stack.append(gpa, sub_decl);
- }
- }
+ // TODO: this should be done after all AstGen workers complete, to avoid
+ // iterating over this full set for every updated file.
+ for (zcu.intern_pool.tracked_insts.keys()) |*ti| {
+ if (!std.mem.eql(u8, &ti.path_digest, &file.path_digest)) continue;
+ ti.inst = inst_map.get(ti.inst) orelse {
+ // TODO: invalidate this `TrackedInst` via the dependency mechanism
+ continue;
+ };
}
}
@@ -3494,7 +3442,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
const struct_ty = sema.getStructType(
new_decl_index,
new_namespace_index,
- .main_struct_inst,
+ try mod.intern_pool.trackZir(gpa, file, .main_struct_inst),
) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
};
@@ -4472,7 +4420,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato
};
defer inner_block.instructions.deinit(gpa);
- const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).*);
+ const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).resolve(ip));
// Here we are performing "runtime semantic analysis" for a function body, which means
// we must map the parameter ZIR instructions to `arg` AIR instructions.
@@ -6125,7 +6073,7 @@ pub fn getParamName(mod: *Module, func_index: InternPool.Index, index: u32) [:0]
const tags = file.zir.instructions.items(.tag);
const data = file.zir.instructions.items(.data);
- const param_body = file.zir.getParamBody(func.zir_body_inst);
+ const param_body = file.zir.getParamBody(func.zir_body_inst.resolve(&mod.intern_pool));
const param = param_body[index];
return switch (tags[@intFromEnum(param)]) {