diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2024-01-07 00:42:30 +0000 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2024-01-23 21:19:53 +0000 |
| commit | 06d8bb32e3edfb4a26c6d3ecdf198574f4bd3f87 (patch) | |
| tree | c71cc2ab5af1f24f224031f947c9ac0396df2562 /src/Module.zig | |
| parent | ae845a33c04fb287ae5a7445743c2b570e40ca1f (diff) | |
| download | zig-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.zig | 120 |
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)]) { |
